mnemonica 1.0.1 → 1.0.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.
Files changed (99) hide show
  1. package/.ai/AGENTS.md +1 -1
  2. package/.ai/ONBOARDING.md +3 -2
  3. package/.ai/PROTOTYPE-CHAIN.md +122 -0
  4. package/.ai/TACTICA-DEEP-DIVE.md +52 -49
  5. package/.ai/TACTICA-RULES.md +8 -8
  6. package/.ai/ask/AGENTS.md +15 -9
  7. package/.ai/rules-skill/contributing.md +1 -1
  8. package/.ai/rules-skill/define-patterns.md +8 -2
  9. package/.ai/rules-skill/ecosystem.md +4 -4
  10. package/.ai/rules-skill/instance-methods.md +48 -15
  11. package/.ai/rules-skill/lookup-typed.md +17 -19
  12. package/.ai/rules-skill/type-system.md +12 -2
  13. package/AGENTS.md +25 -15
  14. package/CONTRIBUTING.md +8 -2
  15. package/FOR_HUMANS.md +193 -218
  16. package/README.md +84 -47
  17. package/SKILL.md +4 -4
  18. package/build/api/errors/exceptionConstructor.js +20 -12
  19. package/build/api/errors/index.js +9 -5
  20. package/build/api/errors/throwModificationError.js +15 -9
  21. package/build/api/index.js +35 -2
  22. package/build/api/types/InstanceCreator.js +5 -2
  23. package/build/api/types/Mnemosyne.d.ts +6 -6
  24. package/build/api/types/Mnemosyne.js +43 -120
  25. package/build/api/types/Props.js +5 -3
  26. package/build/api/types/TypeProxy.js +13 -9
  27. package/build/api/types/compileNewModificatorFunctionBody.js +14 -8
  28. package/build/api/types/createInstanceModificator.js +1 -1
  29. package/build/api/types/index.js +56 -13
  30. package/build/api/utils/index.js +21 -12
  31. package/build/constants/index.js +11 -8
  32. package/build/descriptors/types/index.js +79 -26
  33. package/build/index.d.ts +9 -10
  34. package/build/index.js +65 -25
  35. package/build/types/index.d.ts +76 -32
  36. package/build/types/index.js +1 -1
  37. package/build/utils/clone.d.ts +1 -0
  38. package/build/utils/clone.js +11 -0
  39. package/build/utils/collectConstructors.js +5 -3
  40. package/build/utils/exception.d.ts +1 -0
  41. package/build/utils/exception.js +14 -0
  42. package/build/utils/extract.d.ts +2 -3
  43. package/build/utils/extract.js +3 -2
  44. package/build/utils/fork.d.ts +1 -0
  45. package/build/utils/fork.js +33 -0
  46. package/build/utils/index.d.ts +2 -3
  47. package/build/utils/index.js +21 -7
  48. package/build/utils/merge.d.ts +2 -1
  49. package/build/utils/merge.js +10 -6
  50. package/build/utils/parent.d.ts +4 -1
  51. package/build/utils/parent.js +7 -7
  52. package/build/utils/parse.d.ts +2 -12
  53. package/build/utils/parse.js +1 -1
  54. package/build/utils/pick.d.ts +4 -3
  55. package/build/utils/pick.js +4 -5
  56. package/build/utils/sibling.d.ts +1 -0
  57. package/build/utils/sibling.js +25 -0
  58. package/build/utils/toJSON.d.ts +1 -1
  59. package/build/utils/toJSON.js +3 -2
  60. package/docs/UTILS.md +184 -0
  61. package/docs/ai-learning-trajectory.md +9 -9
  62. package/docs/async-constructors.md +21 -1
  63. package/docs/empathy-in-ai.md +170 -0
  64. package/docs/hott-primer.md +47 -0
  65. package/docs/performance-vs-security.md +2 -2
  66. package/docs/prototype-chain.md +127 -0
  67. package/docs/purpose.md +42 -11
  68. package/docs/tactica-pattern.md +20 -20
  69. package/docs/theory-of-operations.md +224 -0
  70. package/docs/typed-lookup.md +30 -26
  71. package/module/index.js +0 -1
  72. package/package.json +13 -7
  73. package/src/api/errors/exceptionConstructor.ts +14 -9
  74. package/src/api/errors/index.ts +8 -4
  75. package/src/api/errors/throwModificationError.ts +10 -9
  76. package/src/api/types/InstanceCreator.ts +5 -8
  77. package/src/api/types/Mnemosyne.ts +72 -231
  78. package/src/api/types/Props.ts +4 -2
  79. package/src/api/types/TypeProxy.ts +7 -11
  80. package/src/api/types/compileNewModificatorFunctionBody.ts +13 -7
  81. package/src/api/types/createInstanceModificator.ts +13 -0
  82. package/src/api/types/index.ts +15 -8
  83. package/src/api/utils/index.ts +16 -14
  84. package/src/constants/index.ts +6 -9
  85. package/src/descriptors/types/index.ts +45 -25
  86. package/src/index.ts +60 -71
  87. package/src/types/index.ts +188 -70
  88. package/src/utils/clone.ts +11 -0
  89. package/src/utils/collectConstructors.ts +4 -2
  90. package/src/utils/exception.ts +16 -0
  91. package/src/utils/extract.ts +5 -2
  92. package/src/utils/fork.ts +57 -0
  93. package/src/utils/index.ts +32 -13
  94. package/src/utils/merge.ts +18 -6
  95. package/src/utils/parent.ts +16 -7
  96. package/src/utils/parse.ts +5 -4
  97. package/src/utils/pick.ts +10 -2
  98. package/src/utils/sibling.ts +33 -0
  99. package/src/utils/toJSON.ts +3 -2
