gsd-pi 2.37.0 → 2.37.1-dev.7775114
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 +20 -19
- package/dist/resources/extensions/cmux/package.json +7 -0
- package/dist/resources/extensions/gsd/auto-loop.js +18 -4
- package/dist/resources/extensions/gsd/auto.js +42 -5
- package/dist/resources/extensions/gsd/commands.js +80 -33
- 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/session-lock.js +26 -6
- 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 +1 -1
- 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/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/package.json +7 -0
- package/src/resources/extensions/gsd/auto-loop.ts +24 -6
- package/src/resources/extensions/gsd/auto.ts +56 -5
- package/src/resources/extensions/gsd/commands.ts +85 -31
- 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/session-lock.ts +41 -6
- 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/session-lock-regression.test.ts +45 -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -12,7 +12,7 @@ import { deriveState } from "./state.js";
|
|
|
12
12
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
13
13
|
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
14
14
|
import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
|
|
15
|
-
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote } from "./auto.js";
|
|
15
|
+
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, stopAutoRemote, checkRemoteAutoSession } from "./auto.js";
|
|
16
16
|
import { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
17
17
|
import { resolveProjectRoot } from "./worktree.js";
|
|
18
18
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
@@ -36,8 +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
39
|
import { handleCmux } from "./commands-cmux.js";
|
|
40
|
+
import { showNextAction } from "../shared/mod.js";
|
|
41
41
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
42
42
|
export function projectRoot() {
|
|
43
43
|
const cwd = process.cwd();
|
|
@@ -57,36 +57,81 @@ export function projectRoot() {
|
|
|
57
57
|
return root;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
* Returns
|
|
60
|
+
* Guard against starting auto-mode when a remote session is already running.
|
|
61
|
+
* Returns true if the caller should proceed with startAuto, false if handled.
|
|
62
62
|
*/
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
async function guardRemoteSession(ctx, pi) {
|
|
64
|
+
// Local session already active — proceed (startAuto handles re-entrant calls)
|
|
65
|
+
if (isAutoActive() || isAutoPaused())
|
|
66
|
+
return true;
|
|
67
|
+
const remote = checkRemoteAutoSession(projectRoot());
|
|
68
|
+
if (!remote.running || !remote.pid)
|
|
69
|
+
return true;
|
|
70
|
+
const unitLabel = remote.unitType && remote.unitId
|
|
71
|
+
? `${remote.unitType} (${remote.unitId})`
|
|
72
|
+
: "unknown unit";
|
|
73
|
+
const unitsMsg = remote.completedUnits != null
|
|
74
|
+
? `${remote.completedUnits} units completed`
|
|
75
|
+
: "";
|
|
76
|
+
const choice = await showNextAction(ctx, {
|
|
77
|
+
title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
|
|
78
|
+
summary: [
|
|
79
|
+
`Currently executing: ${unitLabel}`,
|
|
80
|
+
...(unitsMsg ? [unitsMsg] : []),
|
|
81
|
+
...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
|
|
82
|
+
],
|
|
83
|
+
actions: [
|
|
84
|
+
{
|
|
85
|
+
id: "status",
|
|
86
|
+
label: "View status",
|
|
87
|
+
description: "Show the current GSD progress dashboard.",
|
|
88
|
+
recommended: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "steer",
|
|
92
|
+
label: "Steer the session",
|
|
93
|
+
description: "Use /gsd steer <instruction> to redirect the running session.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "stop",
|
|
97
|
+
label: "Stop remote session",
|
|
98
|
+
description: `Send SIGTERM to PID ${remote.pid} to stop it gracefully.`,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "force",
|
|
102
|
+
label: "Force start (steal lock)",
|
|
103
|
+
description: "Start a new session, terminating the existing one.",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
notYetMessage: "Run /gsd when ready.",
|
|
107
|
+
});
|
|
108
|
+
if (choice === "status") {
|
|
109
|
+
await handleStatus(ctx);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (choice === "steer") {
|
|
113
|
+
ctx.ui.notify("Use /gsd steer <instruction> to redirect the running auto-mode session.\n" +
|
|
114
|
+
"Example: /gsd steer Use Postgres instead of SQLite", "info");
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (choice === "stop") {
|
|
118
|
+
const result = stopAutoRemote(projectRoot());
|
|
119
|
+
if (result.found) {
|
|
120
|
+
ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
|
|
121
|
+
}
|
|
122
|
+
else if (result.error) {
|
|
123
|
+
ctx.ui.notify(`Failed to stop remote auto-mode: ${result.error}`, "error");
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
ctx.ui.notify("Remote session is no longer running.", "info");
|
|
127
|
+
}
|
|
80
128
|
return false;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
` /gsd capture — fire-and-forget thought\n` +
|
|
88
|
-
` /gsd stop — stop auto-mode`, "warning");
|
|
89
|
-
return true;
|
|
129
|
+
}
|
|
130
|
+
if (choice === "force") {
|
|
131
|
+
return true; // Proceed — startAuto will steal the lock
|
|
132
|
+
}
|
|
133
|
+
// "not_yet" or escape
|
|
134
|
+
return false;
|
|
90
135
|
}
|
|
91
136
|
export function registerGSDCommand(pi) {
|
|
92
137
|
pi.registerCommand("gsd", {
|
|
@@ -542,12 +587,12 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
542
587
|
await handleDryRun(ctx, projectRoot());
|
|
543
588
|
return;
|
|
544
589
|
}
|
|
545
|
-
if (notifyRemoteAutoActive(ctx, projectRoot()))
|
|
546
|
-
return;
|
|
547
590
|
const verboseMode = trimmed.includes("--verbose");
|
|
548
591
|
const debugMode = trimmed.includes("--debug");
|
|
549
592
|
if (debugMode)
|
|
550
593
|
enableDebug(projectRoot());
|
|
594
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
595
|
+
return;
|
|
551
596
|
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
552
597
|
return;
|
|
553
598
|
}
|
|
@@ -556,6 +601,8 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
556
601
|
const debugMode = trimmed.includes("--debug");
|
|
557
602
|
if (debugMode)
|
|
558
603
|
enableDebug(projectRoot());
|
|
604
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
605
|
+
return;
|
|
559
606
|
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
560
607
|
return;
|
|
561
608
|
}
|
|
@@ -899,7 +946,7 @@ Examples:
|
|
|
899
946
|
return;
|
|
900
947
|
}
|
|
901
948
|
if (trimmed === "") {
|
|
902
|
-
if (
|
|
949
|
+
if (!(await guardRemoteSession(ctx, pi)))
|
|
903
950
|
return;
|
|
904
951
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
905
952
|
return;
|
|
@@ -349,10 +349,18 @@ export class GitServiceImpl {
|
|
|
349
349
|
}
|
|
350
350
|
const wtName = detectWorktreeName(this.basePath);
|
|
351
351
|
if (wtName) {
|
|
352
|
+
// Auto-mode worktrees use milestone/<MID> branches (wtName = milestone ID)
|
|
353
|
+
const milestoneBranch = `milestone/${wtName}`;
|
|
354
|
+
const currentBranch = nativeGetCurrentBranch(this.basePath);
|
|
355
|
+
// If we're on a milestone/<MID> branch, use it (auto-mode case)
|
|
356
|
+
if (currentBranch.startsWith("milestone/")) {
|
|
357
|
+
return currentBranch;
|
|
358
|
+
}
|
|
359
|
+
// Otherwise check for manual worktree branch (worktree/<name>)
|
|
352
360
|
const wtBranch = `worktree/${wtName}`;
|
|
353
361
|
if (nativeBranchExists(this.basePath, wtBranch))
|
|
354
362
|
return wtBranch;
|
|
355
|
-
return
|
|
363
|
+
return currentBranch;
|
|
356
364
|
}
|
|
357
365
|
// Repo-level default detection: origin/HEAD → main → master → current branch.
|
|
358
366
|
// Native path uses libgit2 (single call), fallback spawns multiple git processes.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// GSD Extension — Session History View
|
|
2
2
|
// Human-readable display of past auto-mode unit executions.
|
|
3
|
-
import { formatDuration,
|
|
3
|
+
import { formatDuration, truncateWithEllipsis } from "../shared/format-utils.js";
|
|
4
|
+
import { padRight } from "../shared/layout-utils.js";
|
|
4
5
|
import { getLedger, getProjectTotals, formatCost, formatTokenCount, aggregateBySlice, aggregateByPhase, aggregateByModel, loadLedgerFromDisk, } from "./metrics.js";
|
|
5
6
|
/**
|
|
6
7
|
* Show recent unit execution history with cost, tokens, and duration.
|
|
@@ -17,8 +17,10 @@ import { gsdRoot } from "./paths.js";
|
|
|
17
17
|
import { getAndClearSkills } from "./skill-telemetry.js";
|
|
18
18
|
import { loadJsonFile, loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
19
19
|
import { parseUnitId } from "./unit-id.js";
|
|
20
|
-
// Re-export from shared —
|
|
21
|
-
|
|
20
|
+
// Re-export from shared — import directly from format-utils to avoid pulling
|
|
21
|
+
// in the full barrel (mod.js → ui.js → @gsd/pi-tui) which breaks when loaded
|
|
22
|
+
// outside jiti's alias resolution (e.g. dynamic import in auto-loop reports).
|
|
23
|
+
export { formatTokenCount } from "../shared/format-utils.js";
|
|
22
24
|
export function classifyUnitPhase(unitType) {
|
|
23
25
|
switch (unitType) {
|
|
24
26
|
case "research-milestone":
|
|
@@ -320,7 +320,7 @@ export function updateSessionLock(basePath, unitType, unitId, completedUnits, se
|
|
|
320
320
|
*
|
|
321
321
|
* This is called periodically during the dispatch loop.
|
|
322
322
|
*/
|
|
323
|
-
export function
|
|
323
|
+
export function getSessionLockStatus(basePath) {
|
|
324
324
|
// Lock was compromised by proper-lockfile (mtime drift from sleep, stall, etc.)
|
|
325
325
|
if (_lockCompromised) {
|
|
326
326
|
// Recovery gate (#1512): Before declaring the lock lost, check if the lock
|
|
@@ -335,27 +335,47 @@ export function validateSessionLock(basePath) {
|
|
|
335
335
|
const result = acquireSessionLock(basePath);
|
|
336
336
|
if (result.acquired) {
|
|
337
337
|
process.stderr.write(`[gsd] Lock recovered after onCompromised — lock file PID matched, re-acquired.\n`);
|
|
338
|
-
return true;
|
|
338
|
+
return { valid: true, recovered: true };
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
catch {
|
|
342
342
|
// Re-acquisition failed — fall through to return false
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
|
-
return
|
|
345
|
+
return {
|
|
346
|
+
valid: false,
|
|
347
|
+
failureReason: "compromised",
|
|
348
|
+
existingPid: existing?.pid,
|
|
349
|
+
expectedPid: process.pid,
|
|
350
|
+
};
|
|
346
351
|
}
|
|
347
352
|
// If we have an OS-level lock, we're still the owner
|
|
348
353
|
if (_releaseFunction && _lockedPath === basePath) {
|
|
349
|
-
return true;
|
|
354
|
+
return { valid: true };
|
|
350
355
|
}
|
|
351
356
|
// Fallback: check the lock file PID
|
|
352
357
|
const lp = lockPath(basePath);
|
|
353
358
|
const existing = readExistingLockData(lp);
|
|
354
359
|
if (!existing) {
|
|
355
360
|
// Lock file was deleted — we lost ownership
|
|
356
|
-
return
|
|
361
|
+
return {
|
|
362
|
+
valid: false,
|
|
363
|
+
failureReason: "missing-metadata",
|
|
364
|
+
expectedPid: process.pid,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (existing.pid !== process.pid) {
|
|
368
|
+
return {
|
|
369
|
+
valid: false,
|
|
370
|
+
failureReason: "pid-mismatch",
|
|
371
|
+
existingPid: existing.pid,
|
|
372
|
+
expectedPid: process.pid,
|
|
373
|
+
};
|
|
357
374
|
}
|
|
358
|
-
return
|
|
375
|
+
return { valid: true };
|
|
376
|
+
}
|
|
377
|
+
export function validateSessionLock(basePath) {
|
|
378
|
+
return getSessionLockStatus(basePath).valid;
|
|
359
379
|
}
|
|
360
380
|
/**
|
|
361
381
|
* Release the session lock. Called on clean stop/pause.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared formatting
|
|
2
|
+
* Shared pure formatting utilities — no @gsd/pi-tui dependency.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* ANSI-aware layout helpers (padRight, joinColumns, centerLine, fitColumns)
|
|
5
|
+
* live in layout-utils.ts to avoid pulling @gsd/pi-tui into modules that
|
|
6
|
+
* run outside jiti's alias resolution (e.g. HTML report generation via
|
|
7
|
+
* dynamic import in auto-loop).
|
|
6
8
|
*/
|
|
7
|
-
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
8
9
|
// ─── Duration Formatting ──────────────────────────────────────────────────────
|
|
9
10
|
/** Format a millisecond duration as a compact human-readable string. */
|
|
10
11
|
export function formatDuration(ms) {
|
|
@@ -30,43 +31,6 @@ export function formatTokenCount(count) {
|
|
|
30
31
|
return `${(count / 1000).toFixed(1)}k`;
|
|
31
32
|
return `${(count / 1_000_000).toFixed(2)}M`;
|
|
32
33
|
}
|
|
33
|
-
// ─── Layout Helpers ───────────────────────────────────────────────────────────
|
|
34
|
-
/** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
|
|
35
|
-
export function padRight(content, width) {
|
|
36
|
-
const vis = visibleWidth(content);
|
|
37
|
-
return content + " ".repeat(Math.max(0, width - vis));
|
|
38
|
-
}
|
|
39
|
-
/** Build a line with left-aligned and right-aligned content. */
|
|
40
|
-
export function joinColumns(left, right, width) {
|
|
41
|
-
const leftW = visibleWidth(left);
|
|
42
|
-
const rightW = visibleWidth(right);
|
|
43
|
-
if (leftW + rightW + 2 > width) {
|
|
44
|
-
return truncateToWidth(`${left} ${right}`, width);
|
|
45
|
-
}
|
|
46
|
-
return left + " ".repeat(width - leftW - rightW) + right;
|
|
47
|
-
}
|
|
48
|
-
/** Center content within `width` (ANSI-aware). */
|
|
49
|
-
export function centerLine(content, width) {
|
|
50
|
-
const vis = visibleWidth(content);
|
|
51
|
-
if (vis >= width)
|
|
52
|
-
return truncateToWidth(content, width);
|
|
53
|
-
const leftPad = Math.floor((width - vis) / 2);
|
|
54
|
-
return " ".repeat(leftPad) + content;
|
|
55
|
-
}
|
|
56
|
-
/** Join as many parts as fit within `width`, separated by `separator`. */
|
|
57
|
-
export function fitColumns(parts, width, separator = " ") {
|
|
58
|
-
const filtered = parts.filter(Boolean);
|
|
59
|
-
if (filtered.length === 0)
|
|
60
|
-
return "";
|
|
61
|
-
let result = filtered[0];
|
|
62
|
-
for (let i = 1; i < filtered.length; i++) {
|
|
63
|
-
const candidate = `${result}${separator}${filtered[i]}`;
|
|
64
|
-
if (visibleWidth(candidate) > width)
|
|
65
|
-
break;
|
|
66
|
-
result = candidate;
|
|
67
|
-
}
|
|
68
|
-
return truncateToWidth(result, width);
|
|
69
|
-
}
|
|
70
34
|
// ─── Text Truncation ─────────────────────────────────────────────────────────
|
|
71
35
|
/** Truncate a string to `maxLength` characters, replacing the last character with an ellipsis if needed. */
|
|
72
36
|
export function truncateWithEllipsis(text, maxLength) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI-aware TUI layout utilities that depend on @gsd/pi-tui.
|
|
3
|
+
*
|
|
4
|
+
* Separated from format-utils.ts so that modules needing only pure
|
|
5
|
+
* formatting (e.g. HTML report generation) can import format-utils
|
|
6
|
+
* without pulling in the @gsd/pi-tui dependency — which fails when
|
|
7
|
+
* loaded outside jiti's alias resolution context.
|
|
8
|
+
*/
|
|
9
|
+
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
10
|
+
// ─── Layout Helpers ───────────────────────────────────────────────────────────
|
|
11
|
+
/** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
|
|
12
|
+
export function padRight(content, width) {
|
|
13
|
+
const vis = visibleWidth(content);
|
|
14
|
+
return content + " ".repeat(Math.max(0, width - vis));
|
|
15
|
+
}
|
|
16
|
+
/** Build a line with left-aligned and right-aligned content. */
|
|
17
|
+
export function joinColumns(left, right, width) {
|
|
18
|
+
const leftW = visibleWidth(left);
|
|
19
|
+
const rightW = visibleWidth(right);
|
|
20
|
+
if (leftW + rightW + 2 > width) {
|
|
21
|
+
return truncateToWidth(`${left} ${right}`, width);
|
|
22
|
+
}
|
|
23
|
+
return left + " ".repeat(width - leftW - rightW) + right;
|
|
24
|
+
}
|
|
25
|
+
/** Center content within `width` (ANSI-aware). */
|
|
26
|
+
export function centerLine(content, width) {
|
|
27
|
+
const vis = visibleWidth(content);
|
|
28
|
+
if (vis >= width)
|
|
29
|
+
return truncateToWidth(content, width);
|
|
30
|
+
const leftPad = Math.floor((width - vis) / 2);
|
|
31
|
+
return " ".repeat(leftPad) + content;
|
|
32
|
+
}
|
|
33
|
+
/** Join as many parts as fit within `width`, separated by `separator`. */
|
|
34
|
+
export function fitColumns(parts, width, separator = " ") {
|
|
35
|
+
const filtered = parts.filter(Boolean);
|
|
36
|
+
if (filtered.length === 0)
|
|
37
|
+
return "";
|
|
38
|
+
let result = filtered[0];
|
|
39
|
+
for (let i = 1; i < filtered.length; i++) {
|
|
40
|
+
const candidate = `${result}${separator}${filtered[i]}`;
|
|
41
|
+
if (visibleWidth(candidate) > width)
|
|
42
|
+
break;
|
|
43
|
+
result = candidate;
|
|
44
|
+
}
|
|
45
|
+
return truncateToWidth(result, width);
|
|
46
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Barrel file — re-exports consumed by external modules
|
|
2
2
|
export { makeUI, GLYPH, INDENT, STATUS_GLYPH, STATUS_COLOR, } from "./ui.js";
|
|
3
|
-
export { stripAnsi, formatTokenCount, formatDuration,
|
|
3
|
+
export { stripAnsi, formatTokenCount, formatDuration, sparkline, normalizeStringArray, fileLink, } from "./format-utils.js";
|
|
4
|
+
export { padRight, joinColumns, centerLine, fitColumns, } from "./layout-utils.js";
|
|
4
5
|
export { shortcutDesc } from "./terminal.js";
|
|
5
6
|
export { toPosixPath } from "./path-display.js";
|
|
6
7
|
export { showInterviewRound } from "./interview-ui.js";
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyBH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,KAAK,EACX,SAAS,EAET,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EAKpB,MAAM,YAAY,CAAC;AAoGpB,wBAAsB,qBAAqB,CAAC,CAAC,GAAG,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAI/G;AA6BD;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAmCzD;AAkMD;;GAEG;AACH,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,gBAAgB,EACzB,aAAa,SAAa,GACxB,OAAO,CAAC,SAAS,CAAC,CAKpB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyBrH;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyBH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,KAAK,EACX,SAAS,EAET,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EAKpB,MAAM,YAAY,CAAC;AAoGpB,wBAAsB,qBAAqB,CAAC,CAAC,GAAG,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAI/G;AA6BD;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAmCzD;AAkMD;;GAEG;AACH,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,gBAAgB,EACzB,aAAa,SAAa,GACxB,OAAO,CAAC,SAAS,CAAC,CAKpB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyBrH;AAmHD;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,eAAe,EAAE,MAAM,EAAE,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,EAChC,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,oBAAoB,CAAC,CAoD/B"}
|