gsd-pi 2.28.0-dev.e19bf89 → 2.29.0-dev.2ccf3fb
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 +24 -17
- package/dist/cli.js +15 -9
- package/dist/resource-loader.js +80 -8
- package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/dist/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/dist/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/dist/resources/extensions/gsd/auto-start.ts +25 -10
- package/dist/resources/extensions/gsd/auto-verification.ts +41 -7
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/dist/resources/extensions/gsd/auto.ts +67 -22
- package/dist/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/dist/resources/extensions/gsd/commands.ts +75 -29
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/dist/resources/extensions/gsd/doctor-types.ts +13 -0
- package/dist/resources/extensions/gsd/doctor.ts +2 -6
- package/dist/resources/extensions/gsd/export.ts +28 -2
- package/dist/resources/extensions/gsd/gsd-db.ts +19 -0
- package/dist/resources/extensions/gsd/index.ts +2 -1
- package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
- package/dist/resources/extensions/gsd/metrics.ts +17 -31
- package/dist/resources/extensions/gsd/paths.ts +0 -8
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/dist/resources/extensions/gsd/queue-order.ts +10 -11
- package/dist/resources/extensions/gsd/routing-history.ts +13 -17
- package/dist/resources/extensions/gsd/session-lock.ts +284 -0
- package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/dist/resources/extensions/gsd/types.ts +1 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/dist/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/dist/resources/extensions/gsd/verification-gate.ts +13 -2
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/dist/resources/extensions/mcp-client/index.ts +459 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
- package/dist/resources/extensions/remote-questions/notify.ts +1 -2
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/dist/resources/extensions/remote-questions/types.ts +3 -0
- package/dist/resources/extensions/shared/mod.ts +3 -0
- package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/dist/resources/skills/create-skill/SKILL.md +184 -0
- package/dist/resources/skills/create-skill/references/api-security.md +226 -0
- package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
- package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/package.json +6 -3
- package/packages/native/dist/native.d.ts +2 -0
- package/packages/native/dist/native.js +19 -5
- package/packages/native/src/native.ts +23 -9
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -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 +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +10 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.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 +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/scripts/copy-assets.cjs +39 -8
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -1
- package/packages/pi-tui/dist/autocomplete.d.ts +3 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +14 -0
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/src/autocomplete.ts +19 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/src/resources/extensions/gsd/auto-start.ts +25 -10
- package/src/resources/extensions/gsd/auto-verification.ts +41 -7
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/src/resources/extensions/gsd/auto.ts +67 -22
- package/src/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/src/resources/extensions/gsd/commands-logs.ts +536 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/src/resources/extensions/gsd/commands.ts +75 -29
- package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/src/resources/extensions/gsd/doctor-types.ts +13 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -6
- package/src/resources/extensions/gsd/export.ts +28 -2
- package/src/resources/extensions/gsd/gsd-db.ts +19 -0
- package/src/resources/extensions/gsd/index.ts +2 -1
- package/src/resources/extensions/gsd/json-persistence.ts +67 -0
- package/src/resources/extensions/gsd/metrics.ts +17 -31
- package/src/resources/extensions/gsd/paths.ts +0 -8
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/src/resources/extensions/gsd/queue-order.ts +10 -11
- package/src/resources/extensions/gsd/routing-history.ts +13 -17
- package/src/resources/extensions/gsd/session-lock.ts +284 -0
- package/src/resources/extensions/gsd/session-status-io.ts +23 -41
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/src/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/src/resources/extensions/gsd/verification-gate.ts +13 -2
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/src/resources/extensions/mcp-client/index.ts +459 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/src/resources/extensions/remote-questions/http-client.ts +76 -0
- package/src/resources/extensions/remote-questions/notify.ts +1 -2
- package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/src/resources/extensions/remote-questions/types.ts +3 -0
- package/src/resources/extensions/shared/mod.ts +3 -0
- package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/src/resources/skills/create-skill/SKILL.md +184 -0
- package/src/resources/skills/create-skill/references/api-security.md +226 -0
- package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/src/resources/skills/create-skill/references/core-principles.md +437 -0
- package/src/resources/skills/create-skill/references/executable-code.md +175 -0
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/src/resources/skills/create-skill/references/using-templates.md +112 -0
- package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/dist/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/dist/resources/extensions/mcporter/index.ts +0 -525
- package/dist/resources/extensions/shared/progress-widget.ts +0 -282
- package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
- package/src/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/src/resources/extensions/mcporter/index.ts +0 -525
- package/src/resources/extensions/shared/progress-widget.ts +0 -282
- package/src/resources/extensions/shared/thinking-widget.ts +0 -107
|
@@ -204,6 +204,13 @@ export function estimateTimeRemaining(): string | null {
|
|
|
204
204
|
|
|
205
205
|
// ─── Slice Progress Cache ─────────────────────────────────────────────────────
|
|
206
206
|
|
|
207
|
+
/** Cached task detail for the widget task checklist */
|
|
208
|
+
interface CachedTaskDetail {
|
|
209
|
+
id: string;
|
|
210
|
+
title: string;
|
|
211
|
+
done: boolean;
|
|
212
|
+
}
|
|
213
|
+
|
|
207
214
|
/** Cached slice progress for the widget — avoid async in render */
|
|
208
215
|
let cachedSliceProgress: {
|
|
209
216
|
done: number;
|
|
@@ -211,6 +218,8 @@ let cachedSliceProgress: {
|
|
|
211
218
|
milestoneId: string;
|
|
212
219
|
/** Real task progress for the active slice, if its plan file exists */
|
|
213
220
|
activeSliceTasks: { done: number; total: number } | null;
|
|
221
|
+
/** Full task list for the active slice checklist */
|
|
222
|
+
taskDetails: CachedTaskDetail[] | null;
|
|
214
223
|
} | null = null;
|
|
215
224
|
|
|
216
225
|
export function updateSliceProgressCache(base: string, mid: string, activeSid?: string): void {
|
|
@@ -221,6 +230,7 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
221
230
|
const roadmap = parseRoadmap(content);
|
|
222
231
|
|
|
223
232
|
let activeSliceTasks: { done: number; total: number } | null = null;
|
|
233
|
+
let taskDetails: CachedTaskDetail[] | null = null;
|
|
224
234
|
if (activeSid) {
|
|
225
235
|
try {
|
|
226
236
|
const planFile = resolveSliceFile(base, mid, activeSid, "PLAN");
|
|
@@ -231,6 +241,7 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
231
241
|
done: plan.tasks.filter(t => t.done).length,
|
|
232
242
|
total: plan.tasks.length,
|
|
233
243
|
};
|
|
244
|
+
taskDetails = plan.tasks.map(t => ({ id: t.id, title: t.title, done: t.done }));
|
|
234
245
|
}
|
|
235
246
|
} catch {
|
|
236
247
|
// Non-fatal — just omit task count
|
|
@@ -242,13 +253,19 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
242
253
|
total: roadmap.slices.length,
|
|
243
254
|
milestoneId: mid,
|
|
244
255
|
activeSliceTasks,
|
|
256
|
+
taskDetails,
|
|
245
257
|
};
|
|
246
258
|
} catch {
|
|
247
259
|
// Non-fatal — widget just won't show progress bar
|
|
248
260
|
}
|
|
249
261
|
}
|
|
250
262
|
|
|
251
|
-
export function getRoadmapSlicesSync(): {
|
|
263
|
+
export function getRoadmapSlicesSync(): {
|
|
264
|
+
done: number;
|
|
265
|
+
total: number;
|
|
266
|
+
activeSliceTasks: { done: number; total: number } | null;
|
|
267
|
+
taskDetails: CachedTaskDetail[] | null;
|
|
268
|
+
} | null {
|
|
252
269
|
return cachedSliceProgress;
|
|
253
270
|
}
|
|
254
271
|
|
|
@@ -349,87 +366,84 @@ export function updateProgressWidget(
|
|
|
349
366
|
const lines: string[] = [];
|
|
350
367
|
const pad = INDENT.base;
|
|
351
368
|
|
|
352
|
-
// ──
|
|
369
|
+
// ── Top bar ─────────────────────────────────────────────────────
|
|
353
370
|
lines.push(...ui.bar());
|
|
354
371
|
|
|
372
|
+
// ── Header: GSD AUTO ... elapsed ────────────────────────────────
|
|
355
373
|
const dot = pulseBright
|
|
356
374
|
? theme.fg("accent", GLYPH.statusActive)
|
|
357
375
|
: theme.fg("dim", GLYPH.statusPending);
|
|
358
376
|
const elapsed = formatAutoElapsed(accessors.getAutoStartTime());
|
|
359
377
|
const modeTag = accessors.isStepMode() ? "NEXT" : "AUTO";
|
|
360
|
-
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))}
|
|
378
|
+
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}`;
|
|
361
379
|
const headerRight = elapsed ? theme.fg("dim", elapsed) : "";
|
|
362
380
|
lines.push(rightAlign(headerLeft, headerRight, width));
|
|
363
381
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (mid)
|
|
367
|
-
lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}`, width));
|
|
368
|
-
}
|
|
369
|
-
|
|
382
|
+
// ── Context: project · slice · action (merged into one line) ────
|
|
383
|
+
const contextParts: string[] = [];
|
|
384
|
+
if (mid) contextParts.push(theme.fg("dim", mid.title));
|
|
370
385
|
if (slice && unitType !== "research-milestone" && unitType !== "plan-milestone") {
|
|
371
|
-
|
|
372
|
-
`${pad}${theme.fg("text", theme.bold(`${slice.id}: ${slice.title}`))}`,
|
|
373
|
-
width,
|
|
374
|
-
));
|
|
386
|
+
contextParts.push(theme.fg("text", theme.bold(`${slice.id}: ${slice.title}`)));
|
|
375
387
|
}
|
|
376
|
-
|
|
377
|
-
lines.push("");
|
|
378
|
-
|
|
379
388
|
const isHook = unitType.startsWith("hook/");
|
|
380
389
|
const target = isHook
|
|
381
390
|
? (unitId.split("/").pop() ?? unitId)
|
|
382
391
|
: (task ? `${task.id}: ${task.title}` : unitId);
|
|
383
|
-
|
|
392
|
+
contextParts.push(`${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`);
|
|
393
|
+
|
|
384
394
|
const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
|
|
385
395
|
const phaseBadge = `${tierTag}${theme.fg("dim", phaseLabel)}`;
|
|
386
|
-
|
|
387
|
-
lines.push(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
396
|
+
const contextLine = contextParts.join(theme.fg("dim", " · "));
|
|
397
|
+
lines.push(rightAlign(`${pad}${contextLine}`, phaseBadge, width));
|
|
398
|
+
|
|
399
|
+
// ── Two-column body ─────────────────────────────────────────────
|
|
400
|
+
// Left: progress, ETA, next, stats (fixed) | Right: task checklist (fixed, adjacent)
|
|
401
|
+
// Both columns sit left-to-center; empty space is on the right.
|
|
402
|
+
const divider = theme.fg("dim", "│");
|
|
403
|
+
const minTwoColWidth = 100;
|
|
404
|
+
const rightColFixed = 44;
|
|
405
|
+
const colGap = 5; // breathing room between columns
|
|
406
|
+
// Left column takes remaining space — no truncation on wide terminals
|
|
407
|
+
const useTwoCol = width >= minTwoColWidth;
|
|
408
|
+
const rightColWidth = useTwoCol ? rightColFixed : 0;
|
|
409
|
+
const leftColWidth = useTwoCol ? width - rightColWidth - colGap : width;
|
|
410
|
+
|
|
411
|
+
const roadmapSlices = mid ? getRoadmapSlicesSync() : null;
|
|
412
|
+
|
|
413
|
+
// Build left column: progress bar, ETA, next step, token stats
|
|
414
|
+
const leftLines: string[] = [];
|
|
415
|
+
|
|
416
|
+
if (roadmapSlices) {
|
|
417
|
+
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
418
|
+
const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
|
|
419
|
+
const pct = total > 0 ? done / total : 0;
|
|
420
|
+
const filled = Math.round(pct * barWidth);
|
|
421
|
+
const bar = theme.fg("success", "█".repeat(filled))
|
|
422
|
+
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
423
|
+
|
|
424
|
+
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
425
|
+
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
426
|
+
const taskNum = isHook
|
|
427
|
+
? Math.max(activeSliceTasks.done, 1)
|
|
428
|
+
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
429
|
+
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
430
|
+
}
|
|
431
|
+
leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
|
|
414
432
|
|
|
415
|
-
|
|
433
|
+
const eta = estimateTimeRemaining();
|
|
434
|
+
if (eta) {
|
|
435
|
+
leftLines.push(truncateToWidth(`${pad}${theme.fg("dim", eta)}`, leftColWidth));
|
|
416
436
|
}
|
|
417
437
|
}
|
|
418
438
|
|
|
419
|
-
lines.push("");
|
|
420
|
-
|
|
421
439
|
if (next) {
|
|
422
|
-
|
|
440
|
+
leftLines.push(truncateToWidth(
|
|
423
441
|
`${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
|
|
424
|
-
|
|
442
|
+
leftColWidth,
|
|
425
443
|
));
|
|
426
444
|
}
|
|
427
445
|
|
|
428
|
-
//
|
|
429
|
-
lines.push("");
|
|
430
|
-
lines.push(truncateToWidth(theme.fg("dim", `${pad}${widgetPwd}`), width, theme.fg("dim", "…")));
|
|
431
|
-
|
|
432
|
-
// Token stats from current unit session + cumulative cost from metrics
|
|
446
|
+
// Token stats
|
|
433
447
|
{
|
|
434
448
|
const cmdCtx = accessors.getCmdCtx();
|
|
435
449
|
let totalInput = 0, totalOutput = 0;
|
|
@@ -464,7 +478,6 @@ export function updateProgressWidget(
|
|
|
464
478
|
if (totalOutput) sp.push(`↓${formatWidgetTokens(totalOutput)}`);
|
|
465
479
|
if (totalCacheRead) sp.push(`R${formatWidgetTokens(totalCacheRead)}`);
|
|
466
480
|
if (totalCacheWrite) sp.push(`W${formatWidgetTokens(totalCacheWrite)}`);
|
|
467
|
-
// Cache hit rate for current unit
|
|
468
481
|
if (totalCacheRead + totalInput > 0) {
|
|
469
482
|
const hitRate = Math.round((totalCacheRead / (totalCacheRead + totalInput)) * 100);
|
|
470
483
|
sp.push(`\u26A1${hitRate}%`);
|
|
@@ -483,33 +496,134 @@ export function updateProgressWidget(
|
|
|
483
496
|
sp.push(cxDisplay);
|
|
484
497
|
}
|
|
485
498
|
|
|
486
|
-
const
|
|
499
|
+
const tokenLine = sp.map(p => p.includes("\x1b[") ? p : theme.fg("dim", p))
|
|
487
500
|
.join(theme.fg("dim", " "));
|
|
501
|
+
leftLines.push(truncateToWidth(`${pad}${tokenLine}`, leftColWidth));
|
|
488
502
|
|
|
489
503
|
const modelId = cmdCtx?.model?.id ?? "";
|
|
490
504
|
const modelProvider = cmdCtx?.model?.provider ?? "";
|
|
491
|
-
const modelPhase = phaseLabel ? theme.fg("dim", `[${phaseLabel}] `) : "";
|
|
492
505
|
const modelDisplay = modelProvider && modelId
|
|
493
506
|
? `${modelProvider}/${modelId}`
|
|
494
507
|
: modelId;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
lines.push(rightAlign(`${pad}${sLeft}`, sRight, width));
|
|
508
|
+
if (modelDisplay) {
|
|
509
|
+
leftLines.push(truncateToWidth(`${pad}${theme.fg("dim", modelDisplay)}`, leftColWidth));
|
|
510
|
+
}
|
|
499
511
|
|
|
500
|
-
// Dynamic routing savings
|
|
512
|
+
// Dynamic routing savings
|
|
501
513
|
if (mLedger && mLedger.units.some(u => u.tier)) {
|
|
502
514
|
const savings = formatTierSavings(mLedger.units);
|
|
503
515
|
if (savings) {
|
|
504
|
-
|
|
516
|
+
leftLines.push(truncateToWidth(`${pad}${theme.fg("dim", savings)}`, leftColWidth));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Build right column: task checklist (pegged to right edge)
|
|
522
|
+
const rightLines: string[] = [];
|
|
523
|
+
const taskDetails = roadmapSlices?.taskDetails ?? null;
|
|
524
|
+
const maxVisibleTasks = 8;
|
|
525
|
+
const rpad = " ";
|
|
526
|
+
|
|
527
|
+
if (useTwoCol) {
|
|
528
|
+
if (taskDetails && taskDetails.length > 0) {
|
|
529
|
+
const visibleTasks = taskDetails.slice(0, maxVisibleTasks);
|
|
530
|
+
for (const t of visibleTasks) {
|
|
531
|
+
const isCurrent = task && t.id === task.id;
|
|
532
|
+
const glyph = t.done
|
|
533
|
+
? theme.fg("success", GLYPH.statusDone)
|
|
534
|
+
: isCurrent
|
|
535
|
+
? theme.fg("accent", "▸")
|
|
536
|
+
: theme.fg("dim", " ");
|
|
537
|
+
const label = isCurrent
|
|
538
|
+
? theme.fg("text", `${t.id}: ${t.title}`)
|
|
539
|
+
: t.done
|
|
540
|
+
? theme.fg("dim", `${t.id}: ${t.title}`)
|
|
541
|
+
: theme.fg("text", `${t.id}: ${t.title}`);
|
|
542
|
+
rightLines.push(truncateToWidth(`${rpad}${glyph} ${label}`, rightColWidth));
|
|
543
|
+
}
|
|
544
|
+
if (taskDetails.length > maxVisibleTasks) {
|
|
545
|
+
rightLines.push(truncateToWidth(
|
|
546
|
+
`${rpad}${theme.fg("dim", ` …+${taskDetails.length - maxVisibleTasks} more`)}`,
|
|
547
|
+
rightColWidth,
|
|
548
|
+
));
|
|
549
|
+
}
|
|
550
|
+
} else if (roadmapSlices?.activeSliceTasks) {
|
|
551
|
+
const { done: tDone, total: tTotal } = roadmapSlices.activeSliceTasks;
|
|
552
|
+
rightLines.push(`${rpad}${theme.fg("dim", `${tDone}/${tTotal} tasks`)}`);
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
// Narrow single-column: task list goes into left column
|
|
556
|
+
if (taskDetails && taskDetails.length > 0) {
|
|
557
|
+
for (const t of taskDetails.slice(0, maxVisibleTasks)) {
|
|
558
|
+
const isCurrent = task && t.id === task.id;
|
|
559
|
+
const glyph = t.done
|
|
560
|
+
? theme.fg("success", GLYPH.statusDone)
|
|
561
|
+
: isCurrent
|
|
562
|
+
? theme.fg("accent", "▸")
|
|
563
|
+
: theme.fg("dim", " ");
|
|
564
|
+
const label = isCurrent
|
|
565
|
+
? theme.fg("text", `${t.id}: ${t.title}`)
|
|
566
|
+
: t.done
|
|
567
|
+
? theme.fg("dim", `${t.id}: ${t.title}`)
|
|
568
|
+
: theme.fg("text", `${t.id}: ${t.title}`);
|
|
569
|
+
leftLines.push(truncateToWidth(`${pad}${glyph} ${label}`, leftColWidth));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// Add progress bar inline
|
|
573
|
+
if (roadmapSlices) {
|
|
574
|
+
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
575
|
+
const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
|
|
576
|
+
const pct = total > 0 ? done / total : 0;
|
|
577
|
+
const filled = Math.round(pct * barWidth);
|
|
578
|
+
const bar = theme.fg("success", "█".repeat(filled))
|
|
579
|
+
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
580
|
+
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
581
|
+
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
582
|
+
const taskNum = isHook
|
|
583
|
+
? Math.max(activeSliceTasks.done, 1)
|
|
584
|
+
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
585
|
+
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
505
586
|
}
|
|
587
|
+
const eta = estimateTimeRemaining();
|
|
588
|
+
if (eta) meta += theme.fg("dim", ` · ${eta}`);
|
|
589
|
+
leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
|
|
590
|
+
}
|
|
591
|
+
if (next) {
|
|
592
|
+
leftLines.push(truncateToWidth(
|
|
593
|
+
`${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
|
|
594
|
+
leftColWidth,
|
|
595
|
+
));
|
|
506
596
|
}
|
|
507
597
|
}
|
|
508
598
|
|
|
599
|
+
// Compose columns
|
|
600
|
+
if (useTwoCol) {
|
|
601
|
+
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
602
|
+
if (maxRows > 0) {
|
|
603
|
+
lines.push(""); // spacer before columns
|
|
604
|
+
for (let i = 0; i < maxRows; i++) {
|
|
605
|
+
const left = padToWidth(leftLines[i] ?? "", leftColWidth);
|
|
606
|
+
const gap = " ".repeat(colGap - 2); // colGap minus divider and its trailing space
|
|
607
|
+
const right = rightLines[i] ?? "";
|
|
608
|
+
lines.push(truncateToWidth(`${left}${gap}${divider} ${right}`, width));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
// Narrow single-column: just stack
|
|
613
|
+
if (leftLines.length > 0) {
|
|
614
|
+
lines.push("");
|
|
615
|
+
for (const l of leftLines) lines.push(l);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ── Footer: pwd + hints ─────────────────────────────────────────
|
|
620
|
+
lines.push("");
|
|
509
621
|
const hintParts: string[] = [];
|
|
510
622
|
hintParts.push("esc pause");
|
|
511
623
|
hintParts.push(process.platform === "darwin" ? "⌃⌥G dashboard" : "Ctrl+Alt+G dashboard");
|
|
512
|
-
|
|
624
|
+
const hintStr = theme.fg("dim", hintParts.join(" | "));
|
|
625
|
+
const pwdStr = theme.fg("dim", widgetPwd);
|
|
626
|
+
lines.push(rightAlign(`${pad}${pwdStr}`, hintStr, width));
|
|
513
627
|
|
|
514
628
|
lines.push(...ui.bar());
|
|
515
629
|
|
|
@@ -597,3 +711,10 @@ function rightAlign(left: string, right: string, width: number): string {
|
|
|
597
711
|
const gap = Math.max(1, width - leftVis - rightVis);
|
|
598
712
|
return truncateToWidth(left + " ".repeat(gap) + right, width);
|
|
599
713
|
}
|
|
714
|
+
|
|
715
|
+
/** Pad a string with trailing spaces to fill exactly `colWidth` (ANSI-aware). */
|
|
716
|
+
function padToWidth(s: string, colWidth: number): string {
|
|
717
|
+
const vis = visibleWidth(s);
|
|
718
|
+
if (vis >= colWidth) return truncateToWidth(s, colWidth);
|
|
719
|
+
return s + " ".repeat(colWidth - vis);
|
|
720
|
+
}
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
36
36
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
37
37
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
38
|
+
import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
38
39
|
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
39
40
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
40
41
|
import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
@@ -154,13 +155,17 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
154
155
|
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
// Proactive health tracking
|
|
158
|
-
|
|
158
|
+
// Proactive health tracking — exclude completion-transition codes at task level
|
|
159
|
+
// since they are expected after the last task and resolved by complete-slice
|
|
160
|
+
const issuesForHealth = effectiveFixLevel === "task"
|
|
161
|
+
? report.issues.filter(i => !COMPLETION_TRANSITION_CODES.has(i.code))
|
|
162
|
+
: report.issues;
|
|
163
|
+
const summary = summarizeDoctorIssues(issuesForHealth);
|
|
159
164
|
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
160
165
|
|
|
161
166
|
// Check if we should escalate to LLM-assisted heal
|
|
162
167
|
if (summary.errors > 0) {
|
|
163
|
-
const unresolvedErrors =
|
|
168
|
+
const unresolvedErrors = issuesForHealth
|
|
164
169
|
.filter(i => i.severity === "error" && !i.fixable)
|
|
165
170
|
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
166
171
|
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
@@ -171,7 +176,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
171
176
|
);
|
|
172
177
|
try {
|
|
173
178
|
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
174
|
-
const { dispatchDoctorHeal } = await import("./commands.js");
|
|
179
|
+
const { dispatchDoctorHeal } = await import("./commands-handlers.js");
|
|
175
180
|
const actionable = report.issues.filter(i => i.severity === "error");
|
|
176
181
|
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
177
182
|
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
@@ -197,10 +202,13 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
197
202
|
}
|
|
198
203
|
}
|
|
199
204
|
|
|
200
|
-
// Prune dead bg-shell processes
|
|
205
|
+
// Prune dead bg-shell processes and kill non-persistent live ones.
|
|
206
|
+
// Without killing live processes between units, dev servers spawned during
|
|
207
|
+
// one task keep ports bound, causing conflicts in subsequent tasks (#1209).
|
|
201
208
|
try {
|
|
202
|
-
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
209
|
+
const { pruneDeadProcesses, killSessionProcesses } = await import("../bg-shell/process-manager.js");
|
|
203
210
|
pruneDeadProcesses();
|
|
211
|
+
killSessionProcesses();
|
|
204
212
|
} catch {
|
|
205
213
|
// Non-fatal
|
|
206
214
|
}
|
|
@@ -36,8 +36,10 @@ import {
|
|
|
36
36
|
clearPathCache,
|
|
37
37
|
resolveGsdRootFile,
|
|
38
38
|
} from "./paths.js";
|
|
39
|
+
import { isValidationTerminal } from "./state.js";
|
|
39
40
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
40
41
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
42
|
+
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
41
43
|
import { dirname, join } from "node:path";
|
|
42
44
|
|
|
43
45
|
// ─── Artifact Resolution & Verification ───────────────────────────────────────
|
|
@@ -137,6 +139,21 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
137
139
|
if (!absPath) return false;
|
|
138
140
|
if (!existsSync(absPath)) return false;
|
|
139
141
|
|
|
142
|
+
// validate-milestone must have a VALIDATION file with a terminal verdict
|
|
143
|
+
// (pass, needs-attention, or needs-remediation). Without this check, a
|
|
144
|
+
// VALIDATION file with missing/malformed frontmatter or an unrecognized
|
|
145
|
+
// verdict is treated as "complete" by the artifact check but deriveState
|
|
146
|
+
// still returns phase:"validating-milestone" (because isValidationTerminal
|
|
147
|
+
// returns false), creating an infinite skip loop that hits the lifetime cap.
|
|
148
|
+
if (unitType === "validate-milestone") {
|
|
149
|
+
try {
|
|
150
|
+
const validationContent = readFileSync(absPath, "utf-8");
|
|
151
|
+
if (!isValidationTerminal(validationContent)) return false;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
140
157
|
// plan-slice must produce a plan with actual task entries, not just a scaffold.
|
|
141
158
|
// The plan file may exist from a prior discussion/context step with only headings
|
|
142
159
|
// but no tasks. Without this check the artifact is considered "complete" and the
|
|
@@ -211,7 +228,7 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
211
228
|
try {
|
|
212
229
|
const roadmapContent = readFileSync(roadmapFile, "utf-8");
|
|
213
230
|
const roadmap = parseRoadmap(roadmapContent);
|
|
214
|
-
const slice = roadmap.slices.find(s => s.id === sid);
|
|
231
|
+
const slice = (roadmap.slices ?? []).find(s => s.id === sid);
|
|
215
232
|
if (slice && !slice.done) return false;
|
|
216
233
|
} catch {
|
|
217
234
|
// Corrupt/unparseable roadmap — fail verification so the unit
|
|
@@ -338,6 +355,10 @@ export function skipExecuteTask(
|
|
|
338
355
|
|
|
339
356
|
// ─── Disk-backed completed-unit helpers ───────────────────────────────────────
|
|
340
357
|
|
|
358
|
+
function isStringArray(data: unknown): data is string[] {
|
|
359
|
+
return Array.isArray(data) && data.every(item => typeof item === "string");
|
|
360
|
+
}
|
|
361
|
+
|
|
341
362
|
/** Path to the persisted completed-unit keys file. */
|
|
342
363
|
export function completedKeysPath(base: string): string {
|
|
343
364
|
return join(base, ".gsd", "completed-units.json");
|
|
@@ -346,12 +367,7 @@ export function completedKeysPath(base: string): string {
|
|
|
346
367
|
/** Write a completed unit key to disk (read-modify-write append to set). */
|
|
347
368
|
export function persistCompletedKey(base: string, key: string): void {
|
|
348
369
|
const file = completedKeysPath(base);
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
if (existsSync(file)) {
|
|
352
|
-
keys = JSON.parse(readFileSync(file, "utf-8"));
|
|
353
|
-
}
|
|
354
|
-
} catch (e) { /* corrupt file — start fresh */ void e; }
|
|
370
|
+
const keys = loadJsonFileOrNull(file, isStringArray) ?? [];
|
|
355
371
|
const keySet = new Set(keys);
|
|
356
372
|
if (!keySet.has(key)) {
|
|
357
373
|
keys.push(key);
|
|
@@ -362,27 +378,21 @@ export function persistCompletedKey(base: string, key: string): void {
|
|
|
362
378
|
/** Remove a stale completed unit key from disk. */
|
|
363
379
|
export function removePersistedKey(base: string, key: string): void {
|
|
364
380
|
const file = completedKeysPath(base);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
atomicWriteSync(file, JSON.stringify(filtered));
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
} catch (e) { /* non-fatal: removePersistedKey failure */ void e; }
|
|
381
|
+
const keys = loadJsonFileOrNull(file, isStringArray);
|
|
382
|
+
if (!keys) return;
|
|
383
|
+
const filtered = keys.filter(k => k !== key);
|
|
384
|
+
if (filtered.length !== keys.length) {
|
|
385
|
+
atomicWriteSync(file, JSON.stringify(filtered));
|
|
386
|
+
}
|
|
375
387
|
}
|
|
376
388
|
|
|
377
389
|
/** Load all completed unit keys from disk into the in-memory set. */
|
|
378
390
|
export function loadPersistedKeys(base: string, target: Set<string>): void {
|
|
379
391
|
const file = completedKeysPath(base);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
} catch (e) { /* non-fatal: loadPersistedKeys failure */ void e; }
|
|
392
|
+
const keys = loadJsonFileOrNull(file, isStringArray);
|
|
393
|
+
if (keys) {
|
|
394
|
+
for (const k of keys) target.add(k);
|
|
395
|
+
}
|
|
386
396
|
}
|
|
387
397
|
|
|
388
398
|
// ─── Merge State Reconciliation ───────────────────────────────────────────────
|
|
@@ -26,6 +26,13 @@ import {
|
|
|
26
26
|
import { invalidateAllCaches } from "./cache.js";
|
|
27
27
|
import { synthesizeCrashRecovery } from "./session-forensics.js";
|
|
28
28
|
import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
|
|
29
|
+
import {
|
|
30
|
+
acquireSessionLock,
|
|
31
|
+
updateSessionLock,
|
|
32
|
+
releaseSessionLock,
|
|
33
|
+
readSessionLockData,
|
|
34
|
+
isSessionLockProcessAlive,
|
|
35
|
+
} from "./session-lock.js";
|
|
29
36
|
import { selfHealRuntimeRecords } from "./auto-recovery.js";
|
|
30
37
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
31
38
|
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
|
|
@@ -81,6 +88,18 @@ export async function bootstrapAutoSession(
|
|
|
81
88
|
): Promise<boolean> {
|
|
82
89
|
const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase } = deps;
|
|
83
90
|
|
|
91
|
+
// ── Session lock: acquire FIRST, before any state mutation ──────────────
|
|
92
|
+
// This is the primary guard against concurrent sessions on the same project.
|
|
93
|
+
// Uses OS-level file locking (proper-lockfile) to prevent TOCTOU races.
|
|
94
|
+
const lockResult = acquireSessionLock(base);
|
|
95
|
+
if (!lockResult.acquired) {
|
|
96
|
+
ctx.ui.notify(
|
|
97
|
+
`${lockResult.reason}\nStop it with \`kill ${lockResult.existingPid ?? "the other process"}\` before starting a new session.`,
|
|
98
|
+
"error",
|
|
99
|
+
);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
84
103
|
// Ensure git repo exists
|
|
85
104
|
if (!nativeIsRepo(base)) {
|
|
86
105
|
const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
@@ -109,16 +128,11 @@ export async function bootstrapAutoSession(
|
|
|
109
128
|
// Initialize GitServiceImpl
|
|
110
129
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
111
130
|
|
|
112
|
-
// Check for crash from previous session
|
|
131
|
+
// Check for crash from previous session (use both old and new lock data)
|
|
113
132
|
const crashLock = readCrashLock(base);
|
|
114
133
|
if (crashLock) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
`Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
|
|
118
|
-
"error",
|
|
119
|
-
);
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
134
|
+
// We already hold the session lock, so no concurrent session is running.
|
|
135
|
+
// The crash lock is from a dead process — recover context from it.
|
|
122
136
|
const recoveredMid = crashLock.unitId.split("/")[0];
|
|
123
137
|
const milestoneAlreadyComplete = recoveredMid
|
|
124
138
|
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
|
|
@@ -401,13 +415,14 @@ export async function bootstrapAutoSession(
|
|
|
401
415
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
402
416
|
ctx.ui.setFooter(hideFooter);
|
|
403
417
|
const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
|
|
404
|
-
const pendingCount = state.registry.filter(m => m.status !== 'complete' && m.status !== 'parked').length;
|
|
418
|
+
const pendingCount = (state.registry ?? []).filter(m => m.status !== 'complete' && m.status !== 'parked').length;
|
|
405
419
|
const scopeMsg = pendingCount > 1
|
|
406
420
|
? `Will loop through ${pendingCount} milestones.`
|
|
407
421
|
: "Will loop until milestone complete.";
|
|
408
422
|
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
409
423
|
|
|
410
|
-
//
|
|
424
|
+
// Update lock file with milestone info (OS lock already acquired at bootstrap start)
|
|
425
|
+
updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
|
|
411
426
|
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
|
|
412
427
|
|
|
413
428
|
// Secrets collection gate — pause instead of blocking (#1146)
|
|
@@ -105,19 +105,39 @@ export async function runPostUnitVerification(
|
|
|
105
105
|
const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
106
106
|
|
|
107
107
|
if (result.checks.length > 0) {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
108
|
+
const blockingChecks = result.checks.filter(c => c.blocking);
|
|
109
|
+
const advisoryChecks = result.checks.filter(c => !c.blocking);
|
|
110
|
+
const blockingPassCount = blockingChecks.filter(c => c.exitCode === 0).length;
|
|
111
|
+
const advisoryFailCount = advisoryChecks.filter(c => c.exitCode !== 0).length;
|
|
112
|
+
|
|
110
113
|
if (result.passed) {
|
|
111
|
-
|
|
114
|
+
let msg = blockingChecks.length > 0
|
|
115
|
+
? `Verification gate: ${blockingPassCount}/${blockingChecks.length} blocking checks passed`
|
|
116
|
+
: `Verification gate: passed (no blocking checks)`;
|
|
117
|
+
if (advisoryFailCount > 0) {
|
|
118
|
+
msg += ` (${advisoryFailCount} advisory warning${advisoryFailCount > 1 ? "s" : ""})`;
|
|
119
|
+
}
|
|
120
|
+
ctx.ui.notify(msg);
|
|
121
|
+
// Log advisory warnings to stderr for visibility
|
|
122
|
+
if (advisoryFailCount > 0) {
|
|
123
|
+
const advisoryFailures = advisoryChecks.filter(c => c.exitCode !== 0);
|
|
124
|
+
process.stderr.write(`verification-gate: ${advisoryFailCount} advisory (non-blocking) failure(s)\n`);
|
|
125
|
+
for (const f of advisoryFailures) {
|
|
126
|
+
process.stderr.write(` [advisory] ${f.command} exited ${f.exitCode}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
112
129
|
} else {
|
|
113
|
-
const
|
|
114
|
-
const failNames =
|
|
130
|
+
const blockingFailures = blockingChecks.filter(c => c.exitCode !== 0);
|
|
131
|
+
const failNames = blockingFailures.map(f => f.command).join(", ");
|
|
115
132
|
ctx.ui.notify(`Verification gate: FAILED — ${failNames}`);
|
|
116
|
-
process.stderr.write(`verification-gate: ${
|
|
117
|
-
for (const f of
|
|
133
|
+
process.stderr.write(`verification-gate: ${blockingFailures.length}/${blockingChecks.length} blocking checks failed\n`);
|
|
134
|
+
for (const f of blockingFailures) {
|
|
118
135
|
process.stderr.write(` ${f.command} exited ${f.exitCode}\n`);
|
|
119
136
|
if (f.stderr) process.stderr.write(` stderr: ${f.stderr.slice(0, 500)}\n`);
|
|
120
137
|
}
|
|
138
|
+
if (advisoryFailCount > 0) {
|
|
139
|
+
process.stderr.write(`verification-gate: ${advisoryFailCount} additional advisory (non-blocking) failure(s)\n`);
|
|
140
|
+
}
|
|
121
141
|
}
|
|
122
142
|
}
|
|
123
143
|
|
|
@@ -155,6 +175,20 @@ export async function runPostUnitVerification(
|
|
|
155
175
|
s.verificationRetryCount.delete(s.currentUnit.id);
|
|
156
176
|
s.pendingVerificationRetry = null;
|
|
157
177
|
return "continue";
|
|
178
|
+
} else if (result.discoverySource === "package-json") {
|
|
179
|
+
// Auto-discovered checks from package.json may fail on pre-existing errors
|
|
180
|
+
// that the current task didn't introduce. Don't trigger the retry loop —
|
|
181
|
+
// log a warning and let the task proceed (#1186).
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
`verification-gate: auto-discovered checks failed (source: package-json) — treating as advisory, not blocking\n`,
|
|
184
|
+
);
|
|
185
|
+
ctx.ui.notify(
|
|
186
|
+
`Verification: auto-discovered checks failed (pre-existing errors likely). Continuing without retry.`,
|
|
187
|
+
"warning",
|
|
188
|
+
);
|
|
189
|
+
s.verificationRetryCount.delete(s.currentUnit.id);
|
|
190
|
+
s.pendingVerificationRetry = null;
|
|
191
|
+
return "continue";
|
|
158
192
|
} else if (autoFixEnabled && attempt + 1 <= maxRetries) {
|
|
159
193
|
const nextAttempt = attempt + 1;
|
|
160
194
|
s.verificationRetryCount.set(s.currentUnit.id, nextAttempt);
|