gsd-pi 2.37.1-dev.d3ace49 → 2.38.0-dev.361f5e3
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/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +7 -8
- package/dist/resources/extensions/gsd/auto-loop.js +149 -170
- package/dist/resources/extensions/gsd/auto-post-unit.js +92 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +7 -31
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +2 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +8 -1
- package/dist/resources/extensions/gsd/index.js +2 -1
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +8 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +6 -8
- package/src/resources/extensions/gsd/auto-loop.ts +207 -252
- package/src/resources/extensions/gsd/auto-post-unit.ts +69 -41
- package/src/resources/extensions/gsd/auto-prompts.ts +7 -33
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +2 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +13 -1
- package/src/resources/extensions/gsd/index.ts +3 -1
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +8 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +16 -37
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -22,25 +22,24 @@ function missingSliceStop(mid, phase) {
|
|
|
22
22
|
}
|
|
23
23
|
// ─── Rewrite Circuit Breaker ──────────────────────────────────────────────
|
|
24
24
|
const MAX_REWRITE_ATTEMPTS = 3;
|
|
25
|
-
let rewriteAttemptCount = 0;
|
|
26
|
-
export function resetRewriteCircuitBreaker() {
|
|
27
|
-
rewriteAttemptCount = 0;
|
|
28
|
-
}
|
|
29
25
|
// ─── Rules ────────────────────────────────────────────────────────────────
|
|
30
26
|
const DISPATCH_RULES = [
|
|
31
27
|
{
|
|
32
28
|
name: "rewrite-docs (override gate)",
|
|
33
|
-
match: async ({ mid, midTitle, state, basePath }) => {
|
|
29
|
+
match: async ({ mid, midTitle, state, basePath, session }) => {
|
|
34
30
|
const pendingOverrides = await loadActiveOverrides(basePath);
|
|
35
31
|
if (pendingOverrides.length === 0)
|
|
36
32
|
return null;
|
|
37
|
-
|
|
33
|
+
const count = session?.rewriteAttemptCount ?? 0;
|
|
34
|
+
if (count >= MAX_REWRITE_ATTEMPTS) {
|
|
38
35
|
const { resolveAllOverrides } = await import("./files.js");
|
|
39
36
|
await resolveAllOverrides(basePath);
|
|
40
|
-
|
|
37
|
+
if (session)
|
|
38
|
+
session.rewriteAttemptCount = 0;
|
|
41
39
|
return null;
|
|
42
40
|
}
|
|
43
|
-
|
|
41
|
+
if (session)
|
|
42
|
+
session.rewriteAttemptCount++;
|
|
44
43
|
const unitId = state.activeSlice ? `${mid}/${state.activeSlice.id}` : mid;
|
|
45
44
|
return {
|
|
46
45
|
action: "dispatch",
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* pattern with a while loop. The agent_end event resolves a promise instead
|
|
6
6
|
* of recursing.
|
|
7
7
|
*
|
|
8
|
-
* MAINTENANCE RULE:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* MAINTENANCE RULE: Module-level mutable state is limited to `_currentResolve`
|
|
9
|
+
* (per-unit one-shot resolver) and `_sessionSwitchInFlight` (guard for
|
|
10
|
+
* session rotation). No queue — stale agent_end events are dropped.
|
|
11
11
|
*/
|
|
12
12
|
import { NEW_SESSION_TIMEOUT_MS } from "./auto/session.js";
|
|
13
13
|
import { debugLog } from "./debug-logger.js";
|
|
@@ -18,71 +18,68 @@ import { debugLog } from "./debug-logger.js";
|
|
|
18
18
|
* generous headroom including retries and sidecar work.
|
|
19
19
|
*/
|
|
20
20
|
const MAX_LOOP_ITERATIONS = 500;
|
|
21
|
-
|
|
21
|
+
/** Data-driven budget threshold notifications (descending). The 100% entry
|
|
22
|
+
* triggers special enforcement logic (halt/pause/warn); sub-100 entries fire
|
|
23
|
+
* a simple notification. */
|
|
24
|
+
const BUDGET_THRESHOLDS = [
|
|
25
|
+
{ pct: 100, label: "Budget ceiling reached", notifyLevel: "error", cmuxLevel: "error" },
|
|
26
|
+
{ pct: 90, label: "Budget 90%", notifyLevel: "warning", cmuxLevel: "warning" },
|
|
27
|
+
{ pct: 80, label: "Approaching budget ceiling — 80%", notifyLevel: "warning", cmuxLevel: "warning" },
|
|
28
|
+
{ pct: 75, label: "Budget 75%", notifyLevel: "info", cmuxLevel: "progress" },
|
|
29
|
+
];
|
|
30
|
+
// ─── Per-unit one-shot promise state ────────────────────────────────────────
|
|
22
31
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*/
|
|
30
|
-
let _activeSession = null;
|
|
32
|
+
// A single module-level resolve function scoped to the current unit execution.
|
|
33
|
+
// No queue — if an agent_end arrives with no pending resolver, it is dropped
|
|
34
|
+
// (logged as warning). This is simpler and safer than the previous session-
|
|
35
|
+
// scoped pendingResolve + pendingAgentEndQueue pattern.
|
|
36
|
+
let _currentResolve = null;
|
|
37
|
+
let _sessionSwitchInFlight = false;
|
|
31
38
|
// ─── resolveAgentEnd ─────────────────────────────────────────────────────────
|
|
32
39
|
/**
|
|
33
40
|
* Called from the agent_end event handler in index.ts to resolve the
|
|
34
41
|
* in-flight unit promise. One-shot: the resolver is nulled before calling
|
|
35
42
|
* to prevent double-resolution from model fallback retries.
|
|
36
43
|
*
|
|
37
|
-
* If no
|
|
38
|
-
* the event is
|
|
44
|
+
* If no resolver exists (event arrived between loop iterations or during
|
|
45
|
+
* session switch), the event is dropped with a debug warning.
|
|
39
46
|
*/
|
|
40
47
|
export function resolveAgentEnd(event) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
debugLog("resolveAgentEnd", {
|
|
44
|
-
status: "no-active-session",
|
|
45
|
-
warning: "agent_end with no active loop session",
|
|
46
|
-
});
|
|
48
|
+
if (_sessionSwitchInFlight) {
|
|
49
|
+
debugLog("resolveAgentEnd", { status: "ignored-during-switch" });
|
|
47
50
|
return;
|
|
48
51
|
}
|
|
49
|
-
if (
|
|
52
|
+
if (_currentResolve) {
|
|
50
53
|
debugLog("resolveAgentEnd", { status: "resolving", hasEvent: true });
|
|
51
|
-
const r =
|
|
52
|
-
|
|
54
|
+
const r = _currentResolve;
|
|
55
|
+
_currentResolve = null;
|
|
53
56
|
r({ status: "completed", event });
|
|
54
57
|
}
|
|
55
58
|
else {
|
|
56
|
-
// Queue the event so the next runUnit picks it up immediately
|
|
57
59
|
debugLog("resolveAgentEnd", {
|
|
58
|
-
status: "
|
|
59
|
-
|
|
60
|
-
warning: "agent_end arrived between loop iterations — queued for next runUnit",
|
|
60
|
+
status: "no-pending-resolve",
|
|
61
|
+
warning: "agent_end with no pending unit",
|
|
61
62
|
});
|
|
62
|
-
s.pendingAgentEndQueue.push(event);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
export function isSessionSwitchInFlight() {
|
|
66
|
-
return
|
|
66
|
+
return _sessionSwitchInFlight;
|
|
67
67
|
}
|
|
68
68
|
// ─── resetPendingResolve (test helper) ───────────────────────────────────────
|
|
69
69
|
/**
|
|
70
|
-
* Reset
|
|
71
|
-
* should never call this.
|
|
70
|
+
* Reset module-level promise state. Only exported for test cleanup —
|
|
71
|
+
* production code should never call this.
|
|
72
72
|
*/
|
|
73
73
|
export function _resetPendingResolve() {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
_activeSession.pendingAgentEndQueue = [];
|
|
77
|
-
}
|
|
78
|
-
_activeSession = null;
|
|
74
|
+
_currentResolve = null;
|
|
75
|
+
_sessionSwitchInFlight = false;
|
|
79
76
|
}
|
|
80
77
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
78
|
+
* No-op for backward compatibility with tests that previously set the
|
|
79
|
+
* active session. The module no longer holds a session reference.
|
|
83
80
|
*/
|
|
84
|
-
export function _setActiveSession(
|
|
85
|
-
|
|
81
|
+
export function _setActiveSession(_session) {
|
|
82
|
+
// No-op — kept for test backward compatibility
|
|
86
83
|
}
|
|
87
84
|
// ─── runUnit ─────────────────────────────────────────────────────────────────
|
|
88
85
|
/**
|
|
@@ -93,41 +90,16 @@ export function _setActiveSession(session) {
|
|
|
93
90
|
* On session creation failure or timeout, returns { status: 'cancelled' }
|
|
94
91
|
* without awaiting the promise.
|
|
95
92
|
*/
|
|
96
|
-
export async function runUnit(ctx, pi, s, unitType, unitId, prompt
|
|
93
|
+
export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
97
94
|
debugLog("runUnit", { phase: "start", unitType, unitId });
|
|
98
|
-
// ── Drain queued events from error-recovery retries ──
|
|
99
|
-
// If an agent_end arrived between iterations (e.g. from a model fallback
|
|
100
|
-
// sendMessage retry), consume it immediately instead of creating a new promise.
|
|
101
|
-
// Cap queue to 3 entries to prevent unbounded growth from stale events.
|
|
102
|
-
if (s.pendingAgentEndQueue.length > 3) {
|
|
103
|
-
debugLog("runUnit", {
|
|
104
|
-
phase: "queue-overflow",
|
|
105
|
-
dropped: s.pendingAgentEndQueue.length - 1,
|
|
106
|
-
unitType,
|
|
107
|
-
unitId,
|
|
108
|
-
});
|
|
109
|
-
s.pendingAgentEndQueue = [
|
|
110
|
-
s.pendingAgentEndQueue[s.pendingAgentEndQueue.length - 1],
|
|
111
|
-
];
|
|
112
|
-
}
|
|
113
|
-
if (s.pendingAgentEndQueue.length > 0) {
|
|
114
|
-
const queued = s.pendingAgentEndQueue.shift();
|
|
115
|
-
debugLog("runUnit", {
|
|
116
|
-
phase: "drained-queued-event",
|
|
117
|
-
unitType,
|
|
118
|
-
unitId,
|
|
119
|
-
queueRemaining: s.pendingAgentEndQueue.length,
|
|
120
|
-
});
|
|
121
|
-
return { status: "completed", event: queued };
|
|
122
|
-
}
|
|
123
95
|
// ── Session creation with timeout ──
|
|
124
96
|
debugLog("runUnit", { phase: "session-create", unitType, unitId });
|
|
125
97
|
let sessionResult;
|
|
126
98
|
let sessionTimeoutHandle;
|
|
127
|
-
|
|
99
|
+
_sessionSwitchInFlight = true;
|
|
128
100
|
try {
|
|
129
101
|
const sessionPromise = s.cmdCtx.newSession().finally(() => {
|
|
130
|
-
|
|
102
|
+
_sessionSwitchInFlight = false;
|
|
131
103
|
});
|
|
132
104
|
const timeoutPromise = new Promise((resolve) => {
|
|
133
105
|
sessionTimeoutHandle = setTimeout(() => resolve({ cancelled: true }), NEW_SESSION_TIMEOUT_MS);
|
|
@@ -155,11 +127,12 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt, _prefs) {
|
|
|
155
127
|
if (!s.active) {
|
|
156
128
|
return { status: "cancelled" };
|
|
157
129
|
}
|
|
158
|
-
// ── Create the agent_end promise (
|
|
130
|
+
// ── Create the agent_end promise (per-unit one-shot) ──
|
|
159
131
|
// This happens after newSession completes so session-switch agent_end events
|
|
160
132
|
// from the previous session cannot resolve the new unit.
|
|
133
|
+
_sessionSwitchInFlight = false;
|
|
161
134
|
const unitPromise = new Promise((resolve) => {
|
|
162
|
-
|
|
135
|
+
_currentResolve = resolve;
|
|
163
136
|
});
|
|
164
137
|
// Ensure cwd matches basePath before dispatch (#1389).
|
|
165
138
|
// async_bash and background jobs can drift cwd away from the worktree.
|
|
@@ -184,6 +157,60 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt, _prefs) {
|
|
|
184
157
|
});
|
|
185
158
|
return result;
|
|
186
159
|
}
|
|
160
|
+
// ─── generateMilestoneReport ──────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Generate and write an HTML milestone report snapshot.
|
|
163
|
+
* Extracted from the milestone-transition block in autoLoop.
|
|
164
|
+
*/
|
|
165
|
+
async function generateMilestoneReport(s, ctx, milestoneId) {
|
|
166
|
+
const { loadVisualizerData } = await import("./visualizer-data.js");
|
|
167
|
+
const { generateHtmlReport } = await import("./export-html.js");
|
|
168
|
+
const { writeReportSnapshot } = await import("./reports.js");
|
|
169
|
+
const { basename } = await import("node:path");
|
|
170
|
+
const snapData = await loadVisualizerData(s.basePath);
|
|
171
|
+
const completedMs = snapData.milestones.find((m) => m.id === milestoneId);
|
|
172
|
+
const msTitle = completedMs?.title ?? milestoneId;
|
|
173
|
+
const gsdVersion = process.env.GSD_VERSION ?? "0.0.0";
|
|
174
|
+
const projName = basename(s.basePath);
|
|
175
|
+
const doneSlices = snapData.milestones.reduce((acc, m) => acc + m.slices.filter((sl) => sl.done).length, 0);
|
|
176
|
+
const totalSlices = snapData.milestones.reduce((acc, m) => acc + m.slices.length, 0);
|
|
177
|
+
const outPath = writeReportSnapshot({
|
|
178
|
+
basePath: s.basePath,
|
|
179
|
+
html: generateHtmlReport(snapData, {
|
|
180
|
+
projectName: projName,
|
|
181
|
+
projectPath: s.basePath,
|
|
182
|
+
gsdVersion,
|
|
183
|
+
milestoneId,
|
|
184
|
+
indexRelPath: "index.html",
|
|
185
|
+
}),
|
|
186
|
+
milestoneId,
|
|
187
|
+
milestoneTitle: msTitle,
|
|
188
|
+
kind: "milestone",
|
|
189
|
+
projectName: projName,
|
|
190
|
+
projectPath: s.basePath,
|
|
191
|
+
gsdVersion,
|
|
192
|
+
totalCost: snapData.totals?.cost ?? 0,
|
|
193
|
+
totalTokens: snapData.totals?.tokens.total ?? 0,
|
|
194
|
+
totalDuration: snapData.totals?.duration ?? 0,
|
|
195
|
+
doneSlices,
|
|
196
|
+
totalSlices,
|
|
197
|
+
doneMilestones: snapData.milestones.filter((m) => m.status === "complete").length,
|
|
198
|
+
totalMilestones: snapData.milestones.length,
|
|
199
|
+
phase: snapData.phase,
|
|
200
|
+
});
|
|
201
|
+
ctx.ui.notify(`Report saved: .gsd/reports/${basename(outPath)} — open index.html to browse progression.`, "info");
|
|
202
|
+
}
|
|
203
|
+
// ─── closeoutAndStop ──────────────────────────────────────────────────────────
|
|
204
|
+
/**
|
|
205
|
+
* If a unit is in-flight, close it out, then stop auto-mode.
|
|
206
|
+
* Extracted from ~4 identical if-closeout-then-stop sequences in autoLoop.
|
|
207
|
+
*/
|
|
208
|
+
async function closeoutAndStop(ctx, pi, s, deps, reason) {
|
|
209
|
+
if (s.currentUnit) {
|
|
210
|
+
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
211
|
+
}
|
|
212
|
+
await deps.stopAuto(ctx, pi, reason);
|
|
213
|
+
}
|
|
187
214
|
// ─── autoLoop ────────────────────────────────────────────────────────────────
|
|
188
215
|
/**
|
|
189
216
|
* Main auto-mode execution loop. Iterates: derive → dispatch → guards →
|
|
@@ -195,7 +222,6 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt, _prefs) {
|
|
|
195
222
|
*/
|
|
196
223
|
export async function autoLoop(ctx, pi, s, deps) {
|
|
197
224
|
debugLog("autoLoop", { phase: "enter" });
|
|
198
|
-
_activeSession = s;
|
|
199
225
|
let iteration = 0;
|
|
200
226
|
let lastDerivedUnit = "";
|
|
201
227
|
let sameUnitCount = 0;
|
|
@@ -292,43 +318,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
292
318
|
}
|
|
293
319
|
if (vizPrefs?.auto_report !== false) {
|
|
294
320
|
try {
|
|
295
|
-
|
|
296
|
-
const { generateHtmlReport } = await import("./export-html.js");
|
|
297
|
-
const { writeReportSnapshot } = await import("./reports.js");
|
|
298
|
-
const { basename } = await import("node:path");
|
|
299
|
-
const snapData = await loadVisualizerData(s.basePath);
|
|
300
|
-
const completedMs = snapData.milestones.find((m) => m.id === s.currentMilestoneId);
|
|
301
|
-
const msTitle = completedMs?.title ?? s.currentMilestoneId;
|
|
302
|
-
const gsdVersion = process.env.GSD_VERSION ?? "0.0.0";
|
|
303
|
-
const projName = basename(s.basePath);
|
|
304
|
-
const doneSlices = snapData.milestones.reduce((acc, m) => acc +
|
|
305
|
-
m.slices.filter((sl) => sl.done).length, 0);
|
|
306
|
-
const totalSlices = snapData.milestones.reduce((acc, m) => acc + m.slices.length, 0);
|
|
307
|
-
const outPath = writeReportSnapshot({
|
|
308
|
-
basePath: s.basePath,
|
|
309
|
-
html: generateHtmlReport(snapData, {
|
|
310
|
-
projectName: projName,
|
|
311
|
-
projectPath: s.basePath,
|
|
312
|
-
gsdVersion,
|
|
313
|
-
milestoneId: s.currentMilestoneId,
|
|
314
|
-
indexRelPath: "index.html",
|
|
315
|
-
}),
|
|
316
|
-
milestoneId: s.currentMilestoneId,
|
|
317
|
-
milestoneTitle: msTitle,
|
|
318
|
-
kind: "milestone",
|
|
319
|
-
projectName: projName,
|
|
320
|
-
projectPath: s.basePath,
|
|
321
|
-
gsdVersion,
|
|
322
|
-
totalCost: snapData.totals?.cost ?? 0,
|
|
323
|
-
totalTokens: snapData.totals?.tokens.total ?? 0,
|
|
324
|
-
totalDuration: snapData.totals?.duration ?? 0,
|
|
325
|
-
doneSlices,
|
|
326
|
-
totalSlices,
|
|
327
|
-
doneMilestones: snapData.milestones.filter((m) => m.status === "complete").length,
|
|
328
|
-
totalMilestones: snapData.milestones.length,
|
|
329
|
-
phase: snapData.phase,
|
|
330
|
-
});
|
|
331
|
-
ctx.ui.notify(`Report saved: .gsd/reports/${(await import("node:path")).basename(outPath)} — open index.html to browse progression.`, "info");
|
|
321
|
+
await generateMilestoneReport(s, ctx, s.currentMilestoneId);
|
|
332
322
|
}
|
|
333
323
|
catch (err) {
|
|
334
324
|
ctx.ui.notify(`Report generation failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
@@ -373,7 +363,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
373
363
|
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
374
364
|
}
|
|
375
365
|
const incomplete = state.registry.filter((m) => m.status !== "complete" && m.status !== "parked");
|
|
376
|
-
if (incomplete.length === 0) {
|
|
366
|
+
if (incomplete.length === 0 && state.registry.length > 0) {
|
|
377
367
|
// All milestones complete — merge milestone branch before stopping
|
|
378
368
|
if (s.currentMilestoneId) {
|
|
379
369
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
@@ -382,6 +372,12 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
382
372
|
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, "All milestones complete.", "success");
|
|
383
373
|
await deps.stopAuto(ctx, pi, "All milestones complete");
|
|
384
374
|
}
|
|
375
|
+
else if (incomplete.length === 0 && state.registry.length === 0) {
|
|
376
|
+
// Empty registry — no milestones visible, likely a path resolution bug
|
|
377
|
+
const diag = `basePath=${s.basePath}, phase=${state.phase}`;
|
|
378
|
+
ctx.ui.notify(`No milestones visible in current scope. Possible path resolution issue.\n Diagnostic: ${diag}`, "error");
|
|
379
|
+
await deps.stopAuto(ctx, pi, `No milestones found — check basePath resolution`);
|
|
380
|
+
}
|
|
385
381
|
else if (state.phase === "blocked") {
|
|
386
382
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
387
383
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
@@ -410,13 +406,10 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
410
406
|
midTitle = state.activeMilestone?.title;
|
|
411
407
|
}
|
|
412
408
|
if (!mid || !midTitle) {
|
|
413
|
-
if (s.currentUnit) {
|
|
414
|
-
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
415
|
-
}
|
|
416
409
|
const noMilestoneReason = !mid
|
|
417
410
|
? "No active milestone after merge reconciliation"
|
|
418
411
|
: `Milestone ${mid} has no title after reconciliation`;
|
|
419
|
-
await
|
|
412
|
+
await closeoutAndStop(ctx, pi, s, deps, noMilestoneReason);
|
|
420
413
|
debugLog("autoLoop", {
|
|
421
414
|
phase: "exit",
|
|
422
415
|
reason: "no-milestone-after-reconciliation",
|
|
@@ -425,26 +418,20 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
425
418
|
}
|
|
426
419
|
// Terminal: complete
|
|
427
420
|
if (state.phase === "complete") {
|
|
428
|
-
|
|
429
|
-
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
430
|
-
}
|
|
431
|
-
// Milestone merge on complete
|
|
421
|
+
// Milestone merge on complete (before closeout so branch state is clean)
|
|
432
422
|
if (s.currentMilestoneId) {
|
|
433
423
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
434
424
|
}
|
|
435
425
|
deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone");
|
|
436
426
|
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, `Milestone ${mid} complete.`, "success");
|
|
437
|
-
await
|
|
427
|
+
await closeoutAndStop(ctx, pi, s, deps, `Milestone ${mid} complete`);
|
|
438
428
|
debugLog("autoLoop", { phase: "exit", reason: "milestone-complete" });
|
|
439
429
|
break;
|
|
440
430
|
}
|
|
441
431
|
// Terminal: blocked
|
|
442
432
|
if (state.phase === "blocked") {
|
|
443
|
-
if (s.currentUnit) {
|
|
444
|
-
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
445
|
-
}
|
|
446
433
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
447
|
-
await
|
|
434
|
+
await closeoutAndStop(ctx, pi, s, deps, blockerMsg);
|
|
448
435
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
449
436
|
deps.sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
450
437
|
deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, blockerMsg, "error");
|
|
@@ -465,48 +452,39 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
465
452
|
const newBudgetAlertLevel = deps.getNewBudgetAlertLevel(s.lastBudgetAlertLevel, budgetPct);
|
|
466
453
|
const enforcement = prefs?.budget_enforcement ?? "pause";
|
|
467
454
|
const budgetEnforcementAction = deps.getBudgetEnforcementAction(enforcement, budgetPct);
|
|
468
|
-
|
|
469
|
-
|
|
455
|
+
// Data-driven threshold check — loop descending, fire first match
|
|
456
|
+
const threshold = BUDGET_THRESHOLDS.find((t) => newBudgetAlertLevel >= t.pct);
|
|
457
|
+
if (threshold) {
|
|
470
458
|
s.lastBudgetAlertLevel =
|
|
471
459
|
newBudgetAlertLevel;
|
|
472
|
-
if (
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
460
|
+
if (threshold.pct === 100 && budgetEnforcementAction !== "none") {
|
|
461
|
+
// 100% — special enforcement logic (halt/pause/warn)
|
|
462
|
+
const msg = `Budget ceiling ${deps.formatCost(budgetCeiling)} reached (spent ${deps.formatCost(totalCost)}).`;
|
|
463
|
+
if (budgetEnforcementAction === "halt") {
|
|
464
|
+
deps.sendDesktopNotification("GSD", msg, "error", "budget");
|
|
465
|
+
await deps.stopAuto(ctx, pi, "Budget ceiling reached");
|
|
466
|
+
debugLog("autoLoop", { phase: "exit", reason: "budget-halt" });
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
if (budgetEnforcementAction === "pause") {
|
|
470
|
+
ctx.ui.notify(`${msg} Pausing auto-mode — /gsd auto to override and continue.`, "warning");
|
|
471
|
+
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
472
|
+
deps.logCmuxEvent(prefs, msg, "warning");
|
|
473
|
+
await deps.pauseAuto(ctx, pi);
|
|
474
|
+
debugLog("autoLoop", { phase: "exit", reason: "budget-pause" });
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
ctx.ui.notify(`${msg} Continuing (enforcement: warn).`, "warning");
|
|
480
478
|
deps.sendDesktopNotification("GSD", msg, "warning", "budget");
|
|
481
479
|
deps.logCmuxEvent(prefs, msg, "warning");
|
|
482
|
-
await deps.pauseAuto(ctx, pi);
|
|
483
|
-
debugLog("autoLoop", { phase: "exit", reason: "budget-pause" });
|
|
484
|
-
break;
|
|
485
480
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
ctx.ui.notify(`Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
494
|
-
deps.sendDesktopNotification("GSD", `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
|
|
495
|
-
deps.logCmuxEvent(prefs, `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
496
|
-
}
|
|
497
|
-
else if (newBudgetAlertLevel === 80) {
|
|
498
|
-
s.lastBudgetAlertLevel =
|
|
499
|
-
newBudgetAlertLevel;
|
|
500
|
-
ctx.ui.notify(`Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
501
|
-
deps.sendDesktopNotification("GSD", `Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
|
|
502
|
-
deps.logCmuxEvent(prefs, `Budget 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
|
|
503
|
-
}
|
|
504
|
-
else if (newBudgetAlertLevel === 75) {
|
|
505
|
-
s.lastBudgetAlertLevel =
|
|
506
|
-
newBudgetAlertLevel;
|
|
507
|
-
ctx.ui.notify(`Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info");
|
|
508
|
-
deps.sendDesktopNotification("GSD", `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info", "budget");
|
|
509
|
-
deps.logCmuxEvent(prefs, `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "progress");
|
|
481
|
+
else if (threshold.pct < 100) {
|
|
482
|
+
// Sub-100% — simple notification
|
|
483
|
+
const msg = `${threshold.label}: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`;
|
|
484
|
+
ctx.ui.notify(msg, threshold.notifyLevel);
|
|
485
|
+
deps.sendDesktopNotification("GSD", msg, threshold.notifyLevel, "budget");
|
|
486
|
+
deps.logCmuxEvent(prefs, msg, threshold.cmuxLevel);
|
|
487
|
+
}
|
|
510
488
|
}
|
|
511
489
|
else if (budgetAlertLevel === 0) {
|
|
512
490
|
s.lastBudgetAlertLevel = 0;
|
|
@@ -557,12 +535,10 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
557
535
|
midTitle: midTitle,
|
|
558
536
|
state,
|
|
559
537
|
prefs,
|
|
538
|
+
session: s,
|
|
560
539
|
});
|
|
561
540
|
if (dispatchResult.action === "stop") {
|
|
562
|
-
|
|
563
|
-
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
564
|
-
}
|
|
565
|
-
await deps.stopAuto(ctx, pi, dispatchResult.reason);
|
|
541
|
+
await closeoutAndStop(ctx, pi, s, deps, dispatchResult.reason);
|
|
566
542
|
debugLog("autoLoop", { phase: "exit", reason: "dispatch-stop" });
|
|
567
543
|
break;
|
|
568
544
|
}
|
|
@@ -666,8 +642,8 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
666
642
|
if (s.currentUnit) {
|
|
667
643
|
await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
668
644
|
if (s.currentUnitRouting) {
|
|
669
|
-
const
|
|
670
|
-
deps.recordOutcome(s.currentUnit.type, s.currentUnitRouting.tier, !
|
|
645
|
+
const isRetryForOutcome = s.currentUnit.type === unitType && s.currentUnit.id === unitId;
|
|
646
|
+
deps.recordOutcome(s.currentUnit.type, s.currentUnitRouting.tier, !isRetryForOutcome);
|
|
671
647
|
}
|
|
672
648
|
const closeoutKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
673
649
|
const incomingKey = `${unitType}/${unitId}`;
|
|
@@ -794,7 +770,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
794
770
|
unitType,
|
|
795
771
|
unitId,
|
|
796
772
|
});
|
|
797
|
-
const unitResult = await runUnit(ctx, pi, s, unitType, unitId, finalPrompt
|
|
773
|
+
const unitResult = await runUnit(ctx, pi, s, unitType, unitId, finalPrompt);
|
|
798
774
|
debugLog("autoLoop", {
|
|
799
775
|
phase: "runUnit-end",
|
|
800
776
|
iteration,
|
|
@@ -908,7 +884,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
908
884
|
const sidecarSessionFile = deps.getSessionFile(ctx);
|
|
909
885
|
deps.writeLock(deps.lockBase(), item.unitType, item.unitId, s.completedUnits.length, sidecarSessionFile);
|
|
910
886
|
// Execute via standard runUnit
|
|
911
|
-
const sidecarResult = await runUnit(ctx, pi, s, item.unitType, item.unitId, item.prompt
|
|
887
|
+
const sidecarResult = await runUnit(ctx, pi, s, item.unitType, item.unitId, item.prompt);
|
|
912
888
|
deps.clearUnitTimeout();
|
|
913
889
|
if (sidecarResult.status === "cancelled") {
|
|
914
890
|
ctx.ui.notify(`Sidecar unit ${item.unitType} ${item.unitId} session cancelled. Stopping.`, "warning");
|
|
@@ -916,8 +892,11 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
916
892
|
sidecarBroke = true;
|
|
917
893
|
break;
|
|
918
894
|
}
|
|
919
|
-
// Run pre-verification for the sidecar unit
|
|
920
|
-
const
|
|
895
|
+
// Run pre-verification for the sidecar unit (lightweight path)
|
|
896
|
+
const sidecarPreOpts = item.kind === "hook"
|
|
897
|
+
? { skipSettleDelay: true, skipDoctor: true, skipStateRebuild: true, skipWorktreeSync: true }
|
|
898
|
+
: { skipSettleDelay: true, skipStateRebuild: true };
|
|
899
|
+
const sidecarPreResult = await deps.postUnitPreVerification(postUnitCtx, sidecarPreOpts);
|
|
921
900
|
if (sidecarPreResult === "dispatched") {
|
|
922
901
|
// Pre-verification caused stop/pause
|
|
923
902
|
debugLog("autoLoop", {
|
|
@@ -990,6 +969,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
990
969
|
}
|
|
991
970
|
}
|
|
992
971
|
}
|
|
993
|
-
|
|
972
|
+
_currentResolve = null;
|
|
994
973
|
debugLog("autoLoop", { phase: "exit", totalIterations: iteration });
|
|
995
974
|
}
|