gsd-pi 2.47.0 → 2.48.0-dev.ced2eca
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/resources/extensions/gsd/auto-dispatch.js +17 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -3
- package/dist/resources/extensions/gsd/auto-start.js +8 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +5 -2
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +43 -3
- package/dist/resources/extensions/gsd/forensics.js +292 -1
- package/dist/resources/extensions/gsd/git-service.js +11 -10
- package/dist/resources/extensions/gsd/guided-flow.js +85 -3
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
- package/dist/resources/extensions/gsd/prompts/forensics.md +37 -5
- package/dist/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/dist/resources/extensions/gsd/session-forensics.js +10 -1
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/chunks/229.js +1 -1
- package/dist/web/standalone/.next/server/chunks/471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/worktree-cli.js +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +3 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +3 -2
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +43 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +26 -3
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +70 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +29 -2
- package/packages/pi-tui/dist/components/box.d.ts +1 -0
- package/packages/pi-tui/dist/components/box.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/box.js +10 -0
- package/packages/pi-tui/dist/components/box.js.map +1 -1
- package/packages/pi-tui/src/components/box.ts +10 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +8 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -3
- package/src/resources/extensions/gsd/auto-start.ts +7 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +4 -2
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +46 -3
- package/src/resources/extensions/gsd/forensics.ts +329 -2
- package/src/resources/extensions/gsd/git-service.ts +12 -11
- package/src/resources/extensions/gsd/guided-flow.ts +105 -3
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
- package/src/resources/extensions/gsd/prompts/forensics.md +37 -5
- package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/src/resources/extensions/gsd/session-forensics.ts +11 -1
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +14 -12
- package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +241 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +121 -0
- package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +19 -9
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/stale-milestone-id-reservation.test.ts +79 -0
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{VPcLnRF4BL8VoJEilBwlB → PTL5V00OW8q4-092tUQKx}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{VPcLnRF4BL8VoJEilBwlB → PTL5V00OW8q4-092tUQKx}/_ssgManifest.js +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* data structure that is inspectable, testable per-rule, and extensible
|
|
9
9
|
* without modifying orchestration code.
|
|
10
10
|
*/
|
|
11
|
-
import { loadFile, loadActiveOverrides } from "./files.js";
|
|
11
|
+
import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
|
|
12
12
|
import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
|
|
13
13
|
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, relSliceFile, buildMilestoneFileName, } from "./paths.js";
|
|
14
14
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
@@ -112,7 +112,22 @@ export const DISPATCH_RULES = [
|
|
|
112
112
|
continue;
|
|
113
113
|
const verdictMatch = content.match(/verdict:\s*([\w-]+)/i);
|
|
114
114
|
const verdict = verdictMatch?.[1]?.toLowerCase();
|
|
115
|
-
|
|
115
|
+
// Determine acceptable verdicts based on UAT type.
|
|
116
|
+
// mixed / human-experience / live-runtime modes may legitimately
|
|
117
|
+
// produce PARTIAL when all automatable checks pass but human-only
|
|
118
|
+
// checks remain — this should not block progression.
|
|
119
|
+
const acceptableVerdicts = ["pass", "passed"];
|
|
120
|
+
const uatFile = resolveSliceFile(basePath, mid, sliceId, "UAT");
|
|
121
|
+
if (uatFile) {
|
|
122
|
+
const uatContent = await loadFile(uatFile);
|
|
123
|
+
if (uatContent) {
|
|
124
|
+
const uatType = extractUatType(uatContent);
|
|
125
|
+
if (uatType === "mixed" || uatType === "human-experience" || uatType === "live-runtime") {
|
|
126
|
+
acceptableVerdicts.push("partial");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (verdict && !acceptableVerdicts.includes(verdict)) {
|
|
116
131
|
return {
|
|
117
132
|
action: "stop",
|
|
118
133
|
reason: `UAT verdict for ${sliceId} is "${verdict}" — blocking progression until resolved.\nReview the UAT result and update the verdict to PASS, or re-run /gsd auto after fixing.`,
|
|
@@ -26,6 +26,15 @@ import { consumeSignal } from "./session-status-io.js";
|
|
|
26
26
|
import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
|
|
27
27
|
import { hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
28
28
|
import { debugLog } from "./debug-logger.js";
|
|
29
|
+
/** Unit types that only touch `.gsd/` internal state files (no code changes).
|
|
30
|
+
* Auto-commit is skipped for these — their state files are picked up by the
|
|
31
|
+
* next actual task commit via `smartStage()`. */
|
|
32
|
+
const LIFECYCLE_ONLY_UNITS = new Set([
|
|
33
|
+
"research-milestone", "discuss-milestone", "plan-milestone",
|
|
34
|
+
"validate-milestone", "research-slice", "plan-slice",
|
|
35
|
+
"replan-slice", "complete-slice", "run-uat",
|
|
36
|
+
"reassess-roadmap", "rewrite-docs",
|
|
37
|
+
]);
|
|
29
38
|
import { existsSync, unlinkSync } from "node:fs";
|
|
30
39
|
import { join } from "node:path";
|
|
31
40
|
import { _resetHasChangesCache } from "./native-git-bridge.js";
|
|
@@ -210,9 +219,14 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
210
219
|
// code files only in the working tree where they are destroyed by
|
|
211
220
|
// `git worktree remove --force` during teardown.
|
|
212
221
|
_resetHasChangesCache();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
// Skip auto-commit for lifecycle-only units (#2553) — they only touch
|
|
223
|
+
// `.gsd/` internal state files. Those files are picked up by the next
|
|
224
|
+
// actual task commit via smartStage().
|
|
225
|
+
if (!LIFECYCLE_ONLY_UNITS.has(s.currentUnit.type)) {
|
|
226
|
+
const commitMsg = autoCommitCurrentBranch(s.basePath, s.currentUnit.type, s.currentUnit.id, taskContext);
|
|
227
|
+
if (commitMsg) {
|
|
228
|
+
ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
229
|
+
}
|
|
216
230
|
}
|
|
217
231
|
}
|
|
218
232
|
catch (e) {
|
|
@@ -30,7 +30,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
30
30
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
31
31
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
32
32
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
33
|
-
import { isDbAvailable } from "./gsd-db.js";
|
|
33
|
+
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
34
34
|
import { hideFooter } from "./auto-dashboard.js";
|
|
35
35
|
import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
|
|
36
36
|
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, } from "node:fs";
|
|
@@ -503,6 +503,13 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
503
503
|
if (milestoneIds.length > 1) {
|
|
504
504
|
const issues = [];
|
|
505
505
|
for (const id of milestoneIds) {
|
|
506
|
+
// Skip completed/parked milestones — a leftover CONTEXT-DRAFT.md
|
|
507
|
+
// on a finished milestone is harmless residue, not an actionable warning.
|
|
508
|
+
if (isDbAvailable()) {
|
|
509
|
+
const ms = getMilestone(id);
|
|
510
|
+
if (ms?.status === "complete" || ms?.status === "parked")
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
506
513
|
const draft = resolveMilestoneFile(base, id, "CONTEXT-DRAFT");
|
|
507
514
|
if (draft)
|
|
508
515
|
issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`);
|
|
@@ -910,13 +910,16 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
910
910
|
milestoneTitle = titleMatch[1].trim();
|
|
911
911
|
}
|
|
912
912
|
milestoneTitle = milestoneTitle || milestoneId;
|
|
913
|
-
const subject = `feat
|
|
913
|
+
const subject = `feat: ${milestoneTitle}`;
|
|
914
914
|
let body = "";
|
|
915
915
|
if (completedSlices.length > 0) {
|
|
916
916
|
const sliceLines = completedSlices
|
|
917
917
|
.map((s) => `- ${s.id}: ${s.title}`)
|
|
918
918
|
.join("\n");
|
|
919
|
-
body = `\n\nCompleted slices:\n${sliceLines}\n\nBranch: ${milestoneBranch}`;
|
|
919
|
+
body = `\n\nCompleted slices:\n${sliceLines}\n\nGSD-Milestone: ${milestoneId}\nBranch: ${milestoneBranch}`;
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
body = `\n\nGSD-Milestone: ${milestoneId}\nBranch: ${milestoneBranch}`;
|
|
920
923
|
}
|
|
921
924
|
const commitMessage = subject + body;
|
|
922
925
|
// 6b. Reconcile worktree HEAD with milestone branch ref (#1846).
|
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
1
3
|
import { enableDebug } from "../../debug-logger.js";
|
|
2
4
|
import { isAutoActive, isAutoPaused, pauseAuto, startAuto, stopAuto, stopAutoRemote } from "../../auto.js";
|
|
3
5
|
import { handleRate } from "../../commands-rate.js";
|
|
4
6
|
import { guardRemoteSession, projectRoot } from "../context.js";
|
|
7
|
+
/**
|
|
8
|
+
* Parse --yolo flag and optional file path from the auto command string.
|
|
9
|
+
* Supports: `/gsd auto --yolo path/to/file.md` or `/gsd auto -y path/to/file.md`
|
|
10
|
+
*/
|
|
11
|
+
function parseYoloFlag(trimmed) {
|
|
12
|
+
const yoloRe = /(?:--yolo|-y)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)/;
|
|
13
|
+
const match = trimmed.match(yoloRe);
|
|
14
|
+
if (!match)
|
|
15
|
+
return { yoloSeedFile: null, rest: trimmed };
|
|
16
|
+
// Strip quotes if present
|
|
17
|
+
let filePath = match[1];
|
|
18
|
+
if ((filePath.startsWith('"') && filePath.endsWith('"')) ||
|
|
19
|
+
(filePath.startsWith("'") && filePath.endsWith("'"))) {
|
|
20
|
+
filePath = filePath.slice(1, -1);
|
|
21
|
+
}
|
|
22
|
+
const rest = trimmed.replace(match[0], "").replace(/\s+/g, " ").trim();
|
|
23
|
+
return { yoloSeedFile: filePath, rest };
|
|
24
|
+
}
|
|
5
25
|
export async function handleAutoCommand(trimmed, ctx, pi) {
|
|
6
26
|
if (trimmed === "next" || trimmed.startsWith("next ")) {
|
|
7
27
|
if (trimmed.includes("--dry-run")) {
|
|
@@ -19,13 +39,33 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
|
|
|
19
39
|
return true;
|
|
20
40
|
}
|
|
21
41
|
if (trimmed === "auto" || trimmed.startsWith("auto ")) {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
42
|
+
const { yoloSeedFile, rest } = parseYoloFlag(trimmed);
|
|
43
|
+
const verboseMode = rest.includes("--verbose");
|
|
44
|
+
const debugMode = rest.includes("--debug");
|
|
24
45
|
if (debugMode)
|
|
25
46
|
enableDebug(projectRoot());
|
|
26
47
|
if (!(await guardRemoteSession(ctx, pi)))
|
|
27
48
|
return true;
|
|
28
|
-
|
|
49
|
+
if (yoloSeedFile) {
|
|
50
|
+
const resolved = resolve(projectRoot(), yoloSeedFile);
|
|
51
|
+
if (!existsSync(resolved)) {
|
|
52
|
+
ctx.ui.notify(`Yolo seed file not found: ${resolved}`, "error");
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
const seedContent = readFileSync(resolved, "utf-8").trim();
|
|
56
|
+
if (!seedContent) {
|
|
57
|
+
ctx.ui.notify(`Yolo seed file is empty: ${resolved}`, "error");
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// Headless path: bootstrap project, dispatch non-interactive discuss,
|
|
61
|
+
// then auto-mode starts automatically via checkAutoStartAfterDiscuss
|
|
62
|
+
// when the LLM says "Milestone X ready."
|
|
63
|
+
const { showHeadlessMilestoneCreation } = await import("../../guided-flow.js");
|
|
64
|
+
await showHeadlessMilestoneCreation(ctx, pi, projectRoot(), seedContent);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
68
|
+
}
|
|
29
69
|
return true;
|
|
30
70
|
}
|
|
31
71
|
if (trimmed === "stop") {
|
|
@@ -197,7 +197,11 @@ export async function buildForensicReport(basePath) {
|
|
|
197
197
|
// Extensions run from ~/.gsd/agent/extensions/gsd/ at runtime, so path-traversal
|
|
198
198
|
// from import.meta.url would resolve to ~/package.json (wrong on every system).
|
|
199
199
|
const gsdVersion = process.env.GSD_VERSION || "unknown";
|
|
200
|
-
// 9.
|
|
200
|
+
// 9. Scan journal for flow timeline and structured events
|
|
201
|
+
const journalSummary = scanJournalForForensics(basePath);
|
|
202
|
+
// 10. Gather activity log directory metadata
|
|
203
|
+
const activityLogMeta = gatherActivityLogMeta(basePath, activeMilestone);
|
|
204
|
+
// 11. Run anomaly detectors
|
|
201
205
|
if (metrics?.units)
|
|
202
206
|
detectStuckLoops(metrics.units, anomalies);
|
|
203
207
|
if (metrics?.units)
|
|
@@ -207,6 +211,7 @@ export async function buildForensicReport(basePath) {
|
|
|
207
211
|
detectCrash(crashLock, anomalies);
|
|
208
212
|
detectDoctorIssues(doctorIssues, anomalies);
|
|
209
213
|
detectErrorTraces(unitTraces, anomalies);
|
|
214
|
+
detectJournalAnomalies(journalSummary, anomalies);
|
|
210
215
|
return {
|
|
211
216
|
gsdVersion,
|
|
212
217
|
timestamp: new Date().toISOString(),
|
|
@@ -221,10 +226,14 @@ export async function buildForensicReport(basePath) {
|
|
|
221
226
|
doctorIssues,
|
|
222
227
|
anomalies,
|
|
223
228
|
recentUnits,
|
|
229
|
+
journalSummary,
|
|
230
|
+
activityLogMeta,
|
|
224
231
|
};
|
|
225
232
|
}
|
|
226
233
|
// ─── Activity Log Scanner ─────────────────────────────────────────────────────
|
|
227
234
|
const ACTIVITY_FILENAME_RE = /^(\d+)-(.+?)-(.+)\.jsonl$/;
|
|
235
|
+
/** Threshold below which iteration cadence is considered rapid (thrashing). */
|
|
236
|
+
const RAPID_ITERATION_THRESHOLD_MS = 5000;
|
|
228
237
|
function scanActivityLogs(basePath, activeMilestone) {
|
|
229
238
|
const activityDirs = resolveActivityDirs(basePath, activeMilestone);
|
|
230
239
|
const allTraces = [];
|
|
@@ -292,6 +301,152 @@ function resolveActivityDirs(basePath, activeMilestone) {
|
|
|
292
301
|
dirs.push(rootActivityDir);
|
|
293
302
|
return dirs;
|
|
294
303
|
}
|
|
304
|
+
// ─── Journal Scanner ──────────────────────────────────────────────────────────
|
|
305
|
+
/**
|
|
306
|
+
* Max recent journal files to fully parse for event counts and recent events.
|
|
307
|
+
* Older files are line-counted only to avoid loading huge amounts of data.
|
|
308
|
+
*/
|
|
309
|
+
const MAX_JOURNAL_RECENT_FILES = 3;
|
|
310
|
+
/** Max recent events to extract for the forensic report timeline. */
|
|
311
|
+
const MAX_JOURNAL_RECENT_EVENTS = 20;
|
|
312
|
+
/**
|
|
313
|
+
* Intelligently scan journal files for forensic summary.
|
|
314
|
+
*
|
|
315
|
+
* Journal files can be huge (thousands of JSONL entries over weeks of auto-mode).
|
|
316
|
+
* Instead of loading all entries into memory:
|
|
317
|
+
* - Only fully parse the most recent N daily files (event counts, flow tracking)
|
|
318
|
+
* - Line-count older files for approximate totals (no JSON parsing)
|
|
319
|
+
* - Extract only the last 20 events for the timeline
|
|
320
|
+
*/
|
|
321
|
+
function scanJournalForForensics(basePath) {
|
|
322
|
+
try {
|
|
323
|
+
const journalDir = join(gsdRoot(basePath), "journal");
|
|
324
|
+
if (!existsSync(journalDir))
|
|
325
|
+
return null;
|
|
326
|
+
const files = readdirSync(journalDir).filter(f => f.endsWith(".jsonl")).sort();
|
|
327
|
+
if (files.length === 0)
|
|
328
|
+
return null;
|
|
329
|
+
// Split into recent (fully parsed) and older (line-counted only)
|
|
330
|
+
const recentFiles = files.slice(-MAX_JOURNAL_RECENT_FILES);
|
|
331
|
+
const olderFiles = files.slice(0, -MAX_JOURNAL_RECENT_FILES);
|
|
332
|
+
// Line-count older files without parsing — avoids loading megabytes of JSON
|
|
333
|
+
let olderEntryCount = 0;
|
|
334
|
+
let oldestEntry = null;
|
|
335
|
+
for (const file of olderFiles) {
|
|
336
|
+
try {
|
|
337
|
+
const raw = readFileSync(join(journalDir, file), "utf-8");
|
|
338
|
+
const lines = raw.split("\n");
|
|
339
|
+
for (const line of lines) {
|
|
340
|
+
if (!line.trim())
|
|
341
|
+
continue;
|
|
342
|
+
olderEntryCount++;
|
|
343
|
+
// Extract only the timestamp from the first non-empty line of the oldest file
|
|
344
|
+
if (!oldestEntry) {
|
|
345
|
+
try {
|
|
346
|
+
const parsed = JSON.parse(line);
|
|
347
|
+
if (parsed.ts)
|
|
348
|
+
oldestEntry = parsed.ts;
|
|
349
|
+
}
|
|
350
|
+
catch { /* skip malformed */ }
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch { /* skip unreadable files */ }
|
|
355
|
+
}
|
|
356
|
+
// Fully parse recent files for event counts and timeline
|
|
357
|
+
const eventCounts = {};
|
|
358
|
+
const flowIds = new Set();
|
|
359
|
+
const recentParsedEntries = [];
|
|
360
|
+
let recentEntryCount = 0;
|
|
361
|
+
for (const file of recentFiles) {
|
|
362
|
+
try {
|
|
363
|
+
const raw = readFileSync(join(journalDir, file), "utf-8");
|
|
364
|
+
for (const line of raw.split("\n")) {
|
|
365
|
+
if (!line.trim())
|
|
366
|
+
continue;
|
|
367
|
+
try {
|
|
368
|
+
const entry = JSON.parse(line);
|
|
369
|
+
recentEntryCount++;
|
|
370
|
+
eventCounts[entry.eventType] = (eventCounts[entry.eventType] ?? 0) + 1;
|
|
371
|
+
flowIds.add(entry.flowId);
|
|
372
|
+
if (!oldestEntry)
|
|
373
|
+
oldestEntry = entry.ts;
|
|
374
|
+
// Keep a rolling window of last N events — avoids accumulating unbounded arrays
|
|
375
|
+
recentParsedEntries.push({
|
|
376
|
+
ts: entry.ts,
|
|
377
|
+
flowId: entry.flowId,
|
|
378
|
+
eventType: entry.eventType,
|
|
379
|
+
rule: entry.rule,
|
|
380
|
+
unitId: entry.data?.unitId,
|
|
381
|
+
});
|
|
382
|
+
if (recentParsedEntries.length > MAX_JOURNAL_RECENT_EVENTS) {
|
|
383
|
+
recentParsedEntries.shift();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch { /* skip malformed lines */ }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch { /* skip unreadable files */ }
|
|
390
|
+
}
|
|
391
|
+
const totalEntries = olderEntryCount + recentEntryCount;
|
|
392
|
+
if (totalEntries === 0)
|
|
393
|
+
return null;
|
|
394
|
+
const newestEntry = recentParsedEntries.length > 0
|
|
395
|
+
? recentParsedEntries[recentParsedEntries.length - 1].ts
|
|
396
|
+
: null;
|
|
397
|
+
return {
|
|
398
|
+
totalEntries,
|
|
399
|
+
flowCount: flowIds.size,
|
|
400
|
+
eventCounts,
|
|
401
|
+
recentEvents: recentParsedEntries,
|
|
402
|
+
oldestEntry,
|
|
403
|
+
newestEntry,
|
|
404
|
+
fileCount: files.length,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// ─── Activity Log Metadata ────────────────────────────────────────────────────
|
|
412
|
+
function gatherActivityLogMeta(basePath, activeMilestone) {
|
|
413
|
+
try {
|
|
414
|
+
const activityDirs = resolveActivityDirs(basePath, activeMilestone);
|
|
415
|
+
let fileCount = 0;
|
|
416
|
+
let totalSizeBytes = 0;
|
|
417
|
+
let oldestFile = null;
|
|
418
|
+
let newestFile = null;
|
|
419
|
+
let oldestMtime = Infinity;
|
|
420
|
+
let newestMtime = 0;
|
|
421
|
+
for (const activityDir of activityDirs) {
|
|
422
|
+
if (!existsSync(activityDir))
|
|
423
|
+
continue;
|
|
424
|
+
const files = readdirSync(activityDir).filter(f => f.endsWith(".jsonl"));
|
|
425
|
+
for (const file of files) {
|
|
426
|
+
const filePath = join(activityDir, file);
|
|
427
|
+
const stat = statSync(filePath, { throwIfNoEntry: false });
|
|
428
|
+
if (!stat)
|
|
429
|
+
continue;
|
|
430
|
+
fileCount++;
|
|
431
|
+
totalSizeBytes += stat.size;
|
|
432
|
+
if (stat.mtimeMs < oldestMtime) {
|
|
433
|
+
oldestMtime = stat.mtimeMs;
|
|
434
|
+
oldestFile = file;
|
|
435
|
+
}
|
|
436
|
+
if (stat.mtimeMs > newestMtime) {
|
|
437
|
+
newestMtime = stat.mtimeMs;
|
|
438
|
+
newestFile = file;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (fileCount === 0)
|
|
443
|
+
return null;
|
|
444
|
+
return { fileCount, totalSizeBytes, oldestFile, newestFile };
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
295
450
|
// ─── Completed Keys Loader ────────────────────────────────────────────────────
|
|
296
451
|
function loadCompletedKeys(basePath) {
|
|
297
452
|
const file = join(gsdRoot(basePath), "completed-units.json");
|
|
@@ -423,6 +578,64 @@ function detectErrorTraces(traces, anomalies) {
|
|
|
423
578
|
}
|
|
424
579
|
}
|
|
425
580
|
}
|
|
581
|
+
function detectJournalAnomalies(journal, anomalies) {
|
|
582
|
+
if (!journal)
|
|
583
|
+
return;
|
|
584
|
+
// Detect stuck-detected events from the journal
|
|
585
|
+
const stuckCount = journal.eventCounts["stuck-detected"] ?? 0;
|
|
586
|
+
if (stuckCount > 0) {
|
|
587
|
+
anomalies.push({
|
|
588
|
+
type: "journal-stuck",
|
|
589
|
+
severity: stuckCount >= 3 ? "error" : "warning",
|
|
590
|
+
summary: `Journal recorded ${stuckCount} stuck-detected event(s)`,
|
|
591
|
+
details: `The auto-mode loop detected it was stuck ${stuckCount} time(s). Check journal events for flow IDs and causal chains to trace the root cause.`,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
// Detect guard-block events (dispatch was blocked by a guard)
|
|
595
|
+
const guardCount = journal.eventCounts["guard-block"] ?? 0;
|
|
596
|
+
if (guardCount > 0) {
|
|
597
|
+
anomalies.push({
|
|
598
|
+
type: "journal-guard-block",
|
|
599
|
+
severity: guardCount >= 5 ? "warning" : "info",
|
|
600
|
+
summary: `Journal recorded ${guardCount} guard-block event(s)`,
|
|
601
|
+
details: `Dispatch was blocked by a guard condition ${guardCount} time(s). This may indicate a persistent blocking condition preventing progress.`,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
// Detect rapid iterations (many flows in short time = likely thrashing)
|
|
605
|
+
if (journal.flowCount > 0 && journal.oldestEntry && journal.newestEntry) {
|
|
606
|
+
const oldest = new Date(journal.oldestEntry).getTime();
|
|
607
|
+
const newest = new Date(journal.newestEntry).getTime();
|
|
608
|
+
const spanMs = newest - oldest;
|
|
609
|
+
if (spanMs > 0 && journal.flowCount > 10) {
|
|
610
|
+
const avgMs = spanMs / journal.flowCount;
|
|
611
|
+
if (avgMs < RAPID_ITERATION_THRESHOLD_MS) {
|
|
612
|
+
anomalies.push({
|
|
613
|
+
type: "journal-rapid-iterations",
|
|
614
|
+
severity: "warning",
|
|
615
|
+
summary: `${journal.flowCount} iterations in ${formatDuration(spanMs)} (avg ${formatDuration(avgMs)}/iteration)`,
|
|
616
|
+
details: `Unusually rapid iteration cadence suggests the loop may be thrashing without making progress. Review recent journal events for dispatch-stop or terminal events.`,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// Detect worktree failures from journal events
|
|
622
|
+
const wtCreateFailed = journal.eventCounts["worktree-create-failed"] ?? 0;
|
|
623
|
+
const wtMergeFailed = journal.eventCounts["worktree-merge-failed"] ?? 0;
|
|
624
|
+
const wtFailures = wtCreateFailed + wtMergeFailed;
|
|
625
|
+
if (wtFailures > 0) {
|
|
626
|
+
const parts = [];
|
|
627
|
+
if (wtCreateFailed > 0)
|
|
628
|
+
parts.push(`${wtCreateFailed} create failure(s)`);
|
|
629
|
+
if (wtMergeFailed > 0)
|
|
630
|
+
parts.push(`${wtMergeFailed} merge failure(s)`);
|
|
631
|
+
anomalies.push({
|
|
632
|
+
type: "journal-worktree-failure",
|
|
633
|
+
severity: "warning",
|
|
634
|
+
summary: `Worktree failures: ${parts.join(", ")}`,
|
|
635
|
+
details: `Journal recorded worktree operation failures. These may indicate git state corruption or conflicting branches.`,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
426
639
|
// ─── Report Persistence ───────────────────────────────────────────────────────
|
|
427
640
|
function saveForensicReport(basePath, report, problemDescription) {
|
|
428
641
|
const dir = join(gsdRoot(basePath), "forensics");
|
|
@@ -491,6 +704,48 @@ function saveForensicReport(basePath, report, problemDescription) {
|
|
|
491
704
|
sections.push(`## Crash Lock`, ``);
|
|
492
705
|
sections.push(redact(formatCrashInfo(report.crashLock)), ``);
|
|
493
706
|
}
|
|
707
|
+
// Activity log metadata
|
|
708
|
+
if (report.activityLogMeta) {
|
|
709
|
+
const meta = report.activityLogMeta;
|
|
710
|
+
sections.push(`## Activity Log Metadata`, ``);
|
|
711
|
+
sections.push(`- Files: ${meta.fileCount}`);
|
|
712
|
+
sections.push(`- Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
|
|
713
|
+
if (meta.oldestFile)
|
|
714
|
+
sections.push(`- Oldest: ${meta.oldestFile}`);
|
|
715
|
+
if (meta.newestFile)
|
|
716
|
+
sections.push(`- Newest: ${meta.newestFile}`);
|
|
717
|
+
sections.push(``);
|
|
718
|
+
}
|
|
719
|
+
// Journal summary
|
|
720
|
+
if (report.journalSummary) {
|
|
721
|
+
const js = report.journalSummary;
|
|
722
|
+
sections.push(`## Journal Summary`, ``);
|
|
723
|
+
sections.push(`- Total entries: ${js.totalEntries}`);
|
|
724
|
+
sections.push(`- Distinct flows (iterations): ${js.flowCount}`);
|
|
725
|
+
sections.push(`- Daily files: ${js.fileCount}`);
|
|
726
|
+
if (js.oldestEntry)
|
|
727
|
+
sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
|
|
728
|
+
sections.push(``);
|
|
729
|
+
sections.push(`### Event Type Distribution`, ``);
|
|
730
|
+
sections.push(`| Event Type | Count |`);
|
|
731
|
+
sections.push(`|------------|-------|`);
|
|
732
|
+
for (const [evType, count] of Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1])) {
|
|
733
|
+
sections.push(`| ${evType} | ${count} |`);
|
|
734
|
+
}
|
|
735
|
+
sections.push(``);
|
|
736
|
+
if (js.recentEvents.length > 0) {
|
|
737
|
+
sections.push(`### Recent Journal Events (last ${js.recentEvents.length})`, ``);
|
|
738
|
+
for (const ev of js.recentEvents) {
|
|
739
|
+
const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
|
|
740
|
+
if (ev.rule)
|
|
741
|
+
parts.push(`rule=${ev.rule}`);
|
|
742
|
+
if (ev.unitId)
|
|
743
|
+
parts.push(`unit=${ev.unitId}`);
|
|
744
|
+
sections.push(`- ${parts.join(" ")}`);
|
|
745
|
+
}
|
|
746
|
+
sections.push(``);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
494
749
|
writeFileSync(filePath, sections.join("\n"), "utf-8");
|
|
495
750
|
return filePath;
|
|
496
751
|
}
|
|
@@ -565,6 +820,42 @@ function formatReportForPrompt(report) {
|
|
|
565
820
|
sections.push(`- Total duration: ${formatDuration(totals.duration)}`);
|
|
566
821
|
sections.push("");
|
|
567
822
|
}
|
|
823
|
+
// Activity log metadata
|
|
824
|
+
if (report.activityLogMeta) {
|
|
825
|
+
const meta = report.activityLogMeta;
|
|
826
|
+
sections.push("### Activity Log Overview");
|
|
827
|
+
sections.push(`- Files: ${meta.fileCount}, Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
|
|
828
|
+
if (meta.oldestFile)
|
|
829
|
+
sections.push(`- Oldest: ${meta.oldestFile}`);
|
|
830
|
+
if (meta.newestFile)
|
|
831
|
+
sections.push(`- Newest: ${meta.newestFile}`);
|
|
832
|
+
sections.push("");
|
|
833
|
+
}
|
|
834
|
+
// Journal summary — structured event timeline
|
|
835
|
+
if (report.journalSummary) {
|
|
836
|
+
const js = report.journalSummary;
|
|
837
|
+
sections.push("### Journal Summary (Iteration Event Log)");
|
|
838
|
+
sections.push(`- Total entries: ${js.totalEntries}, Distinct flows: ${js.flowCount}, Daily files: ${js.fileCount}`);
|
|
839
|
+
if (js.oldestEntry)
|
|
840
|
+
sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
|
|
841
|
+
// Event type distribution (compact)
|
|
842
|
+
const eventPairs = Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1]);
|
|
843
|
+
sections.push(`- Events: ${eventPairs.map(([t, c]) => `${t}(${c})`).join(", ")}`);
|
|
844
|
+
// Recent events timeline (for tracing what just happened)
|
|
845
|
+
if (js.recentEvents.length > 0) {
|
|
846
|
+
sections.push("");
|
|
847
|
+
sections.push(`**Recent Journal Events (last ${js.recentEvents.length}):**`);
|
|
848
|
+
for (const ev of js.recentEvents) {
|
|
849
|
+
const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
|
|
850
|
+
if (ev.rule)
|
|
851
|
+
parts.push(`rule=${ev.rule}`);
|
|
852
|
+
if (ev.unitId)
|
|
853
|
+
parts.push(`unit=${ev.unitId}`);
|
|
854
|
+
sections.push(`- ${parts.join(" ")}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
sections.push("");
|
|
858
|
+
}
|
|
568
859
|
// Completed keys count
|
|
569
860
|
sections.push(`### Completed Keys: ${report.completedKeys.length}`);
|
|
570
861
|
sections.push(`### GSD Version: ${report.gsdVersion}`);
|
|
@@ -20,21 +20,23 @@ import { getErrorMessage } from "./error-utils.js";
|
|
|
20
20
|
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
|
|
21
21
|
/**
|
|
22
22
|
* Build a meaningful conventional commit message from task execution context.
|
|
23
|
-
* Format: `{type}
|
|
23
|
+
* Format: `{type}: {description}` (clean conventional commit — no GSD IDs in subject).
|
|
24
|
+
*
|
|
25
|
+
* GSD metadata is placed in a `GSD-Task:` git trailer at the end of the body,
|
|
26
|
+
* following the same convention as `Signed-off-by:` or `Co-Authored-By:`.
|
|
24
27
|
*
|
|
25
28
|
* The description is the task summary one-liner if available (it describes
|
|
26
29
|
* what was actually built), falling back to the task title (what was planned).
|
|
27
30
|
*/
|
|
28
31
|
export function buildTaskCommitMessage(ctx) {
|
|
29
|
-
const scope = ctx.taskId; // e.g. "S01/T02" or just "T02"
|
|
30
32
|
const description = ctx.oneLiner || ctx.taskTitle;
|
|
31
33
|
const type = inferCommitType(ctx.taskTitle, ctx.oneLiner);
|
|
32
|
-
// Truncate description to ~72 chars for subject line
|
|
33
|
-
const maxDescLen =
|
|
34
|
+
// Truncate description to ~72 chars for subject line (full budget without scope)
|
|
35
|
+
const maxDescLen = 70 - type.length;
|
|
34
36
|
const truncated = description.length > maxDescLen
|
|
35
37
|
? description.slice(0, maxDescLen - 1).trimEnd() + "…"
|
|
36
38
|
: description;
|
|
37
|
-
const subject = `${type}
|
|
39
|
+
const subject = `${type}: ${truncated}`;
|
|
38
40
|
// Build body with key files if available
|
|
39
41
|
const bodyParts = [];
|
|
40
42
|
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
@@ -44,13 +46,12 @@ export function buildTaskCommitMessage(ctx) {
|
|
|
44
46
|
.join("\n");
|
|
45
47
|
bodyParts.push(fileLines);
|
|
46
48
|
}
|
|
49
|
+
// Trailers: GSD-Task first, then Resolves
|
|
50
|
+
bodyParts.push(`GSD-Task: ${ctx.taskId}`);
|
|
47
51
|
if (ctx.issueNumber) {
|
|
48
52
|
bodyParts.push(`Resolves #${ctx.issueNumber}`);
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
52
|
-
}
|
|
53
|
-
return subject;
|
|
54
|
+
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
56
57
|
* Thrown when a slice merge hits code conflicts in non-.gsd files.
|
|
@@ -385,7 +386,7 @@ export class GitServiceImpl {
|
|
|
385
386
|
return null;
|
|
386
387
|
const message = taskContext
|
|
387
388
|
? buildTaskCommitMessage(taskContext)
|
|
388
|
-
: `chore
|
|
389
|
+
: `chore: auto-commit after ${unitType}\n\nGSD-Unit: ${unitId}`;
|
|
389
390
|
nativeCommit(this.basePath, message, { allowEmpty: false });
|
|
390
391
|
return message;
|
|
391
392
|
}
|