package/.ai/AGENTS.md CHANGED
@@ -132,7 +132,7 @@ Based on observed agent behavior:
132
132
  | [`rules-skill/philosophy.md`](./rules-skill/philosophy.md) | HoTT concepts applied to mnemonica's self-reflection model |
133
133
  | [`rules-skill/ecosystem.md`](./rules-skill/ecosystem.md) | PACT framework: personas, collaboration modes, integration points |
134
134
  | [`rules-skill/contributing.md`](./rules-skill/contributing.md) | Behavioral guidelines for AI contributors |
135
- | [`TACTICA-DEEP-DIVE.md`](./TACTICA-DEEP-DIVE.md) | Comprehensive tactica + lookupTyped technical guide |
135
+ | [`TACTICA-DEEP-DIVE.md`](./TACTICA-DEEP-DIVE.md) | Comprehensive tactica + lookup technical guide |
136
136
  | [`../docs/async-constructors.md`](../docs/async-constructors.md) | Async constructors: super() return values, native class mixing, chains |
137
137
 
138
138
  - Main README: [`../README.md`](../README.md)
package/.ai/ONBOARDING.md CHANGED
@@ -25,7 +25,7 @@ const admin = new user.Admin({ role: 'admin' }); // inherits from user instance
25
25
 
26
26
  **Why not TypeScript interfaces alone?** TypeScript is structural at compile time: a `Payment` and an `Invoice` with identical fields are interchangeable to the type checker. At runtime, `processPayment(invoice)` silently succeeds and produces wrong results. mnemonica's runtime types are nominal — the type IS its name, established at `define()` time and stable thereafter.
27
27
 
28
- **Why not Object.assign / spread?** `{...raw, ...apiResult}` is one flat object with no lineage. You cannot answer "which step set `amount`?" without reading the source. mnemonica's prototype chain preserves every ancestor; `enriched.parent('ApiResult')` returns the exact API response object.
28
+ **Why not Object.assign / spread?** `{...raw, ...apiResult}` is one flat object with no lineage. You cannot answer "which step set `amount`?" without reading the source. mnemonica's prototype chain preserves every ancestor; `utils.parent(enriched, 'ApiResult')` returns the exact API response object.
29
29
 
30
30
  ---
31
31
 
@@ -103,7 +103,7 @@ src/
103
103
  Key components:
104
104
  - **TypeProxy** — wraps type constructors
105
105
  - **InstanceCreator** — orchestrates construction lifecycle
106
- - **Mnemosyne** — handles instance method access via Proxy
106
+ - **Mnemosyne** — root memory proxy; stores construction context and resolves subtype lookups
107
107
 
108
108
  ---
109
109
 
