gsd-pi 2.31.2 → 2.32.0-dev.3d7932c
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 +27 -20
- package/dist/cli.js +5 -5
- package/dist/resource-loader.js +13 -3
- package/dist/resources/extensions/gsd/auto-constants.ts +6 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +23 -27
- package/dist/resources/extensions/gsd/auto-direct-dispatch.ts +1 -6
- package/dist/resources/extensions/gsd/auto-dispatch.ts +4 -8
- package/dist/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/dist/resources/extensions/gsd/auto-observability.ts +2 -4
- package/dist/resources/extensions/gsd/auto-post-unit.ts +32 -37
- package/dist/resources/extensions/gsd/auto-prompts.ts +84 -78
- package/dist/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/dist/resources/extensions/gsd/auto-start.ts +16 -12
- package/dist/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/dist/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/dist/resources/extensions/gsd/auto-timers.ts +3 -2
- package/dist/resources/extensions/gsd/auto-verification.ts +6 -6
- package/dist/resources/extensions/gsd/auto-worktree.ts +5 -4
- package/dist/resources/extensions/gsd/auto.ts +82 -60
- package/dist/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +5 -6
- package/dist/resources/extensions/gsd/commands.ts +19 -0
- package/dist/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/dist/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +28 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/dist/resources/extensions/gsd/doctor-environment.ts +497 -0
- package/dist/resources/extensions/gsd/doctor-providers.ts +343 -0
- package/dist/resources/extensions/gsd/doctor-types.ts +14 -1
- package/dist/resources/extensions/gsd/doctor.ts +6 -0
- package/dist/resources/extensions/gsd/error-utils.ts +6 -0
- package/dist/resources/extensions/gsd/export.ts +2 -1
- package/dist/resources/extensions/gsd/git-service.ts +12 -2
- package/dist/resources/extensions/gsd/guided-flow-queue.ts +1 -8
- package/dist/resources/extensions/gsd/guided-flow.ts +3 -2
- package/dist/resources/extensions/gsd/health-widget.ts +167 -0
- package/dist/resources/extensions/gsd/index.ts +18 -5
- package/dist/resources/extensions/gsd/key-manager.ts +2 -1
- package/dist/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/dist/resources/extensions/gsd/metrics.ts +3 -3
- package/dist/resources/extensions/gsd/migrate-external.ts +21 -4
- package/dist/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/dist/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/dist/resources/extensions/gsd/preferences-types.ts +8 -0
- package/dist/resources/extensions/gsd/preferences-validation.ts +3 -10
- package/dist/resources/extensions/gsd/progress-score.ts +273 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -42
- package/dist/resources/extensions/gsd/quick.ts +61 -8
- package/dist/resources/extensions/gsd/repo-identity.ts +22 -1
- package/dist/resources/extensions/gsd/session-lock.ts +12 -1
- package/dist/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +127 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/doctor-environment.test.ts +314 -0
- package/dist/resources/extensions/gsd/tests/doctor-providers.test.ts +298 -0
- package/dist/resources/extensions/gsd/tests/export-html-enhancements.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +7 -3
- package/dist/resources/extensions/gsd/tests/progress-score.test.ts +206 -0
- package/dist/resources/extensions/gsd/tests/run-uat.test.ts +56 -7
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +12 -0
- package/dist/resources/extensions/gsd/undo.ts +5 -7
- package/dist/resources/extensions/gsd/unit-id.ts +14 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +60 -2
- package/dist/resources/extensions/gsd/visualizer-views.ts +54 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +8 -7
- package/dist/worktree-cli.d.ts +42 -6
- package/dist/worktree-cli.js +88 -48
- package/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-constants.ts +6 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +23 -27
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -6
- package/src/resources/extensions/gsd/auto-dispatch.ts +4 -8
- package/src/resources/extensions/gsd/auto-idempotency.ts +3 -2
- package/src/resources/extensions/gsd/auto-observability.ts +2 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +32 -37
- package/src/resources/extensions/gsd/auto-prompts.ts +84 -78
- package/src/resources/extensions/gsd/auto-recovery.ts +8 -22
- package/src/resources/extensions/gsd/auto-start.ts +16 -12
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +3 -2
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/auto-timers.ts +3 -2
- package/src/resources/extensions/gsd/auto-verification.ts +6 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +5 -4
- package/src/resources/extensions/gsd/auto.ts +82 -60
- package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -6
- package/src/resources/extensions/gsd/commands.ts +19 -0
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -7
- package/src/resources/extensions/gsd/crash-recovery.ts +15 -2
- package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/src/resources/extensions/gsd/doctor-environment.ts +497 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +343 -0
- package/src/resources/extensions/gsd/doctor-types.ts +14 -1
- package/src/resources/extensions/gsd/doctor.ts +6 -0
- package/src/resources/extensions/gsd/error-utils.ts +6 -0
- package/src/resources/extensions/gsd/export.ts +2 -1
- package/src/resources/extensions/gsd/git-service.ts +12 -2
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -8
- package/src/resources/extensions/gsd/guided-flow.ts +3 -2
- package/src/resources/extensions/gsd/health-widget.ts +167 -0
- package/src/resources/extensions/gsd/index.ts +18 -5
- package/src/resources/extensions/gsd/key-manager.ts +2 -1
- package/src/resources/extensions/gsd/marketplace-discovery.ts +4 -3
- package/src/resources/extensions/gsd/metrics.ts +3 -3
- package/src/resources/extensions/gsd/migrate-external.ts +21 -4
- package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +2 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +2 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +8 -9
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +3 -10
- package/src/resources/extensions/gsd/progress-score.ts +273 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +1 -42
- package/src/resources/extensions/gsd/quick.ts +61 -8
- package/src/resources/extensions/gsd/repo-identity.ts +22 -1
- package/src/resources/extensions/gsd/session-lock.ts +12 -1
- package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +314 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +298 -0
- package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +206 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +56 -7
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +12 -0
- package/src/resources/extensions/gsd/undo.ts +5 -7
- package/src/resources/extensions/gsd/unit-id.ts +14 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +2 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +60 -2
- package/src/resources/extensions/gsd/visualizer-views.ts +54 -0
- package/src/resources/extensions/gsd/worktree-command.ts +8 -7
package/README.md
CHANGED
|
@@ -24,21 +24,24 @@ 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.
|
|
28
|
-
|
|
29
|
-
- **
|
|
30
|
-
-
|
|
31
|
-
- **
|
|
32
|
-
-
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
41
|
-
-
|
|
27
|
+
## What's New in v2.32
|
|
28
|
+
|
|
29
|
+
- **Simplified pipeline** — research merged into planning, mechanical completion (ADR-003)
|
|
30
|
+
- **Always-on health widget** — 🟢🟡🔴 traffic-light indicator in the progress widget and visualizer health tab
|
|
31
|
+
- **Environment health checks** — progress scoring and status integration for auto-mode
|
|
32
|
+
- **Extension registry** — user-managed enable/disable for bundled and custom extensions
|
|
33
|
+
- **Built-in skill authoring** — create and distribute custom skills from within GSD
|
|
34
|
+
- **Workflow templates** — right-sized workflows for every task type (research, plan, execute, complete)
|
|
35
|
+
- **AWS Bedrock auth** — automatic credential refresh via the new `aws-auth` extension
|
|
36
|
+
- **`-w` / `--worktree` CLI flag** — launch isolated worktree sessions from the command line
|
|
37
|
+
- **Native MCP client** — replaced MCPorter with a built-in MCP client for better reliability
|
|
38
|
+
- **External state directory** — `.gsd/` now lives in `~/.gsd/projects/` with a symlink (ADR-002)
|
|
39
|
+
- **Model health indicator** — live health status based on error trends and consecutive failures
|
|
40
|
+
- **Quick-task branch cleanup** — `/gsd quick` branches auto-merge back after completion
|
|
41
|
+
- **Windows EPERM fallback** — migration rename uses copy+delete when NTFS blocks rename
|
|
42
|
+
- **Worktree identity fix** — stable project hash across worktrees and main repo
|
|
43
|
+
- **Crash recovery guidance** — actionable next-step messages based on what was interrupted
|
|
44
|
+
- **UAT verdict gating** — non-PASS verdicts now block slice progression instead of being ignored
|
|
42
45
|
|
|
43
46
|
See the full [Changelog](./CHANGELOG.md) for details.
|
|
44
47
|
|
|
@@ -65,6 +68,7 @@ Full documentation is available in the [`docs/`](./docs/) directory:
|
|
|
65
68
|
- **[Visualizer](./docs/visualizer.md)** — workflow visualizer with stats and discussion status
|
|
66
69
|
- **[Remote Questions](./docs/remote-questions.md)** — route decisions to Slack or Discord when human input is needed
|
|
67
70
|
- **[Dynamic Model Routing](./docs/dynamic-model-routing.md)** — complexity-based model selection and budget pressure
|
|
71
|
+
- **[Pipeline Simplification (ADR-003)](./docs/ADR-003-pipeline-simplification.md)** — merged research into planning, mechanical completion
|
|
68
72
|
- **[Migration from v1](./docs/migration.md)** — `.planning` → `.gsd` migration
|
|
69
73
|
|
|
70
74
|
---
|
|
@@ -141,12 +145,12 @@ The iron rule: **a task must fit in one context window.** If it can't, it's two
|
|
|
141
145
|
Each slice flows through phases automatically:
|
|
142
146
|
|
|
143
147
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
Plan (with integrated research) → Execute (per task) → Complete → Reassess Roadmap → Next Slice
|
|
149
|
+
↓ (all slices done)
|
|
150
|
+
Validate Milestone → Complete Milestone
|
|
147
151
|
```
|
|
148
152
|
|
|
149
|
-
**
|
|
153
|
+
**Plan** scouts the codebase, researches relevant docs, and decomposes the slice into tasks with must-haves (mechanically verifiable outcomes). **Execute** runs each task in a fresh context window with only the relevant files pre-loaded — then runs configured verification commands (lint, test, etc.) with auto-fix retries. **Complete** writes the summary, UAT script, marks the roadmap, and commits with meaningful messages derived from task summaries. **Reassess** checks if the roadmap still makes sense given what was learned. **Validate Milestone** runs a reconciliation gate after all slices complete — comparing roadmap success criteria against actual results before sealing the milestone.
|
|
150
154
|
|
|
151
155
|
### `/gsd auto` — The Main Event
|
|
152
156
|
|
|
@@ -326,6 +330,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
326
330
|
| `gsd headless [cmd]` | Run `/gsd` commands without TUI (CI, cron, scripts) |
|
|
327
331
|
| `gsd headless query` | Instant JSON snapshot — state, next dispatch, costs (no LLM) |
|
|
328
332
|
| `gsd --continue` (`-c`) | Resume the most recent session for the current directory |
|
|
333
|
+
| `gsd --worktree` (`-w`) | Launch an isolated worktree session for the active milestone |
|
|
329
334
|
| `gsd sessions` | Interactive session picker — browse and resume any saved session |
|
|
330
335
|
|
|
331
336
|
---
|
|
@@ -483,7 +488,7 @@ See the full [Token Optimization Guide](./docs/token-optimization.md) for detail
|
|
|
483
488
|
|
|
484
489
|
### Bundled Tools
|
|
485
490
|
|
|
486
|
-
GSD ships with
|
|
491
|
+
GSD ships with 18 extensions, all loaded automatically:
|
|
487
492
|
|
|
488
493
|
| Extension | What it provides |
|
|
489
494
|
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -503,6 +508,8 @@ GSD ships with 16 extensions, all loaded automatically:
|
|
|
503
508
|
| **Secure Env Collect** | Masked secret collection without manual .env editing |
|
|
504
509
|
| **Remote Questions** | Route decisions to Slack/Discord when human input is needed in headless/CI mode |
|
|
505
510
|
| **Universal Config** | Discover and import MCP servers and rules from other AI coding tools |
|
|
511
|
+
| **AWS Auth** | Automatic Bedrock credential refresh for AWS-hosted models |
|
|
512
|
+
| **TTSR** | Tool-use type-safe runtime validation |
|
|
506
513
|
|
|
507
514
|
### Bundled Agents
|
|
508
515
|
|
package/dist/cli.js
CHANGED
|
@@ -359,16 +359,16 @@ if (cliFlags.messages[0] === 'worktree' || cliFlags.messages[0] === 'wt') {
|
|
|
359
359
|
const sub = cliFlags.messages[1];
|
|
360
360
|
const subArgs = cliFlags.messages.slice(2);
|
|
361
361
|
if (!sub || sub === 'list') {
|
|
362
|
-
handleList(process.cwd());
|
|
362
|
+
await handleList(process.cwd());
|
|
363
363
|
}
|
|
364
364
|
else if (sub === 'merge') {
|
|
365
365
|
await handleMerge(process.cwd(), subArgs);
|
|
366
366
|
}
|
|
367
367
|
else if (sub === 'clean') {
|
|
368
|
-
handleClean(process.cwd());
|
|
368
|
+
await handleClean(process.cwd());
|
|
369
369
|
}
|
|
370
370
|
else if (sub === 'remove' || sub === 'rm') {
|
|
371
|
-
handleRemove(process.cwd(), subArgs);
|
|
371
|
+
await handleRemove(process.cwd(), subArgs);
|
|
372
372
|
}
|
|
373
373
|
else {
|
|
374
374
|
process.stderr.write(`Unknown worktree command: ${sub}\n`);
|
|
@@ -381,7 +381,7 @@ if (cliFlags.messages[0] === 'worktree' || cliFlags.messages[0] === 'wt') {
|
|
|
381
381
|
// ---------------------------------------------------------------------------
|
|
382
382
|
if (cliFlags.worktree) {
|
|
383
383
|
const { handleWorktreeFlag } = await import('./worktree-cli.js');
|
|
384
|
-
handleWorktreeFlag(cliFlags.worktree);
|
|
384
|
+
await handleWorktreeFlag(cliFlags.worktree);
|
|
385
385
|
}
|
|
386
386
|
// ---------------------------------------------------------------------------
|
|
387
387
|
// Active worktree banner — remind user of unmerged worktrees on normal launch
|
|
@@ -389,7 +389,7 @@ if (cliFlags.worktree) {
|
|
|
389
389
|
if (!cliFlags.worktree && !isPrintMode) {
|
|
390
390
|
try {
|
|
391
391
|
const { handleStatusBanner } = await import('./worktree-cli.js');
|
|
392
|
-
handleStatusBanner(process.cwd());
|
|
392
|
+
await handleStatusBanner(process.cwd());
|
|
393
393
|
}
|
|
394
394
|
catch { /* non-fatal */ }
|
|
395
395
|
}
|
package/dist/resource-loader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DefaultResourceLoader } from '@gsd/pi-coding-agent';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { compareSemver } from './update-check.js';
|
|
@@ -118,7 +118,12 @@ export function getNewerManagedResourceVersion(agentDir, currentVersion) {
|
|
|
118
118
|
function makeTreeWritable(dirPath) {
|
|
119
119
|
if (!existsSync(dirPath))
|
|
120
120
|
return;
|
|
121
|
-
|
|
121
|
+
// Use lstatSync to avoid following symlinks into immutable filesystems
|
|
122
|
+
// (e.g., Nix store on NixOS/nix-darwin). Symlinks don't carry their own
|
|
123
|
+
// permissions and their targets may be read-only by design (#1298).
|
|
124
|
+
const stats = lstatSync(dirPath);
|
|
125
|
+
if (stats.isSymbolicLink())
|
|
126
|
+
return;
|
|
122
127
|
const isDir = stats.isDirectory();
|
|
123
128
|
const currentMode = stats.mode & 0o777;
|
|
124
129
|
// Ensure owner-write; for directories also ensure owner-exec so they remain traversable.
|
|
@@ -127,7 +132,12 @@ function makeTreeWritable(dirPath) {
|
|
|
127
132
|
newMode |= 0o100;
|
|
128
133
|
}
|
|
129
134
|
if (newMode !== currentMode) {
|
|
130
|
-
|
|
135
|
+
try {
|
|
136
|
+
chmodSync(dirPath, newMode);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Non-fatal — may fail on read-only filesystems or insufficient permissions
|
|
140
|
+
}
|
|
131
141
|
}
|
|
132
142
|
if (isDir) {
|
|
133
143
|
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
@@ -20,6 +20,7 @@ import { parseRoadmap, parsePlan } from "./files.js";
|
|
|
20
20
|
import { readFileSync, existsSync } from "node:fs";
|
|
21
21
|
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
22
22
|
import { makeUI, GLYPH, INDENT } from "../shared/mod.js";
|
|
23
|
+
import { parseUnitId } from "./unit-id.js";
|
|
23
24
|
|
|
24
25
|
// ─── Dashboard Data ───────────────────────────────────────────────────────────
|
|
25
26
|
|
|
@@ -48,40 +49,34 @@ export interface AutoDashboardData {
|
|
|
48
49
|
|
|
49
50
|
// ─── Unit Description Helpers ─────────────────────────────────────────────────
|
|
50
51
|
|
|
52
|
+
/** Canonical verb and phase label for each known unit type. */
|
|
53
|
+
const UNIT_TYPE_INFO: Record<string, { verb: string; phaseLabel: string }> = {
|
|
54
|
+
"research-milestone": { verb: "researching", phaseLabel: "RESEARCH" },
|
|
55
|
+
"research-slice": { verb: "researching", phaseLabel: "RESEARCH" },
|
|
56
|
+
"plan-milestone": { verb: "planning", phaseLabel: "PLAN" },
|
|
57
|
+
"plan-slice": { verb: "planning", phaseLabel: "PLAN" },
|
|
58
|
+
"execute-task": { verb: "executing", phaseLabel: "EXECUTE" },
|
|
59
|
+
"complete-slice": { verb: "completing", phaseLabel: "COMPLETE" },
|
|
60
|
+
"replan-slice": { verb: "replanning", phaseLabel: "REPLAN" },
|
|
61
|
+
"rewrite-docs": { verb: "rewriting", phaseLabel: "REWRITE" },
|
|
62
|
+
"reassess-roadmap": { verb: "reassessing", phaseLabel: "REASSESS" },
|
|
63
|
+
"run-uat": { verb: "running UAT", phaseLabel: "UAT" },
|
|
64
|
+
};
|
|
65
|
+
|
|
51
66
|
export function unitVerb(unitType: string): string {
|
|
52
67
|
if (unitType.startsWith("hook/")) return `hook: ${unitType.slice(5)}`;
|
|
53
|
-
|
|
54
|
-
case "research-milestone":
|
|
55
|
-
case "research-slice": return "researching";
|
|
56
|
-
case "plan-milestone":
|
|
57
|
-
case "plan-slice": return "planning";
|
|
58
|
-
case "execute-task": return "executing";
|
|
59
|
-
case "complete-slice": return "completing";
|
|
60
|
-
case "replan-slice": return "replanning";
|
|
61
|
-
case "rewrite-docs": return "rewriting";
|
|
62
|
-
case "reassess-roadmap": return "reassessing";
|
|
63
|
-
case "run-uat": return "running UAT";
|
|
64
|
-
default: return unitType;
|
|
65
|
-
}
|
|
68
|
+
return UNIT_TYPE_INFO[unitType]?.verb ?? unitType;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export function unitPhaseLabel(unitType: string): string {
|
|
69
72
|
if (unitType.startsWith("hook/")) return "HOOK";
|
|
70
|
-
|
|
71
|
-
case "research-milestone": return "RESEARCH";
|
|
72
|
-
case "research-slice": return "RESEARCH";
|
|
73
|
-
case "plan-milestone": return "PLAN";
|
|
74
|
-
case "plan-slice": return "PLAN";
|
|
75
|
-
case "execute-task": return "EXECUTE";
|
|
76
|
-
case "complete-slice": return "COMPLETE";
|
|
77
|
-
case "replan-slice": return "REPLAN";
|
|
78
|
-
case "rewrite-docs": return "REWRITE";
|
|
79
|
-
case "reassess-roadmap": return "REASSESS";
|
|
80
|
-
case "run-uat": return "UAT";
|
|
81
|
-
default: return unitType.toUpperCase();
|
|
82
|
-
}
|
|
73
|
+
return UNIT_TYPE_INFO[unitType]?.phaseLabel ?? unitType.toUpperCase();
|
|
83
74
|
}
|
|
84
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Describe the expected next step after the current unit completes.
|
|
78
|
+
* Unit types here mirror the keys in UNIT_TYPE_INFO above.
|
|
79
|
+
*/
|
|
85
80
|
function peekNext(unitType: string, state: GSDState): string {
|
|
86
81
|
// Show active hook info in progress display
|
|
87
82
|
const activeHookState = getActiveHook();
|
|
@@ -378,8 +373,9 @@ export function updateProgressWidget(
|
|
|
378
373
|
lines.push("");
|
|
379
374
|
|
|
380
375
|
const isHook = unitType.startsWith("hook/");
|
|
376
|
+
const hookParsed = isHook ? parseUnitId(unitId) : undefined;
|
|
381
377
|
const target = isHook
|
|
382
|
-
? (
|
|
378
|
+
? (hookParsed!.task ?? hookParsed!.slice ?? unitId)
|
|
383
379
|
: (task ? `${task.id}: ${task.title}` : unitId);
|
|
384
380
|
const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
|
|
385
381
|
const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
|
|
@@ -182,15 +182,10 @@ export async function dispatchDirectPhase(
|
|
|
182
182
|
ctx.ui.notify("Cannot dispatch run-uat: no UAT file found.", "warning");
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
|
-
const uatContent = await loadFile(uatFile);
|
|
186
|
-
if (!uatContent) {
|
|
187
|
-
ctx.ui.notify("Cannot dispatch run-uat: UAT file is empty.", "warning");
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
185
|
const uatPath = relSliceFile(base, mid, sid, "UAT");
|
|
191
186
|
unitType = "run-uat";
|
|
192
187
|
unitId = `${mid}/${sid}`;
|
|
193
|
-
prompt = await buildRunUatPrompt(mid, sid, uatPath,
|
|
188
|
+
prompt = await buildRunUatPrompt(mid, sid, uatPath, base);
|
|
194
189
|
break;
|
|
195
190
|
}
|
|
196
191
|
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { GSDState } from "./types.js";
|
|
13
13
|
import type { GSDPreferences } from "./preferences.js";
|
|
14
|
-
import
|
|
15
|
-
import { loadFile, extractUatType, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
14
|
+
import { loadFile, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
16
15
|
import {
|
|
17
16
|
resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile,
|
|
18
17
|
relSliceFile, buildMilestoneFileName,
|
|
@@ -39,7 +38,7 @@ import {
|
|
|
39
38
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
40
39
|
|
|
41
40
|
export type DispatchAction =
|
|
42
|
-
| { action: "dispatch"; unitType: string; unitId: string; prompt: string
|
|
41
|
+
| { action: "dispatch"; unitType: string; unitId: string; prompt: string }
|
|
43
42
|
| { action: "stop"; reason: string; level: "info" | "warning" | "error" }
|
|
44
43
|
| { action: "skip" };
|
|
45
44
|
|
|
@@ -138,17 +137,14 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
138
137
|
match: async ({ state, mid, basePath, prefs }) => {
|
|
139
138
|
const needsRunUat = await checkNeedsRunUat(basePath, mid, state, prefs);
|
|
140
139
|
if (!needsRunUat) return null;
|
|
141
|
-
const { sliceId
|
|
142
|
-
const uatFile = resolveSliceFile(basePath, mid, sliceId, "UAT")!;
|
|
143
|
-
const uatContent = await loadFile(uatFile);
|
|
140
|
+
const { sliceId } = needsRunUat;
|
|
144
141
|
return {
|
|
145
142
|
action: "dispatch",
|
|
146
143
|
unitType: "run-uat",
|
|
147
144
|
unitId: `${mid}/${sliceId}`,
|
|
148
145
|
prompt: await buildRunUatPrompt(
|
|
149
|
-
mid, sliceId, relSliceFile(basePath, mid, sliceId, "UAT"),
|
|
146
|
+
mid, sliceId, relSliceFile(basePath, mid, sliceId, "UAT"), basePath,
|
|
150
147
|
),
|
|
151
|
-
pauseAfterDispatch: uatType !== "artifact-driven",
|
|
152
148
|
};
|
|
153
149
|
},
|
|
154
150
|
},
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { resolveMilestoneFile } from "./paths.js";
|
|
19
19
|
import { MAX_CONSECUTIVE_SKIPS, MAX_LIFETIME_DISPATCHES } from "./auto/session.js";
|
|
20
20
|
import type { AutoSession } from "./auto/session.js";
|
|
21
|
+
import { parseUnitId } from "./unit-id.js";
|
|
21
22
|
|
|
22
23
|
export interface IdempotencyContext {
|
|
23
24
|
s: AutoSession;
|
|
@@ -54,7 +55,7 @@ export function checkIdempotency(ictx: IdempotencyContext): IdempotencyResult {
|
|
|
54
55
|
s.unitConsecutiveSkips.set(idempotencyKey, skipCount);
|
|
55
56
|
if (skipCount > MAX_CONSECUTIVE_SKIPS) {
|
|
56
57
|
// Cross-check: verify the unit's milestone is still active (#790)
|
|
57
|
-
const skippedMid = unitId.
|
|
58
|
+
const skippedMid = parseUnitId(unitId).milestone;
|
|
58
59
|
const skippedMilestoneComplete = skippedMid
|
|
59
60
|
? !!resolveMilestoneFile(basePath, skippedMid, "SUMMARY")
|
|
60
61
|
: false;
|
|
@@ -110,7 +111,7 @@ export function checkIdempotency(ictx: IdempotencyContext): IdempotencyResult {
|
|
|
110
111
|
const skipCount2 = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
|
|
111
112
|
s.unitConsecutiveSkips.set(idempotencyKey, skipCount2);
|
|
112
113
|
if (skipCount2 > MAX_CONSECUTIVE_SKIPS) {
|
|
113
|
-
const skippedMid2 = unitId.
|
|
114
|
+
const skippedMid2 = parseUnitId(unitId).milestone;
|
|
114
115
|
const skippedMilestoneComplete2 = skippedMid2
|
|
115
116
|
? !!resolveMilestoneFile(basePath, skippedMid2, "SUMMARY")
|
|
116
117
|
: false;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
formatValidationIssues,
|
|
13
13
|
} from "./observability-validator.js";
|
|
14
14
|
import type { ValidationIssue } from "./observability-validator.js";
|
|
15
|
+
import { parseUnitId } from "./unit-id.js";
|
|
15
16
|
|
|
16
17
|
export async function collectObservabilityWarnings(
|
|
17
18
|
ctx: ExtensionContext,
|
|
@@ -22,10 +23,7 @@ export async function collectObservabilityWarnings(
|
|
|
22
23
|
// Hook units have custom artifacts — skip standard observability checks
|
|
23
24
|
if (unitType.startsWith("hook/")) return [];
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
const mid = parts[0];
|
|
27
|
-
const sid = parts[1];
|
|
28
|
-
const tid = parts[2];
|
|
26
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
29
27
|
|
|
30
28
|
if (!mid || !sid) return [];
|
|
31
29
|
|
|
@@ -60,9 +60,32 @@ import {
|
|
|
60
60
|
hideFooter,
|
|
61
61
|
} from "./auto-dashboard.js";
|
|
62
62
|
import { join } from "node:path";
|
|
63
|
+
import { STATE_REBUILD_MIN_INTERVAL_MS } from "./auto-constants.js";
|
|
64
|
+
import { parseUnitId } from "./unit-id.js";
|
|
63
65
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Initialize a unit dispatch: stamp the current time, set `s.currentUnit`,
|
|
68
|
+
* and persist the initial runtime record. Returns `startedAt` for callers
|
|
69
|
+
* that need the timestamp.
|
|
70
|
+
*/
|
|
71
|
+
function dispatchUnit(
|
|
72
|
+
s: AutoSession,
|
|
73
|
+
basePath: string,
|
|
74
|
+
unitType: string,
|
|
75
|
+
unitId: string,
|
|
76
|
+
): number {
|
|
77
|
+
const startedAt = Date.now();
|
|
78
|
+
s.currentUnit = { type: unitType, id: unitId, startedAt };
|
|
79
|
+
writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, {
|
|
80
|
+
phase: "dispatched",
|
|
81
|
+
wrapupWarningSent: false,
|
|
82
|
+
timeoutAt: null,
|
|
83
|
+
lastProgressAt: startedAt,
|
|
84
|
+
progressCount: 0,
|
|
85
|
+
lastProgressKind: "dispatch",
|
|
86
|
+
});
|
|
87
|
+
return startedAt;
|
|
88
|
+
}
|
|
66
89
|
|
|
67
90
|
export interface PostUnitContext {
|
|
68
91
|
s: AutoSession;
|
|
@@ -112,8 +135,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
112
135
|
let taskContext: TaskCommitContext | undefined;
|
|
113
136
|
|
|
114
137
|
if (s.currentUnit.type === "execute-task") {
|
|
115
|
-
const
|
|
116
|
-
const [mid, sid, tid] = parts;
|
|
138
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
|
|
117
139
|
if (mid && sid && tid) {
|
|
118
140
|
const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
|
|
119
141
|
if (summaryPath) {
|
|
@@ -145,8 +167,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
145
167
|
|
|
146
168
|
// Doctor: fix mechanical bookkeeping
|
|
147
169
|
try {
|
|
148
|
-
const
|
|
149
|
-
const doctorScope =
|
|
170
|
+
const { milestone, slice } = parseUnitId(s.currentUnit.id);
|
|
171
|
+
const doctorScope = slice ? `${milestone}/${slice}` : milestone;
|
|
150
172
|
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
151
173
|
const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" as const : "task" as const;
|
|
152
174
|
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
@@ -326,7 +348,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
326
348
|
// instead of dispatching LLM sessions for complete-slice / validate-milestone.
|
|
327
349
|
if (s.currentUnit?.type === "execute-task" && !s.stepMode) {
|
|
328
350
|
try {
|
|
329
|
-
const
|
|
351
|
+
const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
|
|
330
352
|
if (mid && sid) {
|
|
331
353
|
const state = await deriveState(s.basePath);
|
|
332
354
|
if (state.phase === "summarizing" && state.activeSlice?.id === sid) {
|
|
@@ -364,19 +386,10 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
364
386
|
if (s.currentUnit && !s.stepMode) {
|
|
365
387
|
const hookUnit = checkPostUnitHooks(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
366
388
|
if (hookUnit) {
|
|
367
|
-
const hookStartedAt = Date.now();
|
|
368
389
|
if (s.currentUnit) {
|
|
369
390
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
370
391
|
}
|
|
371
|
-
s.
|
|
372
|
-
writeUnitRuntimeRecord(s.basePath, hookUnit.unitType, hookUnit.unitId, hookStartedAt, {
|
|
373
|
-
phase: "dispatched",
|
|
374
|
-
wrapupWarningSent: false,
|
|
375
|
-
timeoutAt: null,
|
|
376
|
-
lastProgressAt: hookStartedAt,
|
|
377
|
-
progressCount: 0,
|
|
378
|
-
lastProgressKind: "dispatch",
|
|
379
|
-
});
|
|
392
|
+
dispatchUnit(s, s.basePath, hookUnit.unitType, hookUnit.unitId);
|
|
380
393
|
|
|
381
394
|
const state = await deriveState(s.basePath);
|
|
382
395
|
updateProgressWidget(ctx, hookUnit.unitType, hookUnit.unitId, state);
|
|
@@ -498,16 +511,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
498
511
|
|
|
499
512
|
const triageUnitType = "triage-captures";
|
|
500
513
|
const triageUnitId = `${mid}/${sid}/triage`;
|
|
501
|
-
|
|
502
|
-
s.currentUnit = { type: triageUnitType, id: triageUnitId, startedAt: triageStartedAt };
|
|
503
|
-
writeUnitRuntimeRecord(s.basePath, triageUnitType, triageUnitId, triageStartedAt, {
|
|
504
|
-
phase: "dispatched",
|
|
505
|
-
wrapupWarningSent: false,
|
|
506
|
-
timeoutAt: null,
|
|
507
|
-
lastProgressAt: triageStartedAt,
|
|
508
|
-
progressCount: 0,
|
|
509
|
-
lastProgressKind: "dispatch",
|
|
510
|
-
});
|
|
514
|
+
dispatchUnit(s, s.basePath, triageUnitType, triageUnitId);
|
|
511
515
|
updateProgressWidget(ctx, triageUnitType, triageUnitId, state);
|
|
512
516
|
|
|
513
517
|
const result = await s.cmdCtx!.newSession();
|
|
@@ -568,16 +572,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
568
572
|
|
|
569
573
|
const qtUnitType = "quick-task";
|
|
570
574
|
const qtUnitId = `${s.currentMilestoneId}/${capture.id}`;
|
|
571
|
-
|
|
572
|
-
s.currentUnit = { type: qtUnitType, id: qtUnitId, startedAt: qtStartedAt };
|
|
573
|
-
writeUnitRuntimeRecord(s.basePath, qtUnitType, qtUnitId, qtStartedAt, {
|
|
574
|
-
phase: "dispatched",
|
|
575
|
-
wrapupWarningSent: false,
|
|
576
|
-
timeoutAt: null,
|
|
577
|
-
lastProgressAt: qtStartedAt,
|
|
578
|
-
progressCount: 0,
|
|
579
|
-
lastProgressKind: "dispatch",
|
|
580
|
-
});
|
|
575
|
+
dispatchUnit(s, s.basePath, qtUnitType, qtUnitId);
|
|
581
576
|
const state = await deriveState(s.basePath);
|
|
582
577
|
updateProgressWidget(ctx, qtUnitType, qtUnitId, state);
|
|
583
578
|
|