@mnemonica/tactica 0.3.0 → 0.3.7

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, **inherited members included**: `extends` chains are walked (depth-bounded, cycle-guarded) and parent fields merge with the declaration's own fields overriding on name clash. A field typed by a `typeof` query over a module-level const array (`status?: typeof statusList[number]`) expands to the element literal union (`status?: 'active' | 'closed'`); any other `typeof` source degrades that field to `unknown` — a bare `typeof name` is never emitted, since the generated file has no imports to resolve it against. 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,21 @@ 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
+ Two expansion boundaries stay name-based and deliberate: **heritage** — `extends` chains are walked through the same import-aware machinery (depth-capped, cycle-guarded), but bases that resolve to an external/node_modules declaration or to a non-identifier expression (mixin calls like `extends mixin(X)`) are not followed — their inherited fields simply stay absent, same as before the walk existed. And **`typeof` fields** — only module-level const arrays with statically visible literal elements expand to the literal union (cross-file only when the const is imported by the declaring file); every other `typeof` source degrades the field to `unknown`.
943
+
944
+ ### Same-file interface merging is last-wins
945
+
946
+ 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:
947
+
948
+ ```ts
949
+ // merged.ts — resolves to { b: number }; the `a: string` member is not picked up
950
+ export interface SharedShape { a: string; }
951
+ export interface SharedShape { b: number; }
952
+ ```
953
+
954
+ **Workaround:** declare the merged shape once, or split the interfaces under distinct names and import the one you mean.
810
955
 
811
956
  ### Custom collection name conflicts
812
957
 
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,27 @@ 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 referencedTypeConstArrays;
41
+ private referencedTypeCompilerOptions;
42
+ private currentReferencedTypeFile;
43
+ private expandingReferencedAliases;
44
+ private defineSites;
45
+ private graphReferenceErrors;
46
+ private lookupReferencesValidated;
47
+ private lookupReferences;
48
+ private plainTypeReferencesValidated;
49
+ private plainTypeReferences;
50
+ private fileGraphBindings;
51
+ private currentGraphAnchor;
52
+ private processedCalls;
33
53
  constructor(program?: ts.Program, plugins?: TacticaPlugin[]);
34
54
  /**
35
55
  * Reset usage-related state for a fresh pass.
@@ -93,6 +113,250 @@ export declare class MnemonicaAnalyzer {
93
113
  * createTypesCollection are recognized without relying on the type checker.
94
114
  */
95
115
  private trackImports;
