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
|
@@ -58,7 +58,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
58
58
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
59
59
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
60
60
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
61
|
-
import { isDbAvailable } from "./gsd-db.js";
|
|
61
|
+
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
62
62
|
import { hideFooter } from "./auto-dashboard.js";
|
|
63
63
|
import {
|
|
64
64
|
debugLog,
|
|
@@ -683,6 +683,12 @@ export async function bootstrapAutoSession(
|
|
|
683
683
|
if (milestoneIds.length > 1) {
|
|
684
684
|
const issues: string[] = [];
|
|
685
685
|
for (const id of milestoneIds) {
|
|
686
|
+
// Skip completed/parked milestones — a leftover CONTEXT-DRAFT.md
|
|
687
|
+
// on a finished milestone is harmless residue, not an actionable warning.
|
|
688
|
+
if (isDbAvailable()) {
|
|
689
|
+
const ms = getMilestone(id);
|
|
690
|
+
if (ms?.status === "complete" || ms?.status === "parked") continue;
|
|
691
|
+
}
|
|
686
692
|
const draft = resolveMilestoneFile(base, id, "CONTEXT-DRAFT");
|
|
687
693
|
if (draft)
|
|
688
694
|
issues.push(
|
|
@@ -1058,13 +1058,15 @@ export function mergeMilestoneToMain(
|
|
|
1058
1058
|
if (titleMatch) milestoneTitle = titleMatch[1].trim();
|
|
1059
1059
|
}
|
|
1060
1060
|
milestoneTitle = milestoneTitle || milestoneId;
|
|
1061
|
-
const subject = `feat
|
|
1061
|
+
const subject = `feat: ${milestoneTitle}`;
|
|
1062
1062
|
let body = "";
|
|
1063
1063
|
if (completedSlices.length > 0) {
|
|
1064
1064
|
const sliceLines = completedSlices
|
|
1065
1065
|
.map((s) => `- ${s.id}: ${s.title}`)
|
|
1066
1066
|
.join("\n");
|
|
1067
|
-
body = `\n\nCompleted slices:\n${sliceLines}\n\nBranch: ${milestoneBranch}`;
|
|
1067
|
+
body = `\n\nCompleted slices:\n${sliceLines}\n\nGSD-Milestone: ${milestoneId}\nBranch: ${milestoneBranch}`;
|
|
1068
|
+
} else {
|
|
1069
|
+
body = `\n\nGSD-Milestone: ${milestoneId}\nBranch: ${milestoneBranch}`;
|
|
1068
1070
|
}
|
|
1069
1071
|
const commitMessage = subject + body;
|
|
1070
1072
|
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
|
|
3
6
|
import { enableDebug } from "../../debug-logger.js";
|
|
4
7
|
import { getAutoDashboardData, isAutoActive, isAutoPaused, pauseAuto, startAuto, stopAuto, stopAutoRemote } from "../../auto.js";
|
|
5
8
|
import { handleRate } from "../../commands-rate.js";
|
|
6
9
|
import { guardRemoteSession, projectRoot } from "../context.js";
|
|
7
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Parse --yolo flag and optional file path from the auto command string.
|
|
13
|
+
* Supports: `/gsd auto --yolo path/to/file.md` or `/gsd auto -y path/to/file.md`
|
|
14
|
+
*/
|
|
15
|
+
function parseYoloFlag(trimmed: string): { yoloSeedFile: string | null; rest: string } {
|
|
16
|
+
const yoloRe = /(?:--yolo|-y)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)/;
|
|
17
|
+
const match = trimmed.match(yoloRe);
|
|
18
|
+
if (!match) return { yoloSeedFile: null, rest: trimmed };
|
|
19
|
+
|
|
20
|
+
// Strip quotes if present
|
|
21
|
+
let filePath = match[1];
|
|
22
|
+
if ((filePath.startsWith('"') && filePath.endsWith('"')) ||
|
|
23
|
+
(filePath.startsWith("'") && filePath.endsWith("'"))) {
|
|
24
|
+
filePath = filePath.slice(1, -1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const rest = trimmed.replace(match[0], "").replace(/\s+/g, " ").trim();
|
|
28
|
+
return { yoloSeedFile: filePath, rest };
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<boolean> {
|
|
9
32
|
if (trimmed === "next" || trimmed.startsWith("next ")) {
|
|
10
33
|
if (trimmed.includes("--dry-run")) {
|
|
@@ -21,11 +44,31 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
|
|
|
21
44
|
}
|
|
22
45
|
|
|
23
46
|
if (trimmed === "auto" || trimmed.startsWith("auto ")) {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
47
|
+
const { yoloSeedFile, rest } = parseYoloFlag(trimmed);
|
|
48
|
+
const verboseMode = rest.includes("--verbose");
|
|
49
|
+
const debugMode = rest.includes("--debug");
|
|
26
50
|
if (debugMode) enableDebug(projectRoot());
|
|
27
51
|
if (!(await guardRemoteSession(ctx, pi))) return true;
|
|
28
|
-
|
|
52
|
+
|
|
53
|
+
if (yoloSeedFile) {
|
|
54
|
+
const resolved = resolve(projectRoot(), yoloSeedFile);
|
|
55
|
+
if (!existsSync(resolved)) {
|
|
56
|
+
ctx.ui.notify(`Yolo seed file not found: ${resolved}`, "error");
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const seedContent = readFileSync(resolved, "utf-8").trim();
|
|
60
|
+
if (!seedContent) {
|
|
61
|
+
ctx.ui.notify(`Yolo seed file is empty: ${resolved}`, "error");
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
// Headless path: bootstrap project, dispatch non-interactive discuss,
|
|
65
|
+
// then auto-mode starts automatically via checkAutoStartAfterDiscuss
|
|
66
|
+
// when the LLM says "Milestone X ready."
|
|
67
|
+
const { showHeadlessMilestoneCreation } = await import("../../guided-flow.js");
|
|
68
|
+
await showHeadlessMilestoneCreation(ctx, pi, projectRoot(), seedContent);
|
|
69
|
+
} else {
|
|
70
|
+
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
71
|
+
}
|
|
29
72
|
return true;
|
|
30
73
|
}
|
|
31
74
|
|
|
@@ -37,7 +37,7 @@ import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./comm
|
|
|
37
37
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
39
|
interface ForensicAnomaly {
|
|
40
|
-
type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace";
|
|
40
|
+
type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace" | "journal-stuck" | "journal-guard-block" | "journal-rapid-iterations" | "journal-worktree-failure";
|
|
41
41
|
severity: "info" | "warning" | "error";
|
|
42
42
|
unitType?: string;
|
|
43
43
|
unitId?: string;
|
|
@@ -54,6 +54,37 @@ interface UnitTrace {
|
|
|
54
54
|
mtime: number;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** Summary of .gsd/activity/ directory metadata. */
|
|
58
|
+
interface ActivityLogMeta {
|
|
59
|
+
fileCount: number;
|
|
60
|
+
totalSizeBytes: number;
|
|
61
|
+
oldestFile: string | null;
|
|
62
|
+
newestFile: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Summary of .gsd/journal/ data for forensic investigation.
|
|
67
|
+
*
|
|
68
|
+
* To avoid loading huge journal histories into memory, only the most recent
|
|
69
|
+
* daily files are fully parsed. Older files are line-counted for totals.
|
|
70
|
+
* Event counts and flow IDs reflect only recent files.
|
|
71
|
+
*/
|
|
72
|
+
interface JournalSummary {
|
|
73
|
+
/** Total journal entries across all files (recent parsed + older line-counted) */
|
|
74
|
+
totalEntries: number;
|
|
75
|
+
/** Distinct flow IDs from recent files (each = one auto-mode iteration) */
|
|
76
|
+
flowCount: number;
|
|
77
|
+
/** Event counts by type (from recent files only) */
|
|
78
|
+
eventCounts: Record<string, number>;
|
|
79
|
+
/** Most recent journal entries (last 20) for context */
|
|
80
|
+
recentEvents: { ts: string; flowId: string; eventType: string; rule?: string; unitId?: string }[];
|
|
81
|
+
/** Date range of journal data */
|
|
82
|
+
oldestEntry: string | null;
|
|
83
|
+
newestEntry: string | null;
|
|
84
|
+
/** Daily file count */
|
|
85
|
+
fileCount: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
57
88
|
interface ForensicReport {
|
|
58
89
|
gsdVersion: string;
|
|
59
90
|
timestamp: string;
|
|
@@ -68,6 +99,8 @@ interface ForensicReport {
|
|
|
68
99
|
doctorIssues: DoctorIssue[];
|
|
69
100
|
anomalies: ForensicAnomaly[];
|
|
70
101
|
recentUnits: { type: string; id: string; cost: number; duration: number; model: string; finishedAt: number }[];
|
|
102
|
+
journalSummary: JournalSummary | null;
|
|
103
|
+
activityLogMeta: ActivityLogMeta | null;
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
// ─── Duplicate Detection ──────────────────────────────────────────────────────
|
|
@@ -276,7 +309,13 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
|
|
|
276
309
|
// from import.meta.url would resolve to ~/package.json (wrong on every system).
|
|
277
310
|
const gsdVersion = process.env.GSD_VERSION || "unknown";
|
|
278
311
|
|
|
279
|
-
// 9.
|
|
312
|
+
// 9. Scan journal for flow timeline and structured events
|
|
313
|
+
const journalSummary = scanJournalForForensics(basePath);
|
|
314
|
+
|
|
315
|
+
// 10. Gather activity log directory metadata
|
|
316
|
+
const activityLogMeta = gatherActivityLogMeta(basePath, activeMilestone);
|
|
317
|
+
|
|
318
|
+
// 11. Run anomaly detectors
|
|
280
319
|
if (metrics?.units) detectStuckLoops(metrics.units, anomalies);
|
|
281
320
|
if (metrics?.units) detectCostSpikes(metrics.units, anomalies);
|
|
282
321
|
detectTimeouts(unitTraces, anomalies);
|
|
@@ -284,6 +323,7 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
|
|
|
284
323
|
detectCrash(crashLock, anomalies);
|
|
285
324
|
detectDoctorIssues(doctorIssues, anomalies);
|
|
286
325
|
detectErrorTraces(unitTraces, anomalies);
|
|
326
|
+
detectJournalAnomalies(journalSummary, anomalies);
|
|
287
327
|
|
|
288
328
|
return {
|
|
289
329
|
gsdVersion,
|
|
@@ -299,6 +339,8 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
|
|
|
299
339
|
doctorIssues,
|
|
300
340
|
anomalies,
|
|
301
341
|
recentUnits,
|
|
342
|
+
journalSummary,
|
|
343
|
+
activityLogMeta,
|
|
302
344
|
};
|
|
303
345
|
}
|
|
304
346
|
|
|
@@ -306,6 +348,9 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
|
|
|
306
348
|
|
|
307
349
|
const ACTIVITY_FILENAME_RE = /^(\d+)-(.+?)-(.+)\.jsonl$/;
|
|
308
350
|
|
|
351
|
+
/** Threshold below which iteration cadence is considered rapid (thrashing). */
|
|
352
|
+
const RAPID_ITERATION_THRESHOLD_MS = 5000;
|
|
353
|
+
|
|
309
354
|
function scanActivityLogs(basePath: string, activeMilestone?: string | null): UnitTrace[] {
|
|
310
355
|
const activityDirs = resolveActivityDirs(basePath, activeMilestone);
|
|
311
356
|
const allTraces: UnitTrace[] = [];
|
|
@@ -380,6 +425,154 @@ function resolveActivityDirs(basePath: string, activeMilestone?: string | null):
|
|
|
380
425
|
return dirs;
|
|
381
426
|
}
|
|
382
427
|
|
|
428
|
+
// ─── Journal Scanner ──────────────────────────────────────────────────────────
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Max recent journal files to fully parse for event counts and recent events.
|
|
432
|
+
* Older files are line-counted only to avoid loading huge amounts of data.
|
|
433
|
+
*/
|
|
434
|
+
const MAX_JOURNAL_RECENT_FILES = 3;
|
|
435
|
+
|
|
436
|
+
/** Max recent events to extract for the forensic report timeline. */
|
|
437
|
+
const MAX_JOURNAL_RECENT_EVENTS = 20;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Intelligently scan journal files for forensic summary.
|
|
441
|
+
*
|
|
442
|
+
* Journal files can be huge (thousands of JSONL entries over weeks of auto-mode).
|
|
443
|
+
* Instead of loading all entries into memory:
|
|
444
|
+
* - Only fully parse the most recent N daily files (event counts, flow tracking)
|
|
445
|
+
* - Line-count older files for approximate totals (no JSON parsing)
|
|
446
|
+
* - Extract only the last 20 events for the timeline
|
|
447
|
+
*/
|
|
448
|
+
function scanJournalForForensics(basePath: string): JournalSummary | null {
|
|
449
|
+
try {
|
|
450
|
+
const journalDir = join(gsdRoot(basePath), "journal");
|
|
451
|
+
if (!existsSync(journalDir)) return null;
|
|
452
|
+
|
|
453
|
+
const files = readdirSync(journalDir).filter(f => f.endsWith(".jsonl")).sort();
|
|
454
|
+
if (files.length === 0) return null;
|
|
455
|
+
|
|
456
|
+
// Split into recent (fully parsed) and older (line-counted only)
|
|
457
|
+
const recentFiles = files.slice(-MAX_JOURNAL_RECENT_FILES);
|
|
458
|
+
const olderFiles = files.slice(0, -MAX_JOURNAL_RECENT_FILES);
|
|
459
|
+
|
|
460
|
+
// Line-count older files without parsing — avoids loading megabytes of JSON
|
|
461
|
+
let olderEntryCount = 0;
|
|
462
|
+
let oldestEntry: string | null = null;
|
|
463
|
+
for (const file of olderFiles) {
|
|
464
|
+
try {
|
|
465
|
+
const raw = readFileSync(join(journalDir, file), "utf-8");
|
|
466
|
+
const lines = raw.split("\n");
|
|
467
|
+
for (const line of lines) {
|
|
468
|
+
if (!line.trim()) continue;
|
|
469
|
+
olderEntryCount++;
|
|
470
|
+
// Extract only the timestamp from the first non-empty line of the oldest file
|
|
471
|
+
if (!oldestEntry) {
|
|
472
|
+
try {
|
|
473
|
+
const parsed = JSON.parse(line) as { ts?: string };
|
|
474
|
+
if (parsed.ts) oldestEntry = parsed.ts;
|
|
475
|
+
} catch { /* skip malformed */ }
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch { /* skip unreadable files */ }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Fully parse recent files for event counts and timeline
|
|
482
|
+
const eventCounts: Record<string, number> = {};
|
|
483
|
+
const flowIds = new Set<string>();
|
|
484
|
+
const recentParsedEntries: { ts: string; flowId: string; eventType: string; rule?: string; unitId?: string }[] = [];
|
|
485
|
+
let recentEntryCount = 0;
|
|
486
|
+
|
|
487
|
+
for (const file of recentFiles) {
|
|
488
|
+
try {
|
|
489
|
+
const raw = readFileSync(join(journalDir, file), "utf-8");
|
|
490
|
+
for (const line of raw.split("\n")) {
|
|
491
|
+
if (!line.trim()) continue;
|
|
492
|
+
try {
|
|
493
|
+
const entry = JSON.parse(line) as { ts: string; flowId: string; eventType: string; rule?: string; data?: Record<string, unknown> };
|
|
494
|
+
recentEntryCount++;
|
|
495
|
+
eventCounts[entry.eventType] = (eventCounts[entry.eventType] ?? 0) + 1;
|
|
496
|
+
flowIds.add(entry.flowId);
|
|
497
|
+
|
|
498
|
+
if (!oldestEntry) oldestEntry = entry.ts;
|
|
499
|
+
|
|
500
|
+
// Keep a rolling window of last N events — avoids accumulating unbounded arrays
|
|
501
|
+
recentParsedEntries.push({
|
|
502
|
+
ts: entry.ts,
|
|
503
|
+
flowId: entry.flowId,
|
|
504
|
+
eventType: entry.eventType,
|
|
505
|
+
rule: entry.rule,
|
|
506
|
+
unitId: entry.data?.unitId as string | undefined,
|
|
507
|
+
});
|
|
508
|
+
if (recentParsedEntries.length > MAX_JOURNAL_RECENT_EVENTS) {
|
|
509
|
+
recentParsedEntries.shift();
|
|
510
|
+
}
|
|
511
|
+
} catch { /* skip malformed lines */ }
|
|
512
|
+
}
|
|
513
|
+
} catch { /* skip unreadable files */ }
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const totalEntries = olderEntryCount + recentEntryCount;
|
|
517
|
+
if (totalEntries === 0) return null;
|
|
518
|
+
|
|
519
|
+
const newestEntry = recentParsedEntries.length > 0
|
|
520
|
+
? recentParsedEntries[recentParsedEntries.length - 1]!.ts
|
|
521
|
+
: null;
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
totalEntries,
|
|
525
|
+
flowCount: flowIds.size,
|
|
526
|
+
eventCounts,
|
|
527
|
+
recentEvents: recentParsedEntries,
|
|
528
|
+
oldestEntry,
|
|
529
|
+
newestEntry,
|
|
530
|
+
fileCount: files.length,
|
|
531
|
+
};
|
|
532
|
+
} catch {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ─── Activity Log Metadata ────────────────────────────────────────────────────
|
|
538
|
+
|
|
539
|
+
function gatherActivityLogMeta(basePath: string, activeMilestone?: string | null): ActivityLogMeta | null {
|
|
540
|
+
try {
|
|
541
|
+
const activityDirs = resolveActivityDirs(basePath, activeMilestone);
|
|
542
|
+
let fileCount = 0;
|
|
543
|
+
let totalSizeBytes = 0;
|
|
544
|
+
let oldestFile: string | null = null;
|
|
545
|
+
let newestFile: string | null = null;
|
|
546
|
+
let oldestMtime = Infinity;
|
|
547
|
+
let newestMtime = 0;
|
|
548
|
+
|
|
549
|
+
for (const activityDir of activityDirs) {
|
|
550
|
+
if (!existsSync(activityDir)) continue;
|
|
551
|
+
const files = readdirSync(activityDir).filter(f => f.endsWith(".jsonl"));
|
|
552
|
+
for (const file of files) {
|
|
553
|
+
const filePath = join(activityDir, file);
|
|
554
|
+
const stat = statSync(filePath, { throwIfNoEntry: false });
|
|
555
|
+
if (!stat) continue;
|
|
556
|
+
fileCount++;
|
|
557
|
+
totalSizeBytes += stat.size;
|
|
558
|
+
if (stat.mtimeMs < oldestMtime) {
|
|
559
|
+
oldestMtime = stat.mtimeMs;
|
|
560
|
+
oldestFile = file;
|
|
561
|
+
}
|
|
562
|
+
if (stat.mtimeMs > newestMtime) {
|
|
563
|
+
newestMtime = stat.mtimeMs;
|
|
564
|
+
newestFile = file;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (fileCount === 0) return null;
|
|
570
|
+
return { fileCount, totalSizeBytes, oldestFile, newestFile };
|
|
571
|
+
} catch {
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
383
576
|
// ─── Completed Keys Loader ────────────────────────────────────────────────────
|
|
384
577
|
|
|
385
578
|
function loadCompletedKeys(basePath: string): string[] {
|
|
@@ -524,6 +717,66 @@ function detectErrorTraces(traces: UnitTrace[], anomalies: ForensicAnomaly[]): v
|
|
|
524
717
|
}
|
|
525
718
|
}
|
|
526
719
|
|
|
720
|
+
function detectJournalAnomalies(journal: JournalSummary | null, anomalies: ForensicAnomaly[]): void {
|
|
721
|
+
if (!journal) return;
|
|
722
|
+
|
|
723
|
+
// Detect stuck-detected events from the journal
|
|
724
|
+
const stuckCount = journal.eventCounts["stuck-detected"] ?? 0;
|
|
725
|
+
if (stuckCount > 0) {
|
|
726
|
+
anomalies.push({
|
|
727
|
+
type: "journal-stuck",
|
|
728
|
+
severity: stuckCount >= 3 ? "error" : "warning",
|
|
729
|
+
summary: `Journal recorded ${stuckCount} stuck-detected event(s)`,
|
|
730
|
+
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.`,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Detect guard-block events (dispatch was blocked by a guard)
|
|
735
|
+
const guardCount = journal.eventCounts["guard-block"] ?? 0;
|
|
736
|
+
if (guardCount > 0) {
|
|
737
|
+
anomalies.push({
|
|
738
|
+
type: "journal-guard-block",
|
|
739
|
+
severity: guardCount >= 5 ? "warning" : "info",
|
|
740
|
+
summary: `Journal recorded ${guardCount} guard-block event(s)`,
|
|
741
|
+
details: `Dispatch was blocked by a guard condition ${guardCount} time(s). This may indicate a persistent blocking condition preventing progress.`,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Detect rapid iterations (many flows in short time = likely thrashing)
|
|
746
|
+
if (journal.flowCount > 0 && journal.oldestEntry && journal.newestEntry) {
|
|
747
|
+
const oldest = new Date(journal.oldestEntry).getTime();
|
|
748
|
+
const newest = new Date(journal.newestEntry).getTime();
|
|
749
|
+
const spanMs = newest - oldest;
|
|
750
|
+
if (spanMs > 0 && journal.flowCount > 10) {
|
|
751
|
+
const avgMs = spanMs / journal.flowCount;
|
|
752
|
+
if (avgMs < RAPID_ITERATION_THRESHOLD_MS) {
|
|
753
|
+
anomalies.push({
|
|
754
|
+
type: "journal-rapid-iterations",
|
|
755
|
+
severity: "warning",
|
|
756
|
+
summary: `${journal.flowCount} iterations in ${formatDuration(spanMs)} (avg ${formatDuration(avgMs)}/iteration)`,
|
|
757
|
+
details: `Unusually rapid iteration cadence suggests the loop may be thrashing without making progress. Review recent journal events for dispatch-stop or terminal events.`,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Detect worktree failures from journal events
|
|
764
|
+
const wtCreateFailed = journal.eventCounts["worktree-create-failed"] ?? 0;
|
|
765
|
+
const wtMergeFailed = journal.eventCounts["worktree-merge-failed"] ?? 0;
|
|
766
|
+
const wtFailures = wtCreateFailed + wtMergeFailed;
|
|
767
|
+
if (wtFailures > 0) {
|
|
768
|
+
const parts: string[] = [];
|
|
769
|
+
if (wtCreateFailed > 0) parts.push(`${wtCreateFailed} create failure(s)`);
|
|
770
|
+
if (wtMergeFailed > 0) parts.push(`${wtMergeFailed} merge failure(s)`);
|
|
771
|
+
anomalies.push({
|
|
772
|
+
type: "journal-worktree-failure",
|
|
773
|
+
severity: "warning",
|
|
774
|
+
summary: `Worktree failures: ${parts.join(", ")}`,
|
|
775
|
+
details: `Journal recorded worktree operation failures. These may indicate git state corruption or conflicting branches.`,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
527
780
|
// ─── Report Persistence ───────────────────────────────────────────────────────
|
|
528
781
|
|
|
529
782
|
function saveForensicReport(basePath: string, report: ForensicReport, problemDescription: string): string {
|
|
@@ -600,6 +853,45 @@ function saveForensicReport(basePath: string, report: ForensicReport, problemDes
|
|
|
600
853
|
sections.push(redact(formatCrashInfo(report.crashLock)), ``);
|
|
601
854
|
}
|
|
602
855
|
|
|
856
|
+
// Activity log metadata
|
|
857
|
+
if (report.activityLogMeta) {
|
|
858
|
+
const meta = report.activityLogMeta;
|
|
859
|
+
sections.push(`## Activity Log Metadata`, ``);
|
|
860
|
+
sections.push(`- Files: ${meta.fileCount}`);
|
|
861
|
+
sections.push(`- Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
|
|
862
|
+
if (meta.oldestFile) sections.push(`- Oldest: ${meta.oldestFile}`);
|
|
863
|
+
if (meta.newestFile) sections.push(`- Newest: ${meta.newestFile}`);
|
|
864
|
+
sections.push(``);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Journal summary
|
|
868
|
+
if (report.journalSummary) {
|
|
869
|
+
const js = report.journalSummary;
|
|
870
|
+
sections.push(`## Journal Summary`, ``);
|
|
871
|
+
sections.push(`- Total entries: ${js.totalEntries}`);
|
|
872
|
+
sections.push(`- Distinct flows (iterations): ${js.flowCount}`);
|
|
873
|
+
sections.push(`- Daily files: ${js.fileCount}`);
|
|
874
|
+
if (js.oldestEntry) sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
|
|
875
|
+
sections.push(``);
|
|
876
|
+
sections.push(`### Event Type Distribution`, ``);
|
|
877
|
+
sections.push(`| Event Type | Count |`);
|
|
878
|
+
sections.push(`|------------|-------|`);
|
|
879
|
+
for (const [evType, count] of Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1])) {
|
|
880
|
+
sections.push(`| ${evType} | ${count} |`);
|
|
881
|
+
}
|
|
882
|
+
sections.push(``);
|
|
883
|
+
if (js.recentEvents.length > 0) {
|
|
884
|
+
sections.push(`### Recent Journal Events (last ${js.recentEvents.length})`, ``);
|
|
885
|
+
for (const ev of js.recentEvents) {
|
|
886
|
+
const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
|
|
887
|
+
if (ev.rule) parts.push(`rule=${ev.rule}`);
|
|
888
|
+
if (ev.unitId) parts.push(`unit=${ev.unitId}`);
|
|
889
|
+
sections.push(`- ${parts.join(" ")}`);
|
|
890
|
+
}
|
|
891
|
+
sections.push(``);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
603
895
|
writeFileSync(filePath, sections.join("\n"), "utf-8");
|
|
604
896
|
return filePath;
|
|
605
897
|
}
|
|
@@ -681,6 +973,41 @@ function formatReportForPrompt(report: ForensicReport): string {
|
|
|
681
973
|
sections.push("");
|
|
682
974
|
}
|
|
683
975
|
|
|
976
|
+
// Activity log metadata
|
|
977
|
+
if (report.activityLogMeta) {
|
|
978
|
+
const meta = report.activityLogMeta;
|
|
979
|
+
sections.push("### Activity Log Overview");
|
|
980
|
+
sections.push(`- Files: ${meta.fileCount}, Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
|
|
981
|
+
if (meta.oldestFile) sections.push(`- Oldest: ${meta.oldestFile}`);
|
|
982
|
+
if (meta.newestFile) sections.push(`- Newest: ${meta.newestFile}`);
|
|
983
|
+
sections.push("");
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Journal summary — structured event timeline
|
|
987
|
+
if (report.journalSummary) {
|
|
988
|
+
const js = report.journalSummary;
|
|
989
|
+
sections.push("### Journal Summary (Iteration Event Log)");
|
|
990
|
+
sections.push(`- Total entries: ${js.totalEntries}, Distinct flows: ${js.flowCount}, Daily files: ${js.fileCount}`);
|
|
991
|
+
if (js.oldestEntry) sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
|
|
992
|
+
|
|
993
|
+
// Event type distribution (compact)
|
|
994
|
+
const eventPairs = Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1]);
|
|
995
|
+
sections.push(`- Events: ${eventPairs.map(([t, c]) => `${t}(${c})`).join(", ")}`);
|
|
996
|
+
|
|
997
|
+
// Recent events timeline (for tracing what just happened)
|
|
998
|
+
if (js.recentEvents.length > 0) {
|
|
999
|
+
sections.push("");
|
|
1000
|
+
sections.push(`**Recent Journal Events (last ${js.recentEvents.length}):**`);
|
|
1001
|
+
for (const ev of js.recentEvents) {
|
|
1002
|
+
const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
|
|
1003
|
+
if (ev.rule) parts.push(`rule=${ev.rule}`);
|
|
1004
|
+
if (ev.unitId) parts.push(`unit=${ev.unitId}`);
|
|
1005
|
+
sections.push(`- ${parts.join(" ")}`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
sections.push("");
|
|
1009
|
+
}
|
|
1010
|
+
|
|
684
1011
|
// Completed keys count
|
|
685
1012
|
sections.push(`### Completed Keys: ${report.completedKeys.length}`);
|
|
686
1013
|
sections.push(`### GSD Version: ${report.gsdVersion}`);
|
|
@@ -102,23 +102,25 @@ export interface TaskCommitContext {
|
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* Build a meaningful conventional commit message from task execution context.
|
|
105
|
-
* Format: `{type}
|
|
105
|
+
* Format: `{type}: {description}` (clean conventional commit — no GSD IDs in subject).
|
|
106
|
+
*
|
|
107
|
+
* GSD metadata is placed in a `GSD-Task:` git trailer at the end of the body,
|
|
108
|
+
* following the same convention as `Signed-off-by:` or `Co-Authored-By:`.
|
|
106
109
|
*
|
|
107
110
|
* The description is the task summary one-liner if available (it describes
|
|
108
111
|
* what was actually built), falling back to the task title (what was planned).
|
|
109
112
|
*/
|
|
110
113
|
export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
|
|
111
|
-
const scope = ctx.taskId; // e.g. "S01/T02" or just "T02"
|
|
112
114
|
const description = ctx.oneLiner || ctx.taskTitle;
|
|
113
115
|
const type = inferCommitType(ctx.taskTitle, ctx.oneLiner);
|
|
114
116
|
|
|
115
|
-
// Truncate description to ~72 chars for subject line
|
|
116
|
-
const maxDescLen =
|
|
117
|
+
// Truncate description to ~72 chars for subject line (full budget without scope)
|
|
118
|
+
const maxDescLen = 70 - type.length;
|
|
117
119
|
const truncated = description.length > maxDescLen
|
|
118
120
|
? description.slice(0, maxDescLen - 1).trimEnd() + "…"
|
|
119
121
|
: description;
|
|
120
122
|
|
|
121
|
-
const subject = `${type}
|
|
123
|
+
const subject = `${type}: ${truncated}`;
|
|
122
124
|
|
|
123
125
|
// Build body with key files if available
|
|
124
126
|
const bodyParts: string[] = [];
|
|
@@ -131,15 +133,14 @@ export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
|
|
|
131
133
|
bodyParts.push(fileLines);
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
// Trailers: GSD-Task first, then Resolves
|
|
137
|
+
bodyParts.push(`GSD-Task: ${ctx.taskId}`);
|
|
138
|
+
|
|
134
139
|
if (ctx.issueNumber) {
|
|
135
140
|
bodyParts.push(`Resolves #${ctx.issueNumber}`);
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return subject;
|
|
143
|
+
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
/**
|
|
@@ -538,7 +539,7 @@ export class GitServiceImpl {
|
|
|
538
539
|
|
|
539
540
|
const message = taskContext
|
|
540
541
|
? buildTaskCommitMessage(taskContext)
|
|
541
|
-
: `chore
|
|
542
|
+
: `chore: auto-commit after ${unitType}\n\nGSD-Unit: ${unitId}`;
|
|
542
543
|
nativeCommit(this.basePath, message, { allowEmpty: false });
|
|
543
544
|
return message;
|
|
544
545
|
}
|