@aroman22/codegraph-vba 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +131 -3
  2. package/dist/bin/daemon-release.d.ts +7 -0
  3. package/dist/db/queries.d.ts +41 -0
  4. package/dist/extraction/access-erd-extractor.d.ts +57 -0
  5. package/dist/extraction/extraction-version.d.ts +1 -1
  6. package/dist/extraction/grammars.d.ts +20 -0
  7. package/dist/extraction/index.d.ts +1 -0
  8. package/dist/extraction/parse-pool.d.ts +8 -3
  9. package/dist/extraction/sql-query-extractor.d.ts +16 -14
  10. package/dist/extraction/sql-table-scan.d.ts +184 -0
  11. package/dist/extraction/tree-sitter.d.ts +10 -1
  12. package/dist/extraction/vba/call-sweep.d.ts +2 -7
  13. package/dist/extraction/vba/calls.d.ts +24 -4
  14. package/dist/extraction/vba/constants.d.ts +7 -14
  15. package/dist/extraction/vba/context.d.ts +443 -7
  16. package/dist/extraction/vba/controls.d.ts +18 -1
  17. package/dist/extraction/vba/declarations.d.ts +1 -6
  18. package/dist/extraction/vba/dims.d.ts +11 -7
  19. package/dist/extraction/vba/docmd.d.ts +29 -2
  20. package/dist/extraction/vba/enums-consts.d.ts +7 -14
  21. package/dist/extraction/vba/error-channel.d.ts +57 -0
  22. package/dist/extraction/vba/errors.d.ts +64 -0
  23. package/dist/extraction/vba/filesystem-statements.d.ts +23 -0
  24. package/dist/extraction/vba/implements.d.ts +1 -6
  25. package/dist/extraction/vba/labels.d.ts +26 -0
  26. package/dist/extraction/vba/module-vars.d.ts +36 -0
  27. package/dist/extraction/vba/options.d.ts +88 -0
  28. package/dist/extraction/vba/parameters.d.ts +35 -0
  29. package/dist/extraction/vba/procedures.d.ts +1 -9
  30. package/dist/extraction/vba/rules.d.ts +10 -5
  31. package/dist/extraction/vba/runtime-objects.d.ts +59 -0
  32. package/dist/extraction/vba/signature.d.ts +57 -0
  33. package/dist/extraction/vba/sql-wrapper.d.ts +76 -3
  34. package/dist/extraction/vba/text-utils.d.ts +62 -2
  35. package/dist/extraction/vba-extractor.d.ts +18 -1
  36. package/dist/extraction/vba-form-extractor.d.ts +28 -14
  37. package/dist/extraction/vba-preprocess.d.ts +89 -3
  38. package/dist/extraction/vba-source.d.ts +0 -10
  39. package/dist/extraction/vba-test-manifest-extractor.d.ts +0 -6
  40. package/dist/graph/behavior-evidence.d.ts +162 -0
  41. package/dist/index.d.ts +22 -0
  42. package/dist/mcp/daemon-paths.d.ts +6 -0
  43. package/dist/mcp/daemon-registry.d.ts +82 -7
  44. package/dist/mcp/daemon-watchdog.d.ts +12 -0
  45. package/dist/mcp/daemon.d.ts +20 -1
  46. package/dist/mcp/proxy.d.ts +32 -0
  47. package/dist/mcp/server-instructions.d.ts +1 -1
  48. package/dist/mcp/tools.d.ts +12 -0
  49. package/dist/project-config.d.ts +43 -2
  50. package/dist/resolution/index.d.ts +47 -2
  51. package/dist/resolution/name-matcher.d.ts +25 -0
  52. package/dist/resolution/vba-runtime-objects.d.ts +11 -11
  53. package/dist/types.d.ts +13 -5
  54. package/dist/utils/backtrace-helpers.d.ts +14 -2
  55. package/package.json +7 -7
