@mnemonica/tactica 0.3.11 → 0.3.15

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
@@ -125,7 +125,7 @@ EDS tracking is **auto-enabled** when `@mnemonica/dive` is present in `package.j
125
125
 
126
126
  ## Generated Files
127
127
 
128
- Tactica writes everything under the `--output` directory (default `.tactica/`):
128
+ Tactica writes everything under the `--output` directory (default `.tactica/`). All file paths inside the JSON files are emitted **project-relative** (anchored at the tsconfig directory) so the output is portable across machines; consumers resolve them against the directory holding `.tactica`. Earlier versions emitted absolute paths, so consumers should accept both.
129
129
 
130
130
  | File | When | Purpose |
131
131
  |---|---|---|
@@ -490,6 +490,8 @@ The analyzer infers property types from constructor bodies and class members. Su
490
490
 
491
491
  An existing annotation always beats a weaker inference: `unknown` (and `unknown`-bearing guesses like `Array<unknown>`) never overwrites a known type — `Record<string, unknown>` IS a known type — and an overwrite never drops an optionality modifier.
492
492
 
493
+ **Method/member return annotations** follow an emission law (0.2.0 behavior, restored after the F10 bare-name ban overshot): an annotation — or any type argument inside it — that resolves to a mnemonica graph type emits the generated alias (`SomeEntry[]` → `Array<EDSRoot_SomeEntry>`; generated-alias spellings resolve via the underscore→dotted law). `InstanceType<typeof X>` — directly or through a local alias (`XInstance = InstanceType<typeof X>`) — drops the wrapper and emits the generated alias (`Definitions_DefinitionEntry`), since the alias already IS the instance type; when X does not resolve to a graph type the WHOLE expression degrades to `unknown` — `InstanceType<unknown>` is invalid TypeScript (TS2344) and is never emitted. An outer generic that is a non-graph **ambient/lib** construct (`MapIterator`, lib helpers — not declared in any analyzed project file) is emitted **verbatim** with inner graph aliases resolved, since it needs no import in the generated file. A **project-local** non-graph name stays `unknown` — the self-contained `types.ts` can carry neither the bare name nor an import (ambiguity between same-named project declarations is the fatal class, as elsewhere). This law covers method/member return annotations; constructor-signature parameters keep their accepted mirror policy.
494
+
493
495
  When inference fails the property's type falls back to `unknown` (or `any` in some Topologica paths) — safe, and you can refine manually.
494
496
 
495
497
  ### Referenced types in constructor signatures
@@ -703,7 +705,7 @@ class ModuleGraphBuilder {
703
705
 
704
706
  `ModuleGraph` = `{ modules: Map<absPath, ModuleInfo>, edges: CrossModuleUsage[], cycles: string[][] }`.
705
707
  Each `ModuleBinding` carries `{ name, kind: 'function'|'class'|'const'|'type'|'unknown',
706
- sourceModule (resolved absolute path), importKind?, importAlias?, isReExport, external? }`.
708
+ sourceModule (resolved project-relative path), importKind?, importAlias?, isReExport, external? }`.
707
709
  Node.js builtins (`'path'`, `'node:fs'`, …) are skipped entirely and recorded
708
710
  only in the module's `builtinSpecifiers`; bindings resolved into `node_modules`
709
711
  carry `external: true` and never enter `dependencies`. Re-export chains
