gsd-pi 2.22.0 → 2.24.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 +25 -1
- package/dist/cli.js +74 -7
- package/dist/headless.d.ts +25 -0
- package/dist/headless.js +454 -0
- package/dist/help-text.js +47 -0
- package/dist/mcp-server.d.ts +20 -3
- package/dist/mcp-server.js +21 -1
- package/dist/models-resolver.d.ts +32 -0
- package/dist/models-resolver.js +50 -0
- package/dist/resource-loader.js +64 -9
- package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/dist/resources/extensions/bg-shell/types.ts +33 -1
- package/dist/resources/extensions/browser-tools/capture.ts +18 -16
- package/dist/resources/extensions/browser-tools/index.ts +20 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/dist/resources/extensions/gsd/auto.ts +560 -52
- package/dist/resources/extensions/gsd/captures.ts +49 -0
- package/dist/resources/extensions/gsd/commands.ts +194 -11
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/dist/resources/extensions/gsd/diff-context.ts +73 -80
- package/dist/resources/extensions/gsd/doctor.ts +76 -12
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/forensics.ts +95 -52
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
- package/dist/resources/extensions/gsd/index.ts +34 -1
- package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
- 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/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/dist/resources/extensions/gsd/session-forensics.ts +36 -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/templates/milestone-validation.md +62 -0
- 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-lock-creation.test.ts +186 -0
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -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/doctor.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -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/roadmap-slices.test.ts +43 -1
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
- 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-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +16 -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/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/src/providers/anthropic.ts +21 -8
- 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/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- 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/core/tools/bash-background.test.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.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/core/tools/bash-background.test.ts +91 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
- package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/scripts/postinstall.js +7 -109
- package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/src/resources/extensions/bg-shell/types.ts +33 -1
- package/src/resources/extensions/browser-tools/capture.ts +18 -16
- package/src/resources/extensions/browser-tools/index.ts +20 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/src/resources/extensions/gsd/auto.ts +560 -52
- package/src/resources/extensions/gsd/captures.ts +49 -0
- package/src/resources/extensions/gsd/commands.ts +194 -11
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/src/resources/extensions/gsd/diff-context.ts +73 -80
- package/src/resources/extensions/gsd/doctor.ts +76 -12
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/forensics.ts +95 -52
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +85 -5
- package/src/resources/extensions/gsd/index.ts +34 -1
- package/src/resources/extensions/gsd/mcp-server.ts +33 -12
- 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/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/src/resources/extensions/gsd/session-forensics.ts +36 -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/templates/milestone-validation.md +62 -0
- 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-lock-creation.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -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/doctor.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -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/roadmap-slices.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/src/resources/extensions/gsd/workspace-index.ts +34 -6
- package/src/resources/extensions/subagent/index.ts +5 -0
- package/src/resources/extensions/subagent/worker-registry.ts +99 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Standalone module: only imports node:child_process and node:path.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { execFileSync, execFile } from "node:child_process";
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
|
|
12
12
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
@@ -32,10 +32,23 @@ const EXEC_OPTS = {
|
|
|
32
32
|
stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"],
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
/** Synchronous git — used where sequential control flow is required (fallback paths). */
|
|
36
|
+
function gitSync(args: string[], cwd: string): string {
|
|
36
37
|
return execFileSync("git", args, { ...EXEC_OPTS, cwd }).trim();
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/** Async git — returns stdout on success, empty string on any error. */
|
|
41
|
+
function gitAsync(args: string[], cwd: string): Promise<string> {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
execFile(
|
|
44
|
+
"git",
|
|
45
|
+
args,
|
|
46
|
+
{ encoding: "utf-8", timeout: 5000, cwd },
|
|
47
|
+
(err, stdout) => resolve(err ? "" : stdout.trim()),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
function splitLines(output: string): string[] {
|
|
40
53
|
return output
|
|
41
54
|
.split("\n")
|
|
@@ -49,6 +62,8 @@ function splitLines(output: string): string[] {
|
|
|
49
62
|
* Returns recently-changed file paths, deduplicated and sorted by recency
|
|
50
63
|
* (most recent first). Combines committed diffs, staged changes, and
|
|
51
64
|
* unstaged/untracked files from `git status`.
|
|
65
|
+
*
|
|
66
|
+
* The three git queries (log, diff --cached, status) run concurrently.
|
|
52
67
|
*/
|
|
53
68
|
export async function getRecentlyChangedFiles(
|
|
54
69
|
cwd: string,
|
|
@@ -59,40 +74,23 @@ export async function getRecentlyChangedFiles(
|
|
|
59
74
|
const dir = resolve(cwd);
|
|
60
75
|
|
|
61
76
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// 2. Staged changes
|
|
80
|
-
let stagedFiles: string[] = [];
|
|
81
|
-
try {
|
|
82
|
-
const raw = git(["diff", "--cached", "--name-only"], dir);
|
|
83
|
-
stagedFiles = splitLines(raw);
|
|
84
|
-
} catch {
|
|
85
|
-
// ignore
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 3. Unstaged / untracked via porcelain status
|
|
89
|
-
let statusFiles: string[] = [];
|
|
90
|
-
try {
|
|
91
|
-
const raw = git(["status", "--porcelain"], dir);
|
|
92
|
-
statusFiles = splitLines(raw).map((line) => line.slice(3)); // strip XY + space
|
|
93
|
-
} catch {
|
|
94
|
-
// ignore
|
|
95
|
-
}
|
|
77
|
+
const days = Math.max(1, Math.floor(Number(sinceDays)));
|
|
78
|
+
if (!Number.isFinite(days)) throw new Error("invalid sinceDays");
|
|
79
|
+
|
|
80
|
+
// Run all three queries concurrently — they read independent git state
|
|
81
|
+
const [logRaw, stagedRaw, statusRaw] = await Promise.all([
|
|
82
|
+
// 1. Committed changes since N days ago (fallback to HEAD~10 on error)
|
|
83
|
+
gitAsync(["log", "--diff-filter=ACMR", "--name-only", "--pretty=format:", `--since=${days} days ago`], dir)
|
|
84
|
+
.then((out) => out || gitAsync(["diff", "--name-only", "HEAD~10"], dir)),
|
|
85
|
+
// 2. Staged changes
|
|
86
|
+
gitAsync(["diff", "--cached", "--name-only"], dir),
|
|
87
|
+
// 3. Unstaged / untracked
|
|
88
|
+
gitAsync(["status", "--porcelain"], dir),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const committedFiles = splitLines(logRaw);
|
|
92
|
+
const stagedFiles = splitLines(stagedRaw);
|
|
93
|
+
const statusFiles = splitLines(statusRaw).map((line) => line.slice(3)); // strip XY + space
|
|
96
94
|
|
|
97
95
|
// Deduplicate, preserving insertion order (most-recent-first: status → staged → committed)
|
|
98
96
|
const seen = new Set<string>();
|
|
@@ -113,6 +111,9 @@ export async function getRecentlyChangedFiles(
|
|
|
113
111
|
|
|
114
112
|
/**
|
|
115
113
|
* Returns richer change metadata: change type and approximate line counts.
|
|
114
|
+
*
|
|
115
|
+
* The three git queries (diff --cached --numstat, diff --numstat, status --porcelain)
|
|
116
|
+
* run concurrently — they read independent git state.
|
|
116
117
|
*/
|
|
117
118
|
export async function getChangedFilesWithContext(
|
|
118
119
|
cwd: string,
|
|
@@ -120,6 +121,13 @@ export async function getChangedFilesWithContext(
|
|
|
120
121
|
const dir = resolve(cwd);
|
|
121
122
|
|
|
122
123
|
try {
|
|
124
|
+
// Run all three queries concurrently
|
|
125
|
+
const [cachedNumstat, unstagedNumstat, statusRaw] = await Promise.all([
|
|
126
|
+
gitAsync(["diff", "--cached", "--numstat"], dir),
|
|
127
|
+
gitAsync(["diff", "--numstat"], dir),
|
|
128
|
+
gitAsync(["status", "--porcelain"], dir),
|
|
129
|
+
]);
|
|
130
|
+
|
|
123
131
|
const result: ChangedFileInfo[] = [];
|
|
124
132
|
const seen = new Set<string>();
|
|
125
133
|
|
|
@@ -131,57 +139,42 @@ export async function getChangedFilesWithContext(
|
|
|
131
139
|
};
|
|
132
140
|
|
|
133
141
|
// 1. Staged files with numstat
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
added
|
|
141
|
-
|
|
142
|
-
: Number(added) + Number(deleted);
|
|
143
|
-
add({ path: filePath, changeType: "staged", linesChanged: lines });
|
|
144
|
-
}
|
|
145
|
-
} catch {
|
|
146
|
-
// ignore
|
|
142
|
+
for (const line of splitLines(cachedNumstat)) {
|
|
143
|
+
const [added, deleted, filePath] = line.split("\t");
|
|
144
|
+
if (!filePath) continue;
|
|
145
|
+
const lines =
|
|
146
|
+
added === "-" || deleted === "-"
|
|
147
|
+
? undefined
|
|
148
|
+
: Number(added) + Number(deleted);
|
|
149
|
+
add({ path: filePath, changeType: "staged", linesChanged: lines });
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
// 2. Unstaged modifications with numstat
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
added
|
|
157
|
-
|
|
158
|
-
: Number(added) + Number(deleted);
|
|
159
|
-
add({ path: filePath, changeType: "modified", linesChanged: lines });
|
|
160
|
-
}
|
|
161
|
-
} catch {
|
|
162
|
-
// ignore
|
|
153
|
+
for (const line of splitLines(unstagedNumstat)) {
|
|
154
|
+
const [added, deleted, filePath] = line.split("\t");
|
|
155
|
+
if (!filePath) continue;
|
|
156
|
+
const lines =
|
|
157
|
+
added === "-" || deleted === "-"
|
|
158
|
+
? undefined
|
|
159
|
+
: Number(added) + Number(deleted);
|
|
160
|
+
add({ path: filePath, changeType: "modified", linesChanged: lines });
|
|
163
161
|
}
|
|
164
162
|
|
|
165
163
|
// 3. Untracked / deleted from porcelain status
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} else {
|
|
180
|
-
add({ path: filePath, changeType: "modified" });
|
|
181
|
-
}
|
|
164
|
+
for (const line of splitLines(statusRaw)) {
|
|
165
|
+
const code = line.slice(0, 2);
|
|
166
|
+
const filePath = line.slice(3);
|
|
167
|
+
if (seen.has(filePath)) continue;
|
|
168
|
+
|
|
169
|
+
if (code.includes("?")) {
|
|
170
|
+
add({ path: filePath, changeType: "added" });
|
|
171
|
+
} else if (code.includes("D")) {
|
|
172
|
+
add({ path: filePath, changeType: "deleted" });
|
|
173
|
+
} else if (code.includes("A")) {
|
|
174
|
+
add({ path: filePath, changeType: "added" });
|
|
175
|
+
} else {
|
|
176
|
+
add({ path: filePath, changeType: "modified" });
|
|
182
177
|
}
|
|
183
|
-
} catch {
|
|
184
|
-
// ignore
|
|
185
178
|
}
|
|
186
179
|
|
|
187
180
|
return result;
|
|
@@ -11,6 +11,7 @@ import { RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
|
11
11
|
import { nativeIsRepo, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached } from "./native-git-bridge.js";
|
|
12
12
|
import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
|
|
13
13
|
import { ensureGitignore } from "./gitignore.js";
|
|
14
|
+
import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
|
|
14
15
|
|
|
15
16
|
export type DoctorSeverity = "info" | "warning" | "error";
|
|
16
17
|
export type DoctorIssueCode =
|
|
@@ -24,6 +25,7 @@ export type DoctorIssueCode =
|
|
|
24
25
|
| "all_tasks_done_roadmap_not_checked"
|
|
25
26
|
| "slice_checked_missing_summary"
|
|
26
27
|
| "slice_checked_missing_uat"
|
|
28
|
+
| "all_slices_done_missing_milestone_validation"
|
|
27
29
|
| "all_slices_done_missing_milestone_summary"
|
|
28
30
|
| "task_done_must_haves_not_verified"
|
|
29
31
|
| "active_requirement_missing_owner"
|
|
@@ -36,12 +38,14 @@ export type DoctorIssueCode =
|
|
|
36
38
|
| "tracked_runtime_files"
|
|
37
39
|
| "legacy_slice_branches"
|
|
38
40
|
| "stale_crash_lock"
|
|
41
|
+
| "stale_parallel_session"
|
|
39
42
|
| "orphaned_completed_units"
|
|
40
43
|
| "stale_hook_state"
|
|
41
44
|
| "activity_log_bloat"
|
|
42
45
|
| "state_file_stale"
|
|
43
46
|
| "state_file_missing"
|
|
44
|
-
| "gitignore_missing_patterns"
|
|
47
|
+
| "gitignore_missing_patterns"
|
|
48
|
+
| "unresolvable_dependency";
|
|
45
49
|
|
|
46
50
|
export interface DoctorIssue {
|
|
47
51
|
severity: DoctorSeverity;
|
|
@@ -709,6 +713,31 @@ async function checkRuntimeHealth(
|
|
|
709
713
|
// Non-fatal — crash lock check failed
|
|
710
714
|
}
|
|
711
715
|
|
|
716
|
+
// ── Stale parallel sessions ────────────────────────────────────────────
|
|
717
|
+
try {
|
|
718
|
+
const parallelStatuses = readAllSessionStatuses(basePath);
|
|
719
|
+
for (const status of parallelStatuses) {
|
|
720
|
+
if (isSessionStale(status)) {
|
|
721
|
+
issues.push({
|
|
722
|
+
severity: "warning",
|
|
723
|
+
code: "stale_parallel_session",
|
|
724
|
+
scope: "project",
|
|
725
|
+
unitId: status.milestoneId,
|
|
726
|
+
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`,
|
|
727
|
+
file: `.gsd/parallel/${status.milestoneId}.status.json`,
|
|
728
|
+
fixable: true,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
if (shouldFix("stale_parallel_session")) {
|
|
732
|
+
removeSessionStatus(basePath, status.milestoneId);
|
|
733
|
+
fixesApplied.push(`cleaned up stale parallel session for ${status.milestoneId}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
} catch {
|
|
738
|
+
// Non-fatal — parallel session check failed
|
|
739
|
+
}
|
|
740
|
+
|
|
712
741
|
// ── Orphaned completed-units keys ─────────────────────────────────────
|
|
713
742
|
try {
|
|
714
743
|
const completedKeysFile = join(root, "completed-units.json");
|
|
@@ -1041,17 +1070,37 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
1041
1070
|
});
|
|
1042
1071
|
}
|
|
1043
1072
|
|
|
1073
|
+
// Check for unresolvable dependency IDs — catches range syntax like "S01-S04"
|
|
1074
|
+
// that the parser expanded but that don't match any actual slice in the roadmap.
|
|
1075
|
+
// Also catches plain typos or IDs referencing slices not yet defined.
|
|
1076
|
+
const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
|
|
1077
|
+
for (const dep of slice.depends) {
|
|
1078
|
+
if (!knownSliceIds.has(dep)) {
|
|
1079
|
+
issues.push({
|
|
1080
|
+
severity: "warning",
|
|
1081
|
+
code: "unresolvable_dependency",
|
|
1082
|
+
scope: "slice",
|
|
1083
|
+
unitId,
|
|
1084
|
+
message: `Slice ${unitId} depends on "${dep}" which is not a slice ID in this roadmap. This permanently blocks the slice. Use comma-separated IDs: \`depends:[S01,S02]\``,
|
|
1085
|
+
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
1086
|
+
fixable: false,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1044
1091
|
const slicePath = resolveSlicePath(basePath, milestoneId, slice.id);
|
|
1045
1092
|
if (!slicePath) continue;
|
|
1046
1093
|
|
|
1047
1094
|
const tasksDir = resolveTasksDir(basePath, milestoneId, slice.id);
|
|
1048
1095
|
if (!tasksDir) {
|
|
1049
1096
|
issues.push({
|
|
1050
|
-
severity: "error",
|
|
1097
|
+
severity: slice.done ? "warning" : "error",
|
|
1051
1098
|
code: "missing_tasks_dir",
|
|
1052
1099
|
scope: "slice",
|
|
1053
1100
|
unitId,
|
|
1054
|
-
message:
|
|
1101
|
+
message: slice.done
|
|
1102
|
+
? `Missing tasks directory for ${unitId} (slice is complete — cosmetic only)`
|
|
1103
|
+
: `Missing tasks directory for ${unitId}`,
|
|
1055
1104
|
file: relSlicePath(basePath, milestoneId, slice.id),
|
|
1056
1105
|
fixable: true,
|
|
1057
1106
|
});
|
|
@@ -1065,15 +1114,17 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
1065
1114
|
const planContent = planPath ? await loadFile(planPath) : null;
|
|
1066
1115
|
const plan = planContent ? parsePlan(planContent) : null;
|
|
1067
1116
|
if (!plan) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1117
|
+
if (!slice.done) {
|
|
1118
|
+
issues.push({
|
|
1119
|
+
severity: "warning",
|
|
1120
|
+
code: "missing_slice_plan",
|
|
1121
|
+
scope: "slice",
|
|
1122
|
+
unitId,
|
|
1123
|
+
message: `Slice ${unitId} has no plan file`,
|
|
1124
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"),
|
|
1125
|
+
fixable: false,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1077
1128
|
continue;
|
|
1078
1129
|
}
|
|
1079
1130
|
|
|
@@ -1236,6 +1287,19 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
1236
1287
|
}
|
|
1237
1288
|
}
|
|
1238
1289
|
|
|
1290
|
+
// Milestone-level check: all slices done but no validation file
|
|
1291
|
+
if (isMilestoneComplete(roadmap) && !resolveMilestoneFile(basePath, milestoneId, "VALIDATION") && !resolveMilestoneFile(basePath, milestoneId, "SUMMARY")) {
|
|
1292
|
+
issues.push({
|
|
1293
|
+
severity: "info",
|
|
1294
|
+
code: "all_slices_done_missing_milestone_validation",
|
|
1295
|
+
scope: "milestone",
|
|
1296
|
+
unitId: milestoneId,
|
|
1297
|
+
message: `All slices are done but ${milestoneId}-VALIDATION.md is missing — milestone is in validating-milestone phase`,
|
|
1298
|
+
file: relMilestoneFile(basePath, milestoneId, "VALIDATION"),
|
|
1299
|
+
fixable: false,
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1239
1303
|
// Milestone-level check: all slices done but no milestone summary
|
|
1240
1304
|
if (isMilestoneComplete(roadmap) && !resolveMilestoneFile(basePath, milestoneId, "SUMMARY")) {
|
|
1241
1305
|
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
|
});
|
|
@@ -27,6 +27,7 @@ import { isAutoActive } from "./auto.js";
|
|
|
27
27
|
import { loadPrompt } from "./prompt-loader.js";
|
|
28
28
|
import { gsdRoot } from "./paths.js";
|
|
29
29
|
import { formatDuration } from "./history.js";
|
|
30
|
+
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
30
31
|
|
|
31
32
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
32
33
|
|
|
@@ -54,6 +55,7 @@ interface ForensicReport {
|
|
|
54
55
|
basePath: string;
|
|
55
56
|
activeMilestone: string | null;
|
|
56
57
|
activeSlice: string | null;
|
|
58
|
+
activeWorktree: string | null;
|
|
57
59
|
unitTraces: UnitTrace[];
|
|
58
60
|
metrics: MetricsLedger | null;
|
|
59
61
|
completedKeys: string[];
|
|
@@ -143,8 +145,11 @@ async function buildForensicReport(basePath: string): Promise<ForensicReport> {
|
|
|
143
145
|
activeSlice = state.activeSlice?.id ?? null;
|
|
144
146
|
} catch { /* state derivation failure is non-fatal */ }
|
|
145
147
|
|
|
146
|
-
//
|
|
147
|
-
const
|
|
148
|
+
// 1b. Check for active auto-worktree
|
|
149
|
+
const activeWorktree = activeMilestone ? getAutoWorktreePath(basePath, activeMilestone) : null;
|
|
150
|
+
|
|
151
|
+
// 2. Scan activity logs (last 5) — worktree-aware
|
|
152
|
+
const unitTraces = scanActivityLogs(basePath, activeMilestone);
|
|
148
153
|
|
|
149
154
|
// 3. Load metrics
|
|
150
155
|
const metrics = loadLedgerFromDisk(basePath);
|
|
@@ -178,20 +183,16 @@ async function buildForensicReport(basePath: string): Promise<ForensicReport> {
|
|
|
178
183
|
}
|
|
179
184
|
}
|
|
180
185
|
|
|
181
|
-
// 8. GSD version
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (existsSync(pkgPath)) {
|
|
186
|
-
gsdVersion = JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "unknown";
|
|
187
|
-
}
|
|
188
|
-
} catch { /* non-fatal */ }
|
|
186
|
+
// 8. GSD version — use GSD_VERSION env var set by the loader at startup.
|
|
187
|
+
// Extensions run from ~/.gsd/agent/extensions/gsd/ at runtime, so path-traversal
|
|
188
|
+
// from import.meta.url would resolve to ~/package.json (wrong on every system).
|
|
189
|
+
const gsdVersion = process.env.GSD_VERSION || "unknown";
|
|
189
190
|
|
|
190
191
|
// 9. Run anomaly detectors
|
|
191
192
|
if (metrics?.units) detectStuckLoops(metrics.units, anomalies);
|
|
192
193
|
if (metrics?.units) detectCostSpikes(metrics.units, anomalies);
|
|
193
194
|
detectTimeouts(unitTraces, anomalies);
|
|
194
|
-
detectMissingArtifacts(completedKeys, basePath, anomalies);
|
|
195
|
+
detectMissingArtifacts(completedKeys, basePath, activeMilestone, anomalies);
|
|
195
196
|
detectCrash(crashLock, anomalies);
|
|
196
197
|
detectDoctorIssues(doctorIssues, anomalies);
|
|
197
198
|
detectErrorTraces(unitTraces, anomalies);
|
|
@@ -202,6 +203,7 @@ async function buildForensicReport(basePath: string): Promise<ForensicReport> {
|
|
|
202
203
|
basePath,
|
|
203
204
|
activeMilestone,
|
|
204
205
|
activeSlice,
|
|
206
|
+
activeWorktree: activeWorktree ? relative(basePath, activeWorktree) : null,
|
|
205
207
|
unitTraces,
|
|
206
208
|
metrics,
|
|
207
209
|
completedKeys,
|
|
@@ -216,48 +218,78 @@ async function buildForensicReport(basePath: string): Promise<ForensicReport> {
|
|
|
216
218
|
|
|
217
219
|
const ACTIVITY_FILENAME_RE = /^(\d+)-(.+?)-(.+)\.jsonl$/;
|
|
218
220
|
|
|
219
|
-
function scanActivityLogs(basePath: string): UnitTrace[] {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
221
|
+
function scanActivityLogs(basePath: string, activeMilestone?: string | null): UnitTrace[] {
|
|
222
|
+
const activityDirs = resolveActivityDirs(basePath, activeMilestone);
|
|
223
|
+
const allTraces: UnitTrace[] = [];
|
|
224
|
+
|
|
225
|
+
for (const activityDir of activityDirs) {
|
|
226
|
+
if (!existsSync(activityDir)) continue;
|
|
227
|
+
|
|
228
|
+
const files = readdirSync(activityDir).filter(f => f.endsWith(".jsonl")).sort();
|
|
229
|
+
const lastFiles = files.slice(-5);
|
|
230
|
+
|
|
231
|
+
for (const file of lastFiles) {
|
|
232
|
+
const match = ACTIVITY_FILENAME_RE.exec(file);
|
|
233
|
+
if (!match) continue;
|
|
234
|
+
|
|
235
|
+
const seq = parseInt(match[1]!, 10);
|
|
236
|
+
const unitType = match[2]!;
|
|
237
|
+
const unitId = match[3]!;
|
|
238
|
+
const filePath = join(activityDir, file);
|
|
239
|
+
|
|
240
|
+
let entries: unknown[] = [];
|
|
241
|
+
const nativeResult = nativeParseJsonlTail(filePath, MAX_JSONL_BYTES);
|
|
242
|
+
if (nativeResult) {
|
|
243
|
+
entries = nativeResult.entries;
|
|
244
|
+
} else {
|
|
245
|
+
try {
|
|
246
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
247
|
+
entries = parseJSONL(raw);
|
|
248
|
+
} catch { continue; }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const trace = extractTrace(entries);
|
|
252
|
+
const stat = statSync(filePath, { throwIfNoEntry: false });
|
|
253
|
+
|
|
254
|
+
allTraces.push({
|
|
255
|
+
file: activityDirs.length > 1 ? `[${relative(basePath, activityDir)}] ${file}` : file,
|
|
256
|
+
unitType,
|
|
257
|
+
unitId,
|
|
258
|
+
seq,
|
|
259
|
+
trace,
|
|
260
|
+
mtime: stat?.mtimeMs ?? 0,
|
|
261
|
+
});
|
|
245
262
|
}
|
|
263
|
+
}
|
|
246
264
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
// Sort by mtime descending so the most recent traces (regardless of source) come first
|
|
266
|
+
return allTraces.sort((a, b) => b.mtime - a.mtime).slice(0, 5);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Resolve activity directories to scan for forensics.
|
|
271
|
+
* If an active auto-worktree exists for the milestone, its activity dir
|
|
272
|
+
* is included first (preferred) so stale root logs don't mask worktree progress.
|
|
273
|
+
*/
|
|
274
|
+
function resolveActivityDirs(basePath: string, activeMilestone?: string | null): string[] {
|
|
275
|
+
const dirs: string[] = [];
|
|
276
|
+
|
|
277
|
+
// Check for active auto-worktree activity logs
|
|
278
|
+
if (activeMilestone) {
|
|
279
|
+
const wtPath = getAutoWorktreePath(basePath, activeMilestone);
|
|
280
|
+
if (wtPath) {
|
|
281
|
+
const wtActivityDir = join(wtPath, ".gsd", "activity");
|
|
282
|
+
if (existsSync(wtActivityDir)) {
|
|
283
|
+
dirs.push(wtActivityDir);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
258
286
|
}
|
|
259
287
|
|
|
260
|
-
|
|
288
|
+
// Always include root activity logs
|
|
289
|
+
const rootActivityDir = join(gsdRoot(basePath), "activity");
|
|
290
|
+
dirs.push(rootActivityDir);
|
|
291
|
+
|
|
292
|
+
return dirs;
|
|
261
293
|
}
|
|
262
294
|
|
|
263
295
|
// ─── Completed Keys Loader ────────────────────────────────────────────────────
|
|
@@ -336,21 +368,27 @@ function detectTimeouts(traces: UnitTrace[], anomalies: ForensicAnomaly[]): void
|
|
|
336
368
|
}
|
|
337
369
|
}
|
|
338
370
|
|
|
339
|
-
function detectMissingArtifacts(completedKeys: string[], basePath: string, anomalies: ForensicAnomaly[]): void {
|
|
371
|
+
function detectMissingArtifacts(completedKeys: string[], basePath: string, activeMilestone: string | null, anomalies: ForensicAnomaly[]): void {
|
|
372
|
+
// Also check the worktree path for artifacts — they may exist there but not at root
|
|
373
|
+
const wtBasePath = activeMilestone ? getAutoWorktreePath(basePath, activeMilestone) : null;
|
|
374
|
+
|
|
340
375
|
for (const key of completedKeys) {
|
|
341
376
|
const slashIdx = key.indexOf("/");
|
|
342
377
|
if (slashIdx === -1) continue;
|
|
343
378
|
const unitType = key.slice(0, slashIdx);
|
|
344
379
|
const unitId = key.slice(slashIdx + 1);
|
|
345
380
|
|
|
346
|
-
|
|
381
|
+
const rootHasArtifact = verifyExpectedArtifact(unitType, unitId, basePath);
|
|
382
|
+
const wtHasArtifact = wtBasePath ? verifyExpectedArtifact(unitType, unitId, wtBasePath) : false;
|
|
383
|
+
|
|
384
|
+
if (!rootHasArtifact && !wtHasArtifact) {
|
|
347
385
|
anomalies.push({
|
|
348
386
|
type: "missing-artifact",
|
|
349
387
|
severity: "error",
|
|
350
388
|
unitType,
|
|
351
389
|
unitId,
|
|
352
390
|
summary: `Completed key ${key} but artifact missing or invalid`,
|
|
353
|
-
details: `The unit is recorded as completed but verifyExpectedArtifact() returns false. The completion state is stale.`,
|
|
391
|
+
details: `The unit is recorded as completed but verifyExpectedArtifact() returns false at both project root and worktree. The completion state is stale.`,
|
|
354
392
|
});
|
|
355
393
|
}
|
|
356
394
|
}
|
|
@@ -416,6 +454,7 @@ function saveForensicReport(basePath: string, report: ForensicReport, problemDes
|
|
|
416
454
|
`**GSD Version:** ${report.gsdVersion}`,
|
|
417
455
|
`**Active Milestone:** ${report.activeMilestone ?? "none"}`,
|
|
418
456
|
`**Active Slice:** ${report.activeSlice ?? "none"}`,
|
|
457
|
+
`**Active Worktree:** ${report.activeWorktree ?? "none"}`,
|
|
419
458
|
``,
|
|
420
459
|
`## Problem Description`,
|
|
421
460
|
``,
|
|
@@ -559,6 +598,10 @@ function formatReportForPrompt(report: ForensicReport): string {
|
|
|
559
598
|
sections.push(`### GSD Version: ${report.gsdVersion}`);
|
|
560
599
|
sections.push(`### Active Milestone: ${report.activeMilestone ?? "none"}`);
|
|
561
600
|
sections.push(`### Active Slice: ${report.activeSlice ?? "none"}`);
|
|
601
|
+
if (report.activeWorktree) {
|
|
602
|
+
sections.push(`### Active Worktree: ${report.activeWorktree}`);
|
|
603
|
+
sections.push(`Note: Activity logs were scanned from both the worktree and the project root. Worktree logs take priority.`);
|
|
604
|
+
}
|
|
562
605
|
|
|
563
606
|
let result = sections.join("\n");
|
|
564
607
|
if (result.length > MAX_BYTES) {
|