@@ -1,7 +1,68 @@
1
- import { VbaExtractorContext } from './context';
2
- export declare function scanSqlInLine(ctx: VbaExtractorContext, line: string, lineNum: number, dedupe: Set<string>, sqlVariables: Map<string, string>): void;
1
+ import { VbaExtractorContext, ProcInfo } from './context';
3
2
  /**
4
- * #13 fix: `sql = sql & "..."` (self-referential concatenation) must
3
+ * Issue #252 `metadata.synthesizedBy` for a table reached through SQL
4
+ * assigned to a binding property at RUNTIME. Deliberately distinct from the
5
+ * `.form.txt` sweep's static `vba-row-source` so an audit can tell the two
6
+ * provenances apart: one is what the designer stored, the other is what the
7
+ * code actually binds.
8
+ */
9
+ export declare const RUNTIME_BINDING_SYNTHESIZER = "vba-row-source-dynamic";
10
+ /**
11
+ * Issue #244 — the wrapper list used when `codegraph.json` →
12
+ * `vba.sqlWrappers` is absent. A STRICT SUPERSET of the pre-#244 behaviour:
13
+ *
14
+ * - `db` covers every receiver the old `…db\b` regex reached (see
15
+ * {@link receiverMatches} for the suffix arm that preserves it) plus the
16
+ * `db`-prefixed locals it missed (`dbUse`, `dbToUse`, `m_dbLanzadera`).
17
+ * - `getdb` covers the per-backend accessor family (`getdbHPS()`,
18
+ * `getdbExpedientes()`, `getdbLanzadera()`) the issue measured at 26-36%
19
+ * of all execution sites.
20
+ * - `CurrentDb` / `DBEngine` are the Access built-ins.
21
+ * - `qd` / `qdf` are the conventional DAO `QueryDef` receivers.
22
+ * - `DoCmd.RunSQL` is the Access statement form (both the literal and the
23
+ * variable spelling, previously two dedicated regexes).
24
+ * - `Connection.Execute` / `Recordset.Open` are the ADO pair.
25
+ *
26
+ * Project-specific accessors that do not fit these names are added through
27
+ * `vba.sqlWrappers`, which EXTENDS this list rather than replacing it.
28
+ */
29
+ export declare const DEFAULT_SQL_WRAPPERS: readonly string[];
30
+ /** One parsed wrapper entry. Both fields are lowercase — VBA is case-insensitive. */
31
+ interface SqlWrapperMatcher {
32
+ /** Receiver name fragment, e.g. `getdb` from `"getdb"` or `cnn` from `"cnn.Execute"`. */
33
+ receiver: string;
34
+ /** Method name, or `null` for a bare entry (which implies {@link DEFAULT_WRAPPER_METHODS}). */
35
+ method: string | null;
36
+ }
37
+ /**
38
+ * The per-extractor compiled wrapper state. Built ONCE per `VbaExtractor`
39
+ * (see `vba-extractor.ts`) and cached on `VbaExtractorContext` — the two
40
+ * RegExps are stateful (`/g`) and `scanSqlInLine` runs on every line of every
41
+ * file, so re-compiling them per line (which is what the pre-#244 code did)
42
+ * is pure waste on the hottest path in VBA extraction.
43
+ */
44
+ export interface CompiledSqlWrappers {
45
+ /** Literal form — `<receiver>[()].<method> "…"`. Stateful; reset before use. */
46
+ readonly literalRe: RegExp;
47
+ /** Variable form — `<receiver>[()].<method> <identifier>`. Stateful; reset before use. */
48
+ readonly varRe: RegExp;
49
+ /** Parsed entries, defaults first, project entries appended. */
50
+ readonly matchers: readonly SqlWrapperMatcher[];
51
+ }
52
+ /**
53
+ * Parse the plain-string wrapper entries into matchers and pair them with a
54
+ * fresh pair of scanning RegExps.
55
+ *
56
+ * `configured` entries are APPENDED to {@link DEFAULT_SQL_WRAPPERS}, never
57
+ * substituted for them: a project that names its own accessor must not lose
58
+ * `CurrentDb` in the trade. Entries are accepted in exactly two forms — a
59
+ * bare identifier fragment (`"getdb"`) or a `receiver.method` pair
60
+ * (`"cnn.Execute"`). Anything else is dropped here; `project-config.ts`
61
+ * already warned about it at load time.
62
+ */
63
+ export declare function compileSqlWrappers(configured?: readonly string[]): CompiledSqlWrappers;
64
+ /**
65
+ * Issue #13: `sql = sql & "..."` (self-referential concatenation) must
5
66
  * ACCUMULATE the new fragment onto whatever was already tracked for
6
67
  * `varName`, not overwrite it. Overwriting silently dropped earlier
7
68
  * fragments' tables — typically the initial `FROM <table>` in
@@ -14,4 +75,16 @@ export declare function scanSqlInLine(ctx: VbaExtractorContext, line: string, li
14
75
  * behavior is unchanged.
15
76
  */
16
77
  export declare function trackSqlVariableAssignment(lines: string[], lineIndex: number, sqlVariables: Map<string, string>): void;
78
+ /**
79
+ * Issue #255: scan one line for domain-aggregate calls and emit the saved-query
80
+ * reference their second argument names.
81
+ *
82
+ * Deliberately NOT handled: a domain spelled as a full SQL statement
83
+ * (`DLookup("N", "SELECT Id FROM T")`), which Access also accepts. It is
84
+ * rejected by `looksLikeSavedQueryName`'s verb gate and produces nothing —
85
+ * the same silence T11 chose for every literal it cannot classify.
86
+ */
87
+ export declare function scanDomainFunctionsInLine(ctx: VbaExtractorContext, line: string, lineNum: number, dedupe: Set<string>, caller: ProcInfo): void;
88
+ export declare function scanSqlInLine(ctx: VbaExtractorContext, line: string, lineNum: number, dedupe: Set<string>, sqlVariables: Map<string, string>, caller?: ProcInfo): void;
89
+ export {};
17
90
  //# sourceMappingURL=sql-wrapper.d.ts.map
@@ -11,8 +11,17 @@ export declare function foldVisibility(raw: string): 'public' | 'private';
11
11
  * call-site patterns inside `"..."` spans are invisible to CALL_RE and
12
12
  * the statement-form detectors. Column positions are preserved (each
13
13
  * character is replaced 1-for-1) so any col-based metadata stays correct.