116
+ /**
117
+ * Record a named referenced-type declaration (type alias, class, or
118
+ * interface) for the file currently being visited.
119
+ */
120
+ private trackReferencedTypeDeclaration;
121
+ /**
122
+ * Record consts initialized with an array literal (optionally wrapped in
123
+ * `as const` / `satisfies`), so a `typeof statusList[number]` field type
124
+ * expands to the element literal union — the generated file carries no
125
+ * imports, so emitting the bare `typeof statusList` query would be an
126
+ * unresolvable name downstream. First binding wins: a nested shadow
127
+ * must not replace the module-level const the typeof refers to.
128
+ */
129
+ private trackReferencedTypeConstArray;
130
+ /**
131
+ * Find the array literal behind a module const referenced through
132
+ * `typeof`: the declaring file's own consts first (the F13 case is a
133
+ * NON-exported const in the same module as the expanded class), then —
134
+ * when the file imports the name — the imported module's consts.
135
+ * External modules are never analyzed, so those yield nothing.
136
+ */
137
+ private findReferencedConstArray;
138
+ /**
139
+ * Element literal types of a tracked const array: every element must be
140
+ * a plain literal (optionally wrapped in `as const` / `satisfies`) —
141
+ * string, numeric, boolean, or null. Spreads, identifiers, and nested
142
+ * arrays mean the union is not statically visible and yield undefined,
143
+ * so the caller degrades the field to `unknown` rather than guessing.
144
+ */
145
+ private literalTypesOfArray;
146
+ /**
147
+ * Emit-type for `typeof name` when `name` is a tracked const array: the
148
+ * union of its element literal types (`'active' | 'closed'`). Every
149
+ * other typeof source — non-array consts, functions, classes, names not
150
+ * tracked at all — yields undefined, so the caller degrades the field
151
+ * to `unknown`: a bare `typeof name` emitted into types.ts has no
152
+ * import to resolve against downstream.
153
+ */
154
+ private typeOfConstArrayUnion;
155
+ /**
156
+ * Record the importing file's named/namespace/default import bindings so
157
+ * referenced-type names resolve through the file's own import statements
158
+ * (F10) rather than a program-wide name map.
159
+ */
160
+ private trackReferencedTypeImport;
161
+ /**
162
+ * Record re-export wiring (`export { X } from '…'`, `export * from '…'`,
163
+ * `export { X as Y }`) so resolution can chase barrels to the origin
164
+ * module. Mirrors ModuleGraphBuilder.resolveOrigin, name-based only.
165
+ */
166
+ private trackReferencedTypeReExport;
167
+ /**
168
+ * Resolve a module specifier from a containing file with the program's
169
+ * compilerOptions (tsconfig `paths`, extensionless imports, index files).
170
+ * Module resolution only — the no-getTypeChecker() precedent stays.
171
+ */
172
+ private resolveReferencedTypeModule;
173
+ /**
174
+ * Look up a name in one resolved module, chasing re-export barrels with a
175
+ * bounded depth. External (node_modules) modules hold no in-project
176
+ * declarations and stop the chase.
177
+ */
178
+ private findReferencedTypeInModule;
179
+ /**
180
+ * Resolve a referenced type name as used in fromFile, import-aware:
181
+ * 1. the file's own import statements (relative + tsconfig paths,
182
+ * chased through re-export barrels),
183
+ * 2. the file's local declarations,
184
+ * 3. the unique same-named declaration across scanned files.
185
+ * Returns undefined when nothing matches (or the match is ambiguous),
186
+ * in which case the caller falls back to `unknown`.
187
+ */
188
+ private resolveReferencedTypeDeclaration;
189
+ /**
190
+ * External/ambient declaration files (.d.ts, anything under
191
+ * node_modules) never participate in plain-TS referenced-type
192
+ * resolution or the ambiguity law: they are not project source, the
193
+ * CLI never analyzes them, and a user-local declaration always wins
194
+ * over a package-declared same-named type.
195
+ */
196
+ private isExternalDeclFile;
197
+ /**
198
+ * Properties of a referenced class/interface/alias-of-literal declaration,
199
+ * shared by `this:`-parameter expansion and inline type emission.
200
+ * Inherited members are included: the extends chain is walked
201
+ * (depth-capped, cycle-guarded) and parent fields merge first, the
202
+ * declaration's own fields overriding on name clash.
203
+ */
204
+ private referencedDeclarationProperties;
205
+ private referencedDeclarationPropertiesInner;
206
+ /**
207
+ * Property signatures of interface/alias type-literal members, into
208
+ * the given map.
209
+ */
210
+ private collectTypeElementProperties;
211
+ /**
212
+ * Resolve the heritage clause of a class (`extends Base`) or interface
213
+ * (`extends A, B`) to referenced-type declarations through the SAME
214
+ * import-aware machinery as plain references (the declaring file's own
215
+ * imports first, then its locals, then the unique program-wide
216
+ * declaration). Unresolvable or external bases yield nothing — their
217
+ * inherited fields simply stay absent, same as before this walk
218
+ * existed. Mixin calls (`extends mixin(X)`) and namespace access are
219
+ * not followed.
220
+ */
221
+ private resolveHeritageDeclarations;
222
+ /**
223
+ * Expand a referenced-type declaration to a self-contained type string
224
+ * for emission into generated files: type aliases through inferType,
225
+ * classes and interfaces through their (public, non-method) fields.
226
+ * Nested references resolve against the declaring file while expanding.
227
+ */
228
+ private expandReferencedTypeDeclaration;
229
+ private expandReferencedTypeDeclarationInner;
230
+ /**
231
+ * Resolve a simple (non-qualified) type reference: import-aware
232
+ * declaration expansion first, then the InstanceType<typeof X> pattern,
233
+ * then mnemonica graph types; known globals keep their bare name and
234
+ * anything else falls back to `unknown` so generated files never carry
235
+ * an unresolvable bare name. Returns undefined when the caller should
236
+ * keep the generic spelling (handled separately).
237
+ */
238
+ private resolveSimpleTypeReference;
239
+ /**
240
+ * Resolve a qualified type reference (models.Inner.Crate) through the
241
+ * current file's namespace imports. The chain's head must be a namespace
242
+ * import; middle segments descend through namespace declarations, named
243
+ * re-exports of namespaces, and `export * as ns from '…'` barrels (each
244
+ * segment consumed exactly once, so the walk cannot cycle); the final
245
+ * segment resolves to a declaration which is expanded inline. When the
246
+ * precise walk finds nothing, the legacy rightmost-name lookup in the
247
+ * head module keeps one-level forms (models.Type) working — nested
248
+ * declarations are recorded by plain name there too. Returns undefined
249
+ * when the head is not a namespace import or nothing resolves.
250
+ */
251
+ private inferQualifiedTypeReference;
252
+ /**
253
+ * Find a namespace declaration by name directly inside a module block.
254
+ */
255
+ private findNamespaceInBlock;
256
+ /**
257
+ * Find a named type declaration (alias, class, interface) directly inside
258
+ * a namespace block — the final segment of a descended qualified chain.
259
+ */
260
+ private findReferencedTypeInBlock;
261
+ /**
262
+ * Fallback for a type-reference name that resolves to no declaration and
263
+ * no graph type: known globals keep their bare name (they resolve without
264
+ * an import); everything else becomes `unknown` so generated types.ts
265
+ * never carries an unresolvable bare name (README's documented behavior)
266
+ * and the site is recorded for the plain-TS ambiguity validation.
267
+ */
268
+ private unresolvedTypeReferenceFallback;
269
+ /**
270
+ * Record one define()/lazy()/@decorate() site under its runtime
271
+ * namespace key. Two sites in one namespace are a same-namespace
272
+ * duplicate (the runtime throws ALREADY_DECLARED); every site is kept
273
+ * so the failure can report all locations.
274
+ */
275
+ private recordDefineSite;
276
+ /**
277
+ * Fatal resolution failures (hard-fail law): same-namespace duplicate
278
+ * mnemonica definitions plus ambiguous/unresolved mnemonica-graph
279
+ * references. The CLI prints every location and writes no output.
280
+ */
281
+ getResolutionErrors(): ResolutionError[];
282
+ /**
283
+ * Resolve a reference to a mnemonica graph type name, import-aware and
284
+ * path-aware (the hard-fail identity law, mirroring the runtime):
285
+ * 1. value scope — a tracked top-level binding in the referencing file
286
+ * (`const Address = User.define('Address', …)`),
287
+ * 2. import scope — a binding exported from a module this file imports
288
+ * (barrels chased),
289
+ * 3. nearest-chain — the anchor type's own subtypes first, then each
290
+ * ancestor level (relative-first),
291
+ * 4. root — roots of the anchor's collection,
292
+ * 5. program-wide — only when exactly one type carries the name.
293
+ * Ambiguity (several candidates and nothing disambiguates) and absence
294
+ * are both returned as such — the caller records a hard failure; a bare
295
+ * first-match name is never emitted.
296
+ */
297
+ private resolveGraphTypeName;
298
+ /**
299
+ * Find a graph constructor binding exported by a resolved module,
300
+ * chasing re-export barrels with a bounded depth.
301
+ */
302
+ private findGraphBindingInModule;
303
+ /**
304
+ * Validate literal lookup() paths recorded during the usages pass
305
+ * against the complete graph. A lookup path matching no type is what the
306
+ * runtime answers with `undefined` — the TypeError arrives one line
307
+ * later at the `new` — so it joins the hard-fail law. The relative-first
308
+ * step already ran inside resolveLookupPath; whatever was recorded is
309
+ * the root-resolution result, so a plain findType check is the exact
310
+ * runtime law. Same-named types elsewhere in the graph are listed as
311
+ * did-you-mean candidates. Runs once per usages pass (re-armed by
312
+ * resetUsages); non-literal lookup arguments are never recorded and
313
+ * stay best-effort.
314
+ */
315
+ private validateLookupReferences;
316
+ /**
317
+ * Record a plain-TS type reference site that resolved to nothing and
318
+ * fell back to `unknown`, for the lazily-run ambiguity validation.
319
+ * Deduped by (name, location): inferType can visit the same node more
320
+ * than once per pass (constructor params + property inference).
321
+ */
322
+ private recordPlainTypeReferenceSite;
323
+ /**
324
+ * Project-source declaration files carrying `name` — one entry per
325
+ * file, so same-file interface merging counts once (not ambiguous).
326
+ * External/ambient declarations (.d.ts, anything under node_modules)
327
+ * never count: a user-local declaration always wins over a package-
328
+ * declared same-named type, so an external collision stays soft.
329
+ */
330
+ private plainTypeDeclarationFiles;
331
+ /**
332
+ * Validate plain-TS type reference sites recorded during the usages
333
+ * pass against the complete declaration map. A name declared in
334
+ * several project-source files — with no import in the referencing
335
+ * file to anchor it — is ambiguous: silently emitting `unknown` would
336
+ * hide a real type the author meant, so it joins the hard-fail law
337
+ * (the plain-TS tier of the same identity law as graph references).
338
+ * Absence (ghost names) and external collisions stay soft `unknown`.
339
+ * Runs once per usages pass (re-armed by resetUsages), mirroring
340
+ * validateLookupReferences: recording happens on every pass, but only
341
+ * the usages pass sees the complete declaration map.
342
+ */
343
+ private validatePlainTypeReferences;
344
+ /**
345
+ * `file:line:column` of a recorded declaration, for the ambiguity
346
+ * report. Nodes recorded during traversal keep their positions; a
347
+ * synthetic/unpositioned node falls back to the file itself.
348
+ */
349
+ private plainDeclLocation;
350
+ /**
351
+ * Record a hard-fail graph reference error with the reference site and
352
+ * every candidate location.
353
+ */
354
+ private recordGraphReferenceError;
355
+ /**
356
+ * Location (`file:line:column`) of an AST node, derived without parent
357
+ * pointers when necessary.
358
+ */
359
+ private nodeLocation;
96
360
  /**
97
361
  * Track aliases of the mnemonica module object, e.g.:
98
362
  * const m = mnemonica;
@@ -203,6 +467,12 @@ export declare class MnemonicaAnalyzer {
203
467
  * For chained calls like const X = define('A').define('B'), we map X -> A (the root type)
204
468
  */
