gsd-pi 2.37.0 → 2.37.1-dev.3bbb0a9
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 +21 -20
- package/dist/onboarding.js +1 -0
- package/dist/resources/extensions/cmux/package.json +7 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +67 -1
- package/dist/resources/extensions/gsd/auto-loop.js +18 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +14 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +91 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto.js +42 -5
- package/dist/resources/extensions/gsd/commands.js +80 -33
- package/dist/resources/extensions/gsd/doctor-providers.js +35 -1
- package/dist/resources/extensions/gsd/files.js +41 -0
- package/dist/resources/extensions/gsd/git-service.js +9 -1
- package/dist/resources/extensions/gsd/history.js +2 -1
- package/dist/resources/extensions/gsd/metrics.js +4 -2
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +42 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/session-lock.js +26 -6
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/shared/format-utils.js +5 -41
- package/dist/resources/extensions/shared/layout-utils.js +46 -0
- package/dist/resources/extensions/shared/mod.js +2 -1
- 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/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -4
- 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/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/package.json +7 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +93 -0
- package/src/resources/extensions/gsd/auto-loop.ts +24 -6
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +125 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto.ts +56 -5
- package/src/resources/extensions/gsd/commands.ts +85 -31
- package/src/resources/extensions/gsd/doctor-providers.ts +38 -1
- package/src/resources/extensions/gsd/files.ts +45 -0
- package/src/resources/extensions/gsd/git-service.ts +12 -1
- package/src/resources/extensions/gsd/history.ts +2 -1
- package/src/resources/extensions/gsd/metrics.ts +4 -2
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +41 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/session-lock.ts +41 -6
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +37 -1
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +108 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -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/session-lock-regression.test.ts +45 -0
- package/src/resources/extensions/gsd/types.ts +43 -0
- package/src/resources/extensions/shared/format-utils.ts +5 -44
- package/src/resources/extensions/shared/layout-utils.ts +49 -0
- package/src/resources/extensions/shared/mod.ts +7 -4
- package/src/resources/extensions/shared/tests/format-utils.test.ts +5 -3
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ This version is different. GSD is now a standalone CLI built on the [Pi SDK](htt
|
|
|
16
16
|
|
|
17
17
|
One command. Walk away. Come back to a built project with clean git history.
|
|
18
18
|
|
|
19
|
-
<pre><code>npm install -g gsd-pi</code></pre>
|
|
19
|
+
<pre><code>npm install -g gsd-pi@latest</code></pre>
|
|
20
20
|
|
|
21
21
|
> **📋 NOTICE: New to Node on Mac?** If you installed Node.js via Homebrew, you may be running a development release instead of LTS. **[Read this guide](./docs/node-lts-macos.md)** to pin Node 24 LTS and avoid compatibility issues.
|
|
22
22
|
|
|
@@ -24,19 +24,19 @@ One command. Walk away. Come back to a built project with clean git history.
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## What's New in v2.
|
|
27
|
+
## What's New in v2.37
|
|
28
28
|
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
39
|
-
-
|
|
29
|
+
- **cmux integration** — sidebar status, progress bars, and notifications for [cmux](https://cmux.com) terminal multiplexer users
|
|
30
|
+
- **Redesigned dashboard** — two-column layout with redesigned widget
|
|
31
|
+
- **Search budget enforcement** — session-level search budget prevents unbounded native web search
|
|
32
|
+
- **AGENTS.md support** — deprecated `agent-instructions.md` in favor of standard `AGENTS.md` / `CLAUDE.md`
|
|
33
|
+
- **AI-powered triage** — automated issue and PR triage via Claude Haiku
|
|
34
|
+
- **Auto-generated OpenRouter registry** — model registry built from OpenRouter API for always-current model support
|
|
35
|
+
- **Extension manifest system** — user-managed enable/disable for bundled extensions
|
|
36
|
+
- **Pipeline simplification (ADR-003)** — merged research into planning, mechanical completion
|
|
37
|
+
- **Workflow templates** — right-sized workflows for every task type
|
|
38
|
+
- **Health widget** — always-on environment health checks with progress scoring
|
|
39
|
+
- **`/gsd changelog`** — LLM-summarized release notes for any version
|
|
40
40
|
|
|
41
41
|
See the full [Changelog](./CHANGELOG.md) for details.
|
|
42
42
|
|
|
@@ -49,7 +49,7 @@ Full documentation is available in the [`docs/`](./docs/) directory:
|
|
|
49
49
|
- **[Getting Started](./docs/getting-started.md)** — install, first run, basic usage
|
|
50
50
|
- **[Auto Mode](./docs/auto-mode.md)** — autonomous execution deep-dive
|
|
51
51
|
- **[Configuration](./docs/configuration.md)** — all preferences, models, git, and hooks
|
|
52
|
-
- **[Token Optimization](./docs/token-optimization.md)** — profiles, context compression, complexity routing
|
|
52
|
+
- **[Token Optimization](./docs/token-optimization.md)** — profiles, context compression, complexity routing
|
|
53
53
|
- **[Cost Management](./docs/cost-management.md)** — budgets, tracking, projections
|
|
54
54
|
- **[Git Strategy](./docs/git-strategy.md)** — worktree isolation, branching, merge behavior
|
|
55
55
|
- **[Parallel Orchestration](./docs/parallel-orchestration.md)** — run multiple milestones simultaneously
|
|
@@ -463,9 +463,9 @@ Place an `AGENTS.md` file in any directory to provide persistent behavioral guid
|
|
|
463
463
|
|
|
464
464
|
Start GSD with `gsd --debug` to enable structured JSONL diagnostic logging. Debug logs capture dispatch decisions, state transitions, and timing data for troubleshooting auto-mode issues.
|
|
465
465
|
|
|
466
|
-
### Token Optimization
|
|
466
|
+
### Token Optimization
|
|
467
467
|
|
|
468
|
-
GSD
|
|
468
|
+
GSD includes a coordinated token optimization system that reduces usage by 40-60% on cost-sensitive workloads. Set a single preference to coordinate model selection, phase skipping, and context compression:
|
|
469
469
|
|
|
470
470
|
```yaml
|
|
471
471
|
token_profile: budget # or balanced (default), quality
|
|
@@ -485,7 +485,7 @@ See the full [Token Optimization Guide](./docs/token-optimization.md) for detail
|
|
|
485
485
|
|
|
486
486
|
### Bundled Tools
|
|
487
487
|
|
|
488
|
-
GSD ships with
|
|
488
|
+
GSD ships with 19 extensions, all loaded automatically:
|
|
489
489
|
|
|
490
490
|
| Extension | What it provides |
|
|
491
491
|
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -495,12 +495,13 @@ GSD ships with 18 extensions, all loaded automatically:
|
|
|
495
495
|
| **Google Search** | Gemini-powered web search with AI-synthesized answers |
|
|
496
496
|
| **Context7** | Up-to-date library/framework documentation |
|
|
497
497
|
| **Background Shell** | Long-running process management with readiness detection |
|
|
498
|
+
| **Async Jobs** | Background bash commands with job tracking and cancellation |
|
|
498
499
|
| **Subagent** | Delegated tasks with isolated context windows |
|
|
500
|
+
| **GitHub** | Full-suite GitHub issues and PR management via `/gh` command |
|
|
499
501
|
| **Mac Tools** | macOS native app automation via Accessibility APIs |
|
|
500
502
|
| **MCP Client** | Native MCP server integration via @modelcontextprotocol/sdk |
|
|
501
503
|
| **Voice** | Real-time speech-to-text transcription (macOS, Linux — Ubuntu 22.04+) |
|
|
502
504
|
| **Slash Commands** | Custom command creation |
|
|
503
|
-
| **LSP** | Language Server Protocol integration — diagnostics, go-to-definition, references, hover, symbols, rename, code actions |
|
|
504
505
|
| **Ask User Questions** | Structured user input with single/multi-select |
|
|
505
506
|
| **Secure Env Collect** | Masked secret collection without manual .env editing |
|
|
506
507
|
| **Remote Questions** | Route decisions to Slack/Discord when human input is needed in headless/CI mode |
|
|
@@ -591,7 +592,7 @@ gsd (CLI binary)
|
|
|
591
592
|
├─ resource-loader.ts Syncs bundled extensions + agents to ~/.gsd/agent/
|
|
592
593
|
└─ src/resources/
|
|
593
594
|
├─ extensions/gsd/ Core GSD extension (auto, state, commands, ...)
|
|
594
|
-
├─ extensions/...
|
|
595
|
+
├─ extensions/... 18 supporting extensions
|
|
595
596
|
├─ agents/ scout, researcher, worker
|
|
596
597
|
├─ AGENTS.md Agent routing instructions
|
|
597
598
|
└─ GSD-WORKFLOW.md Manual bootstrap protocol
|
|
@@ -628,7 +629,7 @@ GSD isn't locked to one provider. It runs on the [Pi SDK](https://github.com/bad
|
|
|
628
629
|
|
|
629
630
|
### Built-in Providers
|
|
630
631
|
|
|
631
|
-
Anthropic, OpenAI, Google (Gemini), OpenRouter, GitHub Copilot, Amazon Bedrock, Azure OpenAI, Google Vertex, Groq, Cerebras, Mistral, xAI, HuggingFace, Vercel AI Gateway, and more.
|
|
632
|
+
Anthropic, Anthropic (Vertex AI), OpenAI, Google (Gemini), OpenRouter, GitHub Copilot, Amazon Bedrock, Azure OpenAI, Google Vertex, Groq, Cerebras, Mistral, xAI, HuggingFace, Vercel AI Gateway, and more.
|
|
632
633
|
|
|
633
634
|
### OAuth / Max Plans
|
|
634
635
|
|
package/dist/onboarding.js
CHANGED
|
@@ -12,7 +12,7 @@ import { loadFile, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
|
12
12
|
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, relSliceFile, buildMilestoneFileName, } from "./paths.js";
|
|
13
13
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
-
import { buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, buildPlanSlicePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt, buildReplanSlicePrompt, buildRunUatPrompt, buildReassessRoadmapPrompt, buildRewriteDocsPrompt, checkNeedsReassessment, checkNeedsRunUat, } from "./auto-prompts.js";
|
|
15
|
+
import { buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, buildPlanSlicePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildValidateMilestonePrompt, buildReplanSlicePrompt, buildRunUatPrompt, buildReassessRoadmapPrompt, buildRewriteDocsPrompt, buildReactiveExecutePrompt, checkNeedsReassessment, checkNeedsRunUat, } from "./auto-prompts.js";
|
|
16
16
|
function missingSliceStop(mid, phase) {
|
|
17
17
|
return {
|
|
18
18
|
action: "stop",
|
|
@@ -223,6 +223,72 @@ const DISPATCH_RULES = [
|
|
|
223
223
|
};
|
|
224
224
|
},
|
|
225
225
|
},
|
|
226
|
+
{
|
|
227
|
+
name: "executing → reactive-execute (parallel dispatch)",
|
|
228
|
+
match: async ({ state, mid, midTitle, basePath, prefs }) => {
|
|
229
|
+
if (state.phase !== "executing" || !state.activeTask)
|
|
230
|
+
return null;
|
|
231
|
+
if (!state.activeSlice)
|
|
232
|
+
return null; // fall through
|
|
233
|
+
// Only activate when reactive_execution is explicitly enabled
|
|
234
|
+
const reactiveConfig = prefs?.reactive_execution;
|
|
235
|
+
if (!reactiveConfig?.enabled)
|
|
236
|
+
return null;
|
|
237
|
+
const sid = state.activeSlice.id;
|
|
238
|
+
const sTitle = state.activeSlice.title;
|
|
239
|
+
const maxParallel = reactiveConfig.max_parallel ?? 2;
|
|
240
|
+
// Dry-run mode: max_parallel=1 means graph is derived and logged but
|
|
241
|
+
// execution remains sequential
|
|
242
|
+
if (maxParallel <= 1)
|
|
243
|
+
return null;
|
|
244
|
+
try {
|
|
245
|
+
const { loadSliceTaskIO, deriveTaskGraph, isGraphAmbiguous, getReadyTasks, chooseNonConflictingSubset, graphMetrics, } = await import("./reactive-graph.js");
|
|
246
|
+
const taskIO = await loadSliceTaskIO(basePath, mid, sid);
|
|
247
|
+
if (taskIO.length < 2)
|
|
248
|
+
return null; // single task, no point
|
|
249
|
+
const graph = deriveTaskGraph(taskIO);
|
|
250
|
+
// Ambiguous graph → fall through to sequential
|
|
251
|
+
if (isGraphAmbiguous(graph))
|
|
252
|
+
return null;
|
|
253
|
+
const completed = new Set(graph.filter((n) => n.done).map((n) => n.id));
|
|
254
|
+
const readyIds = getReadyTasks(graph, completed, new Set());
|
|
255
|
+
// Only activate reactive dispatch when >1 task is ready
|
|
256
|
+
if (readyIds.length <= 1)
|
|
257
|
+
return null;
|
|
258
|
+
const selected = chooseNonConflictingSubset(readyIds, graph, maxParallel, new Set());
|
|
259
|
+
if (selected.length <= 1)
|
|
260
|
+
return null;
|
|
261
|
+
// Log graph metrics for observability
|
|
262
|
+
const metrics = graphMetrics(graph);
|
|
263
|
+
process.stderr.write(`gsd-reactive: ${mid}/${sid} graph — tasks:${metrics.taskCount} edges:${metrics.edgeCount} ` +
|
|
264
|
+
`ready:${metrics.readySetSize} dispatching:${selected.length} ambiguous:${metrics.ambiguous}\n`);
|
|
265
|
+
// Persist dispatched batch so verification and recovery can check
|
|
266
|
+
// exactly which tasks were sent.
|
|
267
|
+
const { saveReactiveState } = await import("./reactive-graph.js");
|
|
268
|
+
saveReactiveState(basePath, mid, sid, {
|
|
269
|
+
sliceId: sid,
|
|
270
|
+
completed: [...completed],
|
|
271
|
+
dispatched: selected,
|
|
272
|
+
graphSnapshot: metrics,
|
|
273
|
+
updatedAt: new Date().toISOString(),
|
|
274
|
+
});
|
|
275
|
+
// Encode selected task IDs in unitId for artifact verification.
|
|
276
|
+
// Format: M001/S01/reactive+T02,T03
|
|
277
|
+
const batchSuffix = selected.join(",");
|
|
278
|
+
return {
|
|
279
|
+
action: "dispatch",
|
|
280
|
+
unitType: "reactive-execute",
|
|
281
|
+
unitId: `${mid}/${sid}/reactive+${batchSuffix}`,
|
|
282
|
+
prompt: await buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, selected, basePath),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
// Non-fatal — fall through to sequential execution
|
|
287
|
+
process.stderr.write(`gsd-reactive: graph derivation failed: ${err.message}\n`);
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
},
|
|
226
292
|
{
|
|
227
293
|
name: "executing → execute-task (recover missing task plan → plan-slice)",
|
|
228
294
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
@@ -218,10 +218,24 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
218
218
|
}
|
|
219
219
|
try {
|
|
220
220
|
// ── Blanket try/catch: one bad iteration must not kill the session
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
const sessionLockBase = deps.lockBase();
|
|
222
|
+
if (sessionLockBase) {
|
|
223
|
+
const lockStatus = deps.validateSessionLock(sessionLockBase);
|
|
224
|
+
if (!lockStatus.valid) {
|
|
225
|
+
debugLog("autoLoop", {
|
|
226
|
+
phase: "session-lock-invalid",
|
|
227
|
+
reason: lockStatus.failureReason ?? "unknown",
|
|
228
|
+
existingPid: lockStatus.existingPid,
|
|
229
|
+
expectedPid: lockStatus.expectedPid,
|
|
230
|
+
});
|
|
231
|
+
deps.handleLostSessionLock(ctx, lockStatus);
|
|
232
|
+
debugLog("autoLoop", {
|
|
233
|
+
phase: "exit",
|
|
234
|
+
reason: "session-lock-lost",
|
|
235
|
+
detail: lockStatus.failureReason ?? "unknown",
|
|
236
|
+
});
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
225
239
|
}
|
|
226
240
|
// ── Phase 1: Pre-dispatch ───────────────────────────────────────────
|
|
227
241
|
// Resource version guard
|
|
@@ -171,6 +171,20 @@ export async function postUnitPreVerification(pctx) {
|
|
|
171
171
|
// Non-fatal
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
// Reactive state cleanup on slice completion
|
|
175
|
+
if (s.currentUnit.type === "complete-slice") {
|
|
176
|
+
try {
|
|
177
|
+
const parts = s.currentUnit.id.split("/");
|
|
178
|
+
const [mid, sid] = parts;
|
|
179
|
+
if (mid && sid) {
|
|
180
|
+
const { clearReactiveState } = await import("./reactive-graph.js");
|
|
181
|
+
clearReactiveState(s.basePath, mid, sid);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Non-fatal
|
|
186
|
+
}
|
|
187
|
+
}
|
|
174
188
|
// Post-triage: execute actionable resolutions
|
|
175
189
|
if (s.currentUnit.type === "triage-captures") {
|
|
176
190
|
try {
|
|
@@ -414,6 +414,35 @@ export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
|
|
|
414
414
|
})
|
|
415
415
|
.map(f => `${sRel}/tasks/${f}`);
|
|
416
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Get carry-forward summary paths scoped to a task's derived dependencies.
|
|
419
|
+
*
|
|
420
|
+
* Instead of all prior tasks (order-based), returns only summaries for task
|
|
421
|
+
* IDs in `dependsOn`. Used by reactive-execute to give each subagent only
|
|
422
|
+
* the context it actually needs — not sibling tasks from a parallel batch.
|
|
423
|
+
*
|
|
424
|
+
* Falls back to order-based when dependsOn is empty (root tasks still get
|
|
425
|
+
* any available prior summaries for continuity).
|
|
426
|
+
*/
|
|
427
|
+
export async function getDependencyTaskSummaryPaths(mid, sid, currentTid, dependsOn, base) {
|
|
428
|
+
// If no dependencies, fall back to order-based for root tasks
|
|
429
|
+
if (dependsOn.length === 0) {
|
|
430
|
+
return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
|
|
431
|
+
}
|
|
432
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
433
|
+
if (!tDir)
|
|
434
|
+
return [];
|
|
435
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
436
|
+
const sRel = relSlicePath(base, mid, sid);
|
|
437
|
+
const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
|
|
438
|
+
return summaryFiles
|
|
439
|
+
.filter((f) => {
|
|
440
|
+
// Extract task ID from filename: "T02-SUMMARY.md" → "T02"
|
|
441
|
+
const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
|
|
442
|
+
return depSet.has(tid);
|
|
443
|
+
})
|
|
444
|
+
.map((f) => `${sRel}/tasks/${f}`);
|
|
445
|
+
}
|
|
417
446
|
// ─── Adaptive Replanning Checks ────────────────────────────────────────────
|
|
418
447
|
/**
|
|
419
448
|
* Check if the most recently completed slice needs reassessment.
|
|
@@ -688,8 +717,11 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
688
717
|
});
|
|
689
718
|
}
|
|
690
719
|
export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, level) {
|
|
691
|
-
const
|
|
692
|
-
|
|
720
|
+
const opts = typeof level === "object" && level !== null && !Array.isArray(level)
|
|
721
|
+
? level
|
|
722
|
+
: { level: level };
|
|
723
|
+
const inlineLevel = opts.level ?? resolveInlineLevel();
|
|
724
|
+
const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
693
725
|
const priorLines = priorSummaries.length > 0
|
|
694
726
|
? priorSummaries.map(p => `- \`${p}\``).join("\n")
|
|
695
727
|
: "- (no prior tasks)";
|
|
@@ -1090,6 +1122,63 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1090
1122
|
commitInstruction: reassessCommitInstruction,
|
|
1091
1123
|
});
|
|
1092
1124
|
}
|
|
1125
|
+
// ─── Reactive Execute Prompt ──────────────────────────────────────────────
|
|
1126
|
+
export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base) {
|
|
1127
|
+
const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
|
|
1128
|
+
// Build graph for context
|
|
1129
|
+
const taskIO = await loadSliceTaskIO(base, mid, sid);
|
|
1130
|
+
const graph = deriveTaskGraph(taskIO);
|
|
1131
|
+
const metrics = graphMetrics(graph);
|
|
1132
|
+
// Build graph context section
|
|
1133
|
+
const graphLines = [];
|
|
1134
|
+
for (const node of graph) {
|
|
1135
|
+
const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
|
|
1136
|
+
const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
|
|
1137
|
+
graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
|
|
1138
|
+
if (node.outputFiles.length > 0) {
|
|
1139
|
+
graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
const graphContext = [
|
|
1143
|
+
`Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
|
|
1144
|
+
"",
|
|
1145
|
+
...graphLines,
|
|
1146
|
+
].join("\n");
|
|
1147
|
+
// Build individual subagent prompts for each ready task
|
|
1148
|
+
const subagentSections = [];
|
|
1149
|
+
const readyTaskListLines = [];
|
|
1150
|
+
for (const tid of readyTaskIds) {
|
|
1151
|
+
const node = graph.find((n) => n.id === tid);
|
|
1152
|
+
const tTitle = node?.title ?? tid;
|
|
1153
|
+
readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
|
|
1154
|
+
// Build dependency-scoped carry-forward paths for this task
|
|
1155
|
+
const depPaths = await getDependencyTaskSummaryPaths(mid, sid, tid, node?.dependsOn ?? [], base);
|
|
1156
|
+
// Build a full execute-task prompt with dependency-based carry-forward
|
|
1157
|
+
const taskPrompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, { carryForwardPaths: depPaths });
|
|
1158
|
+
subagentSections.push([
|
|
1159
|
+
`### ${tid}: ${tTitle}`,
|
|
1160
|
+
"",
|
|
1161
|
+
"Use this as the prompt for a `subagent` call:",
|
|
1162
|
+
"",
|
|
1163
|
+
"```",
|
|
1164
|
+
taskPrompt,
|
|
1165
|
+
"```",
|
|
1166
|
+
].join("\n"));
|
|
1167
|
+
}
|
|
1168
|
+
const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
|
|
1169
|
+
return loadPrompt("reactive-execute", {
|
|
1170
|
+
workingDirectory: base,
|
|
1171
|
+
milestoneId: mid,
|
|
1172
|
+
milestoneTitle: midTitle,
|
|
1173
|
+
sliceId: sid,
|
|
1174
|
+
sliceTitle: sTitle,
|
|
1175
|
+
graphContext,
|
|
1176
|
+
readyTaskCount: String(readyTaskIds.length),
|
|
1177
|
+
readyTaskList: readyTaskListLines.join("\n"),
|
|
1178
|
+
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
1179
|
+
inlinedTemplates,
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1093
1182
|
export async function buildRewriteDocsPrompt(mid, midTitle, activeSlice, base, overrides) {
|
|
1094
1183
|
const sid = activeSlice?.id;
|
|
1095
1184
|
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).
|
|
@@ -18,7 +18,7 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
18
18
|
import { clearActivityLogState } from "./activity-log.js";
|
|
19
19
|
import { synthesizeCrashRecovery, getDeepDiagnostic, } from "./session-forensics.js";
|
|
20
20
|
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, } from "./crash-recovery.js";
|
|
21
|
-
import { acquireSessionLock,
|
|
21
|
+
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
22
22
|
import { clearUnitRuntimeRecord, readUnitRuntimeRecord, writeUnitRuntimeRecord, } from "./unit-runtime.js";
|
|
23
23
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
|
|
24
24
|
import { sendDesktopNotification } from "./notifications.js";
|
|
@@ -209,6 +209,29 @@ export function stopAutoRemote(projectRoot) {
|
|
|
209
209
|
return { found: false, error: err.message };
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if a remote auto-mode session is running (from a different process).
|
|
214
|
+
* Reads the crash lock, checks PID liveness, and returns session details.
|
|
215
|
+
* Used by the guard in commands.ts to prevent bare /gsd, /gsd next, and
|
|
216
|
+
* /gsd auto from stealing the session lock.
|
|
217
|
+
*/
|
|
218
|
+
export function checkRemoteAutoSession(projectRoot) {
|
|
219
|
+
const lock = readCrashLock(projectRoot);
|
|
220
|
+
if (!lock)
|
|
221
|
+
return { running: false };
|
|
222
|
+
if (!isLockProcessAlive(lock)) {
|
|
223
|
+
// Stale lock from a dead process — not a live remote session
|
|
224
|
+
return { running: false };
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
running: true,
|
|
228
|
+
pid: lock.pid,
|
|
229
|
+
unitType: lock.unitType,
|
|
230
|
+
unitId: lock.unitId,
|
|
231
|
+
startedAt: lock.startedAt,
|
|
232
|
+
completedUnits: lock.completedUnits,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
212
235
|
export function isStepMode() {
|
|
213
236
|
return s.stepMode;
|
|
214
237
|
}
|
|
@@ -243,14 +266,28 @@ function buildSnapshotOpts(unitType, unitId) {
|
|
|
243
266
|
...(runtime?.continueHereFired ? { continueHereFired: true } : {}),
|
|
244
267
|
};
|
|
245
268
|
}
|
|
246
|
-
function handleLostSessionLock(ctx) {
|
|
247
|
-
debugLog("session-lock-lost", {
|
|
269
|
+
function handleLostSessionLock(ctx, lockStatus) {
|
|
270
|
+
debugLog("session-lock-lost", {
|
|
271
|
+
lockBase: lockBase(),
|
|
272
|
+
reason: lockStatus?.failureReason,
|
|
273
|
+
existingPid: lockStatus?.existingPid,
|
|
274
|
+
expectedPid: lockStatus?.expectedPid,
|
|
275
|
+
});
|
|
248
276
|
s.active = false;
|
|
249
277
|
s.paused = false;
|
|
250
278
|
clearUnitTimeout();
|
|
251
279
|
deregisterSigtermHandler();
|
|
252
280
|
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
253
|
-
|
|
281
|
+
const message = lockStatus?.failureReason === "pid-mismatch"
|
|
282
|
+
? lockStatus.existingPid
|
|
283
|
+
? `Session lock moved to PID ${lockStatus.existingPid} — another GSD process appears to have taken over. Stopping gracefully.`
|
|
284
|
+
: "Session lock moved to a different process — another GSD process appears to have taken over. Stopping gracefully."
|
|
285
|
+
: lockStatus?.failureReason === "missing-metadata"
|
|
286
|
+
? "Session lock metadata disappeared, so ownership could not be confirmed. Stopping gracefully."
|
|
287
|
+
: lockStatus?.failureReason === "compromised"
|
|
288
|
+
? "Session lock was compromised or invalidated during heartbeat checks; takeover was not confirmed. Stopping gracefully."
|
|
289
|
+
: "Session lock lost. Stopping gracefully.";
|
|
290
|
+
ctx?.ui.notify(message, "error");
|
|
254
291
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
255
292
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
256
293
|
ctx?.ui.setFooter(undefined);
|
|
@@ -473,7 +510,7 @@ function buildLoopDeps() {
|
|
|
473
510
|
// Resource version guard
|
|
474
511
|
checkResourcesStale,
|
|
475
512
|
// Session lock
|
|
476
|
-
validateSessionLock,
|
|
513
|
+
validateSessionLock: getSessionLockStatus,
|
|
477
514
|
updateSessionLock,
|
|
478
515
|
handleLostSessionLock,
|
|
479
516
|
// Milestone transition
|