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.
- package/README.md +46 -28
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
- package/dist/resources/extensions/gsd/auto/phases.js +9 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +11 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +5 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
- package/dist/resources/extensions/gsd/context-store.js +112 -0
- package/dist/resources/extensions/gsd/db-writer.js +150 -84
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
- package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
- package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
- package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
- package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +6 -1
- package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
- package/dist/resources/extensions/gsd/templates/knowledge.md +2 -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 +11 -11
- 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 +11 -11
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- 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 +3 -2
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/README.md +2 -0
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -2
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -1
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/src/index.ts +7 -2
- package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
- package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +14 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
- package/src/resources/extensions/gsd/context-store.ts +120 -1
- package/src/resources/extensions/gsd/db-writer.ts +167 -84
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
- package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
- package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
- package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
- package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
- package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
- package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
- package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
- package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → KDRTXR-22LPCsa80X9dey}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → KDRTXR-22LPCsa80X9dey}/_ssgManifest.js +0 -0
package/pkg/package.json
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
395
|
-
if (
|
|
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
|
|
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
|
|
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
|
-
|
|
372
|
-
|
|
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%)
|