gsd-pi 2.74.0-dev.2b524c3 → 2.74.0-dev.b741afb
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/cli.js +85 -0
- package/dist/headless-query.js +4 -1
- package/dist/help-text.js +23 -0
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +11 -4
- package/dist/resources/extensions/gsd/auto/phases.js +45 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +52 -56
- package/dist/resources/extensions/gsd/auto-prompts.js +12 -0
- package/dist/resources/extensions/gsd/auto.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +21 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +26 -1
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +20 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +68 -9
- package/dist/resources/extensions/gsd/commands-add-tests.js +111 -0
- package/dist/resources/extensions/gsd/commands-backlog.js +140 -0
- package/dist/resources/extensions/gsd/commands-do.js +79 -0
- package/dist/resources/extensions/gsd/commands-maintenance.js +6 -6
- package/dist/resources/extensions/gsd/commands-pr-branch.js +180 -0
- package/dist/resources/extensions/gsd/commands-session-report.js +82 -0
- package/dist/resources/extensions/gsd/commands-ship.js +187 -0
- package/dist/resources/extensions/gsd/db-writer.js +3 -5
- package/dist/resources/extensions/gsd/graph-context.js +66 -0
- package/dist/resources/extensions/gsd/gsd-db.js +321 -0
- package/dist/resources/extensions/gsd/index.js +15 -2
- package/dist/resources/extensions/gsd/md-importer.js +3 -4
- package/dist/resources/extensions/gsd/memory-store.js +19 -51
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +13 -12
- package/dist/resources/extensions/gsd/native-git-bridge.js +7 -4
- package/dist/resources/extensions/gsd/prompts/add-tests.md +35 -0
- package/dist/resources/extensions/gsd/state.js +5 -1
- package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +3 -14
- package/dist/resources/extensions/gsd/triage-resolution.js +2 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +8 -69
- package/dist/resources/extensions/gsd/workflow-migration.js +21 -22
- package/dist/resources/extensions/gsd/workflow-projections.js +4 -1
- package/dist/resources/extensions/gsd/workflow-reconcile.js +14 -11
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -2
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/dist/index.d.ts +3 -0
- package/packages/mcp-server/dist/index.d.ts.map +1 -1
- package/packages/mcp-server/dist/index.js +3 -0
- package/packages/mcp-server/dist/index.js.map +1 -1
- package/packages/mcp-server/dist/readers/graph.d.ts +87 -0
- package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/graph.js +548 -0
- package/packages/mcp-server/dist/readers/graph.js.map +1 -0
- package/packages/mcp-server/dist/readers/index.d.ts +2 -0
- package/packages/mcp-server/dist/readers/index.d.ts.map +1 -1
- package/packages/mcp-server/dist/readers/index.js +1 -0
- package/packages/mcp-server/dist/readers/index.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +65 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/index.ts +15 -0
- package/packages/mcp-server/src/readers/graph.test.ts +426 -0
- package/packages/mcp-server/src/readers/graph.ts +708 -0
- package/packages/mcp-server/src/readers/index.ts +12 -0
- package/packages/mcp-server/src/server.ts +83 -0
- package/packages/mcp-server/tsconfig.json +1 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -0
- package/packages/native/package.json +2 -2
- package/packages/native/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.json +1 -0
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/tsconfig.json +1 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-coding-agent/tsconfig.json +1 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/tsconfig.json +1 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -0
- package/packages/rpc-client/package.json +1 -1
- package/packages/rpc-client/tsconfig.json +1 -0
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -0
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +12 -4
- package/src/resources/extensions/gsd/auto/loop-deps.ts +6 -0
- package/src/resources/extensions/gsd/auto/phases.ts +68 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +60 -57
- package/src/resources/extensions/gsd/auto-prompts.ts +13 -0
- package/src/resources/extensions/gsd/auto.ts +7 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +26 -1
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +20 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +74 -9
- package/src/resources/extensions/gsd/commands-add-tests.ts +137 -0
- package/src/resources/extensions/gsd/commands-backlog.ts +182 -0
- package/src/resources/extensions/gsd/commands-do.ts +109 -0
- package/src/resources/extensions/gsd/commands-maintenance.ts +6 -6
- package/src/resources/extensions/gsd/commands-pr-branch.ts +234 -0
- package/src/resources/extensions/gsd/commands-session-report.ts +101 -0
- package/src/resources/extensions/gsd/commands-ship.ts +219 -0
- package/src/resources/extensions/gsd/db-writer.ts +3 -5
- package/src/resources/extensions/gsd/graph-context.ts +85 -0
- package/src/resources/extensions/gsd/gsd-db.ts +467 -0
- package/src/resources/extensions/gsd/index.ts +18 -2
- package/src/resources/extensions/gsd/md-importer.ts +3 -5
- package/src/resources/extensions/gsd/memory-store.ts +31 -62
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +13 -14
- package/src/resources/extensions/gsd/native-git-bridge.ts +11 -12
- package/src/resources/extensions/gsd/prompts/add-tests.md +35 -0
- package/src/resources/extensions/gsd/state.ts +9 -2
- package/src/resources/extensions/gsd/tests/commands-backlog.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/commands-do.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/commands-pr-branch.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/commands-session-report.test.ts +82 -0
- package/src/resources/extensions/gsd/tests/commands-ship.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +154 -0
- package/src/resources/extensions/gsd/tests/graph-context.test.ts +337 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +68 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +223 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +19 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +3 -11
- package/src/resources/extensions/gsd/triage-resolution.ts +2 -7
- package/src/resources/extensions/gsd/workflow-manifest.ts +9 -104
- package/src/resources/extensions/gsd/workflow-migration.ts +21 -29
- package/src/resources/extensions/gsd/workflow-projections.ts +8 -1
- package/src/resources/extensions/gsd/workflow-reconcile.ts +15 -15
- /package/dist/web/standalone/.next/static/{YzIEI9sxJy4t5xgClF08g → XnHY5eXUsTCFmNodWHetD}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{YzIEI9sxJy4t5xgClF08g → XnHY5eXUsTCFmNodWHetD}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -141,6 +141,91 @@ if (cliFlags.messages[0] === 'update') {
|
|
|
141
141
|
await runUpdate();
|
|
142
142
|
process.exit(0);
|
|
143
143
|
}
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Graph subcommand — `gsd graph build|status|query|diff`
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
if (cliFlags.messages[0] === 'graph') {
|
|
148
|
+
const sub = cliFlags.messages[1];
|
|
149
|
+
const { buildGraph, writeGraph, graphStatus, graphQuery, graphDiff, resolveGsdRoot } = await import('@gsd-build/mcp-server');
|
|
150
|
+
const projectDir = process.cwd();
|
|
151
|
+
const gsdRoot = resolveGsdRoot(projectDir);
|
|
152
|
+
if (!sub || sub === 'build') {
|
|
153
|
+
try {
|
|
154
|
+
const graph = await buildGraph(projectDir);
|
|
155
|
+
await writeGraph(gsdRoot, graph);
|
|
156
|
+
process.stdout.write(`Graph built: ${graph.nodes.length} nodes, ${graph.edges.length} edges\n`);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
process.stderr.write(`[gsd] graph build failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (sub === 'status') {
|
|
164
|
+
try {
|
|
165
|
+
const result = await graphStatus(projectDir);
|
|
166
|
+
if (!result.exists) {
|
|
167
|
+
process.stdout.write('Graph: not built yet. Run: gsd graph build\n');
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
process.stdout.write(`Graph status:\n`);
|
|
171
|
+
process.stdout.write(` exists: ${result.exists}\n`);
|
|
172
|
+
process.stdout.write(` nodes: ${result.nodeCount}\n`);
|
|
173
|
+
process.stdout.write(` edges: ${result.edgeCount}\n`);
|
|
174
|
+
process.stdout.write(` stale: ${result.stale}\n`);
|
|
175
|
+
process.stdout.write(` ageHours: ${result.ageHours !== undefined ? result.ageHours.toFixed(2) : 'n/a'}\n`);
|
|
176
|
+
process.stdout.write(` lastBuild: ${result.lastBuild ?? 'n/a'}\n`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
process.stderr.write(`[gsd] graph status failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (sub === 'query') {
|
|
185
|
+
const term = cliFlags.messages[2];
|
|
186
|
+
if (!term) {
|
|
187
|
+
process.stderr.write('Usage: gsd graph query <term>\n');
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const result = await graphQuery(projectDir, term);
|
|
192
|
+
if (result.nodes.length === 0) {
|
|
193
|
+
process.stdout.write(`No nodes found for term: "${term}"\n`);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
process.stdout.write(`Query results for "${term}" (${result.nodes.length} nodes, ${result.edges.length} edges):\n`);
|
|
197
|
+
for (const node of result.nodes) {
|
|
198
|
+
process.stdout.write(` [${node.type}] ${node.label} (${node.confidence})\n`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
process.stderr.write(`[gsd] graph query failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (sub === 'diff') {
|
|
208
|
+
try {
|
|
209
|
+
const result = await graphDiff(projectDir);
|
|
210
|
+
process.stdout.write(`Graph diff:\n`);
|
|
211
|
+
process.stdout.write(` nodes added: ${result.nodes.added.length}\n`);
|
|
212
|
+
process.stdout.write(` nodes removed: ${result.nodes.removed.length}\n`);
|
|
213
|
+
process.stdout.write(` nodes changed: ${result.nodes.changed.length}\n`);
|
|
214
|
+
process.stdout.write(` edges added: ${result.edges.added.length}\n`);
|
|
215
|
+
process.stdout.write(` edges removed: ${result.edges.removed.length}\n`);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
process.stderr.write(`[gsd] graph diff failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
process.stderr.write(`Unknown graph command: ${sub}\n`);
|
|
224
|
+
process.stderr.write('Commands: build, status, query <term>, diff\n');
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
process.exit(0);
|
|
228
|
+
}
|
|
144
229
|
exitIfManagedResourcesAreNewer(agentDir);
|
|
145
230
|
// Early TTY check — must come before heavy initialization to avoid dangling
|
|
146
231
|
// handles that prevent process.exit() from completing promptly.
|
package/dist/headless-query.js
CHANGED
|
@@ -33,7 +33,9 @@ async function loadExtensionModules() {
|
|
|
33
33
|
const dispatchModule = await jiti.import(gsdExtensionPath('auto-dispatch.ts'), {});
|
|
34
34
|
const sessionModule = await jiti.import(gsdExtensionPath('session-status-io.ts'), {});
|
|
35
35
|
const prefsModule = await jiti.import(gsdExtensionPath('preferences.ts'), {});
|
|
36
|
+
const autoStartModule = await jiti.import(gsdExtensionPath('auto-start.ts'), {});
|
|
36
37
|
return {
|
|
38
|
+
openProjectDbIfPresent: autoStartModule.openProjectDbIfPresent,
|
|
37
39
|
deriveState: stateModule.deriveState,
|
|
38
40
|
resolveDispatch: dispatchModule.resolveDispatch,
|
|
39
41
|
readAllSessionStatuses: sessionModule.readAllSessionStatuses,
|
|
@@ -42,7 +44,8 @@ async function loadExtensionModules() {
|
|
|
42
44
|
}
|
|
43
45
|
// ─── Implementation ─────────────────────────────────────────────────────────
|
|
44
46
|
export async function handleQuery(basePath) {
|
|
45
|
-
const { deriveState, resolveDispatch, readAllSessionStatuses, loadEffectiveGSDPreferences } = await loadExtensionModules();
|
|
47
|
+
const { openProjectDbIfPresent, deriveState, resolveDispatch, readAllSessionStatuses, loadEffectiveGSDPreferences, } = await loadExtensionModules();
|
|
48
|
+
await openProjectDbIfPresent(basePath);
|
|
46
49
|
const state = await deriveState(basePath);
|
|
47
50
|
// Derive next dispatch action
|
|
48
51
|
let next;
|
package/dist/help-text.js
CHANGED
|
@@ -84,6 +84,28 @@ const SUBCOMMAND_HELP = {
|
|
|
84
84
|
' gsd worktree remove old-branch Remove a specific worktree',
|
|
85
85
|
' gsd worktree remove old-branch --force Remove even with unmerged changes',
|
|
86
86
|
].join('\n'),
|
|
87
|
+
graph: [
|
|
88
|
+
'Usage: gsd graph <subcommand> [options]',
|
|
89
|
+
'',
|
|
90
|
+
'Manage the GSD project knowledge graph. Reads .gsd/ artifacts and builds',
|
|
91
|
+
'a queryable graph of milestones, slices, tasks, rules, patterns, and lessons.',
|
|
92
|
+
'',
|
|
93
|
+
'Subcommands:',
|
|
94
|
+
' build Parse .gsd/ artifacts (STATE.md, milestone ROADMAPs, slice PLANs,',
|
|
95
|
+
' KNOWLEDGE.md) and write .gsd/graphs/graph.json atomically.',
|
|
96
|
+
' query Search graph nodes by term (BFS from seed matches, budget-trimmed).',
|
|
97
|
+
' Returns matching nodes and reachable edges within the token budget.',
|
|
98
|
+
' status Show whether graph.json exists, its age, node/edge counts, and',
|
|
99
|
+
' whether it is stale (built more than 24 hours ago).',
|
|
100
|
+
' diff Compare current graph.json with .last-build-snapshot.json.',
|
|
101
|
+
' Returns added, removed, and changed nodes and edges.',
|
|
102
|
+
'',
|
|
103
|
+
'Examples:',
|
|
104
|
+
' gsd graph build Build the graph from .gsd/ artifacts',
|
|
105
|
+
' gsd graph status Check graph age and node/edge counts',
|
|
106
|
+
' gsd graph query auth Find nodes related to "auth"',
|
|
107
|
+
' gsd graph diff Show changes since last snapshot',
|
|
108
|
+
].join('\n'),
|
|
87
109
|
headless: [
|
|
88
110
|
'Usage: gsd headless [flags] [command] [args...]',
|
|
89
111
|
'',
|
|
@@ -165,6 +187,7 @@ export function printHelp(version) {
|
|
|
165
187
|
process.stdout.write(' worktree <cmd> Manage worktrees (list, merge, clean, remove)\n');
|
|
166
188
|
process.stdout.write(' auto [args] Run auto-mode without TUI (pipeable)\n');
|
|
167
189
|
process.stdout.write(' headless [cmd] [args] Run /gsd commands without TUI (default: auto)\n');
|
|
190
|
+
process.stdout.write(' graph <subcommand> Manage knowledge graph (build, query, status, diff)\n');
|
|
168
191
|
process.stdout.write('\nRun gsd <subcommand> --help for subcommand-specific help.\n');
|
|
169
192
|
}
|
|
170
193
|
export function printSubcommandHelp(subcommand, version) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Leaf node in the import DAG.
|
|
5
5
|
*/
|
|
6
|
+
import { summarizeLogs } from "../workflow-logger.js";
|
|
6
7
|
/**
|
|
7
8
|
* Pattern matching ENOENT errors with a file path.
|
|
8
9
|
* Matches: "ENOENT: no such file or directory, access '/path/to/file'"
|
|
@@ -22,13 +23,19 @@ const ENOENT_PATH_RE = /ENOENT[^']*'([^']+)'/;
|
|
|
22
23
|
export function detectStuck(window) {
|
|
23
24
|
if (window.length < 2)
|
|
24
25
|
return null;
|
|
26
|
+
// Peek (not drain) the workflow-logger buffer so stuck reasons can surface
|
|
27
|
+
// the underlying diagnostic context (projection failures, DB degradations,
|
|
28
|
+
// reconcile warnings) that usually explains *why* the loop is stuck. The
|
|
29
|
+
// auto-loop's finalize step owns the buffer lifecycle — this is read-only.
|
|
30
|
+
const loggerSummary = summarizeLogs();
|
|
31
|
+
const suffix = loggerSummary ? ` — ${loggerSummary}` : "";
|
|
25
32
|
const last = window[window.length - 1];
|
|
26
33
|
const prev = window[window.length - 2];
|
|
27
34
|
// Rule 1: Same error repeated consecutively
|
|
28
35
|
if (last.error && prev.error && last.error === prev.error) {
|
|
29
36
|
return {
|
|
30
37
|
stuck: true,
|
|
31
|
-
reason: `Same error repeated: ${last.error.slice(0, 200)}`,
|
|
38
|
+
reason: `Same error repeated: ${last.error.slice(0, 200)}${suffix}`,
|
|
32
39
|
};
|
|
33
40
|
}
|
|
34
41
|
// Rule 2: Same unit 3+ consecutive times
|
|
@@ -37,7 +44,7 @@ export function detectStuck(window) {
|
|
|
37
44
|
if (lastThree.every((u) => u.key === last.key)) {
|
|
38
45
|
return {
|
|
39
46
|
stuck: true,
|
|
40
|
-
reason: `${last.key} derived 3 consecutive times without progress`,
|
|
47
|
+
reason: `${last.key} derived 3 consecutive times without progress${suffix}`,
|
|
41
48
|
};
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -49,7 +56,7 @@ export function detectStuck(window) {
|
|
|
49
56
|
w[0].key !== w[1].key) {
|
|
50
57
|
return {
|
|
51
58
|
stuck: true,
|
|
52
|
-
reason: `Oscillation detected: ${w[0].key} ↔ ${w[1].key}`,
|
|
59
|
+
reason: `Oscillation detected: ${w[0].key} ↔ ${w[1].key}${suffix}`,
|
|
53
60
|
};
|
|
54
61
|
}
|
|
55
62
|
}
|
|
@@ -67,7 +74,7 @@ export function detectStuck(window) {
|
|
|
67
74
|
if (count >= 2) {
|
|
68
75
|
return {
|
|
69
76
|
stuck: true,
|
|
70
|
-
reason: `Missing file referenced twice: ${filePath} (ENOENT)`,
|
|
77
|
+
reason: `Missing file referenced twice: ${filePath} (ENOENT)${suffix}`,
|
|
71
78
|
};
|
|
72
79
|
}
|
|
73
80
|
enoentPaths.set(filePath, count);
|
|
@@ -16,7 +16,7 @@ import { MergeConflictError } from "../git-service.js";
|
|
|
16
16
|
import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
17
17
|
import { join, basename, dirname, parse as parsePath } from "node:path";
|
|
18
18
|
import { existsSync, cpSync, readdirSync } from "node:fs";
|
|
19
|
-
import { logWarning, logError } from "../workflow-logger.js";
|
|
19
|
+
import { logWarning, logError, _resetLogs, drainLogs, drainAndSummarize, formatForNotification, hasAnyIssues, } from "../workflow-logger.js";
|
|
20
20
|
import { gsdRoot } from "../paths.js";
|
|
21
21
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
22
22
|
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
@@ -100,6 +100,22 @@ async function closeoutAndStop(ctx, pi, s, deps, reason) {
|
|
|
100
100
|
}
|
|
101
101
|
await deps.stopAuto(ctx, pi, reason);
|
|
102
102
|
}
|
|
103
|
+
async function emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, errorContext) {
|
|
104
|
+
ic.deps.emitJournalEvent({
|
|
105
|
+
ts: new Date().toISOString(),
|
|
106
|
+
flowId: ic.flowId,
|
|
107
|
+
seq: ic.nextSeq(),
|
|
108
|
+
eventType: "unit-end",
|
|
109
|
+
data: {
|
|
110
|
+
unitType,
|
|
111
|
+
unitId,
|
|
112
|
+
status: "cancelled",
|
|
113
|
+
artifactVerified: false,
|
|
114
|
+
...(errorContext ? { errorContext } : {}),
|
|
115
|
+
},
|
|
116
|
+
causedBy: { flowId: ic.flowId, seq: unitStartSeq },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
103
119
|
// ─── runPreDispatch ───────────────────────────────────────────────────────────
|
|
104
120
|
/**
|
|
105
121
|
* Phase 1: Pre-dispatch — resource guard, health gate, state derivation,
|
|
@@ -776,6 +792,10 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
776
792
|
s.currentUnit.type === unitType &&
|
|
777
793
|
s.currentUnit.id === unitId);
|
|
778
794
|
const previousTier = s.currentUnitRouting?.tier;
|
|
795
|
+
// Scope workflow-logger buffer to this unit so post-finalize drains are
|
|
796
|
+
// per-unit. Without this, the module-level _buffer accumulates across every
|
|
797
|
+
// unit in the same Node process (see workflow-logger.ts module header).
|
|
798
|
+
_resetLogs();
|
|
779
799
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
780
800
|
setCurrentPhase(unitType);
|
|
781
801
|
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
@@ -975,6 +995,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
975
995
|
// Provider-error pause: pauseAuto already handled cleanup and scheduled
|
|
976
996
|
// recovery. Don't hard-stop — just break out of the loop (#2762).
|
|
977
997
|
if (unitResult.errorContext?.category === "provider") {
|
|
998
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
978
999
|
debugLog("autoLoop", { phase: "exit", reason: "provider-pause", isTransient: unitResult.errorContext.isTransient });
|
|
979
1000
|
return { action: "break", reason: "provider-pause" };
|
|
980
1001
|
}
|
|
@@ -988,9 +1009,16 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
988
1009
|
ctx.ui.notify(`Session creation timed out for ${unitType} ${unitId}. Pausing auto-mode (recoverable).`, "warning");
|
|
989
1010
|
debugLog("autoLoop", { phase: "session-timeout-pause", unitType, unitId });
|
|
990
1011
|
await deps.pauseAuto(ctx, pi);
|
|
1012
|
+
await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
|
|
1013
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
991
1014
|
return { action: "break", reason: "session-timeout" };
|
|
992
1015
|
}
|
|
993
1016
|
// All other cancelled states (structural errors, non-transient failures): hard stop
|
|
1017
|
+
if (s.currentUnit) {
|
|
1018
|
+
await deps.closeoutUnit(ctx, s.basePath, unitType, unitId, s.currentUnit.startedAt, deps.buildSnapshotOpts(unitType, unitId));
|
|
1019
|
+
}
|
|
1020
|
+
await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
|
|
1021
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
994
1022
|
ctx.ui.notify(`Session creation failed for ${unitType} ${unitId}: ${unitResult.errorContext?.message ?? "unknown"}. Stopping auto-mode.`, "warning");
|
|
995
1023
|
await deps.stopAuto(ctx, pi, `Session creation failed: ${unitResult.errorContext?.message ?? "unknown"}`);
|
|
996
1024
|
debugLog("autoLoop", { phase: "exit", reason: "session-failed" });
|
|
@@ -1124,6 +1152,9 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1124
1152
|
// cannot mutate state for the next unit (#3757).
|
|
1125
1153
|
s.currentUnit = null;
|
|
1126
1154
|
clearCurrentPhase();
|
|
1155
|
+
// Drop any logger entries from the timed-out unit so they don't bleed
|
|
1156
|
+
// into the next iteration's drain.
|
|
1157
|
+
drainLogs();
|
|
1127
1158
|
loopState.consecutiveFinalizeTimeouts++;
|
|
1128
1159
|
debugLog("autoLoop", {
|
|
1129
1160
|
phase: "pre-verification-timeout",
|
|
@@ -1199,6 +1230,9 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1199
1230
|
// cannot mutate state for the next unit (#3757).
|
|
1200
1231
|
s.currentUnit = null;
|
|
1201
1232
|
clearCurrentPhase();
|
|
1233
|
+
// Drop any logger entries from the timed-out unit so they don't bleed
|
|
1234
|
+
// into the next iteration's drain.
|
|
1235
|
+
drainLogs();
|
|
1202
1236
|
loopState.consecutiveFinalizeTimeouts++;
|
|
1203
1237
|
debugLog("autoLoop", {
|
|
1204
1238
|
phase: "post-verification-timeout",
|
|
@@ -1230,5 +1264,15 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1230
1264
|
}
|
|
1231
1265
|
// Both pre and post verification completed without timeout — reset counter
|
|
1232
1266
|
loopState.consecutiveFinalizeTimeouts = 0;
|
|
1267
|
+
// Surface accumulated workflow-logger issues for this unit to the user.
|
|
1268
|
+
// Warnings/errors logged during the unit are buffered in the logger and
|
|
1269
|
+
// drained here so the user sees a single consolidated post-unit alert.
|
|
1270
|
+
if (hasAnyIssues()) {
|
|
1271
|
+
const { logs } = drainAndSummarize();
|
|
1272
|
+
if (logs.length > 0) {
|
|
1273
|
+
const severity = logs.some((e) => e.severity === "error") ? "error" : "warning";
|
|
1274
|
+
ctx.ui.notify(formatForNotification(logs), severity);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1233
1277
|
return { action: "next", data: undefined };
|
|
1234
1278
|
}
|
|
@@ -189,6 +189,57 @@ export function buildStepCompleteMessage(nextState) {
|
|
|
189
189
|
return `Step complete. Next: ${next.label}\n`
|
|
190
190
|
+ `Run /clear, then /gsd to continue (or /gsd auto to run continuously).`;
|
|
191
191
|
}
|
|
192
|
+
export async function autoCommitUnit(basePath, unitType, unitId, ctx) {
|
|
193
|
+
try {
|
|
194
|
+
let taskContext;
|
|
195
|
+
if (unitType === "execute-task") {
|
|
196
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
197
|
+
if (mid && sid && tid) {
|
|
198
|
+
const summaryPath = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
|
|
199
|
+
if (summaryPath) {
|
|
200
|
+
try {
|
|
201
|
+
const summaryContent = await loadFile(summaryPath);
|
|
202
|
+
if (summaryContent) {
|
|
203
|
+
const summary = parseSummary(summaryContent);
|
|
204
|
+
let ghIssueNumber;
|
|
205
|
+
try {
|
|
206
|
+
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
207
|
+
ghIssueNumber = getTaskIssueNumberForCommit(basePath, mid, sid, tid) ?? undefined;
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
logWarning("engine", `GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
211
|
+
}
|
|
212
|
+
taskContext = {
|
|
213
|
+
taskId: `${sid}/${tid}`,
|
|
214
|
+
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
215
|
+
oneLiner: summary.oneLiner || undefined,
|
|
216
|
+
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
217
|
+
issueNumber: ghIssueNumber,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
_resetHasChangesCache();
|
|
228
|
+
if (LIFECYCLE_ONLY_UNITS.has(unitType)) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const commitMsg = autoCommitCurrentBranch(basePath, unitType, unitId, taskContext);
|
|
232
|
+
if (commitMsg) {
|
|
233
|
+
ctx?.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
234
|
+
}
|
|
235
|
+
return commitMsg;
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
239
|
+
ctx?.ui.notify(`Auto-commit failed: ${String(e).split("\n")[0]}`, "warning");
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
192
243
|
/**
|
|
193
244
|
* Pre-verification processing: parallel worker signal check, cache invalidation,
|
|
194
245
|
* auto-commit, doctor run, state rebuild, worktree sync, artifact verification.
|
|
@@ -224,62 +275,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
224
275
|
// Auto-commit
|
|
225
276
|
if (s.currentUnit) {
|
|
226
277
|
const unit = s.currentUnit;
|
|
227
|
-
|
|
228
|
-
let taskContext;
|
|
229
|
-
if (s.currentUnit.type === "execute-task") {
|
|
230
|
-
const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
|
|
231
|
-
if (mid && sid && tid) {
|
|
232
|
-
const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
|
|
233
|
-
if (summaryPath) {
|
|
234
|
-
try {
|
|
235
|
-
const summaryContent = await loadFile(summaryPath);
|
|
236
|
-
if (summaryContent) {
|
|
237
|
-
const summary = parseSummary(summaryContent);
|
|
238
|
-
// Look up GitHub issue number for commit linking
|
|
239
|
-
let ghIssueNumber;
|
|
240
|
-
try {
|
|
241
|
-
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
242
|
-
ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
|
|
243
|
-
}
|
|
244
|
-
catch (err) {
|
|
245
|
-
// GitHub sync not available — skip
|
|
246
|
-
logWarning("engine", `GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
247
|
-
}
|
|
248
|
-
taskContext = {
|
|
249
|
-
taskId: `${sid}/${tid}`,
|
|
250
|
-
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
251
|
-
oneLiner: summary.oneLiner || undefined,
|
|
252
|
-
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
253
|
-
issueNumber: ghIssueNumber,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
catch (e) {
|
|
258
|
-
debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// Invalidate the nativeHasChanges cache before auto-commit (#1853).
|
|
264
|
-
// The cache has a 10-second TTL and is keyed by basePath. A stale
|
|
265
|
-
// `false` result causes autoCommit to skip staging entirely, leaving
|
|
266
|
-
// code files only in the working tree where they are destroyed by
|
|
267
|
-
// `git worktree remove --force` during teardown.
|
|
268
|
-
_resetHasChangesCache();
|
|
269
|
-
// Skip auto-commit for lifecycle-only units (#2553) — they only touch
|
|
270
|
-
// `.gsd/` internal state files. Those files are picked up by the next
|
|
271
|
-
// actual task commit via smartStage().
|
|
272
|
-
if (!LIFECYCLE_ONLY_UNITS.has(s.currentUnit.type)) {
|
|
273
|
-
const commitMsg = autoCommitCurrentBranch(s.basePath, s.currentUnit.type, s.currentUnit.id, taskContext);
|
|
274
|
-
if (commitMsg) {
|
|
275
|
-
ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
catch (e) {
|
|
280
|
-
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
281
|
-
ctx.ui.notify(`Auto-commit failed: ${String(e).split("\n")[0]}`, "warning");
|
|
282
|
-
}
|
|
278
|
+
await autoCommitUnit(s.basePath, unit.type, unit.id, ctx);
|
|
283
279
|
// GitHub sync (non-blocking, opt-in)
|
|
284
280
|
await runSafely("postUnit", "github-sync", async () => {
|
|
285
281
|
const { runGitHubSync } = await import("../github-sync/sync.js");
|
|
@@ -20,6 +20,7 @@ import { assertGateCoverage, getGatesForTurn, } from "./gate-registry.js";
|
|
|
20
20
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
21
21
|
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
22
22
|
import { logWarning } from "./workflow-logger.js";
|
|
23
|
+
import { inlineGraphSubgraph } from "./graph-context.js";
|
|
23
24
|
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
24
25
|
const MAX_PREAMBLE_CHARS = 30_000;
|
|
25
26
|
function capPreamble(preamble) {
|
|
@@ -1040,6 +1041,10 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
1040
1041
|
const knowledgeInlineRS = await inlineKnowledgeScoped(base, keywords);
|
|
1041
1042
|
if (knowledgeInlineRS)
|
|
1042
1043
|
inlined.push(knowledgeInlineRS);
|
|
1044
|
+
// Knowledge graph: subgraph for this slice (graceful — skipped if no graph.json)
|
|
1045
|
+
const graphBlockRS = await inlineGraphSubgraph(base, `${sid} ${sTitle}`, { budget: 3000 });
|
|
1046
|
+
if (graphBlockRS)
|
|
1047
|
+
inlined.push(graphBlockRS);
|
|
1043
1048
|
inlined.push(inlineTemplate("research", "Research"));
|
|
1044
1049
|
const depContent = await inlineDependencySummaries(mid, sid, base);
|
|
1045
1050
|
const activeOverrides = await loadActiveOverrides(base);
|
|
@@ -1111,6 +1116,10 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
1111
1116
|
const knowledgeInlinePS = await inlineKnowledgeScoped(base, keywordsPS);
|
|
1112
1117
|
if (knowledgeInlinePS)
|
|
1113
1118
|
inlined.push(knowledgeInlinePS);
|
|
1119
|
+
// Knowledge graph: subgraph for this slice (graceful — skipped if no graph.json)
|
|
1120
|
+
const graphBlockPS = await inlineGraphSubgraph(base, `${sid} ${sTitle}`, { budget: 3000 });
|
|
1121
|
+
if (graphBlockPS)
|
|
1122
|
+
inlined.push(graphBlockPS);
|
|
1114
1123
|
inlined.push(inlineTemplate("plan", "Slice Plan"));
|
|
1115
1124
|
if (inlineLevel === "full") {
|
|
1116
1125
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
@@ -1194,12 +1203,15 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
1194
1203
|
: null;
|
|
1195
1204
|
// Only include if it has content (not a "not found" result)
|
|
1196
1205
|
const knowledgeContent = knowledgeInlineET && !knowledgeInlineET.includes("not found") ? knowledgeInlineET : null;
|
|
1206
|
+
// Knowledge graph: tight subgraph for this task (graceful — skipped if no graph.json)
|
|
1207
|
+
const graphBlockET = await inlineGraphSubgraph(base, `${tid} ${tTitle}`, { budget: 2000 });
|
|
1197
1208
|
const inlinedTemplates = inlineLevel === "minimal"
|
|
1198
1209
|
? inlineTemplate("task-summary", "Task Summary")
|
|
1199
1210
|
: [
|
|
1200
1211
|
inlineTemplate("task-summary", "Task Summary"),
|
|
1201
1212
|
inlineTemplate("decisions", "Decisions"),
|
|
1202
1213
|
...(knowledgeContent ? [knowledgeContent] : []),
|
|
1214
|
+
...(graphBlockET ? [graphBlockET] : []),
|
|
1203
1215
|
].join("\n\n---\n\n");
|
|
1204
1216
|
const taskSummaryPath = join(base, `${relSlicePath(base, mid, sid)}/tasks/${tid}-SUMMARY.md`);
|
|
1205
1217
|
const activeOverrides = await loadActiveOverrides(base);
|
|
@@ -36,7 +36,7 @@ import { captureAvailableSkills, resetSkillTelemetry, } from "./skill-telemetry.
|
|
|
36
36
|
import { getRtkSessionSavings } from "../shared/rtk-session-stats.js";
|
|
37
37
|
import { deactivateGSD } from "../shared/gsd-phase-state.js";
|
|
38
38
|
import { initMetrics, resetMetrics, getLedger, getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js";
|
|
39
|
-
import { logWarning } from "./workflow-logger.js";
|
|
39
|
+
import { setLogBasePath, logWarning } from "./workflow-logger.js";
|
|
40
40
|
import { homedir } from "node:os";
|
|
41
41
|
import { join } from "node:path";
|
|
42
42
|
import { pathToFileURL } from "node:url";
|
|
@@ -61,7 +61,7 @@ import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.j
|
|
|
61
61
|
// ── Extracted modules ──────────────────────────────────────────────────────
|
|
62
62
|
import { startUnitSupervision } from "./auto-timers.js";
|
|
63
63
|
import { runPostUnitVerification } from "./auto-verification.js";
|
|
64
|
-
import { postUnitPreVerification, postUnitPostVerification, } from "./auto-post-unit.js";
|
|
64
|
+
import { autoCommitUnit, postUnitPreVerification, postUnitPostVerification, } from "./auto-post-unit.js";
|
|
65
65
|
import { bootstrapAutoSession, openProjectDbIfPresent } from "./auto-start.js";
|
|
66
66
|
import { initHealthWidget } from "./health-widget.js";
|
|
67
67
|
import { autoLoop, resolveAgentEnd, resolveAgentEndCancelled, _resetPendingResolve, isSessionSwitchInFlight } from "./auto-loop.js";
|
|
@@ -884,6 +884,7 @@ function buildLoopDeps() {
|
|
|
884
884
|
getMainBranch,
|
|
885
885
|
// Unit closeout + runtime records
|
|
886
886
|
closeoutUnit,
|
|
887
|
+
autoCommitUnit,
|
|
887
888
|
recordOutcome,
|
|
888
889
|
writeLock,
|
|
889
890
|
captureAvailableSkills,
|
|
@@ -1081,6 +1082,11 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1081
1082
|
s.stepMode = requestedStepMode;
|
|
1082
1083
|
s.cmdCtx = ctx;
|
|
1083
1084
|
s.basePath = base;
|
|
1085
|
+
// Ensure the workflow-logger audit log is pinned to the project root
|
|
1086
|
+
// even when auto-mode is entered via a path that bypasses the
|
|
1087
|
+
// bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
|
|
1088
|
+
// (e.g. /clear resume, hot-reload).
|
|
1089
|
+
setLogBasePath(base);
|
|
1084
1090
|
s.unitDispatchCount.clear();
|
|
1085
1091
|
s.unitLifetimeDispatches.clear();
|
|
1086
1092
|
if (!getLedger())
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// GSD2 — Extension registration: wires all GSD tools, commands, and hooks into pi
|
|
2
|
-
import { registerGSDCommand } from "../commands.js";
|
|
3
2
|
import { registerExitCommand } from "../exit-command.js";
|
|
4
3
|
import { registerWorktreeCommand } from "../worktree-command.js";
|
|
5
4
|
import { registerDbTools } from "./db-tools.js";
|
|
@@ -9,6 +8,7 @@ import { registerQueryTools } from "./query-tools.js";
|
|
|
9
8
|
import { registerHooks } from "./register-hooks.js";
|
|
10
9
|
import { registerShortcuts } from "./register-shortcuts.js";
|
|
11
10
|
import { writeCrashLog } from "./crash-log.js";
|
|
11
|
+
import { logWarning } from "../workflow-logger.js";
|
|
12
12
|
export { writeCrashLog } from "./crash-log.js";
|
|
13
13
|
export function handleRecoverableExtensionProcessError(err) {
|
|
14
14
|
if (err.code === "EPIPE") {
|
|
@@ -52,7 +52,8 @@ function installEpipeGuard() {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
export function registerGsdExtension(pi) {
|
|
55
|
-
registerGSDCommand
|
|
55
|
+
// Note: registerGSDCommand is called by index.ts before this function,
|
|
56
|
+
// so we intentionally skip it here to avoid double-registration.
|
|
56
57
|
registerWorktreeCommand(pi);
|
|
57
58
|
registerExitCommand(pi);
|
|
58
59
|
installEpipeGuard();
|
|
@@ -62,10 +63,22 @@ export function registerGsdExtension(pi) {
|
|
|
62
63
|
process.exit(0);
|
|
63
64
|
},
|
|
64
65
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
// Wrap non-critical registrations individually so one failure
|
|
67
|
+
// doesn't prevent the others from loading.
|
|
68
|
+
const nonCriticalRegistrations = [
|
|
69
|
+
["dynamic-tools", () => registerDynamicTools(pi)],
|
|
70
|
+
["db-tools", () => registerDbTools(pi)],
|
|
71
|
+
["journal-tools", () => registerJournalTools(pi)],
|
|
72
|
+
["query-tools", () => registerQueryTools(pi)],
|
|
73
|
+
["shortcuts", () => registerShortcuts(pi)],
|
|
74
|
+
["hooks", () => registerHooks(pi)],
|
|
75
|
+
];
|
|
76
|
+
for (const [name, register] of nonCriticalRegistrations) {
|
|
77
|
+
try {
|
|
78
|
+
register();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logWarning("bootstrap", `Failed to register ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
71
84
|
}
|
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { loadRegistry } from "../workflow-templates.js";
|
|
5
5
|
import { resolveProjectRoot } from "../worktree.js";
|
|
6
6
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
7
|
-
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications";
|
|
7
|
+
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests";
|
|
8
8
|
export const TOP_LEVEL_SUBCOMMANDS = [
|
|
9
9
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
10
10
|
{ cmd: "next", desc: "Explicit step mode (same as /gsd)" },
|
|
@@ -62,6 +62,12 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
62
62
|
{ cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
|
|
63
63
|
{ cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
|
|
64
64
|
{ cmd: "codebase", desc: "Generate, refresh, and inspect the codebase map cache (.gsd/CODEBASE.md)" },
|
|
65
|
+
{ cmd: "ship", desc: "Create PR from milestone artifacts and open for review" },
|
|
66
|
+
{ cmd: "do", desc: "Route freeform text to the right GSD command" },
|
|
67
|
+
{ cmd: "session-report", desc: "Session cost, tokens, and work summary" },
|
|
68
|
+
{ cmd: "backlog", desc: "Manage backlog items (add, promote, remove, list)" },
|
|
69
|
+
{ cmd: "pr-branch", desc: "Create clean PR branch filtering .gsd/ commits" },
|
|
70
|
+
{ cmd: "add-tests", desc: "Generate tests for completed slices" },
|
|
65
71
|
];
|
|
66
72
|
const NESTED_COMPLETIONS = {
|
|
67
73
|
auto: [
|
|
@@ -231,6 +237,25 @@ const NESTED_COMPLETIONS = {
|
|
|
231
237
|
{ cmd: "stats", desc: "Show file count, description coverage, and generation time" },
|
|
232
238
|
{ cmd: "help", desc: "Show usage and available subcommands" },
|
|
233
239
|
],
|
|
240
|
+
ship: [
|
|
241
|
+
{ cmd: "--dry-run", desc: "Preview PR without creating" },
|
|
242
|
+
{ cmd: "--draft", desc: "Open as draft PR" },
|
|
243
|
+
{ cmd: "--base", desc: "Override target branch (default: main)" },
|
|
244
|
+
{ cmd: "--force", desc: "Ship even with pending tasks" },
|
|
245
|
+
],
|
|
246
|
+
"session-report": [
|
|
247
|
+
{ cmd: "--json", desc: "Machine-readable JSON output" },
|
|
248
|
+
{ cmd: "--save", desc: "Save report to .gsd/reports/" },
|
|
249
|
+
],
|
|
250
|
+
backlog: [
|
|
251
|
+
{ cmd: "add", desc: "Add item to backlog" },
|
|
252
|
+
{ cmd: "promote", desc: "Promote backlog item to active slice" },
|
|
253
|
+
{ cmd: "remove", desc: "Remove backlog item" },
|
|
254
|
+
],
|
|
255
|
+
"pr-branch": [
|
|
256
|
+
{ cmd: "--dry-run", desc: "Preview what would be filtered" },
|
|
257
|
+
{ cmd: "--name", desc: "Custom branch name" },
|
|
258
|
+
],
|
|
234
259
|
};
|
|
235
260
|
function filterOptions(partial, options, prefix = "") {
|
|
236
261
|
const normalizedPrefix = prefix ? `${prefix} ` : "";
|
|
@@ -8,6 +8,9 @@ import { handleExport } from "../../export.js";
|
|
|
8
8
|
import { handleHistory } from "../../history.js";
|
|
9
9
|
import { handleUndo } from "../../undo.js";
|
|
10
10
|
import { handleRemote } from "../../../remote-questions/mod.js";
|
|
11
|
+
import { handleShip } from "../../commands-ship.js";
|
|
12
|
+
import { handleSessionReport } from "../../commands-session-report.js";
|
|
13
|
+
import { handlePrBranch } from "../../commands-pr-branch.js";
|
|
11
14
|
import { projectRoot } from "../context.js";
|
|
12
15
|
export async function handleOpsCommand(trimmed, ctx, pi) {
|
|
13
16
|
if (trimmed === "init") {
|
|
@@ -213,5 +216,22 @@ Examples:
|
|
|
213
216
|
await handleCodebase(trimmed.replace(/^codebase\s*/, "").trim(), ctx, pi);
|
|
214
217
|
return true;
|
|
215
218
|
}
|
|
219
|
+
if (trimmed === "ship" || trimmed.startsWith("ship ")) {
|
|
220
|
+
await handleShip(trimmed.replace(/^ship\s*/, "").trim(), ctx, pi);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (trimmed === "session-report" || trimmed.startsWith("session-report ")) {
|
|
224
|
+
await handleSessionReport(trimmed.replace(/^session-report\s*/, "").trim(), ctx);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
if (trimmed === "pr-branch" || trimmed.startsWith("pr-branch ")) {
|
|
228
|
+
await handlePrBranch(trimmed.replace(/^pr-branch\s*/, "").trim(), ctx);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (trimmed === "add-tests" || trimmed.startsWith("add-tests ")) {
|
|
232
|
+
const { handleAddTests } = await import("../../commands-add-tests.js");
|
|
233
|
+
await handleAddTests(trimmed.replace(/^add-tests\s*/, "").trim(), ctx, pi);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
216
236
|
return false;
|
|
217
237
|
}
|