@@ -159,6 +159,7 @@ Read [`../docs/async-constructors.md`](../docs/async-constructors.md) for the `s
159
159
  |------|------|
160
160
  | Coding standards, type rules | [`CODE.md`](./CODE.md) |
161
161
  | Design patterns, constraints | [`ARCHITECT.md`](./ARCHITECT.md) |
162
+ | Prototype chain internals | [`PROTOTYPE-CHAIN.md`](./PROTOTYPE-CHAIN.md) |
162
163
  | Debugging commands, issues | [`DEBUG.md`](./DEBUG.md) |
163
164
  | Async constructor deep dive | [`../docs/async-constructors.md`](../docs/async-constructors.md) |
164
165
  | tactica type-safe lookup | [`TACTICA-RULES.md`](./TACTICA-RULES.md) |
@@ -0,0 +1,122 @@
1
+ # Prototype Chain Architecture
2
+
3
+ ## Why this document exists
4
+
5
+ mnemonica instances do not have a normal JavaScript prototype chain. Between every parent instance and child instance there are two intermediate objects. If you are changing construction, props storage, `instanceof`, or subtype lookup, you need to know what those objects are and why they exist.
6
+
7
+ ## High-level shape
8
+
9
+ For a chain `user → admin → superadmin` the instance-level chain is:
10
+
11
+ ```
12
+ superadmin
13
+ └── SuperAdminType.prototype
14
+ └── SuperAdminMemory
15
+ └── admin
16
+ └── AdminType.prototype
17
+ └── AdminMemory
18
+ └── user
19
+ └── UserType.prototype
20
+ └── UserMemory
21
+ └── root Mnemosyne Proxy
22
+ └── Mnemonica instance
23
+ └── Mnemonica.prototype
24
+ └── uranus
25
+ ```
26
+
27
+ `SuperAdminMemory`, `AdminMemory`, and `UserMemory` are the **memory layers**. Only `UserMemory`’s parent is the real `Mnemosyne` constructor result wrapped in a Proxy. The deeper memory layers are plain objects that play the same role.
28
+
29
+ ## The three levels contributed by each type
30
+
31
+ Every mnemonica type adds three levels to the chain:
32
+
33
+ 1. **Instance object** — the object returned by `new parent.ChildType(...)`. Holds only the own properties assigned by the user constructor.
34
+ 2. **`ModificatorType.prototype`** — the user-prototype layer. Holds methods and getters captured at `define()` time. It gets a fresh `constructor` property pointing to the per-construction `ModificatorType` function.
35
+ 3. **Memory layer** — a plain object whose `[[Prototype]]` is the parent instance. It is the `WeakMap` key for internal props and has a `constructor` getter returning `ModificatorType`.
36
+
37
+ ## Files and functions involved
38
+
39
+ - `src/api/types/compileNewModificatorFunctionBody.ts` builds the `ModificatorType`, the actual function or class used for `new`. For function constructors it preserves `new.target`; for class constructors it generates a class that extends the user class so `super(...)` works.
40
+ - `src/api/types/createInstanceModificator.ts` is the default `ModificationConstructor`. It creates the memory layer, wires `_addProps`, copies the captured user prototype onto `ModificatorType.prototype`, and rewires `ModificatorType.prototype` to inherit from the memory layer.
41
+ - `src/api/types/Props.ts` implements `_addProps` and `getProps`. Internal props are stored in a module-level `WeakMap` keyed by the memory-layer object.
42
+ - `src/api/types/Mnemosyne.ts` builds the root memory proxy. Only the root type goes through `createMnemosyne`; all deeper memory layers are plain objects produced by `createInstanceModificator`.
43
+ - `src/api/types/InstanceCreator.ts` orchestrates the pipeline and passes `existentInstance` (the parent instance) into the `ModificationConstructor`.
44
+ - `src/api/types/TypeProxy.ts` is the constructor-like object returned by `define()`. Its `construct` trap creates the root Mnemosyne proxy and then invokes `InstanceCreator`.
45
+
46
+ ## Capturing the user prototype
47
+
48
+ At `define()` time the `TypeDescriptor` stores `proto` — a snapshot of the user constructor’s `.prototype`. During construction that snapshot is copied onto the fresh `ModificatorType.prototype`. The user constructor’s original `.prototype` is restored after construction, so it is never part of the instance chain.
49
+
50
+ This is why the same constructor function can be reused across multiple type definitions without bleeding prototype state.
51
+
52
+ ## Internal props storage
53
+
54
+ `_addProps` receives the memory-layer object and stores a `value` object in the module `WeakMap` keyed by that object. The stored object contains getters for:
55
+
56
+ - `__type__`
57
+ - `__parent__`
58
+ - `__args__`
59
+ - `__timestamp__`
60
+ - `__creator__`
61
+ - `__collection__`
62
+ - `__subtypes__`
63
+ - `__proto_proto__`
64
+ - `__stack__`
65
+
66
+ `getProps(instance)` walks the prototype chain from the instance until it finds the first object with a `WeakMap` entry. That is always the instance’s own memory layer. `parent(instance)` reads `__parent__` from that props object; it is the `existentInstance` passed to `InstanceCreator`.
67
+
68
+ ## `_setSelf` and async construction
69
+
70
+ `_setSelf(instance)` is called at the end of successful construction. It adds one more getter to the props object:
71
+
72
+ ```js
73
+ __self__: () => instance
74
+ ```
75
+
76
+ Then it stores the props object in the `WeakMap` keyed by the instance itself. This solves the async-constructor problem.
77
+
78
+ When a constructor returns a Promise, the initial value of `new Constructor()` is that Promise. `makeAwaiter` waits for resolution, then checks:
79
+
80
+ ```js
81
+ if (props.__self__ !== self.inheritedInstance) {
82
+ self.postProcessing(type);
83
+ }
84
+ ```
85
+
86
+ If `__self__` is missing or does not match the resolved instance, post-processing has not run yet, so it runs validation and hooks. If it matches, post-processing has already happened and is skipped. The constructor can therefore return a Promise, and mnemonica finalizes the instance only after the Promise resolves — without double-initializing or losing the construction context.
87
+
88
+ ## Subtype lookup
89
+
90
+ Only the root has a Proxy. When you access `admin.SomeSubType`, the property lookup walks:
91
+
92
+ ```
93
+ admin → AdminType.prototype → AdminMemory → user → UserType.prototype → UserMemory → root Mnemosyne Proxy
94
+ ```
95
+
96
+ The Proxy’s `get` trap calls `prepareSubtypeForConstruction(prop, receiver)`. It uses `_getProps` to find the memory layer of `receiver` by walking from `Reflect.getPrototypeOf(receiver)`, reads `__subtypes__`, and returns a `SubTypeProxy` that closes over the subtype `TypeDef` and the parent instance.
97
+
98
+ This means the root Proxy serves the entire branch below it.
99
+
100
+ ## Why the root Proxy is kept
101
+
102
+ The root Proxy exists so that **subtypes defined after an instance is created are still visible on that instance**.
103
+
104
+ If subtype constructors were attached to the memory layer at construction time, an instance would only know about the subtypes that existed when it was built. Any `ParentType.define('NewSubType', ...)` call afterward would not appear on existing instances.
105
+
106
+ The Proxy avoids that by doing a live lookup in the type’s `__subtypes__` Map on every property access. Instances do not carry a snapshot of the Trie; they delegate to the current type graph. That decouples instance lifetime from type-graph evolution and is the main reason a Proxy is necessary. The other Proxy-based mechanisms in the codebase could be replaced with simpler constructs, but this live lookup is hard to achieve without a Proxy.
107
+
108
+ ## Classes vs functions
109
+
110
+ The final chain shape is identical for class and function definitions. The difference is only in how `ModificatorType` runs the user constructor:
111
+
112
+ - **Function:** the wrapper temporarily swaps `ConstructHandler.prototype`, calls `new ConstructHandler(...)`, then restores it.
113
+ - **Class:** the generated class `extends ConstructHandler`, calls `super(...)`, then runs the post-construction handler.
114
+
115
+ In both cases the resulting prototype chain is rewired to `instance → ModificatorType.prototype → memoryLayer → parent`.
116
+
117
+ ## Common mistakes
118
+
119
+ - **Assuming internal props are own properties of the instance.** They are stored in a `WeakMap` keyed by the memory layer.
120
+ - **Assuming `instance.constructor` is the user’s original function.** It points to the per-construction `ModificatorType`, which delegates behavior to the user handler.
121
+ - **Thinking the `Mnemosyne` constructor is used for every instance.** It is used once for the root; subtype memory layers are plain objects.
122
+ - **Expecting the user constructor’s `.prototype` to be in the chain.** It is snapshotted and restored; only copies of its descriptors end up on `ModificatorType.prototype`.
@@ -12,7 +12,7 @@
12
12
  3. [How tactica Bridges the Gap](#3-how-tactica-bridges-the-gap)
13
13
  4. [Declaration Merging: The TypeScript Mechanism](#4-declaration-merging-the-typescript-mechanism)
14
14
  5. [tsconfig.json Setup](#5-tsconfigjson-setup)
15
- 6. [How lookupTyped Works](#6-how-lookuptyped-works)
15
+ 6. [How lookup Works](#6-how-lookuptyped-works)
16
16
  7. [Why Direct Import Fails](#7-why-direct-import-fails)
17
17
  8. [The Epiphany: Zero-Cast Chaining](#8-the-epiphany-zero-cast-chaining)
18
18
  9. [Common Mistakes](#9-common-mistakes)
@@ -188,12 +188,12 @@ declare module 'some-lib' {
188
188
 
189
189
  ### 4.3 How tactica Uses It
190
190
 
191
- mnemonica core defines:
191
+ mnemonica core defines an empty `TypeRegistry`:
192
192
 
193
193
  ```typescript
