gsd-pi 2.37.1 → 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/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/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 +75 -10
- package/dist/resources/extensions/gsd/auto-loop.js +597 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +111 -68
- package/dist/resources/extensions/gsd/auto-prompts.js +114 -45
- 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-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 +62 -12
- 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 +47 -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/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 +27 -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/roadmap-mutations.js +24 -0
- 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/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 +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/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/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/extensions/loader.ts +223 -7
- 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 +100 -9
- package/src/resources/extensions/gsd/auto-loop.ts +484 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +92 -42
- package/src/resources/extensions/gsd/auto-prompts.ts +150 -48
- 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-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 +64 -10
- 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 +50 -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/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 +27 -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/roadmap-mutations.ts +29 -0
- 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 +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 +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/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
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
debugLog("postUnit", { phase: "doctor", error: String(e) });
|
|
141
160
|
}
|
|
142
|
-
|
|
143
|
-
|
|
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,41 @@ 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) });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Reactive state cleanup on slice completion
|
|
204
|
+
if (s.currentUnit.type === "complete-slice") {
|
|
205
|
+
try {
|
|
206
|
+
const parts = s.currentUnit.id.split("/");
|
|
207
|
+
const [mid, sid] = parts;
|
|
208
|
+
if (mid && sid) {
|
|
209
|
+
const { clearReactiveState } = await import("./reactive-graph.js");
|
|
210
|
+
clearReactiveState(s.basePath, mid, sid);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
|
|
172
215
|
}
|
|
173
216
|
}
|
|
174
217
|
// Post-triage: execute actionable resolutions
|
|
@@ -210,8 +253,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
210
253
|
invalidateAllCaches();
|
|
211
254
|
}
|
|
212
255
|
}
|
|
213
|
-
catch {
|
|
214
|
-
|
|
256
|
+
catch (e) {
|
|
257
|
+
debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
|
|
215
258
|
}
|
|
216
259
|
}
|
|
217
260
|
else {
|
|
@@ -224,8 +267,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
224
267
|
});
|
|
225
268
|
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
226
269
|
}
|
|
227
|
-
catch {
|
|
228
|
-
|
|
270
|
+
catch (e) {
|
|
271
|
+
debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
|
|
229
272
|
}
|
|
230
273
|
}
|
|
231
274
|
}
|
|
@@ -338,8 +381,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
338
381
|
}
|
|
339
382
|
}
|
|
340
383
|
}
|
|
341
|
-
catch {
|
|
342
|
-
|
|
384
|
+
catch (e) {
|
|
385
|
+
debugLog("postUnit", { phase: "triage-check", error: String(e) });
|
|
343
386
|
}
|
|
344
387
|
}
|
|
345
388
|
// ── Quick-task dispatch ──
|
|
@@ -373,8 +416,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
373
416
|
ctx.ui.notify(`Executing quick-task: ${capture.id} — "${capture.text}"`, "info");
|
|
374
417
|
return "continue";
|
|
375
418
|
}
|
|
376
|
-
catch {
|
|
377
|
-
|
|
419
|
+
catch (e) {
|
|
420
|
+
debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
|
|
378
421
|
}
|
|
379
422
|
}
|
|
380
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;
|
|
@@ -414,6 +398,35 @@ export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
|
|
|
414
398
|
})
|
|
415
399
|
.map(f => `${sRel}/tasks/${f}`);
|
|
416
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Get carry-forward summary paths scoped to a task's derived dependencies.
|
|
403
|
+
*
|
|
404
|
+
* Instead of all prior tasks (order-based), returns only summaries for task
|
|
405
|
+
* IDs in `dependsOn`. Used by reactive-execute to give each subagent only
|
|
406
|
+
* the context it actually needs — not sibling tasks from a parallel batch.
|
|
407
|
+
*
|
|
408
|
+
* Falls back to order-based when dependsOn is empty (root tasks still get
|
|
409
|
+
* any available prior summaries for continuity).
|
|
410
|
+
*/
|
|
411
|
+
export async function getDependencyTaskSummaryPaths(mid, sid, currentTid, dependsOn, base) {
|
|
412
|
+
// If no dependencies, fall back to order-based for root tasks
|
|
413
|
+
if (dependsOn.length === 0) {
|
|
414
|
+
return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
|
|
415
|
+
}
|
|
416
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
417
|
+
if (!tDir)
|
|
418
|
+
return [];
|
|
419
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
420
|
+
const sRel = relSlicePath(base, mid, sid);
|
|
421
|
+
const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
|
|
422
|
+
return summaryFiles
|
|
423
|
+
.filter((f) => {
|
|
424
|
+
// Extract task ID from filename: "T02-SUMMARY.md" → "T02"
|
|
425
|
+
const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
|
|
426
|
+
return depSet.has(tid);
|
|
427
|
+
})
|
|
428
|
+
.map((f) => `${sRel}/tasks/${f}`);
|
|
429
|
+
}
|
|
417
430
|
// ─── Adaptive Replanning Checks ────────────────────────────────────────────
|
|
418
431
|
/**
|
|
419
432
|
* Check if the most recently completed slice needs reassessment.
|
|
@@ -517,7 +530,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
|
|
|
517
530
|
if (knowledgeInlineRM)
|
|
518
531
|
inlined.push(knowledgeInlineRM);
|
|
519
532
|
inlined.push(inlineTemplate("research", "Research"));
|
|
520
|
-
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")}`);
|
|
521
534
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
522
535
|
return loadPrompt("research-milestone", {
|
|
523
536
|
workingDirectory: base,
|
|
@@ -570,7 +583,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
570
583
|
inlined.push(inlineTemplate("plan", "Slice Plan"));
|
|
571
584
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
572
585
|
}
|
|
573
|
-
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")}`);
|
|
574
587
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
575
588
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
576
589
|
const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
|
|
@@ -618,7 +631,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
618
631
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
619
632
|
if (overridesInline)
|
|
620
633
|
inlined.unshift(overridesInline);
|
|
621
|
-
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")}`);
|
|
622
635
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
623
636
|
return loadPrompt("research-slice", {
|
|
624
637
|
workingDirectory: base,
|
|
@@ -664,7 +677,7 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
664
677
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
665
678
|
if (planOverridesInline)
|
|
666
679
|
inlined.unshift(planOverridesInline);
|
|
667
|
-
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")}`);
|
|
668
681
|
// Build executor context constraints from the budget engine
|
|
669
682
|
const executorContextConstraints = formatExecutorConstraints();
|
|
670
683
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
@@ -688,8 +701,11 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
688
701
|
});
|
|
689
702
|
}
|
|
690
703
|
export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, level) {
|
|
691
|
-
const
|
|
692
|
-
|
|
704
|
+
const opts = typeof level === "object" && level !== null && !Array.isArray(level)
|
|
705
|
+
? level
|
|
706
|
+
: { level: level };
|
|
707
|
+
const inlineLevel = opts.level ?? resolveInlineLevel();
|
|
708
|
+
const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
693
709
|
const priorLines = priorSummaries.length > 0
|
|
694
710
|
? priorSummaries.map(p => `- \`${p}\``).join("\n")
|
|
695
711
|
: "- (no prior tasks)";
|
|
@@ -745,15 +761,11 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
745
761
|
const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
|
|
746
762
|
const budgets = computeBudgets(contextWindow);
|
|
747
763
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
748
|
-
//
|
|
749
|
-
// Only compress when compression_strategy is "compress" (budget/balanced profiles).
|
|
764
|
+
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
750
765
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
751
766
|
let finalCarryForward = carryForwardSection;
|
|
752
767
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
753
|
-
|
|
754
|
-
if (resolveCompressionStrategy() === "compress") {
|
|
755
|
-
finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
|
|
756
|
-
}
|
|
768
|
+
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
757
769
|
}
|
|
758
770
|
return loadPrompt("execute-task", {
|
|
759
771
|
overridesSection,
|
|
@@ -811,7 +823,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
811
823
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
812
824
|
if (completeOverridesInline)
|
|
813
825
|
inlined.unshift(completeOverridesInline);
|
|
814
|
-
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")}`);
|
|
815
827
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
816
828
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
817
829
|
const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
|
|
@@ -867,7 +879,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
|
867
879
|
if (contextInline)
|
|
868
880
|
inlined.push(contextInline);
|
|
869
881
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
870
|
-
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")}`);
|
|
871
883
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
872
884
|
return loadPrompt("complete-milestone", {
|
|
873
885
|
workingDirectory: base,
|
|
@@ -934,7 +946,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
|
|
|
934
946
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
935
947
|
if (contextInline)
|
|
936
948
|
inlined.push(contextInline);
|
|
937
|
-
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")}`);
|
|
938
950
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
939
951
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
940
952
|
return loadPrompt("validate-milestone", {
|
|
@@ -982,7 +994,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
982
994
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
983
995
|
if (replanOverridesInline)
|
|
984
996
|
inlined.unshift(replanOverridesInline);
|
|
985
|
-
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")}`);
|
|
986
998
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
987
999
|
// Build capture context for replan prompt (captures that triggered this replan)
|
|
988
1000
|
let captureContext = "(none)";
|
|
@@ -1022,7 +1034,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
|
|
|
1022
1034
|
const projectInline = await inlineProjectFromDb(base);
|
|
1023
1035
|
if (projectInline)
|
|
1024
1036
|
inlined.push(projectInline);
|
|
1025
|
-
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")}`);
|
|
1026
1038
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1027
1039
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
1028
1040
|
return loadPrompt("run-uat", {
|
|
@@ -1058,7 +1070,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1058
1070
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1059
1071
|
if (knowledgeInlineRA)
|
|
1060
1072
|
inlined.push(knowledgeInlineRA);
|
|
1061
|
-
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")}`);
|
|
1062
1074
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1063
1075
|
// Build deferred captures context for reassess prompt
|
|
1064
1076
|
let deferredCaptures = "(none)";
|
|
@@ -1090,6 +1102,63 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1090
1102
|
commitInstruction: reassessCommitInstruction,
|
|
1091
1103
|
});
|
|
1092
1104
|
}
|
|
1105
|
+
// ─── Reactive Execute Prompt ──────────────────────────────────────────────
|
|
1106
|
+
export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base) {
|
|
1107
|
+
const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
|
|
1108
|
+
// Build graph for context
|
|
1109
|
+
const taskIO = await loadSliceTaskIO(base, mid, sid);
|
|
1110
|
+
const graph = deriveTaskGraph(taskIO);
|
|
1111
|
+
const metrics = graphMetrics(graph);
|
|
1112
|
+
// Build graph context section
|
|
1113
|
+
const graphLines = [];
|
|
1114
|
+
for (const node of graph) {
|
|
1115
|
+
const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
|
|
1116
|
+
const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
|
|
1117
|
+
graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
|
|
1118
|
+
if (node.outputFiles.length > 0) {
|
|
1119
|
+
graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const graphContext = [
|
|
1123
|
+
`Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
|
|
1124
|
+
"",
|
|
1125
|
+
...graphLines,
|
|
1126
|
+
].join("\n");
|
|
1127
|
+
// Build individual subagent prompts for each ready task
|
|
1128
|
+
const subagentSections = [];
|
|
1129
|
+
const readyTaskListLines = [];
|
|
1130
|
+
for (const tid of readyTaskIds) {
|
|
1131
|
+
const node = graph.find((n) => n.id === tid);
|
|
1132
|
+
const tTitle = node?.title ?? tid;
|
|
1133
|
+
readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
|
|
1134
|
+
// Build dependency-scoped carry-forward paths for this task
|
|
1135
|
+
const depPaths = await getDependencyTaskSummaryPaths(mid, sid, tid, node?.dependsOn ?? [], base);
|
|
1136
|
+
// Build a full execute-task prompt with dependency-based carry-forward
|
|
1137
|
+
const taskPrompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, { carryForwardPaths: depPaths });
|
|
1138
|
+
subagentSections.push([
|
|
1139
|
+
`### ${tid}: ${tTitle}`,
|
|
1140
|
+
"",
|
|
1141
|
+
"Use this as the prompt for a `subagent` call:",
|
|
1142
|
+
"",
|
|
1143
|
+
"```",
|
|
1144
|
+
taskPrompt,
|
|
1145
|
+
"```",
|
|
1146
|
+
].join("\n"));
|
|
1147
|
+
}
|
|
1148
|
+
const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
|
|
1149
|
+
return loadPrompt("reactive-execute", {
|
|
1150
|
+
workingDirectory: base,
|
|
1151
|
+
milestoneId: mid,
|
|
1152
|
+
milestoneTitle: midTitle,
|
|
1153
|
+
sliceId: sid,
|
|
1154
|
+
sliceTitle: sTitle,
|
|
1155
|
+
graphContext,
|
|
1156
|
+
readyTaskCount: String(readyTaskIds.length),
|
|
1157
|
+
readyTaskList: readyTaskListLines.join("\n"),
|
|
1158
|
+
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
1159
|
+
inlinedTemplates,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1093
1162
|
export async function buildRewriteDocsPrompt(mid, midTitle, activeSlice, base, overrides) {
|
|
1094
1163
|
const sid = activeSlice?.id;
|
|
1095
1164
|
const sTitle = activeSlice?.title ?? "";
|
|
@@ -11,7 +11,7 @@ import { clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
|
11
11
|
import { clearParseCache, parseRoadmap, parsePlan } from "./files.js";
|
|
12
12
|
import { isValidationTerminal } from "./state.js";
|
|
13
13
|
import { nativeConflictFiles, nativeCommit, nativeCheckoutTheirs, nativeAddPaths, nativeMergeAbort, nativeResetHard, } from "./native-git-bridge.js";
|
|
14
|
-
import { resolveMilestonePath, resolveSlicePath, resolveSliceFile, resolveTasksDir, relMilestoneFile, relSliceFile, relSlicePath, relTaskFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
|
|
14
|
+
import { resolveMilestonePath, resolveSlicePath, resolveSliceFile, resolveTasksDir, resolveTaskFiles, relMilestoneFile, relSliceFile, relSlicePath, relTaskFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
|
|
15
15
|
import { markSliceDoneInRoadmap } from "./roadmap-mutations.js";
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, } from "node:fs";
|
|
17
17
|
import { dirname, join } from "node:path";
|
|
@@ -73,6 +73,9 @@ export function resolveExpectedArtifactPath(unitType, unitId, base) {
|
|
|
73
73
|
}
|
|
74
74
|
case "rewrite-docs":
|
|
75
75
|
return null;
|
|
76
|
+
case "reactive-execute":
|
|
77
|
+
// Reactive execute produces multiple task summaries — verified separately
|
|
78
|
+
return null;
|
|
76
79
|
default:
|
|
77
80
|
return null;
|
|
78
81
|
}
|
|
@@ -105,6 +108,39 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
105
108
|
const content = readFileSync(overridesPath, "utf-8");
|
|
106
109
|
return !content.includes("**Scope:** active");
|
|
107
110
|
}
|
|
111
|
+
// Reactive-execute: verify that each dispatched task's summary exists.
|
|
112
|
+
// The unitId encodes the batch: "{mid}/{sid}/reactive+T02,T03"
|
|
113
|
+
if (unitType === "reactive-execute") {
|
|
114
|
+
const parts = unitId.split("/");
|
|
115
|
+
const mid = parts[0];
|
|
116
|
+
const sidAndBatch = parts[1];
|
|
117
|
+
const batchPart = parts[2]; // "reactive+T02,T03"
|
|
118
|
+
if (!mid || !sidAndBatch || !batchPart)
|
|
119
|
+
return false;
|
|
120
|
+
const sid = sidAndBatch;
|
|
121
|
+
const plusIdx = batchPart.indexOf("+");
|
|
122
|
+
if (plusIdx === -1) {
|
|
123
|
+
// Legacy format "reactive" without batch IDs — fall back to "any summary"
|
|
124
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
125
|
+
if (!tDir)
|
|
126
|
+
return false;
|
|
127
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
128
|
+
return summaryFiles.length > 0;
|
|
129
|
+
}
|
|
130
|
+
const batchIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
|
|
131
|
+
if (batchIds.length === 0)
|
|
132
|
+
return false;
|
|
133
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
134
|
+
if (!tDir)
|
|
135
|
+
return false;
|
|
136
|
+
const existingSummaries = new Set(resolveTaskFiles(tDir, "SUMMARY").map((f) => f.replace(/-SUMMARY\.md$/i, "").toUpperCase()));
|
|
137
|
+
// Every dispatched task must have a summary file
|
|
138
|
+
for (const tid of batchIds) {
|
|
139
|
+
if (!existingSummaries.has(tid.toUpperCase()))
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
108
144
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
109
145
|
// For unit types with no verifiable artifact (null path), the parent directory
|
|
110
146
|
// is missing on disk — treat as stale completion state so the key gets evicted (#313).
|
|
@@ -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() &&
|