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.
- package/dist/resources/extensions/gsd/auto/phases.js +2 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
- package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
- package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
- package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +128 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +17 -4
- package/dist/resources/extensions/gsd/commands/handlers/onboarding.js +52 -92
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-memory.js +462 -0
- package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
- package/dist/resources/extensions/gsd/gsd-db.js +237 -4
- package/dist/resources/extensions/gsd/memory-embeddings.js +219 -0
- package/dist/resources/extensions/gsd/memory-extractor.js +78 -27
- package/dist/resources/extensions/gsd/memory-ingest.js +218 -0
- package/dist/resources/extensions/gsd/memory-relations.js +189 -0
- package/dist/resources/extensions/gsd/memory-source-store.js +113 -0
- package/dist/resources/extensions/gsd/memory-store.js +299 -6
- package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
- package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/dist/resources/extensions/gsd/tools/memory-tools.js +306 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
- package/dist/resources/extensions/search-the-web/native-search.js +13 -2
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +12 -10
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +8 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +207 -71
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +40 -4
- package/packages/mcp-server/src/server.ts +12 -10
- package/packages/mcp-server/src/session-manager.ts +10 -3
- package/packages/mcp-server/src/workflow-tools.test.ts +346 -1
- package/packages/mcp-server/src/workflow-tools.ts +228 -75
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
- package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.js +47 -0
- package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
- package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
- package/packages/pi-ai/src/index.ts +1 -0
- package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
- package/packages/pi-ai/src/providers/api-family.ts +57 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts +6 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +14 -4
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/cli/args.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/cli/args.test.js +38 -0
- package/packages/pi-coding-agent/dist/cli/args.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +7 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +3 -0
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.test.ts +44 -0
- package/packages/pi-coding-agent/src/cli/args.ts +21 -6
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
- package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/main.ts +4 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
- package/src/resources/extensions/gsd/auto/phases.ts +3 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
- package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
- package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +158 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +20 -4
- package/src/resources/extensions/gsd/commands/handlers/onboarding.ts +65 -131
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-memory.ts +551 -0
- package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
- package/src/resources/extensions/gsd/gsd-db.ts +273 -4
- package/src/resources/extensions/gsd/memory-embeddings.ts +235 -0
- package/src/resources/extensions/gsd/memory-extractor.ts +100 -34
- package/src/resources/extensions/gsd/memory-ingest.ts +286 -0
- package/src/resources/extensions/gsd/memory-relations.ts +240 -0
- package/src/resources/extensions/gsd/memory-source-store.ts +138 -0
- package/src/resources/extensions/gsd/memory-store.ts +351 -7
- package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
- package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-embeddings.test.ts +213 -0
- package/src/resources/extensions/gsd/tests/memory-ingest.test.ts +153 -0
- package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/memory-relations.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/memory-tools.test.ts +295 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
- package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
- package/src/resources/extensions/gsd/tools/memory-tools.ts +380 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
- package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
- package/src/resources/extensions/search-the-web/native-search.ts +13 -3
- package/src/resources/extensions/gsd/tests/onboarding-handler-loader.test.ts +0 -41
- /package/dist/web/standalone/.next/static/{J2z3GMC9QtSLr7gyoM38c → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
- /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
|
|
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'],
|
|
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
|
+
});
|