gsd-pi 2.45.0-dev.6b9da3e → 2.45.0-dev.fdcf73c
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/help-text.js +1 -1
- package/dist/loader.js +34 -0
- package/dist/resources/extensions/gsd/auto/phases.js +16 -10
- package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
- package/dist/resources/extensions/gsd/auto.js +4 -5
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
- package/dist/resources/extensions/gsd/db-writer.js +9 -9
- package/dist/resources/extensions/gsd/doctor-checks.js +1 -1
- package/dist/resources/extensions/gsd/doctor.js +2 -2
- package/dist/resources/extensions/gsd/gsd-db.js +5 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -2
- package/dist/resources/extensions/gsd/preferences.js +8 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +20 -7
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +138 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
- package/dist/resources/extensions/voice/index.js +11 -16
- package/dist/resources/extensions/voice/linux-ready.js +67 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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 +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 +17 -17
- 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/package.json +2 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- 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/lifecycle-hooks.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
- package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
- package/src/resources/extensions/gsd/auto/phases.ts +16 -12
- package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
- package/src/resources/extensions/gsd/auto.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
- package/src/resources/extensions/gsd/db-writer.ts +9 -17
- package/src/resources/extensions/gsd/doctor-checks.ts +1 -1
- package/src/resources/extensions/gsd/doctor.ts +2 -2
- package/src/resources/extensions/gsd/gsd-db.ts +5 -1
- package/src/resources/extensions/gsd/journal.ts +6 -1
- package/src/resources/extensions/gsd/preferences-types.ts +2 -2
- package/src/resources/extensions/gsd/preferences.ts +7 -3
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +20 -7
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +6 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +193 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
- package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
- package/src/resources/extensions/voice/index.ts +11 -21
- package/src/resources/extensions/voice/linux-ready.ts +87 -0
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
- /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → zWYDSwB-terOjfhmWzqk1}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → zWYDSwB-terOjfhmWzqk1}/_ssgManifest.js +0 -0
|
@@ -6,6 +6,7 @@ import { findMilestoneIds, nextMilestoneId, claimReservedId, getReservedMileston
|
|
|
6
6
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
7
7
|
import { ensureDbOpen } from "./dynamic-tools.js";
|
|
8
8
|
import { StringEnum } from "@gsd/pi-ai";
|
|
9
|
+
import { logError } from "../workflow-logger.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Register an alias tool that shares the same execute function as its canonical counterpart.
|
|
@@ -52,7 +53,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
52
53
|
};
|
|
53
54
|
} catch (err) {
|
|
54
55
|
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
-
|
|
56
|
+
logError("tool", `gsd_decision_save tool failed: ${msg}`, { tool: "gsd_decision_save", error: String(err) });
|
|
56
57
|
return {
|
|
57
58
|
content: [{ type: "text" as const, text: `Error saving decision: ${msg}` }],
|
|
58
59
|
details: { operation: "save_decision", error: msg } as any,
|
|
@@ -143,7 +144,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
143
144
|
};
|
|
144
145
|
} catch (err) {
|
|
145
146
|
const msg = err instanceof Error ? err.message : String(err);
|
|
146
|
-
|
|
147
|
+
logError("tool", `gsd_requirement_update tool failed: ${msg}`, { tool: "gsd_requirement_update", error: String(err) });
|
|
147
148
|
return {
|
|
148
149
|
content: [{ type: "text" as const, text: `Error updating requirement: ${msg}` }],
|
|
149
150
|
details: { operation: "update_requirement", id: params.id, error: msg } as any,
|
|
@@ -239,7 +240,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
239
240
|
};
|
|
240
241
|
} catch (err) {
|
|
241
242
|
const msg = err instanceof Error ? err.message : String(err);
|
|
242
|
-
|
|
243
|
+
logError("tool", `gsd_summary_save tool failed: ${msg}`, { tool: "gsd_summary_save", error: String(err) });
|
|
243
244
|
return {
|
|
244
245
|
content: [{ type: "text" as const, text: `Error saving artifact: ${msg}` }],
|
|
245
246
|
details: { operation: "save_summary", error: msg } as any,
|
|
@@ -402,7 +403,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
402
403
|
};
|
|
403
404
|
} catch (err) {
|
|
404
405
|
const msg = err instanceof Error ? err.message : String(err);
|
|
405
|
-
|
|
406
|
+
logError("tool", `plan_milestone tool failed: ${msg}`, { tool: "gsd_plan_milestone", error: String(err) });
|
|
406
407
|
return {
|
|
407
408
|
content: [{ type: "text" as const, text: `Error planning milestone: ${msg}` }],
|
|
408
409
|
details: { operation: "plan_milestone", error: msg } as any,
|
|
@@ -495,7 +496,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
495
496
|
};
|
|
496
497
|
} catch (err) {
|
|
497
498
|
const msg = err instanceof Error ? err.message : String(err);
|
|
498
|
-
|
|
499
|
+
logError("tool", `plan_slice tool failed: ${msg}`, { tool: "gsd_plan_slice", error: String(err) });
|
|
499
500
|
return {
|
|
500
501
|
content: [{ type: "text" as const, text: `Error planning slice: ${msg}` }],
|
|
501
502
|
details: { operation: "plan_slice", error: msg } as any,
|
|
@@ -572,7 +573,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
572
573
|
};
|
|
573
574
|
} catch (err) {
|
|
574
575
|
const msg = err instanceof Error ? err.message : String(err);
|
|
575
|
-
|
|
576
|
+
logError("tool", `plan_task tool failed: ${msg}`, { tool: "gsd_plan_task", error: String(err) });
|
|
576
577
|
return {
|
|
577
578
|
content: [{ type: "text" as const, text: `Error planning task: ${msg}` }],
|
|
578
579
|
details: { operation: "plan_task", error: msg } as any,
|
|
@@ -642,7 +643,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
642
643
|
};
|
|
643
644
|
} catch (err) {
|
|
644
645
|
const msg = err instanceof Error ? err.message : String(err);
|
|
645
|
-
|
|
646
|
+
logError("tool", `complete_task tool failed: ${msg}`, { tool: "gsd_task_complete", error: String(err) });
|
|
646
647
|
return {
|
|
647
648
|
content: [{ type: "text" as const, text: `Error completing task: ${msg}` }],
|
|
648
649
|
details: { operation: "complete_task", error: msg } as any,
|
|
@@ -723,7 +724,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
723
724
|
};
|
|
724
725
|
} catch (err) {
|
|
725
726
|
const msg = err instanceof Error ? err.message : String(err);
|
|
726
|
-
|
|
727
|
+
logError("tool", `complete_slice tool failed: ${msg}`, { tool: "gsd_slice_complete", error: String(err) });
|
|
727
728
|
return {
|
|
728
729
|
content: [{ type: "text" as const, text: `Error completing slice: ${msg}` }],
|
|
729
730
|
details: { operation: "complete_slice", error: msg } as any,
|
|
@@ -834,7 +835,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
834
835
|
};
|
|
835
836
|
} catch (err) {
|
|
836
837
|
const msg = err instanceof Error ? err.message : String(err);
|
|
837
|
-
|
|
838
|
+
logError("tool", `complete_milestone tool failed: ${msg}`, { tool: "gsd_complete_milestone", error: String(err) });
|
|
838
839
|
return {
|
|
839
840
|
content: [{ type: "text" as const, text: `Error completing milestone: ${msg}` }],
|
|
840
841
|
details: { operation: "complete_milestone", error: msg } as any,
|
|
@@ -852,6 +853,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
852
853
|
promptGuidelines: [
|
|
853
854
|
"Use gsd_complete_milestone when all slices in a milestone are finished and the milestone needs to be recorded.",
|
|
854
855
|
"All slices in the milestone must have status 'complete' — the handler validates this before proceeding.",
|
|
856
|
+
"verificationPassed must be explicitly set to true — the handler rejects completion if verification did not pass.",
|
|
855
857
|
"On success, returns summaryPath where the MILESTONE-SUMMARY.md was written.",
|
|
856
858
|
],
|
|
857
859
|
parameters: Type.Object({
|
|
@@ -867,6 +869,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
867
869
|
lessonsLearned: Type.Array(Type.String(), { description: "Lessons learned during the milestone" }),
|
|
868
870
|
followUps: Type.Optional(Type.String({ description: "Follow-up items for future milestones" })),
|
|
869
871
|
deviations: Type.Optional(Type.String({ description: "Deviations from the original plan" })),
|
|
872
|
+
verificationPassed: Type.Boolean({ description: "Must be true — confirms that code change verification, success criteria, and definition of done checks all passed before completion" }),
|
|
870
873
|
}),
|
|
871
874
|
execute: milestoneCompleteExecute,
|
|
872
875
|
};
|
|
@@ -904,7 +907,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
904
907
|
};
|
|
905
908
|
} catch (err) {
|
|
906
909
|
const msg = err instanceof Error ? err.message : String(err);
|
|
907
|
-
|
|
910
|
+
logError("tool", `validate_milestone tool failed: ${msg}`, { tool: "gsd_validate_milestone", error: String(err) });
|
|
908
911
|
return {
|
|
909
912
|
content: [{ type: "text" as const, text: `Error validating milestone: ${msg}` }],
|
|
910
913
|
details: { operation: "validate_milestone", error: msg } as any,
|
|
@@ -973,7 +976,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
973
976
|
};
|
|
974
977
|
} catch (err) {
|
|
975
978
|
const msg = err instanceof Error ? err.message : String(err);
|
|
976
|
-
|
|
979
|
+
logError("tool", `replan_slice tool failed: ${msg}`, { tool: "gsd_replan_slice", error: String(err) });
|
|
977
980
|
return {
|
|
978
981
|
content: [{ type: "text" as const, text: `Error replanning slice: ${msg}` }],
|
|
979
982
|
details: { operation: "replan_slice", error: msg } as any,
|
|
@@ -1053,7 +1056,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
1053
1056
|
};
|
|
1054
1057
|
} catch (err) {
|
|
1055
1058
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1056
|
-
|
|
1059
|
+
logError("tool", `reassess_roadmap tool failed: ${msg}`, { tool: "gsd_reassess_roadmap", error: String(err) });
|
|
1057
1060
|
return {
|
|
1058
1061
|
content: [{ type: "text" as const, text: `Error reassessing roadmap: ${msg}` }],
|
|
1059
1062
|
details: { operation: "reassess_roadmap", error: msg } as any,
|
|
@@ -14,6 +14,7 @@ import type { Decision, Requirement } from './types.js';
|
|
|
14
14
|
import { resolveGsdRootFile } from './paths.js';
|
|
15
15
|
import { saveFile } from './files.js';
|
|
16
16
|
import { GSDError, GSD_STALE_STATE, GSD_IO_ERROR } from './errors.js';
|
|
17
|
+
import { logWarning, logError } from './workflow-logger.js';
|
|
17
18
|
import { invalidateStateCache } from './state.js';
|
|
18
19
|
import { clearPathCache } from './paths.js';
|
|
19
20
|
import { clearParseCache } from './files.js';
|
|
@@ -221,7 +222,7 @@ export async function nextDecisionId(): Promise<string> {
|
|
|
221
222
|
const next = maxNum + 1;
|
|
222
223
|
return `D${String(next).padStart(3, '0')}`;
|
|
223
224
|
} catch (err) {
|
|
224
|
-
|
|
225
|
+
logError('manifest', 'nextDecisionId failed', { fn: 'nextDecisionId', error: String((err as Error).message) });
|
|
225
226
|
return 'D001';
|
|
226
227
|
}
|
|
227
228
|
}
|
|
@@ -311,9 +312,7 @@ export async function saveDecisionToDb(
|
|
|
311
312
|
try {
|
|
312
313
|
await saveFile(filePath, md);
|
|
313
314
|
} catch (diskErr) {
|
|
314
|
-
|
|
315
|
-
`gsd-db: saveDecisionToDb — disk write failed, rolling back DB row: ${(diskErr as Error).message}\n`,
|
|
316
|
-
);
|
|
315
|
+
logError('manifest', 'disk write failed, rolling back DB row', { fn: 'saveDecisionToDb', error: String((diskErr as Error).message) });
|
|
317
316
|
adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
|
|
318
317
|
throw diskErr;
|
|
319
318
|
}
|
|
@@ -325,7 +324,7 @@ export async function saveDecisionToDb(
|
|
|
325
324
|
|
|
326
325
|
return { id };
|
|
327
326
|
} catch (err) {
|
|
328
|
-
|
|
327
|
+
logError('manifest', 'saveDecisionToDb failed', { fn: 'saveDecisionToDb', error: String((err as Error).message) });
|
|
329
328
|
throw err;
|
|
330
329
|
}
|
|
331
330
|
}
|
|
@@ -388,9 +387,7 @@ export async function updateRequirementInDb(
|
|
|
388
387
|
try {
|
|
389
388
|
await saveFile(filePath, md);
|
|
390
389
|
} catch (diskErr) {
|
|
391
|
-
|
|
392
|
-
`gsd-db: updateRequirementInDb — disk write failed, reverting DB row: ${(diskErr as Error).message}\n`,
|
|
393
|
-
);
|
|
390
|
+
logError('manifest', 'disk write failed, reverting DB row', { fn: 'updateRequirementInDb', error: String((diskErr as Error).message) });
|
|
394
391
|
db.upsertRequirement(existing);
|
|
395
392
|
throw diskErr;
|
|
396
393
|
}
|
|
@@ -400,7 +397,7 @@ export async function updateRequirementInDb(
|
|
|
400
397
|
clearPathCache();
|
|
401
398
|
clearParseCache();
|
|
402
399
|
} catch (err) {
|
|
403
|
-
|
|
400
|
+
logError('manifest', 'updateRequirementInDb failed', { fn: 'updateRequirementInDb', error: String((err as Error).message) });
|
|
404
401
|
throw err;
|
|
405
402
|
}
|
|
406
403
|
}
|
|
@@ -444,10 +441,7 @@ export async function saveArtifactToDb(
|
|
|
444
441
|
const existingSize = statSync(fullPath).size;
|
|
445
442
|
const newSize = Buffer.byteLength(opts.content, 'utf-8');
|
|
446
443
|
if (existingSize > 0 && newSize < existingSize * 0.5) {
|
|
447
|
-
|
|
448
|
-
`gsd-db: saveArtifactToDb — new content (${newSize}B) is <50% of existing file ` +
|
|
449
|
-
`(${existingSize}B) at ${opts.path}. Preserving disk file to prevent data loss.\n`,
|
|
450
|
-
);
|
|
444
|
+
logWarning('manifest', `new content (${newSize}B) is <50% of existing file (${existingSize}B), preserving disk file`, { fn: 'saveArtifactToDb', path: opts.path });
|
|
451
445
|
dbContent = readFileSync(fullPath, 'utf-8');
|
|
452
446
|
skipDiskWrite = true;
|
|
453
447
|
}
|
|
@@ -467,9 +461,7 @@ export async function saveArtifactToDb(
|
|
|
467
461
|
try {
|
|
468
462
|
await saveFile(fullPath, opts.content);
|
|
469
463
|
} catch (diskErr) {
|
|
470
|
-
|
|
471
|
-
`gsd-db: saveArtifactToDb — disk write failed, rolling back DB row: ${(diskErr as Error).message}\n`,
|
|
472
|
-
);
|
|
464
|
+
logError('manifest', 'disk write failed, rolling back DB row', { fn: 'saveArtifactToDb', error: String((diskErr as Error).message) });
|
|
473
465
|
const rollbackAdapter = db._getAdapter();
|
|
474
466
|
rollbackAdapter?.prepare('DELETE FROM artifacts WHERE path = :path').run({ ':path': opts.path });
|
|
475
467
|
throw diskErr;
|
|
@@ -481,7 +473,7 @@ export async function saveArtifactToDb(
|
|
|
481
473
|
clearPathCache();
|
|
482
474
|
clearParseCache();
|
|
483
475
|
} catch (err) {
|
|
484
|
-
|
|
476
|
+
logError('manifest', 'saveArtifactToDb failed', { fn: 'saveArtifactToDb', error: String((err as Error).message) });
|
|
485
477
|
throw err;
|
|
486
478
|
}
|
|
487
479
|
}
|
|
@@ -25,7 +25,7 @@ export async function checkGitHealth(
|
|
|
25
25
|
issues: DoctorIssue[],
|
|
26
26
|
fixesApplied: string[],
|
|
27
27
|
shouldFix: (code: DoctorIssueCode) => boolean,
|
|
28
|
-
isolationMode: "none" | "worktree" | "branch" = "
|
|
28
|
+
isolationMode: "none" | "worktree" | "branch" = "none",
|
|
29
29
|
): Promise<void> {
|
|
30
30
|
// Degrade gracefully if not a git repo
|
|
31
31
|
if (!nativeIsRepo(basePath)) {
|
|
@@ -360,8 +360,8 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
360
360
|
// Git health checks — timed
|
|
361
361
|
const t0git = Date.now();
|
|
362
362
|
const isolationMode: "none" | "worktree" | "branch" = options?.isolationMode ??
|
|
363
|
-
(prefs?.preferences?.git?.isolation === "
|
|
364
|
-
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "
|
|
363
|
+
(prefs?.preferences?.git?.isolation === "worktree" ? "worktree" :
|
|
364
|
+
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "none");
|
|
365
365
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
366
366
|
const gitMs = Date.now() - t0git;
|
|
367
367
|
|
|
@@ -78,8 +78,12 @@ function loadProvider(): void {
|
|
|
78
78
|
// unavailable
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
82
|
+
const versionHint = nodeMajor < 22
|
|
83
|
+
? ` GSD requires Node >= 22.0.0 (current: v${process.versions.node}). Upgrade Node to fix this.`
|
|
84
|
+
: "";
|
|
81
85
|
process.stderr.write(
|
|
82
|
-
|
|
86
|
+
`gsd-db: No SQLite provider available (tried node:sqlite, better-sqlite3).${versionHint}\n`,
|
|
83
87
|
);
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -32,7 +32,12 @@ export type JournalEventType =
|
|
|
32
32
|
| "milestone-transition"
|
|
33
33
|
| "stuck-detected"
|
|
34
34
|
| "sidecar-dequeue"
|
|
35
|
-
| "iteration-end"
|
|
35
|
+
| "iteration-end"
|
|
36
|
+
| "worktree-enter"
|
|
37
|
+
| "worktree-create-failed"
|
|
38
|
+
| "worktree-skip"
|
|
39
|
+
| "worktree-merge-start"
|
|
40
|
+
| "worktree-merge-failed";
|
|
36
41
|
|
|
37
42
|
/** A single structured event in the journal. */
|
|
38
43
|
export interface JournalEntry {
|
|
@@ -34,7 +34,7 @@ export const MODE_DEFAULTS: Record<WorkflowMode, Partial<GSDPreferences>> = {
|
|
|
34
34
|
push_branches: false,
|
|
35
35
|
pre_merge_check: false,
|
|
36
36
|
merge_strategy: "squash",
|
|
37
|
-
isolation: "
|
|
37
|
+
isolation: "none",
|
|
38
38
|
},
|
|
39
39
|
unique_milestone_ids: false,
|
|
40
40
|
},
|
|
@@ -44,7 +44,7 @@ export const MODE_DEFAULTS: Record<WorkflowMode, Partial<GSDPreferences>> = {
|
|
|
44
44
|
push_branches: true,
|
|
45
45
|
pre_merge_check: true,
|
|
46
46
|
merge_strategy: "squash",
|
|
47
|
-
isolation: "
|
|
47
|
+
isolation: "none",
|
|
48
48
|
},
|
|
49
49
|
unique_milestone_ids: true,
|
|
50
50
|
},
|
|
@@ -497,13 +497,17 @@ export function resolvePreDispatchHooks(): PreDispatchHookConfig[] {
|
|
|
497
497
|
|
|
498
498
|
/**
|
|
499
499
|
* Resolve the effective git isolation mode from preferences.
|
|
500
|
-
* Returns "
|
|
500
|
+
* Returns "none" (default), "worktree", or "branch".
|
|
501
|
+
*
|
|
502
|
+
* Default is "none" so GSD works out of the box without preferences.md.
|
|
503
|
+
* Worktree isolation requires explicit opt-in because it depends on git
|
|
504
|
+
* branch infrastructure that must be set up before use.
|
|
501
505
|
*/
|
|
502
506
|
export function getIsolationMode(): "none" | "worktree" | "branch" {
|
|
503
507
|
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
504
|
-
if (prefs?.isolation === "
|
|
508
|
+
if (prefs?.isolation === "worktree") return "worktree";
|
|
505
509
|
if (prefs?.isolation === "branch") return "branch";
|
|
506
|
-
return "
|
|
510
|
+
return "none"; // default — no isolation, work on current branch
|
|
507
511
|
}
|
|
508
512
|
|
|
509
513
|
export function resolveParallelConfig(prefs: GSDPreferences | undefined): import("./types.js").ParallelConfig {
|
|
@@ -17,18 +17,31 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|
|
17
17
|
Then:
|
|
18
18
|
1. Use the **Milestone Summary** output template from the inlined context above
|
|
19
19
|
2. {{skillActivation}}
|
|
20
|
-
3. **Verify code changes exist.** Run `git diff --stat HEAD $(git merge-base HEAD main) -- ':!.gsd/'` (or the equivalent for the integration branch). If no non-`.gsd/` files appear in the diff, the milestone produced only planning artifacts and no actual code.
|
|
21
|
-
4. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior.
|
|
22
|
-
5. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
|
|
20
|
+
3. **Verify code changes exist.** Run `git diff --stat HEAD $(git merge-base HEAD main) -- ':!.gsd/'` (or the equivalent for the integration branch). If no non-`.gsd/` files appear in the diff, the milestone produced only planning artifacts and no actual code. Record this as a **verification failure**.
|
|
21
|
+
4. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. Record any criterion that was NOT met as a **verification failure**.
|
|
22
|
+
5. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly. Record any unmet items as a **verification failure**.
|
|
23
23
|
6. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
### Verification Gate — STOP if verification failed
|
|
26
|
+
|
|
27
|
+
**If ANY verification failure was recorded in steps 3, 4, or 5, you MUST follow the failure path below. Do NOT proceed to step 7.**
|
|
28
|
+
|
|
29
|
+
**Failure path** (verification failed):
|
|
30
|
+
- Do NOT call `gsd_complete_milestone` — the milestone must not be marked as complete.
|
|
31
|
+
- Do NOT update `.gsd/PROJECT.md` to reflect completion.
|
|
32
|
+
- Do NOT update `.gsd/REQUIREMENTS.md` to mark requirements as validated.
|
|
33
|
+
- Write a clear summary of what failed and why to help the next attempt.
|
|
34
|
+
- Say: "Milestone {{milestoneId}} verification FAILED — not complete." and stop.
|
|
35
|
+
|
|
36
|
+
**Success path** (all verifications passed — continue with steps 7–11):
|
|
37
|
+
|
|
38
|
+
7. **Persist completion through `gsd_complete_milestone`.** Call it with: `milestoneId`, `title`, `oneLiner`, `narrative`, `successCriteriaResults`, `definitionOfDoneResults`, `requirementOutcomes`, `keyDecisions`, `keyFiles`, `lessonsLearned`, `followUps`, `deviations`, `verificationPassed: true`. The tool updates the milestone status in the DB, renders `{{milestoneSummaryPath}}`, and validates all slices are complete before proceeding.
|
|
25
39
|
8. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 6.
|
|
26
40
|
9. Update `.gsd/PROJECT.md` to reflect milestone completion and current project state.
|
|
27
41
|
10. Review all slice summaries for cross-cutting lessons, patterns, or gotchas that emerged during this milestone. Append any non-obvious, reusable insights to `.gsd/KNOWLEDGE.md`.
|
|
28
42
|
11. Do not commit manually — the system auto-commits your changes after this unit completes.
|
|
43
|
+
- Say: "Milestone {{milestoneId}} complete."
|
|
29
44
|
|
|
30
|
-
**Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success.
|
|
45
|
+
**Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. Verification failures BLOCK completion — there is no override. The milestone stays in its current state until issues are resolved and verification is re-run.
|
|
31
46
|
|
|
32
47
|
**File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
|
33
|
-
|
|
34
|
-
When done, say: "Milestone {{milestoneId}} complete."
|
|
@@ -115,6 +115,102 @@ describe("complete-milestone", () => {
|
|
|
115
115
|
assert.ok(prompt.includes("Milestone M002 complete"), "prompt contains completion sentinel for M002");
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
test("prompt contains verification gate that blocks completion on failure", () => {
|
|
119
|
+
const prompt = loadPromptFromWorktree("complete-milestone", {
|
|
120
|
+
workingDirectory: "/tmp/test-project",
|
|
121
|
+
milestoneId: "M001",
|
|
122
|
+
milestoneTitle: "Gate Test",
|
|
123
|
+
roadmapPath: ".gsd/milestones/M001/M001-ROADMAP.md",
|
|
124
|
+
inlinedContext: "context",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Verification gate section must exist
|
|
128
|
+
assert.ok(
|
|
129
|
+
prompt.includes("Verification Gate"),
|
|
130
|
+
"prompt contains 'Verification Gate' section",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Failure path must block gsd_complete_milestone
|
|
134
|
+
assert.ok(
|
|
135
|
+
prompt.includes("Do NOT call `gsd_complete_milestone`"),
|
|
136
|
+
"failure path explicitly blocks calling the completion tool",
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Failure path must have its own sentinel distinct from success
|
|
140
|
+
assert.ok(
|
|
141
|
+
prompt.includes("verification FAILED"),
|
|
142
|
+
"failure path outputs a FAILED sentinel",
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// verificationPassed parameter must be referenced
|
|
146
|
+
assert.ok(
|
|
147
|
+
prompt.includes("verificationPassed"),
|
|
148
|
+
"prompt references verificationPassed parameter",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("handleCompleteMilestone rejects when verificationPassed is false", async () => {
|
|
153
|
+
const { handleCompleteMilestone } = await import("../tools/complete-milestone.ts");
|
|
154
|
+
const base = createFixtureBase();
|
|
155
|
+
try {
|
|
156
|
+
const result = await handleCompleteMilestone({
|
|
157
|
+
milestoneId: "M001",
|
|
158
|
+
title: "Test Milestone",
|
|
159
|
+
oneLiner: "Test",
|
|
160
|
+
narrative: "Test narrative",
|
|
161
|
+
successCriteriaResults: "None met",
|
|
162
|
+
definitionOfDoneResults: "Incomplete",
|
|
163
|
+
requirementOutcomes: "None validated",
|
|
164
|
+
keyDecisions: [],
|
|
165
|
+
keyFiles: [],
|
|
166
|
+
lessonsLearned: [],
|
|
167
|
+
followUps: "",
|
|
168
|
+
deviations: "",
|
|
169
|
+
verificationPassed: false,
|
|
170
|
+
}, base);
|
|
171
|
+
|
|
172
|
+
assert.ok("error" in result, "returns error when verificationPassed is false");
|
|
173
|
+
assert.ok(
|
|
174
|
+
(result as { error: string }).error.includes("verification did not pass"),
|
|
175
|
+
"error message mentions verification did not pass",
|
|
176
|
+
);
|
|
177
|
+
} finally {
|
|
178
|
+
cleanup(base);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("handleCompleteMilestone rejects when verificationPassed is omitted", async () => {
|
|
183
|
+
const { handleCompleteMilestone } = await import("../tools/complete-milestone.ts");
|
|
184
|
+
const base = createFixtureBase();
|
|
185
|
+
try {
|
|
186
|
+
// Simulate omitted verificationPassed (undefined coerced via any)
|
|
187
|
+
const params: any = {
|
|
188
|
+
milestoneId: "M001",
|
|
189
|
+
title: "Test Milestone",
|
|
190
|
+
oneLiner: "Test",
|
|
191
|
+
narrative: "Test narrative",
|
|
192
|
+
successCriteriaResults: "Results",
|
|
193
|
+
definitionOfDoneResults: "Done results",
|
|
194
|
+
requirementOutcomes: "Outcomes",
|
|
195
|
+
keyDecisions: [],
|
|
196
|
+
keyFiles: [],
|
|
197
|
+
lessonsLearned: [],
|
|
198
|
+
followUps: "",
|
|
199
|
+
deviations: "",
|
|
200
|
+
// verificationPassed intentionally omitted
|
|
201
|
+
};
|
|
202
|
+
const result = await handleCompleteMilestone(params, base);
|
|
203
|
+
|
|
204
|
+
assert.ok("error" in result, "returns error when verificationPassed is omitted");
|
|
205
|
+
assert.ok(
|
|
206
|
+
(result as { error: string }).error.includes("verification did not pass"),
|
|
207
|
+
"error message mentions verification did not pass",
|
|
208
|
+
);
|
|
209
|
+
} finally {
|
|
210
|
+
cleanup(base);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
118
214
|
test("diagnoseExpectedArtifact logic for complete-milestone", async () => {
|
|
119
215
|
// Import the path helpers used by diagnoseExpectedArtifact
|
|
120
216
|
const { relMilestoneFile } = await import("../paths.ts");
|
|
@@ -27,7 +27,7 @@ console.log("\n=== #2330: Merge conflict stops auto loop ===");
|
|
|
27
27
|
const methodStart = resolverSrc.indexOf("Worktree-mode merge:");
|
|
28
28
|
assertTrue(methodStart > 0, "worktree-resolver has _mergeWorktreeMode method");
|
|
29
29
|
|
|
30
|
-
const methodBody = resolverSrc.slice(methodStart, methodStart +
|
|
30
|
+
const methodBody = resolverSrc.slice(methodStart, methodStart + 6000);
|
|
31
31
|
const rethrowsConflict =
|
|
32
32
|
methodBody.includes("MergeConflictError") &&
|
|
33
33
|
methodBody.includes("throw err");
|
|
@@ -70,18 +70,20 @@ try {
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
// Test 4: shouldUseWorktreeIsolation returns
|
|
73
|
+
// Test 4: shouldUseWorktreeIsolation returns false for no prefs (default: none)
|
|
74
|
+
// Worktree isolation requires explicit opt-in — default is "none" so GSD
|
|
75
|
+
// works out of the box without preferences.md (#2480).
|
|
74
76
|
// Skip if global prefs exist — they override the default and this test
|
|
75
77
|
// cannot control ~/.gsd/preferences.md.
|
|
76
78
|
|
|
77
|
-
test('shouldUseWorktreeIsolation returns
|
|
79
|
+
test('shouldUseWorktreeIsolation returns false for no prefs (default: none)', () => {
|
|
78
80
|
const globalPrefsExist = existsSync(join(homedir(), ".gsd", "preferences.md"))
|
|
79
81
|
|| existsSync(join(homedir(), ".gsd", "PREFERENCES.md"));
|
|
80
82
|
if (!globalPrefsExist) {
|
|
81
83
|
try {
|
|
82
84
|
removeRunnerPreferences(); // ensure no prefs file
|
|
83
85
|
invalidateAllCaches();
|
|
84
|
-
assert.deepStrictEqual(shouldUseWorktreeIsolation(),
|
|
86
|
+
assert.deepStrictEqual(shouldUseWorktreeIsolation(), false, "shouldUseWorktreeIsolation() with no prefs (default none)");
|
|
85
87
|
} finally {
|
|
86
88
|
invalidateAllCaches();
|
|
87
89
|
}
|
|
@@ -89,6 +91,21 @@ test('shouldUseWorktreeIsolation returns true for no prefs (default)', () => {
|
|
|
89
91
|
}
|
|
90
92
|
});
|
|
91
93
|
|
|
94
|
+
// Test 5: getIsolationMode returns "none" when no preferences.md exists (#2480)
|
|
95
|
+
test('getIsolationMode returns "none" with no prefs (default)', () => {
|
|
96
|
+
const globalPrefsExist = existsSync(join(homedir(), ".gsd", "preferences.md"))
|
|
97
|
+
|| existsSync(join(homedir(), ".gsd", "PREFERENCES.md"));
|
|
98
|
+
if (!globalPrefsExist) {
|
|
99
|
+
try {
|
|
100
|
+
removeRunnerPreferences();
|
|
101
|
+
invalidateAllCaches();
|
|
102
|
+
assert.deepStrictEqual(getIsolationMode(), "none", "getIsolationMode() with no prefs defaults to none");
|
|
103
|
+
} finally {
|
|
104
|
+
invalidateAllCaches();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
92
109
|
test('getIsolationMode returns "none" with none prefs', () => {
|
|
93
110
|
try {
|
|
94
111
|
writeRunnerPreferences("none");
|
|
@@ -100,6 +117,28 @@ try {
|
|
|
100
117
|
}
|
|
101
118
|
});
|
|
102
119
|
|
|
120
|
+
test('getIsolationMode returns "worktree" with worktree prefs', () => {
|
|
121
|
+
try {
|
|
122
|
+
writeRunnerPreferences("worktree");
|
|
123
|
+
invalidateAllCaches();
|
|
124
|
+
assert.deepStrictEqual(getIsolationMode(), "worktree", "getIsolationMode() with worktree prefs");
|
|
125
|
+
} finally {
|
|
126
|
+
removeRunnerPreferences();
|
|
127
|
+
invalidateAllCaches();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('getIsolationMode returns "branch" with branch prefs', () => {
|
|
132
|
+
try {
|
|
133
|
+
writeRunnerPreferences("branch");
|
|
134
|
+
invalidateAllCaches();
|
|
135
|
+
assert.deepStrictEqual(getIsolationMode(), "branch", "getIsolationMode() with branch prefs");
|
|
136
|
+
} finally {
|
|
137
|
+
removeRunnerPreferences();
|
|
138
|
+
invalidateAllCaches();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
103
142
|
test('getActiveAutoWorktreeContext returns null at baseline', () => {
|
|
104
143
|
assert.deepStrictEqual(getActiveAutoWorktreeContext(), null, "getActiveAutoWorktreeContext() returns null without enterAutoWorktree()");
|
|
105
144
|
});
|
|
@@ -41,18 +41,16 @@ test("git.merge_to_main produces deprecation warning", () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
test("getIsolationMode defaults to
|
|
44
|
+
test("getIsolationMode defaults to none when preferences have no isolation setting", () => {
|
|
45
45
|
// Validate the default via validatePreferences: when no isolation is set,
|
|
46
|
-
// preferences.git.isolation is undefined, and getIsolationMode returns "
|
|
47
|
-
//
|
|
46
|
+
// preferences.git.isolation is undefined, and getIsolationMode returns "none".
|
|
47
|
+
// Default changed from "worktree" to "none" so GSD works out of the box
|
|
48
|
+
// without preferences.md (#2480).
|
|
48
49
|
const { preferences } = validatePreferences({});
|
|
49
50
|
assert.equal(preferences.git?.isolation, undefined, "no isolation in empty prefs");
|
|
50
|
-
// The function returns "worktree" when prefs?.git?.isolation is not "none" or "branch"
|
|
51
|
-
// This is a compile-time-verifiable truth from the function body — test it directly
|
|
52
|
-
// by constructing the same conditions getIsolationMode checks.
|
|
53
51
|
const isolation = preferences.git?.isolation;
|
|
54
|
-
const expected = isolation === "
|
|
55
|
-
assert.equal(expected, "
|
|
52
|
+
const expected = isolation === "worktree" ? "worktree" : isolation === "branch" ? "branch" : "none";
|
|
53
|
+
assert.equal(expected, "none", "default isolation mode is none");
|
|
56
54
|
});
|
|
57
55
|
|
|
58
56
|
// ── Mode defaults ────────────────────────────────────────────────────────────
|
|
@@ -63,7 +61,7 @@ test("solo mode applies correct defaults", () => {
|
|
|
63
61
|
assert.equal(result.git?.push_branches, false);
|
|
64
62
|
assert.equal(result.git?.pre_merge_check, false);
|
|
65
63
|
assert.equal(result.git?.merge_strategy, "squash");
|
|
66
|
-
assert.equal(result.git?.isolation, "
|
|
64
|
+
assert.equal(result.git?.isolation, "none");
|
|
67
65
|
assert.equal(result.unique_milestone_ids, false);
|
|
68
66
|
});
|
|
69
67
|
|