@mnemonica/tactica 0.3.0 → 0.3.5

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.
package/README.md CHANGED
@@ -138,7 +138,7 @@ Tactica writes everything under the `--output` directory (default `.tactica/`):
138
138
  | `flow.json` | always | Native instance-flow patterns (property reads/writes, method calls, destructuring, returns, spreads, etc.) per type. |
139
139
  | `instrumentation.json` | always | v2 envelope. `points`: framework lifecycle crossroads (interceptors, guards, pipes, filters, middleware) detected via **plugin-supplied vocabulary** — heritage declarations, decorator sites, provider-token registrations, `consumer.apply()` wiring. Syntactic only — no dive dependency; with no plugins loaded, `points` is `[]`. `creationGraph`: the inside-out walk from every instantiation site out to the starters — see "Creation graph" below. |
140
140
  | `modules.json` | always | Module-scope graph: every module's `exportedBindings`/`importedBindings` (functions, classes, consts, types — not only mnemonica types), project-internal `dependencies`, `builtinSpecifiers` (Node builtins are skipped entirely — both `'path'` and `'node:path'` forms), `unresolvedSpecifiers`, circular-import `cycles`, and cross-module mnemonica-type `edges`. Resolution uses `ts.resolveModuleName` with the project's compilerOptions (tsconfig `paths`, extensionless imports, index files) — no type checker. Bindings resolved into `node_modules` are marked `external: true` and never enter `dependencies`. |
