gsd-pi 2.81.0-dev.72a81bdf3 → 2.82.0-dev.725028083

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 (135) hide show
  1. package/README.md +46 -28
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
  4. package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
  5. package/dist/resources/extensions/gsd/auto/phases.js +9 -0
  6. package/dist/resources/extensions/gsd/auto-prompts.js +11 -3
  7. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +5 -0
  8. package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
  9. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
  10. package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
  11. package/dist/resources/extensions/gsd/context-store.js +112 -0
  12. package/dist/resources/extensions/gsd/db-writer.js +150 -84
  13. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  14. package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
  15. package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
  16. package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
  17. package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
  18. package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
  19. package/dist/resources/extensions/gsd/markdown-renderer.js +6 -1
  20. package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
  21. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
  22. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  23. package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
  24. package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
  25. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  26. package/dist/web/standalone/.next/BUILD_ID +1 -1
  27. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  28. package/dist/web/standalone/.next/build-manifest.json +2 -2
  29. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  30. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.html +1 -1
  47. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  54. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  56. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  57. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  58. package/package.json +3 -2
  59. package/packages/daemon/package.json +2 -2
  60. package/packages/mcp-server/README.md +2 -0
  61. package/packages/mcp-server/package.json +2 -2
  62. package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
  63. package/packages/native/package.json +1 -1
  64. package/packages/pi-agent-core/package.json +1 -1
  65. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  66. package/packages/pi-ai/dist/index.d.ts +2 -2
  67. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  68. package/packages/pi-ai/dist/index.js +1 -1
  69. package/packages/pi-ai/dist/index.js.map +1 -1
  70. package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
  71. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  72. package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
  73. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  74. package/packages/pi-ai/package.json +1 -1
  75. package/packages/pi-ai/src/index.ts +7 -2
  76. package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
  77. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  78. package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
  79. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
  83. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
  84. package/packages/pi-coding-agent/package.json +1 -1
  85. package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
  86. package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
  87. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  88. package/packages/pi-tui/package.json +1 -1
  89. package/packages/rpc-client/package.json +1 -1
  90. package/pkg/package.json +1 -1
  91. package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
  92. package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
  93. package/src/resources/extensions/gsd/auto/phases.ts +14 -0
  94. package/src/resources/extensions/gsd/auto-prompts.ts +11 -3
  95. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
  96. package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
  97. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  98. package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
  99. package/src/resources/extensions/gsd/context-store.ts +120 -1
  100. package/src/resources/extensions/gsd/db-writer.ts +167 -84
  101. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  102. package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
  103. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  104. package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
  105. package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
  106. package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
  107. package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
  108. package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
  109. package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
  110. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
  111. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  112. package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
  113. package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
  114. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +75 -0
  115. package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
  116. package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
  117. package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
  118. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
  119. package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
  120. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
  121. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
  122. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
  123. package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
  124. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
  125. package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
  126. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
  127. package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
  128. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
  129. package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
  130. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -0
  131. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
  132. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
  133. package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
  134. /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → KDRTXR-22LPCsa80X9dey}/_buildManifest.js +0 -0
  135. /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → KDRTXR-22LPCsa80X9dey}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-tui",
3
- "version": "2.81.0",
3
+ "version": "2.82.0",
4
4
  "description": "Terminal User Interface library (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd-build/rpc-client",
3
- "version": "2.81.0",
3
+ "version": "2.82.0",
4
4
  "description": "Standalone RPC client SDK for GSD — zero internal dependencies",
5
5
  "license": "MIT",