194
- // In mnemonica core (src/types/index.ts)
194
+ // In mnemonica core (src/index.ts)
195
195
  export interface TypeRegistry {
196
- [key: string]: never; // Empty by default, prevents accidental usage
196
+ // Intentionally empty. Augment via declaration merging.
197
197
  }
198
198
  ```
199
199
 
@@ -203,37 +203,33 @@ tactica generates:
203
203
  // In .tactica/registry.ts
204
204
  declare module 'mnemonica' {
205
205
  interface TypeRegistry {
206
- 'RequestData': new (...args: unknown[]) => RequestData;
206
+ 'RequestData': TypeConstructor<RequestData>;
207
207
  }
208
208
  }
209
209
  ```
210
210
 
211
211
  After this augmentation, `TypeRegistry` contains the `'RequestData'` key. Any code that imports from `'mnemonica'` sees the augmented `TypeRegistry`.
212
212
 
213
- ### 4.4 Why `[key: string]: never`?
213
+ ### 4.4 Why an Empty `TypeRegistry`?
214
214
 
215
- The `[key: string]: never` pattern in the base `TypeRegistry` is a TypeScript trick:
215
+ The base `TypeRegistry` is intentionally empty:
216
216
 
217
217
  ```typescript
218
- interface TypeRegistry {
219
- [key: string]: never;
220
- }
221
-
222
- type Test = TypeRegistry['anything']; // Error: Type 'anything' is not assignable to type 'never'
218
+ export interface TypeRegistry {}
223
219
  ```
224
220
 
225
- This means **you cannot use `TypeRegistry` without augmentation**. It forces you to run tactica and generate the registry. Without it, any `lookupTyped('Something')` would be a compile error.
221
+ Without augmentation, `lookup('Something')` falls back to the broad `TypeClass | undefined` return type. This keeps the library usable without a registry while still allowing full type inference once you augment it.
226
222
 
227
223
  Once augmented:
228
224
 
229
225
  ```typescript
230
226
  declare module 'mnemonica' {
231
227
  interface TypeRegistry {
232
- 'RequestData': new (...args: unknown[]) => RequestData;
228
+ 'RequestData': TypeConstructor<RequestData>;
233
229
  }
234
230
  }
235
231
 
236
- type Test = TypeRegistry['RequestData']; // OK returns the constructor type
232
+ const RequestData = lookup('RequestData'); // typed constructor | undefined
237
233
  ```
238
234
 
239
235
  ---
@@ -272,30 +268,37 @@ For declaration merging to work, the `.tactica/registry.ts` file must be include
272
268
  TypeScript only processes files in the `include` array. If `.tactica/` is not included:
273
269
 
274
270
  ```typescript
275
- import { lookupTyped } from 'mnemonica';
276
- const RequestData = lookupTyped('RequestData');
277
- // Error: 'RequestData' is not assignable to parameter of type 'never'
271
+ import { lookup } from 'mnemonica';
272
+ const RequestData = lookup('RequestData');
273
+ // RequestData is typed as TypeClass | undefined instead of the concrete constructor
278
274
  ```
