Summary
Make error handlers addressable in the graph: one label node per line label, and a handles-error edge from the procedure to the handler it routes to.
The error-policy work (#259) records whether a procedure has a handler; the handler-region work (#260) marks which edges come from inside one. Neither gives the handler an identity you can point at, search for, or traverse to. This does.
What the corpus contains
Measured across 00_EXPEDIENTES, 00_GESTION_RIESGOS, HPS_SOLICITUDES:
| |
|---|
| Label definitions | 3,912 |
…that are On Error GoTo targets | 3,776 (96.5%) |
…that are pure control flow (siguiente, salir, fin, Teardown) | 136 |
On Error GoTo <label> statements | 3,850 |
Procedures with more than one On Error statement | 337 |
Plain GoTo <label> jumps | ~450 |
| Dangling targets (referenced, never defined) | 1 |
Label names are procedure-scoped in VBA and heavily repeated: errores alone is defined 3,735 times. The qualified name must include the owning procedure or every handler in the codebase collides.
Prerequisite
Land #259 first. Its classifier already computes everything this issue needs — label definitions, On Error GoTo targets, handler-region boundaries, dangling-target resolution. This issue adds no parsing. It is emission only, reading state #259 already carries on ProcInfo.
That is why it is cheap to build and why building it before #259 would mean writing the same scanner twice.
Design
Node
New NodeKind value 'label'.
id generateNodeId(filePath, 'label', name, lineNum)
kind 'label'
name the label as written (e.g. 'errores')
qualifiedName '<ModuleOrClass>.<Procedure>.<label>' // mandatory — see collision note above
filePath the .bas / .cls
startLine the label definition line
endLine handler labels: the procedure's End Sub/Function/Property
control-flow labels: same as startLine
metadata {
isHandler: boolean, // true when an On Error GoTo targets it
handlerBehavior?: 'channel' | 'display' | 'reraise' | 'mixed' | 'unknown',
regionStartLine?: number, // handler labels only
regionEndLine?: number
}
handlerBehavior is the value #260 already derives for errorPolicy.behavior. Copy it; do not re-classify.
Edges
| Edge | Source | Target | Kind | Tag |
|---|
| procedure owns its label | function | label | contains | — |
| procedure routes errors here | function | label | handles-error (new) | synthesizedBy: 'vba-error-handler' |
| procedure jumps here | function | label | references | synthesizedBy: 'vba-goto' |
Only one new EdgeKind. Plain GoTo reuses the generic references kind — 450 sites do not justify a second kind, and the synthesizer tag keeps them filterable.
handles-error is not deduplicated per procedure: 337 procedures issue more than one On Error GoTo, and each statement is a distinct routing decision with its own line number. Emit one edge per statement.
Dangling targets
On Error GoTo X where X: is never defined in that procedure emits an UnresolvedReference (referenceKind: 'references', synthesizedBy: 'vba-goto-unresolved'). Never fabricate a node for a label that does not exist — a graph that invents its own targets cannot be used to find this defect, which is the whole point. Exactly 1 site in the corpus.
What this deliberately does NOT do
Calls inside a handler stay attributed to the enclosing procedure. Do not re-parent them onto the label node.
Re-parenting would change callers / callees results for 3,774 procedures and silently break every existing consumer query that expects a call to belong to the procedure that contains it. #260's inErrorHandler: true flag already answers "did this call come from the error path". The label node is addressable, not a container.
Integration points
Verified against the current tree:
src/types.ts — add 'label' to the NODE_KINDS array and 'handles-error' to the EdgeKind union, each with a JSDoc block in the style of the existing VBA entries.src/context/index.tsHIGH_VALUE_NODE_KINDS — do not add 'label'. That array is the default node filter for context results; adding labels would push ~3,900 of them into every default context response.src/mcp/tools.tsCONTAINER_NODE_KINDS — do not add 'label'. Container kinds get their body expanded in explore output; a handler label would print the tail of every procedure.src/search/query-parser.ts — derives KIND_VALUES from NODE_KINDS automatically, so kind:label becomes a valid search filter with no code change. Add a test that pins it.- Database —
nodes.kind and edges.kind are plain TEXT with no CHECK constraint (src/db/schema.sql). No migration needed. docs/index-schema.md — auto-generated; regenerate with npm run schema:dump.EXTRACTION_VERSION — bump. A new node kind and a new edge kind change extraction output, so existing indexes should be rebuilt.
Node budget
On the three-project corpus:
+3,912 label nodes
+3,912 contains edges
+3,850 handles-error edges
+ 450 references edges (vba-goto)
------
~+3,900 nodes, ~+8,200 edges
Against a post-#245 baseline of roughly 13,000 nodes that is about +30% nodes. Budget it deliberately: this is the largest single node addition in the roadmap, and the reason the HIGH_VALUE_NODE_KINDS and CONTAINER_NODE_KINDS exclusions above are not optional.
What it buys over #259 and #260
- A stable id per handler, so tooling and other issues can reference one directly
kind:label search, and handlers visible in codegraph_explore listings- Dangling and duplicate label detection as a graph query rather than a scan
- The 136 pure control-flow labels become visible for the first time
Ordering
After #259 and #260. Before #262 if the published queries should be able to select handlers by node rather than by procedure metadata.
Acceptance criteria
Context
Task E6 of docs/vba-error-handling-plan.md. That document's §4 argues the cheaper model (#259, #260) covers most of the value at zero node cost — read it for the trade-off, then build this on top of it rather than instead of it.
Summary
Make error handlers addressable in the graph: one
labelnode per line label, and ahandles-erroredge from the procedure to the handler it routes to.The error-policy work (#259) records whether a procedure has a handler; the handler-region work (#260) marks which edges come from inside one. Neither gives the handler an identity you can point at, search for, or traverse to. This does.
What the corpus contains
Measured across
00_EXPEDIENTES,00_GESTION_RIESGOS,HPS_SOLICITUDES:On Error GoTotargetssiguiente,salir,fin,Teardown)On Error GoTo <label>statementsOn ErrorstatementGoTo <label>jumpsLabel names are procedure-scoped in VBA and heavily repeated:
erroresalone is defined 3,735 times. The qualified name must include the owning procedure or every handler in the codebase collides.Prerequisite
Land #259 first. Its classifier already computes everything this issue needs — label definitions,
On Error GoTotargets, handler-region boundaries, dangling-target resolution. This issue adds no parsing. It is emission only, reading state #259 already carries onProcInfo.That is why it is cheap to build and why building it before #259 would mean writing the same scanner twice.
Design
Node
New
NodeKindvalue'label'.handlerBehavioris the value #260 already derives forerrorPolicy.behavior. Copy it; do not re-classify.Edges
functionlabelcontainsfunctionlabelhandles-error(new)synthesizedBy: 'vba-error-handler'functionlabelreferencessynthesizedBy: 'vba-goto'Only one new
EdgeKind. PlainGoToreuses the genericreferenceskind — 450 sites do not justify a second kind, and the synthesizer tag keeps them filterable.handles-erroris not deduplicated per procedure: 337 procedures issue more than oneOn Error GoTo, and each statement is a distinct routing decision with its own line number. Emit one edge per statement.Dangling targets
On Error GoTo XwhereX:is never defined in that procedure emits anUnresolvedReference(referenceKind: 'references',synthesizedBy: 'vba-goto-unresolved'). Never fabricate a node for a label that does not exist — a graph that invents its own targets cannot be used to find this defect, which is the whole point. Exactly 1 site in the corpus.What this deliberately does NOT do
Calls inside a handler stay attributed to the enclosing procedure. Do not re-parent them onto the label node.
Re-parenting would change
callers/calleesresults for 3,774 procedures and silently break every existing consumer query that expects a call to belong to the procedure that contains it. #260'sinErrorHandler: trueflag already answers "did this call come from the error path". The label node is addressable, not a container.Integration points
Verified against the current tree:
src/types.ts— add'label'to theNODE_KINDSarray and'handles-error'to theEdgeKindunion, each with a JSDoc block in the style of the existing VBA entries.src/context/index.tsHIGH_VALUE_NODE_KINDS— do not add'label'. That array is the default node filter for context results; adding labels would push ~3,900 of them into every default context response.src/mcp/tools.tsCONTAINER_NODE_KINDS— do not add'label'. Container kinds get their body expanded in explore output; a handler label would print the tail of every procedure.src/search/query-parser.ts— derivesKIND_VALUESfromNODE_KINDSautomatically, sokind:labelbecomes a valid search filter with no code change. Add a test that pins it.nodes.kindandedges.kindare plainTEXTwith noCHECKconstraint (src/db/schema.sql). No migration needed.docs/index-schema.md— auto-generated; regenerate withnpm run schema:dump.EXTRACTION_VERSION— bump. A new node kind and a new edge kind change extraction output, so existing indexes should be rebuilt.Node budget
On the three-project corpus:
Against a post-#245 baseline of roughly 13,000 nodes that is about +30% nodes. Budget it deliberately: this is the largest single node addition in the roadmap, and the reason the
HIGH_VALUE_NODE_KINDSandCONTAINER_NODE_KINDSexclusions above are not optional.What it buys over #259 and #260
kind:labelsearch, and handlers visible incodegraph_explorelistingsOrdering
After #259 and #260. Before #262 if the published queries should be able to select handlers by node rather than by procedure metadata.
Acceptance criteria
'label'inNODE_KINDS,'handles-error'inEdgeKind, both documented insrc/types.tslabelnode per label definition;qualifiedNameincludes the owning procedureerroresproduce two distinct nodes with distinct ids and distinct qualified namesisHandler: trueplus its region lines; a control-flow label carriesisHandler: falseand no regioncontainsedge from the owningfunctionnode for every labelhandles-erroredge perOn Error GoTostatement — a procedure with two such statements produces two edgesGoToproduces areferencesedge taggedvba-gotoOn Error GoTo noExisteproduces anUnresolvedReferenceand no nodecallers/calleesoutput for a sample of 10 procedures with handlers is byte-identical to before this change (no re-parenting)'label'absent fromHIGH_VALUE_NODE_KINDSand fromCONTAINER_NODE_KINDS, asserted by testkind:labelworks as a search filter, asserted by testnpm run schema:dumpre-run and the regenerated doc committedEXTRACTION_VERSIONbumpedContext
Task E6 of
docs/vba-error-handling-plan.md. That document's §4 argues the cheaper model (#259, #260) covers most of the value at zero node cost — read it for the trade-off, then build this on top of it rather than instead of it.