mnemonica 1.0.1 → 1.0.6

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 (89) hide show
  1. package/.ai/ONBOARDING.md +1 -1
  2. package/.ai/ask/AGENTS.md +15 -9
  3. package/.ai/rules-skill/contributing.md +1 -1
  4. package/.ai/rules-skill/define-patterns.md +8 -2
  5. package/.ai/rules-skill/ecosystem.md +4 -4
  6. package/.ai/rules-skill/instance-methods.md +48 -15
  7. package/.ai/rules-skill/type-system.md +12 -2
  8. package/AGENTS.md +6 -2
  9. package/CONTRIBUTING.md +7 -1
  10. package/FOR_HUMANS.md +143 -199
  11. package/README.md +74 -39
  12. package/build/api/errors/exceptionConstructor.js +20 -12
  13. package/build/api/errors/index.js +9 -5
  14. package/build/api/errors/throwModificationError.js +15 -9
  15. package/build/api/index.js +35 -2
  16. package/build/api/types/InstanceCreator.js +5 -2
  17. package/build/api/types/Mnemosyne.d.ts +6 -6
  18. package/build/api/types/Mnemosyne.js +43 -120
  19. package/build/api/types/Props.js +5 -3
  20. package/build/api/types/TypeProxy.js +13 -9
  21. package/build/api/types/compileNewModificatorFunctionBody.js +14 -8
  22. package/build/api/types/index.js +56 -13
  23. package/build/api/utils/index.js +21 -12
  24. package/build/constants/index.js +11 -8
  25. package/build/descriptors/types/index.js +79 -26
  26. package/build/index.d.ts +4 -4
  27. package/build/index.js +62 -15
  28. package/build/types/index.d.ts +52 -31
  29. package/build/types/index.js +1 -1
  30. package/build/utils/clone.d.ts +1 -0
  31. package/build/utils/clone.js +11 -0
  32. package/build/utils/collectConstructors.js +5 -3
  33. package/build/utils/exception.d.ts +1 -0
  34. package/build/utils/exception.js +14 -0
  35. package/build/utils/extract.d.ts +2 -3
  36. package/build/utils/extract.js +3 -2
  37. package/build/utils/fork.d.ts +1 -0
  38. package/build/utils/fork.js +33 -0
  39. package/build/utils/index.d.ts +2 -3
  40. package/build/utils/index.js +21 -7
  41. package/build/utils/merge.d.ts +2 -1
  42. package/build/utils/merge.js +10 -6
  43. package/build/utils/parent.d.ts +1 -1
  44. package/build/utils/parent.js +3 -2
  45. package/build/utils/parse.d.ts +2 -12
  46. package/build/utils/parse.js +1 -1
  47. package/build/utils/pick.d.ts +4 -3
  48. package/build/utils/pick.js +4 -5
  49. package/build/utils/sibling.d.ts +1 -0
  50. package/build/utils/sibling.js +25 -0
  51. package/build/utils/toJSON.d.ts +1 -1
  52. package/build/utils/toJSON.js +3 -2
  53. package/docs/UTILS.md +184 -0
  54. package/docs/ai-learning-trajectory.md +1 -1
  55. package/docs/async-constructors.md +3 -1
  56. package/docs/empathy-in-ai.md +170 -0
  57. package/docs/hott-primer.md +47 -0
  58. package/docs/performance-vs-security.md +2 -2
  59. package/docs/purpose.md +38 -7
  60. package/docs/tactica-pattern.md +10 -10
  61. package/docs/theory-of-operations.md +224 -0
  62. package/docs/typed-lookup.md +12 -12
  63. package/package.json +12 -7
  64. package/src/api/errors/exceptionConstructor.ts +14 -9
  65. package/src/api/errors/index.ts +8 -4
  66. package/src/api/errors/throwModificationError.ts +10 -9
  67. package/src/api/types/InstanceCreator.ts +5 -8
  68. package/src/api/types/Mnemosyne.ts +72 -231
  69. package/src/api/types/Props.ts +4 -2
  70. package/src/api/types/TypeProxy.ts +7 -11
  71. package/src/api/types/compileNewModificatorFunctionBody.ts +13 -7
  72. package/src/api/types/index.ts +15 -8
  73. package/src/api/utils/index.ts +16 -14
  74. package/src/constants/index.ts +6 -9
  75. package/src/descriptors/types/index.ts +44 -24
  76. package/src/index.ts +28 -21
  77. package/src/types/index.ts +101 -69
  78. package/src/utils/clone.ts +11 -0
  79. package/src/utils/collectConstructors.ts +4 -2
  80. package/src/utils/exception.ts +16 -0
  81. package/src/utils/extract.ts +5 -2
  82. package/src/utils/fork.ts +57 -0
  83. package/src/utils/index.ts +32 -13
  84. package/src/utils/merge.ts +18 -6
  85. package/src/utils/parent.ts +4 -6
  86. package/src/utils/parse.ts +5 -4
  87. package/src/utils/pick.ts +10 -2
  88. package/src/utils/sibling.ts +33 -0
  89. package/src/utils/toJSON.ts +3 -2