@@ -787,7 +789,7 @@ When enabled, tactica detects execution-flow patterns alongside type definitions
787
789
  "eds": {
788
790
  "UserEntity": [
789
791
  {
790
- "location": "/project/src/queue.ts:45:12",
792
+ "location": "src/queue.ts:45:12",
791
793
  "kind": "wrap",
792
794
  "code": "wrap(process)",
793
795
  "targetType": "UserEntity"
@@ -857,7 +859,7 @@ When a referenced class is declared in the analyzed project, the point's `locati
857
859
  {
858
860
  "kind": "pipe",
859
861
  "className": "PayloadPipe",
860
- "location": "/project/src/user.controller.ts:49:2",
862
+ "location": "src/user.controller.ts:49:2",
861
863
  "code": "@RegisterPipe(new PayloadPipe({ transform: true }))",
862
864
  "scope": "method:UserController.createUser",
863
865
  "targets": ["UserController"]
@@ -866,21 +868,21 @@ When a referenced class is declared in the analyzed project, the point's `locati
866
868
  "creationGraph": {
867
869
  "nodes": [
868
870
  {
869
- "scopeId": "/project/src/main.ts",
870
- "name": "/project/src/main.ts",
871
+ "scopeId": "src/main.ts",
872
+ "name": "src/main.ts",
871
873
  "kind": "module",
872
- "filePath": "/project/src/main.ts",
873
- "location": "/project/src/main.ts:1:1",
874
+ "filePath": "src/main.ts",
875
+ "location": "src/main.ts:1:1",
874
876
  "starter": true
875
877
  }
876
878
  ],
877
879
  "edges": [
878
- { "caller": "/project/src/main.ts", "callee": "/project/src/user.service.ts:26:2" }
880
+ { "caller": "src/main.ts", "callee": "src/user.service.ts:26:2" }
879
881
  ],
880
882
  "anchors": [
881
883
  {
882
- "location": "/project/src/user.service.ts:29:24",
883
- "holderScopeId": "/project/src/user.service.ts:26:2",
884
+ "location": "src/user.service.ts:29:24",
885
+ "holderScopeId": "src/user.service.ts:26:2",
884
886
  "typePath": "UserEntity.UserResponse",
885
887
  "constructorText": "user.UserResponse",
886
888
  "variable": "userResponse"
@@ -960,7 +962,7 @@ Mnemonica accepts the `exposeInstanceMethods` option at runtime, but tactica's `
960
962
 
961
963
  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`.
962
964
 
963
- 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, with the index suffix consumed entirely (`typeof statusList[number]` emits exactly `'active' | 'not_active' | 'hold'`, never `'hold'[number]`). The value-level twin — element access over a const-asserted literal array (`(<const>[…])[0]`, `([…] as const)[1]`, tracked `x[0]`) — infers the element literal under the same rules; non-numeric, out-of-range, spread, or non-literal elements and general `<T>` assertions (as opposed to `<const>`) stay `unknown`. Any assertion spelling is tracked (`[…] as const`, `satisfies`, `<const>[…]`), and signed numeric literals keep their sign (`-1 | 1`). Cross-file only when the const is imported by the declaring file. Every other `typeof` source — and any indexed-access target that fails to resolve — degrades the whole expression to `unknown`: an index suffix glued onto a fallback type (`unknown[number]`) is invalid TypeScript and is never emitted.
965
+ 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, with the index suffix consumed entirely (`typeof statusList[number]` emits exactly `'active' | 'not_active' | 'hold'`, never `'hold'[number]`). The value-level twin — element access over a const-asserted literal array (`(<const>[…])[0]`, `([…] as const)[1]`, tracked `x[0]`) — infers the element literal under the same rules; non-numeric, out-of-range, spread, or non-literal elements and general `<T>` assertions (as opposed to `<const>`) stay `unknown`. Any assertion spelling is tracked (`[…] as const`, `satisfies`, `<const>[…]`), and signed numeric literals keep their sign (`-1 | 1`). Cross-file only when the const is imported by the declaring file. Every other `typeof` source — and any indexed-access target that fails to resolve — degrades the whole expression to `unknown`: an index suffix glued onto a fallback type (`unknown[number]`) is invalid TypeScript and is never emitted. A sibling emission law governs method/member return annotations (0.2.0 behavior restored after the F10 bare-name ban overshot): inner type arguments resolving to graph types emit the generated aliases; `InstanceType<typeof X>` (directly or via a local alias) drops the wrapper and emits the alias — X unresolvable degrades the WHOLE expression to `unknown`, never `InstanceType<unknown>` (invalid TS, TS2344); a non-graph **ambient/lib** outer generic (`MapIterator`, lib helpers — not declared in any analyzed project file) emits verbatim with inner aliases resolved; a **project-local** non-graph name stays `unknown` (no imports in the generated file; same-named ambiguity is the fatal class). Constructor-signature parameters keep their accepted mirror policy and are not covered.
964
966
 
965
967
  ### Same-file interface merging is last-wins
966
968
 
package/lib/analyzer.d.ts CHANGED
@@ -776,6 +776,14 @@ export declare class MnemonicaAnalyzer {
776
776
  * metadata heuristic, not the identity-law surface.
777
777
  */
778
778
  private resolveWrapInstanceTypePath;
779
+ /**
780
+ * Emission-law helper (0.2.0 restoration): is `name` declared in any
781
+ * ANALYZED PROJECT file? External/ambient files (.d.ts, node_modules)
782
+ * do not count. A name with no project declaration is an ambient/lib
783
+ * construct — safe to emit verbatim into the self-contained types.ts;
784
+ * a project-local name is not (no imports in the generated file).
785
+ */
786
+ private isProjectDeclaredTypeName;
779
787
  /**
780
788
  * F24: resolve a bare-identifier annotation to a graph fullPath. The
781
789
  * annotation may name the type directly (`LedgerUpdate`) or carry