279
275
 
280
- Because without the augmentation, `TypeRegistry` still has `[key: string]: never`.
276
+ Because without the augmentation, `TypeRegistry` is empty and `lookup()` falls back to its unaugmented overload.
281
277
 
282
278
  ---
283
279
 
284
- ## 6. How lookupTyped Works
280
+ ## 6. How lookup Works
285
281
 
286
282
  ### 6.1 Runtime Behavior
287
283
 
288
284
  ```typescript
289
285
  // In mnemonica core (src/index.ts)
290
- export const lookupTyped = function <const K extends keyof TypeRegistry>(
286
+ export function lookup<const K extends keyof TypeRegistry>(
287
+ this: unknown,
291
288
  TypeNestedPath: K
292
- ): TypeRegistry[K] {
293
- // Runtime delegates to lookup()
294
- return types.lookup(TypeNestedPath as string) as TypeRegistry[K];
289
+ ): TypeRegistry[K] | undefined;
290
+ export function lookup(
291
+ this: unknown,
292
+ TypeNestedPath: string
293
+ ): TypeClass | undefined {
294
+ // Runtime delegates to types.lookup()
295
+ const types = checkThis(this) ? defaultTypes : this || defaultTypes;
296
+ return types.lookup(TypeNestedPath);
297
+ }
295
298
  };
296
299
  ```
297
300
 
298
- At runtime, `lookupTyped('RequestData')`:
301
+ At runtime, `lookup('RequestData')`:
299
302
  1. Calls `types.lookup('RequestData')`
300
303
  2. Searches the default types collection
301
304
  3. Returns the constructor function
@@ -307,7 +310,7 @@ At runtime, `lookupTyped('RequestData')`:
307
310
  At compile time, TypeScript sees:
308
311
 
309
312
  ```typescript
310
- const RequestData = lookupTyped('RequestData');
313
+ const RequestData = lookup('RequestData');
311
314
  ```
312
315
 
313
316
  TypeScript infers:
@@ -350,7 +353,7 @@ It checks the constructor signature → `new (...args: unknown[]) => RequestData
350
353
 
351
354
  ## 7. Why Direct Import Fails
352
355
 
353
- Let's trace what happens with direct import vs `lookupTyped`.
356
+ Let's trace what happens with direct import vs `lookup`.
354
357
 
355
358
  ### 7.1 Direct Import
356
359
 
@@ -370,12 +373,12 @@ const routeData = new requestData.RouteData({ ... });
370
373
 
371
374
  The direct import returns the constructor, but TypeScript's type for that constructor doesn't include `.RouteData`. The `define()` function's return type is not augmented with sub-constructor information.
372
375
 
373
- ### 7.2 lookupTyped
376
+ ### 7.2 lookup
374
377
 
375
378
  ```typescript
376
- import { lookupTyped } from 'mnemonica';
379
+ import { lookup } from 'mnemonica';
377
380
 
378
- const RequestData = lookupTyped('RequestData');
381
+ const RequestData = lookup('RequestData');
379
382
  // ↑
380
383
  // TypeScript sees: TypeRegistry['RequestData']
381
384
  // Which is: new (...args: unknown[]) => RequestData
@@ -431,9 +434,9 @@ Creates:
431
434
 
432
435
  ```typescript
433
436
  // src/server.ts or any file
434
- import { lookupTyped } from 'mnemonica';
437
+ import { lookup } from 'mnemonica';
435
438
 
436
- const RequestData = lookupTyped('RequestData');
439
+ const RequestData = lookup('RequestData');
437
440
  ```
438
441
 
439
442
  ### Step 4: Chain (Zero Casts)
@@ -463,7 +466,7 @@ const requestData = new RequestData({ ... }) as unknown as RequestDataT;
463
466
 
464
467
  **Why wrong:** You are fighting the type system instead of using it. Every cast is a bug waiting to happen. If the type changes, the cast still compiles but may break at runtime.
465
468
 
466
- **Fix:** Use `lookupTyped`.
469
+ **Fix:** Use `lookup`.
467
470
 
468
471
  ### 9.2 "I'll import the generated types too"
469
472
 
@@ -476,23 +479,23 @@ const requestData = new RequestData({ ... }) as unknown as RequestDataT;
476
479
 
477
480
  **Why wrong:** You are importing both the runtime constructor AND the generated type, then bridging them with a cast. This is twice the work and still unsafe.
478
481
 
479
- **Fix:** Use `lookupTyped` — it gives you both the runtime constructor AND the type in one call.
482
+ **Fix:** Use `lookup` — it gives you both the runtime constructor AND the type in one call.
480
483
 
481
- ### 9.3 "lookupTyped only works in route handlers"
484
+ ### 9.3 "lookup only works in route handlers"
482
485
 
483
486
  ```typescript
484
487
  // ❌ WRONG (unnecessary)
485
488
  app.get('/test', async () => {
486
- const RequestData = lookupTyped('RequestData');
489
+ const RequestData = lookup('RequestData');
487
490
  const requestData = new RequestData({ ... });
488
491
  });
489
492
  ```
490
493
 
