gsd-pi 2.35.0 → 2.36.0-dev.d612764
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 +3 -1
- package/dist/cli.js +7 -2
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +13 -1
- package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
- package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
- package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
- package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
- package/dist/resources/extensions/bg-shell/types.js +0 -2
- package/dist/resources/extensions/cmux/index.js +321 -0
- package/dist/resources/extensions/context7/index.js +5 -0
- package/dist/resources/extensions/get-secrets-from-user.js +2 -30
- package/dist/resources/extensions/google-search/index.js +5 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
- package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
- package/dist/resources/extensions/gsd/auto-loop.js +28 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
- package/dist/resources/extensions/gsd/auto-start.js +35 -2
- package/dist/resources/extensions/gsd/auto.js +75 -4
- package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
- package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands-rate.js +31 -0
- package/dist/resources/extensions/gsd/commands.js +94 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
- package/dist/resources/extensions/gsd/files.js +11 -2
- package/dist/resources/extensions/gsd/gitignore.js +54 -7
- package/dist/resources/extensions/gsd/guided-flow.js +8 -2
- package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
- package/dist/resources/extensions/gsd/health-widget.js +97 -46
- package/dist/resources/extensions/gsd/index.js +31 -33
- package/dist/resources/extensions/gsd/migrate-external.js +55 -2
- package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
- package/dist/resources/extensions/gsd/notifications.js +10 -1
- package/dist/resources/extensions/gsd/paths.js +74 -7
- package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
- package/dist/resources/extensions/gsd/preferences.js +15 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
- package/dist/resources/extensions/gsd/session-lock.js +53 -2
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/templates/plan.md +8 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
- package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
- package/dist/resources/extensions/search-the-web/native-search.js +45 -4
- package/dist/resources/extensions/shared/mod.js +1 -1
- package/dist/resources/extensions/shared/sanitize.js +30 -0
- package/dist/resources/extensions/shared/terminal.js +5 -0
- package/dist/resources/extensions/subagent/index.js +186 -74
- package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/dist/resources/skills/github-workflows/SKILL.md +0 -2
- package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/package.json +2 -1
- package/packages/pi-agent-core/dist/agent.d.ts +10 -2
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +19 -8
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +31 -10
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
- package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +4 -0
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +5 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
- package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
- package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
- package/src/resources/extensions/bg-shell/types.ts +0 -12
- package/src/resources/extensions/cmux/index.ts +384 -0
- package/src/resources/extensions/context7/index.ts +7 -0
- package/src/resources/extensions/get-secrets-from-user.ts +2 -35
- package/src/resources/extensions/google-search/index.ts +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
- package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
- package/src/resources/extensions/gsd/auto-loop.ts +64 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
- package/src/resources/extensions/gsd/auto-start.ts +42 -2
- package/src/resources/extensions/gsd/auto.ts +82 -3
- package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
- package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands-rate.ts +55 -0
- package/src/resources/extensions/gsd/commands.ts +97 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
- package/src/resources/extensions/gsd/files.ts +12 -2
- package/src/resources/extensions/gsd/gitignore.ts +54 -7
- package/src/resources/extensions/gsd/guided-flow.ts +8 -2
- package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
- package/src/resources/extensions/gsd/health-widget.ts +103 -59
- package/src/resources/extensions/gsd/index.ts +37 -32
- package/src/resources/extensions/gsd/migrate-external.ts +47 -2
- package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
- package/src/resources/extensions/gsd/notifications.ts +10 -1
- package/src/resources/extensions/gsd/paths.ts +73 -7
- package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
- package/src/resources/extensions/gsd/preferences.ts +18 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
- package/src/resources/extensions/gsd/session-lock.ts +59 -2
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/templates/plan.md +8 -0
- package/src/resources/extensions/gsd/templates/preferences.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
- package/src/resources/extensions/search-the-web/native-search.ts +50 -4
- package/src/resources/extensions/shared/mod.ts +1 -1
- package/src/resources/extensions/shared/sanitize.ts +36 -0
- package/src/resources/extensions/shared/terminal.ts +5 -0
- package/src/resources/extensions/subagent/index.ts +242 -91
- package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/src/resources/skills/github-workflows/SKILL.md +0 -2
- package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/dist/resources/extensions/shared/wizard-ui.js +0 -478
- package/dist/resources/skills/swiftui/SKILL.md +0 -208
- package/dist/resources/skills/swiftui/references/animations.md +0 -921
- package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
- package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
- package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
- package/dist/resources/skills/swiftui/references/performance.md +0 -1706
- package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
- package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
- package/src/resources/extensions/shared/wizard-ui.ts +0 -551
- package/src/resources/skills/swiftui/SKILL.md +0 -208
- package/src/resources/skills/swiftui/references/animations.md +0 -921
- package/src/resources/skills/swiftui/references/architecture.md +0 -1561
- package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/src/resources/skills/swiftui/references/navigation.md +0 -1492
- package/src/resources/skills/swiftui/references/networking-async.md +0 -214
- package/src/resources/skills/swiftui/references/performance.md +0 -1706
- package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/src/resources/skills/swiftui/references/state-management.md +0 -1443
- package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/src/resources/skills/swiftui/workflows/write-tests.md +0 -235
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /gsd rate — Submit feedback on the last unit's model tier assignment.
|
|
3
|
+
* Feeds into the adaptive routing history so future dispatches improve.
|
|
4
|
+
*/
|
|
5
|
+
import { loadLedgerFromDisk } from "./metrics.js";
|
|
6
|
+
import { recordFeedback, initRoutingHistory } from "./routing-history.js";
|
|
7
|
+
const VALID_RATINGS = new Set(["over", "under", "ok"]);
|
|
8
|
+
export async function handleRate(args, ctx, basePath) {
|
|
9
|
+
const rating = args.trim().toLowerCase();
|
|
10
|
+
if (!rating || !VALID_RATINGS.has(rating)) {
|
|
11
|
+
ctx.ui.notify("Usage: /gsd rate <over|ok|under>\n" +
|
|
12
|
+
" over — model was overpowered for that task (encourage cheaper)\n" +
|
|
13
|
+
" ok — model was appropriate\n" +
|
|
14
|
+
" under — model was too weak (encourage stronger)", "info");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const ledger = loadLedgerFromDisk(basePath);
|
|
18
|
+
if (!ledger || ledger.units.length === 0) {
|
|
19
|
+
ctx.ui.notify("No completed units found — nothing to rate.", "warning");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const lastUnit = ledger.units[ledger.units.length - 1];
|
|
23
|
+
const tier = lastUnit.tier;
|
|
24
|
+
if (!tier) {
|
|
25
|
+
ctx.ui.notify("Last unit has no tier data (dynamic routing was not active). Rating skipped.", "warning");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
initRoutingHistory(basePath);
|
|
29
|
+
recordFeedback(lastUnit.type, lastUnit.id, tier, rating);
|
|
30
|
+
ctx.ui.notify(`Recorded "${rating}" for ${lastUnit.type}/${lastUnit.id} at tier ${tier}.`, "info");
|
|
31
|
+
}
|
|
@@ -36,6 +36,8 @@ import { computeProgressScore, formatProgressLine } from "./progress-score.js";
|
|
|
36
36
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
37
37
|
import { handleLogs } from "./commands-logs.js";
|
|
38
38
|
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
39
|
+
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
40
|
+
import { handleCmux } from "./commands-cmux.js";
|
|
39
41
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
40
42
|
export function projectRoot() {
|
|
41
43
|
const cwd = process.cwd();
|
|
@@ -54,9 +56,41 @@ export function projectRoot() {
|
|
|
54
56
|
}
|
|
55
57
|
return root;
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if another process holds the auto-mode session lock.
|
|
61
|
+
* Returns the lock data if a remote session is alive, null otherwise.
|
|
62
|
+
*/
|
|
63
|
+
function getRemoteAutoSession(basePath) {
|
|
64
|
+
const lockData = readSessionLockData(basePath);
|
|
65
|
+
if (!lockData)
|
|
66
|
+
return null;
|
|
67
|
+
if (lockData.pid === process.pid)
|
|
68
|
+
return null;
|
|
69
|
+
if (!isSessionLockProcessAlive(lockData))
|
|
70
|
+
return null;
|
|
71
|
+
return { pid: lockData.pid };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Show a steering menu when auto-mode is running in another process.
|
|
75
|
+
* Returns true if a remote session was detected (caller should return early).
|
|
76
|
+
*/
|
|
77
|
+
function notifyRemoteAutoActive(ctx, basePath) {
|
|
78
|
+
const remote = getRemoteAutoSession(basePath);
|
|
79
|
+
if (!remote)
|
|
80
|
+
return false;
|
|
81
|
+
ctx.ui.notify(`Auto-mode is running in another process (PID ${remote.pid}).\n` +
|
|
82
|
+
`Use these commands to interact with it:\n` +
|
|
83
|
+
` /gsd status — check progress\n` +
|
|
84
|
+
` /gsd discuss — discuss architecture decisions\n` +
|
|
85
|
+
` /gsd queue — queue the next milestone\n` +
|
|
86
|
+
` /gsd steer — apply an override to active work\n` +
|
|
87
|
+
` /gsd capture — fire-and-forget thought\n` +
|
|
88
|
+
` /gsd stop — stop auto-mode`, "warning");
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
57
91
|
export function registerGSDCommand(pi) {
|
|
58
92
|
pi.registerCommand("gsd", {
|
|
59
|
-
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|update",
|
|
93
|
+
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
|
|
60
94
|
getArgumentCompletions: (prefix) => {
|
|
61
95
|
const subcommands = [
|
|
62
96
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -65,6 +99,7 @@ export function registerGSDCommand(pi) {
|
|
|
65
99
|
{ cmd: "stop", desc: "Stop auto mode gracefully" },
|
|
66
100
|
{ cmd: "pause", desc: "Pause auto-mode (preserves state, /gsd auto to resume)" },
|
|
67
101
|
{ cmd: "status", desc: "Progress dashboard" },
|
|
102
|
+
{ cmd: "widget", desc: "Cycle widget: full → small → min → off" },
|
|
68
103
|
{ cmd: "visualize", desc: "Open 10-tab workflow visualizer (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)" },
|
|
69
104
|
{ cmd: "queue", desc: "Queue and reorder future milestones" },
|
|
70
105
|
{ cmd: "quick", desc: "Execute a quick task without full planning overhead" },
|
|
@@ -74,6 +109,7 @@ export function registerGSDCommand(pi) {
|
|
|
74
109
|
{ cmd: "triage", desc: "Manually trigger triage of pending captures" },
|
|
75
110
|
{ cmd: "dispatch", desc: "Dispatch a specific phase directly" },
|
|
76
111
|
{ cmd: "history", desc: "View execution history" },
|
|
112
|
+
{ cmd: "rate", desc: "Rate last unit's model tier (over/ok/under) — improves adaptive routing" },
|
|
77
113
|
{ cmd: "undo", desc: "Revert last completed unit" },
|
|
78
114
|
{ cmd: "skip", desc: "Prevent a unit from auto-mode dispatch" },
|
|
79
115
|
{ cmd: "export", desc: "Export milestone/slice results" },
|
|
@@ -97,6 +133,7 @@ export function registerGSDCommand(pi) {
|
|
|
97
133
|
{ cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
|
|
98
134
|
{ cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
|
|
99
135
|
{ cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
|
|
136
|
+
{ cmd: "cmux", desc: "Manage cmux integration (status, sidebar, notifications, splits)" },
|
|
100
137
|
{ cmd: "park", desc: "Park a milestone — skip without deleting" },
|
|
101
138
|
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
|
|
102
139
|
{ cmd: "update", desc: "Update GSD to the latest version" },
|
|
@@ -148,6 +185,36 @@ export function registerGSDCommand(pi) {
|
|
|
148
185
|
.filter((s) => s.cmd.startsWith(subPrefix))
|
|
149
186
|
.map((s) => ({ value: `parallel ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
150
187
|
}
|
|
188
|
+
if (parts[0] === "cmux") {
|
|
189
|
+
if (parts.length <= 2) {
|
|
190
|
+
const subPrefix = parts[1] ?? "";
|
|
191
|
+
const subs = [
|
|
192
|
+
{ cmd: "status", desc: "Show cmux detection, prefs, and capabilities" },
|
|
193
|
+
{ cmd: "on", desc: "Enable cmux integration" },
|
|
194
|
+
{ cmd: "off", desc: "Disable cmux integration" },
|
|
195
|
+
{ cmd: "notifications", desc: "Toggle cmux desktop notifications" },
|
|
196
|
+
{ cmd: "sidebar", desc: "Toggle cmux sidebar metadata" },
|
|
197
|
+
{ cmd: "splits", desc: "Toggle cmux visual subagent splits" },
|
|
198
|
+
{ cmd: "browser", desc: "Toggle future browser integration flag" },
|
|
199
|
+
];
|
|
200
|
+
return subs
|
|
201
|
+
.filter((s) => s.cmd.startsWith(subPrefix))
|
|
202
|
+
.map((s) => ({ value: `cmux ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
203
|
+
}
|
|
204
|
+
if (parts.length <= 3 && ["notifications", "sidebar", "splits", "browser"].includes(parts[1])) {
|
|
205
|
+
const togglePrefix = parts[2] ?? "";
|
|
206
|
+
return [
|
|
207
|
+
{ cmd: "on", desc: "Enable this cmux area" },
|
|
208
|
+
{ cmd: "off", desc: "Disable this cmux area" },
|
|
209
|
+
]
|
|
210
|
+
.filter((item) => item.cmd.startsWith(togglePrefix))
|
|
211
|
+
.map((item) => ({
|
|
212
|
+
value: `cmux ${parts[1]} ${item.cmd}`,
|
|
213
|
+
label: item.cmd,
|
|
214
|
+
description: item.desc,
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
151
218
|
if (parts[0] === "setup" && parts.length <= 2) {
|
|
152
219
|
const subPrefix = parts[1] ?? "";
|
|
153
220
|
const subs = [
|
|
@@ -396,6 +463,18 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
396
463
|
await handleStatus(ctx);
|
|
397
464
|
return;
|
|
398
465
|
}
|
|
466
|
+
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
467
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import("./auto-dashboard.js");
|
|
468
|
+
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
469
|
+
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
470
|
+
setWidgetMode(arg);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
cycleWidgetMode();
|
|
474
|
+
}
|
|
475
|
+
ctx.ui.notify(`Widget: ${getWidgetMode()}`, "info");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
399
478
|
if (trimmed === "visualize") {
|
|
400
479
|
await handleVisualize(ctx);
|
|
401
480
|
return;
|
|
@@ -412,6 +491,10 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
412
491
|
await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
|
|
413
492
|
return;
|
|
414
493
|
}
|
|
494
|
+
if (trimmed === "cmux" || trimmed.startsWith("cmux ")) {
|
|
495
|
+
await handleCmux(trimmed.replace(/^cmux\s*/, "").trim(), ctx);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
415
498
|
if (trimmed === "init") {
|
|
416
499
|
const { detectProjectState } = await import("./detection.js");
|
|
417
500
|
const { showProjectInit, handleReinit } = await import("./init-wizard.js");
|
|
@@ -459,6 +542,8 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
459
542
|
await handleDryRun(ctx, projectRoot());
|
|
460
543
|
return;
|
|
461
544
|
}
|
|
545
|
+
if (notifyRemoteAutoActive(ctx, projectRoot()))
|
|
546
|
+
return;
|
|
462
547
|
const verboseMode = trimmed.includes("--verbose");
|
|
463
548
|
const debugMode = trimmed.includes("--debug");
|
|
464
549
|
if (debugMode)
|
|
@@ -513,6 +598,11 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
513
598
|
await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi, projectRoot());
|
|
514
599
|
return;
|
|
515
600
|
}
|
|
601
|
+
if (trimmed === "rate" || trimmed.startsWith("rate ")) {
|
|
602
|
+
const { handleRate } = await import("./commands-rate.js");
|
|
603
|
+
await handleRate(trimmed.replace(/^rate\s*/, "").trim(), ctx, projectRoot());
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
516
606
|
if (trimmed.startsWith("skip ")) {
|
|
517
607
|
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
|
|
518
608
|
return;
|
|
@@ -809,7 +899,8 @@ Examples:
|
|
|
809
899
|
return;
|
|
810
900
|
}
|
|
811
901
|
if (trimmed === "") {
|
|
812
|
-
|
|
902
|
+
if (notifyRemoteAutoActive(ctx, projectRoot()))
|
|
903
|
+
return;
|
|
813
904
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
814
905
|
return;
|
|
815
906
|
}
|
|
@@ -858,6 +949,7 @@ function showHelp(ctx) {
|
|
|
858
949
|
" /gsd setup Global setup status [llm|search|remote|keys|prefs]",
|
|
859
950
|
" /gsd mode Set workflow mode (solo/team) [global|project]",
|
|
860
951
|
" /gsd prefs Manage preferences [global|project|status|wizard|setup|import-claude]",
|
|
952
|
+
" /gsd cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]",
|
|
861
953
|
" /gsd config Set API keys for external tools",
|
|
862
954
|
" /gsd keys API key manager [list|add|remove|test|rotate|doctor]",
|
|
863
955
|
" /gsd hooks Show post-unit hook configuration",
|
|
@@ -173,6 +173,13 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
173
173
|
- `on_milestone`: boolean — notify when a milestone finishes. Default: `true`.
|
|
174
174
|
- `on_attention`: boolean — notify when manual attention is needed. Default: `true`.
|
|
175
175
|
|
|
176
|
+
- `cmux`: configures cmux terminal integration when GSD is running inside a cmux workspace. Keys:
|
|
177
|
+
- `enabled`: boolean — master toggle for cmux integration. Default: `false`.
|
|
178
|
+
- `notifications`: boolean — route desktop notifications through cmux. Default: `true` when enabled.
|
|
179
|
+
- `sidebar`: boolean — publish status, progress, and log metadata to the cmux sidebar. Default: `true` when enabled.
|
|
180
|
+
- `splits`: boolean — run supported subagent work in visible cmux splits. Default: `false`.
|
|
181
|
+
- `browser`: boolean — reserve the future browser integration flag. Default: `false`.
|
|
182
|
+
|
|
176
183
|
- `dynamic_routing`: configures the dynamic model router that adjusts model selection based on task complexity. Keys:
|
|
177
184
|
- `enabled`: boolean — enable dynamic routing. Default: `false`.
|
|
178
185
|
- `tier_models`: object — model overrides per complexity tier. Keys: `light`, `standard`, `heavy`. Values are model ID strings.
|
|
@@ -477,6 +484,24 @@ Disables per-unit completion notifications (noisy in long runs) while keeping er
|
|
|
477
484
|
|
|
478
485
|
---
|
|
479
486
|
|
|
487
|
+
## cmux Example
|
|
488
|
+
|
|
489
|
+
```yaml
|
|
490
|
+
---
|
|
491
|
+
version: 1
|
|
492
|
+
cmux:
|
|
493
|
+
enabled: true
|
|
494
|
+
notifications: true
|
|
495
|
+
sidebar: true
|
|
496
|
+
splits: true
|
|
497
|
+
browser: false
|
|
498
|
+
---
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Enables cmux-aware notifications, sidebar metadata, and visible subagent splits when GSD is running inside a cmux terminal.
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
480
505
|
## Post-Unit Hooks Example
|
|
481
506
|
|
|
482
507
|
```yaml
|
|
@@ -148,27 +148,36 @@ function checkPortConflicts(basePath) {
|
|
|
148
148
|
// Try to detect ports from package.json scripts
|
|
149
149
|
const portsToCheck = new Set();
|
|
150
150
|
const pkgPath = join(basePath, "package.json");
|
|
151
|
-
if (existsSync(pkgPath)) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
151
|
+
if (!existsSync(pkgPath)) {
|
|
152
|
+
// No package.json — this isn't a Node.js project. Skip port checks
|
|
153
|
+
// entirely to avoid false positives from system services (e.g., macOS
|
|
154
|
+
// AirPlay Receiver on port 5000). (#1381)
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
159
|
+
const scripts = pkg.scripts ?? {};
|
|
160
|
+
const scriptText = Object.values(scripts).join(" ");
|
|
161
|
+
// Look for --port NNNN, -p NNNN, PORT=NNNN, :NNNN patterns
|
|
162
|
+
const portMatches = scriptText.matchAll(/(?:--port\s+|(?:^|[^a-z])PORT[=:]\s*|-p\s+|:)(\d{4,5})\b/gi);
|
|
163
|
+
for (const m of portMatches) {
|
|
164
|
+
const port = parseInt(m[1], 10);
|
|
165
|
+
if (port >= 1024 && port <= 65535)
|
|
166
|
+
portsToCheck.add(port);
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
+
catch {
|
|
170
|
+
// parse failed — skip port checks rather than using defaults
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
// If no ports found in scripts, check common defaults.
|
|
174
|
+
// Filter out port 5000 on macOS — AirPlay Receiver uses it by default (#1381).
|
|
169
175
|
if (portsToCheck.size === 0) {
|
|
170
|
-
for (const p of DEFAULT_DEV_PORTS)
|
|
176
|
+
for (const p of DEFAULT_DEV_PORTS) {
|
|
177
|
+
if (p === 5000 && process.platform === "darwin")
|
|
178
|
+
continue;
|
|
171
179
|
portsToCheck.add(p);
|
|
180
|
+
}
|
|
172
181
|
}
|
|
173
182
|
for (const port of portsToCheck) {
|
|
174
183
|
const result = tryExec(`lsof -i :${port} -sTCP:LISTEN -t`, basePath);
|
|
@@ -509,7 +509,8 @@ export async function loadFile(path) {
|
|
|
509
509
|
return await fs.readFile(path, 'utf-8');
|
|
510
510
|
}
|
|
511
511
|
catch (err) {
|
|
512
|
-
|
|
512
|
+
const code = err.code;
|
|
513
|
+
if (code === 'ENOENT' || code === 'EISDIR')
|
|
513
514
|
return null;
|
|
514
515
|
throw err;
|
|
515
516
|
}
|
|
@@ -704,7 +705,7 @@ export async function inlinePriorMilestoneSummary(mid, base) {
|
|
|
704
705
|
* Returns `null` when no manifest file exists (path resolution failure or
|
|
705
706
|
* file not on disk) - callers can distinguish "no manifest" from "empty manifest".
|
|
706
707
|
*/
|
|
707
|
-
export async function getManifestStatus(base, milestoneId) {
|
|
708
|
+
export async function getManifestStatus(base, milestoneId, projectRoot) {
|
|
708
709
|
const resolvedPath = resolveMilestoneFile(base, milestoneId, 'SECRETS');
|
|
709
710
|
if (!resolvedPath)
|
|
710
711
|
return null;
|
|
@@ -713,8 +714,16 @@ export async function getManifestStatus(base, milestoneId) {
|
|
|
713
714
|
return null;
|
|
714
715
|
const manifest = parseSecretsManifest(content);
|
|
715
716
|
const keys = manifest.entries.map(e => e.key);
|
|
717
|
+
// Check both the base path .env AND the project root .env (#1387).
|
|
718
|
+
// In worktree mode, base is the worktree path which may not have .env.
|
|
719
|
+
// The project root's .env is where the user actually defined their keys.
|
|
716
720
|
const existingKeys = await checkExistingEnvKeys(keys, resolve(base, '.env'));
|
|
717
721
|
const existingSet = new Set(existingKeys);
|
|
722
|
+
if (projectRoot && projectRoot !== base) {
|
|
723
|
+
const rootKeys = await checkExistingEnvKeys(keys, resolve(projectRoot, '.env'));
|
|
724
|
+
for (const k of rootKeys)
|
|
725
|
+
existingSet.add(k);
|
|
726
|
+
}
|
|
718
727
|
const result = {
|
|
719
728
|
pending: [],
|
|
720
729
|
collected: [],
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Both idempotent — non-destructive if already present.
|
|
7
7
|
*/
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
-
import { nativeRmCached } from "./native-git-bridge.js";
|
|
9
|
+
import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { nativeRmCached, nativeLsFiles } from "./native-git-bridge.js";
|
|
11
11
|
import { gsdRoot } from "./paths.js";
|
|
12
12
|
/**
|
|
13
13
|
* GSD runtime patterns for git index cleanup.
|
|
@@ -67,12 +67,48 @@ const BASELINE_PATTERNS = [
|
|
|
67
67
|
"tmp/",
|
|
68
68
|
];
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* Check whether `.gsd/` contains files tracked by git.
|
|
71
|
+
* If so, the project intentionally keeps `.gsd/` in version control
|
|
72
|
+
* and we must NOT add `.gsd` to `.gitignore` or attempt migration.
|
|
73
|
+
*
|
|
74
|
+
* Returns true if git tracks at least one file under `.gsd/`.
|
|
75
|
+
* Returns false (safe to ignore) if:
|
|
76
|
+
* - Not a git repo
|
|
77
|
+
* - `.gsd/` is a symlink (external state, should be ignored)
|
|
78
|
+
* - `.gsd/` doesn't exist
|
|
79
|
+
* - No tracked files found under `.gsd/`
|
|
80
|
+
*/
|
|
81
|
+
export function hasGitTrackedGsdFiles(basePath) {
|
|
82
|
+
const localGsd = join(basePath, ".gsd");
|
|
83
|
+
// If .gsd doesn't exist or is already a symlink, no tracked files concern
|
|
84
|
+
if (!existsSync(localGsd))
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
if (lstatSync(localGsd).isSymbolicLink())
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// Check if git tracks any files under .gsd/
|
|
94
|
+
try {
|
|
95
|
+
const tracked = nativeLsFiles(basePath, ".gsd");
|
|
96
|
+
return tracked.length > 0;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Not a git repo or git not available — safe to proceed
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Ensure basePath/.gitignore contains baseline ignore patterns.
|
|
105
|
+
* Creates the file if missing; appends missing patterns.
|
|
72
106
|
* Returns true if the file was created or modified, false if already complete.
|
|
73
107
|
*
|
|
74
|
-
* `.gsd/`
|
|
75
|
-
*
|
|
108
|
+
* **Safety check:** If `.gsd/` contains git-tracked files (i.e., the project
|
|
109
|
+
* intentionally keeps `.gsd/` in version control), the `.gsd` ignore pattern
|
|
110
|
+
* is excluded to prevent data loss. Only the `.gsd` pattern is affected —
|
|
111
|
+
* all other baseline patterns are still applied normally.
|
|
76
112
|
*/
|
|
77
113
|
export function ensureGitignore(basePath, options) {
|
|
78
114
|
// If manage_gitignore is explicitly false, do not touch .gitignore at all
|
|
@@ -88,8 +124,14 @@ export function ensureGitignore(basePath, options) {
|
|
|
88
124
|
.split("\n")
|
|
89
125
|
.map((l) => l.trim())
|
|
90
126
|
.filter((l) => l && !l.startsWith("#")));
|
|
127
|
+
// Determine which patterns to apply. If .gsd/ has tracked files,
|
|
128
|
+
// exclude the ".gsd" pattern to prevent deleting tracked state.
|
|
129
|
+
const gsdIsTracked = hasGitTrackedGsdFiles(basePath);
|
|
130
|
+
const patternsToApply = gsdIsTracked
|
|
131
|
+
? BASELINE_PATTERNS.filter((p) => p !== ".gsd")
|
|
132
|
+
: BASELINE_PATTERNS;
|
|
91
133
|
// Find patterns not yet present
|
|
92
|
-
const missing =
|
|
134
|
+
const missing = patternsToApply.filter((p) => !existingLines.has(p));
|
|
93
135
|
if (missing.length === 0)
|
|
94
136
|
return false;
|
|
95
137
|
// Build the block to append
|
|
@@ -111,6 +153,11 @@ export function ensureGitignore(basePath, options) {
|
|
|
111
153
|
* already in the index even after .gitignore is updated.
|
|
112
154
|
*
|
|
113
155
|
* Only removes from the index (`--cached`), never from disk. Idempotent.
|
|
156
|
+
*
|
|
157
|
+
* Note: These are strictly runtime/ephemeral paths (activity logs, lock files,
|
|
158
|
+
* metrics, STATE.md). They are always safe to untrack, even when the project
|
|
159
|
+
* intentionally keeps other `.gsd/` files (like PROJECT.md, milestones/) in
|
|
160
|
+
* version control.
|
|
114
161
|
*/
|
|
115
162
|
export function untrackRuntimeFiles(basePath) {
|
|
116
163
|
const runtimePaths = GSD_RUNTIME_PATTERNS;
|
|
@@ -17,6 +17,7 @@ import { resolveExpectedArtifactPath } from "./auto.js";
|
|
|
17
17
|
import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, } from "./paths.js";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { readFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
|
|
20
|
+
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
20
21
|
import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
|
|
21
22
|
import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
|
|
22
23
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
@@ -154,7 +155,7 @@ function parseMilestoneSequenceFromProject(content) {
|
|
|
154
155
|
* This is the only way the wizard triggers work — everything else is the LLM's job.
|
|
155
156
|
*/
|
|
156
157
|
function dispatchWorkflow(pi, note, customType = "gsd-run") {
|
|
157
|
-
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".
|
|
158
|
+
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|
|
158
159
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
159
160
|
pi.sendMessage({
|
|
160
161
|
customType,
|
|
@@ -426,7 +427,12 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
426
427
|
// If all pending slices are discussed, notify and exit instead of looping
|
|
427
428
|
const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
|
|
428
429
|
if (allDiscussed) {
|
|
429
|
-
|
|
430
|
+
const lockData = readSessionLockData(basePath);
|
|
431
|
+
const remoteAutoRunning = lockData && lockData.pid !== process.pid && isSessionLockProcessAlive(lockData);
|
|
432
|
+
const nextStep = remoteAutoRunning
|
|
433
|
+
? "Auto-mode is already running — use /gsd status to check progress."
|
|
434
|
+
: "Run /gsd to start planning.";
|
|
435
|
+
ctx.ui.notify(`All ${pendingSlices.length} slices discussed. ${nextStep}`, "info");
|
|
430
436
|
return;
|
|
431
437
|
}
|
|
432
438
|
// Find the first undiscussed slice to recommend
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure GSD health widget logic.
|
|
3
|
+
*
|
|
4
|
+
* Separates project-state detection and line rendering from the widget's
|
|
5
|
+
* runtime integrations so the regressions can be tested directly.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
8
|
+
import { gsdRoot } from "./paths.js";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
export function detectHealthWidgetProjectState(basePath) {
|
|
11
|
+
const root = gsdRoot(basePath);
|
|
12
|
+
if (!existsSync(root))
|
|
13
|
+
return "none";
|
|
14
|
+
// Lightweight milestone count — avoids the full detectProjectState() scan
|
|
15
|
+
// (CI markers, Makefile targets, etc.) that is unnecessary on the 60s refresh.
|
|
16
|
+
try {
|
|
17
|
+
const milestonesDir = join(root, "milestones");
|
|
18
|
+
if (existsSync(milestonesDir)) {
|
|
19
|
+
const entries = readdirSync(milestonesDir, { withFileTypes: true });
|
|
20
|
+
if (entries.some(e => e.isDirectory()))
|
|
21
|
+
return "active";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch { /* non-fatal */ }
|
|
25
|
+
return "initialized";
|
|
26
|
+
}
|
|
27
|
+
function formatCost(n) {
|
|
28
|
+
return n >= 1 ? `$${n.toFixed(2)}` : `${(n * 100).toFixed(1)}¢`;
|
|
29
|
+
}
|
|
30
|
+
function formatProgress(progress) {
|
|
31
|
+
if (!progress)
|
|
32
|
+
return null;
|
|
33
|
+
const parts = [];
|
|
34
|
+
parts.push(`M ${progress.milestones.done}/${progress.milestones.total}`);
|
|
35
|
+
if (progress.slices)
|
|
36
|
+
parts.push(`S ${progress.slices.done}/${progress.slices.total}`);
|
|
37
|
+
if (progress.tasks)
|
|
38
|
+
parts.push(`T ${progress.tasks.done}/${progress.tasks.total}`);
|
|
39
|
+
return parts.length > 0 ? `Progress: ${parts.join(" · ")}` : null;
|
|
40
|
+
}
|
|
41
|
+
function formatEnvironmentSummary(errorCount, warningCount) {
|
|
42
|
+
if (errorCount <= 0 && warningCount <= 0)
|
|
43
|
+
return null;
|
|
44
|
+
const parts = [];
|
|
45
|
+
if (errorCount > 0)
|
|
46
|
+
parts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
|
|
47
|
+
if (warningCount > 0)
|
|
48
|
+
parts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
|
|
49
|
+
return `Env: ${parts.join(", ")}`;
|
|
50
|
+
}
|
|
51
|
+
function formatBudgetSummary(data) {
|
|
52
|
+
if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
|
|
53
|
+
const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
|
|
54
|
+
return `Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`;
|
|
55
|
+
}
|
|
56
|
+
if (data.budgetSpent > 0) {
|
|
57
|
+
return `Spent: ${formatCost(data.budgetSpent)}`;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function buildExecutionHeadline(data) {
|
|
62
|
+
const status = data.executionStatus ?? "Active project";
|
|
63
|
+
const target = data.executionTarget ?? data.blocker ?? "loading status…";
|
|
64
|
+
return ` GSD ${status}${target ? ` - ${target}` : ""}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build compact health lines for the widget.
|
|
68
|
+
* Returns a string array suitable for setWidget().
|
|
69
|
+
*/
|
|
70
|
+
export function buildHealthLines(data) {
|
|
71
|
+
if (data.projectState === "none") {
|
|
72
|
+
return [" GSD No project loaded — run /gsd to start"];
|
|
73
|
+
}
|
|
74
|
+
if (data.projectState === "initialized") {
|
|
75
|
+
return [" GSD Project initialized — run /gsd to continue setup"];
|
|
76
|
+
}
|
|
77
|
+
const lines = [buildExecutionHeadline(data)];
|
|
78
|
+
const details = [];
|
|
79
|
+
const progress = formatProgress(data.progress);
|
|
80
|
+
if (progress)
|
|
81
|
+
details.push(progress);
|
|
82
|
+
if (data.providerIssue)
|
|
83
|
+
details.push(data.providerIssue);
|
|
84
|
+
const environment = formatEnvironmentSummary(data.environmentErrorCount, data.environmentWarningCount);
|
|
85
|
+
if (environment)
|
|
86
|
+
details.push(environment);
|
|
87
|
+
const budget = formatBudgetSummary(data);
|
|
88
|
+
if (budget)
|
|
89
|
+
details.push(budget);
|
|
90
|
+
if (data.eta)
|
|
91
|
+
details.push(data.eta);
|
|
92
|
+
if (details.length > 0) {
|
|
93
|
+
lines.push(` ${details.join(" │ ")}`);
|
|
94
|
+
}
|
|
95
|
+
return lines;
|
|
96
|
+
}
|