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
@@ -2,7 +2,8 @@
2
2
  //
3
3
  // Storage layer for auto-learned project memories. Follows context-store.ts patterns.
4
4
  // All functions degrade gracefully: return empty results when DB unavailable, never throw.
5
- import { isDbAvailable, _getAdapter, transaction, insertMemoryRow, rewriteMemoryId, updateMemoryContentRow, incrementMemoryHitCount, supersedeMemoryRow, markMemoryUnitProcessed, decayMemoriesBefore, supersedeLowestRankedMemories, } from './gsd-db.js';
5
+ import { isDbAvailable, _getAdapter, transaction, insertMemoryRow, rewriteMemoryId, updateMemoryContentRow, incrementMemoryHitCount, supersedeMemoryRow, markMemoryUnitProcessed, decayMemoriesBefore, supersedeLowestRankedMemories, deleteMemoryEmbedding, deleteMemoryRelationsFor, } from './gsd-db.js';
6
+ import { createMemoryRelation, isValidRelation } from './memory-relations.js';
6
7
  // ─── Category Display Order ─────────────────────────────────────────────────
7
8
  const CATEGORY_PRIORITY = {
8
9
  gotcha: 0,
@@ -26,8 +27,21 @@ function rowToMemory(row) {
26
27
  updated_at: row['updated_at'],
27
28
  superseded_by: row['superseded_by'] ?? null,
28
29
  hit_count: row['hit_count'],
30
+ scope: row['scope'] ?? 'project',
31
+ tags: parseTags(row['tags']),
29
32
  };
30
33
  }