491
- **Why wrong:** `lookupTyped` is a runtime lookup, but it's deterministic and cached. Calling it at module level is perfectly fine and more efficient:
494
+ **Why wrong:** `lookup` is a runtime lookup, but it's deterministic and cached. Calling it at module level is perfectly fine and more efficient:
492
495
 
493
496
  ```typescript
494
497
  // ✅ CORRECT
495
- const RequestData = lookupTyped('RequestData');
498
+ const RequestData = lookup('RequestData');
496
499
 
497
500
  app.get('/test', async () => {
498
501
  const requestData = new RequestData({ ... });
@@ -504,19 +507,19 @@ app.get('/test', async () => {
504
507
  ```typescript
505
508
  // ❌ WRONG (mixing patterns)
506
509
  import { RequestData } from './collections/requestTypes.js';
507
- const TypedRequestData = lookupTyped('RequestData');
510
+ const TypedRequestData = lookup('RequestData');
508
511
 
509
512
  app.decorate('RequestData', RequestData); // direct import
510
- const requestData = new TypedRequestData({ ... }); // lookupTyped
513
+ const requestData = new TypedRequestData({ ... }); // lookup
511
514
  ```
512
515
 
513
516
  **Why wrong:** You're maintaining two references to the same object.
514
517
 
515
- **Fix:** Use `lookupTyped` for everything. The returned constructor is the same object:
518
+ **Fix:** Use `lookup` for everything. The returned constructor is the same object:
516
519
 
517
520
  ```typescript
518
521
  // ✅ CORRECT
519
- const RequestData = lookupTyped('RequestData');
522
+ const RequestData = lookup('RequestData');
520
523
 
521
524
  app.decorate('RequestData', RequestData);
522
525
  const requestData = new RequestData({ ... });
@@ -527,7 +530,7 @@ const requestData = new RequestData({ ... });
527
530
  ```typescript
528
531
  // You add a new property to the define() call
529
532
  // But forget to run tactica
530
- const RequestData = lookupTyped('RequestData');
533
+ const RequestData = lookup('RequestData');
531
534
  const requestData = new RequestData({ newField: 'value' });
532
535
  // Error: Object literal may only specify known properties
533
536
  ```
@@ -544,10 +547,10 @@ npm run tactica
544
547
 
545
548
  | I want to... | Do this | Don't do this |
546
549
  |---|---|---|
547
- | Get a typed constructor | `const T = lookupTyped('T')` | `import { T } from './collections/T.js'` |
550
+ | Get a typed constructor | `const T = lookup('T')` | `import { T } from './collections/T.js'` |
548
551
  | Create an instance | `new T({ ... })` | `new T({ ... }) as unknown as TT` |
549
552
  | Chain to a child type | `new instance.Child({ ... })` | `new (instance as any).Child({ ... })` |
550
- | Decorate Fastify | `app.decorate('T', T)` with `lookupTyped` | Direct import + separate lookupTyped |
553
+ | Decorate Fastify | `app.decorate('T', T)` with `lookup` | Direct import + separate lookup |
551
554
  | Add a new property | Modify `define()` → run `tactica` | Modify `define()` + manual cast |
552
555
  | Fix "Property does not exist" | Run `tactica` to regenerate | Add `as any` or `as unknown` |
553
556
 
@@ -575,9 +578,9 @@ This pattern works because:
575
578
 
576
579
  1. **mnemonica creates a Trie at runtime** — sub-constructors exist, instances can spawn children
577
580
  2. **tactica teaches TypeScript about the Trie** — via declaration merging of `TypeRegistry`
578
- 3. **lookupTyped retrieves the typed constructor** — compile-time type safety + runtime correctness
579
- 4. **The same object is returned either way** — `import { T }` and `lookupTyped('T')` are identical at runtime
581
+ 3. **lookup retrieves the typed constructor** — compile-time type safety + runtime correctness
582
+ 4. **The same object is returned either way** — `import { T }` and `lookup('T')` are identical at runtime
580
583
 
581
- **The only difference is TypeScript's knowledge.** Use `lookupTyped` and let TypeScript help you.
584
+ **The only difference is TypeScript's knowledge.** Use `lookup` and let TypeScript help you.
582
585
 
583
- If you find yourself writing `as unknown as` with mnemonica types, **you have taken a wrong turn.** Stop. Use `lookupTyped`. Trust the registry.
586
+ If you find yourself writing `as unknown as` with mnemonica types, **you have taken a wrong turn.** Stop. Use `lookup`. Trust the registry.
@@ -1,6 +1,6 @@
1
- # mnemonica + `lookupTyped`: rules and the underlying pattern
1
+ # mnemonica + `lookup`: rules and the underlying pattern
2
2
 
3
- > **The real rule:** use `lookupTyped()` plus an augmented `TypeRegistry` — never `import { X } from './collections/...'` plus `as unknown as` casts. The augmentation can be hand-written or generated by [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica). Tactica is the productivity tool; the **augmentation** is the requirement. For the full mechanism and a side-by-side example, see [`../docs/typed-lookup.md`](../docs/typed-lookup.md).
3
+ > **The real rule:** use `lookup()` plus an augmented `TypeRegistry` — never `import { X } from './collections/...'` plus `as unknown as` casts. The augmentation can be hand-written or generated by [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica). Tactica is the productivity tool; the **augmentation** is the requirement. For the full mechanism and a side-by-side example, see [`../docs/typed-lookup.md`](../docs/typed-lookup.md).
4
4
 
