gsd-pi 2.75.0-dev.b6ad8c5f7 → 2.75.0-dev.e41b70b10

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 (181) hide show
  1. package/dist/resources/extensions/gsd/auto/phases.js +2 -0
  2. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
  3. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
  6. package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
  7. package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
  8. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +128 -0
  9. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  10. package/dist/resources/extensions/gsd/bootstrap/system-context.js +17 -4
  11. package/dist/resources/extensions/gsd/commands/handlers/onboarding.js +52 -92
  12. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  13. package/dist/resources/extensions/gsd/commands-memory.js +462 -0
  14. package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
  15. package/dist/resources/extensions/gsd/gsd-db.js +237 -4
  16. package/dist/resources/extensions/gsd/memory-embeddings.js +219 -0
  17. package/dist/resources/extensions/gsd/memory-extractor.js +78 -27
  18. package/dist/resources/extensions/gsd/memory-ingest.js +218 -0
  19. package/dist/resources/extensions/gsd/memory-relations.js +189 -0
  20. package/dist/resources/extensions/gsd/memory-source-store.js +113 -0
  21. package/dist/resources/extensions/gsd/memory-store.js +299 -6
  22. package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
  23. package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
  24. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  25. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  26. package/dist/resources/extensions/gsd/tools/memory-tools.js +306 -0
  27. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
  28. package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
  29. package/dist/resources/extensions/search-the-web/native-search.js +13 -2
  30. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  31. package/dist/web/standalone/.next/BUILD_ID +1 -1
  32. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  33. package/dist/web/standalone/.next/build-manifest.json +2 -2
  34. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  35. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.html +1 -1
  52. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  59. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  61. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  62. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  63. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  64. package/package.json +1 -1
  65. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  66. package/packages/mcp-server/dist/server.js +12 -10
  67. package/packages/mcp-server/dist/server.js.map +1 -1
  68. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  69. package/packages/mcp-server/dist/session-manager.js +8 -1
  70. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  71. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
  72. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  73. package/packages/mcp-server/dist/workflow-tools.js +207 -71
  74. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  75. package/packages/mcp-server/src/mcp-server.test.ts +40 -4
  76. package/packages/mcp-server/src/server.ts +12 -10
  77. package/packages/mcp-server/src/session-manager.ts +10 -3
  78. package/packages/mcp-server/src/workflow-tools.test.ts +346 -1
  79. package/packages/mcp-server/src/workflow-tools.ts +228 -75
  80. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  81. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  82. package/packages/pi-ai/dist/index.d.ts +1 -0
  83. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  84. package/packages/pi-ai/dist/index.js +1 -0
  85. package/packages/pi-ai/dist/index.js.map +1 -1
  86. package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
  87. package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
  88. package/packages/pi-ai/dist/providers/api-family.js +47 -0
  89. package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
  90. package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
  91. package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
  92. package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
  93. package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
  94. package/packages/pi-ai/src/index.ts +1 -0
  95. package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
  96. package/packages/pi-ai/src/providers/api-family.ts +57 -0
  97. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  98. package/packages/pi-coding-agent/dist/cli/args.d.ts +6 -0
  99. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/cli/args.js +14 -4
  101. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/cli/args.test.d.ts +2 -0
  103. package/packages/pi-coding-agent/dist/cli/args.test.d.ts.map +1 -0
  104. package/packages/pi-coding-agent/dist/cli/args.test.js +38 -0
  105. package/packages/pi-coding-agent/dist/cli/args.test.js.map +1 -0
  106. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
  107. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  110. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
  114. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -0
  116. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/sdk.js +7 -1
  118. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/main.js +3 -0
  121. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  122. package/packages/pi-coding-agent/src/cli/args.test.ts +44 -0
  123. package/packages/pi-coding-agent/src/cli/args.ts +21 -6
  124. package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
  125. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
  126. package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
  127. package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
  128. package/packages/pi-coding-agent/src/main.ts +4 -0
  129. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  130. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
  131. package/src/resources/extensions/gsd/auto/phases.ts +3 -0
  132. package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
  133. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
  134. package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
  135. package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
  136. package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
  137. package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
  138. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +158 -0
  139. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  140. package/src/resources/extensions/gsd/bootstrap/system-context.ts +20 -4
  141. package/src/resources/extensions/gsd/commands/handlers/onboarding.ts +65 -131
  142. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  143. package/src/resources/extensions/gsd/commands-memory.ts +551 -0
  144. package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
  145. package/src/resources/extensions/gsd/gsd-db.ts +273 -4
  146. package/src/resources/extensions/gsd/memory-embeddings.ts +235 -0
  147. package/src/resources/extensions/gsd/memory-extractor.ts +100 -34
  148. package/src/resources/extensions/gsd/memory-ingest.ts +286 -0
  149. package/src/resources/extensions/gsd/memory-relations.ts +240 -0
  150. package/src/resources/extensions/gsd/memory-source-store.ts +138 -0
  151. package/src/resources/extensions/gsd/memory-store.ts +351 -7
  152. package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
  153. package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
  154. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  155. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  156. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
  157. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  158. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  159. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -2
  160. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  161. package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
  162. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  163. package/src/resources/extensions/gsd/tests/memory-embeddings.test.ts +213 -0
  164. package/src/resources/extensions/gsd/tests/memory-ingest.test.ts +153 -0
  165. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +107 -0
  166. package/src/resources/extensions/gsd/tests/memory-relations.test.ts +175 -0
  167. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  168. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +295 -0
  169. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
  170. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
  171. package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
  172. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
  173. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
  174. package/src/resources/extensions/gsd/tools/memory-tools.ts +380 -0
  175. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
  176. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  177. package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
  178. package/src/resources/extensions/search-the-web/native-search.ts +13 -3
  179. package/src/resources/extensions/gsd/tests/onboarding-handler-loader.test.ts +0 -41
  180. /package/dist/web/standalone/.next/static/{J2z3GMC9QtSLr7gyoM38c → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
  181. /package/dist/web/standalone/.next/static/{J2z3GMC9QtSLr7gyoM38c → By_yegSJ-AA1OP0QjYbSl}/_ssgManifest.js +0 -0
@@ -0,0 +1,175 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { closeDatabase, openDatabase } from '../gsd-db.ts';
5
+ import { applyMemoryActions, createMemory, supersedeMemory } from '../memory-store.ts';
6
+ import {
7
+ createMemoryRelation,
8
+ isValidRelation,
9
+ listRelationsFor,
10
+ removeMemoryRelationsFor,
11
+ traverseGraph,
12
+ VALID_RELATIONS,
13
+ } from '../memory-relations.ts';
14
+ import { executeGsdGraph } from '../tools/memory-tools.ts';
15
+
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+ // Validation
18
+ // ═══════════════════════════════════════════════════════════════════════════
19
+
20
+ test('memory-relations: isValidRelation recognises the canonical set', () => {
21
+ for (const rel of VALID_RELATIONS) {
22
+ assert.ok(isValidRelation(rel));
23
+ }
24
+ assert.equal(isValidRelation('bogus'), false);
25
+ assert.equal(isValidRelation(null), false);
26
+ });
27
+
28
+ // ═══════════════════════════════════════════════════════════════════════════
29
+ // createMemoryRelation
30
+ // ═══════════════════════════════════════════════════════════════════════════
31
+
32
+ test('memory-relations: createMemoryRelation round-trips', () => {
33
+ openDatabase(':memory:');
34
+ createMemory({ category: 'architecture', content: 'auth lives in src/auth' });
35
+ createMemory({ category: 'pattern', content: 'jwt tokens signed with HS256' });
36
+
37
+ const ok = createMemoryRelation('MEM001', 'MEM002', 'elaborates', 0.9);
38
+ assert.equal(ok, true);
39
+
40
+ const relations = listRelationsFor('MEM001');
41
+ assert.equal(relations.length, 1);
42
+ assert.equal(relations[0].from, 'MEM001');
43
+ assert.equal(relations[0].to, 'MEM002');
44
+ assert.equal(relations[0].rel, 'elaborates');
45
+ assert.equal(relations[0].confidence, 0.9);
46
+
47
+ closeDatabase();
48
+ });
49
+
50
+ test('memory-relations: rejects self-loops and unknown memories', () => {
51
+ openDatabase(':memory:');
52
+ createMemory({ category: 'gotcha', content: 'only one memory exists' });
53
+
54
+ assert.equal(createMemoryRelation('MEM001', 'MEM001', 'related_to'), false);
55
+ assert.equal(createMemoryRelation('MEM001', 'MEM999', 'related_to'), false);
56
+ assert.equal(createMemoryRelation('MEM001', 'MEM002', 'bogus-rel' as never), false);
57
+
58
+ closeDatabase();
59
+ });
60
+
61
+ test('memory-relations: duplicate (from, to, rel) upserts instead of erroring', () => {
62
+ openDatabase(':memory:');
63
+ createMemory({ category: 'pattern', content: 'A' });
64
+ createMemory({ category: 'pattern', content: 'B' });
65
+
66
+ assert.equal(createMemoryRelation('MEM001', 'MEM002', 'related_to', 0.7), true);
67
+ assert.equal(createMemoryRelation('MEM001', 'MEM002', 'related_to', 0.9), true);
68
+
69
+ const relations = listRelationsFor('MEM001');
70
+ assert.equal(relations.length, 1);
71
+ assert.equal(relations[0].confidence, 0.9);
72
+
73
+ closeDatabase();
74
+ });
75
+
76
+ test('memory-relations: removeMemoryRelationsFor wipes both directions', () => {
77
+ openDatabase(':memory:');
78
+ createMemory({ category: 'pattern', content: 'A' });
79
+ createMemory({ category: 'pattern', content: 'B' });
80
+ createMemory({ category: 'pattern', content: 'C' });
81
+ createMemoryRelation('MEM001', 'MEM002', 'related_to');
82
+ createMemoryRelation('MEM003', 'MEM002', 'elaborates');
83
+
84
+ removeMemoryRelationsFor('MEM002');
85
+
86
+ assert.equal(listRelationsFor('MEM001').length, 0);
87
+ assert.equal(listRelationsFor('MEM002').length, 0);
88
+ assert.equal(listRelationsFor('MEM003').length, 0);
89
+
90
+ closeDatabase();
91
+ });
92
+
93
+ // ═══════════════════════════════════════════════════════════════════════════
94
+ // LINK action via applyMemoryActions
95
+ // ═══════════════════════════════════════════════════════════════════════════
96
+
97
+ test('memory-relations: applyMemoryActions processes LINK actions', () => {
98
+ openDatabase(':memory:');
99
+ createMemory({ category: 'architecture', content: 'core lives in src/core' });
100
+ createMemory({ category: 'pattern', content: 'events flow through EventEmitter' });
101
+
102
+ applyMemoryActions([
103
+ { action: 'LINK', from: 'MEM001', to: 'MEM002', rel: 'related_to', confidence: 0.75 },
104
+ // unknown rel type — should be skipped silently
105
+ { action: 'LINK', from: 'MEM001', to: 'MEM002', rel: 'nonsense-rel' as never },
106
+ ]);
107
+
108
+ const relations = listRelationsFor('MEM001');
109
+ assert.equal(relations.length, 1);
110
+ assert.equal(relations[0].rel, 'related_to');
111
+
112
+ closeDatabase();
113
+ });
114
+
115
+ // ═══════════════════════════════════════════════════════════════════════════
116
+ // traverseGraph + gsd_graph tool
117
+ // ═══════════════════════════════════════════════════════════════════════════
118
+
119
+ test('memory-relations: traverseGraph walks multi-hop edges', () => {
120
+ openDatabase(':memory:');
121
+ createMemory({ category: 'pattern', content: 'A' });
122
+ createMemory({ category: 'pattern', content: 'B' });
123
+ createMemory({ category: 'pattern', content: 'C' });
124
+ createMemoryRelation('MEM001', 'MEM002', 'related_to');
125
+ createMemoryRelation('MEM002', 'MEM003', 'depends_on');
126
+
127
+ const hop1 = traverseGraph('MEM001', 1);
128
+ assert.equal(hop1.nodes.length, 2);
129
+ assert.equal(hop1.edges.filter((e) => e.rel !== 'supersedes').length, 1);
130
+
131
+ const hop2 = traverseGraph('MEM001', 2);
132
+ assert.equal(hop2.nodes.length, 3);
133
+ assert.equal(hop2.edges.filter((e) => e.rel !== 'supersedes').length, 2);
134
+
135
+ closeDatabase();
136
+ });
137
+
138
+ test('memory-relations: traverseGraph still reports supersedes edges', () => {
139
+ openDatabase(':memory:');
140
+ createMemory({ category: 'convention', content: 'old style' });
141
+ createMemory({ category: 'convention', content: 'new style' });
142
+ supersedeMemory('MEM001', 'MEM002');
143
+
144
+ const graph = traverseGraph('MEM001', 1);
145
+ const ids = graph.nodes.map((n) => n.id).sort();
146
+ assert.deepEqual(ids, ['MEM001', 'MEM002']);
147
+ assert.ok(graph.edges.some((e) => e.rel === 'supersedes' && e.from === 'MEM001' && e.to === 'MEM002'));
148
+
149
+ closeDatabase();
150
+ });
151
+
152
+ test('memory-tools: gsd_graph returns LINK edges and filters by rel', () => {
153
+ openDatabase(':memory:');
154
+ createMemory({ category: 'pattern', content: 'alpha' });
155
+ createMemory({ category: 'pattern', content: 'beta' });
156
+ createMemory({ category: 'pattern', content: 'gamma' });
157
+ createMemoryRelation('MEM001', 'MEM002', 'related_to');
158
+ createMemoryRelation('MEM001', 'MEM003', 'contradicts');
159
+
160
+ const all = executeGsdGraph({ mode: 'query', memoryId: 'MEM001', depth: 1 });
161
+ assert.ok(!all.isError);
162
+ const allEdges = all.details.edges as Array<{ rel: string }>;
163
+ const relTypes = new Set(allEdges.map((e) => e.rel));
164
+ assert.ok(relTypes.has('related_to'));
165
+ assert.ok(relTypes.has('contradicts'));
166
+
167
+ const filtered = executeGsdGraph({ mode: 'query', memoryId: 'MEM001', depth: 1, rel: 'related_to' });
168
+ assert.ok(!filtered.isError);
169
+ const fEdges = filtered.details.edges as Array<{ rel: string; to: string }>;
170
+ assert.equal(fEdges.length, 1);
171
+ assert.equal(fEdges[0].rel, 'related_to');
172
+ assert.equal(fEdges[0].to, 'MEM002');
173
+
174
+ closeDatabase();
175
+ });
@@ -323,9 +323,9 @@ test('memory-store: schema includes memories table', () => {
323
323
  const viewCount = adapter.prepare('SELECT count(*) as cnt FROM active_memories').get();
324
324
  assert.deepStrictEqual(viewCount?.['cnt'], 0, 'active_memories view should exist');
325
325
 
326
- // Verify schema version is 17 (ADR-011 P2 escalation columns included)
326
+ // Verify schema version is 20 (memory Phase 4 relations included)
327
327
  const version = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
328
- assert.deepStrictEqual(version?.['v'], 17, 'schema version should be 17');
328
+ assert.deepStrictEqual(version?.['v'], 20, 'schema version should be 20');
329
329
 
330
330
  closeDatabase();
331
331
  });
@@ -0,0 +1,295 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { closeDatabase, openDatabase } from '../gsd-db.ts';
5
+ import { createMemory, supersedeMemory } from '../memory-store.ts';
6
+ import {
7
+ executeGsdGraph,
8
+ executeMemoryCapture,
9
+ executeMemoryQuery,
10
+ } from '../tools/memory-tools.ts';
11
+
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+ // capture_thought
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+
16
+ test('memory-tools: capture_thought creates a memory with a MEM id', () => {
17
+ openDatabase(':memory:');
18
+
19
+ const result = executeMemoryCapture({
20
+ category: 'gotcha',
21
+ content: 'sql.js FTS5 virtual tables need explicit triggers.',
22
+ });
23
+
24
+ assert.ok(!result.isError, 'capture should not error');
25
+ assert.equal(result.details.operation, 'memory_capture');
26
+ assert.equal(result.details.id, 'MEM001');
27
+ assert.equal(result.details.category, 'gotcha');
28
+ assert.equal(result.details.confidence, 0.8);
29
+
30
+ closeDatabase();
31
+ });
32
+
33
+ test('memory-tools: capture_thought rejects invalid category', () => {
34
+ openDatabase(':memory:');
35
+
36
+ const result = executeMemoryCapture({
37
+ category: 'opinion', // not in the allow-list
38
+ content: 'some content',
39
+ });
40
+
41
+ assert.ok(result.isError, 'invalid category should error');
42
+ assert.equal(result.details.error, 'invalid_category');
43
+
44
+ closeDatabase();
45
+ });
46
+
47
+ test('memory-tools: capture_thought rejects missing fields', () => {
48
+ openDatabase(':memory:');
49
+
50
+ const empty = executeMemoryCapture({ category: '', content: '' });
51
+ assert.ok(empty.isError, 'missing fields should error');
52
+ assert.equal(empty.details.error, 'missing_fields');
53
+
54
+ closeDatabase();
55
+ });
56
+
57
+ test('memory-tools: capture_thought clamps confidence to the 0.1–0.99 range', () => {
58
+ openDatabase(':memory:');
59
+
60
+ const hi = executeMemoryCapture({
61
+ category: 'convention',
62
+ content: 'clamp test high',
63
+ confidence: 42,
64
+ });
65
+ assert.ok(!hi.isError);
66
+ assert.equal(hi.details.confidence, 0.99);
67
+
68
+ const lo = executeMemoryCapture({
69
+ category: 'convention',
70
+ content: 'clamp test low',
71
+ confidence: -5,
72
+ });
73
+ assert.ok(!lo.isError);
74
+ assert.equal(lo.details.confidence, 0.1);
75
+
76
+ closeDatabase();
77
+ });
78
+
79
+ test('memory-tools: capture_thought fails gracefully when DB is closed', () => {
80
+ closeDatabase();
81
+
82
+ const result = executeMemoryCapture({
83
+ category: 'gotcha',
84
+ content: 'db closed',
85
+ });
86
+
87
+ assert.ok(result.isError, 'db-closed capture should error');
88
+ assert.equal(result.details.error, 'db_unavailable');
89
+ });
90
+
91
+ // ═══════════════════════════════════════════════════════════════════════════
92
+ // memory_query
93
+ // ═══════════════════════════════════════════════════════════════════════════
94
+
95
+ test('memory-tools: memory_query keyword-matches and ranks by confidence × hits', () => {
96
+ openDatabase(':memory:');
97
+
98
+ createMemory({ category: 'gotcha', content: 'sql.js FTS5 triggers must mirror insert/update/delete', confidence: 0.9 });
99
+ createMemory({ category: 'pattern', content: 'use prepared statements for hot paths', confidence: 0.7 });
100
+ createMemory({ category: 'convention', content: 'prefer async iterators for streaming', confidence: 0.8 });
101
+
102
+ const result = executeMemoryQuery({ query: 'sql triggers', k: 5 });
103
+ assert.ok(!result.isError);
104
+ const hits = result.details.hits as Array<{ id: string; content: string; reason: string }>;
105
+ assert.ok(hits.length >= 1, 'should return at least one keyword hit');
106
+ assert.equal(hits[0].id, 'MEM001', 'most-relevant memory should rank first');
107
+ assert.equal(hits[0].reason, 'keyword');
108
+
109
+ closeDatabase();
110
+ });
111
+
112
+ test('memory-tools: memory_query returns empty list when no keywords match', () => {
113
+ openDatabase(':memory:');
114
+ createMemory({ category: 'gotcha', content: 'unrelated content' });
115
+
116
+ const result = executeMemoryQuery({ query: 'totally different phrase', k: 5 });
117
+ assert.ok(!result.isError);
118
+ const hits = result.details.hits as unknown[];
119
+ assert.equal(hits.length, 0);
120
+ assert.equal(result.content[0].text, 'No matching memories.');
121
+
122
+ closeDatabase();
123
+ });
124
+
125
+ test('memory-tools: memory_query with empty query returns ranked memories', () => {
126
+ openDatabase(':memory:');
127
+ createMemory({ category: 'gotcha', content: 'first', confidence: 0.95 });
128
+ createMemory({ category: 'pattern', content: 'second', confidence: 0.5 });
129
+
130
+ const result = executeMemoryQuery({ query: ' ' });
131
+ assert.ok(!result.isError);
132
+ const hits = result.details.hits as Array<{ id: string; reason: string }>;
133
+ assert.equal(hits.length, 2);
134
+ assert.equal(hits[0].id, 'MEM001', 'higher-confidence memory ranks first under empty query');
135
+ assert.equal(hits[0].reason, 'ranked');
136
+
137
+ closeDatabase();
138
+ });
139
+
140
+ test('memory-tools: memory_query filters by category', () => {
141
+ openDatabase(':memory:');
142
+ createMemory({ category: 'gotcha', content: 'db pragma needed' });
143
+ createMemory({ category: 'pattern', content: 'db pragma needed' });
144
+
145
+ const result = executeMemoryQuery({ query: 'db pragma', category: 'gotcha' });
146
+ assert.ok(!result.isError);
147
+ const hits = result.details.hits as Array<{ id: string; category: string }>;
148
+ assert.equal(hits.length, 1);
149
+ assert.equal(hits[0].category, 'gotcha');
150
+
151
+ closeDatabase();
152
+ });
153
+
154
+ test('memory-tools: memory_query filters by scope and tag', () => {
155
+ openDatabase(':memory:');
156
+ createMemory({ category: 'gotcha', content: 'shared fact', scope: 'project', tags: ['db', 'sqlite'] });
157
+ createMemory({ category: 'gotcha', content: 'global fact', scope: 'global', tags: ['db'] });
158
+ createMemory({ category: 'gotcha', content: 'other scope', scope: 'project', tags: ['net'] });
159
+
160
+ const projectScoped = executeMemoryQuery({ query: 'fact', scope: 'project' });
161
+ assert.ok(!projectScoped.isError);
162
+ const projectHits = projectScoped.details.hits as Array<{ id: string }>;
163
+ assert.equal(projectHits.length, 1);
164
+ assert.equal(projectHits[0].id, 'MEM001');
165
+
166
+ const tagged = executeMemoryQuery({ query: 'fact', tag: 'sqlite' });
167
+ assert.ok(!tagged.isError);
168
+ const taggedHits = tagged.details.hits as Array<{ id: string }>;
169
+ assert.equal(taggedHits.length, 1);
170
+ assert.equal(taggedHits[0].id, 'MEM001');
171
+
172
+ closeDatabase();
173
+ });
174
+
175
+ test('memory-tools: capture_thought stores scope and tags', () => {
176
+ openDatabase(':memory:');
177
+ const result = executeMemoryCapture({
178
+ category: 'architecture',
179
+ content: 'use WAL journaling by default',
180
+ scope: 'global',
181
+ tags: ['sqlite', 'wal'],
182
+ });
183
+ assert.ok(!result.isError);
184
+ assert.equal(result.details.scope, 'global');
185
+ assert.deepEqual(result.details.tags, ['sqlite', 'wal']);
186
+
187
+ const lookup = executeMemoryQuery({ query: 'WAL', scope: 'global', tag: 'wal' });
188
+ const hits = lookup.details.hits as Array<{ id: string }>;
189
+ assert.equal(hits.length, 1);
190
+ assert.equal(hits[0].id, 'MEM001');
191
+
192
+ closeDatabase();
193
+ });
194
+
195
+ test('memory-tools: memory_query clamps k to the 1–50 range', () => {
196
+ openDatabase(':memory:');
197
+ for (let i = 0; i < 10; i++) {
198
+ createMemory({ category: 'pattern', content: `pattern ${i}` });
199
+ }
200
+
201
+ const tooLow = executeMemoryQuery({ query: 'pattern', k: 0 });
202
+ assert.ok(!tooLow.isError);
203
+ assert.equal((tooLow.details.hits as unknown[]).length, 1);
204
+
205
+ const sane = executeMemoryQuery({ query: 'pattern', k: 999 });
206
+ assert.ok(!sane.isError);
207
+ assert.equal((sane.details.hits as unknown[]).length, 10);
208
+
209
+ closeDatabase();
210
+ });
211
+
212
+ test('memory-tools: memory_query can reinforce returned hits', () => {
213
+ openDatabase(':memory:');
214
+ createMemory({ category: 'gotcha', content: 'needs reinforcement' });
215
+
216
+ executeMemoryQuery({ query: 'reinforcement', reinforce_hits: true });
217
+ const follow = executeMemoryQuery({ query: 'reinforcement' });
218
+ const hits = follow.details.hits as Array<{ hit_count: number }>;
219
+ assert.equal(hits.length, 1);
220
+ assert.ok(hits[0].hit_count >= 1, 'hit_count should increment on reinforce');
221
+
222
+ closeDatabase();
223
+ });
224
+
225
+ test('memory-tools: memory_query ignores superseded memories by default', () => {
226
+ openDatabase(':memory:');
227
+ createMemory({ category: 'convention', content: 'old way of doing X' });
228
+ createMemory({ category: 'convention', content: 'new way of doing X' });
229
+ supersedeMemory('MEM001', 'MEM002');
230
+
231
+ const active = executeMemoryQuery({ query: 'doing' });
232
+ const activeHits = active.details.hits as Array<{ id: string }>;
233
+ assert.equal(activeHits.length, 1);
234
+ assert.equal(activeHits[0].id, 'MEM002');
235
+
236
+ const withSuperseded = executeMemoryQuery({ query: 'doing', include_superseded: true });
237
+ const allHits = withSuperseded.details.hits as Array<{ id: string }>;
238
+ assert.equal(allHits.length, 2);
239
+
240
+ closeDatabase();
241
+ });
242
+
243
+ // ═══════════════════════════════════════════════════════════════════════════
244
+ // gsd_graph
245
+ // ═══════════════════════════════════════════════════════════════════════════
246
+
247
+ test('memory-tools: gsd_graph build acknowledges request', () => {
248
+ openDatabase(':memory:');
249
+ const result = executeGsdGraph({ mode: 'build' });
250
+ assert.ok(!result.isError);
251
+ assert.equal(result.details.operation, 'gsd_graph');
252
+ assert.equal(result.details.mode, 'build');
253
+ closeDatabase();
254
+ });
255
+
256
+ test('memory-tools: gsd_graph query rejects missing memoryId', () => {
257
+ openDatabase(':memory:');
258
+ const result = executeGsdGraph({ mode: 'query' });
259
+ assert.ok(result.isError);
260
+ assert.equal(result.details.error, 'missing_memory_id');
261
+ closeDatabase();
262
+ });
263
+
264
+ test('memory-tools: gsd_graph query returns node + supersedes edge', () => {
265
+ openDatabase(':memory:');
266
+ createMemory({ category: 'convention', content: 'first' });
267
+ createMemory({ category: 'convention', content: 'second' });
268
+ supersedeMemory('MEM001', 'MEM002');
269
+
270
+ const result = executeGsdGraph({ mode: 'query', memoryId: 'MEM001', depth: 1 });
271
+ assert.ok(!result.isError);
272
+ const nodes = result.details.nodes as Array<{ id: string }>;
273
+ const edges = result.details.edges as Array<{ from: string; to: string; rel: string }>;
274
+
275
+ const nodeIds = nodes.map((n) => n.id).sort();
276
+ assert.deepEqual(nodeIds, ['MEM001', 'MEM002']);
277
+ assert.ok(edges.some((e) => e.from === 'MEM001' && e.to === 'MEM002' && e.rel === 'supersedes'));
278
+
279
+ closeDatabase();
280
+ });
281
+
282
+ test('memory-tools: gsd_graph query returns empty when memoryId does not exist', () => {
283
+ openDatabase(':memory:');
284
+ const result = executeGsdGraph({ mode: 'query', memoryId: 'MEM999' });
285
+ assert.ok(!result.isError, 'missing memory is not an error, just empty');
286
+ assert.equal((result.details.nodes as unknown[]).length, 0);
287
+ closeDatabase();
288
+ });
289
+
290
+ test('memory-tools: gsd_graph errors when DB is closed', () => {
291
+ closeDatabase();
292
+ const result = executeGsdGraph({ mode: 'query', memoryId: 'MEM001' });
293
+ assert.ok(result.isError);
294
+ assert.equal(result.details.error, 'db_unavailable');
295
+ });
@@ -1579,3 +1579,69 @@ describe("checkTaskOrdering directory inputs (#4446)", () => {
1579
1579
  );
1580
1580
  });