14
+ *
15
+ * Issue #265: `filler` selects the replacement character. The default `' '`
16
+ * is what every column-sensitive scanner wants — a masked literal reads as
17
+ * blank space. The statement-call detectors need the opposite: they must
18
+ * still be able to tell `MsgBox "x"` (an argument list, therefore
19
+ * unambiguously a call) from a bare `MsgBox` read, and a space-masked line
20
+ * collapses the two. Passing `'_'` keeps the literal *visible* as an opaque
21
+ * token while preserving both the 1-for-1 column mapping and the property
22
+ * that nothing inside the literal can be parsed as VBA syntax.
14
23
  */
15
- export declare function maskStringContent(line: string): string;
24
+ export declare function maskStringContent(line: string, filler?: string): string;
16
25
  /**
17
26
  * #13 helper: escape a variable name for safe interpolation into the
18
27
  * self-reference RegExp built by `trackSqlVariableAssignment`. VBA
@@ -26,7 +35,6 @@ export declare function parseConstDeclarations(body: string): Array<{
26
35
  asType: string;
27
36
  value: string | null;
28
37
  }>;
29
- export declare function splitOutsideVbaStrings(value: string, separator: string): string[];
30
38
  /**
31
39
  * Issue #50 helper: return true iff `line.charAt(fromIndex..)` (after the
32
40
  * matched TempVars site, including any trailing whitespace) holds an `=`
@@ -65,4 +73,56 @@ export declare function parseEventHandlerName(name: string): {
65
73
  controlName: string;
66
74
  eventName: string;
67
75
  } | null;
76
+ /**
77
+ * Issue #247 helper: parse a FORM-LEVEL Access event-handler Sub name.
78
+ *
79
+ * `parseEventHandlerName` above deliberately refuses `Form_*` because a
80
+ * form-level event fires on the form object, not on a control — routing it
81
+ * through the control path would synthesize a bogus `form-instance-control`
82
+ * node literally named `Form`. This helper is the other half of that
83
+ * decision: it recognises exactly the names the control parser rejects and
84
+ * hands them to the caller so they can be wired to the sibling
85
+ * `form-layout` / `report-layout` node instead.
86
+ *
87
+ * The owner segment must be exactly `Form` or `Report` (case-insensitive,
88
+ * matching VBA's case-insensitive identifiers), and the suffix must be a
89
+ * known Access event name. That second gate is what separates the real
90
+ * lifecycle handler `Form_Load` from an ordinary class method that merely
91
+ * happens to be called `Form_Helper`.
92
+ */
93
+ export declare function parseFormLevelEventHandlerName(name: string): {
94
+ ownerName: string;
95
+ eventName: string;
96
+ } | null;
97
+ /**
98
+ * Sibling layout extension implied by an Access code-behind prefix.
99
+ * `Form_*` binds to a `.form.txt`; `Report_*` binds to a `.report.txt`.
100
+ */
101
+ export type CodeBehindExt = '.form.txt' | '.report.txt';
102
+ /**
103
+ * Issue #249 helper: the `Form_` / `Report_` code-behind prefix carried by a
104
+ * module's RESOLVED `Attribute VB_Name`, used as the fallback when the file
105
+ * on disk does not carry the prefix in its basename.
106
+ *
107
+ * Both binding sites (the event-handler synthesis in `procedures.ts` and the
108
+ * `Me.<Control>` sweep in `controls.ts`) keep their own basename check as the
109
+ * fast path and consult this only on a basename miss, so the common case —
110
+ * a Dysflow export whose filename and `VB_Name` agree — is byte-for-byte
111
+ * unchanged.
112
+ *
113
+ * `classNamePrefix` is `null` for `.bas` modules and holds the resolved
114
+ * `VB_Name` (or the extension-stripped basename when the module carries no
115
+ * `VB_Name`) for `.cls` modules, so this fallback can only ever fire for a
116
+ * class module. That is deliberate: the guard that keeps a plain service
117
+ * class such as `InformeRiesgoPDFServicio.cls` — whose methods
118
+ * (`GenerarHTML_Principal`, `GetEstilosCSS_PDF`) look like event handlers to
119
+ * a naive `<X>_<Y>` split — from synthesizing hundreds of spurious
120
+ * `form-instance-control` stubs is that NEITHER its filename NOR its
121
+ * `VB_Name` starts with `Form_` / `Report_`. Widening the prefix test would
122
+ * reopen exactly that hole, so the test stays the same canonical Access
123
+ * naming convention; only the string it is applied to widens.
124
+ *
125
+ * The trailing `.+` is load-bearing: a bare `Form_` names no form.
126
+ */
127
+ export declare function codeBehindExtFromVbName(classNamePrefix: string | null): CodeBehindExt | null;
68
128
  //# sourceMappingURL=text-utils.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { ExtractionResult } from '../types';
2
+ import type { VbaExtractionOptions } from './vba/options';
2
3
  import type { VbaExtractionRule } from './vba/rules';
3
4
  /**
4
5
  * Issue #152: per-file fanout cap for `RaiseEvent <EventName>` edges. An
@@ -62,7 +63,6 @@ export declare class VbaExtractor {
62
63
  private filePath;
63
64
  private source;
64
65
  private ctx;
65
- private vbaTargets?;
66
66
  /**
67
67
  * Issue #152: per-file fanout cap for `RaiseEvent <EventName>` edges.
68
68
  * When a single event is raised more than this many times in one file,
@@ -72,6 +72,23 @@ export declare class VbaExtractor {
72
72
  * the orchestrator: callers that don't pass a value get the default.
73
73
  */