205
469
  private trackVariableAssignment;
470
+ /**
471
+ * Mirror a variable -> mnemonica fullPath binding into the per-file
472
+ * value-scope map (graph identity law: `typeof X` and bare references
473
+ * resolve through the file's own bindings first).
474
+ */
475
+ private trackFileGraphBinding;
206
476
  /**
207
477
  * Track variable assignments from lookup() calls
208
478
  * e.g., const SentienceConstructor = lookup('Sentience') maps "SentienceConstructor" -> "Sentience"
@@ -255,6 +525,15 @@ export declare class MnemonicaAnalyzer {
255
525
  * collection.lookup('User.Admin')
256
526
  */
257
527
  private resolveLookupPath;
528
+ /**
529
+ * Lookup-law delegate for the local-scope walker (scopes.json typePath
530
+ * metadata): resolve a lookup() initializer call through exactly the
531
+ * tiers the usages pass resolved it against (same source resolution,
532
+ * same complete graph). The walker runs its own scope-chain value-scope
533
+ * tier before delegating; everything above value scope lands here, so
534
+ * scopes.json never disagrees with the hard-fail-law verdicts.
535
+ */
536
+ resolveLookupCallPath(call: ts.CallExpression): string | undefined;
258
537
  /**
259
538
  * Find a parent type by its name, searching in the graph.
260
539
  * When collectionId is provided, only types from that collection are considered.
@@ -338,10 +617,6 @@ export declare class MnemonicaAnalyzer {
338
617
  * Infer return type by analyzing return statements in the method body
339
618
  */