1581
1581
  });
1582
+
1583
+ describe("checkFilePathConsistency self-referential inputs (#4459)", () => {
1584
+ test("input that is also in the same task's expected_output is not blocking when missing on disk", (t) => {
1585
+ const tempDir = join(tmpdir(), `pre-exec-self-output-${Date.now()}`);
1586
+ mkdirSync(tempDir, { recursive: true });
1587
+ t.after(() => rmSync(tempDir, { recursive: true, force: true }));
1588
+
1589
+ const tasks = [
1590
+ createTask({
1591
+ id: "T02",
1592
+ sequence: 0,
1593
+ inputs: ["src/components/email/SnoozePopover.jsx"],
1594
+ expected_output: ["src/components/email/SnoozePopover.jsx"],
1595
+ }),
1596
+ ];
1597
+
1598
+ const results = checkFilePathConsistency(tasks, tempDir);
1599
+ assert.deepEqual(
1600
+ results,
1601
+ [],
1602
+ "File declared as both input and expected_output of the same task should not block — the task itself produces it",
1603
+ );
1604
+ });
1605
+
1606
+ test("input missing from disk, missing from prior outputs, and missing from own expected_output still blocks", (t) => {
1607
+ const tempDir = join(tmpdir(), `pre-exec-self-output-missing-${Date.now()}`);
1608
+ mkdirSync(tempDir, { recursive: true });
1609
+ t.after(() => rmSync(tempDir, { recursive: true, force: true }));
1610
+
1611
+ const tasks = [
1612
+ createTask({
1613
+ id: "T02",
1614
+ sequence: 0,
1615
+ inputs: ["src/components/email/SnoozePopover.jsx"],
1616
+ expected_output: ["src/other/unrelated.jsx"],
1617
+ }),
1618
+ ];
1619
+
1620
+ const results = checkFilePathConsistency(tasks, tempDir);
1621
+ assert.equal(results.length, 1, "Genuinely missing input should still be reported");
1622
+ assert.equal(results[0].blocking, true);
1623
+ assert.equal(results[0].target, "src/components/email/SnoozePopover.jsx");
1624
+ });
1625
+
1626
+ test("self-output exemption matches across path normalization (./ prefix)", (t) => {
1627
+ const tempDir = join(tmpdir(), `pre-exec-self-output-norm-${Date.now()}`);
1628
+ mkdirSync(tempDir, { recursive: true });
1629
+ t.after(() => rmSync(tempDir, { recursive: true, force: true }));
1630
+
1631
+ const tasks = [
1632
+ createTask({
1633
+ id: "T02",
1634
+ sequence: 0,
1635
+ inputs: ["./src/generated.ts"],
1636
+ expected_output: ["src/generated.ts"],
1637
+ }),
1638
+ ];
1639
+
1640
+ const results = checkFilePathConsistency(tasks, tempDir);
1641
+ assert.deepEqual(
1642
+ results,
1643
+ [],
1644
+ "./src/generated.ts and src/generated.ts should compare equal after normalization",
1645
+ );
1646
+ });
1647
+ });