5
5
  ---
6
6
 
@@ -33,13 +33,13 @@ The temptation is to cast around the gap:
33
33
  const requestData = new RequestData({ ... }) as unknown as RequestDataT;
34
34
  ```
35
35
 
36
- That bypasses the type system. The right answer is to teach TypeScript what the Trie looks like — which is exactly what `lookupTyped()` + a `TypeRegistry` augmentation does.
36
+ That bypasses the type system. The right answer is to teach TypeScript what the Trie looks like — which is exactly what `lookup()` + a `TypeRegistry` augmentation does.
37
37
 
38
38
  ---
39
39
 
40
40
  ## The mechanism in one paragraph
41
41
 
42
- `mnemonica` exports `interface TypeRegistry { [key: string]: TypeConstructor<never> }`. `lookupTyped<K extends keyof TypeRegistry>(path: K)` returns `TypeRegistry[K]`. So a `declare module 'mnemonica' { interface TypeRegistry { ... } }` augmentation in your project — by hand, or generated by tactica — gives `lookupTyped('Path.To.Type')` a fully typed return value. Zero casts.
42
+ `mnemonica` exports an empty `interface TypeRegistry {}`. `lookup<const K extends keyof TypeRegistry>(path: K)` returns `TypeRegistry[K] | undefined`. So a `declare module 'mnemonica' { interface TypeRegistry { ... } }` augmentation in your project — by hand, or generated by tactica — gives `lookup('Path.To.Type')` a fully typed return value. Zero casts.
43
43
 
44
44
  After augmentation, TypeScript knows:
45
45
  - The constructor arguments for each type
@@ -62,7 +62,7 @@ When asked to work with a mnemonica project:
62
62
 
63
63
  2. **If `.tactica/` exists, check `tsconfig.json` includes it.** If missing, add `.tactica/**/*.ts` to `include`.
64
64
 
65
- 3. **Check that `lookupTyped` is being used.** Direct imports from collection files + casts are the smell that augmentation is missing or unused.
65
+ 3. **Check that `lookup` is being used.** Direct imports from collection files + casts are the smell that augmentation is missing or unused.
66
66
 
67
67
  4. **Never accept `as unknown as` as a solution.** It is a symptom of the augmentation being missing, not a fix.
68
68
 
@@ -72,7 +72,7 @@ When asked to work with a mnemonica project:
72
72
 
73
73
  | Situation | Wrong | Right |
74
74
  |---|---|---|
75
- | Get a constructor | `import { T } from './collections/T.js'` | `const T = lookupTyped('T')` |
75
+ | Get a constructor | `import { T } from './collections/T.js'` | `const T = lookup('T')` |
76
76
  | Create instance | `new T({ ... }) as unknown as TT` | `new T({ ... })` |
77
77
  | Chain to child | `new (instance as any).Child({ ... })` | `new instance.Child({ ... })` |
78
78
  | Type not found | Add `as any` | Augment `TypeRegistry` (manually or run `tactica`) |
@@ -96,8 +96,8 @@ If you are not using tactica, `npm run tactica` is not required; the hand-writte
96
96
 
97
97
  ## Deeper reading
98
98
 
99
- - [`../docs/typed-lookup.md`](../docs/typed-lookup.md) — `lookupTyped` with or without tactica (canonical reference, side-by-side)
99
+ - [`../docs/typed-lookup.md`](../docs/typed-lookup.md) — `lookup` with or without tactica (canonical reference, side-by-side)
100
100
  - [`../docs/tactica-pattern.md`](../docs/tactica-pattern.md) — human-facing explanation of declaration merging
101
101
  - [`./TACTICA-DEEP-DIVE.md`](./TACTICA-DEEP-DIVE.md) — comprehensive technical guide
102
102
 
103
- The key insight: **the runtime constructor is identical whether you import it directly or look it up. The only difference is TypeScript's compile-time knowledge.** `lookupTyped` + augmented `TypeRegistry` teaches TypeScript what mnemonica already knows at runtime — and the augmentation can come from anywhere.
103
+ The key insight: **the runtime constructor is identical whether you import it directly or look it up. The only difference is TypeScript's compile-time knowledge.** `lookup` + augmented `TypeRegistry` teaches TypeScript what mnemonica already knows at runtime — and the augmentation can come from anywhere.
package/.ai/ask/AGENTS.md CHANGED
@@ -37,7 +37,7 @@ Creates type constructors with special inheritance capabilities:
37
37
  ### 3. Proxy Architecture
38
38
 
39
39
  - **TypeProxy** — Wraps type constructors, handles `.call`, `.apply`
40
- - **Mnemosyne** — Manages instance prototype chain, provides `.fork()`, `.parent()`
40
+ - **Mnemosyne** — Manages instance prototype chain
41
41
  - **TypesCollectionProxy** — Manages type registry, handles type lookup
42
42
 
43
43
  ### 4. Symbol System
@@ -68,15 +68,21 @@ const TypeProxy = new Proxy(Constructor, {
68
68
  });
69
69
  ```
70
70
 
71
- ## Explaining Instance Methods
71
+ ## Explaining Instance Introspection
72
72
 