340
619
  private inferReturnTypeFromBody;
341
- /**
342
- * Get full text from a qualified name (e.g., Namespace.Type)
343
- */
344
- private getQualifiedNameText;
345
620
  /**
346
621
  * Infer type from initializer
347
622
  */
@@ -373,6 +648,24 @@ export declare class MnemonicaAnalyzer {
373
648
  * returns undefined for calls outside any type scope (module top level).
374
649
  */
375
650
  private resolveEDSScope;
651
+ /**
652
+ * Resolve a wrap site's instance/context argument to a mnemonica type
653
+ * path — the fire-and-forget-wrapper attribution fallback when the call
654
+ * sits outside any define()/lazy() handler: a tracked assignment
655
+ * (`const holder = new Holder(...)`), else the root identifier's
656
+ * (property-access roots included) parameter annotation resolved
657
+ * through the graph law. Ambiguity or absence stays silent — this is a
658
+ * metadata heuristic, not the identity-law surface.
659
+ */
660
+ private resolveWrapInstanceTypePath;
661
+ /**
662
+ * Resolve a bare-identifier type annotation of the nearest enclosing
663
+ * function's parameter through the mnemonica-graph tiers (value scope,
664
+ * imports, roots, program-wide-unique). Non-identifier and generic
665
+ * annotations are not graph references; ambiguity and absence yield
666
+ * undefined.
667
+ */
668
+ private resolveParameterAnnotationTypePath;
376
669
  /**
377
670
  * Resolve a wrap() argument to its function node without the type
378
671
  * checker: direct function expressions/arrows, or same-file bindings
@@ -397,6 +690,8 @@ export declare class MnemonicaAnalyzer {
397
690
  * site (`via` = the site whose wrapping caused it) and recurse into
398
691
  * its own returns. Returns through identifiers resolve through the
399
692
  * same-file bindings table; unresolvable returns are simply skipped.
693
+ * A return declared outside any type scope inherits the causing wrap
694
+ * site's scope attribution (the generation chain is the only holder).
400
695
  */
401
696
  private recordWrappedReturn;
402
697
  /**