gsd-pi 2.37.1 → 2.38.0-dev.4d4d14a
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 +1 -1
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/onboarding.js +1 -0
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
- package/dist/resources/extensions/gsd/auto-loop.js +149 -170
- package/dist/resources/extensions/gsd/auto-post-unit.js +105 -68
- package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +43 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +8 -1
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +3 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +101 -11
- package/dist/resources/extensions/gsd/preferences.js +8 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- 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/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
- package/src/resources/extensions/gsd/auto-loop.ts +207 -252
- package/src/resources/extensions/gsd/auto-post-unit.ts +82 -39
- package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +47 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +13 -1
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +9 -5
- package/src/resources/extensions/gsd/preferences-validation.ts +92 -11
- package/src/resources/extensions/gsd/preferences.ts +8 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +16 -37
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +43 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -33,7 +33,6 @@ import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.j
|
|
|
33
33
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
34
34
|
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
35
35
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
36
|
-
import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
37
36
|
import { isDbAvailable } from "./gsd-db.js";
|
|
38
37
|
import { consumeSignal } from "./session-status-io.js";
|
|
39
38
|
import {
|
|
@@ -56,6 +55,13 @@ import { join } from "node:path";
|
|
|
56
55
|
/** Throttle STATE.md rebuilds — at most once per 30 seconds */
|
|
57
56
|
const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
58
57
|
|
|
58
|
+
export interface PreVerificationOpts {
|
|
59
|
+
skipSettleDelay?: boolean;
|
|
60
|
+
skipDoctor?: boolean;
|
|
61
|
+
skipStateRebuild?: boolean;
|
|
62
|
+
skipWorktreeSync?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
export interface PostUnitContext {
|
|
60
66
|
s: AutoSession;
|
|
61
67
|
ctx: ExtensionContext;
|
|
@@ -73,7 +79,7 @@ export interface PostUnitContext {
|
|
|
73
79
|
*
|
|
74
80
|
* Returns "dispatched" if a signal caused stop/pause, "continue" to proceed.
|
|
75
81
|
*/
|
|
76
|
-
export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"dispatched" | "continue"> {
|
|
82
|
+
export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreVerificationOpts): Promise<"dispatched" | "continue"> {
|
|
77
83
|
const { s, ctx, pi, buildSnapshotOpts, stopAuto, pauseAuto } = pctx;
|
|
78
84
|
|
|
79
85
|
// ── Parallel worker signal check ──
|
|
@@ -95,8 +101,10 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
95
101
|
// Invalidate all caches
|
|
96
102
|
invalidateAllCaches();
|
|
97
103
|
|
|
98
|
-
// Small delay to let files settle
|
|
99
|
-
|
|
104
|
+
// Small delay to let files settle (skipped for sidecars where latency matters more)
|
|
105
|
+
if (!opts?.skipSettleDelay) {
|
|
106
|
+
await new Promise(r => setTimeout(r, 100));
|
|
107
|
+
}
|
|
100
108
|
|
|
101
109
|
// Auto-commit
|
|
102
110
|
if (s.currentUnit) {
|
|
@@ -113,15 +121,25 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
113
121
|
const summaryContent = await loadFile(summaryPath);
|
|
114
122
|
if (summaryContent) {
|
|
115
123
|
const summary = parseSummary(summaryContent);
|
|
124
|
+
// Look up GitHub issue number for commit linking
|
|
125
|
+
let ghIssueNumber: number | undefined;
|
|
126
|
+
try {
|
|
127
|
+
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
128
|
+
ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
|
|
129
|
+
} catch {
|
|
130
|
+
// GitHub sync not available — skip
|
|
131
|
+
}
|
|
132
|
+
|
|
116
133
|
taskContext = {
|
|
117
134
|
taskId: `${sid}/${tid}`,
|
|
118
135
|
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
119
136
|
oneLiner: summary.oneLiner || undefined,
|
|
120
137
|
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
138
|
+
issueNumber: ghIssueNumber,
|
|
121
139
|
};
|
|
122
140
|
}
|
|
123
|
-
} catch {
|
|
124
|
-
|
|
141
|
+
} catch (e) {
|
|
142
|
+
debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
|
|
125
143
|
}
|
|
126
144
|
}
|
|
127
145
|
}
|
|
@@ -131,12 +149,20 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
131
149
|
if (commitMsg) {
|
|
132
150
|
ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
133
151
|
}
|
|
134
|
-
} catch {
|
|
135
|
-
|
|
152
|
+
} catch (e) {
|
|
153
|
+
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
136
154
|
}
|
|
137
155
|
|
|
138
|
-
//
|
|
156
|
+
// GitHub sync (non-blocking, opt-in)
|
|
139
157
|
try {
|
|
158
|
+
const { runGitHubSync } = await import("../github-sync/sync.js");
|
|
159
|
+
await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
debugLog("postUnit", { phase: "github-sync", error: String(e) });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
165
|
+
if (!opts?.skipDoctor) try {
|
|
140
166
|
const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
|
|
141
167
|
const doctorScope = scopeParts.join("/");
|
|
142
168
|
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
@@ -168,24 +194,27 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
168
194
|
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
169
195
|
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
170
196
|
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
171
|
-
|
|
172
|
-
|
|
197
|
+
return "dispatched";
|
|
198
|
+
} catch (e) {
|
|
199
|
+
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
173
200
|
}
|
|
174
201
|
}
|
|
175
202
|
}
|
|
176
|
-
} catch {
|
|
177
|
-
|
|
203
|
+
} catch (e) {
|
|
204
|
+
debugLog("postUnit", { phase: "doctor", error: String(e) });
|
|
178
205
|
}
|
|
179
206
|
|
|
180
|
-
// Throttled STATE.md rebuild
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
207
|
+
// Throttled STATE.md rebuild (skipped for lightweight sidecars)
|
|
208
|
+
if (!opts?.skipStateRebuild) {
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
211
|
+
try {
|
|
212
|
+
await rebuildState(s.basePath);
|
|
213
|
+
s.lastStateRebuildAt = now;
|
|
214
|
+
autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
|
|
217
|
+
}
|
|
189
218
|
}
|
|
190
219
|
}
|
|
191
220
|
|
|
@@ -193,16 +222,16 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
193
222
|
try {
|
|
194
223
|
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
195
224
|
pruneDeadProcesses();
|
|
196
|
-
} catch {
|
|
197
|
-
|
|
225
|
+
} catch (e) {
|
|
226
|
+
debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) });
|
|
198
227
|
}
|
|
199
228
|
|
|
200
|
-
// Sync worktree state back to project root
|
|
201
|
-
if (s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
229
|
+
// Sync worktree state back to project root (skipped for lightweight sidecars)
|
|
230
|
+
if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
202
231
|
try {
|
|
203
232
|
syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
|
|
204
|
-
} catch {
|
|
205
|
-
|
|
233
|
+
} catch (e) {
|
|
234
|
+
debugLog("postUnit", { phase: "worktree-sync", error: String(e) });
|
|
206
235
|
}
|
|
207
236
|
}
|
|
208
237
|
|
|
@@ -210,10 +239,24 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
210
239
|
if (s.currentUnit.type === "rewrite-docs") {
|
|
211
240
|
try {
|
|
212
241
|
await resolveAllOverrides(s.basePath);
|
|
213
|
-
|
|
242
|
+
s.rewriteAttemptCount = 0;
|
|
214
243
|
ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
|
|
215
|
-
} catch {
|
|
216
|
-
|
|
244
|
+
} catch (e) {
|
|
245
|
+
debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Reactive state cleanup on slice completion
|
|
250
|
+
if (s.currentUnit.type === "complete-slice") {
|
|
251
|
+
try {
|
|
252
|
+
const parts = s.currentUnit.id.split("/");
|
|
253
|
+
const [mid, sid] = parts;
|
|
254
|
+
if (mid && sid) {
|
|
255
|
+
const { clearReactiveState } = await import("./reactive-graph.js");
|
|
256
|
+
clearReactiveState(s.basePath, mid, sid);
|
|
257
|
+
}
|
|
258
|
+
} catch (e) {
|
|
259
|
+
debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
|
|
217
260
|
}
|
|
218
261
|
}
|
|
219
262
|
|
|
@@ -266,8 +309,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
266
309
|
if (triggerArtifactVerified) {
|
|
267
310
|
invalidateAllCaches();
|
|
268
311
|
}
|
|
269
|
-
} catch {
|
|
270
|
-
|
|
312
|
+
} catch (e) {
|
|
313
|
+
debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
|
|
271
314
|
}
|
|
272
315
|
} else {
|
|
273
316
|
// Hook unit completed — finalize its runtime record
|
|
@@ -278,8 +321,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
278
321
|
lastProgressKind: "hook-completed",
|
|
279
322
|
});
|
|
280
323
|
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
281
|
-
} catch {
|
|
282
|
-
|
|
324
|
+
} catch (e) {
|
|
325
|
+
debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
|
|
283
326
|
}
|
|
284
327
|
}
|
|
285
328
|
}
|
|
@@ -415,8 +458,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
415
458
|
}
|
|
416
459
|
}
|
|
417
460
|
}
|
|
418
|
-
} catch {
|
|
419
|
-
|
|
461
|
+
} catch (e) {
|
|
462
|
+
debugLog("postUnit", { phase: "triage-check", error: String(e) });
|
|
420
463
|
}
|
|
421
464
|
}
|
|
422
465
|
|
|
@@ -461,8 +504,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
461
504
|
);
|
|
462
505
|
|
|
463
506
|
return "continue";
|
|
464
|
-
} catch {
|
|
465
|
-
|
|
507
|
+
} catch (e) {
|
|
508
|
+
debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
|
|
466
509
|
}
|
|
467
510
|
}
|
|
468
511
|
|
|
@@ -21,10 +21,7 @@ import type { GSDPreferences } from "./preferences.js";
|
|
|
21
21
|
import { join } from "node:path";
|
|
22
22
|
import { existsSync } from "node:fs";
|
|
23
23
|
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
24
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
25
|
-
import { distillSummaries } from "./summary-distiller.js";
|
|
26
24
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
27
|
-
import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
|
|
28
25
|
|
|
29
26
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
30
27
|
|
|
@@ -159,16 +156,10 @@ export async function inlineFileSmart(
|
|
|
159
156
|
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
160
157
|
}
|
|
161
158
|
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (result.savingsPercent < 20) {
|
|
167
|
-
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const formatted = formatChunks(result, relPath);
|
|
171
|
-
return `### ${label} (${result.omittedChunks} sections omitted for relevance)\nSource: \`${relPath}\`\n\n${formatted}`;
|
|
159
|
+
// For large files, truncate at section boundary
|
|
160
|
+
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
161
|
+
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
162
|
+
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
172
163
|
}
|
|
173
164
|
|
|
174
165
|
/**
|
|
@@ -202,20 +193,6 @@ export async function inlineDependencySummaries(
|
|
|
202
193
|
|
|
203
194
|
const result = sections.join("\n\n");
|
|
204
195
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
205
|
-
// For 3+ summaries, try distillation first (preserves more information)
|
|
206
|
-
if (sections.length >= 3) {
|
|
207
|
-
const rawSummaries = sections.map(s => {
|
|
208
|
-
// Extract content after the header line
|
|
209
|
-
const lines = s.split("\n");
|
|
210
|
-
const contentStart = lines.findIndex(l => l.startsWith("Source:"));
|
|
211
|
-
return contentStart >= 0 ? lines.slice(contentStart + 1).join("\n").trim() : s;
|
|
212
|
-
});
|
|
213
|
-
const distilled = distillSummaries(rawSummaries, budgetChars);
|
|
214
|
-
if (distilled.content.length <= budgetChars) {
|
|
215
|
-
return distilled.content;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Fall back to section-boundary truncation
|
|
219
196
|
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
220
197
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
221
198
|
}
|
|
@@ -485,6 +462,41 @@ export async function getPriorTaskSummaryPaths(
|
|
|
485
462
|
.map(f => `${sRel}/tasks/${f}`);
|
|
486
463
|
}
|
|
487
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Get carry-forward summary paths scoped to a task's derived dependencies.
|
|
467
|
+
*
|
|
468
|
+
* Instead of all prior tasks (order-based), returns only summaries for task
|
|
469
|
+
* IDs in `dependsOn`. Used by reactive-execute to give each subagent only
|
|
470
|
+
* the context it actually needs — not sibling tasks from a parallel batch.
|
|
471
|
+
*
|
|
472
|
+
* Falls back to order-based when dependsOn is empty (root tasks still get
|
|
473
|
+
* any available prior summaries for continuity).
|
|
474
|
+
*/
|
|
475
|
+
export async function getDependencyTaskSummaryPaths(
|
|
476
|
+
mid: string, sid: string, currentTid: string,
|
|
477
|
+
dependsOn: string[], base: string,
|
|
478
|
+
): Promise<string[]> {
|
|
479
|
+
// If no dependencies, fall back to order-based for root tasks
|
|
480
|
+
if (dependsOn.length === 0) {
|
|
481
|
+
return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
485
|
+
if (!tDir) return [];
|
|
486
|
+
|
|
487
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
488
|
+
const sRel = relSlicePath(base, mid, sid);
|
|
489
|
+
const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
|
|
490
|
+
|
|
491
|
+
return summaryFiles
|
|
492
|
+
.filter((f) => {
|
|
493
|
+
// Extract task ID from filename: "T02-SUMMARY.md" → "T02"
|
|
494
|
+
const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
|
|
495
|
+
return depSet.has(tid);
|
|
496
|
+
})
|
|
497
|
+
.map((f) => `${sRel}/tasks/${f}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
488
500
|
// ─── Adaptive Replanning Checks ────────────────────────────────────────────
|
|
489
501
|
|
|
490
502
|
/**
|
|
@@ -772,13 +784,24 @@ export async function buildPlanSlicePrompt(
|
|
|
772
784
|
});
|
|
773
785
|
}
|
|
774
786
|
|
|
787
|
+
/** Options for customizing execute-task prompt construction. */
|
|
788
|
+
export interface ExecuteTaskPromptOptions {
|
|
789
|
+
level?: InlineLevel;
|
|
790
|
+
/** Override carry-forward paths (dependency-based instead of order-based). */
|
|
791
|
+
carryForwardPaths?: string[];
|
|
792
|
+
}
|
|
793
|
+
|
|
775
794
|
export async function buildExecuteTaskPrompt(
|
|
776
795
|
mid: string, sid: string, sTitle: string,
|
|
777
|
-
tid: string, tTitle: string, base: string,
|
|
796
|
+
tid: string, tTitle: string, base: string,
|
|
797
|
+
level?: InlineLevel | ExecuteTaskPromptOptions,
|
|
778
798
|
): Promise<string> {
|
|
779
|
-
const
|
|
799
|
+
const opts: ExecuteTaskPromptOptions = typeof level === "object" && level !== null && !Array.isArray(level)
|
|
800
|
+
? level
|
|
801
|
+
: { level: level as InlineLevel | undefined };
|
|
802
|
+
const inlineLevel = opts.level ?? resolveInlineLevel();
|
|
780
803
|
|
|
781
|
-
const priorSummaries = await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
804
|
+
const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
782
805
|
const priorLines = priorSummaries.length > 0
|
|
783
806
|
? priorSummaries.map(p => `- \`${p}\``).join("\n")
|
|
784
807
|
: "- (no prior tasks)";
|
|
@@ -854,15 +877,12 @@ export async function buildExecuteTaskPrompt(
|
|
|
854
877
|
const budgets = computeBudgets(contextWindow);
|
|
855
878
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
856
879
|
|
|
857
|
-
//
|
|
858
|
-
// Only compress when compression_strategy is "compress" (budget/balanced profiles).
|
|
880
|
+
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
859
881
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
860
882
|
let finalCarryForward = carryForwardSection;
|
|
861
883
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
862
|
-
const {
|
|
863
|
-
|
|
864
|
-
finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
|
|
865
|
-
}
|
|
884
|
+
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
885
|
+
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
866
886
|
}
|
|
867
887
|
|
|
868
888
|
return loadPrompt("execute-task", {
|
|
@@ -1234,6 +1254,82 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1234
1254
|
});
|
|
1235
1255
|
}
|
|
1236
1256
|
|
|
1257
|
+
// ─── Reactive Execute Prompt ──────────────────────────────────────────────
|
|
1258
|
+
|
|
1259
|
+
export async function buildReactiveExecutePrompt(
|
|
1260
|
+
mid: string, midTitle: string, sid: string, sTitle: string,
|
|
1261
|
+
readyTaskIds: string[], base: string,
|
|
1262
|
+
): Promise<string> {
|
|
1263
|
+
const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
|
|
1264
|
+
|
|
1265
|
+
// Build graph for context
|
|
1266
|
+
const taskIO = await loadSliceTaskIO(base, mid, sid);
|
|
1267
|
+
const graph = deriveTaskGraph(taskIO);
|
|
1268
|
+
const metrics = graphMetrics(graph);
|
|
1269
|
+
|
|
1270
|
+
// Build graph context section
|
|
1271
|
+
const graphLines: string[] = [];
|
|
1272
|
+
for (const node of graph) {
|
|
1273
|
+
const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
|
|
1274
|
+
const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
|
|
1275
|
+
graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
|
|
1276
|
+
if (node.outputFiles.length > 0) {
|
|
1277
|
+
graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const graphContext = [
|
|
1281
|
+
`Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
|
|
1282
|
+
"",
|
|
1283
|
+
...graphLines,
|
|
1284
|
+
].join("\n");
|
|
1285
|
+
|
|
1286
|
+
// Build individual subagent prompts for each ready task
|
|
1287
|
+
const subagentSections: string[] = [];
|
|
1288
|
+
const readyTaskListLines: string[] = [];
|
|
1289
|
+
|
|
1290
|
+
for (const tid of readyTaskIds) {
|
|
1291
|
+
const node = graph.find((n) => n.id === tid);
|
|
1292
|
+
const tTitle = node?.title ?? tid;
|
|
1293
|
+
readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
|
|
1294
|
+
|
|
1295
|
+
// Build dependency-scoped carry-forward paths for this task
|
|
1296
|
+
const depPaths = await getDependencyTaskSummaryPaths(
|
|
1297
|
+
mid, sid, tid, node?.dependsOn ?? [], base,
|
|
1298
|
+
);
|
|
1299
|
+
|
|
1300
|
+
// Build a full execute-task prompt with dependency-based carry-forward
|
|
1301
|
+
const taskPrompt = await buildExecuteTaskPrompt(
|
|
1302
|
+
mid, sid, sTitle, tid, tTitle, base,
|
|
1303
|
+
{ carryForwardPaths: depPaths },
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
subagentSections.push([
|
|
1307
|
+
`### ${tid}: ${tTitle}`,
|
|
1308
|
+
"",
|
|
1309
|
+
"Use this as the prompt for a `subagent` call:",
|
|
1310
|
+
"",
|
|
1311
|
+
"```",
|
|
1312
|
+
taskPrompt,
|
|
1313
|
+
"```",
|
|
1314
|
+
].join("\n"));
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
|
|
1318
|
+
|
|
1319
|
+
return loadPrompt("reactive-execute", {
|
|
1320
|
+
workingDirectory: base,
|
|
1321
|
+
milestoneId: mid,
|
|
1322
|
+
milestoneTitle: midTitle,
|
|
1323
|
+
sliceId: sid,
|
|
1324
|
+
sliceTitle: sTitle,
|
|
1325
|
+
graphContext,
|
|
1326
|
+
readyTaskCount: String(readyTaskIds.length),
|
|
1327
|
+
readyTaskList: readyTaskListLines.join("\n"),
|
|
1328
|
+
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
1329
|
+
inlinedTemplates,
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1237
1333
|
export async function buildRewriteDocsPrompt(
|
|
1238
1334
|
mid: string, midTitle: string,
|
|
1239
1335
|
activeSlice: { id: string; title: string } | null,
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
resolveSlicePath,
|
|
27
27
|
resolveSliceFile,
|
|
28
28
|
resolveTasksDir,
|
|
29
|
+
resolveTaskFiles,
|
|
29
30
|
relMilestoneFile,
|
|
30
31
|
relSliceFile,
|
|
31
32
|
relSlicePath,
|
|
@@ -110,6 +111,9 @@ export function resolveExpectedArtifactPath(
|
|
|
110
111
|
}
|
|
111
112
|
case "rewrite-docs":
|
|
112
113
|
return null;
|
|
114
|
+
case "reactive-execute":
|
|
115
|
+
// Reactive execute produces multiple task summaries — verified separately
|
|
116
|
+
return null;
|
|
113
117
|
default:
|
|
114
118
|
return null;
|
|
115
119
|
}
|
|
@@ -148,6 +152,44 @@ export function verifyExpectedArtifact(
|
|
|
148
152
|
return !content.includes("**Scope:** active");
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
// Reactive-execute: verify that each dispatched task's summary exists.
|
|
156
|
+
// The unitId encodes the batch: "{mid}/{sid}/reactive+T02,T03"
|
|
157
|
+
if (unitType === "reactive-execute") {
|
|
158
|
+
const parts = unitId.split("/");
|
|
159
|
+
const mid = parts[0];
|
|
160
|
+
const sidAndBatch = parts[1];
|
|
161
|
+
const batchPart = parts[2]; // "reactive+T02,T03"
|
|
162
|
+
if (!mid || !sidAndBatch || !batchPart) return false;
|
|
163
|
+
|
|
164
|
+
const sid = sidAndBatch;
|
|
165
|
+
const plusIdx = batchPart.indexOf("+");
|
|
166
|
+
if (plusIdx === -1) {
|
|
167
|
+
// Legacy format "reactive" without batch IDs — fall back to "any summary"
|
|
168
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
169
|
+
if (!tDir) return false;
|
|
170
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
171
|
+
return summaryFiles.length > 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const batchIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
|
|
175
|
+
if (batchIds.length === 0) return false;
|
|
176
|
+
|
|
177
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
178
|
+
if (!tDir) return false;
|
|
179
|
+
|
|
180
|
+
const existingSummaries = new Set(
|
|
181
|
+
resolveTaskFiles(tDir, "SUMMARY").map((f) =>
|
|
182
|
+
f.replace(/-SUMMARY\.md$/i, "").toUpperCase(),
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Every dispatched task must have a summary file
|
|
187
|
+
for (const tid of batchIds) {
|
|
188
|
+
if (!existingSummaries.has(tid.toUpperCase())) return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
152
194
|
// For unit types with no verifiable artifact (null path), the parent directory
|
|
153
195
|
// is missing on disk — treat as stale completion state so the key gets evicted (#313).
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
resolveSkillDiscoveryMode,
|
|
21
21
|
getIsolationMode,
|
|
22
22
|
} from "./preferences.js";
|
|
23
|
-
import { ensureGsdSymlink } from "./repo-identity.js";
|
|
23
|
+
import { ensureGsdSymlink, validateProjectId } from "./repo-identity.js";
|
|
24
24
|
import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
|
|
25
25
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
26
26
|
import { gsdRoot, resolveMilestoneFile, milestonesDir } from "./paths.js";
|
|
@@ -130,6 +130,16 @@ export async function bootstrapAutoSession(
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
try {
|
|
133
|
+
// Validate GSD_PROJECT_ID early so the user gets immediate feedback
|
|
134
|
+
const customProjectId = process.env.GSD_PROJECT_ID;
|
|
135
|
+
if (customProjectId && !validateProjectId(customProjectId)) {
|
|
136
|
+
ctx.ui.notify(
|
|
137
|
+
`GSD_PROJECT_ID must contain only alphanumeric characters, hyphens, and underscores. Got: "${customProjectId}"`,
|
|
138
|
+
"error",
|
|
139
|
+
);
|
|
140
|
+
return releaseLockAndReturn();
|
|
141
|
+
}
|
|
142
|
+
|
|
133
143
|
// Ensure git repo exists
|
|
134
144
|
if (!nativeIsRepo(base)) {
|
|
135
145
|
const mainBranch =
|
|
@@ -429,10 +439,16 @@ export async function bootstrapAutoSession(
|
|
|
429
439
|
s.originalBasePath = base;
|
|
430
440
|
|
|
431
441
|
const isUnderGsdWorktrees = (p: string): boolean => {
|
|
442
|
+
// Direct layout: /.gsd/worktrees/
|
|
432
443
|
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
433
444
|
if (p.includes(marker)) return true;
|
|
434
445
|
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
435
|
-
|
|
446
|
+
if (p.endsWith(worktreesSuffix)) return true;
|
|
447
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
448
|
+
const symlinkRe = new RegExp(
|
|
449
|
+
`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees(?:\\${pathSep}|$)`,
|
|
450
|
+
);
|
|
451
|
+
return symlinkRe.test(p);
|
|
436
452
|
};
|
|
437
453
|
|
|
438
454
|
if (
|
|
@@ -22,6 +22,8 @@ import { join, sep as pathSep } from "node:path";
|
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
23
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
24
24
|
|
|
25
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
26
|
+
|
|
25
27
|
// ─── Project Root → Worktree Sync ─────────────────────────────────────────
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -111,7 +113,7 @@ export function syncStateToProjectRoot(
|
|
|
111
113
|
*/
|
|
112
114
|
export function readResourceVersion(): string | null {
|
|
113
115
|
const agentDir =
|
|
114
|
-
process.env.GSD_CODING_AGENT_DIR || join(
|
|
116
|
+
process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
|
|
115
117
|
const manifestPath = join(agentDir, "managed-resources.json");
|
|
116
118
|
try {
|
|
117
119
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -153,9 +155,18 @@ export function checkResourcesStale(
|
|
|
153
155
|
* Returns the corrected base path.
|
|
154
156
|
*/
|
|
155
157
|
export function escapeStaleWorktree(base: string): string {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
158
|
+
// Direct layout: /.gsd/worktrees/
|
|
159
|
+
const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
160
|
+
let idx = base.indexOf(directMarker);
|
|
161
|
+
if (idx === -1) {
|
|
162
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
163
|
+
const symlinkRe = new RegExp(
|
|
164
|
+
`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`,
|
|
165
|
+
);
|
|
166
|
+
const match = base.match(symlinkRe);
|
|
167
|
+
if (!match || match.index === undefined) return base;
|
|
168
|
+
idx = match.index;
|
|
169
|
+
}
|
|
159
170
|
|
|
160
171
|
// base is inside .gsd/worktrees/<something> — extract the project root
|
|
161
172
|
const projectRoot = base.slice(0, idx);
|