gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e51d2c88c
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 +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +29 -15
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +66 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +33 -4
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +14 -13
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- 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 +18 -18
- 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 +18 -18
- 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 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +3 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +35 -20
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +18 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +78 -3
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +49 -4
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +14 -13
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +234 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 8F5YpnZNBaooIWGF4GBV3}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 8F5YpnZNBaooIWGF4GBV3}/_ssgManifest.js +0 -0
|
@@ -87,6 +87,34 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
87
87
|
import { deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
88
88
|
import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
|
|
89
89
|
|
|
90
|
+
type AutoStartOptions = Parameters<typeof startAutoDetached>[4];
|
|
91
|
+
type AutoStartLauncher = typeof startAutoDetached;
|
|
92
|
+
|
|
93
|
+
function scheduleAutoStartAfterIdle(
|
|
94
|
+
ctx: ExtensionCommandContext,
|
|
95
|
+
pi: ExtensionAPI,
|
|
96
|
+
basePath: string,
|
|
97
|
+
verboseMode: boolean,
|
|
98
|
+
options?: AutoStartOptions,
|
|
99
|
+
launch: AutoStartLauncher = startAutoDetached,
|
|
100
|
+
): void {
|
|
101
|
+
const waitForIdle =
|
|
102
|
+
typeof (ctx as { waitForIdle?: unknown }).waitForIdle === "function"
|
|
103
|
+
? ctx.waitForIdle.bind(ctx)
|
|
104
|
+
: async () => {};
|
|
105
|
+
void waitForIdle()
|
|
106
|
+
.then(() => {
|
|
107
|
+
setTimeout(() => launch(ctx, pi, basePath, verboseMode, options), 0);
|
|
108
|
+
})
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
111
|
+
ctx.ui.notify(`Auto-start failed while waiting for the prior turn to settle: ${message}`, "error");
|
|
112
|
+
logWarning("guided", `auto-start idle wait failed: ${message}`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const _scheduleAutoStartAfterIdleForTest = scheduleAutoStartAfterIdle;
|
|
117
|
+
|
|
90
118
|
// ─── Scope-based validator wrappers ──────────────────────────────────────────
|
|
91
119
|
// These thin wrappers accept a MilestoneScope so callers that already hold a
|
|
92
120
|
// pinned scope never have to re-derive (basePath, milestoneId) separately.
|
|
@@ -446,7 +474,7 @@ async function dispatchNextDeepProjectSetupStage(entry: PendingDeepProjectSetupE
|
|
|
446
474
|
|
|
447
475
|
if (!hasPendingDeepStage(prefs, entry.basePath)) {
|
|
448
476
|
pendingDeepProjectSetupMap.delete(entry.basePath);
|
|
449
|
-
|
|
477
|
+
scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
|
|
450
478
|
return true;
|
|
451
479
|
}
|
|
452
480
|
|
|
@@ -479,7 +507,7 @@ async function dispatchNextDeepProjectSetupStage(entry: PendingDeepProjectSetupE
|
|
|
479
507
|
entry.ctx.ui.notify(result.reason, result.level);
|
|
480
508
|
} else if (hasPendingDeepStage(prefs, entry.basePath)) {
|
|
481
509
|
pendingDeepProjectSetupMap.delete(entry.basePath);
|
|
482
|
-
|
|
510
|
+
scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
|
|
483
511
|
return true;
|
|
484
512
|
}
|
|
485
513
|
return false;
|
|
@@ -487,7 +515,7 @@ async function dispatchNextDeepProjectSetupStage(entry: PendingDeepProjectSetupE
|
|
|
487
515
|
|
|
488
516
|
if (!USER_DRIVEN_DEEP_SETUP_UNITS.has(result.unitType)) {
|
|
489
517
|
pendingDeepProjectSetupMap.delete(entry.basePath);
|
|
490
|
-
|
|
518
|
+
scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
|
|
491
519
|
return true;
|
|
492
520
|
}
|
|
493
521
|
|
|
@@ -693,7 +721,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
693
721
|
|
|
694
722
|
pendingAutoStartMap.delete(basePath);
|
|
695
723
|
ctx.ui.notify(`Milestone ${milestoneId} ready.`, "success");
|
|
696
|
-
|
|
724
|
+
scheduleAutoStartAfterIdle(ctx, pi, basePath, false, { step });
|
|
697
725
|
return true;
|
|
698
726
|
}
|
|
699
727
|
|
|
@@ -2058,6 +2086,23 @@ export async function showSmartEntry(
|
|
|
2058
2086
|
}
|
|
2059
2087
|
}
|
|
2060
2088
|
|
|
2089
|
+
if (interrupted.classification !== "recoverable") {
|
|
2090
|
+
try {
|
|
2091
|
+
const { autoImportMarkdownHierarchyIfDbMismatch } = await import("./migration-auto-check.js");
|
|
2092
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(basePath);
|
|
2093
|
+
if (result.action === "imported") {
|
|
2094
|
+
ctx.ui.notify(
|
|
2095
|
+
`Recovered migrated planning state into gsd.db (${result.reason}): ${result.afterDb.milestones} milestone(s), ${result.afterDb.slices} slice(s), ${result.afterDb.tasks} task(s).`,
|
|
2096
|
+
"info",
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
} catch (err) {
|
|
2100
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2101
|
+
ctx.ui.notify(`GSD could not auto-import existing planning state into gsd.db: ${message}`, "warning");
|
|
2102
|
+
logWarning("guided", `planning state auto-import failed: ${message}`, { file: "guided-flow.ts" });
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2061
2106
|
// Always derive from the project root — the assessment may have derived
|
|
2062
2107
|
// state from a worktree path that was cleaned up in the stale branch above.
|
|
2063
2108
|
const state = await deriveState(basePath);
|
|
@@ -44,6 +44,8 @@ export interface Memory {
|
|
|
44
44
|
* decisions table (Step 5) with the original scope/decision/choice/etc.
|
|
45
45
|
*/
|
|
46
46
|
structured_fields: Record<string, unknown> | null;
|
|
47
|
+
/** ISO timestamp of the most recent memory_query hit. NULL until first hit. */
|
|
48
|
+
last_hit_at: string | null;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export type MemoryActionCreate = {
|
|
@@ -100,6 +102,27 @@ const CATEGORY_PRIORITY: Record<string, number> = {
|
|
|
100
102
|
preference: 5,
|
|
101
103
|
};
|
|
102
104
|
|
|
105
|
+
// ─── Scoring Helpers ─────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Time-decay factor for memory relevance scoring.
|
|
109
|
+
* Returns 1.0 for never-hit or recently-hit memories, decaying linearly to
|
|
110
|
+
* 0.7 for memories not accessed in 90+ days. Floor at 0.7 keeps old-but-valid
|
|
111
|
+
* knowledge from being fully suppressed.
|
|
112
|
+
*
|
|
113
|
+
* Defensive parsing: invalid timestamp strings (NaN from Date.parse) are
|
|
114
|
+
* treated as "no decay" rather than propagating NaN into score arithmetic.
|
|
115
|
+
* Future timestamps (clock skew, manual DB edits) clamp to daysAgo=0 so the
|
|
116
|
+
* factor stays in the documented [0.7, 1.0] contract.
|
|
117
|
+
*/
|
|
118
|
+
export function memoryDecayFactor(lastHitAt: string | null): number {
|
|
119
|
+
if (!lastHitAt) return 1.0;
|
|
120
|
+
const ts = Date.parse(lastHitAt);
|
|
121
|
+
if (!Number.isFinite(ts)) return 1.0;
|
|
122
|
+
const daysAgo = Math.max(0, (Date.now() - ts) / 86_400_000);
|
|
123
|
+
return Math.max(0.7, 1.0 - 0.3 * Math.min(1.0, daysAgo / 90));
|
|
124
|
+
}
|
|
125
|
+
|
|
103
126
|
// ─── Row Mapping ────────────────────────────────────────────────────────────
|
|
104
127
|
|
|
105
128
|
function rowToMemory(row: Record<string, unknown>): Memory {
|
|
@@ -118,6 +141,7 @@ function rowToMemory(row: Record<string, unknown>): Memory {
|
|
|
118
141
|
scope: (row['scope'] as string) ?? 'project',
|
|
119
142
|
tags: parseTags(row['tags']),
|
|
120
143
|
structured_fields: parseStructuredFields(row['structured_fields']),
|
|
144
|
+
last_hit_at: (row['last_hit_at'] as string | null) ?? null,
|
|
121
145
|
};
|
|
122
146
|
}
|
|
123
147
|
|
|
@@ -233,15 +257,39 @@ export function queryMemoriesRanked(opts: QueryMemoriesOptions): RankedMemory[]
|
|
|
233
257
|
: [];
|
|
234
258
|
|
|
235
259
|
if (keywordHits.length === 0 && semanticHits.length === 0 && !trimmedQuery) {
|
|
236
|
-
// No query at all —
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
260
|
+
// No query at all — return top-k by decay-aware ranked score.
|
|
261
|
+
//
|
|
262
|
+
// Build the candidate pool from a direct SQL query that honors the
|
|
263
|
+
// request's activeClause (i.e. include_superseded). Using
|
|
264
|
+
// getActiveMemoriesRanked here would silently drop superseded rows even
|
|
265
|
+
// when the caller explicitly opted in, and would slice by raw score
|
|
266
|
+
// before decay/filters had a chance to reorder.
|
|
267
|
+
const candidatePool = Math.min(Math.max(k * 5, 50), 500);
|
|
268
|
+
const rows = adapter
|
|
269
|
+
.prepare(
|
|
270
|
+
`SELECT * FROM memories ${activeClause}
|
|
271
|
+
ORDER BY (confidence * (1.0 + hit_count * 0.1)) DESC
|
|
272
|
+
LIMIT :limit`,
|
|
273
|
+
)
|
|
274
|
+
.all({ ':limit': candidatePool });
|
|
275
|
+
|
|
276
|
+
const ranked: RankedMemory[] = [];
|
|
277
|
+
for (const row of rows) {
|
|
278
|
+
const memory = rowToMemory(row);
|
|
279
|
+
if (!passesFilters(memory, opts)) continue;
|
|
280
|
+
const decay = memoryDecayFactor(memory.last_hit_at);
|
|
281
|
+
const score = memory.confidence * (1 + memory.hit_count * 0.1) * decay;
|
|
282
|
+
ranked.push({
|
|
283
|
+
memory,
|
|
284
|
+
score,
|
|
285
|
+
keywordRank: null,
|
|
286
|
+
semanticRank: null,
|
|
287
|
+
confidenceBoost: score,
|
|
288
|
+
reason: 'ranked' as const,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
ranked.sort((a, b) => b.score - a.score);
|
|
292
|
+
return ranked.slice(0, k);
|
|
245
293
|
}
|
|
246
294
|
|
|
247
295
|
// 3) Reciprocal rank fusion — each hit contributes 1/(rrfK + rank).
|
|
@@ -275,7 +323,7 @@ export function queryMemoriesRanked(opts: QueryMemoriesOptions): RankedMemory[]
|
|
|
275
323
|
const ranked: RankedMemory[] = [];
|
|
276
324
|
for (const entry of fused.values()) {
|
|
277
325
|
if (!passesFilters(entry.memory, opts)) continue;
|
|
278
|
-
const boost = entry.memory.confidence * (1 + entry.memory.hit_count * 0.1);
|
|
326
|
+
const boost = entry.memory.confidence * (1 + entry.memory.hit_count * 0.1) * memoryDecayFactor(entry.memory.last_hit_at);
|
|
279
327
|
const reason: RankedMemory['reason'] =
|
|
280
328
|
entry.kwRank != null && entry.semRank != null
|
|
281
329
|
? 'both'
|
|
@@ -313,6 +361,8 @@ function passesFilters(memory: Memory, filters: QueryMemoriesFilters): boolean {
|
|
|
313
361
|
return true;
|
|
314
362
|
}
|
|
315
363
|
|
|
364
|
+
let ftsWarningEmitted = false;
|
|
365
|
+
|
|
316
366
|
function keywordSearch(
|
|
317
367
|
adapter: NonNullable<ReturnType<typeof _getAdapter>>,
|
|
318
368
|
rawQuery: string,
|
|
@@ -340,14 +390,29 @@ function keywordSearch(
|
|
|
340
390
|
}
|
|
341
391
|
}
|
|
342
392
|
|
|
343
|
-
// LIKE fallback — scans
|
|
393
|
+
// LIKE fallback — scans a capped candidate pool.
|
|
394
|
+
if (!ftsWarningEmitted) {
|
|
395
|
+
ftsWarningEmitted = true;
|
|
396
|
+
logWarning('memory-store', 'FTS5 unavailable — using LIKE fallback scan (consider enabling FTS5)');
|
|
397
|
+
}
|
|
398
|
+
|
|
344
399
|
const terms = rawQuery
|
|
345
400
|
.toLowerCase()
|
|
346
401
|
.split(/[^a-z0-9_]+/)
|
|
347
402
|
.filter((t) => t.length >= 2);
|
|
348
403
|
if (terms.length === 0) return [];
|
|
349
404
|
|
|
350
|
-
const
|
|
405
|
+
const preScanCap = Math.min(limit * 20, 2000);
|
|
406
|
+
// ORDER BY confidence-weighted hit_count DESC so the cap keeps the most
|
|
407
|
+
// valuable candidates instead of the oldest-by-rowid (which would silently
|
|
408
|
+
// exclude recently-stored memories on tables larger than preScanCap).
|
|
409
|
+
const rows = adapter
|
|
410
|
+
.prepare(
|
|
411
|
+
`SELECT * FROM memories ${activeClause}
|
|
412
|
+
ORDER BY (confidence * (1.0 + hit_count * 0.1)) DESC
|
|
413
|
+
LIMIT :preScanCap`,
|
|
414
|
+
)
|
|
415
|
+
.all({ ':preScanCap': preScanCap });
|
|
351
416
|
const scored: Array<{ memory: Memory; score: number }> = [];
|
|
352
417
|
for (const row of rows) {
|
|
353
418
|
const memory = rowToMemory(row);
|
|
@@ -25,6 +25,50 @@ import {
|
|
|
25
25
|
|
|
26
26
|
import type { MigrationPreview } from "./writer.js";
|
|
27
27
|
import { homedir } from "node:os";
|
|
28
|
+
import { ensureDbOpen } from "../bootstrap/dynamic-tools.js";
|
|
29
|
+
import { clearEngineHierarchy, transaction } from "../gsd-db.js";
|
|
30
|
+
import { migrateFromMarkdown } from "../md-importer.js";
|
|
31
|
+
import { invalidateStateCache } from "../state.js";
|
|
32
|
+
|
|
33
|
+
export type MigrationImportCounts = ReturnType<typeof migrateFromMarkdown>;
|
|
34
|
+
|
|
35
|
+
function assertMigrationImportMatchesPreview(imported: MigrationImportCounts, preview: MigrationPreview): void {
|
|
36
|
+
const mismatches: string[] = [];
|
|
37
|
+
if (imported.hierarchy.milestones !== preview.milestoneCount) {
|
|
38
|
+
mismatches.push(`milestones ${imported.hierarchy.milestones}/${preview.milestoneCount}`);
|
|
39
|
+
}
|
|
40
|
+
if (imported.hierarchy.slices !== preview.totalSlices) {
|
|
41
|
+
mismatches.push(`slices ${imported.hierarchy.slices}/${preview.totalSlices}`);
|
|
42
|
+
}
|
|
43
|
+
if (imported.hierarchy.tasks !== preview.totalTasks) {
|
|
44
|
+
mismatches.push(`tasks ${imported.hierarchy.tasks}/${preview.totalTasks}`);
|
|
45
|
+
}
|
|
46
|
+
if (imported.requirements !== preview.requirements.total) {
|
|
47
|
+
mismatches.push(`requirements ${imported.requirements}/${preview.requirements.total}`);
|
|
48
|
+
}
|
|
49
|
+
if (mismatches.length > 0) {
|
|
50
|
+
throw new Error(`migration DB import verification failed: ${mismatches.join(", ")}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function importWrittenMigrationToDb(
|
|
55
|
+
basePath: string,
|
|
56
|
+
preview?: MigrationPreview,
|
|
57
|
+
): Promise<MigrationImportCounts> {
|
|
58
|
+
const opened = await ensureDbOpen(basePath);
|
|
59
|
+
if (!opened) {
|
|
60
|
+
throw new Error(`failed to open or create the GSD database at ${basePath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const counts = transaction(() => {
|
|
64
|
+
clearEngineHierarchy();
|
|
65
|
+
const imported = migrateFromMarkdown(basePath);
|
|
66
|
+
if (preview) assertMigrationImportMatchesPreview(imported, preview);
|
|
67
|
+
return imported;
|
|
68
|
+
});
|
|
69
|
+
invalidateStateCache();
|
|
70
|
+
return counts;
|
|
71
|
+
}
|
|
28
72
|
|
|
29
73
|
/** Format preview stats for embedding in the review prompt. */
|
|
30
74
|
function formatPreviewStats(preview: MigrationPreview): string {
|
|
@@ -182,9 +226,10 @@ export async function handleMigrate(
|
|
|
182
226
|
|
|
183
227
|
const result = await writeGSDDirectory(project, process.cwd());
|
|
184
228
|
const gsdPath = gsdRoot(process.cwd());
|
|
229
|
+
const imported = await importWrittenMigrationToDb(process.cwd(), preview);
|
|
185
230
|
|
|
186
231
|
ctx.ui.notify(
|
|
187
|
-
`✓ Migration complete — ${result.paths.length} file(s) written to .gsd
|
|
232
|
+
`✓ Migration complete — ${result.paths.length} file(s) written to .gsd/ and ${imported.hierarchy.milestones}M/${imported.hierarchy.slices}S/${imported.hierarchy.tasks}T imported to the database`,
|
|
188
233
|
"info",
|
|
189
234
|
);
|
|
190
235
|
|
|
@@ -193,6 +238,7 @@ export async function handleMigrate(
|
|
|
193
238
|
title: "Migration written",
|
|
194
239
|
summary: [
|
|
195
240
|
`${result.paths.length} files written to .gsd/`,
|
|
241
|
+
`${imported.hierarchy.milestones} milestone(s), ${imported.hierarchy.slices} slice(s), and ${imported.hierarchy.tasks} task(s) imported to gsd.db`,
|
|
196
242
|
"",
|
|
197
243
|
"The agent can now review the migrated output against GSD-2 standards —",
|
|
198
244
|
"checking structure, content quality, deriveState() round-trip, and",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
4
|
+
import {
|
|
5
|
+
clearEngineHierarchy,
|
|
6
|
+
getAllMilestones,
|
|
7
|
+
getMilestoneSlices,
|
|
8
|
+
getSliceTasks,
|
|
9
|
+
isDbAvailable,
|
|
10
|
+
transaction,
|
|
11
|
+
} from "./gsd-db.js";
|
|
12
|
+
import { migrateHierarchyToDb } from "./md-importer.js";
|
|
13
|
+
import { parsePlan, parseRoadmap } from "./parsers-legacy.js";
|
|
14
|
+
import {
|
|
15
|
+
milestonesDir,
|
|
16
|
+
resolveMilestoneFile,
|
|
17
|
+
resolveSliceFile,
|
|
18
|
+
} from "./paths.js";
|
|
19
|
+
import { invalidateStateCache } from "./state.js";
|
|
20
|
+
|
|
21
|
+
export interface HierarchyCounts {
|
|
22
|
+
milestones: number;
|
|
23
|
+
slices: number;
|
|
24
|
+
tasks: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MigrationAutoCheckResult {
|
|
28
|
+
action: "none" | "imported";
|
|
29
|
+
reason: "no-markdown" | "in-sync" | "db-empty" | "count-mismatch";
|
|
30
|
+
markdown: HierarchyCounts;
|
|
31
|
+
beforeDb: HierarchyCounts;
|
|
32
|
+
afterDb: HierarchyCounts;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function zeroCounts(): HierarchyCounts {
|
|
36
|
+
return { milestones: 0, slices: 0, tasks: 0 };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sameCounts(a: HierarchyCounts, b: HierarchyCounts): boolean {
|
|
40
|
+
return a.milestones === b.milestones && a.slices === b.slices && a.tasks === b.tasks;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function countMarkdownHierarchy(basePath: string): HierarchyCounts {
|
|
44
|
+
const root = milestonesDir(basePath);
|
|
45
|
+
if (!existsSync(root)) return zeroCounts();
|
|
46
|
+
|
|
47
|
+
const counts = zeroCounts();
|
|
48
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
49
|
+
if (!entry.isDirectory() || !/^M\d+/.test(entry.name)) continue;
|
|
50
|
+
counts.milestones++;
|
|
51
|
+
|
|
52
|
+
const roadmapPath = resolveMilestoneFile(basePath, entry.name, "ROADMAP");
|
|
53
|
+
if (!roadmapPath || !existsSync(roadmapPath)) continue;
|
|
54
|
+
|
|
55
|
+
const roadmap = parseRoadmap(readFileSync(roadmapPath, "utf-8"));
|
|
56
|
+
counts.slices += roadmap.slices.length;
|
|
57
|
+
|
|
58
|
+
for (const slice of roadmap.slices) {
|
|
59
|
+
const planPath = resolveSliceFile(basePath, entry.name, slice.id, "PLAN");
|
|
60
|
+
if (!planPath || !existsSync(planPath)) continue;
|
|
61
|
+
const plan = parsePlan(readFileSync(planPath, "utf-8"));
|
|
62
|
+
counts.tasks += plan.tasks.length;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return counts;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function countDbHierarchy(): HierarchyCounts {
|
|
70
|
+
if (!isDbAvailable()) return zeroCounts();
|
|
71
|
+
const counts = zeroCounts();
|
|
72
|
+
const milestones = getAllMilestones();
|
|
73
|
+
counts.milestones = milestones.length;
|
|
74
|
+
|
|
75
|
+
for (const milestone of milestones) {
|
|
76
|
+
const slices = getMilestoneSlices(milestone.id);
|
|
77
|
+
counts.slices += slices.length;
|
|
78
|
+
for (const slice of slices) {
|
|
79
|
+
counts.tasks += getSliceTasks(milestone.id, slice.id).length;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return counts;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function autoImportMarkdownHierarchyIfDbMismatch(
|
|
87
|
+
basePath: string,
|
|
88
|
+
): Promise<MigrationAutoCheckResult> {
|
|
89
|
+
const markdown = countMarkdownHierarchy(basePath);
|
|
90
|
+
if (sameCounts(markdown, zeroCounts())) {
|
|
91
|
+
return {
|
|
92
|
+
action: "none",
|
|
93
|
+
reason: "no-markdown",
|
|
94
|
+
markdown,
|
|
95
|
+
beforeDb: zeroCounts(),
|
|
96
|
+
afterDb: zeroCounts(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const opened = await ensureDbOpen(basePath);
|
|
101
|
+
if (!opened || !isDbAvailable()) {
|
|
102
|
+
throw new Error(`failed to open or create the GSD database at ${basePath}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const beforeDb = countDbHierarchy();
|
|
106
|
+
if (sameCounts(markdown, beforeDb)) {
|
|
107
|
+
return { action: "none", reason: "in-sync", markdown, beforeDb, afterDb: beforeDb };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const reason = sameCounts(beforeDb, zeroCounts()) ? "db-empty" : "count-mismatch";
|
|
111
|
+
const imported = transaction(() => {
|
|
112
|
+
clearEngineHierarchy();
|
|
113
|
+
return migrateHierarchyToDb(basePath);
|
|
114
|
+
});
|
|
115
|
+
invalidateStateCache();
|
|
116
|
+
|
|
117
|
+
const afterDb = {
|
|
118
|
+
milestones: imported.milestones,
|
|
119
|
+
slices: imported.slices,
|
|
120
|
+
tasks: imported.tasks,
|
|
121
|
+
};
|
|
122
|
+
if (!sameCounts(markdown, afterDb)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`migration auto-import verification failed: markdown ${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T, db ${afterDb.milestones}M/${afterDb.slices}S/${afterDb.tasks}T`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { action: "imported", reason, markdown, beforeDb, afterDb };
|
|
129
|
+
}
|
|
@@ -32,7 +32,7 @@ export interface ContextManagementConfig {
|
|
|
32
32
|
/**
|
|
33
33
|
* Opt-in tool-output sandboxing for sub-sessions. When enabled, the gsd_exec
|
|
34
34
|
* MCP tool runs scripts in an isolated subprocess and returns only a short
|
|
35
|
-
* digest to the calling agent's context window;
|
|
35
|
+
* digest to the calling agent's context window; capped stdout/stderr persist
|
|
36
36
|
* in the project memory store and can be retrieved by id later.
|
|
37
37
|
*
|
|
38
38
|
* Inspired by mksglu/context-mode (Elastic License 2.0). This is an
|
|
@@ -123,17 +123,42 @@ function warmCache(): void {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
let warmCacheScheduled = false;
|
|
126
|
+
let warmCacheRan = false;
|
|
127
|
+
let warmCacheTimer: ReturnType<typeof setTimeout> | undefined;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Synchronously snapshot the prompt/template tree into the cache.
|
|
131
|
+
*
|
|
132
|
+
* Safe to call immediately after `initResources()` because that function uses
|
|
133
|
+
* synchronous fs APIs — there is no race window that requires deferring the
|
|
134
|
+
* cache warm via setTimeout. Idempotent: subsequent calls are no-ops.
|
|
135
|
+
*
|
|
136
|
+
* Cancels the fallback `scheduleWarmCache` timer if it is still pending.
|
|
137
|
+
*/
|
|
138
|
+
export function primeCache(): void {
|
|
139
|
+
if (warmCacheRan) return;
|
|
140
|
+
if (warmCacheTimer) {
|
|
141
|
+
clearTimeout(warmCacheTimer);
|
|
142
|
+
warmCacheTimer = undefined;
|
|
143
|
+
}
|
|
144
|
+
warmCacheScheduled = true;
|
|
145
|
+
warmCacheRan = true;
|
|
146
|
+
warmCache();
|
|
147
|
+
}
|
|
126
148
|
|
|
127
149
|
function scheduleWarmCache(): void {
|
|
128
150
|
if (warmCacheScheduled) return;
|
|
129
151
|
warmCacheScheduled = true;
|
|
130
152
|
|
|
131
153
|
const run = () => {
|
|
154
|
+
warmCacheTimer = undefined;
|
|
155
|
+
if (warmCacheRan) return;
|
|
156
|
+
warmCacheRan = true;
|
|
132
157
|
warmCache();
|
|
133
158
|
};
|
|
134
159
|
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
warmCacheTimer = setTimeout(run, 1000);
|
|
161
|
+
warmCacheTimer.unref?.();
|
|
137
162
|
}
|
|
138
163
|
|
|
139
164
|
// Snapshot the full prompt/template tree after import so extension startup only
|
|
@@ -29,14 +29,15 @@ Subagents report only; they do not write user source. Fold any findings into Dec
|
|
|
29
29
|
|
|
30
30
|
## Steps
|
|
31
31
|
|
|
32
|
-
1.
|
|
33
|
-
2.
|
|
34
|
-
3.
|
|
35
|
-
4. Verify
|
|
36
|
-
5. Verify **
|
|
37
|
-
6. If
|
|
38
|
-
7.
|
|
39
|
-
8.
|
|
32
|
+
1. **Duplicate completion guard:** Call `gsd_milestone_status` for `{{milestoneId}}` before any durable writes. If the returned milestone **status is `complete`**, this is a stale or duplicate closeout turn: do NOT mutate requirements, do NOT refresh the project document, do NOT write LEARNINGS, and do NOT persist milestone completion again. Say: "Milestone {{milestoneId}} is already complete." and stop.
|
|
33
|
+
2. Use the **Milestone Summary** output template from the inlined context above
|
|
34
|
+
3. {{skillActivation}}
|
|
35
|
+
4. **Verify code changes exist.** Compare milestone work against the integration branch (`main`, `master`, or recorded branch), using merge-base as older revision and `HEAD` as newer. If the diff lists non-`.gsd/` files, pass. If `HEAD` equals the integration branch/merge-base, treat it as a self-diff retry: inspect milestone-scoped commit evidence (`GSD-Unit: {{milestoneId}}` or production `GSD-Task: Sxx/Tyy` trailers touching `.gsd/milestones/{{milestoneId}}/`) and verify those commits touched non-`.gsd/` files. Record **verification failure** only when neither source shows implementation files.
|
|
36
|
+
5. Verify every **success criterion** from `{{roadmapPath}}`. If passing validation is present, summarize the validation evidence instead of re-auditing it; otherwise verify with evidence from summaries, tests, or observable behavior. Record unmet criteria as **verification failure**.
|
|
37
|
+
6. Verify **definition of done**: all slices `[x]`, summaries exist, and integrations work. If passing validation is present, trust its integration/verification verdict unless inconsistent with current artifacts. Record unmet items as **verification failure**.
|
|
38
|
+
7. If the roadmap includes a **Horizontal Checklist**, verify each item and note unchecked items in the summary.
|
|
39
|
+
8. Fill the **Decision Re-evaluation** table: compare each key `.gsd/DECISIONS.md` decision from this milestone with what shipped, and flag decisions to revisit.
|
|
40
|
+
9. Validate **requirement status transitions**. For each changed requirement, confirm evidence supports the new status. Requirements may move between Active, Validated, Deferred, Blocked, or Out of Scope only with proof.
|
|
40
41
|
|
|
41
42
|
**DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')`; the engine owns the WAL connection. Use `gsd_milestone_status`, inlined context, or `gsd_*` tools; never direct SQL.
|
|
42
43
|
|
|
@@ -53,13 +54,13 @@ Subagents report only; they do not write user source. Fold any findings into Dec
|
|
|
53
54
|
|
|
54
55
|
**Success path** (all verifications passed):
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
10. For each requirement whose status changed in step 9, call `gsd_requirement_update` with the requirement ID and updated `status` and `validation` fields — the tool regenerates `.gsd/REQUIREMENTS.md` automatically. Do this BEFORE completing the milestone so requirement updates are persisted.
|
|
58
|
+
11. Update `.gsd/PROJECT.md`: use the `write` tool with `path: ".gsd/PROJECT.md"` and `content` containing the full updated document reflecting milestone completion and current project state. Do NOT use the `edit` tool for this — PROJECT.md is a full-document refresh.
|
|
59
|
+
12. Extract structured learnings from this milestone and persist them to the GSD memory store. Follow the procedure block immediately below — it writes `{{milestoneId}}-LEARNINGS.md` as the audit trail and persists Patterns, Lessons, and Decisions via `capture_thought` (categories: pattern, gotcha/convention, architecture). The memory store is the single source of truth for cross-session durable knowledge (ADR-013).
|
|
59
60
|
|
|
60
61
|
{{extractLearningsSteps}}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
13. **Persist completion through `gsd_complete_milestone`.** Call it with the parameters below. This must be the final persistent write in the unit. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete.
|
|
63
64
|
|
|
64
65
|
**Required parameters:**
|
|
65
66
|
- `milestoneId` (string) — Milestone ID (e.g. M001)
|
|
@@ -78,7 +79,7 @@ Subagents report only; they do not write user source. Fold any findings into Dec
|
|
|
78
79
|
- `followUps` (string) — Follow-up items for future milestones
|
|
79
80
|
- `deviations` (string) — Deviations from the original plan
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
14. Do not commit manually — the system auto-commits your changes after this unit completes.
|
|
82
83
|
- Say: "Milestone {{milestoneId}} complete."
|
|
83
84
|
|
|
84
85
|
**Important:** Do NOT skip code-change, success-criteria, or definition-of-done verification (steps 3-5). The summary must reflect verified outcomes. Verification failures block completion; there is no override. If a verification tool fails, errors, or returns unexpected output, treat it as failure.
|
|
@@ -16,7 +16,7 @@ Dispatch ALL slices simultaneously using the `subagent` tool in **parallel mode*
|
|
|
16
16
|
|
|
17
17
|
1. Call `subagent` with `tasks: [...]` containing one entry per slice below
|
|
18
18
|
2. Wait for ALL subagents to complete
|
|
19
|
-
3. Verify each slice's RESEARCH file was written (check
|
|
19
|
+
3. Verify each slice's RESEARCH file was written (check `.gsd/milestones/{{mid}}/slices/<slice-id>/`)
|
|
20
20
|
4. If a subagent failed to write its RESEARCH file, retry it **once** individually
|
|
21
21
|
5. If it fails a second time, write a partial RESEARCH file for that slice with a `## BLOCKER` section explaining the failure — do NOT retry again
|
|
22
22
|
6. Report which slices completed research and which (if any) needed a blocker note
|
|
@@ -17,11 +17,7 @@ You are executing a GSD quick task — a lightweight, focused unit of work outsi
|
|
|
17
17
|
5. Verify your work:
|
|
18
18
|
- Run tests if applicable.
|
|
19
19
|
- Verify both happy path and failure modes for non-trivial changes.
|
|
20
|
-
6.
|
|
21
|
-
- Use conventional commit messages (feat:, fix:, refactor:, etc.)
|
|
22
|
-
- Stage only relevant files — never commit secrets or runtime files.
|
|
23
|
-
- Commit logical units separately if the task involves distinct changes.
|
|
24
|
-
- Quick tasks run outside the auto-mode lifecycle — there is no system auto-commit, so commit directly here.
|
|
20
|
+
6. {{commitInstruction}}
|
|
25
21
|
7. Write a brief summary to `{{summaryPath}}`:
|
|
26
22
|
- Quick tasks operate outside the milestone/slice/task DB structure, so `gsd_summary_save` (which requires a `milestone_id`) cannot be used here. Write the file directly.
|
|
27
23
|
|
|
@@ -27,13 +27,13 @@ Roadmap, slice summaries, assessments, requirements, decisions, and project cont
|
|
|
27
27
|
Call `subagent` with `tasks: [...]` containing ALL THREE reviewers simultaneously:
|
|
28
28
|
|
|
29
29
|
**Reviewer A - Requirements Coverage**
|
|
30
|
-
Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}. Read `.gsd/
|
|
30
|
+
Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}. Read `.gsd/REQUIREMENTS.md` or use the inlined requirements context. For each requirement, check slice SUMMARY files under `.gsd/milestones/{{milestoneId}}/slices/` and mark COVERED, PARTIAL, or MISSING. Output table: Requirement | Status | Evidence. End with one-line verdict: PASS if all covered, NEEDS-ATTENTION if partials exist, FAIL if any missing."
|
|
31
31
|
|
|
32
32
|
**Reviewer B - Cross-Slice Integration**
|
|
33
33
|
Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Read `{{roadmapPath}}` and find the boundary map (produces/consumes contracts). For each boundary, confirm producer SUMMARY produced the artifact and consumer SUMMARY consumed it. Output table: Boundary | Producer Summary | Consumer Summary | Status. End with one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
|
|
34
34
|
|
|
35
35
|
**Reviewer C - Assessment & Acceptance Criteria**
|
|
36
|
-
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/CONTEXT.md` for criteria. Check slice
|
|
36
|
+
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/milestones/{{milestoneId}}/{{milestoneId}}-CONTEXT.md` for criteria. Check slice SUMMARY/UAT files under `.gsd/milestones/{{milestoneId}}/slices/`. Verify each criterion maps to passing evidence. Then review inlined milestone verification classes. For each non-empty planned class, output table: Class | Planned Check | Evidence | Verdict. Use the exact class names `Contract`, `Integration`, `Operational`, and `UAT` whenever those classes are present. If no verification classes were planned, say that explicitly. Output sections `Acceptance Criteria` with checklist `[ ] Criterion | Evidence`, and `Verification Classes` with the table. End with one-line verdict: PASS if all criteria and classes are covered, NEEDS-ATTENTION if gaps exist."
|
|
37
37
|
|
|
38
38
|
### Step 2 - Synthesize Findings
|
|
39
39
|
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
15
15
|
import { loadPrompt } from "./prompt-loader.js";
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
17
|
import { GitServiceImpl, runGit } from "./git-service.js";
|
|
@@ -77,6 +77,40 @@ function ensureQuickDir(basePath: string, taskNum: number, slug: string): string
|
|
|
77
77
|
return taskDir;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function isPathInside(parent: string, child: string): boolean {
|
|
81
|
+
const rel = relative(parent, child);
|
|
82
|
+
return rel === "" || (!!rel && !rel.startsWith("..") && !isAbsolute(rel));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isExternalGsdRoot(basePath: string, root: string): boolean {
|
|
86
|
+
try {
|
|
87
|
+
return !isPathInside(realpathSync(basePath), realpathSync(root));
|
|
88
|
+
} catch {
|
|
89
|
+
return !isPathInside(basePath, root);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function buildQuickCommitInstruction(basePath: string, root: string): string {
|
|
94
|
+
const externalState = isExternalGsdRoot(basePath, root);
|
|
95
|
+
if (externalState) {
|
|
96
|
+
return [
|
|
97
|
+
"Commit repo changes atomically, but do not stage or commit `.gsd/quick/...`:",
|
|
98
|
+
" - `.gsd/` resolves outside this git repository, so Git cannot stage quick-task summary files from the project repo.",
|
|
99
|
+
" - Write the quick summary file directly at the requested path; that file is persisted by GSD external state.",
|
|
100
|
+
" - Stage and commit only implementation/test/docs files that live inside the repository.",
|
|
101
|
+
" - If the task only writes quick-task research/summary files and no repository files changed, do not run `git commit`; report that there was nothing in the project repo to commit.",
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
"Commit your changes atomically:",
|
|
107
|
+
" - Use conventional commit messages (feat:, fix:, refactor:, etc.)",
|
|
108
|
+
" - Stage only relevant files — never commit secrets or runtime files.",
|
|
109
|
+
" - Commit logical units separately if the task involves distinct changes.",
|
|
110
|
+
" - Quick tasks run outside the auto-mode lifecycle — there is no system auto-commit, so commit directly here.",
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
80
114
|
function quickReturnStatePath(basePath: string): string {
|
|
81
115
|
return join(gsdRoot(basePath), "runtime", "quick-return.json");
|
|
82
116
|
}
|
|
@@ -246,6 +280,7 @@ export async function handleQuick(
|
|
|
246
280
|
taskDir: taskDirRel,
|
|
247
281
|
branch: actualBranch,
|
|
248
282
|
summaryPath,
|
|
283
|
+
commitInstruction: buildQuickCommitInstruction(basePath, root),
|
|
249
284
|
date,
|
|
250
285
|
taskNum: String(taskNum),
|
|
251
286
|
slug,
|