74
74
  private maxRaiseFanout;
75
+ /**
76
+ * Issue #243: the full options object, retained so classifiers that grow
77
+ * a new knob read it from one place instead of a new private field per
78
+ * knob. `targets`/`maxRaiseFanout` keep their dedicated fields because
79
+ * they are read on hot paths.
80
+ */
81
+ private options;
82
+ /**
83
+ * Issue #243 — the options-object form. Every in-repo call site uses this.
84
+ */
85
+ constructor(filePath: string, source: string, options?: VbaExtractionOptions);
86
+ /**
87
+ * @deprecated Pass a {@link VbaExtractionOptions} object instead:
88
+ * `new VbaExtractor(filePath, source, { targets, maxRaiseFanout })`.
89
+ * The positional 3rd/4th parameters are kept working for one release so
90
+ * out-of-repo callers are not broken by #243; they will be removed after.
91
+ */
75
92
  constructor(filePath: string, source: string, vbaTargets?: Record<string, boolean>, maxRaiseFanout?: number);
76
93
  extract(): ExtractionResult;
77
94
  /**
@@ -17,6 +17,14 @@ export declare class VbaFormExtractor {
17
17
  * stays self-contained per the project's per-extractor state rule).
18
18
  */
19
19
  private synthClassNodeIds;
20
+ /**
21
+ * Issue #256: external-backend (`IN "<path>"`) node ids already emitted
22
+ * for this form. Same per-extractor de-dup discipline as
23
+ * `synthClassNodeIds`, keyed on the node id — which is derived from the
24
+ * NORMALIZED backend path, so two bindings naming the same `.accdb`
25
+ * share one node while each still emits its own edge.
26
+ */
27
+ private externalBackendNodeIds;
20
28
  constructor(filePath: string, source: string);
21
29
  extract(): ExtractionResult;
22
30
  private createFileNode;
@@ -63,23 +71,11 @@ export declare class VbaFormExtractor {
63
71
  private emitExpressionHandlers;
64
72
  private emitExpressionHandler;
65
73
  private emitSourceObjectReference;
66
- /**
67
- * Issue #49 — copy of `VbaExtractor.SQL_TABLE_RE`. Same source / flags:
68
- * captures the table name that follows `FROM`/`JOIN`/`INTO`/`UPDATE`,
69
- * tolerates bracketed `[Order Details]` identifiers and `\p{L}` Unicode
70
- * identifiers, and allows an optional schema prefix (`[dbo].[tblA]`).
71
- *
72
- * We duplicate the regex (rather than exporting it from `VbaExtractor`)
73
- * because `VbaExtractor.SQL_TABLE_RE` is `private static` and the
74
- * project's per-extractor state rule keeps each extractor's helpers
75
- * self-contained — see the file-level JSDoc on `VbaExtractor`.
76
- */
77
- private static readonly SQL_TABLE_RE;
78
74
  /**
79
75
  * Issue #49 — classify a RecordSource/RowSource value as SQL.
80
76
  * Anything starting with a SQL keyword (`SELECT`, `PARAMETERS`, `WITH`,
81
77
  * `UPDATE`, `INSERT`, `DELETE`) — case-insensitive — is treated as a
82
- * SQL statement and run through `SQL_TABLE_RE`. Anything else is a
78
+ * SQL statement and run through `scanSqlTables`. Anything else is a
83
79
  * bare table-or-query name and emitted as a single reference.
84
80
  *
85
81
  * `WITH` is included because Access/JET supports CTE-style `WITH` queries
@@ -103,6 +99,20 @@ export declare class VbaFormExtractor {
103
99
  * name — same dual-match `vba-sql-impact`'s `extractFormBindings` does).
104
100
  */
105
101
  private emitTableReference;
102
+ /**
103
+ * Issue #256 — emit one `references` edge from `sourceNodeId` to the
104
+ * external database file an Access `IN "<path>"` clause points at.
105
+ *
106
+ * The target node is built by the shared `buildExternalBackendNode` so
107
+ * it is byte-identical to the node the in-code SQL sweep and the saved-
108
+ * query extractor emit for the same path: the graph ends up with ONE
109
+ * node per external backend, however many places name it. The edge
110
+ * carries only `synthesizedBy` — the `external` / `backendPath` facts
111
+ * describe the node, and live there.
112
+ *
113
+ * `backendPath` is already normalized by `scanSqlExternalBackends`.
114
+ */
115
+ private emitExternalBackendReference;
106
116
  /**
107
117
  * Link a bound control to its enclosing form/report's single bare table.
108
118
  * Expressions and SQL/absent RecordSource values stay metadata-only: column
@@ -112,7 +122,7 @@ export declare class VbaFormExtractor {
112
122
  private emitControlSourceReference;
113
123
  /**
114
124
  * Issue #49 — dispatch the value of a RecordSource/RowSource binding.
115
- * If SQL, run `SQL_TABLE_RE` over the value and emit one edge per
125
+ * If SQL, run `scanSqlTables` over the value and emit one edge per
116
126
  * distinct table (within-value dedup so `FROM tblA JOIN tblA` emits a
117
127
  * single edge). If a bare name, emit a single edge with the name as-is
118
128
  * — the resolver handles the dual-match against `query` and `class`
@@ -122,6 +132,10 @@ export declare class VbaFormExtractor {
122
132
  * the regex capture already includes them as part of the value, so a
123
133
  * single `.replace(/""/g, '"')` collapses them — same technique the
124
134
  * `extractStringLiterals` helper uses for its emitted `text` field.
135
+ *
136
+ * Issue #203: `scanSqlTables` also drops SQL reserved-word captures
137
+ * (`WHERE`, `ORDER`, `SET`, …) so a malformed SQL string never
138
+ * poisons the graph with phantom `class` nodes.
125
139
  */
126
140
  private emitBinding;
127
141
  }
