gsd-pi 2.43.0-next.7-dev.8fab558 → 2.43.0-next.7-dev.4684f0e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +13 -1
- package/dist/help-text.js +24 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +40 -23
- package/dist/resources/extensions/gsd/auto-recovery.js +7 -6
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +65 -0
- package/dist/resources/extensions/gsd/commands-maintenance.js +20 -4
- package/dist/resources/extensions/gsd/db-writer.js +25 -3
- package/dist/resources/extensions/gsd/gsd-db.js +5 -2
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +5 -7
- package/dist/resources/extensions/gsd/state.js +28 -18
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +128 -0
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +4 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +4 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- 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/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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 +19 -19
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -3
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +11 -34
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +4 -4
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +37 -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/fallback-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js +2 -3
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +12 -2
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +38 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +192 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +255 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +40 -3
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.d.ts +25 -0
- package/packages/pi-coding-agent/dist/core/package-commands.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.js +253 -0
- package/packages/pi-coding-agent/dist/core/package-commands.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +225 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +3 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +11 -199
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -37
- package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -2
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -3
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +4 -4
- package/packages/pi-coding-agent/src/core/extensions/index.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +23 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +44 -0
- package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +15 -2
- package/packages/pi-coding-agent/src/core/fallback-resolver.ts +2 -3
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +274 -0
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +288 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +39 -3
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +240 -0
- package/packages/pi-coding-agent/src/core/package-commands.ts +310 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +4 -0
- package/packages/pi-coding-agent/src/index.ts +7 -0
- package/packages/pi-coding-agent/src/main.ts +11 -232
- package/src/resources/extensions/gsd/auto-post-unit.ts +41 -20
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -6
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +69 -0
- package/src/resources/extensions/gsd/commands-maintenance.ts +20 -5
- package/src/resources/extensions/gsd/db-writer.ts +28 -3
- package/src/resources/extensions/gsd/gsd-db.ts +4 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +5 -7
- package/src/resources/extensions/gsd/state.ts +29 -18
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +176 -0
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +7 -2
- package/src/resources/extensions/gsd/tools/plan-slice.ts +7 -2
- /package/dist/web/standalone/.next/static/{vIVEdIRyywg0fXAwrLj3b → ZYERjwjiaf3Mhj69oy-Ms}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vIVEdIRyywg0fXAwrLj3b → ZYERjwjiaf3Mhj69oy-Ms}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthStorage, DefaultResourceLoader, ModelRegistry, SettingsManager, SessionManager, createAgentSession, InteractiveMode, runPrintMode, runRpcMode, } from '@gsd/pi-coding-agent';
|
|
1
|
+
import { AuthStorage, DefaultResourceLoader, ModelRegistry, runPackageCommand, SettingsManager, SessionManager, createAgentSession, InteractiveMode, runPrintMode, runRpcMode, } from '@gsd/pi-coding-agent';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { agentDir, sessionsDir, authFilePath } from './app-paths.js';
|
|
@@ -121,6 +121,18 @@ if (subcommand && process.argv.includes('--help')) {
|
|
|
121
121
|
process.exit(0);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
const packageCommand = await runPackageCommand({
|
|
125
|
+
appName: 'gsd',
|
|
126
|
+
args: process.argv.slice(2),
|
|
127
|
+
cwd: process.cwd(),
|
|
128
|
+
agentDir,
|
|
129
|
+
stdout: process.stdout,
|
|
130
|
+
stderr: process.stderr,
|
|
131
|
+
allowedCommands: new Set(['install', 'remove', 'list']),
|
|
132
|
+
});
|
|
133
|
+
if (packageCommand.handled) {
|
|
134
|
+
process.exit(packageCommand.exitCode);
|
|
135
|
+
}
|
|
124
136
|
// `gsd config` — replay the setup wizard and exit
|
|
125
137
|
if (cliFlags.messages[0] === 'config') {
|
|
126
138
|
const authStorage = AuthStorage.create(authFilePath);
|
package/dist/help-text.js
CHANGED
|
@@ -29,6 +29,27 @@ const SUBCOMMAND_HELP = {
|
|
|
29
29
|
'',
|
|
30
30
|
'Compare with --continue (-c) which always resumes the most recent session.',
|
|
31
31
|
].join('\n'),
|
|
32
|
+
install: [
|
|
33
|
+
'Usage: gsd install <source> [-l, --local]',
|
|
34
|
+
'',
|
|
35
|
+
'Install a package/extension source and run declared lifecycle hooks.',
|
|
36
|
+
'',
|
|
37
|
+
'Examples:',
|
|
38
|
+
' gsd install npm:@foo/bar',
|
|
39
|
+
' gsd install git:github.com/user/repo',
|
|
40
|
+
' gsd install https://github.com/user/repo',
|
|
41
|
+
' gsd install ./local/path',
|
|
42
|
+
].join('\n'),
|
|
43
|
+
remove: [
|
|
44
|
+
'Usage: gsd remove <source> [-l, --local]',
|
|
45
|
+
'',
|
|
46
|
+
'Remove an installed package source and its settings entry.',
|
|
47
|
+
].join('\n'),
|
|
48
|
+
list: [
|
|
49
|
+
'Usage: gsd list',
|
|
50
|
+
'',
|
|
51
|
+
'List installed package sources from user and project settings.',
|
|
52
|
+
].join('\n'),
|
|
32
53
|
worktree: [
|
|
33
54
|
'Usage: gsd worktree <command> [args]',
|
|
34
55
|
'',
|
|
@@ -122,6 +143,9 @@ export function printHelp(version) {
|
|
|
122
143
|
process.stdout.write(' --help, -h Print this help and exit\n');
|
|
123
144
|
process.stdout.write('\nSubcommands:\n');
|
|
124
145
|
process.stdout.write(' config Re-run the setup wizard\n');
|
|
146
|
+
process.stdout.write(' install <source> Install a package/extension source\n');
|
|
147
|
+
process.stdout.write(' remove <source> Remove an installed package source\n');
|
|
148
|
+
process.stdout.write(' list List installed package sources\n');
|
|
125
149
|
process.stdout.write(' update Update GSD to the latest version\n');
|
|
126
150
|
process.stdout.write(' sessions List and resume a past session\n');
|
|
127
151
|
process.stdout.write(' worktree <cmd> Manage worktrees (list, merge, clean, remove)\n');
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { deriveState } from "./state.js";
|
|
14
14
|
import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
|
|
15
15
|
import { loadPrompt } from "./prompt-loader.js";
|
|
16
|
-
import { resolveSliceFile,
|
|
16
|
+
import { resolveSliceFile, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, gsdRoot, } from "./paths.js";
|
|
17
17
|
import { invalidateAllCaches } from "./cache.js";
|
|
18
18
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
19
19
|
import { autoCommitCurrentBranch, } from "./worktree.js";
|
|
@@ -22,13 +22,13 @@ import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.j
|
|
|
22
22
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
23
23
|
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
24
24
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
25
|
-
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus } from "./gsd-db.js";
|
|
25
|
+
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
|
|
26
26
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
27
27
|
import { consumeSignal } from "./session-status-io.js";
|
|
28
28
|
import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
|
|
29
29
|
import { hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
30
30
|
import { debugLog } from "./debug-logger.js";
|
|
31
|
-
import { existsSync, unlinkSync
|
|
31
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
32
32
|
import { join } from "node:path";
|
|
33
33
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
34
34
|
import { _resetHasChangesCache } from "./native-git-bridge.js";
|
|
@@ -101,6 +101,39 @@ export function detectRogueFileWrites(unitType, unitId, basePath) {
|
|
|
101
101
|
if (!hasPlanningState) {
|
|
102
102
|
rogues.push({ path: planPath, unitType, unitId });
|
|
103
103
|
}
|
|
104
|
+
// Also check for rogue REPLAN.md
|
|
105
|
+
const replanPath = resolveSliceFile(basePath, mid, sid, "REPLAN");
|
|
106
|
+
if (replanPath && existsSync(replanPath) && !hasPlanningState) {
|
|
107
|
+
rogues.push({ path: replanPath, unitType, unitId });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (unitType === "reassess-roadmap") {
|
|
111
|
+
const [mid, sid] = parts;
|
|
112
|
+
if (!mid || !sid)
|
|
113
|
+
return [];
|
|
114
|
+
const assessPath = resolveSliceFile(basePath, mid, sid, "ASSESSMENT");
|
|
115
|
+
if (!assessPath || !existsSync(assessPath))
|
|
116
|
+
return [];
|
|
117
|
+
// Assessment file exists on disk — check if DB knows about it via the artifacts table
|
|
118
|
+
const adapter = _getAdapter();
|
|
119
|
+
if (adapter) {
|
|
120
|
+
const row = adapter.prepare(`SELECT 1 FROM artifacts WHERE path LIKE :pattern AND artifact_type = 'ASSESSMENT' LIMIT 1`).get({ ":pattern": `%${sid}-ASSESSMENT.md` });
|
|
121
|
+
if (!row) {
|
|
122
|
+
rogues.push({ path: assessPath, unitType, unitId });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (unitType === "plan-task") {
|
|
127
|
+
const [mid, sid, tid] = parts;
|
|
128
|
+
if (!mid || !sid || !tid)
|
|
129
|
+
return [];
|
|
130
|
+
const taskPlanPath = resolveTaskFile(basePath, mid, sid, tid, "PLAN");
|
|
131
|
+
if (!taskPlanPath || !existsSync(taskPlanPath))
|
|
132
|
+
return [];
|
|
133
|
+
const dbRow = getTask(mid, sid, tid);
|
|
134
|
+
if (!dbRow) {
|
|
135
|
+
rogues.push({ path: taskPlanPath, unitType, unitId });
|
|
136
|
+
}
|
|
104
137
|
}
|
|
105
138
|
return rogues;
|
|
106
139
|
}
|
|
@@ -468,26 +501,10 @@ export async function postUnitPostVerification(pctx) {
|
|
|
468
501
|
updateTaskStatus(mid, sid, tid, "pending");
|
|
469
502
|
await renderPlanCheckboxes(s.basePath, mid, sid);
|
|
470
503
|
}
|
|
471
|
-
catch {
|
|
472
|
-
// DB
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (slicePath) {
|
|
476
|
-
const { readdirSync } = await import("node:fs");
|
|
477
|
-
const planCandidates = readdirSync(slicePath)
|
|
478
|
-
.filter((f) => f.includes("PLAN") && (f.startsWith(sid) || f.startsWith(`${sid}-`)));
|
|
479
|
-
if (planCandidates.length > 0) {
|
|
480
|
-
const planFile = join(slicePath, planCandidates[0]);
|
|
481
|
-
let content = readFileSync(planFile, "utf-8");
|
|
482
|
-
const regex = new RegExp(`^(\\s*-\\s*)\\[x\\](\\s*\\**${tid}\\**[:\\s])`, "mi");
|
|
483
|
-
if (regex.test(content)) {
|
|
484
|
-
content = content.replace(regex, "$1[ ]$2");
|
|
485
|
-
writeFileSync(planFile, content, "utf-8");
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
catch { /* non-fatal: file-based fallback failure */ }
|
|
504
|
+
catch (dbErr) {
|
|
505
|
+
// DB unavailable — fail explicitly rather than silently reverting to markdown mutation.
|
|
506
|
+
// Use 'gsd recover' to rebuild DB state from disk if needed.
|
|
507
|
+
process.stderr.write(`gsd: retry state-reset failed (DB unavailable): ${dbErr.message}. Run 'gsd recover' to reconcile.\n`);
|
|
491
508
|
}
|
|
492
509
|
}
|
|
493
510
|
// 2. Delete SUMMARY.md for the task
|
|
@@ -284,10 +284,10 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
284
284
|
return false;
|
|
285
285
|
}
|
|
286
286
|
else if (!isDbAvailable()) {
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// real signal.
|
|
287
|
+
// LEGACY: Pre-migration fallback for projects without DB.
|
|
288
|
+
// Fall back to plan heading check (format detection, not reconciliation).
|
|
289
|
+
// Heading-style entries (### T01 --) count as verified because the
|
|
290
|
+
// summary file existence (checked above) is the real signal.
|
|
291
291
|
const planAbs = resolveSliceFile(base, mid, sid, "PLAN");
|
|
292
292
|
if (planAbs && existsSync(planAbs)) {
|
|
293
293
|
const planContent = readFileSync(planAbs, "utf-8");
|
|
@@ -320,7 +320,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
320
320
|
taskIds = tasks.map(t => t.id);
|
|
321
321
|
}
|
|
322
322
|
if (!taskIds) {
|
|
323
|
-
// DB unavailable or no tasks in DB — parse plan file for task IDs
|
|
323
|
+
// LEGACY: DB unavailable or no tasks in DB — parse plan file for task IDs
|
|
324
324
|
const planContent = readFileSync(absPath, "utf-8");
|
|
325
325
|
const plan = parseLegacyPlan(planContent);
|
|
326
326
|
if (plan.tasks.length > 0)
|
|
@@ -362,7 +362,8 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
362
362
|
return false;
|
|
363
363
|
}
|
|
364
364
|
else if (!isDbAvailable()) {
|
|
365
|
-
//
|
|
365
|
+
// LEGACY: Pre-migration fallback for projects without DB.
|
|
366
|
+
// Fall back to roadmap checkbox check via parsers-legacy
|
|
366
367
|
const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
367
368
|
if (roadmapFile && existsSync(roadmapFile)) {
|
|
368
369
|
try {
|
|
@@ -667,6 +667,71 @@ export function registerDbTools(pi) {
|
|
|
667
667
|
};
|
|
668
668
|
pi.registerTool(sliceCompleteTool);
|
|
669
669
|
registerAlias(pi, sliceCompleteTool, "gsd_complete_slice", "gsd_slice_complete");
|
|
670
|
+
// ─── gsd_complete_milestone ────────────────────────────────────────────
|
|
671
|
+
const milestoneCompleteExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
|
|
672
|
+
const dbAvailable = await ensureDbOpen();
|
|
673
|
+
if (!dbAvailable) {
|
|
674
|
+
return {
|
|
675
|
+
content: [{ type: "text", text: "Error: GSD database is not available. Cannot complete milestone." }],
|
|
676
|
+
details: { operation: "complete_milestone", error: "db_unavailable" },
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
680
|
+
const { handleCompleteMilestone } = await import("../tools/complete-milestone.js");
|
|
681
|
+
const result = await handleCompleteMilestone(params, process.cwd());
|
|
682
|
+
if ("error" in result) {
|
|
683
|
+
return {
|
|
684
|
+
content: [{ type: "text", text: `Error completing milestone: ${result.error}` }],
|
|
685
|
+
details: { operation: "complete_milestone", error: result.error },
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
content: [{ type: "text", text: `Completed milestone ${result.milestoneId}. Summary written to ${result.summaryPath}` }],
|
|
690
|
+
details: {
|
|
691
|
+
operation: "complete_milestone",
|
|
692
|
+
milestoneId: result.milestoneId,
|
|
693
|
+
summaryPath: result.summaryPath,
|
|
694
|
+
},
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
699
|
+
process.stderr.write(`gsd-db: complete_milestone tool failed: ${msg}\n`);
|
|
700
|
+
return {
|
|
701
|
+
content: [{ type: "text", text: `Error completing milestone: ${msg}` }],
|
|
702
|
+
details: { operation: "complete_milestone", error: msg },
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const milestoneCompleteTool = {
|
|
707
|
+
name: "gsd_complete_milestone",
|
|
708
|
+
label: "Complete Milestone",
|
|
709
|
+
description: "Record a completed milestone to the GSD database, render MILESTONE-SUMMARY.md to disk — all in one atomic operation. " +
|
|
710
|
+
"Validates all slices are complete before proceeding.",
|
|
711
|
+
promptSnippet: "Complete a GSD milestone (DB write + summary render)",
|
|
712
|
+
promptGuidelines: [
|
|
713
|
+
"Use gsd_complete_milestone when all slices in a milestone are finished and the milestone needs to be recorded.",
|
|
714
|
+
"All slices in the milestone must have status 'complete' — the handler validates this before proceeding.",
|
|
715
|
+
"On success, returns summaryPath where the MILESTONE-SUMMARY.md was written.",
|
|
716
|
+
],
|
|
717
|
+
parameters: Type.Object({
|
|
718
|
+
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
719
|
+
title: Type.String({ description: "Milestone title" }),
|
|
720
|
+
oneLiner: Type.String({ description: "One-sentence summary of what the milestone achieved" }),
|
|
721
|
+
narrative: Type.String({ description: "Detailed narrative of what happened during the milestone" }),
|
|
722
|
+
successCriteriaResults: Type.String({ description: "Markdown detailing how each success criterion was met or not met" }),
|
|
723
|
+
definitionOfDoneResults: Type.String({ description: "Markdown detailing how each definition-of-done item was met" }),
|
|
724
|
+
requirementOutcomes: Type.String({ description: "Markdown detailing requirement status transitions with evidence" }),
|
|
725
|
+
keyDecisions: Type.Array(Type.String(), { description: "Key architectural/pattern decisions made during the milestone" }),
|
|
726
|
+
keyFiles: Type.Array(Type.String(), { description: "Key files created or modified during the milestone" }),
|
|
727
|
+
lessonsLearned: Type.Array(Type.String(), { description: "Lessons learned during the milestone" }),
|
|
728
|
+
followUps: Type.Optional(Type.String({ description: "Follow-up items for future milestones" })),
|
|
729
|
+
deviations: Type.Optional(Type.String({ description: "Deviations from the original plan" })),
|
|
730
|
+
}),
|
|
731
|
+
execute: milestoneCompleteExecute,
|
|
732
|
+
};
|
|
733
|
+
pi.registerTool(milestoneCompleteTool);
|
|
734
|
+
registerAlias(pi, milestoneCompleteTool, "gsd_milestone_complete", "gsd_complete_milestone");
|
|
670
735
|
// ─── gsd_replan_slice (gsd_slice_replan alias) ─────────────────────────
|
|
671
736
|
const replanSliceExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
|
|
672
737
|
const dbAvailable = await ensureDbOpen();
|
|
@@ -43,12 +43,29 @@ export async function handleCleanupBranches(ctx, basePath) {
|
|
|
43
43
|
const { loadFile } = await import("./files.js");
|
|
44
44
|
const { parseRoadmap } = await import("./parsers-legacy.js");
|
|
45
45
|
const { isMilestoneComplete } = await import("./state.js");
|
|
46
|
+
const { isDbAvailable, getMilestone } = await import("./gsd-db.js");
|
|
46
47
|
const attachedBranches = new Set(listWorktrees(basePath).map((wt) => wt.branch));
|
|
47
48
|
const milestoneBranches = nativeBranchList(basePath, "milestone/*");
|
|
48
49
|
for (const branch of milestoneBranches) {
|
|
49
50
|
if (attachedBranches.has(branch))
|
|
50
51
|
continue;
|
|
51
52
|
const milestoneId = branch.replace(/^milestone\//, "");
|
|
53
|
+
// DB-first: check milestone status directly
|
|
54
|
+
if (isDbAvailable()) {
|
|
55
|
+
const dbRow = getMilestone(milestoneId);
|
|
56
|
+
if (dbRow) {
|
|
57
|
+
if (dbRow.status !== "complete" && dbRow.status !== "done")
|
|
58
|
+
continue;
|
|
59
|
+
// Milestone is complete per DB — proceed to delete branch
|
|
60
|
+
try {
|
|
61
|
+
nativeBranchDelete(basePath, branch, true);
|
|
62
|
+
deletedStaleMilestones++;
|
|
63
|
+
}
|
|
64
|
+
catch { /* non-fatal */ }
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Filesystem fallback
|
|
52
69
|
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
53
70
|
if (!roadmapPath)
|
|
54
71
|
continue;
|
|
@@ -427,15 +444,14 @@ export async function handleRecover(ctx, basePath) {
|
|
|
427
444
|
return;
|
|
428
445
|
}
|
|
429
446
|
try {
|
|
430
|
-
// 1. Delete
|
|
447
|
+
// 1. Delete + re-populate inside a single transaction for atomicity
|
|
431
448
|
const db = _getAdapter();
|
|
432
|
-
dbTransaction(() => {
|
|
449
|
+
const counts = dbTransaction(() => {
|
|
433
450
|
db.exec("DELETE FROM tasks");
|
|
434
451
|
db.exec("DELETE FROM slices");
|
|
435
452
|
db.exec("DELETE FROM milestones");
|
|
453
|
+
return migrateHierarchyToDb(basePath);
|
|
436
454
|
});
|
|
437
|
-
// 2. Re-populate from rendered markdown on disk
|
|
438
|
-
const counts = migrateHierarchyToDb(basePath);
|
|
439
455
|
// 3. Invalidate state cache so deriveState() picks up fresh DB data
|
|
440
456
|
invalidateStateCache();
|
|
441
457
|
// 4. Derive state to verify sanity
|
|
@@ -265,7 +265,14 @@ export async function saveDecisionToDb(fields, basePath) {
|
|
|
265
265
|
// Table format or no existing file — full regeneration (original behavior)
|
|
266
266
|
md = generateDecisionsMd(allDecisions);
|
|
267
267
|
}
|
|
268
|
-
|
|
268
|
+
try {
|
|
269
|
+
await saveFile(filePath, md);
|
|
270
|
+
}
|
|
271
|
+
catch (diskErr) {
|
|
272
|
+
process.stderr.write(`gsd-db: saveDecisionToDb — disk write failed, rolling back DB row: ${diskErr.message}\n`);
|
|
273
|
+
adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
|
|
274
|
+
throw diskErr;
|
|
275
|
+
}
|
|
269
276
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
270
277
|
// Do NOT clear the artifacts table — we just wrote to it intentionally.
|
|
271
278
|
invalidateStateCache();
|
|
@@ -322,7 +329,14 @@ export async function updateRequirementInDb(id, updates, basePath) {
|
|
|
322
329
|
const nonSuperseded = allRequirements.filter(r => r.superseded_by == null);
|
|
323
330
|
const md = generateRequirementsMd(nonSuperseded);
|
|
324
331
|
const filePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
|
|
325
|
-
|
|
332
|
+
try {
|
|
333
|
+
await saveFile(filePath, md);
|
|
334
|
+
}
|
|
335
|
+
catch (diskErr) {
|
|
336
|
+
process.stderr.write(`gsd-db: updateRequirementInDb — disk write failed, reverting DB row: ${diskErr.message}\n`);
|
|
337
|
+
db.upsertRequirement(existing);
|
|
338
|
+
throw diskErr;
|
|
339
|
+
}
|
|
326
340
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
327
341
|
// Do NOT clear the artifacts table — we just wrote to it intentionally.
|
|
328
342
|
invalidateStateCache();
|
|
@@ -356,7 +370,15 @@ export async function saveArtifactToDb(opts, basePath) {
|
|
|
356
370
|
if (!fullPath.startsWith(gsdDir)) {
|
|
357
371
|
throw new GSDError(GSD_IO_ERROR, `saveArtifactToDb: path escapes .gsd/ directory: ${opts.path}`);
|
|
358
372
|
}
|
|
359
|
-
|
|
373
|
+
try {
|
|
374
|
+
await saveFile(fullPath, opts.content);
|
|
375
|
+
}
|
|
376
|
+
catch (diskErr) {
|
|
377
|
+
process.stderr.write(`gsd-db: saveArtifactToDb — disk write failed, rolling back DB row: ${diskErr.message}\n`);
|
|
378
|
+
const rollbackAdapter = db._getAdapter();
|
|
379
|
+
rollbackAdapter?.prepare('DELETE FROM artifacts WHERE path = :path').run({ ':path': opts.path });
|
|
380
|
+
throw diskErr;
|
|
381
|
+
}
|
|
360
382
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
361
383
|
// Do NOT clear the artifacts table — we just wrote to it intentionally.
|
|
362
384
|
invalidateStateCache();
|
|
@@ -109,6 +109,9 @@ const SCHEMA_VERSION = 10;
|
|
|
109
109
|
function initSchema(db, fileBacked) {
|
|
110
110
|
if (fileBacked)
|
|
111
111
|
db.exec("PRAGMA journal_mode=WAL");
|
|
112
|
+
if (fileBacked)
|
|
113
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
114
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
112
115
|
db.exec("BEGIN");
|
|
113
116
|
try {
|
|
114
117
|
db.exec(`
|
|
@@ -219,7 +222,7 @@ function initSchema(db, fileBacked) {
|
|
|
219
222
|
proof_level TEXT NOT NULL DEFAULT '',
|
|
220
223
|
integration_closure TEXT NOT NULL DEFAULT '',
|
|
221
224
|
observability_impact TEXT NOT NULL DEFAULT '',
|
|
222
|
-
sequence INTEGER DEFAULT 0,
|
|
225
|
+
sequence INTEGER DEFAULT 0, -- DEAD CODE: no tool exposes sequence — always 0
|
|
223
226
|
replan_triggered_at TEXT DEFAULT NULL,
|
|
224
227
|
PRIMARY KEY (milestone_id, id),
|
|
225
228
|
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
@@ -250,7 +253,7 @@ function initSchema(db, fileBacked) {
|
|
|
250
253
|
inputs TEXT NOT NULL DEFAULT '[]',
|
|
251
254
|
expected_output TEXT NOT NULL DEFAULT '[]',
|
|
252
255
|
observability_impact TEXT NOT NULL DEFAULT '',
|
|
253
|
-
sequence INTEGER DEFAULT 0,
|
|
256
|
+
sequence INTEGER DEFAULT 0, -- DEAD CODE: no tool exposes sequence — always 0
|
|
254
257
|
PRIMARY KEY (milestone_id, slice_id, id),
|
|
255
258
|
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
256
259
|
)
|
|
@@ -24,7 +24,7 @@ Then:
|
|
|
24
24
|
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first.
|
|
25
25
|
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
|
|
26
26
|
5. If `.gsd/REQUIREMENTS.md` exists, update it based on what this slice actually proved. Move requirements between Active, Validated, Deferred, Blocked, or Out of Scope only when the evidence from execution supports that change.
|
|
27
|
-
6. Call the `gsd_slice_complete` tool (alias: `gsd_complete_slice`) to record the slice as complete. The tool validates all tasks are complete,
|
|
27
|
+
6. Call the `gsd_slice_complete` tool (alias: `gsd_complete_slice`) to record the slice as complete. The tool validates all tasks are complete, updates the slice status in the DB, renders the summary to `{{sliceSummaryPath}}`, UAT to `{{sliceUatPath}}`, and re-renders `{{roadmapPath}}` — all atomically. Read the summary and UAT templates at `~/.gsd/agent/extensions/gsd/templates/` to understand the expected structure, then pass the following parameters:
|
|
28
28
|
|
|
29
29
|
**Identity:** `sliceId`, `milestoneId`, `sliceTitle`
|
|
30
30
|
|
|
@@ -45,6 +45,6 @@ Then:
|
|
|
45
45
|
9. Do not run git commands — the system commits your changes and handles any merge after this unit succeeds.
|
|
46
46
|
10. Update `.gsd/PROJECT.md` if it exists — refresh current state if needed.
|
|
47
47
|
|
|
48
|
-
**You MUST call `gsd_slice_complete` before finishing.** The tool handles writing `{{sliceSummaryPath}}`, `{{sliceUatPath}}`, and
|
|
48
|
+
**You MUST call `gsd_slice_complete` before finishing.** The tool handles writing `{{sliceSummaryPath}}`, `{{sliceUatPath}}`, and updating `{{roadmapPath}}` atomically. You must still review decisions and knowledge manually (steps 7-8).
|
|
49
49
|
|
|
50
50
|
When done, say: "Slice {{sliceId}} complete."
|
|
@@ -202,7 +202,7 @@ Once the user is satisfied, in a single pass:
|
|
|
202
202
|
When writing context.md, preserve the user's exact terminology, emphasis, and specific framing from the discussion. Do not paraphrase user nuance into generic summaries. If the user said "craft feel," write "craft feel" — not "high-quality user experience." If they emphasized a specific constraint or negative requirement, carry that emphasis through verbatim. The context file is downstream agents' only window into this conversation — flattening specifics into generics loses the signal that shaped every decision.
|
|
203
203
|
|
|
204
204
|
4. Write `{{contextPath}}` — use the **Context** output template below. Preserve key risks, unknowns, existing codebase constraints, integration points, and relevant requirements surfaced during discussion.
|
|
205
|
-
5.
|
|
205
|
+
5. Call `gsd_plan_milestone` to create the roadmap. Decompose into demoable vertical slices with risk, depends, demo sentences, proof strategy, verification classes, milestone definition of done, requirement coverage, and a boundary map. If the milestone crosses multiple runtime boundaries, include an explicit final integration slice that proves the assembled system works end-to-end in a real environment. Use the **Roadmap** output template below to structure the tool call parameters.
|
|
206
206
|
6. Seed `.gsd/DECISIONS.md` — use the **Decisions** output template below. Append rows for any architectural or pattern decisions made during discussion.
|
|
207
207
|
7. {{commitInstruction}}
|
|
208
208
|
|
|
@@ -222,7 +222,7 @@ Once the user confirms the milestone split:
|
|
|
222
222
|
#### Phase 2: Primary milestone
|
|
223
223
|
|
|
224
224
|
5. Write a full `CONTEXT.md` for the primary milestone (the one discussed in depth).
|
|
225
|
-
6.
|
|
225
|
+
6. Call `gsd_plan_milestone` for **only the primary milestone** — detail-planning later milestones now is waste because the codebase will change. Include requirement coverage and a milestone definition of done.
|
|
226
226
|
|
|
227
227
|
#### MANDATORY: depends_on Frontmatter in CONTEXT.md
|
|
228
228
|
|
|
@@ -63,7 +63,7 @@ Then:
|
|
|
63
63
|
11. **Blocker discovery:** If execution reveals that the remaining slice plan is fundamentally invalid — not just a bug or minor deviation, but a plan-invalidating finding like a wrong API, missing capability, or architectural mismatch — set `blocker_discovered: true` in the task summary frontmatter and describe the blocker clearly in the summary narrative. Do NOT set `blocker_discovered: true` for ordinary debugging, minor deviations, or issues that can be fixed within the current task or the remaining plan. This flag triggers an automatic replan of the slice.
|
|
64
64
|
12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (read the template at `~/.gsd/agent/extensions/gsd/templates/decisions.md` if the file doesn't exist yet). Not every task produces decisions — only append when a meaningful choice was made.
|
|
65
65
|
13. If you discover a non-obvious rule, recurring gotcha, or useful pattern during execution, append it to `.gsd/KNOWLEDGE.md`. Only add entries that would save future agents from repeating your investigation. Don't add obvious things.
|
|
66
|
-
14. Call the `gsd_task_complete` tool (alias: `gsd_complete_task`) to record the task completion. This single tool call atomically
|
|
66
|
+
14. Call the `gsd_task_complete` tool (alias: `gsd_complete_task`) to record the task completion. This single tool call atomically updates the task status in the DB, renders the summary file to `{{taskSummaryPath}}`, and re-renders the plan file at `{{planPath}}`. Read the summary template at `~/.gsd/agent/extensions/gsd/templates/task-summary.md` to understand the expected structure — but pass the content as tool parameters, not as a file write. The tool parameters are:
|
|
67
67
|
- `taskId`: "{{taskId}}"
|
|
68
68
|
- `sliceId`: "{{sliceId}}"
|
|
69
69
|
- `milestoneId`: "{{milestoneId}}"
|
|
@@ -80,6 +80,6 @@ Then:
|
|
|
80
80
|
|
|
81
81
|
All work stays in your working directory: `{{workingDirectory}}`.
|
|
82
82
|
|
|
83
|
-
**You MUST call `gsd_task_complete` before finishing.** The tool handles writing `{{taskSummaryPath}}` and
|
|
83
|
+
**You MUST call `gsd_task_complete` before finishing.** The tool handles writing `{{taskSummaryPath}}` and updating the plan file at `{{planPath}}` — do not write the summary file or modify the plan file manually.
|
|
84
84
|
|
|
85
85
|
When done, say: "Task {{taskId}} complete."
|
|
@@ -80,15 +80,13 @@ Apply these when decomposing and ordering slices:
|
|
|
80
80
|
|
|
81
81
|
## Single-Slice Fast Path
|
|
82
82
|
|
|
83
|
-
If the roadmap has only one slice, also
|
|
83
|
+
If the roadmap has only one slice, also plan the slice and its tasks inline during this unit — don't leave them for a separate planning session.
|
|
84
84
|
|
|
85
|
-
1.
|
|
86
|
-
2.
|
|
87
|
-
3.
|
|
88
|
-
4. Write individual task plans at `{{milestonePath}}/slices/S01/tasks/T01-PLAN.md`, etc.
|
|
89
|
-
5. For simple slices, keep the plan lean — omit Proof Level, Integration Closure, and Observability sections if they would all be "none". Executable verification commands are sufficient.
|
|
85
|
+
1. After `gsd_plan_milestone` returns, immediately call `gsd_plan_slice` for S01 with the full task breakdown
|
|
86
|
+
2. Use the **Slice Plan** and **Task Plan** output templates from the inlined context above to structure the tool call parameters
|
|
87
|
+
3. For simple slices, keep the plan lean — omit Proof Level, Integration Closure, and Observability sections if they would all be "none". Executable verification commands are sufficient.
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
Do **not** write plan files manually — use the DB-backed tools so state stays consistent.
|
|
92
90
|
|
|
93
91
|
## Secret Forecasting
|
|
94
92
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// GSD Extension — State Derivation
|
|
2
|
-
//
|
|
2
|
+
// DB-primary state derivation with filesystem fallback for unmigrated projects.
|
|
3
3
|
// Pure TypeScript, zero Pi dependencies.
|
|
4
4
|
import { parseRoadmap, parsePlan, } from './parsers-legacy.js';
|
|
5
5
|
import { parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn, } from './files.js';
|
|
@@ -68,40 +68,48 @@ export function invalidateStateCache() {
|
|
|
68
68
|
* Returns the ID of the first incomplete milestone, or null if all are complete.
|
|
69
69
|
*/
|
|
70
70
|
export async function getActiveMilestoneId(basePath) {
|
|
71
|
-
const milestoneIds = findMilestoneIds(basePath);
|
|
72
71
|
// Parallel worker isolation
|
|
73
72
|
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
74
73
|
if (milestoneLock) {
|
|
74
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
75
75
|
if (!milestoneIds.includes(milestoneLock))
|
|
76
76
|
return null;
|
|
77
|
-
// Locked milestone that is parked should not be active
|
|
78
77
|
const lockedParked = resolveMilestoneFile(basePath, milestoneLock, "PARKED");
|
|
79
78
|
if (lockedParked)
|
|
80
79
|
return null;
|
|
81
80
|
return milestoneLock;
|
|
82
81
|
}
|
|
82
|
+
// DB-first: query milestones table for the first non-complete, non-parked milestone
|
|
83
|
+
if (isDbAvailable()) {
|
|
84
|
+
const allMilestones = getAllMilestones();
|
|
85
|
+
if (allMilestones.length > 0) {
|
|
86
|
+
const sorted = [...allMilestones].sort((a, b) => a.id.localeCompare(b.id));
|
|
87
|
+
for (const m of sorted) {
|
|
88
|
+
if (m.status === "complete" || m.status === "done" || m.status === "parked")
|
|
89
|
+
continue;
|
|
90
|
+
return m.id;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Filesystem fallback for unmigrated projects or empty DB
|
|
96
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
83
97
|
for (const mid of milestoneIds) {
|
|
84
|
-
// Skip parked milestones — they are not eligible for active status
|
|
85
98
|
const parkedFile = resolveMilestoneFile(basePath, mid, "PARKED");
|
|
86
99
|
if (parkedFile)
|
|
87
100
|
continue;
|
|
88
101
|
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
89
102
|
const content = roadmapFile ? await loadFile(roadmapFile) : null;
|
|
90
103
|
if (!content) {
|
|
91
|
-
// No roadmap — but if a summary exists, the milestone is already complete
|
|
92
104
|
const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
|
93
105
|
if (summaryFile)
|
|
94
|
-
continue;
|
|
106
|
+
continue;
|
|
95
107
|
if (isGhostMilestone(basePath, mid))
|
|
96
|
-
continue;
|
|
97
|
-
return mid;
|
|
98
|
-
// Note: draft-awareness (CONTEXT-DRAFT.md) is handled in deriveState(), not here.
|
|
99
|
-
// A draft milestone is still "active" — this function only determines which milestone is current.
|
|
108
|
+
continue;
|
|
109
|
+
return mid;
|
|
100
110
|
}
|
|
101
111
|
const roadmap = parseRoadmap(content);
|
|
102
112
|
if (!isMilestoneComplete(roadmap)) {
|
|
103
|
-
// Summary is the terminal artifact — if it exists, the milestone is
|
|
104
|
-
// complete even when roadmap checkboxes weren't ticked (#864).
|
|
105
113
|
const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
|
106
114
|
if (!summaryFile)
|
|
107
115
|
return mid;
|
|
@@ -110,13 +118,12 @@ export async function getActiveMilestoneId(basePath) {
|
|
|
110
118
|
return null;
|
|
111
119
|
}
|
|
112
120
|
/**
|
|
113
|
-
* Reconstruct GSD state from
|
|
114
|
-
*
|
|
121
|
+
* Reconstruct GSD state from DB (primary) or filesystem (fallback).
|
|
122
|
+
* STATE.md is a rendered cache of this output.
|
|
115
123
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* Falls back to sequential JS file reads when the native module is absent.
|
|
124
|
+
* When DB is available, queries milestone/slice/task tables directly.
|
|
125
|
+
* Falls back to filesystem parsing for unmigrated projects or when DB
|
|
126
|
+
* has zero milestones (e.g. first run before migration).
|
|
120
127
|
*/
|
|
121
128
|
export async function deriveState(basePath) {
|
|
122
129
|
// Return cached result if within the TTL window for the same basePath
|
|
@@ -590,6 +597,9 @@ export async function deriveStateFromDb(basePath) {
|
|
|
590
597
|
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
591
598
|
};
|
|
592
599
|
}
|
|
600
|
+
// LEGACY: Filesystem-based state derivation for unmigrated projects.
|
|
601
|
+
// DB-backed projects use deriveStateFromDb() above. Target: extract to
|
|
602
|
+
// state-legacy.ts when all projects are DB-backed.
|
|
593
603
|
export async function _deriveStateImpl(basePath) {
|
|
594
604
|
const milestoneIds = findMilestoneIds(basePath);
|
|
595
605
|
// ── Parallel worker isolation ──────────────────────────────────────────
|