gsd-pi 2.37.1 → 2.38.0-dev.63ad7e5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/onboarding.js +1 -0
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
- package/dist/resources/extensions/gsd/auto-loop.js +68 -97
- package/dist/resources/extensions/gsd/auto-post-unit.js +87 -69
- package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +43 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/index.js +2 -1
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +2 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +43 -11
- package/dist/resources/extensions/gsd/preferences.js +5 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/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-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
- package/src/resources/extensions/gsd/auto-loop.ts +88 -133
- package/src/resources/extensions/gsd/auto-post-unit.ts +64 -40
- package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +47 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/index.ts +3 -1
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +5 -5
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -11
- package/src/resources/extensions/gsd/preferences.ts +5 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +43 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -12,10 +12,7 @@ import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferen
|
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
15
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
16
|
-
import { distillSummaries } from "./summary-distiller.js";
|
|
17
15
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
18
|
-
import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
|
|
19
16
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
20
17
|
/**
|
|
21
18
|
* Format executor context constraints for injection into the plan-slice prompt.
|
|
@@ -126,14 +123,10 @@ export async function inlineFileSmart(absPath, relPath, label, query, threshold
|
|
|
126
123
|
if (content.length <= threshold || !query) {
|
|
127
124
|
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
128
125
|
}
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
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}`;
|
|
126
|
+
// For large files, truncate at section boundary
|
|
127
|
+
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
128
|
+
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
129
|
+
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
137
130
|
}
|
|
138
131
|
/**
|
|
139
132
|
* Load and inline dependency slice summaries (full content, not just paths).
|
|
@@ -165,20 +158,6 @@ export async function inlineDependencySummaries(mid, sid, base, budgetChars) {
|
|
|
165
158
|
}
|
|
166
159
|
const result = sections.join("\n\n");
|
|
167
160
|
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
161
|
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
183
162
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
184
163
|
}
|
|
@@ -414,6 +393,35 @@ export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
|
|
|
414
393
|
})
|
|
415
394
|
.map(f => `${sRel}/tasks/${f}`);
|
|
416
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Get carry-forward summary paths scoped to a task's derived dependencies.
|
|
398
|
+
*
|
|
399
|
+
* Instead of all prior tasks (order-based), returns only summaries for task
|
|
400
|
+
* IDs in `dependsOn`. Used by reactive-execute to give each subagent only
|
|
401
|
+
* the context it actually needs — not sibling tasks from a parallel batch.
|
|
402
|
+
*
|
|
403
|
+
* Falls back to order-based when dependsOn is empty (root tasks still get
|
|
404
|
+
* any available prior summaries for continuity).
|
|
405
|
+
*/
|
|
406
|
+
export async function getDependencyTaskSummaryPaths(mid, sid, currentTid, dependsOn, base) {
|
|
407
|
+
// If no dependencies, fall back to order-based for root tasks
|
|
408
|
+
if (dependsOn.length === 0) {
|
|
409
|
+
return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
|
|
410
|
+
}
|
|
411
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
412
|
+
if (!tDir)
|
|
413
|
+
return [];
|
|
414
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
415
|
+
const sRel = relSlicePath(base, mid, sid);
|
|
416
|
+
const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
|
|
417
|
+
return summaryFiles
|
|
418
|
+
.filter((f) => {
|
|
419
|
+
// Extract task ID from filename: "T02-SUMMARY.md" → "T02"
|
|
420
|
+
const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
|
|
421
|
+
return depSet.has(tid);
|
|
422
|
+
})
|
|
423
|
+
.map((f) => `${sRel}/tasks/${f}`);
|
|
424
|
+
}
|
|
417
425
|
// ─── Adaptive Replanning Checks ────────────────────────────────────────────
|
|
418
426
|
/**
|
|
419
427
|
* Check if the most recently completed slice needs reassessment.
|
|
@@ -688,8 +696,11 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
688
696
|
});
|
|
689
697
|
}
|
|
690
698
|
export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, level) {
|
|
691
|
-
const
|
|
692
|
-
|
|
699
|
+
const opts = typeof level === "object" && level !== null && !Array.isArray(level)
|
|
700
|
+
? level
|
|
701
|
+
: { level: level };
|
|
702
|
+
const inlineLevel = opts.level ?? resolveInlineLevel();
|
|
703
|
+
const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
693
704
|
const priorLines = priorSummaries.length > 0
|
|
694
705
|
? priorSummaries.map(p => `- \`${p}\``).join("\n")
|
|
695
706
|
: "- (no prior tasks)";
|
|
@@ -745,15 +756,12 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
745
756
|
const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
|
|
746
757
|
const budgets = computeBudgets(contextWindow);
|
|
747
758
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
748
|
-
//
|
|
749
|
-
// Only compress when compression_strategy is "compress" (budget/balanced profiles).
|
|
759
|
+
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
750
760
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
751
761
|
let finalCarryForward = carryForwardSection;
|
|
752
762
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
753
|
-
const {
|
|
754
|
-
|
|
755
|
-
finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
|
|
756
|
-
}
|
|
763
|
+
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
764
|
+
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
757
765
|
}
|
|
758
766
|
return loadPrompt("execute-task", {
|
|
759
767
|
overridesSection,
|
|
@@ -1090,6 +1098,63 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1090
1098
|
commitInstruction: reassessCommitInstruction,
|
|
1091
1099
|
});
|
|
1092
1100
|
}
|
|
1101
|
+
// ─── Reactive Execute Prompt ──────────────────────────────────────────────
|
|
1102
|
+
export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base) {
|
|
1103
|
+
const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
|
|
1104
|
+
// Build graph for context
|
|
1105
|
+
const taskIO = await loadSliceTaskIO(base, mid, sid);
|
|
1106
|
+
const graph = deriveTaskGraph(taskIO);
|
|
1107
|
+
const metrics = graphMetrics(graph);
|
|
1108
|
+
// Build graph context section
|
|
1109
|
+
const graphLines = [];
|
|
1110
|
+
for (const node of graph) {
|
|
1111
|
+
const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
|
|
1112
|
+
const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
|
|
1113
|
+
graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
|
|
1114
|
+
if (node.outputFiles.length > 0) {
|
|
1115
|
+
graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const graphContext = [
|
|
1119
|
+
`Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
|
|
1120
|
+
"",
|
|
1121
|
+
...graphLines,
|
|
1122
|
+
].join("\n");
|
|
1123
|
+
// Build individual subagent prompts for each ready task
|
|
1124
|
+
const subagentSections = [];
|
|
1125
|
+
const readyTaskListLines = [];
|
|
1126
|
+
for (const tid of readyTaskIds) {
|
|
1127
|
+
const node = graph.find((n) => n.id === tid);
|
|
1128
|
+
const tTitle = node?.title ?? tid;
|
|
1129
|
+
readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
|
|
1130
|
+
// Build dependency-scoped carry-forward paths for this task
|
|
1131
|
+
const depPaths = await getDependencyTaskSummaryPaths(mid, sid, tid, node?.dependsOn ?? [], base);
|
|
1132
|
+
// Build a full execute-task prompt with dependency-based carry-forward
|
|
1133
|
+
const taskPrompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, { carryForwardPaths: depPaths });
|
|
1134
|
+
subagentSections.push([
|
|
1135
|
+
`### ${tid}: ${tTitle}`,
|
|
1136
|
+
"",
|
|
1137
|
+
"Use this as the prompt for a `subagent` call:",
|
|
1138
|
+
"",
|
|
1139
|
+
"```",
|
|
1140
|
+
taskPrompt,
|
|
1141
|
+
"```",
|
|
1142
|
+
].join("\n"));
|
|
1143
|
+
}
|
|
1144
|
+
const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
|
|
1145
|
+
return loadPrompt("reactive-execute", {
|
|
1146
|
+
workingDirectory: base,
|
|
1147
|
+
milestoneId: mid,
|
|
1148
|
+
milestoneTitle: midTitle,
|
|
1149
|
+
sliceId: sid,
|
|
1150
|
+
sliceTitle: sTitle,
|
|
1151
|
+
graphContext,
|
|
1152
|
+
readyTaskCount: String(readyTaskIds.length),
|
|
1153
|
+
readyTaskList: readyTaskListLines.join("\n"),
|
|
1154
|
+
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
1155
|
+
inlinedTemplates,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1093
1158
|
export async function buildRewriteDocsPrompt(mid, midTitle, activeSlice, base, overrides) {
|
|
1094
1159
|
const sid = activeSlice?.id;
|
|
1095
1160
|
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() &&
|
|
@@ -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 {
|
|
@@ -297,116 +297,163 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
297
297
|
return;
|
|
298
298
|
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
299
299
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
clearLock(lockBase());
|
|
303
|
-
if (lockBase())
|
|
304
|
-
releaseSessionLock(lockBase());
|
|
305
|
-
clearSkillSnapshot();
|
|
306
|
-
resetSkillTelemetry();
|
|
307
|
-
// Remove SIGTERM handler registered at auto-mode start
|
|
308
|
-
deregisterSigtermHandler();
|
|
309
|
-
// ── Auto-worktree: exit worktree and reset s.basePath on stop ──
|
|
310
|
-
if (s.currentMilestoneId) {
|
|
311
|
-
const notifyCtx = ctx
|
|
312
|
-
? { notify: ctx.ui.notify.bind(ctx.ui) }
|
|
313
|
-
: { notify: () => { } };
|
|
314
|
-
buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
|
|
315
|
-
preserveBranch: true,
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
// ── DB cleanup: close the SQLite connection ──
|
|
319
|
-
if (isDbAvailable()) {
|
|
300
|
+
try {
|
|
301
|
+
// ── Step 1: Timers and locks ──
|
|
320
302
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
303
|
+
clearUnitTimeout();
|
|
304
|
+
if (lockBase())
|
|
305
|
+
clearLock(lockBase());
|
|
306
|
+
if (lockBase())
|
|
307
|
+
releaseSessionLock(lockBase());
|
|
323
308
|
}
|
|
324
309
|
catch (e) {
|
|
325
|
-
debugLog("
|
|
326
|
-
error: e instanceof Error ? e.message : String(e),
|
|
327
|
-
});
|
|
310
|
+
debugLog("stop-cleanup-locks", { error: e instanceof Error ? e.message : String(e) });
|
|
328
311
|
}
|
|
329
|
-
|
|
330
|
-
if (s.originalBasePath) {
|
|
331
|
-
s.basePath = s.originalBasePath;
|
|
312
|
+
// ── Step 2: Skill state ──
|
|
332
313
|
try {
|
|
333
|
-
|
|
314
|
+
clearSkillSnapshot();
|
|
315
|
+
resetSkillTelemetry();
|
|
334
316
|
}
|
|
335
|
-
catch {
|
|
336
|
-
|
|
317
|
+
catch (e) {
|
|
318
|
+
debugLog("stop-cleanup-skills", { error: e instanceof Error ? e.message : String(e) });
|
|
337
319
|
}
|
|
338
|
-
|
|
339
|
-
const ledger = getLedger();
|
|
340
|
-
if (ledger && ledger.units.length > 0) {
|
|
341
|
-
const totals = getProjectTotals(ledger.units);
|
|
342
|
-
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, "info");
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
|
|
346
|
-
}
|
|
347
|
-
if (s.basePath) {
|
|
320
|
+
// ── Step 3: SIGTERM handler ──
|
|
348
321
|
try {
|
|
349
|
-
|
|
322
|
+
deregisterSigtermHandler();
|
|
350
323
|
}
|
|
351
324
|
catch (e) {
|
|
352
|
-
debugLog("stop-
|
|
353
|
-
error: e instanceof Error ? e.message : String(e),
|
|
354
|
-
});
|
|
325
|
+
debugLog("stop-cleanup-sigterm", { error: e instanceof Error ? e.message : String(e) });
|
|
355
326
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
327
|
+
// ── Step 4: Auto-worktree exit ──
|
|
328
|
+
try {
|
|
329
|
+
if (s.currentMilestoneId) {
|
|
330
|
+
const notifyCtx = ctx
|
|
331
|
+
? { notify: ctx.ui.notify.bind(ctx.ui) }
|
|
332
|
+
: { notify: () => { } };
|
|
333
|
+
buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
|
|
334
|
+
preserveBranch: true,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
|
|
340
|
+
}
|
|
341
|
+
// ── Step 5: DB cleanup ──
|
|
342
|
+
if (isDbAvailable()) {
|
|
343
|
+
try {
|
|
344
|
+
const { closeDatabase } = await import("./gsd-db.js");
|
|
345
|
+
closeDatabase();
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
debugLog("db-close-failed", {
|
|
349
|
+
error: e instanceof Error ? e.message : String(e),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// ── Step 6: Restore basePath and chdir ──
|
|
354
|
+
try {
|
|
355
|
+
if (s.originalBasePath) {
|
|
356
|
+
s.basePath = s.originalBasePath;
|
|
357
|
+
try {
|
|
358
|
+
process.chdir(s.basePath);
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
/* best-effort */
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
367
|
+
}
|
|
368
|
+
// ── Step 7: Ledger notification ──
|
|
369
|
+
try {
|
|
370
|
+
const ledger = getLedger();
|
|
371
|
+
if (ledger && ledger.units.length > 0) {
|
|
372
|
+
const totals = getProjectTotals(ledger.units);
|
|
373
|
+
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, "info");
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch (e) {
|
|
380
|
+
debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
|
|
381
|
+
}
|
|
382
|
+
// ── Step 8: Rebuild state ──
|
|
383
|
+
if (s.basePath) {
|
|
384
|
+
try {
|
|
385
|
+
await rebuildState(s.basePath);
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
debugLog("stop-rebuild-state-failed", {
|
|
389
|
+
error: e instanceof Error ? e.message : String(e),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// ── Step 9: Cmux sidebar / event log ──
|
|
394
|
+
try {
|
|
395
|
+
clearCmuxSidebar(loadedPreferences);
|
|
396
|
+
logCmuxEvent(loadedPreferences, `Auto-mode stopped${reasonSuffix || ""}.`, reason?.startsWith("Blocked:") ? "warning" : "info");
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
debugLog("stop-cleanup-cmux", { error: e instanceof Error ? e.message : String(e) });
|
|
400
|
+
}
|
|
401
|
+
// ── Step 10: Debug summary ──
|
|
402
|
+
try {
|
|
403
|
+
if (isDebugEnabled()) {
|
|
404
|
+
const logPath = writeDebugSummary();
|
|
405
|
+
if (logPath) {
|
|
406
|
+
ctx?.ui.notify(`Debug log written → ${logPath}`, "info");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
debugLog("stop-cleanup-debug", { error: e instanceof Error ? e.message : String(e) });
|
|
412
|
+
}
|
|
413
|
+
// ── Step 11: Reset metrics, routing, hooks ──
|
|
414
|
+
try {
|
|
415
|
+
resetMetrics();
|
|
416
|
+
resetRoutingHistory();
|
|
417
|
+
resetHookState();
|
|
418
|
+
if (s.basePath)
|
|
419
|
+
clearPersistedHookState(s.basePath);
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
debugLog("stop-cleanup-metrics", { error: e instanceof Error ? e.message : String(e) });
|
|
423
|
+
}
|
|
424
|
+
// ── Step 12: Remove paused-session metadata (#1383) ──
|
|
425
|
+
try {
|
|
426
|
+
const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
|
|
427
|
+
if (existsSync(pausedPath))
|
|
428
|
+
unlinkSync(pausedPath);
|
|
429
|
+
}
|
|
430
|
+
catch { /* non-fatal */ }
|
|
431
|
+
// ── Step 13: Restore original model (before reset clears IDs) ──
|
|
432
|
+
try {
|
|
433
|
+
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
434
|
+
const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
|
|
435
|
+
if (original)
|
|
436
|
+
await pi.setModel(original);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
|
|
363
441
|
}
|
|
364
442
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
s.paused = false;
|
|
379
|
-
s.stepMode = false;
|
|
380
|
-
s.unitDispatchCount.clear();
|
|
381
|
-
s.unitRecoveryCount.clear();
|
|
382
|
-
clearInFlightTools();
|
|
383
|
-
s.lastBudgetAlertLevel = 0;
|
|
384
|
-
s.lastStateRebuildAt = 0;
|
|
385
|
-
s.unitLifetimeDispatches.clear();
|
|
386
|
-
s.currentUnit = null;
|
|
387
|
-
s.autoModeStartModel = null;
|
|
388
|
-
s.currentMilestoneId = null;
|
|
389
|
-
s.originalBasePath = "";
|
|
390
|
-
s.completedUnits = [];
|
|
391
|
-
s.pendingQuickTasks = [];
|
|
392
|
-
clearSliceProgressCache();
|
|
393
|
-
clearActivityLogState();
|
|
394
|
-
resetProactiveHealing();
|
|
395
|
-
s.pendingCrashRecovery = null;
|
|
396
|
-
s.pendingVerificationRetry = null;
|
|
397
|
-
s.verificationRetryCount.clear();
|
|
398
|
-
s.pausedSessionFile = null;
|
|
399
|
-
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
400
|
-
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
401
|
-
ctx?.ui.setFooter(undefined);
|
|
402
|
-
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
403
|
-
const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
|
|
404
|
-
if (original)
|
|
405
|
-
await pi.setModel(original);
|
|
406
|
-
s.originalModelId = null;
|
|
407
|
-
s.originalModelProvider = null;
|
|
443
|
+
finally {
|
|
444
|
+
// ── Critical invariants: these MUST execute regardless of errors ──
|
|
445
|
+
// External cleanup (not covered by session reset)
|
|
446
|
+
clearInFlightTools();
|
|
447
|
+
clearSliceProgressCache();
|
|
448
|
+
clearActivityLogState();
|
|
449
|
+
resetProactiveHealing();
|
|
450
|
+
// UI cleanup
|
|
451
|
+
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
452
|
+
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
453
|
+
ctx?.ui.setFooter(undefined);
|
|
454
|
+
// Reset all session state in one call
|
|
455
|
+
s.reset();
|
|
408
456
|
}
|
|
409
|
-
s.cmdCtx = null;
|
|
410
457
|
}
|
|
411
458
|
/**
|
|
412
459
|
* Pause auto-mode without destroying state. Context is preserved.
|
|
@@ -30,8 +30,16 @@ const VALID_CLASSIFICATIONS = [
|
|
|
30
30
|
*/
|
|
31
31
|
export function resolveCapturesPath(basePath) {
|
|
32
32
|
const resolved = resolve(basePath);
|
|
33
|
+
// Direct layout: /.gsd/worktrees/
|
|
33
34
|
const worktreeMarker = `${sep}.gsd${sep}worktrees${sep}`;
|
|
34
|
-
|
|
35
|
+
let idx = resolved.indexOf(worktreeMarker);
|
|
36
|
+
if (idx === -1) {
|
|
37
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
38
|
+
const symlinkRe = new RegExp(`\\${sep}\\.gsd\\${sep}projects\\${sep}[a-f0-9]+\\${sep}worktrees\\${sep}`);
|
|
39
|
+
const match = resolved.match(symlinkRe);
|
|
40
|
+
if (match && match.index !== undefined)
|
|
41
|
+
idx = match.index;
|
|
42
|
+
}
|
|
35
43
|
if (idx !== -1) {
|
|
36
44
|
// basePath is inside a worktree — resolve to project root
|
|
37
45
|
const projectRoot = resolved.slice(0, idx);
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
11
12
|
// ─── Registry I/O ───────────────────────────────────────────────────────────
|
|
12
13
|
function getRegistryPath() {
|
|
13
|
-
return join(
|
|
14
|
+
return join(gsdHome, "extensions", "registry.json");
|
|
14
15
|
}
|
|
15
16
|
function getAgentExtensionsDir() {
|
|
16
|
-
return join(
|
|
17
|
+
return join(gsdHome, "agent", "extensions");
|
|
17
18
|
}
|
|
18
19
|
function loadRegistry() {
|
|
19
20
|
const filePath = getRegistryPath();
|
|
@@ -10,7 +10,7 @@ import { deriveState } from "./state.js";
|
|
|
10
10
|
import { gsdRoot } from "./paths.js";
|
|
11
11
|
import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
12
12
|
import { appendOverride, appendKnowledge } from "./files.js";
|
|
13
|
-
import { formatDoctorIssuesForPrompt, formatDoctorReport, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
13
|
+
import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
14
14
|
import { isAutoActive } from "./auto.js";
|
|
15
15
|
import { projectRoot } from "./commands.js";
|
|
16
16
|
import { loadPrompt } from "./prompt-loader.js";
|
|
@@ -28,15 +28,28 @@ export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function handleDoctor(args, ctx, pi) {
|
|
30
30
|
const trimmed = args.trim();
|
|
31
|
-
|
|
31
|
+
// Extract flags before positional parsing
|
|
32
|
+
const jsonMode = trimmed.includes("--json");
|
|
33
|
+
const dryRun = trimmed.includes("--dry-run");
|
|
34
|
+
const includeBuild = trimmed.includes("--build");
|
|
35
|
+
const includeTests = trimmed.includes("--test");
|
|
36
|
+
const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
|
|
37
|
+
const parts = stripped ? stripped.split(/\s+/) : [];
|
|
32
38
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
33
39
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
34
40
|
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
35
41
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
36
42
|
const report = await runGSDDoctor(projectRoot(), {
|
|
37
|
-
fix: mode === "fix" || mode === "heal",
|
|
43
|
+
fix: mode === "fix" || mode === "heal" || dryRun,
|
|
44
|
+
dryRun,
|
|
38
45
|
scope: effectiveScope,
|
|
46
|
+
includeBuild,
|
|
47
|
+
includeTests,
|
|
39
48
|
});
|
|
49
|
+
if (jsonMode) {
|
|
50
|
+
ctx.ui.notify(formatDoctorReportJson(report), "info");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
40
53
|
const reportText = formatDoctorReport(report, {
|
|
41
54
|
scope: effectiveScope,
|
|
42
55
|
includeWarnings: mode === "audit",
|