@@ -32,6 +32,7 @@
32
32
  *
33
33
  * Source unchanged guarantee: every helper treats `src` as read-only.
34
34
  */
35
+ import type { ExtractionError } from '../types';
35
36
  /**
36
37
  * Collapse VBA line continuations: any line whose last non-newline character is
37
38
  * `_` (preceded by exactly one space, per VBA convention) is joined to the
@@ -93,8 +94,36 @@ export declare function stripVbaComments(src: string): string;
93
94
  * promote this evaluator to full integer arithmetic.
94
95
  *
95
96
  * Directives and inactive branch lines are replaced with empty strings so
96
- * downstream extraction keeps source-line parity. Unsupported/unsafe
97
- * expressions evaluate to false rather than throwing.
97
+ * downstream extraction keeps source-line parity.
98
+ *
99
+ * **Issue #206 contract change.** Previously the docstring read
100
+ * *"Unsupported/unsafe expressions evaluate to false rather than
101
+ * throwing"* — accurate, but it framed silent whole-branch source
102
+ * deletion as a safety property. It was not: false-negative extraction is
103
+ * indistinguishable from correct extraction to every downstream consumer
104
+ * (an `#If MODE = "DEBUG" Then` whose `"` throws silently blanks the
105
+ * branch and the symbol vanishes from the graph).
106
+ *
107
+ * The new contract:
108
+ *
109
+ * 1. **Unterminated `#If`/`#ElseIf`**: if the directive stack is
110
+ * non-empty after the loop ends, an `ExtractionError` (severity
111
+ * `warning`, code `unterminated_if`) is pushed onto the optional
112
+ * `errors` array AND the un-blanked lines from the earliest
113
+ * unclosed `#If` are re-emitted. An unterminated `#If` is far
114
+ * more likely a typo than a genuinely dead file tail.
115
+ * 2. **Could-not-evaluate expressions**: `evaluateConditionalExpression`
116
+ * now distinguishes `evaluated: false` (a definitive false — e.g.
117
+ * `#If 0 Then`, unknown identifier falling through to 0) from
118
+ * `could-not-evaluate` (the lexer/parser hit an unsupported token,
119
+ * or `tokenize`/`parse` threw). On could-not-evaluate, the branch
120
+ * is **kept active** (conservative for a code-intelligence tool —
121
+ * a spurious symbol is recoverable, a missing one is invisible)
122
+ * and a warning is emitted through the same `errors` channel.
123
+ *
124
+ * The `errors` channel is OPTIONAL — every existing call site (and
125
+ * existing test) continues to work without it. The orchestrator can
126
+ * supply it to surface the warnings through `ctx.errors`.
98
127
  */
