gsd-pi 2.37.1-dev.d3ace49 → 2.38.0-dev.29edcdc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- 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 +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +597 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
- 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-worktree.js +3 -3
- 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 +24 -3
- 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 +27 -11
- package/dist/resources/extensions/gsd/doctor.js +204 -12
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +6 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +15 -12
- package/dist/resources/extensions/gsd/guided-flow.js +82 -32
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -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 +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -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/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- 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/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- 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 +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/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.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/extensions/loader.ts +223 -7
- 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 +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +484 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
- 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-worktree.ts +3 -3
- 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 +26 -4
- 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 +26 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +199 -14
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +5 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +20 -10
- package/src/resources/extensions/gsd/guided-flow.ts +110 -38
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -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/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- 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/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- 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 +86 -3
- 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/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 +0 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- 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
|
@@ -22,7 +22,6 @@ import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.j
|
|
|
22
22
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
23
23
|
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
24
24
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
25
|
-
import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
26
25
|
import { isDbAvailable } from "./gsd-db.js";
|
|
27
26
|
import { consumeSignal } from "./session-status-io.js";
|
|
28
27
|
import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, } from "./post-unit-hooks.js";
|
|
@@ -36,7 +35,7 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
|
36
35
|
*
|
|
37
36
|
* Returns "dispatched" if a signal caused stop/pause, "continue" to proceed.
|
|
38
37
|
*/
|
|
39
|
-
export async function postUnitPreVerification(pctx) {
|
|
38
|
+
export async function postUnitPreVerification(pctx, opts) {
|
|
40
39
|
const { s, ctx, pi, buildSnapshotOpts, stopAuto, pauseAuto } = pctx;
|
|
41
40
|
// ── Parallel worker signal check ──
|
|
42
41
|
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
@@ -55,8 +54,10 @@ export async function postUnitPreVerification(pctx) {
|
|
|
55
54
|
}
|
|
56
55
|
// Invalidate all caches
|
|
57
56
|
invalidateAllCaches();
|
|
58
|
-
// Small delay to let files settle
|
|
59
|
-
|
|
57
|
+
// Small delay to let files settle (skipped for sidecars where latency matters more)
|
|
58
|
+
if (!opts?.skipSettleDelay) {
|
|
59
|
+
await new Promise(r => setTimeout(r, 100));
|
|
60
|
+
}
|
|
60
61
|
// Auto-commit
|
|
61
62
|
if (s.currentUnit) {
|
|
62
63
|
try {
|
|
@@ -71,16 +72,26 @@ export async function postUnitPreVerification(pctx) {
|
|
|
71
72
|
const summaryContent = await loadFile(summaryPath);
|
|
72
73
|
if (summaryContent) {
|
|
73
74
|
const summary = parseSummary(summaryContent);
|
|
75
|
+
// Look up GitHub issue number for commit linking
|
|
76
|
+
let ghIssueNumber;
|
|
77
|
+
try {
|
|
78
|
+
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
79
|
+
ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// GitHub sync not available — skip
|
|
83
|
+
}
|
|
74
84
|
taskContext = {
|
|
75
85
|
taskId: `${sid}/${tid}`,
|
|
76
86
|
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
77
87
|
oneLiner: summary.oneLiner || undefined,
|
|
78
88
|
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
89
|
+
issueNumber: ghIssueNumber,
|
|
79
90
|
};
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
|
-
catch {
|
|
83
|
-
|
|
93
|
+
catch (e) {
|
|
94
|
+
debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
}
|
|
@@ -90,57 +101,75 @@ export async function postUnitPreVerification(pctx) {
|
|
|
90
101
|
ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
|
-
catch {
|
|
94
|
-
|
|
104
|
+
catch (e) {
|
|
105
|
+
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
95
106
|
}
|
|
96
|
-
//
|
|
107
|
+
// GitHub sync (non-blocking, opt-in)
|
|
97
108
|
try {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
101
|
-
const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
|
|
102
|
-
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
103
|
-
if (report.fixesApplied.length > 0) {
|
|
104
|
-
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
105
|
-
}
|
|
106
|
-
// Proactive health tracking
|
|
107
|
-
const summary = summarizeDoctorIssues(report.issues);
|
|
108
|
-
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
109
|
-
// Check if we should escalate to LLM-assisted heal
|
|
110
|
-
if (summary.errors > 0) {
|
|
111
|
-
const unresolvedErrors = report.issues
|
|
112
|
-
.filter(i => i.severity === "error" && !i.fixable)
|
|
113
|
-
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
114
|
-
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
115
|
-
if (escalation.shouldEscalate) {
|
|
116
|
-
ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
|
|
117
|
-
try {
|
|
118
|
-
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
119
|
-
const { dispatchDoctorHeal } = await import("./commands-handlers.js");
|
|
120
|
-
const actionable = report.issues.filter(i => i.severity === "error");
|
|
121
|
-
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
122
|
-
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
123
|
-
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// Non-fatal
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
109
|
+
const { runGitHubSync } = await import("../github-sync/sync.js");
|
|
110
|
+
await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
130
111
|
}
|
|
131
|
-
catch {
|
|
132
|
-
|
|
112
|
+
catch (e) {
|
|
113
|
+
debugLog("postUnit", { phase: "github-sync", error: String(e) });
|
|
133
114
|
}
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
115
|
+
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
116
|
+
if (!opts?.skipDoctor)
|
|
137
117
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
118
|
+
const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
|
|
119
|
+
const doctorScope = scopeParts.join("/");
|
|
120
|
+
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
121
|
+
const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
|
|
122
|
+
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
123
|
+
if (report.fixesApplied.length > 0) {
|
|
124
|
+
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
125
|
+
}
|
|
126
|
+
// Proactive health tracking — filter to current milestone to avoid
|
|
127
|
+
// cross-milestone stale errors inflating the escalation counter
|
|
128
|
+
const currentMilestoneId = s.currentUnit.id.split("/")[0];
|
|
129
|
+
const milestoneIssues = currentMilestoneId
|
|
130
|
+
? report.issues.filter(i => i.unitId === currentMilestoneId ||
|
|
131
|
+
i.unitId.startsWith(`${currentMilestoneId}/`))
|
|
132
|
+
: report.issues;
|
|
133
|
+
const summary = summarizeDoctorIssues(milestoneIssues);
|
|
134
|
+
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
135
|
+
// Check if we should escalate to LLM-assisted heal
|
|
136
|
+
if (summary.errors > 0) {
|
|
137
|
+
const unresolvedErrors = milestoneIssues
|
|
138
|
+
.filter(i => i.severity === "error" && !i.fixable)
|
|
139
|
+
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
140
|
+
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
141
|
+
if (escalation.shouldEscalate) {
|
|
142
|
+
ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
|
|
143
|
+
try {
|
|
144
|
+
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
145
|
+
const { dispatchDoctorHeal } = await import("./commands-handlers.js");
|
|
146
|
+
const actionable = report.issues.filter(i => i.severity === "error");
|
|
147
|
+
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
148
|
+
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
149
|
+
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
150
|
+
return "dispatched";
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
141
157
|
}
|
|
142
|
-
catch {
|
|
143
|
-
|
|
158
|
+
catch (e) {
|
|
159
|
+
debugLog("postUnit", { phase: "doctor", error: String(e) });
|
|
160
|
+
}
|
|
161
|
+
// Throttled STATE.md rebuild (skipped for lightweight sidecars)
|
|
162
|
+
if (!opts?.skipStateRebuild) {
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
165
|
+
try {
|
|
166
|
+
await rebuildState(s.basePath);
|
|
167
|
+
s.lastStateRebuildAt = now;
|
|
168
|
+
autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
|
|
172
|
+
}
|
|
144
173
|
}
|
|
145
174
|
}
|
|
146
175
|
// Prune dead bg-shell processes
|
|
@@ -148,27 +177,27 @@ export async function postUnitPreVerification(pctx) {
|
|
|
148
177
|
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
149
178
|
pruneDeadProcesses();
|
|
150
179
|
}
|
|
151
|
-
catch {
|
|
152
|
-
|
|
180
|
+
catch (e) {
|
|
181
|
+
debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) });
|
|
153
182
|
}
|
|
154
|
-
// Sync worktree state back to project root
|
|
155
|
-
if (s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
183
|
+
// Sync worktree state back to project root (skipped for lightweight sidecars)
|
|
184
|
+
if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
156
185
|
try {
|
|
157
186
|
syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
|
|
158
187
|
}
|
|
159
|
-
catch {
|
|
160
|
-
|
|
188
|
+
catch (e) {
|
|
189
|
+
debugLog("postUnit", { phase: "worktree-sync", error: String(e) });
|
|
161
190
|
}
|
|
162
191
|
}
|
|
163
192
|
// Rewrite-docs completion
|
|
164
193
|
if (s.currentUnit.type === "rewrite-docs") {
|
|
165
194
|
try {
|
|
166
195
|
await resolveAllOverrides(s.basePath);
|
|
167
|
-
|
|
196
|
+
s.rewriteAttemptCount = 0;
|
|
168
197
|
ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
|
|
169
198
|
}
|
|
170
|
-
catch {
|
|
171
|
-
|
|
199
|
+
catch (e) {
|
|
200
|
+
debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
|
|
172
201
|
}
|
|
173
202
|
}
|
|
174
203
|
// Reactive state cleanup on slice completion
|
|
@@ -181,8 +210,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
181
210
|
clearReactiveState(s.basePath, mid, sid);
|
|
182
211
|
}
|
|
183
212
|
}
|
|
184
|
-
catch {
|
|
185
|
-
|
|
213
|
+
catch (e) {
|
|
214
|
+
debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
|
|
186
215
|
}
|
|
187
216
|
}
|
|
188
217
|
// Post-triage: execute actionable resolutions
|
|
@@ -224,8 +253,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
224
253
|
invalidateAllCaches();
|
|
225
254
|
}
|
|
226
255
|
}
|
|
227
|
-
catch {
|
|
228
|
-
|
|
256
|
+
catch (e) {
|
|
257
|
+
debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
else {
|
|
@@ -238,8 +267,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
238
267
|
});
|
|
239
268
|
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
240
269
|
}
|
|
241
|
-
catch {
|
|
242
|
-
|
|
270
|
+
catch (e) {
|
|
271
|
+
debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
|
|
243
272
|
}
|
|
244
273
|
}
|
|
245
274
|
}
|
|
@@ -352,8 +381,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
352
381
|
}
|
|
353
382
|
}
|
|
354
383
|
}
|
|
355
|
-
catch {
|
|
356
|
-
|
|
384
|
+
catch (e) {
|
|
385
|
+
debugLog("postUnit", { phase: "triage-check", error: String(e) });
|
|
357
386
|
}
|
|
358
387
|
}
|
|
359
388
|
// ── Quick-task dispatch ──
|
|
@@ -387,8 +416,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
387
416
|
ctx.ui.notify(`Executing quick-task: ${capture.id} — "${capture.text}"`, "info");
|
|
388
417
|
return "continue";
|
|
389
418
|
}
|
|
390
|
-
catch {
|
|
391
|
-
|
|
419
|
+
catch (e) {
|
|
420
|
+
debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
|
|
392
421
|
}
|
|
393
422
|
}
|
|
394
423
|
// Step mode → show wizard instead of dispatch
|
|
@@ -11,11 +11,15 @@ import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksD
|
|
|
11
11
|
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
|
-
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
15
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
16
|
-
import { distillSummaries } from "./summary-distiller.js";
|
|
14
|
+
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
17
15
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
18
|
-
|
|
16
|
+
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
17
|
+
const MAX_PREAMBLE_CHARS = 30_000;
|
|
18
|
+
function capPreamble(preamble) {
|
|
19
|
+
if (preamble.length <= MAX_PREAMBLE_CHARS)
|
|
20
|
+
return preamble;
|
|
21
|
+
return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
|
|
22
|
+
}
|
|
19
23
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
20
24
|
/**
|
|
21
25
|
* Format executor context constraints for injection into the plan-slice prompt.
|
|
@@ -126,14 +130,9 @@ export async function inlineFileSmart(absPath, relPath, label, query, threshold
|
|
|
126
130
|
if (content.length <= threshold || !query) {
|
|
127
131
|
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
128
132
|
}
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
if (result.savingsPercent < 20) {
|
|
133
|
-
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
134
|
-
}
|
|
135
|
-
const formatted = formatChunks(result, relPath);
|
|
136
|
-
return `### ${label} (${result.omittedChunks} sections omitted for relevance)\nSource: \`${relPath}\`\n\n${formatted}`;
|
|
133
|
+
// For large files, truncate at section boundary
|
|
134
|
+
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
135
|
+
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
137
136
|
}
|
|
138
137
|
/**
|
|
139
138
|
* Load and inline dependency slice summaries (full content, not just paths).
|
|
@@ -165,21 +164,6 @@ export async function inlineDependencySummaries(mid, sid, base, budgetChars) {
|
|
|
165
164
|
}
|
|
166
165
|
const result = sections.join("\n\n");
|
|
167
166
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
168
|
-
// For 3+ summaries, try distillation first (preserves more information)
|
|
169
|
-
if (sections.length >= 3) {
|
|
170
|
-
const rawSummaries = sections.map(s => {
|
|
171
|
-
// Extract content after the header line
|
|
172
|
-
const lines = s.split("\n");
|
|
173
|
-
const contentStart = lines.findIndex(l => l.startsWith("Source:"));
|
|
174
|
-
return contentStart >= 0 ? lines.slice(contentStart + 1).join("\n").trim() : s;
|
|
175
|
-
});
|
|
176
|
-
const distilled = distillSummaries(rawSummaries, budgetChars);
|
|
177
|
-
if (distilled.content.length <= budgetChars) {
|
|
178
|
-
return distilled.content;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
// Fall back to section-boundary truncation
|
|
182
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
183
167
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
184
168
|
}
|
|
185
169
|
return result;
|
|
@@ -546,7 +530,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
|
|
|
546
530
|
if (knowledgeInlineRM)
|
|
547
531
|
inlined.push(knowledgeInlineRM);
|
|
548
532
|
inlined.push(inlineTemplate("research", "Research"));
|
|
549
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
533
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
550
534
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
551
535
|
return loadPrompt("research-milestone", {
|
|
552
536
|
workingDirectory: base,
|
|
@@ -599,7 +583,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
599
583
|
inlined.push(inlineTemplate("plan", "Slice Plan"));
|
|
600
584
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
601
585
|
}
|
|
602
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
586
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
603
587
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
604
588
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
605
589
|
const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
|
|
@@ -647,7 +631,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
647
631
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
648
632
|
if (overridesInline)
|
|
649
633
|
inlined.unshift(overridesInline);
|
|
650
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
634
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
651
635
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
652
636
|
return loadPrompt("research-slice", {
|
|
653
637
|
workingDirectory: base,
|
|
@@ -693,7 +677,7 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
693
677
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
694
678
|
if (planOverridesInline)
|
|
695
679
|
inlined.unshift(planOverridesInline);
|
|
696
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
680
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
697
681
|
// Build executor context constraints from the budget engine
|
|
698
682
|
const executorContextConstraints = formatExecutorConstraints();
|
|
699
683
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
@@ -777,15 +761,11 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
777
761
|
const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
|
|
778
762
|
const budgets = computeBudgets(contextWindow);
|
|
779
763
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
780
|
-
//
|
|
781
|
-
// Only compress when compression_strategy is "compress" (budget/balanced profiles).
|
|
764
|
+
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
782
765
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
783
766
|
let finalCarryForward = carryForwardSection;
|
|
784
767
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
785
|
-
|
|
786
|
-
if (resolveCompressionStrategy() === "compress") {
|
|
787
|
-
finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
|
|
788
|
-
}
|
|
768
|
+
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
789
769
|
}
|
|
790
770
|
return loadPrompt("execute-task", {
|
|
791
771
|
overridesSection,
|
|
@@ -843,7 +823,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
843
823
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
844
824
|
if (completeOverridesInline)
|
|
845
825
|
inlined.unshift(completeOverridesInline);
|
|
846
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
826
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
847
827
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
848
828
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
849
829
|
const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
|
|
@@ -899,7 +879,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
|
899
879
|
if (contextInline)
|
|
900
880
|
inlined.push(contextInline);
|
|
901
881
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
902
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
882
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
903
883
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
904
884
|
return loadPrompt("complete-milestone", {
|
|
905
885
|
workingDirectory: base,
|
|
@@ -966,7 +946,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
|
|
|
966
946
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
967
947
|
if (contextInline)
|
|
968
948
|
inlined.push(contextInline);
|
|
969
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
949
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
970
950
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
971
951
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
972
952
|
return loadPrompt("validate-milestone", {
|
|
@@ -1014,7 +994,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
1014
994
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
1015
995
|
if (replanOverridesInline)
|
|
1016
996
|
inlined.unshift(replanOverridesInline);
|
|
1017
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
997
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1018
998
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
1019
999
|
// Build capture context for replan prompt (captures that triggered this replan)
|
|
1020
1000
|
let captureContext = "(none)";
|
|
@@ -1054,7 +1034,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
|
|
|
1054
1034
|
const projectInline = await inlineProjectFromDb(base);
|
|
1055
1035
|
if (projectInline)
|
|
1056
1036
|
inlined.push(projectInline);
|
|
1057
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1037
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1058
1038
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1059
1039
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
1060
1040
|
return loadPrompt("run-uat", {
|
|
@@ -1090,7 +1070,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1090
1070
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1091
1071
|
if (knowledgeInlineRA)
|
|
1092
1072
|
inlined.push(knowledgeInlineRA);
|
|
1093
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1073
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1094
1074
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1095
1075
|
// Build deferred captures context for reassess prompt
|
|
1096
1076
|
let deferredCaptures = "(none)";
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { deriveState } from "./state.js";
|
|
12
12
|
import { loadFile, getManifestStatus } from "./files.js";
|
|
13
13
|
import { loadEffectiveGSDPreferences, resolveSkillDiscoveryMode, getIsolationMode, } from "./preferences.js";
|
|
14
|
-
import { ensureGsdSymlink } from "./repo-identity.js";
|
|
14
|
+
import { ensureGsdSymlink, validateProjectId } from "./repo-identity.js";
|
|
15
15
|
import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
|
|
16
16
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
17
17
|
import { gsdRoot, resolveMilestoneFile } from "./paths.js";
|
|
@@ -63,6 +63,12 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
65
65
|
try {
|
|
66
|
+
// Validate GSD_PROJECT_ID early so the user gets immediate feedback
|
|
67
|
+
const customProjectId = process.env.GSD_PROJECT_ID;
|
|
68
|
+
if (customProjectId && !validateProjectId(customProjectId)) {
|
|
69
|
+
ctx.ui.notify(`GSD_PROJECT_ID must contain only alphanumeric characters, hyphens, and underscores. Got: "${customProjectId}"`, "error");
|
|
70
|
+
return releaseLockAndReturn();
|
|
71
|
+
}
|
|
66
72
|
// Ensure git repo exists
|
|
67
73
|
if (!nativeIsRepo(base)) {
|
|
68
74
|
const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
@@ -303,11 +309,16 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
303
309
|
// ── Auto-worktree setup ──
|
|
304
310
|
s.originalBasePath = base;
|
|
305
311
|
const isUnderGsdWorktrees = (p) => {
|
|
312
|
+
// Direct layout: /.gsd/worktrees/
|
|
306
313
|
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
307
314
|
if (p.includes(marker))
|
|
308
315
|
return true;
|
|
309
316
|
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
310
|
-
|
|
317
|
+
if (p.endsWith(worktreesSuffix))
|
|
318
|
+
return true;
|
|
319
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
320
|
+
const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees(?:\\${pathSep}|$)`);
|
|
321
|
+
return symlinkRe.test(p);
|
|
311
322
|
};
|
|
312
323
|
if (s.currentMilestoneId &&
|
|
313
324
|
shouldUseWorktreeIsolation() &&
|
|
@@ -13,6 +13,7 @@ import { existsSync, readFileSync, unlinkSync, readdirSync, } from "node:fs";
|
|
|
13
13
|
import { join, sep as pathSep } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
16
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
16
17
|
// ─── Project Root → Worktree Sync ─────────────────────────────────────────
|
|
17
18
|
/**
|
|
18
19
|
* Sync milestone artifacts from project root INTO worktree before deriveState.
|
|
@@ -75,7 +76,7 @@ export function syncStateToProjectRoot(worktreePath, projectRoot, milestoneId) {
|
|
|
75
76
|
* doesn't falsely trigger staleness (#804).
|
|
76
77
|
*/
|
|
77
78
|
export function readResourceVersion() {
|
|
78
|
-
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(
|
|
79
|
+
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
|
|
79
80
|
const manifestPath = join(agentDir, "managed-resources.json");
|
|
80
81
|
try {
|
|
81
82
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -115,10 +116,17 @@ export function checkResourcesStale(versionOnStart) {
|
|
|
115
116
|
* Returns the corrected base path.
|
|
116
117
|
*/
|
|
117
118
|
export function escapeStaleWorktree(base) {
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
// Direct layout: /.gsd/worktrees/
|
|
120
|
+
const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
121
|
+
let idx = base.indexOf(directMarker);
|
|
122
|
+
if (idx === -1) {
|
|
123
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
124
|
+
const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`);
|
|
125
|
+
const match = base.match(symlinkRe);
|
|
126
|
+
if (!match || match.index === undefined)
|
|
127
|
+
return base;
|
|
128
|
+
idx = match.index;
|
|
129
|
+
}
|
|
122
130
|
// base is inside .gsd/worktrees/<something> — extract the project root
|
|
123
131
|
const projectRoot = base.slice(0, idx);
|
|
124
132
|
try {
|
|
@@ -15,10 +15,10 @@ import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
16
16
|
import { createWorktree, removeWorktree, worktreePath, } from "./worktree-manager.js";
|
|
17
17
|
import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
|
|
18
|
-
import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
|
|
18
|
+
import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
19
19
|
import { parseRoadmap } from "./files.js";
|
|
20
20
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
21
|
-
import { nativeGetCurrentBranch, nativeWorkingTreeStatus,
|
|
21
|
+
import { nativeGetCurrentBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, } from "./native-git-bridge.js";
|
|
22
22
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
23
23
|
/** Original project root before chdir into auto-worktree. */
|
|
24
24
|
let originalBase = null;
|
|
@@ -656,7 +656,7 @@ function autoCommitDirtyState(cwd) {
|
|
|
656
656
|
const status = nativeWorkingTreeStatus(cwd);
|
|
657
657
|
if (!status)
|
|
658
658
|
return false;
|
|
659
|
-
|
|
659
|
+
nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
|
|
660
660
|
const result = nativeCommit(cwd, "chore: auto-commit before milestone merge");
|
|
661
661
|
return result !== null;
|
|
662
662
|
}
|