73
- | Method | Purpose | Defined In |
74
- |--------|---------|------------|
75
- | `.fork()` | Create shallow copy | Mnemosyne.ts |
76
- | `.parent()` | Access parent instance | Mnemosyne.ts |
77
- | `.pick()` | Extract specific properties | Mnemosyne.ts |
78
- | `.extract()` | Get all inherited properties | extract.ts |
79
- | `.parse()` | Parse instance structure | parse.ts |
73
+ Starting from v1.0.6 the legacy instance methods are no longer auto-injected.
74
+ Use the standalone `utils` export:
75
+
76
+ | Utility | Purpose | Defined In |
77
+ |---------|---------|------------|
78
+ | `utils.fork(instance)` | Create a fork constructor | fork.ts |
79
+ | `utils.parent(instance)` | Access parent instance | parent.ts |
80
+ | `utils.pick(instance, ...keys)` | Extract specific properties | pick.ts |
81
+ | `utils.extract(instance)` | Get all inherited properties | extract.ts |
82
+ | `utils.parse(instance)` | Parse instance structure | parse.ts |
83
+
84
+ To restore the old `instance.fork()` / `instance.extract()` style, attach the
85
+ methods to the constructor's prototype before calling `define()`.
80
86
 
81
87
  ## Test Framework Differences
82
88
 
@@ -57,7 +57,7 @@ Before implementing, I need to clarify:
57
57
  1. **Scope**: Should this config be per-type or per-collection?
58
58
  2. **Proxy compatibility**: TypeProxy handles `.get`, `.set`, `.construct`.
59
59
  Adding config requires checking all three traps.
60
- 3. **Existing options**: `strictChain`, `blockErrors`, `exposeInstanceMethods`
60
+ 3. **Existing options**: `strictChain`, `blockErrors`, `awaitReturn`
61
61
  already exist. Does this fit the pattern?
62
62
  4. **TypeRegistry impact**: Will tactica need to regenerate types?
63
63
 
@@ -3,7 +3,7 @@ name: mnemonica-define-patterns
3
3
  description: |
4
4
  Patterns for defining types and subtypes with mnemonica's define() function.
5
5
  Use when the user asks about define(), creating types, subtype inheritance,
6
- strictChain, blockErrors, exposeInstanceMethods, or type configuration options.
6
+ strictChain, blockErrors, awaitReturn, or type configuration options.
7
7
  metadata:
8
8
  tags: [mnemonica, define, types, subtypes, inheritance]
9
9
  ---
@@ -71,9 +71,15 @@ define('Parent.Child', function (this: Child, data: Data) {
71
71
  | `blockErrors` | `true` | Block construction if error exists in prototype chain |
72
72
  | `submitStack` | `false` | Collect stack trace as `__stack__` property |
73
73
  | `awaitReturn` | `true` | `await new Constructor()` must return a value |
74
- | `exposeInstanceMethods` | `true` | Expose `extract()`, `fork()`, etc. on instance |
75
74
  | `asClass` | auto-detected | Force class mode detection |
76
75
 
76
+ ## Instance Method Opt-In
77
+
78
+ Starting from v1.0.6, `extract()`, `pick()`, `parent()`, `fork()`, `exception()`,
79
+ `sibling()`, and `clone()` are **not** auto-injected on instances. Use the standalone
80
+ `utils` export, or attach the methods to the constructor's prototype before calling
81
+ `define()`.
82
+
77
83
  ## Before/After: strictChain
78
84
 
79
85
  **Before** (strictChain: false — allows broken chains)
@@ -56,7 +56,7 @@ supports different users.
56
56
 
57
57
  | Environment | Tools |
58
58
  |-------------|-------|
59
- | **VS Code** | Mnemographica extension, Tactica LSP, Mnemonica Logger |
59
+ | **VS Code** | Mnemographica extension, Tactica CLI, Mnemonica Logger |
60
60
  | **Runtime (Node.js)** | CDP Connection (port 9229), Strategy MCP (ports 9230/9231) |
61
61
 
62
62
  #### Collaboration Modes
@@ -76,7 +76,7 @@ supports different users.
76
76
  │ Mnemonica Ecosystem │
77
77
  ├─────────────────────────────────────────┤
78
78
  │ mnemographica │ VS Code Extension │
79
- │ tactica │ Type Generator/LSP
79
+ │ tactica │ Type Generator/CLI
80
80
  │ strategy │ MCP Server │
81
81
  │ topologica │ Module Loader │
82
82
  │ mnemonica │ Core Runtime │
@@ -88,7 +88,7 @@ supports different users.
88
88
 
89
89
  ### Integration Points
90
90
 
91
- 1. **Tactica → VS Code**: Language Service Plugin
91
+ 1. **Tactica → VS Code**: CLI type generator
92
92
  2. **Strategy → CDP**: Runtime evaluation
93
93
  3. **Mnemographica → Strategy**: HTTP/WebSocket servers
94
94
  4. **All → Topologica**: Module loading
@@ -136,5 +136,5 @@ interface Runnable { run(): void; }
136
136
 
137
137
  ## References
138
138
 
139
- - [Wikipedia: PACT (interaction design)](https://en.wikipedia.org/wiki/PACT_\(interaction_design\))
139
+ - [Wikipedia: PACT (interaction design)](https://en.wikipedia.org/wiki/PACT_%28interaction_design%29)
140
140
  - `strategy/README.md` — MCP architecture