@mnemonica/tactica 0.1.0 → 0.1.1

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
@@ -1,14 +1,20 @@
1
1
  # @mnemonica/tactica
2
2
 
3
- **TypeScript Language Service Plugin for Mnemonica**
3
+ **Type definition generator for Mnemonica.**
4
4
 
5
- Tactica generates type definitions for Mnemonica's dynamic nested constructors, enabling TypeScript to understand runtime type hierarchies created through `define()` and `decorate()` calls.
5
+ Tactica is a CLI tool and Node library that statically analyzes TypeScript / JavaScript source code and generates type definitions, navigation metadata, and runtime-flow data for projects that use [mnemonica](https://github.com/wentout/mnemonica) — the instance-inheritance system that creates types dynamically at runtime through `define()` and `decorate()` calls.
6
+
7
+ Output lives in `.tactica/` and is consumed by:
8
+
9
+ - **`tsc`** — so TypeScript can understand `user.AdminType` after `UserType.define('AdminType', …)`.
10
+ - **`mnemonica.lookupTyped<K>()`** — type-safe runtime lookup via the generated `TypeRegistry` module augmentation.
11
+ - **[mnemographica](https://github.com/mythographica/mnemographica)** (VS Code extension) — for Go to Definition, Find References, and the type-hierarchy graph view.
6
12
 
7
13
  ## The Problem
8
14
 
9
- Mnemonica enables powerful instance-level inheritance:
15
+ Mnemonica enables instance-level inheritance:
10
16
 
11
- ```typescript
17
+ ```ts
12
18
  const UserType = define('UserType', function (this: { name: string }) {
13
19
  this.name = '';
14
20
  });
@@ -18,14 +24,14 @@ const AdminType = UserType.define('AdminType', function (this: { role: string })
18
24
  });
19
25
 
20
26
  const user = new UserType();
21
- const admin = new user.AdminType(); // Works at runtime!
27
+ const admin = new user.AdminType(); // works at runtime
22
28
  ```
23
29
 
24
- But TypeScript doesn't know that `user.AdminType` exists because `UserType.define()` is a runtime operation.
30
+ TypeScript can't infer that `user.AdminType` exists, because `UserType.define()` is a runtime operation.
25
31
 
26
32
  ## The Solution
27
33
 
28
- Tactica analyzes your TypeScript source files and generates declaration files that tell TypeScript about the nested constructor hierarchy.
34
+ Tactica parses your source, builds the type hierarchy, and emits `.tactica/types.ts` + `.tactica/registry.ts`. Once those files are part of your `tsc` compilation, TypeScript understands the full hierarchy — without runtime changes.
29
35
 
30
36
  ## Installation
31
37
 
@@ -33,60 +39,29 @@ Tactica analyzes your TypeScript source files and generates declaration files th
33
39
  npm install --save-dev @mnemonica/tactica
34
40
  ```
35
41
 
36
- ## Usage
37
-
38
- ### 1. As a TypeScript Language Service Plugin (Recommended)
39
-
40
- Add to your `tsconfig.json`:
41
-
42
- ```json
43
- {
44
- "compilerOptions": {
45
- "plugins": [
46
- {
47
- "name": "@mnemonica/tactica",
48
- "outputDir": ".tactica",
49
- "include": ["src/**/*.ts"],
50
- "exclude": ["**/*.test.ts", "**/*.spec.ts"]
51
- }
52
- ]
53
- }
54
- }
55
- ```
56
-
57
- Then include the generated types in your project:
58
-
59
- ```json
60
- {
61
- "compilerOptions": {
62
- "typeRoots": ["./node_modules/@types", "./.tactica"]
63
- }
64
- }
65
- ```
42
+ `mnemonica` is a peer dependency (>= 1.0.1).
66
43
 
67
- ### 2. As a CLI Tool
44
+ ## Usage
68
45
 
69
- Generate types once:
46
+ ### CLI
70
47
 
71
48
  ```bash
49
+ # One-shot generation
72
50
  npx tactica
73
- ```
74
-
75
- Watch mode for development:
76
51
 
77
- ```bash
52
+ # Watch mode (regenerate on file changes)
78
53
  npx tactica --watch
79
- ```
80
54
 
81
- With custom options:
55
+ # Custom output directory
56
+ npx tactica --output ./types/mnemonica
82
57
 
83
- ```bash
84
- npx tactica --project ./src/tsconfig.json --output ./types/mnemonica
58
+ # Custom tsconfig
59
+ npx tactica --project ./src/tsconfig.json
85
60
  ```
86
61
 
87
- ### 3. As a Module
62
+ ### Library
88
63
 
89
- ```typescript
64
+ ```ts
90
65
  import { MnemonicaAnalyzer, TypesGenerator, TypesWriter } from '@mnemonica/tactica';
91
66
  import * as ts from 'typescript';
92
67
 
@@ -100,112 +75,155 @@ for (const sourceFile of program.getSourceFiles()) {
100
75
  }
101
76
 
102
77
  const generator = new TypesGenerator(analyzer.getGraph());
78
+ const writer = new TypesWriter('.tactica');
103
79
 
104
- // Generate types.ts (exportable type aliases - default mode)
105
- const generatedTypes = generator.generateTypesFile();
106
- const writer = new TypesWriter('.tactica');
107
- writer.writeTypesFile(generatedTypes);
80
+ // Default mode — exportable type aliases in types.ts + a TypeRegistry augmentation in registry.ts.
81
+ writer.writeTypesFile(generator.generateTypesFile());
82
+ writer.writeTo('registry.ts', generator.generateTypeRegistry().content);
108
83
 
109
- // Or generate index.d.ts (global augmentation - legacy mode)
110
- const generatedGlobal = generator.generateGlobalAugmentation();
111
- writer.writeGlobalAugmentation(generatedGlobal);
84
+ // Legacy mode — single global augmentation file index.d.ts.
85
+ // writer.writeGlobalAugmentation(generator.generateGlobalAugmentation());
112
86
  ```
113
87
 
114
- ## Configuration Options
88
+ ### Project setup
115
89
 
116
- ### Plugin Options (tsconfig.json)
90
+ After running `npx tactica`, add `.tactica` to your `tsconfig.json` so the generated files are part of the compilation:
117
91
 
118
- | Option | Type | Default | Description |
119
- |--------|------|---------|-------------|
120
- | `outputDir` | string | `.tactica` | Directory for generated types |
121
- | `include` | string[] | `['**/*.ts']` | File patterns to include |
122
- | `exclude` | string[] | `['**/*.d.ts']` | File patterns to exclude |
123
- | `verbose` | boolean | `false` | Enable verbose logging |
92
+ ```json
93
+ {
94
+ "compilerOptions": {
95
+ "paths": {
96
+ "~tactica/*": ["./.tactica/*"]
97
+ }
98
+ },
99
+ "include": ["src/**/*.ts", ".tactica/**/*.ts"]
100
+ }
101
+ ```
102
+
103
+ Then use `lookupTyped` for type-safe access (see "Type-Safe Lookup" below).
124
104
 
125
- ### CLI Options
105
+ You should commit `.tactica/` if you want type information to flow through CI without re-running tactica; alternatively, add `.tactica/` to `.gitignore` and regenerate as part of your build. Tactica does **not** modify `.gitignore` for you.
106
+
107
+ ## CLI Options
126
108
 
127
109
  | Option | Short | Description |
128
- |--------|-------|-------------|
129
- | `--watch` | `-w` | Watch mode - regenerate on file changes |
130
- | `--project` | `-p` | Path to tsconfig.json |
131
- | `--output` | `-o` | Output directory (default: `.tactica`) |
132
- | `--include` | `-i` | Include patterns (comma-separated) |
133
- | `--exclude` | `-e` | Exclude patterns (comma-separated) |
134
- | `--module-augmentation` | `-m` | Generate global augmentation (legacy mode) |
135
- | `--verbose` | `-v` | Enable verbose logging |
136
- | `--help` | `-h` | Show help message |
137
-
138
- **Examples:**
110
+ |---|---|---|
111
+ | `--watch` | `-w` | Watch mode — regenerate on file changes |
112
+ | `--project <path>` | `-p` | Path to `tsconfig.json` (default: nearest ancestor) |
113
+ | `--output <dir>` | `-o` | Output directory (default: `.tactica`) |
114
+ | `--include <patterns>` | `-i` | Comma-separated include patterns |
115
+ | `--exclude <patterns>` | `-e` | Comma-separated exclude patterns |
116
+ | `--topologica <dirs>` | `-t` | Comma-separated Topologica directories to scan |
117
+ | `--module-augmentation` | `-m` | Legacy mode — emit a single global-augmentation `index.d.ts` instead of `types.ts` + `registry.ts` |
118
+ | `--esm` | | Append `.js` extensions to relative imports in generated files (for ESM / NodeNext module resolution) |
119
+ | `--eds` | | Force-enable EDS (Execution Data Storage) tracking |
120
+ | `--no-eds` | | Force-disable EDS tracking |
121
+ | `--verbose` | `-v` | Verbose logging |
122
+ | `--help` | `-h` | Show help |
123
+
124
+ EDS tracking is **auto-enabled** when `@mnemonica/dive` is present in `package.json` dependencies; otherwise it is off. `--eds` / `--no-eds` override the auto-detection.
125
+
126
+ ## Generated Files
127
+
128
+ Tactica writes everything under the `--output` directory (default `.tactica/`):
129
+
130
+ | File | When | Purpose |
131
+ |---|---|---|
132
+ | `types.ts` | default mode | Exportable type aliases — one per discovered type. Uses `ProtoFlat<Parent, Self>` for nested types. |
133
+ | `registry.ts` | default mode | `declare module 'mnemonica' { interface TypeRegistry { … } }` augmentation — powers `lookupTyped<K>()`. |
134
+ | `index.ts` | default mode | Re-exports everything from `types.ts` and `registry.ts`. |
135
+ | `index.d.ts` | with `--module-augmentation` | Single global-augmentation file (legacy mode). |
136
+ | `definitions.json` | always | One entry per discovered type: `{ name, location, kind: 'define'\|'decorate', parent, strictChain, blockErrors }`. Consumed by `mnemographica`'s Go to Definition. |
137
+ | `usages.json` | always | One entry per type, value is an array of `{ location, kind, code }` records — where each type is instantiated, referenced, accessed, or looked up. Consumed by `mnemographica`'s Find References. |
138
+ | `flow.json` | always | Native instance-flow patterns (property reads/writes, method calls, destructuring, returns, spreads, etc.) per type. |
139
+ | `eds.json` | when EDS enabled | Execution-flow patterns (`wrap`, `link`, `getLastContext`, hook attachment, framework adapters). Consumed by tools that visualize execution chains. |
140
+
141
+ ### Default mode (types.ts + registry.ts)
142
+
143
+ ```ts
144
+ // .tactica/types.ts
145
+ export type UserType = {
146
+ name: string;
147
+ email: string;
148
+ AdminType: new (data: { role: string }) => UserType_AdminType;
149
+ };
139
150
 
140
- ```bash
141
- # Default mode - generates .tactica/types.ts
142
- npx tactica
151
+ export type UserType_AdminType = ProtoFlat<UserType, {
152
+ role: string;
153
+ AdminType: undefined;
154
+ }>;
155
+ ```
143
156
 
144
- # Global augmentation mode - generates .tactica/index.d.ts
145
- npx tactica --module-augmentation
157
+ ```ts
158
+ // .tactica/registry.ts
159
+ declare module 'mnemonica' {
160
+ interface TypeRegistry {
161
+ 'UserType': new (data: { name: string; email: string }) => UserType;
162
+ 'UserType.AdminType': new (data: { role: string }) => UserType_AdminType;
163
+ }
164
+ }
165
+ ```
166
+
167
+ **Recommended for new projects.** Explicit imports, better tree-shaking, no global namespace pollution.
146
168
 
147
- # Watch mode with custom output directory
148
- npx tactica --watch --output ./custom-types
169
+ ### Legacy global-augmentation mode (`--module-augmentation`)
149
170
 
150
- # Exclude test files
151
- npx tactica --exclude "*.test.ts,*.spec.ts"
171
+ Emits one `.tactica/index.d.ts` whose contents live inside `declare global { … }`. Reference it from your code with:
172
+
173
+ ```ts
174
+ /// <reference types="./.tactica/index" />
152
175
  ```
153
176
 
154
- ## Generated Output
177
+ or by adding `./.tactica` to `compilerOptions.typeRoots`. Kept for backwards compatibility with older tactica integrations.
155
178
 
156
- By default, Tactica generates `.tactica/types.ts` with exported type aliases:
179
+ ## Type-Safe Lookup with `lookupTyped()`
157
180
 
158
- ```typescript
159
- // Generated by @mnemonica/tactica - DO NOT EDIT
160
- export type UserTypeInstance = {
161
- name: string;
162
- email: string;
163
- AdminType: TypeConstructor<AdminTypeInstance>;
164
- }
181
+ Mnemonica core exposes `lookupTyped<K>()` against an empty `TypeRegistry` interface. Tactica's `registry.ts` augments that interface with all of your discovered types:
165
182
 
166
- export type AdminTypeInstance = UserTypeInstance & {
167
- role: string;
168
- permissions: string[];
183
+ ```ts
184
+ // .tactica/registry.ts (generated)
185
+ declare module 'mnemonica' {
186
+ interface TypeRegistry {
187
+ 'UserType': new (data: { name: string; email: string }) => UserType;
188
+ 'UserType.AdminType': new (data: { role: string }) => UserType_AdminType;
189
+ }
169
190
  }
170
191
  ```
171
192
 
172
- ### Output Modes
193
+ Once `registry.ts` is in your `tsc` compilation, the lookup is fully typed:
173
194
 
174
- **Default mode** (`npx tactica`):
175
- - Generates `.tactica/types.ts` - Exportable type aliases
176
- - Import types explicitly: `import type { UserTypeInstance } from './.tactica/types'`
177
- - Include in tsconfig.json: `"include": ["src/**/*.ts", ".tactica/types.ts"]`
178
- - **Recommended for new projects** - explicit imports, better tree-shaking
195
+ ```ts
196
+ import { lookupTyped } from 'mnemonica';
197
+ import './.tactica/registry'; // or via tsconfig "include"
179
198
 
180
- **Global mode** (`npx tactica --module-augmentation`):
181
- - Generates `.tactica/index.d.ts` - Global type declarations
182
- - Types are available without imports (via `declare global`)
183
- - Add to tsconfig.json: `"typeRoots": ["./node_modules/@types", "./.tactica"]`
184
- - Use triple-slash reference: `/// <reference types="./.tactica/index" />`
199
+ const UserType = lookupTyped('UserType');
200
+ const AdminType = lookupTyped('UserType.AdminType');
185
201
 
186
- **Choosing a mode:**
187
- - Use **Default mode** for new projects - explicit imports are clearer and work better with tree-shaking
188
- - Use **Global mode** if you want types available without imports (legacy behavior)
202
+ const user = new UserType({ name: 'John', email: 'j@x.dev' });
203
+ const admin = new user.AdminType({ role: 'admin' });
204
+ ```
205
+
206
+ No `as unknown as` casts required. See [`docs/lookupTyped-pattern.md`](docs/lookupTyped-pattern.md) for the consumer-side guide.
189
207
 
190
208
  ## What Gets Analyzed
191
209
 
192
- ### 1. define() Calls
210
+ ### `define()` calls
193
211
 
194
- ```typescript
195
- // Root type
196
- const UserType = define('UserType', function (this: { name: string }) {
212
+ ```ts
213
+ const UserType = define('UserType', function (this: { name: string }) {
197
214
  this.name = '';
198
215
  });
199
216
 
200
- // Nested type
201
217
  const AdminType = UserType.define('AdminType', function (this: { role: string }) {
202
218
  this.role = 'admin';
203
219
  });
204
220
  ```
205
221
 
206
- ### 2. @decorate() Decorator
222
+ Chained calls (`define('A').define('B')`) and nested calls via variable references (`const A = define('A', …); A.define('B', …)`) are both supported.
223
+
224
+ ### `@decorate()` decorator
207
225
 
208
- ```typescript
226
+ ```ts
209
227
  @decorate()
210
228
  class User {
211
229
  name: string = '';
@@ -215,78 +233,57 @@ class User {
215
233
  class Admin {
216
234
  role: string = 'admin';
217
235
  }
218
- ```
219
-
220
- #### Why Type Casting is Necessary for @decorate()
221
-
222
- When using `@decorate()` on classes, TypeScript cannot automatically infer that instances have nested type constructors (like `user.Admin`). This is because:
223
-
224
- 1. **`define()` types work automatically**: When you use `define()`, the returned constructor has the correct type signature with nested constructors.
225
-
226
- 2. **`@decorate()` classes need casting**: When you decorate a class, TypeScript sees the class itself, not the augmented type that mnemonica creates at runtime.
227
-
228
- **The Solution:** Cast to the instance type to access nested constructors:
229
-
230
- ```typescript
231
- @decorate()
232
- class Order {
233
- orderId: string = '';
234
- total: number = 0;
235
- }
236
236
 
237
- @decorate(Order)
238
- class AugmentedOrder {
239
- addition: string = 'extra';
237
+ @decorate({ blockErrors: true, strictChain: false })
238
+ class Configurable {
239
+ value: string = '';
240
240
  }
241
+ ```
241
242
 
242
- // Cast to OrderInstance to access AugmentedOrder constructor
243
- const order = new Order() as OrderInstance;
244
- const augmented = new order.AugmentedOrder(); // ✅ Works!
243
+ Decorate-class instances need a cast to the generated instance type to access nested constructors at the type level:
245
244
 
246
- // The 'augmented' variable is automatically typed as AugmentedOrderInstance
247
- console.log(augmented.orderId); // From Order
248
- console.log(augmented.addition); // From AugmentedOrder
245
+ ```ts
246
+ const order = new Order() as Order_AugmentedOrder; // … or whatever your generated alias is
247
+ const sub = new order.AugmentedOrder();
249
248
  ```
250
249
 
251
- Generated types (like `OrderInstance`, `AugmentedOrderInstance`) are automatically available globally - no imports needed!
250
+ ### `Object.assign(this, data)` pattern
252
251
 
253
- #### @decorate() with Options
254
-
255
- ```typescript
256
- @decorate({
257
- blockErrors: true,
258
- strictChain: false,
259
- exposeInstanceMethods: true
260
- })
261
- class ConfigurableClass {
262
- value: string = '';
263
- }
252
+ ```ts
253
+ const UserType = define('UserType', function (this: any, data: { name: string }) {
254
+ Object.assign(this, data); // properties inferred from `data` type
255
+ });
264
256
  ```
265
257
 
266
- ### 3. Object.assign Pattern
258
+ ### `TypeConstructor` casting (from mnemonica)
267
259
 
268
- ```typescript
269
- const UserType = define('UserType', function (this: any, data: any) {
270
- Object.assign(this, data);
271
- });
260
+ ```ts
261
+ import { define } from 'mnemonica';
262
+ import type { TypeConstructor } from 'mnemonica';
263
+
264
+ const MyFn = function (this: any) {
265
+ this.field = 123;
266
+ } as TypeConstructor<{ field: number }>;
267
+
268
+ const MyFnType = define('MyFnType', MyFn);
272
269
  ```
273
270
 
274
- ### 4. Typeomatica Integration
271
+ (In older mnemonica documentation this helper was called `ConstructorFunction`; the analyzer still recognizes that name in source code for back-compat.)
275
272
 
276
- Tactica works seamlessly with Typeomatica patterns:
273
+ ### Typeomatica integration
277
274
 
278
- ```typescript
275
+ Tactica recognizes [typeomatica](https://github.com/wentout/typeomatica) patterns alongside mnemonica:
276
+
277
+ ```ts
279
278
  import { decorate } from 'mnemonica';
280
279
  import { Strict, BaseClass } from 'typeomatica';
281
280
 
282
- // @Strict decorator alongside @decorate
283
281
  @decorate()
284
282
  @Strict({ someProp: 123 })
285
283
  class StrictDecorated {
286
284
  someProp!: number;
287
285
  }
288
286
 
289
- // BaseClass with Object.setPrototypeOf
290
287
  @decorate()
291
288
  class MyBaseClass {
292
289
  base_field = 555;
@@ -295,114 +292,184 @@ class MyBaseClass {
295
292
  Object.setPrototypeOf(MyBaseClass.prototype, new BaseClass({ strict: true }));
296
293
  ```
297
294
 
298
- ### 5. ConstructorFunction Pattern
299
-
300
- ```typescript
301
- import { define, ConstructorFunction } from 'mnemonica';
295
+ ### Topologica directory structures
302
296
 
303
- const MyFn = function (this: any) {
304
- this.field = 123;
305
- } as ConstructorFunction<{ field: number }>;
297
+ [Topologica](https://github.com/mythographica/topologica)-style directory hierarchies are scanned for handler files, and a type tree is built from the directory layout:
306
298
 
307
- const MyFnType = define('MyFnType', MyFn);
308
299
  ```
309
-
310
- ## CLI Features
311
-
312
- ### Tree Output
313
-
314
- The CLI displays type hierarchy as a tree:
315
-
316
- ```bash
317
- $ npx tactica
318
-
319
- Type Hierarchy (Trie):
320
- └── UserTypeInstance
321
- └── AdminTypeInstance
322
- └── SuperAdminTypeInstance
323
- └── ProductTypeInstance
324
- ├── DigitalProductTypeInstance
325
- └── PhysicalProductTypeInstance
300
+ ai-types/
301
+ ├── Sentience/
302
+ │ ├── index.ts // exports SentienceHandler(this, data)
303
+ │ └── Consciousness/
304
+ │ └── index.ts // exports ConsciousnessHandler(this, data)
326
305
  ```
327
306
 
328
- ### Code Coverage
307
+ Auto-discovered directories (relative to `tsconfig.json`): `./ai-types`, `./types`, `./topologica-types`, plus the same names under `./src/`.
329
308
 
330
- Run tests with coverage:
309
+ Add custom directories with `--topologica`:
331
310
 
332
311
  ```bash
333
- npm run test:coverage
312
+ npx tactica --topologica ./src/ai-types,./custom/topologica
334
313
  ```
335
314
 
336
- ## Integration with Your Workflow
337
-
338
- ### .gitignore
339
-
340
- Tactica automatically adds `.tactica/` to your `.gitignore` if not already present.
341
-
342
- ### IDE Support
343
-
344
- With the Language Service Plugin:
345
- - **VS Code**: Automatic type updates on file save
346
- - **WebStorm**: Works with TypeScript service
347
- - **Vim/Neovim**: Works with coc.nvim, nvim-lspconfig
315
+ ## Property Type Inference
316
+
317
+ The analyzer infers property types from constructor bodies and class members. Supported expression patterns:
318
+
319
+ | Pattern | Inferred type |
320
+ |---|---|
321
+ | `this.x = 'foo'` | `string` |
322
+ | `this.x = 123` | `number` |
323
+ | `this.x = true` | `boolean` |
324
+ | `this.x = []` / `new Array()` | `Array<…>` |
325
+ | `this.x = {}` | `object` |
326
+ | `this.x = new Date()` | `Date` |
327
+ | `this.x = new Map()` / `new Set()` | `Map<…>` / `Set<…>` |
328
+ | `this.x = Date.now()` | `number` |
329
+ | `this.x = parseInt(…)` / `parseFloat(…)` | `number` |
330
+ | `this.x = String(…)` / `Number(…)` / `Boolean(…)` | `string` / `number` / `boolean` |
331
+ | `this.x = a + b` (where `a, b: number`) | `number` |
332
+ | `` this.x = `${a} ${b}` `` | `string` |
333
+ | `this.x = data.field` | type of `data.field` from the parameter annotation |
334
+ | `this.x = data.field \|\| []` | type of the fallback expression |
335
+ | `this.x = data.field ? a : b` | type of the truthy branch |
336
+ | `Object.assign(this, data)` | all fields of `data`'s type annotation |
337
+ | async / sync constructor functions | same rules |
338
+
339
+ When inference fails the property's type falls back to `unknown` (or `any` in some Topologica paths) — safe, and you can refine manually.
348
340
 
349
341
  ## API Reference
350
342
 
351
- ### MnemonicaAnalyzer
343
+ ### `MnemonicaAnalyzer`
352
344
 
353
- ```typescript
345
+ ```ts
354
346
  class MnemonicaAnalyzer {
355
347
  constructor(program?: ts.Program);
348
+
356
349
  analyzeFile(sourceFile: ts.SourceFile): AnalyzeResult;
357
350
  analyzeSource(sourceCode: string, fileName?: string): AnalyzeResult;
351
+
352
+ resetUsages(): void; // call between definition pass and usage pass
353
+ addTopologicaType(fullPath: string, node: TypeNode): void;
354
+
358
355
  getGraph(): TypeGraphImpl;
356
+ getDefinitions(): Map<string, DefinitionInfo>;
357
+ getUsages(): Map<string, UsageInfo[]>;
358
+ getEDSUsages(): Map<string, EDSInfo[]>;
359
+ getFlowUsages(): Map<string, FlowInfo[]>;
359
360
  }
360
361
  ```
361
362
 
362
- ### TypeGraphImpl
363
+ ### `TopologicaAnalyzer`
364
+
365
+ ```ts
366
+ class TopologicaAnalyzer {
367
+ analyzeDirectory(directoryPath: string): { types: Map<string, TypeNode>; errors: string[] };
368
+ getGraph(): TypeGraphImpl;
369
+ getErrors(): string[];
370
+ }
371
+ ```
363
372
 
364
- ```typescript
373
+ ### `TypeGraphImpl`
374
+
375
+ ```ts
365
376
  class TypeGraphImpl implements TypeGraph {
366
- roots: Map<string, TypeNode>;
377
+ roots: Map<string, TypeNode>;
367
378
  allTypes: Map<string, TypeNode>;
379
+
368
380
  addRoot(node: TypeNode): void;
369
381
  addChild(parent: TypeNode, child: TypeNode): void;
370
382
  findType(fullPath: string): TypeNode | undefined;
383
+ findTypeByName(name: string): TypeNode | undefined;
371
384
  getAllTypes(): TypeNode[];
385
+ clear(): void;
386
+
372
387
  *bfs(): Generator<TypeNode>;
373
388
  *dfs(node?: TypeNode): Generator<TypeNode>;
389
+
390
+ static createNode(name, parent, sourceFile, line, column): TypeNode;
374
391
  }
375
392
  ```
376
393
 
377
- ### TypesGenerator
394
+ ### `TypesGenerator`
378
395
 
379
- ```typescript
396
+ ```ts
380
397
  class TypesGenerator {
381
- constructor(graph: TypeGraphImpl);
382
- generate(): GeneratedTypes;
398
+ constructor(graph: TypeGraphImpl, esm?: boolean);
399
+
400
+ generateTypesFile(): GeneratedTypes; // default mode → types.ts
401
+ generateTypeRegistry(): GeneratedTypes; // default mode → registry.ts
402
+ generateGlobalAugmentation(): GeneratedTypes; // legacy mode → index.d.ts
383
403
  generateSingleType(node: TypeNode): string;
384
404
  }
385
405
  ```
386
406
 
387
- ### TypesWriter
407
+ ### `TypesWriter`
388
408
 
389
- ```typescript
409
+ ```ts
390
410
  class TypesWriter {
391
411
  constructor(outputDir?: string);
392
- write(generated: GeneratedTypes): string;
393
- writeTo(filename: string, content: string): string;
412
+
413
+ writeTypesFile(generated: GeneratedTypes): string; // → outputDir/types.ts
414
+ writeGlobalAugmentation(generated: GeneratedTypes): string; // → outputDir/index.d.ts
415
+ writeTo(filename: string, content: string): string; // → outputDir/<filename>
416
+
417
+ writeDefinitionsFile(map: Map<string, DefinitionInfo>): string; // → outputDir/definitions.json
418
+ writeUsagesFile (map: Map<string, UsageInfo[]>): string; // → outputDir/usages.json
419
+ writeEDSFile (map: Map<string, EDSInfo[]>): string; // → outputDir/eds.json
420
+ writeFlowFile (map: Map<string, FlowInfo[]>): string; // → outputDir/flow.json
421
+
422
+ write(generated: GeneratedTypes): string; // legacy alias for writeTypesFile
394
423
  clean(): void;
395
424
  getOutputDir(): string;
396
425
  }
397
426
  ```
398
427
 
428
+ ### Types
429
+
430
+ `TacticaConfig`, `TypeNode`, `TypeGraph`, `PropertyInfo`, `ConstructorParamInfo`, `AnalyzeResult`, `AnalyzeError`, `GeneratedTypes`, `DefinitionInfo`, `UsageInfo`, `UsagesJson`, `DefinitionsJson`, `EDSInfo`, `EDSJson`, `EDSKind`, `FlowInfo`, `FlowJson`, `FlowKind` — all exported from `@mnemonica/tactica`. See [`src/types.ts`](src/types.ts) for the full schema.
431
+
432
+ ## EDS (Execution Data Storage) Tracking
433
+
434
+ When enabled, tactica detects execution-flow patterns alongside type definitions. Each detection emits a record into `.tactica/eds.json`.
435
+
436
+ | Function | EDS kind | Description |
437
+ |---|---|---|
438
+ | `wrap(fn)`, `wrapArgs(fn)`, `wrapInstanceMethods(obj)` | `wrap` | Wrap a function / arguments / instance methods for context propagation |
439
+ | `link(parent, child)`, `runWithInstance(inst, fn)` | `link` | Link two instances in the EDS chain |
440
+ | `getLastContext()`, `getErrorInstance(err)` | `contextConsume` | Read the current / error-attached EDS context |
441
+ | `enrichError(err, inst)` | `errorEnrich` | Attach an instance to an error |
442
+ | `attachHooks(types)` | `hookAttach` | Install hooks on the given types |
443
+ | `createDiveInterceptor()` | `adapterUse` | NestJS interceptor adapter |
444
+ | `createDivePlugin()` | `adapterUse` | Fastify plugin adapter |
445
+ | `createDiveMiddleware()` | `adapterUse` | Express middleware adapter |
446
+
447
+ `eds.json` structure:
448
+
449
+ ```json
450
+ {
451
+ "version": "1.0",
452
+ "generatedAt": "2026-05-22T…",
453
+ "eds": {
454
+ "UserEntity": [
455
+ {
456
+ "location": "/project/src/queue.ts:45:12",
457
+ "kind": "wrap",
458
+ "code": "wrap(process)",
459
+ "targetType": "UserEntity"
460
+ }
461
+ ]
462
+ }
463
+ }
464
+ ```
465
+
399
466
  ## How It Works
400
467
 
401
- 1. **Parse**: TypeScript AST is parsed to find `define()` and `decorate()` calls
402
- 2. **Analyze**: The analyzer extracts type names, properties, and hierarchy
403
- 3. **Graph**: A Trie (tree) structure represents the type hierarchy
404
- 4. **Generate**: TypeScript declarations are generated from the graph
405
- 5. **Output**: Files are written to `.tactica/` directory
468
+ 1. **Parse** — load `tsconfig.json`, build a `ts.Program`, walk each source file's AST.
469
+ 2. **Detect** — find `define()` and `@decorate()` calls, plus `lookupTyped` lookups, `new` expressions, and EDS / flow patterns.
470
+ 3. **Graph** — build a Trie of types in `TypeGraphImpl`, with parent links via the chain of `.define()` calls and `@decorate(Parent)` references.
471
+ 4. **Generate** — emit `types.ts`, `registry.ts`, `index.ts` (default mode) or `index.d.ts` (legacy mode), plus `definitions.json`, `usages.json`, `flow.json`, and optionally `eds.json`.
472
+ 5. **Write** — files land in the output directory (default `.tactica/`).
406
473
 
407
474
  ```
408
475
  Type Hierarchy (Trie)
@@ -416,60 +483,71 @@ Type Hierarchy (Trie)
416
483
  └── properties: { items: Item[] }
417
484
  ```
418
485
 
419
- ## Troubleshooting
486
+ ## Known Limitations
420
487
 
421
- ### Types not updating
488
+ ### Rest / spread parameters with tuple types
422
489
 
423
- 1. Check that the plugin is loaded in `tsconfig.json`
424
- 2. Restart TypeScript service (VS Code: Command Palette → "TypeScript: Restart TS Server")
425
- 3. Verify file patterns in `include`/`exclude` config
490
+ The analyzer cannot extract property types from rest parameters that use tuple types:
426
491
 
427
- ### Generated types have errors
492
+ ```ts
493
+ // ❌ Property types resolve to unknown
494
+ export const UserEntity = define('UserEntity', function (
495
+ this: UserEntityInstance,
496
+ ...args: [{ id: string; email: string; name: string }, ...unknown[]]
497
+ ) {
498
+ const data = args[0];
499
+ Object.assign(this, data);
500
+ });
501
+ ```
428
502
 
429
- 1. Ensure all mnemonica types have explicit type annotations
430
- 2. Check that `define()` calls use string literals for type names
431
- 3. Verify property types are valid TypeScript
503
+ **Workaround:** use a direct parameter with the same type annotation.
432
504
 
433
- ### Plugin not working
505
+ ### Deep nested property access
434
506
 
435
- ```bash
436
- # Test with CLI first
437
- npx tactica --verbose
507
+ Multi-level property access in constructors falls back to `unknown`:
438
508
 
439
- # Check for parsing errors
440
- npx tactica --verbose 2>&1 | grep -i error
509
+ ```ts
510
+ // ⚠️ this.name's inferred type is unknown
511
+ const UserType = define('UserType', function (this: any, data: { profile: { name: string } }) {
512
+ this.name = data.profile.name;
513
+ });
441
514
  ```
442
515
 
443
- ## Related Projects
516
+ **Workaround:** flatten the data structure or use a direct parameter.
444
517
 
445
- - [mnemonica](https://github.com/wentout/mnemonica) - Instance inheritance system
446
- - [@mnemonica/topologica](https://github.com/mythographica/topologica) - Filesystem-based type discovery
447
- - [typeomatica](https://github.com/wentout/typeomatica) - Runtime type checking with `@Strict` decorator and `BaseClass`
518
+ ### `exposeInstanceMethods` is not parsed
448
519
 
449
- ## Testing
520
+ Mnemonica accepts the `exposeInstanceMethods` option at runtime, but tactica's `extractConfig()` only parses `strictChain` and `blockErrors` from `define()` options today. The runtime behavior is unaffected.
450
521
 
451
- Tactica includes comprehensive test coverage:
522
+ ### Single-pass analysis without binding
452
523
 
453
- ```bash
454
- # Run all tests
455
- npm test
524
+ The analyzer does not use `ts.Program.getTypeChecker()` for resolution, so cross-file type references in unusual shapes may resolve to `unknown`.
525
+
526
+ ## Troubleshooting
527
+
528
+ **Generated types are missing properties** — make sure the constructor function has either an explicit `this:` type annotation, or that the data parameter has an inline / aliased type (`{ id: string }` or `Foo` where `type Foo = { id: string }`). Property inference walks one level of indirection.
529
+
530
+ **`new user.AdminType()` shows red squiggles** — confirm `.tactica/types.ts` is in your `tsconfig.json` `include`. If you use `lookupTyped`, also import `.tactica/registry.ts` somewhere.
531
+
532
+ **`mnemographica` (VS Code extension) shows no graph** — confirm `.tactica/definitions.json` and `.tactica/types.ts` exist; run `npx tactica --verbose` to see what was discovered.
456
533
 
457
- # Run with coverage
458
- npm run test:coverage
534
+ **`@mnemonica/dive` users**: EDS data should appear in `.tactica/eds.json`. If it's missing, run with `--eds --verbose` to force-enable and check the log.
535
+
536
+ ## Testing
537
+
538
+ ```bash
539
+ npm test # mocha + chai, all suites
540
+ npm run test:coverage # nyc text report
541
+ npm run test:coverage:html # nyc html report
459
542
  ```
460
543
 
461
- Test suites include:
462
- - **Analyzer tests** - Core AST parsing functionality
463
- - **Generator tests** - TypeScript declaration generation
464
- - **Writer tests** - File I/O operations
465
- - **Integration tests** - End-to-end workflows
466
- - **Example tests** - Patterns from `tactica-test/` project
467
- - **Typeomatica tests** - Combined mnemonica + typeomatica patterns
544
+ ## Related Projects
545
+
546
+ - [mnemonica](https://github.com/wentout/mnemonica) — the instance-inheritance runtime that this generator targets
547
+ - [typeomatica](https://github.com/wentout/typeomatica) — companion runtime type guards (`@Strict`, `BaseClass`)
548
+ - [@mnemonica/topologica](https://github.com/mythographica/topologica) — filesystem-based type discovery (consumed by tactica's `--topologica` mode)
549
+ - [mnemographica](https://github.com/mythographica/mnemographica) — VS Code extension that consumes tactica's `.tactica/` output for IDE features
468
550
 
469
551
  ## License
470
552
 
471
553
  MIT
472
-
473
- ## Contributing
474
-
475
- Contributions welcome! Please read the [Contributing Guide](CONTRIBUTING.md) for details.