package/docs/purpose.md CHANGED
@@ -48,6 +48,35 @@ for (item of items) { // Control flow: iteration
48
48
 
49
49
  **Mnemonica does NOT touch this**: Algorithms remain unchanged. Services stay intact. Control flow is crafted by developers (and AI agents) as needed.
50
50
 
51
+ ### What Mnemonica Does NOT Do — Concrete Examples
52
+
53
+ ```typescript
54
+ // ❌ Mnemonica does NOT replace if/else
55
+ if (user.role === 'admin') {
56
+ // This logic is yours. Mnemonica provides the typed data,
57
+ // not the decision rule.
58
+ }
59
+
60
+ // ❌ Mnemonica does NOT replace loops
61
+ for (const item of items) {
62
+ // Iteration control is yours.
63
+ }
64
+
65
+ // ❌ Mnemonica does NOT replace async/await orchestration
66
+ const a = await serviceA.get();
67
+ const b = await serviceB.process(a);
68
+ // Mnemonica types the data, not the execution order.
69
+
70
+ // ❌ Mnemonica does NOT replace service boundaries
71
+ // You still write HTTP handlers, database queries, message queues.
72
+ // Mnemonica types the data that flows through them.
73
+
74
+ // ✅ Mnemonica DOES type the data at each stage
75
+ const RequestData = define('RequestData', function(req) { this.method = req.method; });
76
+ const ResponseData = RequestData.define('ResponseData', function(res) { this.body = res.body; });
77
+ // The types track lineage; the services remain unchanged.
78
+ ```
79
+
51
80
  ---
52
81
 
53
82
  ## Part 2: The Problem - Data Without Memory
@@ -246,7 +275,7 @@ p instanceof Vector; // false - same shape, different nominal type!
246
275
 
247
276
  Mnemonica cares about **constructor identity**. Same shape, different type = not compatible.
248
277
 
249
- ### Protected `constructor.name` - MITM-Resistant
278
+ ### Protected `constructor.name` - Defense-in-Depth
250
279
 
251
280
  Mnemonica types have **read-only, protected constructor names**:
252
281
 
@@ -261,11 +290,13 @@ user.constructor.name = 'Admin'; // ✗ Ignored/Fails - protected
261
290
  ```
262
291
 
263
292
  **Why This Matters**:
264
- - MITM (Man-in-the-Middle) attacks on prototype chains are **impossible**
265
- - An attacker cannot spoof a type by overwriting constructor names
266
- - `instanceof` checks are cryptographically reliable
293
+ - Prototype pollution attacks that rely on name spoofing are **blocked at the assignment level**
294
+ - `instanceof` checks **prototype object references**, not constructor names, so name changes do not affect type identity
295
+ - Constructor names are getter-protected and non-enumerable, making casual tampering impossible
267
296
  - Type identity is immutable once established
268
297
 
298
+ **Threat Model**: This is defense-in-depth, not cryptographic security. A fully compromised execution environment can still use `Object.defineProperty` to redefine the getter. The protection targets casual prototype pollution and accidental corruption, not a determined attacker with full runtime access.
299
+
269
300
  ### Security Through `Object.create(null)`
270
301
 
271
302
  Mnemonica instances are **NOT instances of Object or Function**:
@@ -501,8 +532,8 @@ const superAdmin = new SuperAdmin({});
501
532
 
502
533
  **The Hot Connection**: In HoTT, paths can be composed, inverted, and transformed. In Mnemonica:
503
534
  - **Composition**: `new user.SubType()` extends the path
504
- - **Inversion**: `instance.parent()` traverses backward
505
- - **Transformation**: `.fork()` creates a new path with same starting point
535
+ - **Inversion**: `utils.parent(instance)` traverses backward
536
+ - **Transformation**: `utils.fork(instance)` creates a new path with same starting point
506
537
 
507
538
  ### Higher Inductive Types: The Family Tree
508
539
 
@@ -640,7 +671,7 @@ console.log(props.__parent__); // undefined (root type)
640
671
  - Historical reflection embedded in data structures
641
672
  - The DNA of data as it moves through systems
642
673
  - A **nominal type system** (constructor identity, not shape)
643
- - A **secure** system (MITM-resistant, prototype pollution immune)
674
+ - A **secure** system (defense-in-depth against prototype pollution, nominal typing prevents type confusion)
644
675
 
645
676
  **Mnemonica Is NOT**:
646
677
  - A control flow framework
@@ -88,22 +88,22 @@ Include the file in `tsconfig.json` `include`. You are done.
88
88
 
89
89
  ### Via tactica
90
90
 
91
- For projects with many types or for hands-off maintenance, install [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) and configure it as a TS Language Service Plugin. It scans your `define()` calls and generates the augmentation continuously:
91
+ For projects with many types or for hands-off maintenance, install [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) and run it as a CLI/codegen utility. It scans your `define()` calls and generates the augmentation:
92
+
93
+ ```bash
94
+ npm install --save-dev @mnemonica/tactica
95
+ npx tactica --output ./.tactica --include "src/**/*.ts"
96
+ ```
97
+
98
+ Include the generated files in `tsconfig.json`:
92
99
 
93
100
  ```json
94
101
  {
95
- "compilerOptions": {
96
- "plugins": [{
97
- "name" : "@mnemonica/tactica",
98
- "outputDir" : ".tactica",
99
- "include" : ["src/**/*.ts"]
100
- }]
101
- },
102
102
  "include": ["src/**/*.ts", ".tactica/**/*.ts"]
103
103
  }
104
104
  ```
105
105
 
106
- Tactica writes the same shape of augmentation a careful human would, using `ProtoFlat<Parent, ...>` for nested types and filling in instance-level subtype constructors automatically. The runtime is unaffected.
106
+ Tactica writes the same shape of augmentation a careful human would, using `ProtoFlat<Parent, ...>` for nested types and filling in instance-level subtype constructors automatically. The runtime is unaffected. Re-run the CLI whenever your type graph changes, or wire it into your build script.
107
107
 
108
108
  See [`./typed-lookup.md`](./typed-lookup.md) for a side-by-side recipe with both paths.
109
109
 
@@ -134,7 +134,7 @@ The anti-patterns are the same whether you augment by hand or via tactica:
134
134
  What changes is how you keep the `TypeRegistry` augmentation in sync with your code:
135
135
 
136
136
  - **Hand-written:** edit the `.d.ts` when types change.
137
- - **Tactica:** the LSP plugin keeps `.tactica/` in sync.
137
+ - **Tactica:** the CLI/codegen utility keeps `.tactica/` in sync.
138
138
 
139
139
  Pick whichever fits your project. Both are first-class.
140
140
 
@@ -0,0 +1,224 @@
1
+ # Theory of Operations
2
+
3
+ ## The Full Construction Pipeline
4
+
5
+ This document traces a single `new instance.SubType(args)` call through every stage of mnemonica's internals, with references to the source files that implement each stage. It exists so that contributors (human and AI) can understand the exact data flow without reverse-engineering the code.
6
+
7
+ ---
8
+
9
+ ## Stage 0: Type Definition (`define()`)
10
+
11
+ **File:** [`src/api/types/index.ts`](../src/api/types/index.ts)
12
+
13
+ Before any instance exists, the type must be declared. `define('TypeName', ctor, config?)` performs:
14
+
15
+ 1. **Resolve definition context** — determines whether this is a root type or a nested path (`Parent.SubType`)
16
+ 2. **Check for duplicates** — throws `ALREADY_DECLARED` if the name exists in the target collection
17
+ 3. **Compile the constructor body** — `compileNewModificatorFunctionBody.ts` wraps the user's constructor in a function that preserves `new.target` and handles both class and function constructors
18
+ 4. **Create a `TypeDescriptor`** — stores: name, compiled handler, prototype, config, parent type reference
19
+ 5. **Register in the `TypesCollection` Proxy** — makes the type findable via `.lookup()` and `.define()`
20
+
21
+ The result is a `TypeProxy` — a constructor function with additional methods (`.define()`, `.lookup()`, `.registerHook()`).
22
+
23
+ **Key invariant:** A type is immutable once defined. Its name, handler, and prototype are frozen.
24
+
25
+ ---
26
+
27
+ ## Stage 1: Subtype Access (`instance.SubType`)
28
+
29
+ **File:** [`src/api/types/Mnemosyne.ts`](../src/api/types/Mnemosyne.ts)
30
+
31
+ When code reads `instance.SubType`, the `Mnemosyne` proxy's `get` trap fires:
32
+
33
+ 1. Look up `SubType` in the instance's `__subtypes__` Map (stored in the WeakMap)
34
+ 2. If found, call `prepareSubtypeForConstruction(subtype, instance)`
35
+ 3. This creates a **subtype proxy** — a function that closes over both the `TypeDef` and the parent `instance`
36
+
37
+ The subtype proxy is what makes `new instance.SubType()` work. It is not the raw constructor; it is a wrapper that knows which parent instance to use.
38
+
39
+ **Key invariant:** Every access to `instance.SubType` creates a fresh proxy (this was a deliberate design decision; caching is possible but not implemented to keep the code simple).
40
+
41
+ ---
42
+
43
+ ## Stage 2: Construction Invocation (`new instance.SubType(args)`)
44
+
45
+ **File:** [`src/api/types/Mnemosyne.ts`](../src/api/types/Mnemosyne.ts)
46
+
47
+ The subtype proxy's `construct` trap fires:
48
+
49
+ 1. Call `new InstanceCreator(subtype, instance, args)`
50
+ 2. The `InstanceCreator` is the orchestrator for the full lifecycle
51
+
52
+ ---
53
+
54
+ ## Stage 3: Instance Creator Setup
55
+
56
+ **File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
57
+
58
+ `InstanceCreator` is a class (not a proxy) that manages the 8-stage pipeline:
59
+
60
+ ### 3.1 Extract context
61
+ - `type` — the `TypeDef` for `SubType`
62
+ - `existentInstance` — the parent instance (`instance` in our example)
63
+ - `args` — the arguments array
64
+ - `config` — from the type definition
65
+
66
+ ### 3.2 Resolve the `ModificatorType`
67
+ - For sync constructors: compile the user handler into a new constructor function
68
+ - For async constructors: wrap in an async-compatible class pattern
69
+
70
+ ### 3.3 Prepare hooks
71
+ - `invokePreHooks()` — calls `preCreation` hooks on the type's collection and the type itself
72
+ - If any hook throws, `creationError` hooks fire and construction aborts
73
+
74
+ ---
75
+
76
+ ## Stage 4: Prototype Chain Assembly
77
+
78
+ **File:** [`src/api/types/createInstanceModificator.ts`](../src/api/types/createInstanceModificator.ts)
79
+
80
+ This is where the Trie structure is physically built:
81
+
82
+ 1. **Create `Mnemosyne`** — `const Mnemosyne = {}`
83
+ 2. **Link to parent** — `Reflect.setPrototypeOf(Mnemosyne, existentInstance)`
84
+ - This is the critical step: the new instance's prototype chain will go through the **specific parent instance**
85
+ 3. **Add internal properties** — `_addProps(Mnemosyne)` attaches getters for `__type__`, `__parent__`, `__args__`, `__timestamp__`, etc.
86
+ 4. **Create `ModificatorType`** — a new constructor function with `Mnemosyne` as its prototype
87
+ 5. **Copy user prototype properties** — `Object.getOwnPropertyDescriptors(ModificatorTypePrototype)` are copied onto `ModificatorType.prototype`
88
+ 6. **Link ModificatorType to Mnemosyne** — `Reflect.setPrototypeOf(ModificatorType.prototype, Mnemosyne)`
89
+
90
+ The resulting chain:
91
+ ```
92
+ newObj
93
+ ↓ __proto__
94
+ ModificatorType.prototype
95
+ ↓ __proto__
96
+ Mnemosyne (has __type__, __parent__, etc.)
97
+ ↓ __proto__
98
+ existentInstance (the specific parent)
99
+ ↓ __proto__
100
+ ... (rest of chain to root)
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Stage 5: User Constructor Execution
106
+
107
+ **File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
108
+
109
+ The `ModificatorType` is instantiated with `new ModificatorType(...args)`:
110
+
111
+ 1. The compiled constructor body (from Stage 0) runs
112
+ 2. `this` is the new object whose prototype chain was built in Stage 4
113
+ 3. The user constructor assigns properties to `this`
114
+ 4. If the constructor returns a non-primitive, that value is used as the instance
115
+
116
+ ---
117
+
118
+ ## Stage 6: Post-Processing
119
+
120
+ **File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
121
+
122
+ After the user constructor:
123
+
124
+ 1. **Set `__self__`** — `_setSelf(instance)` stores a reference to the instance itself in the props
125
+ 2. **Validation** (if `strictChain: true`):
126
+ - Verify the instance is actually an instance of the expected parent type
127
+ - Verify the parent's constructor name matches the expected parent type name
128
+ 3. **Invoke `postCreation` hooks**
129
+
130
+ ---
131
+
132
+ ## Stage 7: Return
133
+
134
+ **File:** [`src/api/types/InstanceCreator.ts`](../src/api/types/InstanceCreator.ts)
135
+
136
+ The instance is returned. It carries:
137
+ - All user-assigned properties (own properties)
138
+ - The prototype chain linking back to the root type
139
+ - Internal construction context in the WeakMap (not enumerable, not own properties)
140
+
141
+ ---
142
+
143
+ ## The WeakMap Architecture
144
+
145
+ **File:** [`src/api/types/Props.ts`](../src/api/types/Props.ts)
146
+
147
+ Internal properties are stored in a `WeakMap` keyed by the **prototype object** (Mnemosyne), not the instance itself:
148
+
149
+ ```typescript
150
+ const __props__ = new WeakMap<object, PropsType>();
151
+ ```
152
+
153
+ Why the prototype?
154
+ - Multiple instances of the same type share the same Mnemosyne prototype
155
+ - Storing props on the prototype means all instances of the same construction share the same type metadata
156
+ - This saves memory and keeps instance enumeration clean
157
+
158
+ `getProps(instance)` walks the prototype chain to find the Mnemosyne object, then looks it up in the WeakMap.
159
+
160
+ ---
161
+
162
+ ## Hook Invocation Details
163
+
164
+ **File:** [`src/api/hooks/invokeHook.ts`](../src/api/hooks/invokeHook.ts)
165
+
166
+ Hooks are stored per-type and per-collection:
167
+
168
+ ```typescript
169
+ type.hooks = {
170
+ preCreation: [fn1, fn2],
171
+ postCreation: [fn3],
172
+ creationError: [fn4]
173
+ };
174
+ ```
175
+
176
+ When `invokeHook('preCreation', hookData)` fires:
177
+ 1. Create a `HookInvocation` builder
178
+ 2. For each registered hook, call it with the builder
179
+ 3. Collect results in a Set
180
+ 4. If any hook throws, the error propagates (aborting construction)
181
+
182
+ The `flowCheckers` Map allows hooks to be conditionally enabled/disabled per invocation.
183
+
184
+ ---
185
+
186
+ ## Error Handling
187
+
188
+ **File:** [`src/api/errors/throwModificationError.ts`](../src/api/errors/throwModificationError.ts)
189
+
190
+ If any stage throws:
191
+
192
+ 1. `creationError` hooks fire with the error and partial construction context
193
+ 2. A `BASE_MNEMONICA_ERROR` is constructed with:
194
+ - Cleaned stack trace (mnemonica internals removed)
195
+ - References to the instance, type, and parent
196
+ 3. The error is thrown to the caller
197
+
198
+ If `config.blockErrors` is true (default for most types), errors during construction are caught and re-thrown as mnemonica errors. If false (used internally for exception construction), errors propagate raw.
199
+
200
+ ---
201
+
202
+ ## Summary Table
203
+
204
+ | Stage | File | What Happens |
205
+ |---|---|---|
206
+ | 0. Type Definition | `src/api/types/index.ts` | Compile handler, create TypeDescriptor, register in collection |
207
+ | 1. Subtype Access | `src/api/types/Mnemosyne.ts` | Resolve `instance.SubType` via proxy get trap |
208
+ | 2. Construction Start | `src/api/types/Mnemosyne.ts` | Subtype proxy's construct trap fires |
209
+ | 3. Creator Setup | `src/api/types/InstanceCreator.ts` | Extract context, resolve ModificatorType, run pre-hooks |
210
+ | 4. Prototype Assembly | `src/api/types/createInstanceModificator.ts` | Build Mnemosyne → link to parent → create ModificatorType |
211
+ | 5. User Constructor | `src/api/types/InstanceCreator.ts` | Run compiled handler with `this` bound to new object |
212
+ | 6. Post-Processing | `src/api/types/InstanceCreator.ts` | Set __self__, validate, run post-hooks |
213
+ | 7. Return | `src/api/types/InstanceCreator.ts` | Return instance with full prototype chain and WeakMap props |
214
+
215
+ ---
216
+
217
+ ## For Contributors
218
+
219
+ When modifying any of these files, remember:
220
+
221
+ - **Prototype links are load-bearing.** Changing `Reflect.setPrototypeOf` calls changes the Trie structure.
222
+ - **WeakMap keys are prototype objects, not instances.** `_getProps` walks the chain; `_addProps` writes to the Mnemosyne.
223
+ - **Hook order matters.** `preCreation` → construction → `postCreation`. Errors in `preCreation` abort before `Object.create`.
224
+ - **The `__self__` reference is used by `utils.fork()` and `utils.clone()` to detect self-calls.**
@@ -1,6 +1,6 @@
1
1
  # Typed lookup with `lookupTyped()` — with or without tactica
2
2
 
3
- `lookupTyped()` is a runtime lookup with compile-time type safety. The type safety comes from one mechanism: declaration merging on the `TypeRegistry` interface that mnemonica exports.
3
+ `lookupTyped()` is a runtime lookup with compile-time type safety. The type safety comes from one mechanism: declaration merging on the `TypeRegistry` interface that mnemonica exports. The path argument may use dots, slashes, colons, or bracket notation (e.g. `'UserType.AdminType'`, `'UserType/AdminType'`, `'UserType:AdminType'`, or `'UserType[AdminType]'`), and a single-segment name is resolved relative to the collection or type constructor the lookup is called on.
4
4
 
5
5
  ```typescript
6
6
  // src/index.ts (mnemonica core)
@@ -96,22 +96,22 @@ Install:
96
96
  npm install --save-dev @mnemonica/tactica
97
97
  ```
98
98
 
99
- Configure as a TypeScript Language Service Plugin in `tsconfig.json`:
99
+ Install and run the CLI:
100
+
101
+ ```bash
102
+ npm install --save-dev @mnemonica/tactica
103
+ npx tactica --output ./.tactica --include "src/**/*.ts"
104
+ ```
105
+
106
+ Then include the generated files in `tsconfig.json`:
100
107
 
101
108
  ```json
102
109
  {
103
- "compilerOptions": {
104
- "plugins": [{
105
- "name" : "@mnemonica/tactica",
106
- "outputDir" : ".tactica",
107
- "include" : ["src/**/*.ts"]
108
- }]
109
- },
110
110
  "include": ["src/**/*.ts", ".tactica/**/*.ts"]
111
111
  }
112
112
  ```
113
113
 
114
- Tactica scans your `define()` and `@decorate()` calls and generates `.tactica/types.ts` + `.tactica/registry.ts`. The generated registry has the same shape as the hand-written one above, but tactica also fills in instance-level subtypes (`alice.Employee`) automatically and uses `ProtoFlat<Parent, ...>` for nested types so they merge correctly.
114
+ Tactica scans your `define()` and `@decorate()` calls and generates `.tactica/types.ts` + `.tactica/registry.ts`. The generated registry has the same shape as the hand-written one above, but tactica also fills in instance-level subtypes (`alice.Employee`) automatically and uses `ProtoFlat<Parent, ...>` for nested types so they merge correctly. You can re-run the CLI manually or from a build script; mnemographica can also run it on your behalf.
115
115
 
116
116
  You write nothing for the augmentation; tactica regenerates as your types evolve. See [`@mnemonica/tactica`](https://www.npmjs.com/package/@mnemonica/tactica) for the full configuration surface.
117
117
 
@@ -124,14 +124,14 @@ You write nothing for the augmentation; tactica regenerates as your types evolve
124
124
  - Small projects (a handful of types)
125
125
  - Bootstrap / learning — writing the augmentation by hand is the fastest way to understand what `lookupTyped()` actually does
126
126
  - Projects that want to keep the `TypeRegistry` surface stable and curated, independent of source-code churn
127
- - Environments where adding a TypeScript Language Service plugin is inconvenient
127
+ - Environments where running a CLI/codegen utility is inconvenient
128
128
 
129
129
  **Tactica** is the right choice when:
130
130
 
131
131
  - You have many types or expect the type graph to grow
132
132
  - You don't want to maintain the augmentation by hand as `define()` calls are added or refactored
133
133
  - You want full instance-subtype typing (`alice.Employee`) without writing it
134
- - You're already on a TS-LSP-friendly setup
134
+ - You're already running tactica as part of your build or IDE workflow
135
135
 
136
136
  The runtime is identical either way. There is no "tactica path" vs "non-tactica path" in mnemonica core itself — there is only the `TypeRegistry` augmentation, and two ways to produce it.
137
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnemonica",
3
- "version": "1.0.1",
3
+ "version": "1.0.6",
4
4
  "description": "AI-agent ready instance inheritance system for JavaScript/TypeScript via prototype chains",
5
5
  "type": "commonjs",
6
6
  "main": "./build/index.js",
@@ -51,7 +51,7 @@
51
51
  "example:v8bug": "npm run build && node ./examples/v8bug.js",
52
52
  "example:ts": "npx tsc ./test-ts/*.ts && npx eslint --fix ./test-ts/*.js && node ./test-ts/test-no-types.js && node ./test-ts/test-with-types.js",
53
53
  "deopt": "node --trace-deopt --trace-opt ./node_modules/mocha/bin/mocha.js ./test/index.js",
54
- "decorate:ts": "npx tsc --target ES2021 --moduleResolution NodeNext --module NodeNext --sourceMap ./test/decorate.ts",
54
+ "decorate:ts": "npx tsc --ignoreConfig --target ES2021 --moduleResolution NodeNext --module NodeNext --sourceMap ./test/decorate.ts",
55
55
  "lint:md": "find . -name '*.md' -not -path '*/node_modules/*' | xargs markdown-link-check -q --config .markdown-link-check.json",
56
56
  "lint": "npx eslint",
57
57
  "lint:check": "npx eslint ./src ./test",
@@ -99,9 +99,9 @@
99
99
  "@eslint/eslintrc": "^3.3.5",
100
100
  "@eslint/js": "^9.21.0",
101
101
  "@types/jest": "^29.5.14",
102
- "@types/node": "^22.19.19",
103
- "@typescript-eslint/eslint-plugin": "^8.59.4",
104
- "@typescript-eslint/parser": "^8.59.4",
102
+ "@types/node": "^22.19.21",
103
+ "@typescript-eslint/eslint-plugin": "^8.61.1",
104
+ "@typescript-eslint/parser": "^8.61.1",
105
105
  "chai": "^5.3.3",
106
106
  "eslint": "^9.39.4",
107
107
  "eslint-plugin-import": "^2.32.0",
@@ -112,18 +112,23 @@
112
112
  "jest": "^29.7.0",
113
113
  "json5": "^2.2.3",
114
114
  "lint-staged": "^15.5.2",
115
+ "markdown-link-check": "^3.14.2",
115
116
  "mocha": "^11.7.6",
116
117
  "mocha-lcov-reporter": "^1.3.0",
117
118
  "nyc": "^17.1.0",
118
119
  "oxlint": "^0.16.12",
119
120
  "ts-jest": "^29.4.11",
120
121
  "typeomatica": "^0.3.57",
121
- "markdown-link-check": "^3.13.6",
122
- "typescript": "^5.9.3"
122
+ "typescript": "^6.0.3"
123
123
  },
124
124
  "engines": {
125
125
  "node": ">=18 <26"
126
126
  },
127
+ "overrides": {
128
+ "serialize-javascript": "^7.0.5",
129
+ "uuid": "^11.1.1",
130
+ "diff": "^9.0.0"
131
+ },
127
132
  "lint-staged": {
128
133
  "src/**/*.ts": "eslint --fix",
129
134
  "test/**/*.js": "eslint --fix"
@@ -13,8 +13,8 @@ import {
13
13
  cleanupStack, getStack
14
14
  } from '../errors';
15
15
 
16
- import { utils } from '../../utils';
17
- const { parse } = utils;
16
+ import { parse } from '../../utils/parse';
17
+ import { extract } from '../../utils/extract';
18
18
 
19
19
  import TypesUtils from '../utils';
20
20
 
@@ -140,9 +140,11 @@ const exceptionConsctructHandler = function ( this: Error, opts: { [ index: stri
140
140
  'extract',
141
141
  {
142
142
  get () {
143
- return () => {
144
- return instance.extract();
143
+ const result = () => {
144
+ const extractResult = extract(instance);
145
+ return extractResult;
145
146
  };
147
+ return result;
146
148
  }
147
149
  }
148
150
  );
@@ -152,10 +154,11 @@ const exceptionConsctructHandler = function ( this: Error, opts: { [ index: stri
152
154
  'parse',
153
155
  {
154
156
  get () {
155
- return () => {
156
- const result = parse( instance );
157
- return result;
157
+ const result = () => {
158
+ const parseResult = parse( instance );
159
+ return parseResult;
158
160
  };
161
+ return result;
159
162
  }
160
163
  }
161
164
  );
@@ -250,7 +253,7 @@ const prepareException = function ( this: object, target: unknown, error: Error,
250
253
  ExceptionCreator.ModificatorType = makeErrorModificatorType(
251
254
  TypeName,
252
255
  function (this: Error) {
253
- return exceptionConsctructHandler.call(
256
+ const handlerResult = exceptionConsctructHandler.call(
254
257
  this,
255
258
  {
256
259
  instance,
@@ -260,12 +263,14 @@ const prepareException = function ( this: object, target: unknown, error: Error,
260
263
  error
261
264
  }
262
265
  );
266
+ return handlerResult;
263
267
  }
264
268
  );
265
269
 
266
270
  ExceptionCreator.InstanceModificator = makeInstanceModificator( ExceptionCreator );
267
271
 
268
- return new ExceptionCreator.InstanceModificator();
272
+ const result = new ExceptionCreator.InstanceModificator();
273
+ return result;
269
274
  };
270
275
 
271
276
  export default prepareException;
@@ -31,7 +31,8 @@ export const cleanupStack = ( stack: string[] ) => {
31
31
  },
32
32
  []
33
33
  );
34
- return cleaned.length ? cleaned : stack;
34
+ const result = cleaned.length ? cleaned : stack;
35
+ return result;
35
36
  };
36
37
 
37
38
  export const getStack = function (
@@ -90,7 +91,8 @@ export class BASE_MNEMONICA_ERROR extends Error {
90
91
  }
91
92
 
92
93
  static get [ SymbolConstructorName ] () {
93
- return new String( `base of : ${MNEMONICA} : errors` );
94
+ const result = new String( `base of : ${MNEMONICA} : errors` );
95
+ return result;
94
96
  }
95
97
 
96
98
  }
@@ -100,7 +102,8 @@ Object.defineProperty(
100
102
  'name',
101
103
  {
102
104
  get () {
103
- return new String( 'BASE_MNEMONICA_ERROR' );
105
+ const result = new String( 'BASE_MNEMONICA_ERROR' );
106
+ return result;
104
107
  }
105
108
  }
106
109
  );
@@ -130,7 +133,8 @@ export const constructError = ( name: string, message: string ) => {
130
133
  'name',
131
134
  {
132
135
  get () {
133
- return new String( name );
136
+ const result = new String( name );
137
+ return result;
134
138
  }
135
139
  }
136
140
  );
@@ -21,12 +21,9 @@ import {
21
21
  import TypesUtils from '../utils';
22
22
  const { makeErrorModificatorType } = TypesUtils;
23
23
 
24
- import { utils } from '../../utils';
25
- const {
26
- parse,
27
- parent,
28
- extract
29
- } = utils;
24
+ import { parse } from '../../utils/parse';
25
+ import { parent } from '../../utils/parent';
26
+ import { extract } from '../../utils/extract';
30
27
 
31
28
  import { makeInstanceModificator } from '../types/InstanceModificator';
32
29
 
@@ -244,11 +241,14 @@ export const throwModificationError = function ( this: InstanceCreatorContext, e
244
241
  'extract',
245
242
  {
246
243
  get () {
247
- return () => {
248
- const _parent = parent(erroredInstance);
244
+ const extractGetter = () => {
245
+ // mnemonica instances always have a parent object,
246
+ // so the runtime value is guaranteed to be object here
247
+ const _parent = parent(erroredInstance) as object;
249
248
  const extractResult = extract(_parent);
250
249
  return extractResult;
251
250
  };
251
+ return extractGetter;
252
252
  }
253
253
  }
254
254
  );
@@ -258,10 +258,11 @@ export const throwModificationError = function ( this: InstanceCreatorContext, e
258
258
  'parse',
259
259
  {
260
260
  get () {
261
- return () => {
261
+ const parseGetter = () => {
262
262
  const parseResult = parse( erroredInstance );
263
263
  return parseResult;
264
264
  };
265
+ return parseGetter;
265
266
  }
266
267
  }
267
268
  );
@@ -281,14 +281,11 @@ const makeAwaiter = function ( this: InstanceCreatorContext, type: TypeDef, then
281
281
  type.subtypes.forEach( ( subtype: object, name: string ) => {
282
282
 
283
283
  (self.inheritedInstance as Record<string, unknown>)[ name ] = ( ...args: unknown[] ) => {
284
- self.inheritedInstance = self.makeAwaiter(
285
- subtype as TypeDef,
286
- {
287
- name,
288
- subtype,
289
- args,
290
- }
291
- );
284
+ self.inheritedInstance = self.makeAwaiter( subtype as TypeDef, {
285
+ name,
286
+ subtype,
287
+ args,
288
+ } );
292
289
  return self.inheritedInstance;
293
290
  };
294
291
  } );