34
+ function parseTags(raw) {
35
+ if (typeof raw !== 'string' || raw.length === 0)
36
+ return [];
37
+ try {
38
+ const parsed = JSON.parse(raw);
39
+ return Array.isArray(parsed) ? parsed.filter((t) => typeof t === 'string') : [];
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ }
31
45
  // ─── Query Functions ────────────────────────────────────────────────────────
32
46
  /**
33
47
  * Get all memories where superseded_by IS NULL.
@@ -68,6 +82,245 @@ export function getActiveMemoriesRanked(limit = 30) {
68
82
  return [];
69
83
  }
70
84
  }
85
+ export function queryMemoriesRanked(opts) {
86
+ if (!isDbAvailable())
87
+ return [];
88
+ const adapter = _getAdapter();
89
+ if (!adapter)
90
+ return [];
91
+ const k = clampLimit(opts.k, 10);
92
+ const rrfK = opts.rrfK ?? 60;
93
+ const activeClause = opts.include_superseded === true ? '' : 'WHERE superseded_by IS NULL';
94
+ const trimmedQuery = (opts.query ?? '').trim();
95
+ // 1) Keyword hits — try FTS5 first, fall back to LIKE when unavailable.
96
+ const keywordHits = trimmedQuery ? keywordSearch(adapter, trimmedQuery, activeClause, 50) : [];
97
+ // 2) Semantic hits — cosine over memory_embeddings. Requires opts.queryVector.
98
+ const semanticHits = opts.queryVector
99
+ ? semanticSearch(adapter, opts.queryVector, activeClause, 50)
100
+ : [];
101
+ if (keywordHits.length === 0 && semanticHits.length === 0 && !trimmedQuery) {
102
+ // No query at all — fall back to the existing ranked-by-score listing.
103
+ return getActiveMemoriesRanked(k).map((memory) => ({
104
+ memory,
105
+ score: memory.confidence * (1 + memory.hit_count * 0.1),
106
+ keywordRank: null,
107
+ semanticRank: null,
108
+ confidenceBoost: memory.confidence * (1 + memory.hit_count * 0.1),
109
+ reason: 'ranked',
110
+ })).filter((hit) => passesFilters(hit.memory, opts));
111
+ }
112
+ // 3) Reciprocal rank fusion — each hit contributes 1/(rrfK + rank).
113
+ const fused = new Map();
114
+ for (let i = 0; i < keywordHits.length; i++) {
115
+ const hit = keywordHits[i];
116
+ const existing = fused.get(hit.id);
117
+ const rrf = 1 / (rrfK + i + 1);
118
+ if (existing) {
119
+ existing.kwRank = i + 1;
120
+ existing.score += rrf;
121
+ }
122
+ else {
123
+ fused.set(hit.id, { memory: hit, kwRank: i + 1, semRank: null, score: rrf });
124
+ }
125
+ }
126
+ for (let i = 0; i < semanticHits.length; i++) {
127
+ const hit = semanticHits[i];
128
+ const existing = fused.get(hit.id);
129
+ const rrf = 1 / (rrfK + i + 1);
130
+ if (existing) {
131
+ existing.semRank = i + 1;
132
+ existing.score += rrf;
133
+ }
134
+ else {
135
+ fused.set(hit.id, { memory: hit, kwRank: null, semRank: i + 1, score: rrf });
136
+ }
137
+ }
138
+ // 4) Apply filters + confidence boost, then sort.
139
+ const ranked = [];
140
+ for (const entry of fused.values()) {
141
+ if (!passesFilters(entry.memory, opts))
142
+ continue;
143
+ const boost = entry.memory.confidence * (1 + entry.memory.hit_count * 0.1);
144
+ const reason = entry.kwRank != null && entry.semRank != null
145
+ ? 'both'
146
+ : entry.kwRank != null
147
+ ? 'keyword'
148
+ : 'semantic';
149
+ ranked.push({
150
+ memory: entry.memory,
151
+ score: entry.score * boost,
152
+ keywordRank: entry.kwRank,
153
+ semanticRank: entry.semRank,
154
+ confidenceBoost: boost,
155
+ reason,
156
+ });
157
+ }
158
+ ranked.sort((a, b) => b.score - a.score);
159
+ return ranked.slice(0, k);
160
+ }
161
+ function clampLimit(value, fallback) {
162
+ if (typeof value !== 'number' || !Number.isFinite(value))
163
+ return fallback;
164
+ if (value < 1)
165
+ return 1;
166
+ if (value > 100)
167
+ return 100;
168
+ return Math.floor(value);
169
+ }
170
+ function passesFilters(memory, filters) {
171
+ if (filters.category && memory.category.toLowerCase() !== filters.category.toLowerCase())
172
+ return false;
173
+ if (filters.scope && memory.scope !== filters.scope)
174
+ return false;
175
+ if (filters.tag) {
176
+ const needle = filters.tag.toLowerCase();
177
+ if (!memory.tags.map((t) => t.toLowerCase()).includes(needle))
178
+ return false;
179
+ }
180
+ return true;
181
+ }
182
+ function keywordSearch(adapter, rawQuery, activeClause, limit) {
183
+ const ftsAvailable = isFtsAvailable(adapter);
184
+ if (ftsAvailable) {
185
+ try {
186
+ const matchExpr = toFtsMatchExpr(rawQuery);
187
+ if (!matchExpr)
188
+ return [];
189
+ const activePart = activeClause ? `AND m.${activeClause.replace(/^WHERE\s+/i, '')}` : '';
190
+ const rows = adapter.prepare(`SELECT m.*
191
+ FROM memories_fts f
192
+ JOIN memories m ON m.seq = f.rowid
193
+ WHERE memories_fts MATCH :match
194
+ ${activePart}
195
+ ORDER BY bm25(memories_fts)
196
+ LIMIT :limit`).all({ ':match': matchExpr, ':limit': limit });
197
+ return rows.map(rowToMemory);
198
+ }
199
+ catch {
200
+ // fall through to LIKE
201
+ }
202
+ }
203
+ // LIKE fallback — scans the candidate pool.
204
+ const terms = rawQuery
205
+ .toLowerCase()
206
+ .split(/[^a-z0-9_]+/)
207
+ .filter((t) => t.length >= 2);
208
+ if (terms.length === 0)
209
+ return [];
210
+ const rows = adapter.prepare(`SELECT * FROM memories ${activeClause}`).all();
211
+ const scored = [];
212
+ for (const row of rows) {
213
+ const memory = rowToMemory(row);
214
+ const lower = memory.content.toLowerCase();
215
+ let score = 0;
216
+ for (const term of terms) {
217
+ const idx = lower.indexOf(term);
218
+ if (idx === -1)
219
+ continue;
220
+ score += 1 + (term.length >= 5 ? 0.5 : 0);
221
+ }
222
+ if (score > 0)
223
+ scored.push({ memory, score });
224
+ }
225
+ scored.sort((a, b) => b.score - a.score);
226
+ return scored.slice(0, limit).map((s) => s.memory);
227
+ }
228
+ function isFtsAvailable(adapter) {
229
+ try {
230
+ const row = adapter
231
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'")
232
+ .get();
233
+ return !!row;
234
+ }
235
+ catch {
236
+ return false;
237
+ }
238
+ }
239
+ function toFtsMatchExpr(query) {
240
+ // Build a tolerant AND expression: quote each bare term with a trailing *.
241
+ const tokens = query
242
+ .toLowerCase()
243
+ .split(/[^a-z0-9_]+/)
244
+ .filter((t) => t.length >= 2)
245
+ .slice(0, 8);
246
+ if (tokens.length === 0)
247
+ return null;
248
+ return tokens.map((t) => `"${t.replace(/"/g, '""')}"*`).join(' OR ');
249
+ }
250
+ function semanticSearch(adapter, queryVector, activeClause, limit) {
251
+ try {
252
+ const rows = adapter
253
+ .prepare(`SELECT m.*, e.vector as embedding_vector, e.dim as embedding_dim
254
+ FROM memories m
255
+ JOIN memory_embeddings e ON e.memory_id = m.id
256
+ ${activeClause}`)
257
+ .all();
258
+ const scored = [];
259
+ for (const row of rows) {
260
+ const dim = row['embedding_dim'];
261
+ if (dim !== queryVector.length)
262
+ continue;
263
+ const vector = unpackVector(row['embedding_vector'], dim);
264
+ if (!vector)
265
+ continue;
266
+ const sim = cosine(queryVector, vector);
267
+ if (sim <= 0)
268
+ continue;
269
+ scored.push({ memory: rowToMemory(row), sim });
270
+ }
271
+ scored.sort((a, b) => b.sim - a.sim);
272
+ return scored.slice(0, limit).map((s) => s.memory);
273
+ }
274
+ catch {
275
+ return [];
276
+ }
277
+ }
278
+ function unpackVector(blob, dim) {
279
+ if (!blob)
280
+ return null;
281
+ try {
282
+ let view = null;
283
+ if (blob instanceof Float32Array)
284
+ return blob;
285
+ if (blob instanceof Uint8Array)
286
+ view = blob;
287
+ else if (blob instanceof ArrayBuffer)
288
+ view = new Uint8Array(blob);
289
+ else if (blob.buffer && blob.byteLength != null) {
290
+ const buf = blob;
291
+ view = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
292
+ }
293
+ else if (Array.isArray(blob)) {
294
+ return new Float32Array(blob);
295
+ }
296
+ if (!view || view.byteLength % 4 !== 0)
297
+ return null;
298
+ const aligned = new ArrayBuffer(view.byteLength);
299
+ new Uint8Array(aligned).set(view);
300
+ const f32 = new Float32Array(aligned);
301
+ return f32.length === dim ? f32 : null;
302
+ }
303
+ catch {
304
+ return null;
305
+ }
306
+ }
307
+ function cosine(a, b) {
308
+ if (a.length === 0 || a.length !== b.length)
309
+ return 0;
310
+ let dot = 0;
311
+ let na = 0;
312
+ let nb = 0;
313
+ for (let i = 0; i < a.length; i++) {
314
+ const x = a[i];
315
+ const y = b[i];
316
+ dot += x * y;
317
+ na += x * x;
318
+ nb += y * y;
319
+ }
320
+ if (na === 0 || nb === 0)
321
+ return 0;
322
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
323
+ }
71
324
  /**
72
325
  * Generate the next memory ID: MEM + zero-padded 3-digit from MAX(seq).
73
326
  * Returns MEM001 if no memories exist.
@@ -121,6 +374,8 @@ export function createMemory(fields) {
121
374
  sourceUnitId: fields.source_unit_id ?? null,
122
375
  createdAt: now,
123
376
  updatedAt: now,
377
+ scope: fields.scope ?? 'project',
378
+ tags: fields.tags ?? [],
124
379
  });
125
380
  // Derive the real ID from the assigned seq (SELECT is still fine via adapter)
126
381
  const row = adapter.prepare('SELECT seq FROM memories WHERE id = :id').get({ ':id': placeholder });
@@ -213,29 +468,34 @@ export function markUnitProcessed(unitKey, activityFile) {
213
468
  /**
214
469
  * Reduce confidence for memories not updated within the last N processed units.
215
470
  * "Stale" = updated_at is older than the Nth most recent processed_at.
471
+ * Returns the number of decayed memory IDs for observability.
216
472
  */
217
473
  export function decayStaleMemories(thresholdUnits = 20) {
218
474
  if (!isDbAvailable())
219
- return;
475
+ return [];
220
476
  const adapter = _getAdapter();
221
477
  if (!adapter)
222
- return;
478
+ return [];
223
479
  try {
224
480
  // Find the timestamp of the Nth most recent processed unit (read-only SELECT)
225
481
  const row = adapter.prepare(`SELECT processed_at FROM memory_processed_units
226
482
  ORDER BY processed_at DESC
227
483
  LIMIT 1 OFFSET :offset`).get({ ':offset': thresholdUnits - 1 });
228
484
  if (!row)
229
- return; // not enough processed units yet
485
+ return []; // not enough processed units yet
230
486
  const cutoff = row['processed_at'];
487
+ const affected = adapter.prepare(`SELECT id FROM memories
488
+ WHERE superseded_by IS NULL AND updated_at < :cutoff AND confidence > 0.1`).all({ ':cutoff': cutoff }).map((r) => r['id']);
231
489
  decayMemoriesBefore(cutoff, new Date().toISOString());
490
+ return affected;
232
491
  }
233
492
  catch {
234
- // non-fatal
493
+ return [];
235
494
  }
236
495
  }
237
496
  /**
238
- * Supersede lowest-ranked memories when count exceeds cap.
497
+ * Supersede lowest-ranked memories when count exceeds cap. Cascades to the
498
+ * embedding and relation rows so those tables don't grow unboundedly.
239
499
  */
240
500
  export function enforceMemoryCap(max = 50) {
241
501
  if (!isDbAvailable())
@@ -249,7 +509,24 @@ export function enforceMemoryCap(max = 50) {
249
509
  if (count <= max)
250
510
  return;
251
511
  const excess = count - max;
512
+ // Capture the about-to-be-superseded IDs first so we can cascade cleanup.
513
+ const victims = adapter.prepare(`SELECT id FROM memories
514
+ WHERE superseded_by IS NULL
515
+ ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
516
+ LIMIT :limit`).all({ ':limit': excess }).map((row) => row['id']);
252
517
  supersedeLowestRankedMemories(excess, new Date().toISOString());
518
+ if (victims.length === 0)
519
+ return;
520
+ for (const id of victims) {
521
+ try {
522
+ deleteMemoryEmbedding(id);
523
+ }
524
+ catch { /* non-fatal */ }
525
+ try {
526
+ deleteMemoryRelationsFor(id);
527
+ }
528
+ catch { /* non-fatal */ }
529
+ }
253
530
  }
254
531
  catch {
255
532
  // non-fatal
@@ -274,6 +551,8 @@ export function applyMemoryActions(actions, unitType, unitId) {
274
551
  confidence: action.confidence,
275
552
  source_unit_type: unitType,
276
553
  source_unit_id: unitId,
554
+ scope: action.scope,
555
+ tags: action.tags,
277
556
  });
278
557
  break;
279
558
  case 'UPDATE':
@@ -285,6 +564,9 @@ export function applyMemoryActions(actions, unitType, unitId) {
285
564
  case 'SUPERSEDE':
286
565
  supersedeMemory(action.id, action.superseded_by);
287
566
  break;
567
+ case 'LINK':
568
+ applyLinkAction(action);
569
+ break;
288
570
  }
289
571
  }
290
572
  enforceMemoryCap();
@@ -294,6 +576,17 @@ export function applyMemoryActions(actions, unitType, unitId) {
294
576
  // non-fatal — transaction will have rolled back
295
577
  }
296
578
  }