99
128
  export declare function preprocessConditionalCompilation(src: string, customTargets?: Record<string, boolean>,
100
129
  /**
@@ -106,7 +135,64 @@ export declare function preprocessConditionalCompilation(src: string, customTarg
106
135
  * inner sub-totals. Undefined → no-op, zero cost (just the
107
136
  * `if (timings)` null-check on every CC line).
108
137
  */
109
- timings?: Map<string, number> | null): string;
138
+ timings?: Map<string, number> | null,
139
+ /**
140
+ * Issue #206: optional `ExtractionError` sink for diagnostics the
141
+ * preprocessor wants to surface to extraction callers:
142
+ * - `unterminated_if` (warning) — `#If` / `#ElseIf` pushed but
143
+ * never closed by `#End If` (a likely typo).
144
+ * - `unparseable_expression` (warning) — a `#If` / `#ElseIf`
145
+ * expression the lexer/parser could not handle (the branch is
146
+ * still kept active under the conservative contract).
147
+ *
148
+ * Undefined → no-op, zero cost (the `if (errors)` null-check on
149
+ * every warning site). The orchestrator passes `this.ctx.errors` so
150
+ * the warnings route through `ExtractionResult.errors`.
151
+ */
152
+ errors?: ExtractionError[],
153
+ /**
154
+ * Issue #206: optional file path included on every pushed error
155
+ * when the caller wants telemetry to know which file the warning
156
+ * came from. Independent of `errors` so a caller can attach the
157
+ * path even when collecting warnings elsewhere.
158
+ */
159
+ filePath?: string): string;
160
+ /**
161
+ * Issue #206 discriminated return value for `evaluateConditionalExpression`.
162
+ *
163
+ * - `{ kind: 'evaluated', value }` — the lexer+parser produced a
164
+ * definitive answer. `value` is the truthiness of the expression
165
+ * under VBA semantics (non-zero = true).
166
+ * - `{ kind: 'could-not-evaluate' }` — the lexer/parser threw
167
+ * (unsupported token: string literal, floating-point literal,
168
+ * unrecognized character, mismatched paren, etc.). The caller MUST
169
+ * treat this as ACTIVE under the conservative contract from
170
+ * Issue #206 — see `preprocessConditionalCompilation`.
171
+ */
172
+ export type ConditionalEvaluation = {
173
+ readonly kind: 'evaluated';
174
+ readonly value: boolean;
175
+ } | {
176
+ readonly kind: 'could-not-evaluate';
177
+ };
178
+ /**
179
+ * Issue #206: marker error class for explicitly-unsupported VBA
180
+ * conditional-compilation tokens. The legacy `throw new Error(...)`
181
+ * from `tokenize` was indistinguishable from a real bug
182
+ * (`Unexpected trailing token`, mismatched paren); both bubbled into
183
+ * the same `catch { return false }`. The new helper carries the
184
+ * specific reason so the caller can attribute warnings precisely and
185
+ * distinguish "the lexer simply cannot handle this token" from a
186
+ * legitimate parse failure (which is also routed to
187
+ * could-not-evaluate, but with the original message preserved).
188
+ */
189
+ export declare class UnparseableExpressionError extends Error {
190
+ /** Coarse category: `string_literal`, `float_literal`, `unsupported_character`. */
191
+ readonly reason: string;
192
+ /** The verbatim fragment that triggered the throw (for diagnostics). */
193
+ readonly fragment: string;
194
+ constructor(reason: string, fragment: string);
195
+ }
110
196
  export interface StringLiteralSpan {
111
197
  /** The literal content (no surrounding quotes; `""` collapsed to `"`). */
112
198
  text: string;
@@ -45,14 +45,4 @@ export interface ReadVbaSourceResult {
45
45
  * @param opts optional dependencies; see {@link ReadVbaSourceOptions}.
46
46
  */
47
47
  export declare function readVbaSource(filePath: string, opts?: ReadVbaSourceOptions): ReadVbaSourceResult;
48
- /**
49
- * String-level BOM strip — survives when the upstream `fsp.readFile(path, 'utf-8')`
50
- * already decoded the bytes (it's the only way the `\uFEFF` char survives in
51
- * a string). Used as a defensive last resort when a read site has NOT gone
52
- * through `readVbaSource` and the source text starts with the BOM marker.
53
- *
54
- * Most call sites should prefer `readVbaSource` for fresh reads; this is
55
- * a string post-process for callers that already have a `string` in hand.
56
- */
57
- export declare function stripUtf8Bom(text: string): string;
58
48
  //# sourceMappingURL=vba-source.d.ts.map
@@ -1,10 +1,4 @@
1
1
  import { ExtractionResult } from '../types';
2
- /**
3
- * Content-shape gate: is `parsed` a VBA test manifest — a top-level `tests`
4
- * array with at least one item carrying a string `procedure`? Pure; the file's
5
- * basename is gated separately by `isVbaTestManifestFile` in `grammars.ts`.
6
- */
7
- export declare function isVbaTestManifestShape(parsed: unknown): boolean;
8
2
  export declare class VbaTestManifestExtractor {
9
3
  private filePath;
10
4
  private source;
@@ -0,0 +1,162 @@
1
+ import { QueryBuilder } from '../db/queries';
2
+ /**
3
+ * The consumer compatibility payload.
4
+ *
5
+ * Field types are fixed by the consumer that reads them (Dysflow's
6
+ * `CodeGraphBehaviorEvidence`) and must not be widened here.
7
+ */
8
+ export interface CodeGraphBehaviorEvidence {
9
+ /** Name of the procedure the event is bound to. */
10
+ handler: string;
11
+ /**
12
+ * One root-to-leaf execution path, handler first, callees in the order the
13
+ * graph stores them (by node id, so the output is stable across runs).
14
+ * Distinct branches are separate entries — they are never concatenated into
15
+ * a single sequence the runtime would not take. A path that re-enters a
16
+ * procedure already on it ends there, repeating that name once.
17
+ */
18
+ callPath: string[];
19
+ /** Tables reached along this path, directly or through a saved query. */
20
+ tables?: string[];
21
+ /** See {@link BEHAVIOR_EFFECT_VOCABULARY}. */
22
+ effects?: string[];
23
+ }
24
+ /**
25
+ * The closed vocabulary of `effects` strings. Anything outside this list is a
26
+ * bug, not a new fact.
27
+ *
28
+ * - `read:<name>` / `write:<name>` — a data reference the extractor typed.
29
+ * - `data-access:<name>` — a data reference whose direction is unknown.
30
+ * NOT a read and NOT a write; it means the index cannot say.
31
+ * - `opens-form:<Name>` / `opens-report:<Name>` — an Access object opened.
32
+ * - `raises-event:<Name>` — an event raised from the path.
33
+ */
34
+ export declare const BEHAVIOR_EFFECT_VOCABULARY: readonly ["read:<name>", "write:<name>", "data-access:<name>", "opens-form:<Name>", "opens-report:<Name>", "raises-event:<Name>"];
35
+ /** How a handler is bound to the thing it answers for. */
36
+ export type BehaviorBindingScope =
37
+ /** `<Control>_<Event>` code-behind on a control. */
38
+ 'control'
39
+ /** The form's or report's own lifecycle event. */
40
+ | 'form'
41
+ /** An `=Expression()` property on the control or layout. */
42
+ | 'expression'
43
+ /** The request named a procedure directly; no binding was involved. */
44
+ | 'direct';
45
+ export interface BehaviorEvidenceRequest {
46
+ /** Stable node id. The unambiguous form — always prefer it. */
47
+ nodeId?: string;
48
+ /** Control, handler or layout name. Requires `layout` when not unique. */
49
+ name?: string;
50
+ /**
51
+ * Layout context for `name`: a form/report name or its layout file name.
52
+ * Without it an ambiguous name is REFUSED, never silently narrowed to the
53
+ * first match.
54
+ */
55
+ layout?: string;
56
+ /** Call-path depth budget. Default 5, clamped to 1..20. */
57
+ maxCallDepth?: number;
58
+ /** Maximum evidence entries. Default 50, clamped to 1..500. */
59
+ maxResults?: number;
60
+ }
61
+ export interface BehaviorEvidenceTarget {
62
+ id: string;
63
+ name: string;
64
+ kind: string;
65
+ filePath: string;
66
+ /** Owning form/report, from the layout's `contains` edge. */
67
+ layout: string | null;
68
+ }
69
+ export interface BehaviorHandlerContext {
70
+ handler: string;
71
+ handlerId: string;
72
+ /** Access event name (`Click`, `Load`, …), or null when not bound. */
73
+ event: string | null;
74
+ scope: BehaviorBindingScope;
75
+ provenance: string;
76
+ /** `file:line` of the wiring site. */
77
+ location: string;
78
+ /** `metadata.synthesizedBy` of the binding edge, when present. */
79
+ wiredBy: string | null;
80
+ }
81
+ export interface BehaviorDataEvidence {
82
+ /** Procedure the reference was attributed to. */
83
+ procedure: string;
84
+ name: string;
85
+ /** Node kind of the referenced object (`class`, `table`, `query`, …). */
86
+ targetKind: string;
87
+ access: 'read' | 'write' | 'unknown';
88
+ /** `metadata.synthesizedBy` of the reference edge. */
89
+ via: string | null;
90
+ /** Set when the table was reached through a saved query. */
91
+ throughQuery?: string;
92
+ /**
93
+ * `edge-source` — the reference edge starts at the procedure itself.
94
+ * `source-line` — it starts at the module node and was attributed to this
95
+ * procedure because the edge's line falls inside its range.
96
+ */
97
+ attributedBy: 'edge-source' | 'source-line';
98
+ location: string;
99
+ }
100
+ export interface BehaviorAmbiguity {
101
+ name: string;
102
+ matches: Array<{
103
+ id: string;
104
+ kind: string;
105
+ filePath: string;
106
+ layout: string | null;
107
+ }>;
108
+ }
109
+ export interface BehaviorUnresolvedReference {
110
+ /** Procedure the unresolved reference sits in, when it could be attributed. */
111
+ procedure: string | null;
112
+ referenceName: string;
113
+ referenceKind: string;
114
+ location: string;
115
+ }
116
+ export interface BehaviorEvidenceResult {
117
+ /** What the request resolved to, or null when it resolved to nothing. */
118
+ target: BehaviorEvidenceTarget | null;
119
+ /** The consumer compatibility payload. */
120
+ evidence: CodeGraphBehaviorEvidence[];
121
+ context: {
122
+ handlers: BehaviorHandlerContext[];
123
+ data: BehaviorDataEvidence[];
124
+ unresolved: BehaviorUnresolvedReference[];
125
+ /** Populated only when a name matched more than one node. */
126
+ ambiguous: BehaviorAmbiguity[];
127
+ truncated: {
128
+ /** A path hit `maxCallDepth` and was cut short. */
129
+ callDepth: boolean;
130
+ /** Entries were dropped at `maxResults`. */
131
+ results: boolean;
132
+ /** A path re-entered a procedure already on it. */
133
+ cycle: boolean;
134
+ };
135
+ /** See {@link BEHAVIOR_EVIDENCE_NOTES}. */
136
+ notes: string[];
137
+ };
138
+ }
139
+ /**
140
+ * The closed vocabulary of `context.notes`.
141
+ *
142
+ * - `MISSING_TARGET_SELECTOR` — neither `nodeId` nor `name` was given.
143
+ * - `TARGET_NOT_FOUND` — nothing in the index matches. A lookup miss, not
144
+ * proof the control has no behavior.
145
+ * - `AMBIGUOUS_TARGET` — the name matches several nodes; see
146
+ * `context.ambiguous`. No evidence is returned, and nothing is guessed.
147
+ * - `NO_HANDLER_BOUND` — the target exists but no handler is wired to it in
148
+ * the index. Access macros and `[Event Procedure]` entries with no
149
+ * code-behind land here.
150
+ * - `STATIC_SOURCE_EVIDENCE` — always present. The answer comes from indexed
151
+ * exported source: it does not prove the code ran, and it says nothing
152
+ * about whether the `.accdb` binary matches the export.
153
+ */
154
+ export declare const BEHAVIOR_EVIDENCE_NOTES: readonly ["MISSING_TARGET_SELECTOR", "TARGET_NOT_FOUND", "AMBIGUOUS_TARGET", "NO_HANDLER_BOUND", "STATIC_SOURCE_EVIDENCE"];
155
+ /**
156
+ * Assembles behavior evidence for one control, layout or handler.
157
+ *
158
+ * Pure read path over {@link QueryBuilder}: no new SQL, no second traversal
159
+ * implementation, no writes.
160
+ */
161
+ export declare function buildBehaviorEvidence(queries: QueryBuilder, request: BehaviorEvidenceRequest): BehaviorEvidenceResult;
162
+ //# sourceMappingURL=behavior-evidence.d.ts.map
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * knowledge graph from any codebase.
6
6
  */
7
7
  import { Node, Edge, FileRecord, ExtractionResult, Subgraph, TraversalOptions, SearchOptions, SearchResult, SegmentMatch, Context, GraphStats, TaskInput, TaskContext, BuildContextOptions, FindRelevantContextOptions } from './types';
8
+ import { BehaviorEvidenceRequest, BehaviorEvidenceResult } from './graph/behavior-evidence';
8
9
  import { IndexProgress, IndexResult, SyncResult } from './extraction';
9
10
  import { ResolutionResult } from './resolution';
10
11
  import { WatchOptions, PendingFile } from './sync';
@@ -16,6 +17,7 @@ export { IndexProgress, IndexResult, SyncResult } from './extraction';
16
17
  export { detectLanguage, isLanguageSupported, isGrammarLoaded, getSupportedLanguages, initGrammars, loadGrammarsForLanguages, loadAllGrammars } from './extraction';
17
18
  export { ResolutionResult } from './resolution';
18
19
  export { CodeGraphError, FileError, ParseError, DatabaseError, SearchError, VectorError, ConfigError, Logger, setLogger, getLogger, silentLogger, defaultLogger, } from './errors';
20
+ export { buildBehaviorEvidence, BEHAVIOR_EFFECT_VOCABULARY, BEHAVIOR_EVIDENCE_NOTES, CodeGraphBehaviorEvidence, BehaviorEvidenceRequest, BehaviorEvidenceResult, BehaviorEvidenceTarget, BehaviorHandlerContext, BehaviorDataEvidence, BehaviorAmbiguity, BehaviorUnresolvedReference, BehaviorBindingScope, } from './graph/behavior-evidence';
19
21
  export { Mutex, FileLock, processInBatches, debounce, throttle, MemoryMonitor } from './utils';
20
22
  export { FileWatcher, WatchOptions, PendingFile, LockUnavailableError } from './sync';
21
23
  export { MCPServer } from './mcp';
@@ -527,6 +529,26 @@ export declare class CodeGraph {
527
529
  * @returns Subgraph containing potentially impacted nodes
528
530
  */
529
531
  getImpactRadius(nodeId: string, maxDepth?: number): Subgraph;
532
+ /**
533
+ * Assemble bounded behavior evidence for an Access control, layout or
534
+ * handler (issue #299).
535
+ *
536
+ * Read-only: it joins facts that are already indexed — the event binding,
537
+ * the call paths under it, and the tables/effects those procedures reach —
538
+ * into one typed payload, so a consumer does not re-implement the graph
539
+ * semantics for itself. It never parses source, opens Access, or writes to
540
+ * the index.
541
+ *
542
+ * Identify the target by `nodeId` whenever you have one. A `name` needs a
543
+ * `layout` as soon as it is not unique: an ambiguous name is REFUSED with
544
+ * the candidates listed, never narrowed to an arbitrary first match.
545
+ *
546
+ * The answer is static evidence from exported source. An empty `tables` or
547
+ * `effects` list means the index holds no such fact — not that the code has
548
+ * no runtime effect. See {@link BehaviorEvidenceResult.context} for what
549
+ * could not be answered.
550
+ */
551
+ getBehaviorEvidence(request: BehaviorEvidenceRequest): BehaviorEvidenceResult;
530
552
  /**
531
553
  * Find the shortest path between two nodes
532
554
  *
@@ -52,6 +52,12 @@ export declare function getDaemonSocketCandidates(projectRoot: string): string[]
52
52
  export declare function getDaemonSocketPath(projectRoot: string): string;
53
53
  /** Absolute path to the daemon pid lockfile for `projectRoot`. */
54
54
  export declare function getDaemonPidPath(projectRoot: string): string;
55
+ /** Root-scoped lease held while intentional release owns lifecycle cleanup. */
56
+ export declare function getDaemonReleaseLeasePath(projectRoot: string): string;
57
+ /** Exclusive marker for release-lease recovery/heartbeat publication. */
58
+ export declare function getDaemonReleaseRecoveryPath(projectRoot: string): string;
59
+ /** Short root-scoped arbitration lock shared by startup and release publication. */
60
+ export declare function getDaemonLifecyclePath(projectRoot: string): string;
55
61
  /** Structured contents of the pid lockfile. */
56
62
  export interface DaemonLockInfo {
57
63
  pid: number;