gsd-pi 2.23.0 → 2.25.0
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 +2 -1
- package/dist/cli.js +12 -3
- package/dist/headless.d.ts +4 -0
- package/dist/headless.js +118 -10
- package/dist/help-text.js +22 -7
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +64 -18
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +87 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +134 -4
- package/dist/resources/extensions/gsd/auto.ts +307 -77
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/commands.ts +176 -10
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/dist/resources/extensions/gsd/doctor.ts +58 -11
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +109 -12
- package/dist/resources/extensions/gsd/index.ts +48 -2
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
- package/dist/resources/extensions/gsd/state.ts +72 -30
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
- package/dist/resources/extensions/subagent/index.ts +5 -0
- package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
- package/dist/update-check.d.ts +9 -0
- package/dist/update-check.js +97 -0
- package/package.json +6 -1
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +55 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +59 -9
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- 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 +72 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
- package/scripts/postinstall.js +7 -109
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +87 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +134 -4
- package/src/resources/extensions/gsd/auto.ts +307 -77
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/commands.ts +176 -10
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/src/resources/extensions/gsd/doctor.ts +58 -11
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +109 -12
- package/src/resources/extensions/gsd/index.ts +48 -2
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/session-status-io.ts +197 -0
- package/src/resources/extensions/gsd/state.ts +72 -30
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- package/src/resources/extensions/search-the-web/native-search.ts +15 -5
- package/src/resources/extensions/subagent/index.ts +5 -0
- package/src/resources/extensions/subagent/worker-registry.ts +99 -0
|
@@ -12,16 +12,18 @@
|
|
|
12
12
|
import { invalidateStateCache } from './state.js';
|
|
13
13
|
import { clearPathCache } from './paths.js';
|
|
14
14
|
import { clearParseCache } from './files.js';
|
|
15
|
+
import { clearArtifacts } from './gsd-db.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Invalidate all GSD runtime caches in one call.
|
|
18
19
|
*
|
|
19
20
|
* Call this after file writes, milestone transitions, merge reconciliation,
|
|
20
21
|
* or any operation that changes .gsd/ contents on disk. Forgetting to clear
|
|
21
|
-
* any single cache causes stale reads (see #431).
|
|
22
|
+
* any single cache causes stale reads (see #431, #793).
|
|
22
23
|
*/
|
|
23
24
|
export function invalidateAllCaches(): void {
|
|
24
25
|
invalidateStateCache();
|
|
25
26
|
clearPathCache();
|
|
26
27
|
clearParseCache();
|
|
28
|
+
clearArtifacts();
|
|
27
29
|
}
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
8
8
|
import { AuthStorage } from "@gsd/pi-coding-agent";
|
|
9
|
-
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { existsSync, readFileSync, mkdirSync, unlinkSync } from "node:fs";
|
|
10
10
|
import { join, dirname } from "node:path";
|
|
11
11
|
import { enableDebug, isDebugEnabled } from "./debug-logger.js";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { deriveState } from "./state.js";
|
|
14
14
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
15
15
|
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
16
|
-
import { showQueue, showDiscuss } from "./guided-flow.js";
|
|
16
|
+
import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
|
|
17
17
|
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote, dispatchDirectPhase } from "./auto.js";
|
|
18
18
|
import { resolveProjectRoot } from "./worktree.js";
|
|
19
19
|
import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
@@ -42,6 +42,14 @@ import { handleQuick } from "./quick.js";
|
|
|
42
42
|
import { handleHistory } from "./history.js";
|
|
43
43
|
import { handleUndo } from "./undo.js";
|
|
44
44
|
import { handleExport } from "./export.js";
|
|
45
|
+
import {
|
|
46
|
+
isParallelActive, getOrchestratorState, getWorkerStatuses,
|
|
47
|
+
prepareParallelStart, startParallel, stopParallel,
|
|
48
|
+
pauseWorker, resumeWorker,
|
|
49
|
+
} from "./parallel-orchestrator.js";
|
|
50
|
+
import { formatEligibilityReport } from "./parallel-eligibility.js";
|
|
51
|
+
import { mergeAllCompleted, mergeCompletedMilestone, formatMergeResults } from "./parallel-merge.js";
|
|
52
|
+
import { resolveParallelConfig } from "./preferences.js";
|
|
45
53
|
import { nativeBranchList, nativeDetectMainBranch, nativeBranchListMerged, nativeBranchDelete, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
|
|
46
54
|
|
|
47
55
|
export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
@@ -69,20 +77,53 @@ function projectRoot(): string {
|
|
|
69
77
|
|
|
70
78
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
71
79
|
pi.registerCommand("gsd", {
|
|
72
|
-
description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge",
|
|
80
|
+
description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge|new-milestone|parallel",
|
|
73
81
|
getArgumentCompletions: (prefix: string) => {
|
|
74
82
|
const subcommands = [
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
|
|
83
|
+
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
84
|
+
{ cmd: "next", desc: "Explicit step mode (same as /gsd)" },
|
|
85
|
+
{ cmd: "auto", desc: "Autonomous mode — research, plan, execute, commit, repeat" },
|
|
86
|
+
{ cmd: "stop", desc: "Stop auto mode gracefully" },
|
|
87
|
+
{ cmd: "pause", desc: "Pause auto-mode (preserves state, /gsd auto to resume)" },
|
|
88
|
+
{ cmd: "status", desc: "Progress dashboard" },
|
|
89
|
+
{ cmd: "visualize", desc: "Open workflow visualizer (progress, deps, metrics, timeline)" },
|
|
90
|
+
{ cmd: "queue", desc: "Queue and reorder future milestones" },
|
|
91
|
+
{ cmd: "quick", desc: "Execute a quick task without full planning overhead" },
|
|
92
|
+
{ cmd: "discuss", desc: "Discuss architecture and decisions" },
|
|
93
|
+
{ cmd: "capture", desc: "Fire-and-forget thought capture" },
|
|
94
|
+
{ cmd: "triage", desc: "Manually trigger triage of pending captures" },
|
|
95
|
+
{ cmd: "dispatch", desc: "Dispatch a specific phase directly" },
|
|
96
|
+
{ cmd: "history", desc: "View execution history" },
|
|
97
|
+
{ cmd: "undo", desc: "Revert last completed unit" },
|
|
98
|
+
{ cmd: "skip", desc: "Prevent a unit from auto-mode dispatch" },
|
|
99
|
+
{ cmd: "export", desc: "Export milestone/slice results" },
|
|
100
|
+
{ cmd: "cleanup", desc: "Remove merged branches or snapshots" },
|
|
101
|
+
{ cmd: "mode", desc: "Switch workflow mode (solo/team)" },
|
|
102
|
+
{ cmd: "prefs", desc: "Manage preferences (model selection, timeouts, etc.)" },
|
|
103
|
+
{ cmd: "config", desc: "Set API keys for external tools" },
|
|
104
|
+
{ cmd: "hooks", desc: "Show configured post-unit and pre-dispatch hooks" },
|
|
105
|
+
{ cmd: "run-hook", desc: "Manually trigger a specific hook" },
|
|
106
|
+
{ cmd: "skill-health", desc: "Skill lifecycle dashboard" },
|
|
107
|
+
{ cmd: "doctor", desc: "Runtime health checks with auto-fix" },
|
|
108
|
+
{ cmd: "forensics", desc: "Examine execution logs" },
|
|
109
|
+
{ cmd: "migrate", desc: "Migrate a v1 .planning directory to .gsd format" },
|
|
110
|
+
{ cmd: "remote", desc: "Control remote auto-mode" },
|
|
111
|
+
{ cmd: "steer", desc: "Hard-steer plan documents during execution" },
|
|
112
|
+
{ cmd: "inspect", desc: "Show SQLite DB diagnostics" },
|
|
113
|
+
{ cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
|
|
114
|
+
{ cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
|
|
115
|
+
{ cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
|
|
79
116
|
];
|
|
80
117
|
const parts = prefix.trim().split(/\s+/);
|
|
81
118
|
|
|
82
119
|
if (parts.length <= 1) {
|
|
83
120
|
return subcommands
|
|
84
|
-
.filter((
|
|
85
|
-
.map((
|
|
121
|
+
.filter((item) => item.cmd.startsWith(parts[0] ?? ""))
|
|
122
|
+
.map((item) => ({
|
|
123
|
+
value: item.cmd,
|
|
124
|
+
label: item.cmd,
|
|
125
|
+
description: item.desc
|
|
126
|
+
}));
|
|
86
127
|
}
|
|
87
128
|
|
|
88
129
|
if (parts[0] === "auto" && parts.length <= 2) {
|
|
@@ -99,6 +140,13 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
99
140
|
.map((cmd) => ({ value: `mode ${cmd}`, label: cmd }));
|
|
100
141
|
}
|
|
101
142
|
|
|
143
|
+
if (parts[0] === "parallel" && parts.length <= 2) {
|
|
144
|
+
const subPrefix = parts[1] ?? "";
|
|
145
|
+
return ["start", "status", "stop", "pause", "resume", "merge"]
|
|
146
|
+
.filter((cmd) => cmd.startsWith(subPrefix))
|
|
147
|
+
.map((cmd) => ({ value: `parallel ${cmd}`, label: cmd }));
|
|
148
|
+
}
|
|
149
|
+
|
|
102
150
|
if (parts[0] === "prefs" && parts.length <= 2) {
|
|
103
151
|
const subPrefix = parts[1] ?? "";
|
|
104
152
|
return ["global", "project", "status", "wizard", "setup", "import-claude"]
|
|
@@ -251,7 +299,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
251
299
|
}
|
|
252
300
|
return;
|
|
253
301
|
}
|
|
254
|
-
await stopAuto(ctx, pi);
|
|
302
|
+
await stopAuto(ctx, pi, "User requested stop");
|
|
255
303
|
return;
|
|
256
304
|
}
|
|
257
305
|
|
|
@@ -288,6 +336,108 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
288
336
|
return;
|
|
289
337
|
}
|
|
290
338
|
|
|
339
|
+
// ─── Parallel Orchestration ────────────────────────────────────────
|
|
340
|
+
if (trimmed.startsWith("parallel")) {
|
|
341
|
+
const parallelArgs = trimmed.slice("parallel".length).trim();
|
|
342
|
+
const [subCmd = "", ...restParts] = parallelArgs.split(/\s+/);
|
|
343
|
+
const rest = restParts.join(" ");
|
|
344
|
+
|
|
345
|
+
if (subCmd === "start" || subCmd === "") {
|
|
346
|
+
const loaded = loadEffectiveGSDPreferences();
|
|
347
|
+
const config = resolveParallelConfig(loaded?.preferences);
|
|
348
|
+
if (!config.enabled) {
|
|
349
|
+
pi.sendMessage({
|
|
350
|
+
customType: "gsd-parallel",
|
|
351
|
+
content: "Parallel mode is not enabled. Set `parallel.enabled: true` in your preferences.",
|
|
352
|
+
display: false,
|
|
353
|
+
});
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const candidates = await prepareParallelStart(projectRoot(), loaded?.preferences);
|
|
357
|
+
const report = formatEligibilityReport(candidates);
|
|
358
|
+
if (candidates.eligible.length === 0) {
|
|
359
|
+
pi.sendMessage({ customType: "gsd-parallel", content: report + "\n\nNo milestones are eligible for parallel execution.", display: false });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const result = await startParallel(
|
|
363
|
+
projectRoot(),
|
|
364
|
+
candidates.eligible.map(e => e.milestoneId),
|
|
365
|
+
loaded?.preferences,
|
|
366
|
+
);
|
|
367
|
+
const lines = [`Parallel orchestration started.`, `Workers: ${result.started.join(", ")}`];
|
|
368
|
+
if (result.errors.length > 0) {
|
|
369
|
+
lines.push(`Errors: ${result.errors.map(e => `${e.mid}: ${e.error}`).join("; ")}`);
|
|
370
|
+
}
|
|
371
|
+
pi.sendMessage({ customType: "gsd-parallel", content: report + "\n\n" + lines.join("\n"), display: false });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (subCmd === "status") {
|
|
376
|
+
if (!isParallelActive()) {
|
|
377
|
+
pi.sendMessage({ customType: "gsd-parallel", content: "No parallel orchestration is currently active.", display: false });
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const workers = getWorkerStatuses();
|
|
381
|
+
const lines = ["# Parallel Workers\n"];
|
|
382
|
+
for (const w of workers) {
|
|
383
|
+
lines.push(`- **${w.milestoneId}** (${w.title}) — ${w.state} — ${w.completedUnits} units — $${w.cost.toFixed(2)}`);
|
|
384
|
+
}
|
|
385
|
+
const orchState = getOrchestratorState();
|
|
386
|
+
if (orchState) {
|
|
387
|
+
lines.push(`\nTotal cost: $${orchState.totalCost.toFixed(2)}`);
|
|
388
|
+
}
|
|
389
|
+
pi.sendMessage({ customType: "gsd-parallel", content: lines.join("\n"), display: false });
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (subCmd === "stop") {
|
|
394
|
+
const mid = rest.trim() || undefined;
|
|
395
|
+
await stopParallel(projectRoot(), mid);
|
|
396
|
+
pi.sendMessage({ customType: "gsd-parallel", content: mid ? `Stopped worker for ${mid}.` : "All parallel workers stopped.", display: false });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (subCmd === "pause") {
|
|
401
|
+
const mid = rest.trim() || undefined;
|
|
402
|
+
pauseWorker(projectRoot(), mid);
|
|
403
|
+
pi.sendMessage({ customType: "gsd-parallel", content: mid ? `Paused worker for ${mid}.` : "All parallel workers paused.", display: false });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (subCmd === "resume") {
|
|
408
|
+
const mid = rest.trim() || undefined;
|
|
409
|
+
resumeWorker(projectRoot(), mid);
|
|
410
|
+
pi.sendMessage({ customType: "gsd-parallel", content: mid ? `Resumed worker for ${mid}.` : "All parallel workers resumed.", display: false });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (subCmd === "merge") {
|
|
415
|
+
const mid = rest.trim() || undefined;
|
|
416
|
+
if (mid) {
|
|
417
|
+
// Merge a specific milestone
|
|
418
|
+
const result = await mergeCompletedMilestone(projectRoot(), mid);
|
|
419
|
+
pi.sendMessage({ customType: "gsd-parallel", content: formatMergeResults([result]), display: false });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
// Merge all completed milestones
|
|
423
|
+
const workers = getWorkerStatuses();
|
|
424
|
+
if (workers.length === 0) {
|
|
425
|
+
pi.sendMessage({ customType: "gsd-parallel", content: "No parallel workers to merge.", display: false });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const results = await mergeAllCompleted(projectRoot(), workers);
|
|
429
|
+
pi.sendMessage({ customType: "gsd-parallel", content: formatMergeResults(results), display: false });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
pi.sendMessage({
|
|
434
|
+
customType: "gsd-parallel",
|
|
435
|
+
content: `Unknown parallel subcommand "${subCmd}". Usage: /gsd parallel [start|status|stop|pause|resume|merge]`,
|
|
436
|
+
display: false,
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
291
441
|
if (trimmed === "cleanup") {
|
|
292
442
|
await handleCleanupBranches(ctx, projectRoot());
|
|
293
443
|
await handleCleanupSnapshots(ctx, projectRoot());
|
|
@@ -314,6 +464,21 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
314
464
|
return;
|
|
315
465
|
}
|
|
316
466
|
|
|
467
|
+
if (trimmed === "new-milestone") {
|
|
468
|
+
const basePath = projectRoot();
|
|
469
|
+
const headlessContextPath = join(basePath, ".gsd", "runtime", "headless-context.md");
|
|
470
|
+
if (existsSync(headlessContextPath)) {
|
|
471
|
+
const seedContext = readFileSync(headlessContextPath, "utf-8");
|
|
472
|
+
try { unlinkSync(headlessContextPath); } catch { /* non-fatal */ }
|
|
473
|
+
await showHeadlessMilestoneCreation(ctx, pi, basePath, seedContext);
|
|
474
|
+
} else {
|
|
475
|
+
// No headless context — fall back to interactive smart entry
|
|
476
|
+
const { showSmartEntry } = await import("./guided-flow.js");
|
|
477
|
+
await showSmartEntry(ctx, pi, basePath);
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
317
482
|
if (trimmed.startsWith("capture ") || trimmed === "capture") {
|
|
318
483
|
await handleCapture(trimmed.replace(/^capture\s*/, "").trim(), ctx);
|
|
319
484
|
return;
|
|
@@ -434,6 +599,7 @@ function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
434
599
|
" /gsd stop Stop auto-mode gracefully",
|
|
435
600
|
" /gsd pause Pause auto-mode (preserves state, /gsd auto to resume)",
|
|
436
601
|
" /gsd discuss Start guided milestone/slice discussion",
|
|
602
|
+
" /gsd new-milestone Create milestone from headless context (used by gsd headless)",
|
|
437
603
|
"",
|
|
438
604
|
"VISIBILITY",
|
|
439
605
|
" /gsd status Show progress dashboard (Ctrl+Alt+G)",
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "./metrics.js";
|
|
20
20
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
21
21
|
import { getActiveWorktreeName } from "./worktree-command.js";
|
|
22
|
+
import { getWorkerBatches, hasActiveWorkers, type WorkerEntry } from "../subagent/worker-registry.js";
|
|
22
23
|
|
|
23
24
|
function formatDuration(ms: number): string {
|
|
24
25
|
const s = Math.floor(ms / 1000);
|
|
@@ -363,6 +364,43 @@ export class GSDDashboardOverlay {
|
|
|
363
364
|
lines.push(blank());
|
|
364
365
|
}
|
|
365
366
|
|
|
367
|
+
// Parallel workers section — shows active subagent sessions
|
|
368
|
+
if (hasActiveWorkers()) {
|
|
369
|
+
lines.push(hr());
|
|
370
|
+
lines.push(row(th.fg("text", th.bold("Parallel Workers"))));
|
|
371
|
+
lines.push(blank());
|
|
372
|
+
|
|
373
|
+
const batches = getWorkerBatches();
|
|
374
|
+
for (const [batchId, workers] of batches) {
|
|
375
|
+
const running = workers.filter(w => w.status === "running").length;
|
|
376
|
+
const done = workers.filter(w => w.status === "completed").length;
|
|
377
|
+
const failed = workers.filter(w => w.status === "failed").length;
|
|
378
|
+
const total = workers[0]?.batchSize ?? workers.length;
|
|
379
|
+
|
|
380
|
+
lines.push(row(joinColumns(
|
|
381
|
+
` ${th.fg("accent", "⟐")} ${th.fg("text", `Batch ${batchId.slice(0, 8)}`)}`,
|
|
382
|
+
th.fg("dim", `${done + failed}/${total} done`),
|
|
383
|
+
contentWidth,
|
|
384
|
+
)));
|
|
385
|
+
|
|
386
|
+
for (const w of workers) {
|
|
387
|
+
const icon = w.status === "running"
|
|
388
|
+
? th.fg("accent", "▸")
|
|
389
|
+
: w.status === "completed"
|
|
390
|
+
? th.fg("success", "✓")
|
|
391
|
+
: th.fg("error", "✗");
|
|
392
|
+
const elapsed = th.fg("dim", formatDuration(Date.now() - w.startedAt));
|
|
393
|
+
const taskPreview = truncateToWidth(w.task, Math.max(20, contentWidth - 30));
|
|
394
|
+
lines.push(row(joinColumns(
|
|
395
|
+
` ${icon} ${th.fg("text", w.agent)} ${th.fg("dim", taskPreview)}`,
|
|
396
|
+
elapsed,
|
|
397
|
+
contentWidth,
|
|
398
|
+
)));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
lines.push(blank());
|
|
402
|
+
}
|
|
403
|
+
|
|
366
404
|
// Pending captures badge — only shown when captures are waiting for triage
|
|
367
405
|
if (this.dashData.pendingCaptureCount > 0) {
|
|
368
406
|
const count = this.dashData.pendingCaptureCount;
|
|
@@ -4,6 +4,7 @@ import { join, sep } from "node:path";
|
|
|
4
4
|
import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
5
5
|
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
|
|
6
6
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
7
|
+
import { invalidateAllCaches } from "./cache.js";
|
|
7
8
|
import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
|
|
8
9
|
import { listWorktrees, resolveGitDir } from "./worktree-manager.js";
|
|
9
10
|
import { abortAndReset } from "./git-self-heal.js";
|
|
@@ -11,6 +12,7 @@ import { RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
|
11
12
|
import { nativeIsRepo, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached } from "./native-git-bridge.js";
|
|
12
13
|
import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
|
|
13
14
|
import { ensureGitignore } from "./gitignore.js";
|
|
15
|
+
import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
|
|
14
16
|
|
|
15
17
|
export type DoctorSeverity = "info" | "warning" | "error";
|
|
16
18
|
export type DoctorIssueCode =
|
|
@@ -24,6 +26,7 @@ export type DoctorIssueCode =
|
|
|
24
26
|
| "all_tasks_done_roadmap_not_checked"
|
|
25
27
|
| "slice_checked_missing_summary"
|
|
26
28
|
| "slice_checked_missing_uat"
|
|
29
|
+
| "all_slices_done_missing_milestone_validation"
|
|
27
30
|
| "all_slices_done_missing_milestone_summary"
|
|
28
31
|
| "task_done_must_haves_not_verified"
|
|
29
32
|
| "active_requirement_missing_owner"
|
|
@@ -36,6 +39,7 @@ export type DoctorIssueCode =
|
|
|
36
39
|
| "tracked_runtime_files"
|
|
37
40
|
| "legacy_slice_branches"
|
|
38
41
|
| "stale_crash_lock"
|
|
42
|
+
| "stale_parallel_session"
|
|
39
43
|
| "orphaned_completed_units"
|
|
40
44
|
| "stale_hook_state"
|
|
41
45
|
| "activity_log_bloat"
|
|
@@ -197,6 +201,7 @@ async function updateStateFile(basePath: string, fixesApplied: string[]): Promis
|
|
|
197
201
|
|
|
198
202
|
/** Rebuild STATE.md from current disk state. Exported for auto-mode post-hooks. */
|
|
199
203
|
export async function rebuildState(basePath: string): Promise<void> {
|
|
204
|
+
invalidateAllCaches();
|
|
200
205
|
const state = await deriveState(basePath);
|
|
201
206
|
const path = resolveGsdRootFile(basePath, "STATE");
|
|
202
207
|
await saveFile(path, buildStateMarkdown(state));
|
|
@@ -710,6 +715,31 @@ async function checkRuntimeHealth(
|
|
|
710
715
|
// Non-fatal — crash lock check failed
|
|
711
716
|
}
|
|
712
717
|
|
|
718
|
+
// ── Stale parallel sessions ────────────────────────────────────────────
|
|
719
|
+
try {
|
|
720
|
+
const parallelStatuses = readAllSessionStatuses(basePath);
|
|
721
|
+
for (const status of parallelStatuses) {
|
|
722
|
+
if (isSessionStale(status)) {
|
|
723
|
+
issues.push({
|
|
724
|
+
severity: "warning",
|
|
725
|
+
code: "stale_parallel_session",
|
|
726
|
+
scope: "project",
|
|
727
|
+
unitId: status.milestoneId,
|
|
728
|
+
message: `Stale parallel session for ${status.milestoneId} (PID ${status.pid}, started ${new Date(status.startedAt).toISOString()}, last heartbeat ${new Date(status.lastHeartbeat).toISOString()}) — process is no longer running`,
|
|
729
|
+
file: `.gsd/parallel/${status.milestoneId}.status.json`,
|
|
730
|
+
fixable: true,
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
if (shouldFix("stale_parallel_session")) {
|
|
734
|
+
removeSessionStatus(basePath, status.milestoneId);
|
|
735
|
+
fixesApplied.push(`cleaned up stale parallel session for ${status.milestoneId}`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
} catch {
|
|
740
|
+
// Non-fatal — parallel session check failed
|
|
741
|
+
}
|
|
742
|
+
|
|
713
743
|
// ── Orphaned completed-units keys ─────────────────────────────────────
|
|
714
744
|
try {
|
|
715
745
|
const completedKeysFile = join(root, "completed-units.json");
|
|
@@ -1066,11 +1096,13 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
1066
1096
|
const tasksDir = resolveTasksDir(basePath, milestoneId, slice.id);
|
|
1067
1097
|
if (!tasksDir) {
|
|
1068
1098
|
issues.push({
|
|
1069
|
-
severity: "error",
|
|
1099
|
+
severity: slice.done ? "warning" : "error",
|
|
1070
1100
|
code: "missing_tasks_dir",
|
|
1071
1101
|
scope: "slice",
|
|
1072
1102
|
unitId,
|
|
1073
|
-
message:
|
|
1103
|
+
message: slice.done
|
|
1104
|
+
? `Missing tasks directory for ${unitId} (slice is complete — cosmetic only)`
|
|
1105
|
+
: `Missing tasks directory for ${unitId}`,
|
|
1074
1106
|
file: relSlicePath(basePath, milestoneId, slice.id),
|
|
1075
1107
|
fixable: true,
|
|
1076
1108
|
});
|
|
@@ -1084,15 +1116,17 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
1084
1116
|
const planContent = planPath ? await loadFile(planPath) : null;
|
|
1085
1117
|
const plan = planContent ? parsePlan(planContent) : null;
|
|
1086
1118
|
if (!plan) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1119
|
+
if (!slice.done) {
|
|
1120
|
+
issues.push({
|
|
1121
|
+
severity: "warning",
|
|
1122
|
+
code: "missing_slice_plan",
|
|
1123
|
+
scope: "slice",
|
|
1124
|
+
unitId,
|
|
1125
|
+
message: `Slice ${unitId} has no plan file`,
|
|
1126
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"),
|
|
1127
|
+
fixable: false,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1096
1130
|
continue;
|
|
1097
1131
|
}
|
|
1098
1132
|
|
|
@@ -1255,6 +1289,19 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
1255
1289
|
}
|
|
1256
1290
|
}
|
|
1257
1291
|
|
|
1292
|
+
// Milestone-level check: all slices done but no validation file
|
|
1293
|
+
if (isMilestoneComplete(roadmap) && !resolveMilestoneFile(basePath, milestoneId, "VALIDATION") && !resolveMilestoneFile(basePath, milestoneId, "SUMMARY")) {
|
|
1294
|
+
issues.push({
|
|
1295
|
+
severity: "info",
|
|
1296
|
+
code: "all_slices_done_missing_milestone_validation",
|
|
1297
|
+
scope: "milestone",
|
|
1298
|
+
unitId: milestoneId,
|
|
1299
|
+
message: `All slices are done but ${milestoneId}-VALIDATION.md is missing — milestone is in validating-milestone phase`,
|
|
1300
|
+
file: relMilestoneFile(basePath, milestoneId, "VALIDATION"),
|
|
1301
|
+
fixable: false,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1258
1305
|
// Milestone-level check: all slices done but no milestone summary
|
|
1259
1306
|
if (isMilestoneComplete(roadmap) && !resolveMilestoneFile(basePath, milestoneId, "SUMMARY")) {
|
|
1260
1307
|
issues.push({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI) => Promise<void>;
|
|
3
|
+
type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI, reason?: string) => Promise<void>;
|
|
4
4
|
|
|
5
5
|
export function registerExitCommand(
|
|
6
6
|
pi: ExtensionAPI,
|
|
@@ -11,7 +11,7 @@ export function registerExitCommand(
|
|
|
11
11
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
12
12
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
13
13
|
const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
|
|
14
|
-
await stopAuto(ctx, pi);
|
|
14
|
+
await stopAuto(ctx, pi, "Graceful exit");
|
|
15
15
|
ctx.shutdown();
|
|
16
16
|
},
|
|
17
17
|
});
|
|
@@ -68,6 +68,50 @@ export interface CommitOptions {
|
|
|
68
68
|
allowEmpty?: boolean;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// ─── Meaningful Commit Message Generation ───────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/** Context for generating a meaningful commit message from task execution results. */
|
|
74
|
+
export interface TaskCommitContext {
|
|
75
|
+
taskId: string;
|
|
76
|
+
taskTitle: string;
|
|
77
|
+
/** The one-liner from the task summary (e.g. "Added retry-aware worker status logging") */
|
|
78
|
+
oneLiner?: string;
|
|
79
|
+
/** Files modified by this task (from task summary frontmatter) */
|
|
80
|
+
keyFiles?: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build a meaningful conventional commit message from task execution context.
|
|
85
|
+
* Format: `{type}({sliceId}/{taskId}): {description}`
|
|
86
|
+
*
|
|
87
|
+
* The description is the task summary one-liner if available (it describes
|
|
88
|
+
* what was actually built), falling back to the task title (what was planned).
|
|
89
|
+
*/
|
|
90
|
+
export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
|
|
91
|
+
const scope = ctx.taskId; // e.g. "S01/T02" or just "T02"
|
|
92
|
+
const description = ctx.oneLiner || ctx.taskTitle;
|
|
93
|
+
const type = inferCommitType(ctx.taskTitle, ctx.oneLiner);
|
|
94
|
+
|
|
95
|
+
// Truncate description to ~72 chars for subject line
|
|
96
|
+
const maxDescLen = 68 - type.length - scope.length;
|
|
97
|
+
const truncated = description.length > maxDescLen
|
|
98
|
+
? description.slice(0, maxDescLen - 1).trimEnd() + "…"
|
|
99
|
+
: description;
|
|
100
|
+
|
|
101
|
+
const subject = `${type}(${scope}): ${truncated}`;
|
|
102
|
+
|
|
103
|
+
// Build body with key files if available
|
|
104
|
+
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
105
|
+
const fileLines = ctx.keyFiles
|
|
106
|
+
.slice(0, 8) // cap at 8 files to keep commit concise
|
|
107
|
+
.map(f => `- ${f}`)
|
|
108
|
+
.join("\n");
|
|
109
|
+
return `${subject}\n\n${fileLines}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return subject;
|
|
113
|
+
}
|
|
114
|
+
|
|
71
115
|
/**
|
|
72
116
|
* Thrown when a slice merge hits code conflicts in non-.gsd files.
|
|
73
117
|
* The working tree is left in a conflicted state (no reset) so the
|
|
@@ -253,18 +297,14 @@ export function runGit(basePath: string, args: string[], options: { allowFailure
|
|
|
253
297
|
* Each entry: [keywords[], commitType]
|
|
254
298
|
*/
|
|
255
299
|
const COMMIT_TYPE_RULES: [string[], string][] = [
|
|
256
|
-
[["fix", "bug", "patch", "hotfix"], "fix"],
|
|
300
|
+
[["fix", "fixed", "fixes", "bug", "patch", "hotfix", "repair", "correct"], "fix"],
|
|
257
301
|
[["refactor", "restructure", "reorganize"], "refactor"],
|
|
258
|
-
[["doc", "docs", "documentation"], "docs"],
|
|
259
|
-
[["test", "tests", "testing"], "test"],
|
|
260
|
-
[["
|
|
302
|
+
[["doc", "docs", "documentation", "readme", "changelog"], "docs"],
|
|
303
|
+
[["test", "tests", "testing", "spec", "coverage"], "test"],
|
|
304
|
+
[["perf", "performance", "optimize", "speed", "cache"], "perf"],
|
|
305
|
+
[["chore", "cleanup", "clean up", "dependencies", "deps", "bump", "config", "ci", "archive", "remove", "delete"], "chore"],
|
|
261
306
|
];
|
|
262
307
|
|
|
263
|
-
/**
|
|
264
|
-
* Infer a conventional commit type from a slice title.
|
|
265
|
-
* Uses case-insensitive word-boundary matching against known keywords.
|
|
266
|
-
* Returns "feat" when no keywords match.
|
|
267
|
-
*/
|
|
268
308
|
// ─── GitServiceImpl ────────────────────────────────────────────────────
|
|
269
309
|
|
|
270
310
|
export class GitServiceImpl {
|
|
@@ -356,11 +396,22 @@ export class GitServiceImpl {
|
|
|
356
396
|
}
|
|
357
397
|
|
|
358
398
|
/**
|
|
359
|
-
* Auto-commit dirty working tree
|
|
399
|
+
* Auto-commit dirty working tree.
|
|
400
|
+
*
|
|
401
|
+
* When `taskContext` is provided, generates a meaningful conventional commit
|
|
402
|
+
* message from the task execution results (one-liner, title, inferred type).
|
|
403
|
+
* Falls back to a generic `chore()` message when no context is available
|
|
404
|
+
* (e.g. pre-switch commits, stop commits, state rebuild commits).
|
|
405
|
+
*
|
|
360
406
|
* Returns the commit message on success, or null if nothing to commit.
|
|
361
407
|
* @param extraExclusions Additional paths to exclude from staging (e.g. [".gsd/"] for pre-switch commits).
|
|
362
408
|
*/
|
|
363
|
-
autoCommit(
|
|
409
|
+
autoCommit(
|
|
410
|
+
unitType: string,
|
|
411
|
+
unitId: string,
|
|
412
|
+
extraExclusions: readonly string[] = [],
|
|
413
|
+
taskContext?: TaskCommitContext,
|
|
414
|
+
): string | null {
|
|
364
415
|
// Quick check: is there anything dirty at all?
|
|
365
416
|
// Native path uses libgit2 (single syscall), fallback spawns git.
|
|
366
417
|
if (!nativeHasChanges(this.basePath)) return null;
|
|
@@ -371,7 +422,9 @@ export class GitServiceImpl {
|
|
|
371
422
|
// (all changes might have been runtime files that got excluded)
|
|
372
423
|
if (!nativeHasStagedChanges(this.basePath)) return null;
|
|
373
424
|
|
|
374
|
-
const message =
|
|
425
|
+
const message = taskContext
|
|
426
|
+
? buildTaskCommitMessage(taskContext)
|
|
427
|
+
: `chore(${unitId}): auto-commit after ${unitType}`;
|
|
375
428
|
nativeCommit(this.basePath, message, { allowEmpty: false });
|
|
376
429
|
return message;
|
|
377
430
|
}
|
|
@@ -497,8 +550,15 @@ export class GitServiceImpl {
|
|
|
497
550
|
|
|
498
551
|
// ─── Commit Type Inference ─────────────────────────────────────────────────
|
|
499
552
|
|
|
500
|
-
|
|
501
|
-
|
|
553
|
+
/**
|
|
554
|
+
* Infer a conventional commit type from a title (and optional one-liner).
|
|
555
|
+
* Uses case-insensitive word-boundary matching against known keywords.
|
|
556
|
+
* Returns "feat" when no keywords match.
|
|
557
|
+
*
|
|
558
|
+
* Used for both slice squash-merge titles and task commit messages.
|
|
559
|
+
*/
|
|
560
|
+
export function inferCommitType(title: string, oneLiner?: string): string {
|
|
561
|
+
const lower = `${title} ${oneLiner || ""}`.toLowerCase();
|
|
502
562
|
|
|
503
563
|
for (const [keywords, commitType] of COMMIT_TYPE_RULES) {
|
|
504
564
|
for (const keyword of keywords) {
|