579
+ // ─── LINK action ────────────────────────────────────────────────────────────
580
+ function applyLinkAction(action) {
581
+ try {
582
+ if (!isValidRelation(action.rel))
583
+ return;
584
+ createMemoryRelation(action.from, action.to, action.rel, action.confidence);
585
+ }
586
+ catch {
587
+ // Link failures should never break memory extraction.
588
+ }
589
+ }
297
590
  // ─── Prompt Formatting ──────────────────────────────────────────────────────
298
591
  /**
299
592
  * Format memories as categorized markdown for system prompt injection.
@@ -347,10 +347,13 @@ function getExpectedOutputsUpTo(tasks, taskIndex) {
347
347
  /**
348
348
  * Check that all files referenced in task.inputs either:
349
349
  * 1. Exist on disk, OR
350
- * 2. Are in a prior task's expected_output
350
+ * 2. Are in a prior task's expected_output, OR
351
+ * 3. Are in the current task's own expected_output — the task produces them,
352
+ * so they don't need to pre-exist (#4459, mirroring the exemption #3626
353
+ * introduced for task.files).
351
354
  *
352
- * task.files ("files likely touched") is excluded it intentionally includes
353
- * files the task will create, so they don't need to pre-exist (#3626).
355
+ * task.files ("files likely touched") is excluded entirely from this check —
356
+ * it intentionally includes files the task will create (#3626).
354
357
  *
355
358
  * All paths are normalized before comparison to ensure ./src/a.ts matches src/a.ts.
356
359
  */