141
- | `scopes.json` | always | Local-scope graph: function/method/arrow scopes only (no block scopes) plus one module scope per file; variables with `typePath` (mnemonica type when known), `isParameter`, `isMutable`, and `reassignments` — each reassignment of a mutable binding is a flow-termination point. |
141
+ | `scopes.json` | always | Local-scope graph: function/method/arrow scopes only (no block scopes) plus one module scope per file; variables with `typePath` (mnemonica type when known), `isParameter`, `isMutable`, and `reassignments` — each reassignment of a mutable binding is a flow-termination point. `lookup()` initializers resolve through the same tier law as the analyzer (scope-chain receiver first, then the analyzer's source-relative/root law), so scopes metadata never disagrees with the hard-fail verdicts. |
142
142
  | `eds.json` | when EDS enabled | Execution-flow patterns (`wrap`, `current`, `getFlow`, `attachHooks` lifecycle wiring). Consumed by tools that visualize execution chains. |
143
143
 
144
144
  ### Default mode (types.ts + registry.ts)
@@ -471,6 +471,131 @@ The analyzer infers property types from constructor bodies and class members. Su
471
471
 
472
472
  When inference fails the property's type falls back to `unknown` (or `any` in some Topologica paths) — safe, and you can refine manually.
473
473
 
474
+ ### Referenced types in constructor signatures
475
+
476
+ A constructor parameter that references a named type (`record: SharedShape`) resolves **through the importing file's own import statements** — relative and tsconfig-paths imports are followed to the declaration (chasing re-export barrels), and imported classes/interfaces/type aliases with extractable fields are expanded inline. An import is never shadowed by a same-named declaration elsewhere in the program. A reference that resolves not at all (a ghost name, declared nowhere) emits `unknown` — generated `.tactica/types.ts` carries no import statements of its own, so a bare unresolvable name is never emitted. A reference that resolves *ambiguously* — several of your project's files declare the name and no import chooses between them — is **fatal** like the mnemonica-graph identity law below: tactica prints the reference site and every declaration site, exits non-zero, and writes no output. An import anchoring the reference is never ambiguity (the import wins, duplicate declarations or not), and package-declared same-named types never count — a user-local declaration always wins over a node_modules `.d.ts`. Two same-named interfaces in one file are legal TypeScript merging, not ambiguity (see Known Limitations for the last-wins caveat).
477
+
478
+ **Ambiguous plain-TypeScript reference — fails, and how to fix it.** Two plain interfaces named `SharedShape` exist in different files, and a third file references a bare `SharedShape` with nothing anchoring it:
479
+
480
+ ```ts
481
+ // a.ts // b.ts
482
+ export interface SharedShape { export interface SharedShape {
483
+ a: string; b: number;
484
+ } }
485
+
486
+ // consumer.ts — FATAL: which SharedShape?
487
+ import { define } from 'mnemonica';
488
+
489
+ export const Crate = define('Crate', function (this: Crate, data: SharedShape) {
490
+ this.item = data;
491
+ });
492
+ ```
493
+
494
+ ```
495
+ tactica: Ambiguous reference to type 'SharedShape': 2 declarations share the name and no import disambiguates — import the one you mean
496
+ at src/consumer.ts:6:67
497
+ at /abs/path/src/a.ts:1:1
498
+ at /abs/path/src/b.ts:1:1
499
+ tactica: aborting — 1 resolution failure(s); no .tactica output written
500
+ ```
501
+
502
+ Fix it by importing the declaration you mean — the file's own import wins over every other tier:
503
+
504
+ ```ts
505
+ // consumer.ts — OK: the import anchors SharedShape → a.ts
506
+ import { define } from 'mnemonica';
507
+ import { SharedShape } from './a';
508
+
509
+ export const Crate = define('Crate', function (this: Crate, data: SharedShape) {
510
+ this.item = data; // item: { a: string }
511
+ });
512
+ ```
513
+
514
+ Qualified references through a namespace import resolve one level per segment: `models.SharedShape` finds the declaration in the namespace import's module; multi-level paths (`holders.Inner.Crate`, `barrel.Deep.Gadget`) descend through namespace declarations, `export * as ns from '…'` barrels, and named re-exports of namespaces — each segment disambiguates, so two namespaces may declare the same name without colliding. What is *not* followed: ambient string-named modules (`declare module '…'`), default-export shapes behind a namespace segment, and type-only constructs with no extractable fields (unions of literals, mapped types) — those emit `unknown` like any other unresolvable reference.
515
+
516
+ References to **mnemonica graph types** (types declared via `define()`, as opposed to plain TypeScript declarations) follow the runtime lookup law instead: the file's own value bindings and imports anchor the name first, then the defining type's own chain relative-first, then its collection's roots, then a program-wide search that must be unique. Same-namespace duplicate definitions (which the mnemonica runtime rejects with `ALREADY_DECLARED`) and graph references that stay ambiguous or unresolved are fatal: tactica prints every location, exits non-zero, and writes no `.tactica` output at all.
517
+
518
+ **Ambiguous reference — fails, and how to fix it.** Two types named `Token` exist under different parents, and a third file references a bare `Token` with nothing anchoring it:
519
+
520
+ ```ts
521
+ // defs.ts
522
+ export const Holder = define('Holder', function (this: Holder) { /* … */ });
523
+ export const Token = Holder.define('Token', function (this: Token, data: { mark: string }) { /* … */ });
524
+
525
+ // other.ts
526
+ export const Other = define('Other', function (this: Other) { /* … */ });
527
+ Other.define('Token', function (this: Token, data: { hue: string }) { /* … */ });
528
+
529
+ // consumer.ts — FATAL: which Token?
530
+ export const Crate = define('Crate', function (this: Crate, data: Token) {
531
+ this.item = data;
532
+ });
533
+ ```
534
+
535
+ ```
536
+ tactica: Ambiguous reference to mnemonica type 'Token': 2 types share the name and neither the parent chain nor the imports disambiguate
537
+ at src/consumer.ts:7:67
538
+ at src/defs.ts:8:29
539
+ at src/other.ts:9:7
540
+ tactica: aborting — 1 resolution failure(s); no .tactica output written
541
+ ```
542
+
543
+ Fix it by anchoring the reference — either import the constructor you mean (the import wins over every other tier), or use the full generated name:
544
+
545
+ ```ts
546
+ // consumer.ts — OK: the import anchors Token → Holder.Token
547
+ import { Token } from './defs';
548
+ export const Crate = define('Crate', function (this: Crate, data: Token) {
549
+ this.item = data;
550
+ });
551
+
552
+ // or — OK: fully-qualified generated type name
553
+ export const Crate = define('Crate', function (this: Crate, data: Holder_Token) {
554
+ this.item = data;
555
+ });
556
+ ```
557
+
558
+ **Duplicate in one namespace — fails, and there is only one fix.** Two types with the same name under the same parent (or two same-named roots) are what the mnemonica runtime rejects with `ALREADY_DECLARED`; tactica fails the generation instead of emitting a wrong graph:
559
+
560
+ ```ts
561
+ // dup-a.ts // dup-b.ts
562
+ export const A = define( export const B = define(
563
+ 'DupRoot', …); 'DupRoot', …);
564
+ ```
565
+
566
+ ```
567
+ tactica: Duplicate definition of 'DupRoot' in one namespace — the mnemonica runtime would throw ALREADY_DECLARED
568
+ at src/dup-a.ts:3:22
569
+ at src/dup-b.ts:6:23
570
+ tactica: aborting — 1 resolution failure(s); no .tactica output written
571
+ ```
572
+
573
+ Rename one of them. Same-named types under **different** parents (`User.Address` and `Company.Address`) are legal mnemonica and never reported.
574
+
575
+ **Unresolved `lookup()` path — fails, with a did-you-mean.** Literal `lookup()` paths are validated against the complete graph after analysis. A path matching no type is what the runtime answers with `undefined` — the `TypeError` arrives one line later at the `new` — so it is fatal too, and same-named types elsewhere in the graph are listed as candidates:
576
+
577
+ ```ts
578
+ // consumer.ts — FATAL: no root 'Token'; Holder.Token and Other.Token exist
579
+ const TokenCtor = lookup('Token');
580
+ export const instance = new TokenCtor({ mark: 'x' });
581
+ ```
582
+
583
+ ```
584
+ tactica: Unresolved lookup of mnemonica type 'Token': the runtime would return undefined — 2 graph type(s) carry the name off-root (Holder.Token, Other.Token); use the full dotted path
585
+ at src/consumer.ts:6:19
586
+ at src/defs.ts:8:29
587
+ at src/other.ts:9:7
588
+ tactica: aborting — 1 resolution failure(s); no .tactica output written
589
+ ```
590
+
591
+ ```ts
592
+ // OK: dotted absolute path, or receiver-relative resolution
593
+ const ByPath = lookup('Holder.Token');
594
+ const ByReceiver = Holder.lookup('Token');
595
+ ```
596
+
597
+ Non-literal lookup arguments (computed strings) stay best-effort and are not validated.
598
+
474
599
  ## API Reference
475
600
 
476
601
  ### `MnemonicaAnalyzer`
@@ -491,6 +616,7 @@ class MnemonicaAnalyzer {
491
616
  getEDSUsages(): Map<string, EDSInfo[]>;
492
617
  getFlowUsages(): Map<string, FlowInfo[]>;
493
618
  getInstrumentationPoints(): InstrumentationPoint[];
619
+ getResolutionErrors(): ResolutionError[]; // fatal duplicate/ambiguous graph + plain-TS references (hard-fail law)
494
620
  }
495
621
  ```
496
622
 
@@ -658,6 +784,7 @@ When enabled, tactica detects execution-flow patterns alongside type definitions
658
784
  - `scopeId` — the scopeId of the scope holding the wrap call site (fallback join).
659
785
  - `wrapsTypePath` — the mnemonica fullPath of the instance argument, resolved through the scope-variable chain (innermost binding wins; an untyped local shadows a typed outer one rather than being guessed).
660
786
  - `via` — the location of the enclosing wrap site when the call is nested inside another wrapped body (or returns a function): the wrappers-graph generation chain is built from it.
787
+ - `scope` — the enclosing mnemonica type path. Lexical scoping (the owning `define()`/`lazy()` handler or `@decorate()`-ed class) wins; a wrap site outside any handler — the fire-and-forget-wrapper shape — is attributed through its instance/context argument (a tracked assignment like `const holder = new Holder(…)`, or the enclosing function's parameter annotation resolved through the graph law), and nested sites (function-valued returns, lexically nested wraps) inherit the causing wrap site's scope down the `via` chain. Sites attributable to nothing stay under the `unknown` key with `scope` absent.
661
788
 
662
789
  ## Instrumentation Points
663
790
 
@@ -681,6 +808,9 @@ interface TacticaPlugin {
681
808
  useDecorators?: Record<string, InstrumentationKind>;
682
809
  // `{ provide: TOKEN, useClass: Impl }` registrations: token identifier -> kind
683
810
  appTokens?: Record<string, InstrumentationKind>;
811
+ // factory calls inside listed decorators' args: factory method name ->
812
+ // kind + which factory-call argument holds the target class (default 0)
813
+ decoratorArgFactories?: Record<string, { kind: InstrumentationKind; targetArg?: number }>;
684
814
  // opt in to shape-based `consumer.apply(Mw).forRoutes(...)` detection
685
815
  middlewareWiring?: boolean;
686
816
  }
@@ -689,7 +819,8 @@ interface TacticaPlugin {
689
819
  Programmatic callers pass plugins directly: `new MnemonicaAnalyzer(program, plugins)` or `run({ …, plugins })`; config-file plugins append after programmatic ones, and later plugins override earlier ones on the same identifier key. Detection covers:
690
820
 
691
821
  - **Heritage** — `class X implements <plugin-listed interface>` (matched by interface identifier name). Bare declarations carry scope `module` (attachment statically unknown).
692
- - **Decorator sites** — plugin-listed decorators on classes or methods, including inline instances (`@Register(new Impl(…))`). One point per referenced class; scope is `controller:<Name>` or `method:<Class>.<method>`.
822
+ - **Decorator sites** — plugin-listed decorators on classes, methods, or method parameters, including inline instances (`@Register(new Impl(…))`). One point per referenced class; scope is `controller:<Name>` or `method:<Class>.<method>` (parameter-decorated sites take the enclosing method's scope — the attachment is the handler, not the argument name).
823
+ - **Decorator-arg factories** — a `CallExpression` argument of the form `<anything>.<factory>(…)` inside a listed decorator's args (class, method, or parameter position), e.g. `@UsePipes(mvp.forType(Dto))` and the parameter form `@Body(mvp.forType(Entity, Dto))`: when `<factory>` is plugin-listed, a point of the configured `kind` is emitted with the class in `targetArg` (default 0) as the target. Factory calls whose method name is not listed are ignored, as are listed factories whose configured position is not an identifier. Points resolve `location`/`code` to the target's declaration and dedupe exactly like other decorator sites.
693
824
  - **Global providers** — `{ provide: <plugin-listed token>, useClass: X }` object literals → scope `global`. `useExisting`/`useFactory` without `useClass` are skipped.
694
825
  - **Middleware wiring** — `consumer.apply(Mw).forRoutes(...)` inside a class's `configure()` method → scope `module`, targets from `forRoutes` arguments when statically readable. Requires `middlewareWiring: true` from any loaded plugin.
695
826
 
@@ -806,7 +937,19 @@ Mnemonica accepts the `exposeInstanceMethods` option at runtime, but tactica's `
806
937
 
807
938
  ### Single-pass analysis without binding
808
939
 
809
- The analyzer does not use `ts.Program.getTypeChecker()` for resolution, so cross-file type references in unusual shapes may resolve to `unknown`.
940
+ The analyzer does not use `ts.Program.getTypeChecker()` for resolution. Plain TypeScript references in constructor signatures resolve through the importing file's import statements first, then its local declarations, and only then a unique same-named declaration across the scanned files — so a reference whose name is genuinely absent (or lives behind unresolvable specifiers) resolves to `unknown`, while a name several of your own files declare with no import to disambiguate fails hard (see above). References to mnemonica graph types follow the path-aware lookup law described above and fail hard instead of degrading to `unknown`.
941
+
942
+ ### Same-file interface merging is last-wins
943
+
944
+ Two same-named interfaces in ONE module are legal TypeScript declaration merging, and tactica does not treat them as ambiguity (the fatal class is same-named declarations across several files, unanchored by an import). Resolution keeps a single declaration per name per file, and it is the last one — members from earlier declarations are not combined:
945
+
946
+ ```ts
947
+ // merged.ts — resolves to { b: number }; the `a: string` member is not picked up
948
+ export interface SharedShape { a: string; }
949
+ export interface SharedShape { b: number; }
950
+ ```
951
+
952
+ **Workaround:** declare the merged shape once, or split the interfaces under distinct names and import the one you mean.
810
953
 
811
954
  ### Custom collection name conflicts
812
955
 
package/lib/analyzer.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { AnalyzeResult, DefinitionInfo, UsageInfo, EDSInfo, FlowInfo, InstrumentationPoint } from './types';
2
+ import { AnalyzeResult, DefinitionInfo, UsageInfo, EDSInfo, FlowInfo, InstrumentationPoint, ResolutionError } from './types';
3
3
  import { TypeGraphImpl } from './graph';
4
4
  import { TacticaPlugin } from './plugins';
5
5
  /**
@@ -20,7 +20,6 @@ export declare class MnemonicaAnalyzer {
20
20
  private functionBindings;
21
21
  private nestedWrapVia;
22
22
  private wrapEntryByNode;
23
- private typeAliases;
24
23
  private variableToTypeMap;
25
24
  private moduleObjectVariables;
26
25
  private createTypesCollectionVariables;
@@ -30,6 +29,26 @@ export declare class MnemonicaAnalyzer {
30
29
  private instrumentationClassDecls;
31
30
  private instrumentationSites;
32
31
  private instrumentationVocabulary;
32
+ private referencedTypeDecls;
33
+ private referencedTypeImports;
34
+ private referencedTypeReExports;
35
+ private referencedTypeExportStars;
36
+ private referencedTypeExportAliases;
37
+ private referencedTypeNamespaces;
38
+ private referencedTypeNamespaceStars;
39
+ private referencedTypeResolutionCache;
40
+ private referencedTypeCompilerOptions;
41
+ private currentReferencedTypeFile;
42
+ private expandingReferencedAliases;
43
+ private defineSites;
44
+ private graphReferenceErrors;
45
+ private lookupReferencesValidated;
46
+ private lookupReferences;
47
+ private plainTypeReferencesValidated;
48
+ private plainTypeReferences;
49
+ private fileGraphBindings;
50
+ private currentGraphAnchor;
51
+ private processedCalls;
33
52
  constructor(program?: ts.Program, plugins?: TacticaPlugin[]);
34
53
  /**
35
54
  * Reset usage-related state for a fresh pass.
@@ -93,6 +112,196 @@ export declare class MnemonicaAnalyzer {
93
112
  * createTypesCollection are recognized without relying on the type checker.
94
113
  */
95
114
  private trackImports;
115
+ /**
116
+ * Record a named referenced-type declaration (type alias, class, or
117
+ * interface) for the file currently being visited.
118
+ */
119
+ private trackReferencedTypeDeclaration;
120
+ /**
121
+ * Record the importing file's named/namespace/default import bindings so
122
+ * referenced-type names resolve through the file's own import statements
123
+ * (F10) rather than a program-wide name map.
124
+ */
125
+ private trackReferencedTypeImport;
126
+ /**
127
+ * Record re-export wiring (`export { X } from '…'`, `export * from '…'`,
128
+ * `export { X as Y }`) so resolution can chase barrels to the origin
129
+ * module. Mirrors ModuleGraphBuilder.resolveOrigin, name-based only.
130
+ */
131
+ private trackReferencedTypeReExport;
132
+ /**
133
+ * Resolve a module specifier from a containing file with the program's
134
+ * compilerOptions (tsconfig `paths`, extensionless imports, index files).
135
+ * Module resolution only — the no-getTypeChecker() precedent stays.
136
+ */
137
+ private resolveReferencedTypeModule;
138
+ /**
139
+ * Look up a name in one resolved module, chasing re-export barrels with a
140
+ * bounded depth. External (node_modules) modules hold no in-project
141
+ * declarations and stop the chase.
142
+ */
143
+ private findReferencedTypeInModule;
144
+ /**
145
+ * Resolve a referenced type name as used in fromFile, import-aware:
146
+ * 1. the file's own import statements (relative + tsconfig paths,
147
+ * chased through re-export barrels),
148
+ * 2. the file's local declarations,
149
+ * 3. the unique same-named declaration across scanned files.
150
+ * Returns undefined when nothing matches (or the match is ambiguous),
151
+ * in which case the caller falls back to `unknown`.
152
+ */
153
+ private resolveReferencedTypeDeclaration;
154
+ /**
155
+ * External/ambient declaration files (.d.ts, anything under
156
+ * node_modules) never participate in plain-TS referenced-type
157
+ * resolution or the ambiguity law: they are not project source, the
158
+ * CLI never analyzes them, and a user-local declaration always wins
159
+ * over a package-declared same-named type.
160
+ */
161
+ private isExternalDeclFile;
162
+ /**
163
+ * Properties of a referenced class/interface/alias-of-literal declaration,
164
+ * shared by `this:`-parameter expansion and inline type emission.
165
+ */
166
+ private referencedDeclarationProperties;
167
+ /**
168
+ * Expand a referenced-type declaration to a self-contained type string
169
+ * for emission into generated files: type aliases through inferType,
170
+ * classes and interfaces through their (public, non-method) fields.
171
+ * Nested references resolve against the declaring file while expanding.
172
+ */
173
+ private expandReferencedTypeDeclaration;
174
+ private expandReferencedTypeDeclarationInner;
175
+ /**
176
+ * Resolve a simple (non-qualified) type reference: import-aware
177
+ * declaration expansion first, then the InstanceType<typeof X> pattern,
178
+ * then mnemonica graph types; known globals keep their bare name and
179
+ * anything else falls back to `unknown` so generated files never carry
180
+ * an unresolvable bare name. Returns undefined when the caller should
181
+ * keep the generic spelling (handled separately).
182
+ */
183
+ private resolveSimpleTypeReference;
184
+ /**
185
+ * Resolve a qualified type reference (models.Inner.Crate) through the
186
+ * current file's namespace imports. The chain's head must be a namespace
187
+ * import; middle segments descend through namespace declarations, named
188
+ * re-exports of namespaces, and `export * as ns from '…'` barrels (each
189
+ * segment consumed exactly once, so the walk cannot cycle); the final
190
+ * segment resolves to a declaration which is expanded inline. When the
191
+ * precise walk finds nothing, the legacy rightmost-name lookup in the
192
+ * head module keeps one-level forms (models.Type) working — nested
193
+ * declarations are recorded by plain name there too. Returns undefined
194
+ * when the head is not a namespace import or nothing resolves.
195
+ */
196
+ private inferQualifiedTypeReference;
197
+ /**
198
+ * Find a namespace declaration by name directly inside a module block.
199
+ */
200
+ private findNamespaceInBlock;
201
+ /**
202
+ * Find a named type declaration (alias, class, interface) directly inside
203
+ * a namespace block — the final segment of a descended qualified chain.
204
+ */
205
+ private findReferencedTypeInBlock;
206
+ /**
207
+ * Fallback for a type-reference name that resolves to no declaration and
208
+ * no graph type: known globals keep their bare name (they resolve without
209
+ * an import); everything else becomes `unknown` so generated types.ts
210
+ * never carries an unresolvable bare name (README's documented behavior)
211
+ * and the site is recorded for the plain-TS ambiguity validation.
212
+ */
213
+ private unresolvedTypeReferenceFallback;
214
+ /**
215
+ * Record one define()/lazy()/@decorate() site under its runtime
216
+ * namespace key. Two sites in one namespace are a same-namespace
217
+ * duplicate (the runtime throws ALREADY_DECLARED); every site is kept
218
+ * so the failure can report all locations.
219
+ */
220
+ private recordDefineSite;
221
+ /**
222
+ * Fatal resolution failures (hard-fail law): same-namespace duplicate
223
+ * mnemonica definitions plus ambiguous/unresolved mnemonica-graph
224
+ * references. The CLI prints every location and writes no output.
225
+ */
226
+ getResolutionErrors(): ResolutionError[];
227
+ /**
228
+ * Resolve a reference to a mnemonica graph type name, import-aware and
229
+ * path-aware (the hard-fail identity law, mirroring the runtime):
230
+ * 1. value scope — a tracked top-level binding in the referencing file
231
+ * (`const Address = User.define('Address', …)`),
232
+ * 2. import scope — a binding exported from a module this file imports
233
+ * (barrels chased),
234
+ * 3. nearest-chain — the anchor type's own subtypes first, then each
235
+ * ancestor level (relative-first),
236
+ * 4. root — roots of the anchor's collection,
237
+ * 5. program-wide — only when exactly one type carries the name.
238
+ * Ambiguity (several candidates and nothing disambiguates) and absence
239
+ * are both returned as such — the caller records a hard failure; a bare
240
+ * first-match name is never emitted.
241
+ */
242
+ private resolveGraphTypeName;
243
+ /**
244
+ * Find a graph constructor binding exported by a resolved module,
245
+ * chasing re-export barrels with a bounded depth.
246
+ */
247
+ private findGraphBindingInModule;
248
+ /**
249
+ * Validate literal lookup() paths recorded during the usages pass
250
+ * against the complete graph. A lookup path matching no type is what the
251
+ * runtime answers with `undefined` — the TypeError arrives one line
252
+ * later at the `new` — so it joins the hard-fail law. The relative-first
253
+ * step already ran inside resolveLookupPath; whatever was recorded is
254
+ * the root-resolution result, so a plain findType check is the exact
255
+ * runtime law. Same-named types elsewhere in the graph are listed as
256
+ * did-you-mean candidates. Runs once per usages pass (re-armed by
257
+ * resetUsages); non-literal lookup arguments are never recorded and
258
+ * stay best-effort.
259
+ */
260
+ private validateLookupReferences;
261
+ /**
262
+ * Record a plain-TS type reference site that resolved to nothing and
263
+ * fell back to `unknown`, for the lazily-run ambiguity validation.
264
+ * Deduped by (name, location): inferType can visit the same node more
265
+ * than once per pass (constructor params + property inference).
266
+ */
267
+ private recordPlainTypeReferenceSite;
268
+ /**
269
+ * Project-source declaration files carrying `name` — one entry per
270
+ * file, so same-file interface merging counts once (not ambiguous).
271
+ * External/ambient declarations (.d.ts, anything under node_modules)
272
+ * never count: a user-local declaration always wins over a package-
273
+ * declared same-named type, so an external collision stays soft.
274
+ */
275
+ private plainTypeDeclarationFiles;
276
+ /**
277
+ * Validate plain-TS type reference sites recorded during the usages
278
+ * pass against the complete declaration map. A name declared in
279
+ * several project-source files — with no import in the referencing
280
+ * file to anchor it — is ambiguous: silently emitting `unknown` would
281
+ * hide a real type the author meant, so it joins the hard-fail law
282
+ * (the plain-TS tier of the same identity law as graph references).
283
+ * Absence (ghost names) and external collisions stay soft `unknown`.
284
+ * Runs once per usages pass (re-armed by resetUsages), mirroring
285
+ * validateLookupReferences: recording happens on every pass, but only
286
+ * the usages pass sees the complete declaration map.
287
+ */
288
+ private validatePlainTypeReferences;
289
+ /**
290
+ * `file:line:column` of a recorded declaration, for the ambiguity
291
+ * report. Nodes recorded during traversal keep their positions; a
292
+ * synthetic/unpositioned node falls back to the file itself.
293
+ */
294
+ private plainDeclLocation;
295
+ /**
296
+ * Record a hard-fail graph reference error with the reference site and
297
+ * every candidate location.
298
+ */
299
+ private recordGraphReferenceError;
300
+ /**
301
+ * Location (`file:line:column`) of an AST node, derived without parent
302
+ * pointers when necessary.
303
+ */
304
+ private nodeLocation;
96
305
  /**
97
306
  * Track aliases of the mnemonica module object, e.g.:
98
307
  * const m = mnemonica;
@@ -203,6 +412,12 @@ export declare class MnemonicaAnalyzer {
203
412
  * For chained calls like const X = define('A').define('B'), we map X -> A (the root type)
204
413
  */
205
414
  private trackVariableAssignment;
415
+ /**
416
+ * Mirror a variable -> mnemonica fullPath binding into the per-file
417
+ * value-scope map (graph identity law: `typeof X` and bare references
418
+ * resolve through the file's own bindings first).
419
+ */
420
+ private trackFileGraphBinding;
206
421
  /**
207
422
  * Track variable assignments from lookup() calls
208
423
  * e.g., const SentienceConstructor = lookup('Sentience') maps "SentienceConstructor" -> "Sentience"
@@ -255,6 +470,15 @@ export declare class MnemonicaAnalyzer {
255
470
  * collection.lookup('User.Admin')
256
471
  */
257
472
  private resolveLookupPath;
473
+ /**
474
+ * Lookup-law delegate for the local-scope walker (scopes.json typePath
475
+ * metadata): resolve a lookup() initializer call through exactly the
476
+ * tiers the usages pass resolved it against (same source resolution,
477
+ * same complete graph). The walker runs its own scope-chain value-scope
478
+ * tier before delegating; everything above value scope lands here, so
479
+ * scopes.json never disagrees with the hard-fail-law verdicts.
480
+ */
481
+ resolveLookupCallPath(call: ts.CallExpression): string | undefined;
258
482
  /**
259
483
  * Find a parent type by its name, searching in the graph.
260
484
  * When collectionId is provided, only types from that collection are considered.
@@ -338,10 +562,6 @@ export declare class MnemonicaAnalyzer {
338
562
  * Infer return type by analyzing return statements in the method body
339
563
  */
340
564
  private inferReturnTypeFromBody;
341
- /**
342
- * Get full text from a qualified name (e.g., Namespace.Type)
343
- */
344
- private getQualifiedNameText;
345
565
  /**
346
566
  * Infer type from initializer
347
567
  */
@@ -373,6 +593,24 @@ export declare class MnemonicaAnalyzer {
373
593
  * returns undefined for calls outside any type scope (module top level).
374
594
  */
375
595
  private resolveEDSScope;
596
+ /**
597
+ * Resolve a wrap site's instance/context argument to a mnemonica type
598
+ * path — the fire-and-forget-wrapper attribution fallback when the call
599
+ * sits outside any define()/lazy() handler: a tracked assignment
600
+ * (`const holder = new Holder(...)`), else the root identifier's
601
+ * (property-access roots included) parameter annotation resolved
602
+ * through the graph law. Ambiguity or absence stays silent — this is a
603
+ * metadata heuristic, not the identity-law surface.
604
+ */
605
+ private resolveWrapInstanceTypePath;
606
+ /**
607
+ * Resolve a bare-identifier type annotation of the nearest enclosing
608
+ * function's parameter through the mnemonica-graph tiers (value scope,
609
+ * imports, roots, program-wide-unique). Non-identifier and generic
610
+ * annotations are not graph references; ambiguity and absence yield
611
+ * undefined.
612
+ */
613
+ private resolveParameterAnnotationTypePath;
376
614
  /**
377
615
  * Resolve a wrap() argument to its function node without the type
378
616
  * checker: direct function expressions/arrows, or same-file bindings
@@ -397,6 +635,8 @@ export declare class MnemonicaAnalyzer {
397
635
  * site (`via` = the site whose wrapping caused it) and recurse into
398
636
  * its own returns. Returns through identifiers resolve through the
399
637
  * same-file bindings table; unresolvable returns are simply skipped.
638
+ * A return declared outside any type scope inherits the causing wrap
639
+ * site's scope attribution (the generation chain is the only holder).
400
640
  */
401
641
  private recordWrappedReturn;
402
642
  /**