6
6
  "gsd": {
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.81.0",
3
+ "version": "2.82.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -9,6 +9,7 @@ export function registerScreenshotTools(pi: ExtensionAPI, deps: ToolDeps): void
9
9
  label: "Browser Screenshot",
10
10
  description:
11
11
  "Take a screenshot of the current browser page and return it as an inline image. Uses JPEG for viewport/fullpage (smaller, configurable quality) and PNG for element crops (preserves transparency). Optionally crop to a specific element by CSS selector.",
12
+ compatibility: { producesImages: true },
12
13
  parameters: Type.Object({
13
14
  fullPage: Type.Optional(
14
15
  Type.Boolean({ description: "Capture the full scrollable page (default: false)" })
@@ -14,6 +14,7 @@ export function registerZoomTools(pi: ExtensionAPI, deps: ToolDeps): void {
14
14
  "Capture and optionally upscale a specific rectangular region of the page for detailed inspection. " +
15
15
  "Useful for dense UIs where full-page screenshots have text too small to read. " +
16
16
  "Returns the region as an inline image, same as browser_screenshot.",
17
+ compatibility: { producesImages: true },
17
18
  parameters: Type.Object({
18
19
  x: Type.Number({ description: "Left coordinate of the region in CSS pixels." }),
19
20
  y: Type.Number({ description: "Top coordinate of the region in CSS pixels." }),
@@ -2233,6 +2233,20 @@ export async function runUnitPhase(
2233
2233
  await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
2234
2234
  return { action: "break", reason: "session-timeout" };
2235
2235
  }
2236
+ if (
2237
+ unitResult.errorContext?.isTransient &&
2238
+ errorCategory === "aborted"
2239
+ ) {
2240
+ ctx.ui.notify(
2241
+ `Unit ${unitType} ${unitId} was aborted by the user. Pausing auto-mode (recoverable).`,
2242
+ "warning",
2243
+ );
2244
+ debugLog("autoLoop", { phase: "unit-aborted-transient-pause", unitType, unitId, category: errorCategory });
2245
+ await deps.pauseAuto(ctx, pi);
2246
+ await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
2247
+ await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
2248
+ return { action: "break", reason: "unit-aborted-pause" };
2249
+ }
2236
2250
  // All other cancelled states (structural errors, non-transient failures): hard stop
2237
2251
  if (s.currentUnit) {
2238
2252
  await deps.closeoutUnit(
@@ -806,14 +806,22 @@ export async function inlineDecisionsFromDb(
806
806
  try {
807
807
  const { isDbAvailable } = await import("./gsd-db.js");
808
808
  if (isDbAvailable()) {
809
- const { queryDecisions, formatDecisionsForPrompt } = await import("./context-store.js");
809
+ // ADR-013 Phase 6 cutover (Stage 1): read decisions from the `memories`
810
+ // table. Both `queryDecisions` (legacy) and `queryDecisionsFromMemories`
811
+ // return identical Decision[] for active rows once Phase 5 dual-write is
812
+ // caught up. Switching the read here lets the destructive Phase 6 step
813
+ // (#5755) retire the legacy `decisions` table without changing prompt
814
+ // contents. Projection regen (`DECISIONS.md`) still sources from the
815
+ // legacy table — that switch lands separately to handle superseded
816
+ // history cleanly.
817
+ const { queryDecisionsFromMemories, formatDecisionsForPrompt } = await import("./context-store.js");
810
818
 
811
819
  // First query: try with both milestoneId and scope (if scope provided)
812
- let decisions = queryDecisions({ milestoneId, scope });
820
+ let decisions = queryDecisionsFromMemories({ milestoneId, scope });
813
821
 
814
822
  // Cascade: if empty AND scope was provided, retry without scope
815
823
  if (decisions.length === 0 && scope) {
816
- decisions = queryDecisions({ milestoneId });
824
+ decisions = queryDecisionsFromMemories({ milestoneId });
817
825
  }
818
826
 
819
827
  if (decisions.length > 0) {
@@ -423,6 +423,10 @@ export function registerHooks(
423
423
  pi: ExtensionAPI,
424
424
  ecosystemHandlers: GSDEcosystemBeforeAgentStartHandler[],
425
425
  ): void {
426
+ // ADR-005 Phase 3b: surface pi-ai ProviderSwitchReport via audit, notification, and counter.
427
+ // Idempotent — only the first registerHooks call installs.
428
+ void import("../provider-switch-observer.js").then((m) => m.installProviderSwitchObserver());
429
+
426
430
  pi.on("session_start", async (_event, ctx) => {
427
431
  const basePath = contextBasePath(ctx);
428
432
  initSessionNotifications(ctx);
@@ -480,6 +484,8 @@ export function registerHooks(
480
484
  }
481
485
  await loadToolApiKeysForSession();
482
486
  if (!isAutoActive()) {
487
+ ctx.ui.setWidget("gsd-progress", undefined);
488
+ ctx.ui.setWidget("gsd-outcome", undefined);
483
489
  const { initHealthWidget } = await import("../health-widget.js");
484
490
  initHealthWidget(ctx);
485
491
  } else {
@@ -13,6 +13,7 @@ import { resolveAllSkillReferences, renderPreferencesForSystemPrompt, loadEffect
13
13
  import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
14
14
  import { resolveSkillReference } from "../preferences-skills.js";
15
15
  import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
16
+ import { extractIntroAndRules } from "../knowledge-parser.js";
16
17
  import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
17
18
  import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "../skill-discovery.js";
18
19
  import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
@@ -131,9 +132,14 @@ export async function buildBeforeAgentStartResult(
131
132
  logWarning("bootstrap", `cmux prompt setup skipped: ${(e as Error).message}`);
132
133
  }
133
134
 
135
+ const ctxProjectRoot = (ctx as { projectRoot?: unknown }).projectRoot;
136
+ const basePath = typeof ctxProjectRoot === "string" && ctxProjectRoot.length > 0
137
+ ? ctxProjectRoot
138
+ : process.cwd();
139
+
134
140
  let preferenceBlock = "";
135
141
  if (loadedPreferences) {
136
- const cwd = process.cwd();
142
+ const cwd = basePath;
137
143
  const report = resolveAllSkillReferences(loadedPreferences.preferences, cwd);
138
144
  preferenceBlock = `\n\n${renderPreferencesForSystemPrompt(loadedPreferences.preferences, report.resolutions)}`;
139
145
  if (report.warnings.length > 0) {
@@ -144,14 +150,6 @@ export async function buildBeforeAgentStartResult(
144
150
  }
145
151
  }
146
152
 
147
- const { block: knowledgeBlock, globalSizeKb } = loadKnowledgeBlock(gsdHome(), process.cwd());
148
- if (globalSizeKb > 4) {
149
- ctx.ui.notify(
150
- `GSD: ~/.gsd/agent/KNOWLEDGE.md is ${globalSizeKb.toFixed(1)}KB — consider trimming to keep system prompt lean.`,
151
- "warning",
152
- );
153
- }
154
-
155
153
  // ADR-013 step 5: opportunistic decisions->memories backfill. Idempotent
156
154
  // and best-effort — first run absorbs the existing decisions table into
157
155
  // the memory store; subsequent runs are a single sentinel SELECT.
@@ -159,12 +157,50 @@ export async function buildBeforeAgentStartResult(
159
157
  const { backfillDecisionsToMemories } = await import("../memory-backfill.js");
160
158
  const written = backfillDecisionsToMemories();
161
159
  if (written > 0) {
162
- ctx.ui.notify(`GSD: backfilled ${written} decision${written === 1 ? "" : "s"} into the memory store (ADR-013).`, "info");
160
+ ctx.ui.notify(`GSD: backfilled ${written} decision${written === 1 ? "" : "s"} into the memory store.`, "info");
163
161
  }
164
162
  } catch (e) {
165
163
  logWarning("bootstrap", `decisions backfill failed: ${(e as Error).message}`);
166
164
  }
167
165
 
166
+ // ADR-013 Stage 2b: KNOWLEDGE.md Patterns + Lessons backfill, then
167
+ // re-render the hybrid projection (manual Rules + projected Patterns +
168
+ // projected Lessons). Both are idempotent and best-effort — failures here
169
+ // can't block agent startup.
170
+ try {
171
+ const { backfillKnowledgeToMemories } = await import("../knowledge-backfill.js");
172
+ const writtenK = backfillKnowledgeToMemories(basePath);
173
+ if (writtenK > 0) {
174
+ ctx.ui.notify(`GSD: backfilled ${writtenK} KNOWLEDGE.md row${writtenK === 1 ? "" : "s"} into the memory store.`, "info");
175
+ }
176
+ } catch (e) {
177
+ logWarning("bootstrap", `KNOWLEDGE.md backfill failed: ${(e as Error).message}`);
178
+ }
179
+ try {
180
+ const { renderKnowledgeProjection } = await import("../knowledge-projection.js");
181
+ renderKnowledgeProjection(basePath);
182
+ } catch (e) {
183
+ logWarning("bootstrap", `KNOWLEDGE.md projection render failed: ${(e as Error).message}`);
184
+ }
185
+
186
+ // ADR-013 step 6 preflight: warn when decisions / KNOWLEDGE.md rows are not
187
+ // yet in the memories table. Read-only; never throws. Runs after the two
188
+ // backfills above so the gap report reflects post-backfill state.
189
+ try {
190
+ const { reportConsolidationGaps } = await import("../memory-consolidation-scanner.js");
191
+ reportConsolidationGaps(basePath);
192
+ } catch (e) {
193
+ logWarning("bootstrap", `memory consolidation scan failed: ${(e as Error).message}`);
194
+ }
195
+
196
+ const { block: knowledgeBlock, globalSizeKb } = loadKnowledgeBlock(gsdHome(), basePath);
197
+ if (globalSizeKb > 4) {
198
+ ctx.ui.notify(
199
+ `GSD: ~/.gsd/agent/KNOWLEDGE.md is ${globalSizeKb.toFixed(1)}KB — consider trimming to keep system prompt lean.`,
200
+ "warning",
201
+ );
202
+ }
203
+
168
204
  let newSkillsBlock = "";
169
205
  if (hasSkillSnapshot()) {
170
206
  const newSkills = detectNewSkills();
@@ -370,7 +406,9 @@ export async function loadMemoryBlock(
370
406
  }
371
407
 
372
408
  export function loadKnowledgeBlock(gsdHomeDir: string, cwd: string): { block: string; globalSizeKb: number } {
373
- // 1. Global knowledge (~/.gsd/agent/KNOWLEDGE.md) — cross-project, user-maintained
409
+ // 1. Global knowledge (~/.gsd/agent/KNOWLEDGE.md) — cross-project,
410
+ // user-maintained. NOT migrated to memories (which are project-scoped),
411
+ // so the full file is injected unchanged.
374
412
  let globalKnowledge = "";
375
413
  let globalSizeKb = 0;
376
414
  const globalKnowledgePath = join(gsdHomeDir, "agent", "KNOWLEDGE.md");
@@ -386,13 +424,18 @@ export function loadKnowledgeBlock(gsdHomeDir: string, cwd: string): { block: st
386
424
  }
387
425
  }
388
426
 
389
- // 2. Project knowledge (.gsd/KNOWLEDGE.md) — project-specific
427
+ // 2. Project knowledge (.gsd/KNOWLEDGE.md) — project-specific.
428
+ // ADR-013 Stage 2b: Patterns and Lessons are projected from the
429
+ // memories table and already reach the LLM via loadMemoryBlock. Inject
430
+ // only the intro prose + `## Rules` section here to avoid duplicating
431
+ // Patterns/Lessons content in the prompt. Rules stay manual per
432
+ // ADR-013 line 39 and have no memory equivalent.
390
433
  let projectKnowledge = "";
391
434
  const knowledgePath = resolveGsdRootFile(cwd, "KNOWLEDGE");
392
435
  if (existsSync(knowledgePath)) {
393
436
  try {
394
- const content = readFileSync(knowledgePath, "utf-8").trim();
395
- if (content) projectKnowledge = content;
437
+ const raw = readFileSync(knowledgePath, "utf-8").trim();
438
+ if (raw) projectKnowledge = extractIntroAndRules(raw).trim();
396
439
  } catch (e) {
397
440
  logWarning("bootstrap", `project knowledge file read failed: ${(e as Error).message}`);
398
441
  }
@@ -411,7 +454,7 @@ export function loadKnowledgeBlock(gsdHomeDir: string, cwd: string): { block: st
411
454
  }
412
455
  const body = limitKnowledgeBlock(parts.join("\n\n"), getKnowledgeCharLimit());
413
456
  return {
414
- block: `\n\n[KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${body}`,
457
+ block: `\n\n[KNOWLEDGE — Rules from KNOWLEDGE.md (Patterns and Lessons reach the LLM via the memory block)]\n\n${body}`,
415
458
  globalSizeKb,
416
459
  };
417
460
  }
@@ -96,7 +96,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
96
96
  " /gsd unpark [id] Reactivate a parked milestone",
97
97
  "",
98
98
  "PROJECT KNOWLEDGE",
99
- " /gsd knowledge <type> <text> Add rule, pattern, or lesson to KNOWLEDGE.md",
99
+ " /gsd knowledge <type> <text> Add a rule to KNOWLEDGE.md or capture a pattern/lesson to memories",
100
100
  " /gsd codebase [generate|update|stats] Manage the CODEBASE.md cache used in prompt context",
101
101
  "",
102
102
  "SHIPPING & BACKLOG",
@@ -368,8 +368,25 @@ export async function handleKnowledge(args: string, ctx: ExtensionCommandContext
368
368
  ? `${state.activeMilestone.id}${state.activeSlice ? `/${state.activeSlice.id}` : ""}`
369
369
  : "global";
370
370
 
371
- await appendKnowledge(basePath, type, entryText, scope);
372
- ctx.ui.notify(`Added ${type} to KNOWLEDGE.md: "${entryText}"`, "success");
371
+ // ADR-013 Stage 2c: Patterns and Lessons land in the memories table; the
372
+ // next session-start projection render emits them back into KNOWLEDGE.md.
373
+ // Rules stay file-canonical per ADR-013 line 39 — Rules are not migrated.
374
+ if (type === "rule") {
375
+ await appendKnowledge(basePath, type, entryText, scope);
376
+ ctx.ui.notify(`Added rule to KNOWLEDGE.md: "${entryText}"`, "success");
377
+ return;
378
+ }
379
+
380
+ const { captureKnowledgeEntry } = await import("./knowledge-capture.js");
381
+ const { id, written } = captureKnowledgeEntry(basePath, type, entryText, scope);
382
+ if (!written) {
383
+ ctx.ui.notify(`Could not persist ${type} — see logs for details.`, "error");
384
+ return;
385
+ }
386
+ ctx.ui.notify(
387
+ `Captured ${type} ${id} to memories; KNOWLEDGE.md will render it on next session start.`,
388
+ "success",
389
+ );
373
390
  }
374
391
 
375
392
  export async function handleRunHook(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
@@ -5,7 +5,7 @@
5
5
  // All functions degrade gracefully: return empty results when DB unavailable, never throw.
6
6
 
7
7
  import { isDbAvailable, _getAdapter } from './gsd-db.js';
8
- import type { Decision, Requirement } from './types.js';
8
+ import type { Decision, DecisionMadeBy, Requirement } from './types.js';
9
9
 
10
10
  // ─── Query Functions ───────────────────────────────────────────────────────
11
11
 
@@ -66,6 +66,125 @@ export function queryDecisions(opts?: DecisionQueryOpts): Decision[] {
66
66
  }
67
67
  }
68
68
 
69
+ /**
70
+ * Internal: shared core for the two memory-sourced decision queries. Reads
71
+ * memory rows tagged with `sourceDecisionId` and reconstructs `Decision[]`
72
+ * from their `structured_fields` JSON.
73
+ *
74
+ * @param includeSuperseded — when false, drops rows whose
75
+ * `structured_fields.superseded_by` is non-null. The supersedes-chain is
76
+ * captured by the backfill (`memory-backfill.ts`) and kept in sync by
77
+ * the backfill's drift auto-heal pass.
78
+ */
79
+ function readDecisionsFromMemories(
80
+ opts: DecisionQueryOpts | undefined,
81
+ includeSuperseded: boolean,
82
+ ): Decision[] {
83
+ if (!isDbAvailable()) return [];
84
+ const adapter = _getAdapter();
85
+ if (!adapter) return [];
86
+
87
+ try {
88
+ const clauses: string[] = [
89
+ "category = 'architecture'",
90
+ "structured_fields LIKE '%\"sourceDecisionId\":\"%'",
91
+ ];
92
+ const params: Record<string, unknown> = {};
93
+
94
+ if (opts?.milestoneId) {
95
+ // when_context is a free-text JSON value; substring match preserves the
96
+ // semantics of `when_context LIKE '%milestoneId%'` on the legacy table.
97
+ clauses.push("json_extract(structured_fields, '$.when_context') LIKE :milestone_pattern");
98
+ params[':milestone_pattern'] = `%${opts.milestoneId}%`;
99
+ }
100
+
101
+ if (opts?.scope) {
102
+ // Stage 1 used `json_extract` in main (post-merge); preserve that
103
+ // style here. Exact equality on the JSON value avoids the prefix
104
+ // collision risk LIKE patterns had (scope=M001 vs scope=M001-S01).
105
+ clauses.push("json_extract(structured_fields, '$.scope') = :scope");
106
+ params[':scope'] = opts.scope;
107
+ }
108
+
109
+ const sql = `SELECT seq, structured_fields FROM memories WHERE ${clauses.join(' AND ')} ORDER BY seq`;
110
+ const rows = adapter.prepare(sql).all(params) as Array<Record<string, unknown>>;
111
+
112
+ const decisions: Decision[] = [];
113
+ for (const row of rows) {
114
+ const seq = row['seq'] as number;
115
+ const sfRaw = row['structured_fields'] as string | null;
116
+ if (!sfRaw) continue;
117
+ let sf: Record<string, unknown>;
118
+ try {
119
+ sf = JSON.parse(sfRaw) as Record<string, unknown>;
120
+ } catch {
121
+ continue;
122
+ }
123
+ const sourceId = sf['sourceDecisionId'];
124
+ if (typeof sourceId !== 'string' || sourceId.length === 0) continue;
125
+
126
+ // Decision-level superseded status lives in structured_fields.superseded_by
127
+ // (written by mirrorDecisionToMemory / memory-backfill.ts). The top-level
128
+ // memories.superseded_by column is intentionally never set for decision mirrors,
129
+ // so active-only filtering must be done here in the JS loop.
130
+ const supersededBy = typeof sf['superseded_by'] === 'string' ? (sf['superseded_by'] as string) : null;
131
+ if (!includeSuperseded && supersededBy) continue;
132
+
133
+ decisions.push({
134
+ seq,
135
+ id: sourceId,
136
+ when_context: typeof sf['when_context'] === 'string' ? (sf['when_context'] as string) : '',
137
+ scope: typeof sf['scope'] === 'string' ? (sf['scope'] as string) : '',
138
+ decision: typeof sf['decision'] === 'string' ? (sf['decision'] as string) : '',
139
+ choice: typeof sf['choice'] === 'string' ? (sf['choice'] as string) : '',
140
+ rationale: typeof sf['rationale'] === 'string' ? (sf['rationale'] as string) : '',
141
+ revisable: typeof sf['revisable'] === 'string' ? (sf['revisable'] as string) : '',
142
+ made_by: ((typeof sf['made_by'] === 'string' ? sf['made_by'] : 'agent') as DecisionMadeBy),
143
+ superseded_by: supersededBy,
144
+ });
145
+ }
146
+
147
+ return decisions;
148
+ } catch {
149
+ return [];
150
+ }
151
+ }
152
+
153
+ /**
154
+ * ADR-013 Phase 6 cutover (Stage 1): read **active** decisions from the
155
+ * `memories` table instead of the legacy `decisions` table. Returns the
156
+ * same `Decision[]` shape as `queryDecisions` so downstream formatters
157
+ * work unchanged.
158
+ *
159
+ * Filter semantics match `queryDecisions` exactly:
160
+ * - active only (skips rows where `structured_fields.superseded_by` is set)
161
+ * - `milestoneId`: substring match on `structured_fields.when_context`
162
+ * - `scope`: exact match on `structured_fields.scope`
163
+ *
164
+ * Used by the prompt-inline path (`inlineDecisionsFromDb` in
165
+ * `auto-prompts.ts`). For the projection regen (which renders superseded
166
+ * rows too), see `getAllDecisionsFromMemories`.
167
+ */
168
+ export function queryDecisionsFromMemories(opts?: DecisionQueryOpts): Decision[] {
169
+ return readDecisionsFromMemories(opts, /* includeSuperseded */ false);
170
+ }
171
+
172
+ /**
173
+ * ADR-013 Phase 6 cutover (Stage 2a): read **all** decisions (active +
174
+ * superseded) from the `memories` table. Used by the DECISIONS.md
175
+ * projection regen in `saveDecisionToDb`, which must render the full
176
+ * supersedes-chain for the canonical register format.
177
+ *
178
+ * Equivalent to `SELECT * FROM decisions ORDER BY seq` over the legacy
179
+ * table — but sourced from memories so the legacy table can be retired
180
+ * in Stage 3. Includes `superseded_by` reconstructed from
181
+ * `structured_fields.superseded_by` (populated by the backfill's drift
182
+ * auto-heal pass).
183
+ */
184
+ export function getAllDecisionsFromMemories(): Decision[] {
185
+ return readDecisionsFromMemories(undefined, /* includeSuperseded */ true);
186
+ }
187
+
69
188
  /**
70
189
  * Query active (non-superseded) requirements with optional filters.
71
190
  * - milestoneId: combined with sliceId for precise filtering (e.g. %M005/S01%)