@@ -359,6 +362,7 @@ export function checkFilePathConsistency(tasks, basePath) {
359
362
  for (let i = 0; i < tasks.length; i++) {
360
363
  const task = tasks[i];
361
364
  const priorOutputs = getExpectedOutputsUpTo(tasks, i);
365
+ const ownOutputs = new Set(task.expected_output.map(normalizeFilePath));
362
366
  const filesToCheck = [...task.inputs];
363
367
  for (const file of filesToCheck) {
364
368
  // Skip empty strings
@@ -375,21 +379,21 @@ export function checkFilePathConsistency(tasks, basePath) {
375
379
  const existsOnDisk = existsSync(absolutePath);
376
380
  // Check if file is in prior expected outputs (priorOutputs already normalized)
377
381
  const inPriorOutputs = priorOutputs.has(normalizedFile);
382
+ const inOwnOutputs = ownOutputs.has(normalizedFile);
378
383
  // Directory inputs are satisfied when something produces a file beneath
379
384
  // them — either a prior task or the current task itself.
380
385
  let directorySatisfied = false;
381
- if (!existsOnDisk && !inPriorOutputs && isDirectoryReference(file)) {
382
- const sameTaskOutputs = task.expected_output.map(normalizeFilePath);
386
+ if (!existsOnDisk && !inPriorOutputs && !inOwnOutputs && isDirectoryReference(file)) {
383
387
  directorySatisfied =
384
388
  anyOutputUnderDirectory(normalizedFile, priorOutputs) ||
385
- anyOutputUnderDirectory(normalizedFile, sameTaskOutputs);
389
+ anyOutputUnderDirectory(normalizedFile, ownOutputs);
386
390
  }
387
- if (!existsOnDisk && !inPriorOutputs && !directorySatisfied) {
391
+ if (!existsOnDisk && !inPriorOutputs && !inOwnOutputs && !directorySatisfied) {
388
392
  results.push({
389
393
  category: "file",
390
394
  target: file,
391
395
  passed: false,
392
- message: `Task ${task.id} references '${file}' which doesn't exist and isn't created by prior tasks`,
396
+ message: `Task ${task.id} references '${file}' which doesn't exist and isn't created by prior or same-task outputs`,
393
397
  blocking: true,
394
398
  });
395
399
  }
@@ -31,5 +31,6 @@ You are generating tests for recently completed GSD work.
31
31
  - Do NOT modify implementation files — only create or update test files
32
32
  - Name test files consistently with the project's conventions
33
33
  - Keep tests focused and readable
34
+ - Tests must only reference files that are tracked in git. Do NOT import, read, or depend on paths listed in `.gitignore` — in particular GSD-local state such as `.gsd/`, `.planning/`, and `.audits/`. If a test seems to need one of those files, replace it with an inline fixture or a tracked sample; otherwise the test will fail for everyone but the author.
34
35
 
35
36
  {{skillActivation}}
@@ -36,7 +36,7 @@ Then:
36
36
  2. Execute the steps in the inlined task plan, adapting minor local mismatches when the surrounding code differs from the planner's snapshot
37
37
  3. Before any `Write` that creates an artifact or output file, check whether that path already exists. If it does, read it first and decide whether the work is already done, should be extended, or truly needs replacement. "Create" in the plan does **not** mean the file is missing — a prior session may already have started it.
38
38
  4. Build the real thing. If the task plan says "create login endpoint", build an endpoint that actually authenticates against a real store, not one that returns a hardcoded success response. If the task plan says "create dashboard page", build a page that renders real data from the API, not a component with hardcoded props. Stubs and mocks are for tests, not for the shipped feature.
39
- 5. Write or update tests as part of execution — tests are verification, not an afterthought. If the slice plan defines test files in its Verification section and this is the first task, create them (they should initially fail).
39
+ 5. Write or update tests as part of execution — tests are verification, not an afterthought. If the slice plan defines test files in its Verification section and this is the first task, create them (they should initially fail). Tests must only reference files tracked in git; never import, read, or assert on paths listed in `.gitignore` (e.g. `.gsd/`, `.planning/`, `.audits/`) — those files are local-only and the test will fail for anyone else. Use inline fixtures or tracked samples instead.
40
40
  6. When implementing non-trivial runtime behavior (async flows, API boundaries, background processes, error paths), add or preserve agent-usable observability. Skip this for simple changes where it doesn't apply.
41
41
 
42
42
  **Background process rule:** Never use bare `command &` to run background processes. The shell's `&` operator leaves stdout/stderr attached to the parent, which causes the Bash tool to hang indefinitely waiting for those streams to close. Always redirect output before backgrounding:
@@ -53,6 +53,7 @@ Then:
53
53
  - For simple slices: executable commands or script assertions are fine.
54
54
  - If the project is non-trivial and has no test framework, the first task should set one up.
55
55
  - If this slice establishes a boundary contract, verification must exercise that contract.
56
+ - Planned test files must only read from or import paths that are tracked in git. Do NOT plan tests whose inputs or fixtures are paths listed in `.gitignore` (e.g. `.gsd/`, `.planning/`, `.audits/`). If the scenario seems to require such a file, plan an inline fixture or a tracked sample instead.
56
57
  4. **For non-trivial slices only** — plan observability, proof level, and integration closure:
57
58
  - Include `Observability / Diagnostics` for backend, integration, async, stateful, or UI slices where failure diagnosis matters.
58
59
  - Fill `Proof Level` and `Integration Closure` when the slice crosses runtime boundaries or has meaningful integration concerns.