@kenkaiiii/ggcoder 4.3.232 → 4.3.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -89
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +8 -16
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/prompt-commands.test.js +14 -44
- package/dist/core/prompt-commands.test.js.map +1 -1
- package/dist/core/runtime-mode.d.ts +0 -11
- package/dist/core/runtime-mode.d.ts.map +1 -1
- package/dist/core/runtime-mode.js +0 -9
- package/dist/core/runtime-mode.js.map +1 -1
- package/dist/core/session-restore-display.test.js +4 -110
- package/dist/core/session-restore-display.test.js.map +1 -1
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +1 -1
- package/dist/interactive.js.map +1 -1
- package/dist/system-prompt.d.ts +1 -2
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +11 -58
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +3 -158
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/bash.d.ts +1 -4
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +3 -12
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/checkpoint-hook.test.js +3 -3
- package/dist/tools/checkpoint-hook.test.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -4
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +5 -13
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/enter-plan.js +1 -1
- package/dist/tools/enter-plan.js.map +1 -1
- package/dist/tools/index.d.ts +0 -9
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -8
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/prompt-hints.d.ts.map +1 -1
- package/dist/tools/prompt-hints.js +0 -2
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/screenshot.d.ts +1 -1
- package/dist/tools/subagent.d.ts +1 -4
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +2 -5
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/write.d.ts +1 -4
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +5 -13
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/App.d.ts +5 -20
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +71 -309
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +23 -25
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js +37 -0
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/app-items.test.d.ts +2 -0
- package/dist/ui/app-items.test.d.ts.map +1 -0
- package/dist/ui/app-items.test.js +38 -0
- package/dist/ui/app-items.test.js.map +1 -0
- package/dist/ui/app-state-persistence.test.js +14 -318
- package/dist/ui/app-state-persistence.test.js.map +1 -1
- package/dist/ui/chat-layout-pinning.test.js +74 -4
- package/dist/ui/chat-layout-pinning.test.js.map +1 -1
- package/dist/ui/components/AssistantMessage.test.js +6 -9
- package/dist/ui/components/AssistantMessage.test.js.map +1 -1
- package/dist/ui/components/Banner.js +1 -1
- package/dist/ui/components/Banner.js.map +1 -1
- package/dist/ui/components/ChatFooterPane.d.ts +1 -5
- package/dist/ui/components/ChatFooterPane.d.ts.map +1 -1
- package/dist/ui/components/ChatFooterPane.js +3 -4
- package/dist/ui/components/ChatFooterPane.js.map +1 -1
- package/dist/ui/components/ChatInputStack.d.ts +3 -1
- package/dist/ui/components/ChatInputStack.d.ts.map +1 -1
- package/dist/ui/components/ChatInputStack.js +4 -3
- package/dist/ui/components/ChatInputStack.js.map +1 -1
- package/dist/ui/components/ChatScreen.d.ts +3 -16
- package/dist/ui/components/ChatScreen.d.ts.map +1 -1
- package/dist/ui/components/ChatScreen.js +2 -3
- package/dist/ui/components/ChatScreen.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +3 -8
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +5 -25
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts +1 -9
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +2 -44
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/LiveToolPanel.d.ts +30 -0
- package/dist/ui/components/LiveToolPanel.d.ts.map +1 -0
- package/dist/ui/components/LiveToolPanel.js +66 -0
- package/dist/ui/components/LiveToolPanel.js.map +1 -0
- package/dist/ui/components/ToolExecution.js +0 -2
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/TranscriptViewport.d.ts +1 -1
- package/dist/ui/components/TranscriptViewport.js +1 -1
- package/dist/ui/footer-jump-regression.test.js +12 -2
- package/dist/ui/footer-jump-regression.test.js.map +1 -1
- package/dist/ui/footer-status-layout.test.js +4 -12
- package/dist/ui/footer-status-layout.test.js.map +1 -1
- package/dist/ui/hooks/useChatLayoutMeasurements.d.ts +3 -4
- package/dist/ui/hooks/useChatLayoutMeasurements.d.ts.map +1 -1
- package/dist/ui/hooks/useChatLayoutMeasurements.js +26 -4
- package/dist/ui/hooks/useChatLayoutMeasurements.js.map +1 -1
- package/dist/ui/hooks/useModeState.d.ts +4 -19
- package/dist/ui/hooks/useModeState.d.ts.map +1 -1
- package/dist/ui/hooks/useModeState.js +5 -45
- package/dist/ui/hooks/useModeState.js.map +1 -1
- package/dist/ui/hooks/usePixelFixFlow.js +2 -2
- package/dist/ui/layout-decisions.d.ts +11 -34
- package/dist/ui/layout-decisions.d.ts.map +1 -1
- package/dist/ui/layout-decisions.js +15 -42
- package/dist/ui/layout-decisions.js.map +1 -1
- package/dist/ui/live-area-height.d.ts.map +1 -1
- package/dist/ui/live-area-height.js +6 -1
- package/dist/ui/live-area-height.js.map +1 -1
- package/dist/ui/live-frame-height.test.js +26 -1
- package/dist/ui/live-frame-height.test.js.map +1 -1
- package/dist/ui/plan-overlay.test.js +1 -1
- package/dist/ui/plan-overlay.test.js.map +1 -1
- package/dist/ui/prompt-routing.d.ts +1 -19
- package/dist/ui/prompt-routing.d.ts.map +1 -1
- package/dist/ui/prompt-routing.js +1 -79
- package/dist/ui/prompt-routing.js.map +1 -1
- package/dist/ui/render.d.ts +3 -18
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +0 -10
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/scroll-stabilization.test.js +1 -7
- package/dist/ui/scroll-stabilization.test.js.map +1 -1
- package/dist/ui/slash-command-images.test.js +11 -86
- package/dist/ui/slash-command-images.test.js.map +1 -1
- package/dist/ui/streaming-flush-bounce.test.d.ts +2 -0
- package/dist/ui/streaming-flush-bounce.test.d.ts.map +1 -0
- package/dist/ui/streaming-flush-bounce.test.js +156 -0
- package/dist/ui/streaming-flush-bounce.test.js.map +1 -0
- package/dist/ui/submit-prompt-command.d.ts +1 -16
- package/dist/ui/submit-prompt-command.d.ts.map +1 -1
- package/dist/ui/submit-prompt-command.js +4 -54
- package/dist/ui/submit-prompt-command.js.map +1 -1
- package/dist/ui/submit-slash-commands.d.ts +0 -1
- package/dist/ui/submit-slash-commands.d.ts.map +1 -1
- package/dist/ui/submit-slash-commands.js +0 -4
- package/dist/ui/submit-slash-commands.js.map +1 -1
- package/dist/ui/terminal-history-status-renderers.d.ts +0 -1
- package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -1
- package/dist/ui/terminal-history-status-renderers.js +0 -3
- package/dist/ui/terminal-history-status-renderers.js.map +1 -1
- package/dist/ui/terminal-history.d.ts.map +1 -1
- package/dist/ui/terminal-history.js +11 -56
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/terminal-history.test.js +6 -33
- package/dist/ui/terminal-history.test.js.map +1 -1
- package/dist/ui/tool-line-summary.d.ts +29 -0
- package/dist/ui/tool-line-summary.d.ts.map +1 -0
- package/dist/ui/tool-line-summary.js +153 -0
- package/dist/ui/tool-line-summary.js.map +1 -0
- package/dist/ui/transcript/TranscriptRenderer.d.ts +1 -1
- package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -1
- package/dist/ui/transcript/TranscriptRenderer.js +18 -12
- package/dist/ui/transcript/TranscriptRenderer.js.map +1 -1
- package/dist/ui/transcript/presentation.d.ts +1 -17
- package/dist/ui/transcript/presentation.d.ts.map +1 -1
- package/dist/ui/transcript/presentation.js +0 -26
- package/dist/ui/transcript/presentation.js.map +1 -1
- package/dist/ui/transcript/spacing.d.ts +2 -2
- package/dist/ui/transcript/spacing.d.ts.map +1 -1
- package/dist/ui/transcript/spacing.js +14 -9
- package/dist/ui/transcript/spacing.js.map +1 -1
- package/dist/ui/transcript/spacing.test.js +5 -8
- package/dist/ui/transcript/spacing.test.js.map +1 -1
- package/dist/ui/transcript/tool-presentation.js +1 -1
- package/dist/ui/transcript/tool-presentation.js.map +1 -1
- package/dist/ui/transcript/transcript-lines.d.ts.map +1 -1
- package/dist/ui/transcript/transcript-lines.js +4 -0
- package/dist/ui/transcript/transcript-lines.js.map +1 -1
- package/dist/ui/tui-history-parity.test.js +0 -47
- package/dist/ui/tui-history-parity.test.js.map +1 -1
- package/dist/ui/tui-simulation.test.js +2 -1
- package/dist/ui/tui-simulation.test.js.map +1 -1
- package/package.json +4 -4
- package/dist/core/goal-controller.d.ts +0 -82
- package/dist/core/goal-controller.d.ts.map +0 -1
- package/dist/core/goal-controller.js +0 -838
- package/dist/core/goal-controller.js.map +0 -1
- package/dist/core/goal-controller.test.d.ts +0 -2
- package/dist/core/goal-controller.test.d.ts.map +0 -1
- package/dist/core/goal-controller.test.js +0 -1279
- package/dist/core/goal-controller.test.js.map +0 -1
- package/dist/core/goal-engine.d.ts +0 -61
- package/dist/core/goal-engine.d.ts.map +0 -1
- package/dist/core/goal-engine.js +0 -123
- package/dist/core/goal-engine.js.map +0 -1
- package/dist/core/goal-engine.test.d.ts +0 -2
- package/dist/core/goal-engine.test.d.ts.map +0 -1
- package/dist/core/goal-engine.test.js +0 -295
- package/dist/core/goal-engine.test.js.map +0 -1
- package/dist/core/goal-integration.d.ts +0 -80
- package/dist/core/goal-integration.d.ts.map +0 -1
- package/dist/core/goal-integration.js +0 -296
- package/dist/core/goal-integration.js.map +0 -1
- package/dist/core/goal-integration.test.d.ts +0 -2
- package/dist/core/goal-integration.test.d.ts.map +0 -1
- package/dist/core/goal-integration.test.js +0 -369
- package/dist/core/goal-integration.test.js.map +0 -1
- package/dist/core/goal-lifecycle-smoke.test.d.ts +0 -2
- package/dist/core/goal-lifecycle-smoke.test.d.ts.map +0 -1
- package/dist/core/goal-lifecycle-smoke.test.js +0 -248
- package/dist/core/goal-lifecycle-smoke.test.js.map +0 -1
- package/dist/core/goal-overhead-harness.d.ts +0 -33
- package/dist/core/goal-overhead-harness.d.ts.map +0 -1
- package/dist/core/goal-overhead-harness.js +0 -268
- package/dist/core/goal-overhead-harness.js.map +0 -1
- package/dist/core/goal-prerequisites.d.ts +0 -23
- package/dist/core/goal-prerequisites.d.ts.map +0 -1
- package/dist/core/goal-prerequisites.js +0 -114
- package/dist/core/goal-prerequisites.js.map +0 -1
- package/dist/core/goal-prerequisites.test.d.ts +0 -2
- package/dist/core/goal-prerequisites.test.d.ts.map +0 -1
- package/dist/core/goal-prerequisites.test.js +0 -154
- package/dist/core/goal-prerequisites.test.js.map +0 -1
- package/dist/core/goal-references.d.ts +0 -14
- package/dist/core/goal-references.d.ts.map +0 -1
- package/dist/core/goal-references.js +0 -153
- package/dist/core/goal-references.js.map +0 -1
- package/dist/core/goal-references.test.d.ts +0 -2
- package/dist/core/goal-references.test.d.ts.map +0 -1
- package/dist/core/goal-references.test.js +0 -77
- package/dist/core/goal-references.test.js.map +0 -1
- package/dist/core/goal-store.d.ts +0 -289
- package/dist/core/goal-store.d.ts.map +0 -1
- package/dist/core/goal-store.js +0 -1156
- package/dist/core/goal-store.js.map +0 -1
- package/dist/core/goal-store.test.d.ts +0 -2
- package/dist/core/goal-store.test.d.ts.map +0 -1
- package/dist/core/goal-store.test.js +0 -530
- package/dist/core/goal-store.test.js.map +0 -1
- package/dist/core/goal-verifier.d.ts +0 -17
- package/dist/core/goal-verifier.d.ts.map +0 -1
- package/dist/core/goal-verifier.js +0 -87
- package/dist/core/goal-verifier.js.map +0 -1
- package/dist/core/goal-verifier.test.d.ts +0 -2
- package/dist/core/goal-verifier.test.d.ts.map +0 -1
- package/dist/core/goal-verifier.test.js +0 -131
- package/dist/core/goal-verifier.test.js.map +0 -1
- package/dist/core/goal-worker-dev-server-lifecycle.test.d.ts +0 -2
- package/dist/core/goal-worker-dev-server-lifecycle.test.d.ts.map +0 -1
- package/dist/core/goal-worker-dev-server-lifecycle.test.js +0 -68
- package/dist/core/goal-worker-dev-server-lifecycle.test.js.map +0 -1
- package/dist/core/goal-worker.d.ts +0 -61
- package/dist/core/goal-worker.d.ts.map +0 -1
- package/dist/core/goal-worker.js +0 -467
- package/dist/core/goal-worker.js.map +0 -1
- package/dist/core/goal-worker.test.d.ts +0 -2
- package/dist/core/goal-worker.test.d.ts.map +0 -1
- package/dist/core/goal-worker.test.js +0 -493
- package/dist/core/goal-worker.test.js.map +0 -1
- package/dist/core/goal-worktree.d.ts +0 -108
- package/dist/core/goal-worktree.d.ts.map +0 -1
- package/dist/core/goal-worktree.js +0 -300
- package/dist/core/goal-worktree.js.map +0 -1
- package/dist/core/goal-worktree.test.d.ts +0 -2
- package/dist/core/goal-worktree.test.d.ts.map +0 -1
- package/dist/core/goal-worktree.test.js +0 -448
- package/dist/core/goal-worktree.test.js.map +0 -1
- package/dist/tools/goal-mode.test.d.ts +0 -2
- package/dist/tools/goal-mode.test.d.ts.map +0 -1
- package/dist/tools/goal-mode.test.js +0 -121
- package/dist/tools/goal-mode.test.js.map +0 -1
- package/dist/tools/goals.d.ts +0 -143
- package/dist/tools/goals.d.ts.map +0 -1
- package/dist/tools/goals.js +0 -1038
- package/dist/tools/goals.js.map +0 -1
- package/dist/tools/goals.test.d.ts +0 -2
- package/dist/tools/goals.test.d.ts.map +0 -1
- package/dist/tools/goals.test.js +0 -1444
- package/dist/tools/goals.test.js.map +0 -1
- package/dist/ui/components/GoalOverlay.d.ts +0 -83
- package/dist/ui/components/GoalOverlay.d.ts.map +0 -1
- package/dist/ui/components/GoalOverlay.js +0 -710
- package/dist/ui/components/GoalOverlay.js.map +0 -1
- package/dist/ui/components/GoalPickerMenu.d.ts +0 -9
- package/dist/ui/components/GoalPickerMenu.d.ts.map +0 -1
- package/dist/ui/components/GoalPickerMenu.js +0 -37
- package/dist/ui/components/GoalPickerMenu.js.map +0 -1
- package/dist/ui/components/GoalStatusBar.d.ts +0 -26
- package/dist/ui/components/GoalStatusBar.d.ts.map +0 -1
- package/dist/ui/components/GoalStatusBar.js +0 -130
- package/dist/ui/components/GoalStatusBar.js.map +0 -1
- package/dist/ui/components/GoalStatusBar.test.d.ts +0 -2
- package/dist/ui/components/GoalStatusBar.test.d.ts.map +0 -1
- package/dist/ui/components/GoalStatusBar.test.js +0 -17
- package/dist/ui/components/GoalStatusBar.test.js.map +0 -1
- package/dist/ui/goal-events.d.ts +0 -125
- package/dist/ui/goal-events.d.ts.map +0 -1
- package/dist/ui/goal-events.js +0 -407
- package/dist/ui/goal-events.js.map +0 -1
- package/dist/ui/goal-events.test.d.ts +0 -2
- package/dist/ui/goal-events.test.d.ts.map +0 -1
- package/dist/ui/goal-events.test.js +0 -416
- package/dist/ui/goal-events.test.js.map +0 -1
- package/dist/ui/goal-lifecycle-orchestration.test.d.ts +0 -2
- package/dist/ui/goal-lifecycle-orchestration.test.d.ts.map +0 -1
- package/dist/ui/goal-lifecycle-orchestration.test.js +0 -555
- package/dist/ui/goal-lifecycle-orchestration.test.js.map +0 -1
- package/dist/ui/goal-overlay.test.d.ts +0 -2
- package/dist/ui/goal-overlay.test.d.ts.map +0 -1
- package/dist/ui/goal-overlay.test.js +0 -384
- package/dist/ui/goal-overlay.test.js.map +0 -1
- package/dist/ui/goal-progress.d.ts +0 -31
- package/dist/ui/goal-progress.d.ts.map +0 -1
- package/dist/ui/goal-progress.js +0 -149
- package/dist/ui/goal-progress.js.map +0 -1
- package/dist/ui/goal-run-helpers.d.ts +0 -12
- package/dist/ui/goal-run-helpers.d.ts.map +0 -1
- package/dist/ui/goal-run-helpers.js +0 -42
- package/dist/ui/goal-run-helpers.js.map +0 -1
- package/dist/ui/goal-status-bar.test.d.ts +0 -2
- package/dist/ui/goal-status-bar.test.d.ts.map +0 -1
- package/dist/ui/goal-status-bar.test.js +0 -174
- package/dist/ui/goal-status-bar.test.js.map +0 -1
- package/dist/ui/goal-summary.d.ts +0 -14
- package/dist/ui/goal-summary.d.ts.map +0 -1
- package/dist/ui/goal-summary.js +0 -194
- package/dist/ui/goal-summary.js.map +0 -1
- package/dist/ui/hooks/useGoalOrchestration.d.ts +0 -82
- package/dist/ui/hooks/useGoalOrchestration.d.ts.map +0 -1
- package/dist/ui/hooks/useGoalOrchestration.js +0 -863
- package/dist/ui/hooks/useGoalOrchestration.js.map +0 -1
- package/dist/ui/hooks/useGoalPickerController.d.ts +0 -22
- package/dist/ui/hooks/useGoalPickerController.d.ts.map +0 -1
- package/dist/ui/hooks/useGoalPickerController.js +0 -35
- package/dist/ui/hooks/useGoalPickerController.js.map +0 -1
- package/dist/ui/prompt-routing.test.d.ts +0 -2
- package/dist/ui/prompt-routing.test.d.ts.map +0 -1
- package/dist/ui/prompt-routing.test.js +0 -48
- package/dist/ui/prompt-routing.test.js.map +0 -1
- package/dist/ui/transcript/GoalRows.d.ts +0 -10
- package/dist/ui/transcript/GoalRows.d.ts.map +0 -1
- package/dist/ui/transcript/GoalRows.js +0 -35
- package/dist/ui/transcript/GoalRows.js.map +0 -1
package/dist/core/goal-store.js
DELETED
|
@@ -1,1156 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { mkdir, open, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { basename, join, resolve } from "node:path";
|
|
6
|
-
export function foldGoalTaskIntegration(value) {
|
|
7
|
-
return value === "manual" ? "manual" : "candidate";
|
|
8
|
-
}
|
|
9
|
-
const GOALS_BASE_ENV = "GG_GOALS_BASE";
|
|
10
|
-
const DEFAULT_PROJECT_DIR_NAME = "projects";
|
|
11
|
-
let writeQueue = Promise.resolve();
|
|
12
|
-
function goalsBaseDir() {
|
|
13
|
-
return process.env[GOALS_BASE_ENV] ?? join(homedir(), ".gg", "goals", DEFAULT_PROJECT_DIR_NAME);
|
|
14
|
-
}
|
|
15
|
-
export function normalizeProjectPath(cwd) {
|
|
16
|
-
return resolve(cwd);
|
|
17
|
-
}
|
|
18
|
-
function nowIso() {
|
|
19
|
-
return new Date().toISOString();
|
|
20
|
-
}
|
|
21
|
-
function mergeGoalTasks(existing, input) {
|
|
22
|
-
if (!input)
|
|
23
|
-
return existing;
|
|
24
|
-
const byId = new Map(input.map((task) => [task.id, task]));
|
|
25
|
-
const merged = existing.map((task) => {
|
|
26
|
-
const next = byId.get(task.id);
|
|
27
|
-
if (!next)
|
|
28
|
-
return task;
|
|
29
|
-
return {
|
|
30
|
-
...task,
|
|
31
|
-
...next,
|
|
32
|
-
status: task.status !== next.status || task.attempts > next.attempts ? task.status : next.status,
|
|
33
|
-
attempts: Math.max(task.attempts, next.attempts),
|
|
34
|
-
workerId: task.workerId ?? next.workerId,
|
|
35
|
-
dependsOn: task.dependsOn ?? next.dependsOn,
|
|
36
|
-
parallelGroup: task.parallelGroup ?? next.parallelGroup,
|
|
37
|
-
expectedChangedScope: task.expectedChangedScope ?? next.expectedChangedScope,
|
|
38
|
-
integration: task.integration ?? next.integration,
|
|
39
|
-
verification: task.verification ?? next.verification,
|
|
40
|
-
lastSummary: task.lastSummary ?? next.lastSummary,
|
|
41
|
-
};
|
|
42
|
-
});
|
|
43
|
-
for (const task of input) {
|
|
44
|
-
if (!existing.some((item) => item.id === task.id))
|
|
45
|
-
merged.push(task);
|
|
46
|
-
}
|
|
47
|
-
return merged;
|
|
48
|
-
}
|
|
49
|
-
function mergeGoalEvidence(existing, input) {
|
|
50
|
-
if (!input)
|
|
51
|
-
return existing;
|
|
52
|
-
const byId = new Map(existing.map((item) => [item.id, item]));
|
|
53
|
-
const merged = [...existing];
|
|
54
|
-
for (const item of input) {
|
|
55
|
-
if (!byId.has(item.id))
|
|
56
|
-
merged.push(item);
|
|
57
|
-
}
|
|
58
|
-
return merged;
|
|
59
|
-
}
|
|
60
|
-
function mergeGoalReferences(existing, input) {
|
|
61
|
-
if (!input)
|
|
62
|
-
return existing;
|
|
63
|
-
const merged = [...existing];
|
|
64
|
-
const seen = new Set(existing
|
|
65
|
-
.map((item) => item.id)
|
|
66
|
-
.concat(existing.map((item) => `${item.kind}:${item.value ?? item.path ?? item.content ?? item.label}`)));
|
|
67
|
-
for (const item of input) {
|
|
68
|
-
const identity = `${item.kind}:${item.value ?? item.path ?? item.content ?? item.label}`;
|
|
69
|
-
if (seen.has(item.id) || seen.has(identity))
|
|
70
|
-
continue;
|
|
71
|
-
seen.add(item.id);
|
|
72
|
-
seen.add(identity);
|
|
73
|
-
merged.push(item);
|
|
74
|
-
}
|
|
75
|
-
return merged;
|
|
76
|
-
}
|
|
77
|
-
function isObject(value) {
|
|
78
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
-
}
|
|
80
|
-
function stringArray(value) {
|
|
81
|
-
return Array.isArray(value)
|
|
82
|
-
? value.filter((item) => typeof item === "string")
|
|
83
|
-
: [];
|
|
84
|
-
}
|
|
85
|
-
function optionalString(value) {
|
|
86
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
87
|
-
}
|
|
88
|
-
function isRunStatus(value) {
|
|
89
|
-
return (value === "draft" ||
|
|
90
|
-
value === "blocked" ||
|
|
91
|
-
value === "ready" ||
|
|
92
|
-
value === "running" ||
|
|
93
|
-
value === "verifying" ||
|
|
94
|
-
value === "passed" ||
|
|
95
|
-
value === "failed" ||
|
|
96
|
-
value === "paused");
|
|
97
|
-
}
|
|
98
|
-
function isTaskStatus(value) {
|
|
99
|
-
return (value === "pending" ||
|
|
100
|
-
value === "running" ||
|
|
101
|
-
value === "verifying" ||
|
|
102
|
-
value === "done" ||
|
|
103
|
-
value === "failed" ||
|
|
104
|
-
value === "blocked");
|
|
105
|
-
}
|
|
106
|
-
function isTaskIntegration(value) {
|
|
107
|
-
return value === "candidate" || value === "manual";
|
|
108
|
-
}
|
|
109
|
-
function isPrerequisiteStatus(value) {
|
|
110
|
-
return value === "unknown" || value === "met" || value === "missing";
|
|
111
|
-
}
|
|
112
|
-
function isPrerequisiteKind(value) {
|
|
113
|
-
return value === "local" || value === "external";
|
|
114
|
-
}
|
|
115
|
-
function isEvidenceKind(value) {
|
|
116
|
-
return (value === "log" ||
|
|
117
|
-
value === "command" ||
|
|
118
|
-
value === "screenshot" ||
|
|
119
|
-
value === "file" ||
|
|
120
|
-
value === "summary");
|
|
121
|
-
}
|
|
122
|
-
function isEvidenceMechanism(value) {
|
|
123
|
-
return (value === "command" ||
|
|
124
|
-
value === "test" ||
|
|
125
|
-
value === "script" ||
|
|
126
|
-
value === "fixture" ||
|
|
127
|
-
value === "log" ||
|
|
128
|
-
value === "screenshot" ||
|
|
129
|
-
value === "video" ||
|
|
130
|
-
value === "browser" ||
|
|
131
|
-
value === "device" ||
|
|
132
|
-
value === "source" ||
|
|
133
|
-
value === "file" ||
|
|
134
|
-
value === "manual");
|
|
135
|
-
}
|
|
136
|
-
function isGoalReferenceKind(value) {
|
|
137
|
-
return (value === "prompt" ||
|
|
138
|
-
value === "url" ||
|
|
139
|
-
value === "repo" ||
|
|
140
|
-
value === "file" ||
|
|
141
|
-
value === "image" ||
|
|
142
|
-
value === "text");
|
|
143
|
-
}
|
|
144
|
-
function isEvidencePlanStatus(value) {
|
|
145
|
-
return value === "planned" || value === "ready" || value === "blocked";
|
|
146
|
-
}
|
|
147
|
-
function isVerificationStatus(value) {
|
|
148
|
-
return value === "pass" || value === "fail" || value === "unknown";
|
|
149
|
-
}
|
|
150
|
-
function normalizeVerification(value) {
|
|
151
|
-
if (!isObject(value))
|
|
152
|
-
return undefined;
|
|
153
|
-
return {
|
|
154
|
-
status: isVerificationStatus(value.status) ? value.status : "unknown",
|
|
155
|
-
summary: typeof value.summary === "string" ? value.summary : "",
|
|
156
|
-
...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
|
|
157
|
-
...(typeof value.exitCode === "number" ? { exitCode: value.exitCode } : {}),
|
|
158
|
-
...(optionalString(value.outputPath) ? { outputPath: optionalString(value.outputPath) } : {}),
|
|
159
|
-
checkedAt: typeof value.checkedAt === "string" ? value.checkedAt : nowIso(),
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function normalizePrerequisite(value) {
|
|
163
|
-
if (!isObject(value))
|
|
164
|
-
return null;
|
|
165
|
-
const label = typeof value.label === "string" ? value.label : "Prerequisite";
|
|
166
|
-
return {
|
|
167
|
-
id: typeof value.id === "string" ? value.id : randomUUID(),
|
|
168
|
-
label,
|
|
169
|
-
status: isPrerequisiteStatus(value.status) ? value.status : "unknown",
|
|
170
|
-
...(isPrerequisiteKind(value.kind) ? { kind: value.kind } : {}),
|
|
171
|
-
...(optionalString(value.checkCommand)
|
|
172
|
-
? { checkCommand: optionalString(value.checkCommand) }
|
|
173
|
-
: {}),
|
|
174
|
-
...(optionalString(value.instructions)
|
|
175
|
-
? { instructions: optionalString(value.instructions) }
|
|
176
|
-
: {}),
|
|
177
|
-
...(optionalString(value.evidence) ? { evidence: optionalString(value.evidence) } : {}),
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
function normalizeHarnessItem(value) {
|
|
181
|
-
if (!isObject(value))
|
|
182
|
-
return null;
|
|
183
|
-
const label = typeof value.label === "string" ? value.label : "Harness";
|
|
184
|
-
return {
|
|
185
|
-
id: typeof value.id === "string" ? value.id : randomUUID(),
|
|
186
|
-
label,
|
|
187
|
-
...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
|
|
188
|
-
...(optionalString(value.path) ? { path: optionalString(value.path) } : {}),
|
|
189
|
-
...(optionalString(value.description)
|
|
190
|
-
? { description: optionalString(value.description) }
|
|
191
|
-
: {}),
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function normalizeEvidencePlanItem(value) {
|
|
195
|
-
if (!isObject(value))
|
|
196
|
-
return null;
|
|
197
|
-
const label = typeof value.label === "string" ? value.label : "Evidence path";
|
|
198
|
-
const description = typeof value.description === "string" ? value.description : label;
|
|
199
|
-
return {
|
|
200
|
-
id: typeof value.id === "string" ? value.id : randomUUID(),
|
|
201
|
-
label,
|
|
202
|
-
mechanism: isEvidenceMechanism(value.mechanism) ? value.mechanism : "command",
|
|
203
|
-
description,
|
|
204
|
-
status: isEvidencePlanStatus(value.status) ? value.status : "planned",
|
|
205
|
-
...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
|
|
206
|
-
...(optionalString(value.path) ? { path: optionalString(value.path) } : {}),
|
|
207
|
-
...(optionalString(value.instructions)
|
|
208
|
-
? { instructions: optionalString(value.instructions) }
|
|
209
|
-
: {}),
|
|
210
|
-
...(optionalString(value.evidence) ? { evidence: optionalString(value.evidence) } : {}),
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function normalizeReference(value) {
|
|
214
|
-
if (!isObject(value))
|
|
215
|
-
return null;
|
|
216
|
-
const label = typeof value.label === "string" ? value.label : "Goal reference";
|
|
217
|
-
return {
|
|
218
|
-
id: typeof value.id === "string" ? value.id : randomUUID(),
|
|
219
|
-
kind: isGoalReferenceKind(value.kind) ? value.kind : "text",
|
|
220
|
-
label,
|
|
221
|
-
...(optionalString(value.value) ? { value: optionalString(value.value) } : {}),
|
|
222
|
-
...(optionalString(value.path) ? { path: optionalString(value.path) } : {}),
|
|
223
|
-
...(optionalString(value.mediaType) ? { mediaType: optionalString(value.mediaType) } : {}),
|
|
224
|
-
...(optionalString(value.description)
|
|
225
|
-
? { description: optionalString(value.description) }
|
|
226
|
-
: {}),
|
|
227
|
-
...(optionalString(value.content) ? { content: optionalString(value.content) } : {}),
|
|
228
|
-
...(optionalString(value.source) ? { source: optionalString(value.source) } : {}),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function normalizeTaskWorktree(value) {
|
|
232
|
-
if (!isObject(value))
|
|
233
|
-
return undefined;
|
|
234
|
-
const baseRef = optionalString(value.baseRef);
|
|
235
|
-
const branchName = optionalString(value.branchName);
|
|
236
|
-
const worktreePath = optionalString(value.path);
|
|
237
|
-
const rawStatus = value.status;
|
|
238
|
-
const status = rawStatus === "planned" || rawStatus === "created" || rawStatus === "failed"
|
|
239
|
-
? rawStatus
|
|
240
|
-
: undefined;
|
|
241
|
-
if (!baseRef || !branchName || !worktreePath || !status)
|
|
242
|
-
return undefined;
|
|
243
|
-
return {
|
|
244
|
-
baseRef,
|
|
245
|
-
branchName,
|
|
246
|
-
path: worktreePath,
|
|
247
|
-
status,
|
|
248
|
-
...(optionalString(value.error) ? { error: optionalString(value.error) } : {}),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
function normalizeTaskCandidate(value) {
|
|
252
|
-
if (!isObject(value))
|
|
253
|
-
return undefined;
|
|
254
|
-
const baseRef = optionalString(value.baseRef);
|
|
255
|
-
const headSha = optionalString(value.headSha);
|
|
256
|
-
const branchName = optionalString(value.branchName);
|
|
257
|
-
if (!baseRef || !headSha || !branchName)
|
|
258
|
-
return undefined;
|
|
259
|
-
return {
|
|
260
|
-
baseRef,
|
|
261
|
-
headSha,
|
|
262
|
-
branchName,
|
|
263
|
-
changedFiles: stringArray(value.changedFiles),
|
|
264
|
-
committed: value.committed !== false,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function normalizeTask(value) {
|
|
268
|
-
if (!isObject(value))
|
|
269
|
-
return null;
|
|
270
|
-
const title = typeof value.title === "string" ? value.title : "Goal task";
|
|
271
|
-
const prompt = typeof value.prompt === "string" ? value.prompt : title;
|
|
272
|
-
return {
|
|
273
|
-
id: typeof value.id === "string" ? value.id : randomUUID(),
|
|
274
|
-
title,
|
|
275
|
-
prompt,
|
|
276
|
-
status: isTaskStatus(value.status) ? value.status : "pending",
|
|
277
|
-
...(optionalString(value.workerId) ? { workerId: optionalString(value.workerId) } : {}),
|
|
278
|
-
attempts: typeof value.attempts === "number" && value.attempts >= 0 ? value.attempts : 0,
|
|
279
|
-
...(stringArray(value.dependsOn).length > 0 ? { dependsOn: stringArray(value.dependsOn) } : {}),
|
|
280
|
-
...(optionalString(value.parallelGroup)
|
|
281
|
-
? { parallelGroup: optionalString(value.parallelGroup) }
|
|
282
|
-
: {}),
|
|
283
|
-
...(stringArray(value.expectedChangedScope).length > 0
|
|
284
|
-
? { expectedChangedScope: stringArray(value.expectedChangedScope) }
|
|
285
|
-
: {}),
|
|
286
|
-
integration: foldGoalTaskIntegration(isTaskIntegration(value.integration)
|
|
287
|
-
? value.integration
|
|
288
|
-
: typeof value.mergeStrategy === "string"
|
|
289
|
-
? value.mergeStrategy
|
|
290
|
-
: undefined),
|
|
291
|
-
...(normalizeTaskWorktree(value.worktree)
|
|
292
|
-
? { worktree: normalizeTaskWorktree(value.worktree) }
|
|
293
|
-
: {}),
|
|
294
|
-
...(normalizeTaskCandidate(value.candidate)
|
|
295
|
-
? { candidate: normalizeTaskCandidate(value.candidate) }
|
|
296
|
-
: {}),
|
|
297
|
-
...(normalizeVerification(value.verification)
|
|
298
|
-
? { verification: normalizeVerification(value.verification) }
|
|
299
|
-
: {}),
|
|
300
|
-
...(optionalString(value.lastSummary)
|
|
301
|
-
? { lastSummary: optionalString(value.lastSummary) }
|
|
302
|
-
: {}),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
function normalizeEvidence(value) {
|
|
306
|
-
if (!isObject(value))
|
|
307
|
-
return null;
|
|
308
|
-
const label = typeof value.label === "string" ? value.label : "Evidence";
|
|
309
|
-
return {
|
|
310
|
-
id: typeof value.id === "string" ? value.id : randomUUID(),
|
|
311
|
-
kind: isEvidenceKind(value.kind) ? value.kind : "summary",
|
|
312
|
-
label,
|
|
313
|
-
...(optionalString(value.path) ? { path: optionalString(value.path) } : {}),
|
|
314
|
-
...(optionalString(value.content) ? { content: optionalString(value.content) } : {}),
|
|
315
|
-
createdAt: typeof value.createdAt === "string" ? value.createdAt : nowIso(),
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
function normalizeVerifier(value) {
|
|
319
|
-
if (!isObject(value))
|
|
320
|
-
return undefined;
|
|
321
|
-
const description = typeof value.description === "string" ? value.description : "Goal verifier";
|
|
322
|
-
return {
|
|
323
|
-
description,
|
|
324
|
-
...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
|
|
325
|
-
...(optionalString(value.cwd) ? { cwd: optionalString(value.cwd) } : {}),
|
|
326
|
-
...(normalizeVerification(value.lastResult)
|
|
327
|
-
? { lastResult: normalizeVerification(value.lastResult) }
|
|
328
|
-
: {}),
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
function isIntegrationStatus(value) {
|
|
332
|
-
return value === "none" || value === "applied" || value === "committed";
|
|
333
|
-
}
|
|
334
|
-
function normalizeIntegration(value) {
|
|
335
|
-
if (!isObject(value))
|
|
336
|
-
return undefined;
|
|
337
|
-
if (!isIntegrationStatus(value.status))
|
|
338
|
-
return undefined;
|
|
339
|
-
return {
|
|
340
|
-
status: value.status,
|
|
341
|
-
...(optionalString(value.headSha) ? { headSha: optionalString(value.headSha) } : {}),
|
|
342
|
-
...(optionalString(value.baseRef) ? { baseRef: optionalString(value.baseRef) } : {}),
|
|
343
|
-
...(stringArray(value.files).length > 0 ? { files: stringArray(value.files) } : {}),
|
|
344
|
-
updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : nowIso(),
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Back-compat shim: infer the typed integration state from legacy evidence
|
|
349
|
-
* labels for runs persisted before {@link GoalIntegrationState} existed. This is
|
|
350
|
-
* the ONLY place label-matching is allowed to survive; it migrates old runs once.
|
|
351
|
-
*/
|
|
352
|
-
function inferIntegrationFromEvidence(evidence) {
|
|
353
|
-
const committed = evidence.find((item) => item.label === "Integrated Goal changes committed");
|
|
354
|
-
const applied = evidence.find((item) => item.label === "Integrated worktree applied to main" ||
|
|
355
|
-
item.label === "Goal decision: apply_integration_to_main");
|
|
356
|
-
if (!committed && !applied)
|
|
357
|
-
return undefined;
|
|
358
|
-
const source = committed ?? applied;
|
|
359
|
-
return {
|
|
360
|
-
status: committed ? "committed" : "applied",
|
|
361
|
-
updatedAt: source.createdAt,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Back-compat shim: infer the most recent non-audit worker completion time from
|
|
366
|
-
* legacy "Worker <id> done/failed" evidence for runs persisted before
|
|
367
|
-
* {@link GoalRun.lastSubstantiveWorkerAt} existed.
|
|
368
|
-
*/
|
|
369
|
-
function inferLastSubstantiveWorkerAt(evidence) {
|
|
370
|
-
const workerEvidence = evidence
|
|
371
|
-
.filter((item) => /^Worker\s+\S+\s+(done|failed)$/.test(item.label))
|
|
372
|
-
.map((item) => item.createdAt)
|
|
373
|
-
.sort((a, b) => b.localeCompare(a));
|
|
374
|
-
return workerEvidence[0];
|
|
375
|
-
}
|
|
376
|
-
function normalizeCompletionAudit(value) {
|
|
377
|
-
if (!isObject(value))
|
|
378
|
-
return undefined;
|
|
379
|
-
return {
|
|
380
|
-
status: isVerificationStatus(value.status) ? value.status : "unknown",
|
|
381
|
-
summary: typeof value.summary === "string" ? value.summary : "",
|
|
382
|
-
checkedAt: typeof value.checkedAt === "string" ? value.checkedAt : nowIso(),
|
|
383
|
-
...(optionalString(value.verifierCheckedAt)
|
|
384
|
-
? { verifierCheckedAt: optionalString(value.verifierCheckedAt) }
|
|
385
|
-
: {}),
|
|
386
|
-
...(optionalString(value.outputPath) ? { outputPath: optionalString(value.outputPath) } : {}),
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
function normalizeRun(value, fallbackProjectPath) {
|
|
390
|
-
if (!isObject(value))
|
|
391
|
-
return null;
|
|
392
|
-
const title = typeof value.title === "string" ? value.title : "Untitled goal";
|
|
393
|
-
const goal = typeof value.goal === "string" ? value.goal : title;
|
|
394
|
-
const createdAt = typeof value.createdAt === "string" ? value.createdAt : nowIso();
|
|
395
|
-
const projectPath = typeof value.projectPath === "string" ? value.projectPath : fallbackProjectPath;
|
|
396
|
-
const prerequisites = Array.isArray(value.prerequisites)
|
|
397
|
-
? value.prerequisites
|
|
398
|
-
.map(normalizePrerequisite)
|
|
399
|
-
.filter((item) => !!item)
|
|
400
|
-
: [];
|
|
401
|
-
const tasks = Array.isArray(value.tasks)
|
|
402
|
-
? value.tasks.map(normalizeTask).filter((item) => !!item)
|
|
403
|
-
: [];
|
|
404
|
-
const computedStatus = deriveRunnableStatus(isRunStatus(value.status) ? value.status : "draft", prerequisites);
|
|
405
|
-
const evidence = Array.isArray(value.evidence)
|
|
406
|
-
? value.evidence.map(normalizeEvidence).filter((item) => !!item)
|
|
407
|
-
: [];
|
|
408
|
-
const integration = normalizeIntegration(value.integration) ?? inferIntegrationFromEvidence(evidence);
|
|
409
|
-
const lastSubstantiveWorkerAt = optionalString(value.lastSubstantiveWorkerAt) ?? inferLastSubstantiveWorkerAt(evidence);
|
|
410
|
-
return {
|
|
411
|
-
id: typeof value.id === "string" ? value.id : randomUUID(),
|
|
412
|
-
title,
|
|
413
|
-
goal,
|
|
414
|
-
status: computedStatus,
|
|
415
|
-
createdAt,
|
|
416
|
-
updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : createdAt,
|
|
417
|
-
projectPath,
|
|
418
|
-
successCriteria: stringArray(value.successCriteria),
|
|
419
|
-
prerequisites,
|
|
420
|
-
harness: Array.isArray(value.harness)
|
|
421
|
-
? value.harness.map(normalizeHarnessItem).filter((item) => !!item)
|
|
422
|
-
: [],
|
|
423
|
-
evidencePlan: Array.isArray(value.evidencePlan)
|
|
424
|
-
? value.evidencePlan
|
|
425
|
-
.map(normalizeEvidencePlanItem)
|
|
426
|
-
.filter((item) => !!item)
|
|
427
|
-
: [],
|
|
428
|
-
references: Array.isArray(value.references)
|
|
429
|
-
? value.references.map(normalizeReference).filter((item) => !!item)
|
|
430
|
-
: [],
|
|
431
|
-
tasks,
|
|
432
|
-
evidence,
|
|
433
|
-
...(normalizeVerifier(value.verifier) ? { verifier: normalizeVerifier(value.verifier) } : {}),
|
|
434
|
-
...(normalizeCompletionAudit(value.completionAudit)
|
|
435
|
-
? { completionAudit: normalizeCompletionAudit(value.completionAudit) }
|
|
436
|
-
: {}),
|
|
437
|
-
...(integration ? { integration } : {}),
|
|
438
|
-
...(lastSubstantiveWorkerAt ? { lastSubstantiveWorkerAt } : {}),
|
|
439
|
-
blockers: dedupeGoalBlockers(stringArray(value.blockers)),
|
|
440
|
-
...(optionalString(value.activeWorkerId)
|
|
441
|
-
? { activeWorkerId: optionalString(value.activeWorkerId) }
|
|
442
|
-
: {}),
|
|
443
|
-
...(optionalString(value.continueRequestedAt)
|
|
444
|
-
? { continueRequestedAt: optionalString(value.continueRequestedAt) }
|
|
445
|
-
: {}),
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
function sortNewestFirst(runs) {
|
|
449
|
-
return [...runs].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
450
|
-
}
|
|
451
|
-
function isActiveGoalRun(run) {
|
|
452
|
-
return (run.status === "running" ||
|
|
453
|
-
run.status === "verifying" ||
|
|
454
|
-
run.activeWorkerId !== undefined ||
|
|
455
|
-
run.tasks.some((task) => task.status === "running" || task.status === "verifying"));
|
|
456
|
-
}
|
|
457
|
-
function omittedActiveGoalRuns(previousRuns, nextRuns) {
|
|
458
|
-
const nextIds = new Set(nextRuns.map((run) => run.id));
|
|
459
|
-
return previousRuns.filter((run) => isActiveGoalRun(run) && !nextIds.has(run.id));
|
|
460
|
-
}
|
|
461
|
-
export function dedupeGoalBlockers(blockers) {
|
|
462
|
-
return Array.from(new Set(blockers.map((item) => item.trim()).filter(Boolean)));
|
|
463
|
-
}
|
|
464
|
-
export function appendGoalBlockers(blockers, nextBlockers) {
|
|
465
|
-
const additions = typeof nextBlockers === "string" ? [nextBlockers] : (nextBlockers ?? []);
|
|
466
|
-
return dedupeGoalBlockers([...blockers, ...additions]);
|
|
467
|
-
}
|
|
468
|
-
function deriveRunnableStatus(requestedStatus, prerequisites) {
|
|
469
|
-
if (requestedStatus === "passed" ||
|
|
470
|
-
requestedStatus === "failed" ||
|
|
471
|
-
requestedStatus === "paused") {
|
|
472
|
-
return requestedStatus;
|
|
473
|
-
}
|
|
474
|
-
if (hasBlockingGoalPrerequisites(prerequisites))
|
|
475
|
-
return "blocked";
|
|
476
|
-
return requestedStatus;
|
|
477
|
-
}
|
|
478
|
-
function enqueueWrite(fn) {
|
|
479
|
-
const result = writeQueue.then(fn);
|
|
480
|
-
writeQueue = result.then(() => { }, () => { });
|
|
481
|
-
return result;
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Persist runs to disk. MUST be called while holding the goal-store lock (it is
|
|
485
|
-
* not reentrant). Re-reads on-disk state for the omitted-active-run guard, then
|
|
486
|
-
* writes goals.json + meta.json + journals atomically.
|
|
487
|
-
*/
|
|
488
|
-
async function writeGoalRunsFileCore(normalizedCwd, runs) {
|
|
489
|
-
const dir = projectDir(normalizedCwd);
|
|
490
|
-
await mkdir(dir, { recursive: true });
|
|
491
|
-
const goalsPath = join(dir, "goals.json");
|
|
492
|
-
const existingRuns = await readGoalRunsFile(normalizedCwd);
|
|
493
|
-
const omittedActive = omittedActiveGoalRuns(existingRuns, runs);
|
|
494
|
-
if (omittedActive.length > 0) {
|
|
495
|
-
const timestamp = nowIso();
|
|
496
|
-
const rejectedIds = new Set(omittedActive.map((run) => run.id));
|
|
497
|
-
const repairedRuns = existingRuns.map((run) => rejectedIds.has(run.id)
|
|
498
|
-
? {
|
|
499
|
-
...run,
|
|
500
|
-
evidence: [
|
|
501
|
-
...run.evidence,
|
|
502
|
-
createGoalEvidence({
|
|
503
|
-
kind: "summary",
|
|
504
|
-
label: "Goal store write rejected",
|
|
505
|
-
content: "Rejected an attempted Goal overwrite that omitted active work; preserving existing durable state.",
|
|
506
|
-
createdAt: timestamp,
|
|
507
|
-
}),
|
|
508
|
-
],
|
|
509
|
-
updatedAt: timestamp,
|
|
510
|
-
}
|
|
511
|
-
: run);
|
|
512
|
-
await atomicWriteJson(goalsPath, sortNewestFirst(repairedRuns));
|
|
513
|
-
await Promise.all(repairedRuns.map((run) => writeGoalProgressJournalFromRun(normalizedCwd, run)));
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const sorted = sortNewestFirst([...runs]);
|
|
517
|
-
await atomicWriteJson(goalsPath, sorted);
|
|
518
|
-
await atomicWriteJson(join(dir, "meta.json"), {
|
|
519
|
-
path: normalizedCwd,
|
|
520
|
-
name: basename(normalizedCwd),
|
|
521
|
-
});
|
|
522
|
-
await Promise.all(sorted.map((run) => writeGoalProgressJournalFromRun(normalizedCwd, run)));
|
|
523
|
-
}
|
|
524
|
-
async function writeGoalRunsFile(cwd, runs) {
|
|
525
|
-
const normalizedCwd = normalizeProjectPath(cwd);
|
|
526
|
-
const dir = projectDir(normalizedCwd);
|
|
527
|
-
await withGoalStoreLock(dir, () => writeGoalRunsFileCore(normalizedCwd, runs));
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Cross-process-atomic read-modify-write. Reads the current runs INSIDE the
|
|
531
|
-
* store lock, applies `mutate`, and writes the result before releasing the lock
|
|
532
|
-
* so concurrent processes (the parent orchestrator and worker subprocesses both
|
|
533
|
-
* writing via the goals tool) can never lose each other's field-level updates.
|
|
534
|
-
*/
|
|
535
|
-
async function mutateGoalRunsLocked(cwd, mutate) {
|
|
536
|
-
return enqueueWrite(async () => {
|
|
537
|
-
const normalizedCwd = normalizeProjectPath(cwd);
|
|
538
|
-
const dir = projectDir(normalizedCwd);
|
|
539
|
-
return withGoalStoreLock(dir, async () => {
|
|
540
|
-
const runs = await readGoalRunsFile(normalizedCwd);
|
|
541
|
-
const outcome = await mutate(runs);
|
|
542
|
-
if (outcome.write !== false) {
|
|
543
|
-
await writeGoalRunsFileCore(normalizedCwd, outcome.runs);
|
|
544
|
-
}
|
|
545
|
-
return outcome.result;
|
|
546
|
-
});
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
const GOAL_STORE_LOCK_WAIT_MS = 10_000;
|
|
550
|
-
const GOAL_STORE_STALE_LOCK_MS = 30_000;
|
|
551
|
-
function isProcessAlive(pid) {
|
|
552
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
553
|
-
return false;
|
|
554
|
-
try {
|
|
555
|
-
process.kill(pid, 0);
|
|
556
|
-
return true;
|
|
557
|
-
}
|
|
558
|
-
catch (error) {
|
|
559
|
-
const code = error.code;
|
|
560
|
-
return code === "EPERM";
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
async function shouldRemoveGoalStoreLock(lockPath) {
|
|
564
|
-
let lockStats;
|
|
565
|
-
try {
|
|
566
|
-
lockStats = await stat(lockPath);
|
|
567
|
-
}
|
|
568
|
-
catch {
|
|
569
|
-
return false;
|
|
570
|
-
}
|
|
571
|
-
if (Date.now() - lockStats.mtimeMs > GOAL_STORE_STALE_LOCK_MS)
|
|
572
|
-
return true;
|
|
573
|
-
try {
|
|
574
|
-
const [pidLine] = (await readFile(lockPath, "utf-8")).split("\n");
|
|
575
|
-
const pid = Number(pidLine?.trim());
|
|
576
|
-
return !isProcessAlive(pid);
|
|
577
|
-
}
|
|
578
|
-
catch {
|
|
579
|
-
return false;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
async function withGoalStoreLock(dir, fn) {
|
|
583
|
-
await mkdir(dir, { recursive: true });
|
|
584
|
-
const lockPath = join(dir, "goals.lock");
|
|
585
|
-
const deadline = Date.now() + GOAL_STORE_LOCK_WAIT_MS;
|
|
586
|
-
for (;;) {
|
|
587
|
-
let handle;
|
|
588
|
-
try {
|
|
589
|
-
handle = await open(lockPath, "wx");
|
|
590
|
-
await handle.writeFile(`${process.pid}\n${new Date().toISOString()}\n`, "utf-8");
|
|
591
|
-
await handle.close();
|
|
592
|
-
try {
|
|
593
|
-
return await fn();
|
|
594
|
-
}
|
|
595
|
-
finally {
|
|
596
|
-
await rm(lockPath, { force: true });
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
catch (err) {
|
|
600
|
-
await handle?.close().catch(() => undefined);
|
|
601
|
-
if (err.code !== "EEXIST")
|
|
602
|
-
throw err;
|
|
603
|
-
if (await shouldRemoveGoalStoreLock(lockPath)) {
|
|
604
|
-
await rm(lockPath, { force: true });
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
if (Date.now() > deadline)
|
|
608
|
-
throw err;
|
|
609
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
async function atomicWriteJson(path, value) {
|
|
614
|
-
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
615
|
-
await writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
616
|
-
await rename(tmpPath, path);
|
|
617
|
-
}
|
|
618
|
-
async function readGoalRunsFile(cwd) {
|
|
619
|
-
const normalizedCwd = normalizeProjectPath(cwd);
|
|
620
|
-
try {
|
|
621
|
-
const data = await readFile(join(projectDir(normalizedCwd), "goals.json"), "utf-8");
|
|
622
|
-
const parsed = JSON.parse(data);
|
|
623
|
-
if (!Array.isArray(parsed))
|
|
624
|
-
return [];
|
|
625
|
-
return sortNewestFirst(parsed
|
|
626
|
-
.map((item) => normalizeRun(item, normalizedCwd))
|
|
627
|
-
.filter((run) => run !== null));
|
|
628
|
-
}
|
|
629
|
-
catch {
|
|
630
|
-
return [];
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
export function hashPath(cwd) {
|
|
634
|
-
return createHash("sha256").update(normalizeProjectPath(cwd)).digest("hex").slice(0, 16);
|
|
635
|
-
}
|
|
636
|
-
export function projectDir(cwd) {
|
|
637
|
-
return join(goalsBaseDir(), hashPath(cwd));
|
|
638
|
-
}
|
|
639
|
-
async function discoverGoalRunsById(id) {
|
|
640
|
-
try {
|
|
641
|
-
const entries = await readdir(goalsBaseDir(), { withFileTypes: true });
|
|
642
|
-
const matches = [];
|
|
643
|
-
for (const entry of entries) {
|
|
644
|
-
if (!entry.isDirectory())
|
|
645
|
-
continue;
|
|
646
|
-
try {
|
|
647
|
-
const dir = join(goalsBaseDir(), entry.name);
|
|
648
|
-
const meta = await readProjectMeta(dir);
|
|
649
|
-
const fallbackProjectPath = meta?.path ?? dir;
|
|
650
|
-
const data = await readFile(join(dir, "goals.json"), "utf-8");
|
|
651
|
-
const parsed = JSON.parse(data);
|
|
652
|
-
if (!Array.isArray(parsed))
|
|
653
|
-
continue;
|
|
654
|
-
for (const item of parsed) {
|
|
655
|
-
const run = normalizeRun(item, fallbackProjectPath);
|
|
656
|
-
if (run && (run.id === id || run.id.startsWith(id)))
|
|
657
|
-
matches.push(run);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
catch {
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
return sortNewestFirst(matches)[0] ?? null;
|
|
665
|
-
}
|
|
666
|
-
catch {
|
|
667
|
-
return null;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
async function readProjectMeta(dir) {
|
|
671
|
-
try {
|
|
672
|
-
const parsed = JSON.parse(await readFile(join(dir, "meta.json"), "utf-8"));
|
|
673
|
-
return isObject(parsed) && typeof parsed.path === "string" ? { path: parsed.path } : null;
|
|
674
|
-
}
|
|
675
|
-
catch {
|
|
676
|
-
return null;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
export function createGoalTask(input) {
|
|
680
|
-
return {
|
|
681
|
-
id: input.id ?? randomUUID(),
|
|
682
|
-
title: input.title,
|
|
683
|
-
prompt: input.prompt,
|
|
684
|
-
status: input.status ?? "pending",
|
|
685
|
-
...(input.workerId ? { workerId: input.workerId } : {}),
|
|
686
|
-
attempts: input.attempts ?? 0,
|
|
687
|
-
...(input.dependsOn && input.dependsOn.length > 0 ? { dependsOn: input.dependsOn } : {}),
|
|
688
|
-
...(input.parallelGroup ? { parallelGroup: input.parallelGroup } : {}),
|
|
689
|
-
...(input.expectedChangedScope && input.expectedChangedScope.length > 0
|
|
690
|
-
? { expectedChangedScope: input.expectedChangedScope }
|
|
691
|
-
: {}),
|
|
692
|
-
integration: foldGoalTaskIntegration(input.integration ?? input.mergeStrategy),
|
|
693
|
-
...(input.worktree ? { worktree: input.worktree } : {}),
|
|
694
|
-
...(input.candidate ? { candidate: input.candidate } : {}),
|
|
695
|
-
...(input.verification ? { verification: input.verification } : {}),
|
|
696
|
-
...(input.lastSummary ? { lastSummary: input.lastSummary } : {}),
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
export function createGoalEvidence(input) {
|
|
700
|
-
return {
|
|
701
|
-
id: input.id ?? randomUUID(),
|
|
702
|
-
kind: input.kind,
|
|
703
|
-
label: input.label,
|
|
704
|
-
...(input.path ? { path: input.path } : {}),
|
|
705
|
-
...(input.content ? { content: input.content } : {}),
|
|
706
|
-
createdAt: input.createdAt ?? nowIso(),
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
export function createGoalRun(cwd, input) {
|
|
710
|
-
const normalizedCwd = normalizeProjectPath(cwd);
|
|
711
|
-
const timestamp = nowIso();
|
|
712
|
-
const prerequisites = input.prerequisites ?? [];
|
|
713
|
-
const status = deriveRunnableStatus(input.status ?? "draft", prerequisites);
|
|
714
|
-
return {
|
|
715
|
-
id: input.id ?? randomUUID(),
|
|
716
|
-
title: input.title,
|
|
717
|
-
goal: input.goal,
|
|
718
|
-
status,
|
|
719
|
-
createdAt: timestamp,
|
|
720
|
-
updatedAt: timestamp,
|
|
721
|
-
projectPath: normalizedCwd,
|
|
722
|
-
successCriteria: input.successCriteria ?? [],
|
|
723
|
-
prerequisites,
|
|
724
|
-
harness: input.harness ?? [],
|
|
725
|
-
evidencePlan: input.evidencePlan ?? [],
|
|
726
|
-
references: input.references ?? [],
|
|
727
|
-
tasks: input.tasks ?? [],
|
|
728
|
-
evidence: input.evidence ?? [],
|
|
729
|
-
...(input.verifier ? { verifier: input.verifier } : {}),
|
|
730
|
-
...(input.completionAudit ? { completionAudit: input.completionAudit } : {}),
|
|
731
|
-
...(input.integration ? { integration: input.integration } : {}),
|
|
732
|
-
...(input.lastSubstantiveWorkerAt
|
|
733
|
-
? { lastSubstantiveWorkerAt: input.lastSubstantiveWorkerAt }
|
|
734
|
-
: {}),
|
|
735
|
-
blockers: dedupeGoalBlockers(input.blockers ?? []),
|
|
736
|
-
...(input.activeWorkerId ? { activeWorkerId: input.activeWorkerId } : {}),
|
|
737
|
-
...(input.continueRequestedAt ? { continueRequestedAt: input.continueRequestedAt } : {}),
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
export async function loadGoalRuns(cwd) {
|
|
741
|
-
return readGoalRunsFile(cwd);
|
|
742
|
-
}
|
|
743
|
-
export function loadGoalRunsSync(cwd) {
|
|
744
|
-
const normalizedCwd = normalizeProjectPath(cwd);
|
|
745
|
-
try {
|
|
746
|
-
const data = readFileSync(join(projectDir(normalizedCwd), "goals.json"), "utf-8");
|
|
747
|
-
const parsed = JSON.parse(data);
|
|
748
|
-
if (!Array.isArray(parsed))
|
|
749
|
-
return [];
|
|
750
|
-
return sortNewestFirst(parsed
|
|
751
|
-
.map((item) => normalizeRun(item, normalizedCwd))
|
|
752
|
-
.filter((run) => run !== null));
|
|
753
|
-
}
|
|
754
|
-
catch {
|
|
755
|
-
return [];
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
export async function saveGoalRuns(cwd, runs) {
|
|
759
|
-
return enqueueWrite(() => writeGoalRunsFile(cwd, runs));
|
|
760
|
-
}
|
|
761
|
-
export function saveGoalRunsSync(cwd, runs) {
|
|
762
|
-
const normalizedCwd = normalizeProjectPath(cwd);
|
|
763
|
-
const sorted = sortNewestFirst([...runs]);
|
|
764
|
-
writeFileSync(join(projectDir(normalizedCwd), "goals.json"), JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
765
|
-
}
|
|
766
|
-
export async function reconcileActiveGoalRuns(cwd, options = {}) {
|
|
767
|
-
return mutateGoalRunsLocked(cwd, (runs) => {
|
|
768
|
-
const timestamp = nowIso();
|
|
769
|
-
const repairedRunIds = [];
|
|
770
|
-
let evidenceCount = 0;
|
|
771
|
-
const nextRuns = runs.map((run) => {
|
|
772
|
-
let next = run;
|
|
773
|
-
const evidence = [];
|
|
774
|
-
const blockers = new Set(run.blockers);
|
|
775
|
-
let repaired = false;
|
|
776
|
-
if (run.activeWorkerId && !options.isWorkerActive?.(run.activeWorkerId, run)) {
|
|
777
|
-
const workerId = run.activeWorkerId;
|
|
778
|
-
next = { ...next, activeWorkerId: undefined };
|
|
779
|
-
evidence.push(createGoalEvidence({
|
|
780
|
-
kind: "summary",
|
|
781
|
-
label: "Goal worker reconciled",
|
|
782
|
-
content: `Cleared stale activeWorkerId ${workerId}; no in-memory Goal worker was observed after startup/runtime reconciliation.`,
|
|
783
|
-
createdAt: timestamp,
|
|
784
|
-
}));
|
|
785
|
-
repaired = true;
|
|
786
|
-
}
|
|
787
|
-
const tasks = next.tasks.map((task) => {
|
|
788
|
-
if (task.status !== "running" && task.status !== "verifying")
|
|
789
|
-
return task;
|
|
790
|
-
const workerId = task.workerId;
|
|
791
|
-
if (workerId && options.isWorkerActive?.(workerId, run))
|
|
792
|
-
return task;
|
|
793
|
-
repaired = true;
|
|
794
|
-
evidence.push(createGoalEvidence({
|
|
795
|
-
kind: "summary",
|
|
796
|
-
label: "Goal task reconciled",
|
|
797
|
-
content: `Reset stale task "${task.title}" (${task.id}) from ${task.status} to pending; no in-memory worker/verifier was observed.`,
|
|
798
|
-
createdAt: timestamp,
|
|
799
|
-
}));
|
|
800
|
-
return {
|
|
801
|
-
...task,
|
|
802
|
-
status: "pending",
|
|
803
|
-
lastSummary: `Reset from stale ${task.status} state during Goal reconciliation.`,
|
|
804
|
-
};
|
|
805
|
-
});
|
|
806
|
-
next = { ...next, tasks };
|
|
807
|
-
if (next.status === "running") {
|
|
808
|
-
const hasRunningWorker = next.activeWorkerId !== undefined &&
|
|
809
|
-
options.isWorkerActive?.(next.activeWorkerId, run) === true;
|
|
810
|
-
const hasActiveTask = next.tasks.some((task) => (task.status === "running" || task.status === "verifying") &&
|
|
811
|
-
task.workerId !== undefined &&
|
|
812
|
-
options.isWorkerActive?.(task.workerId, run) === true);
|
|
813
|
-
if (!hasRunningWorker && !hasActiveTask) {
|
|
814
|
-
next = { ...next, status: "ready" };
|
|
815
|
-
repaired = true;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
else if (next.status === "verifying" && !options.isVerifierActive?.(next)) {
|
|
819
|
-
const blocker = "Verifier was interrupted; rerun or continue the Goal to verify again.";
|
|
820
|
-
blockers.add(blocker);
|
|
821
|
-
evidence.push(createGoalEvidence({
|
|
822
|
-
kind: "summary",
|
|
823
|
-
label: "Goal verifier reconciled",
|
|
824
|
-
content: "Reset stale verifying state to ready because no in-memory verifier process was observed.",
|
|
825
|
-
createdAt: timestamp,
|
|
826
|
-
}));
|
|
827
|
-
next = { ...next, status: "ready" };
|
|
828
|
-
repaired = true;
|
|
829
|
-
}
|
|
830
|
-
if (!repaired)
|
|
831
|
-
return run;
|
|
832
|
-
repairedRunIds.push(run.id);
|
|
833
|
-
evidenceCount += evidence.length;
|
|
834
|
-
return {
|
|
835
|
-
...next,
|
|
836
|
-
evidence: [...next.evidence, ...evidence],
|
|
837
|
-
blockers: dedupeGoalBlockers([...blockers]),
|
|
838
|
-
updatedAt: timestamp,
|
|
839
|
-
};
|
|
840
|
-
});
|
|
841
|
-
return {
|
|
842
|
-
runs: nextRuns,
|
|
843
|
-
write: repairedRunIds.length > 0,
|
|
844
|
-
result: { runs: sortNewestFirst(nextRuns), repairedRunIds, evidenceCount },
|
|
845
|
-
};
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
export async function upsertGoalRun(cwd, input) {
|
|
849
|
-
return mutateGoalRunsLocked(cwd, (runs) => {
|
|
850
|
-
const existingIndex = input.id ? runs.findIndex((run) => run.id === input.id) : -1;
|
|
851
|
-
const existing = existingIndex >= 0 ? runs[existingIndex] : undefined;
|
|
852
|
-
const merged = existing
|
|
853
|
-
? {
|
|
854
|
-
...existing,
|
|
855
|
-
...input,
|
|
856
|
-
id: existing.id,
|
|
857
|
-
projectPath: normalizeProjectPath(cwd),
|
|
858
|
-
createdAt: existing.createdAt,
|
|
859
|
-
updatedAt: nowIso(),
|
|
860
|
-
successCriteria: input.successCriteria ?? existing.successCriteria,
|
|
861
|
-
prerequisites: input.prerequisites ?? existing.prerequisites,
|
|
862
|
-
harness: input.harness ?? existing.harness,
|
|
863
|
-
evidencePlan: input.evidencePlan ?? existing.evidencePlan,
|
|
864
|
-
references: mergeGoalReferences(existing.references ?? [], input.references),
|
|
865
|
-
tasks: mergeGoalTasks(existing.tasks, input.tasks),
|
|
866
|
-
evidence: mergeGoalEvidence(existing.evidence, input.evidence),
|
|
867
|
-
blockers: input.blockers
|
|
868
|
-
? dedupeGoalBlockers(input.blockers)
|
|
869
|
-
: dedupeGoalBlockers(existing.blockers),
|
|
870
|
-
status: deriveRunnableStatus(input.status ?? existing.status, input.prerequisites ?? existing.prerequisites),
|
|
871
|
-
}
|
|
872
|
-
: createGoalRun(cwd, input);
|
|
873
|
-
const nextRuns = existingIndex >= 0 ? [...runs] : [merged, ...runs];
|
|
874
|
-
if (existingIndex >= 0)
|
|
875
|
-
nextRuns[existingIndex] = merged;
|
|
876
|
-
return { runs: nextRuns, result: merged };
|
|
877
|
-
});
|
|
878
|
-
}
|
|
879
|
-
export async function getGoalRun(cwd, id) {
|
|
880
|
-
const runs = await loadGoalRuns(cwd);
|
|
881
|
-
return (runs.find((run) => run.id === id || run.id.startsWith(id)) ?? (await discoverGoalRunsById(id)));
|
|
882
|
-
}
|
|
883
|
-
export async function getActiveGoalRun(cwd) {
|
|
884
|
-
const runs = await loadGoalRuns(cwd);
|
|
885
|
-
return (runs.find((run) => run.status === "running" || run.status === "verifying") ??
|
|
886
|
-
runs.find((run) => run.status === "ready" || run.status === "blocked") ??
|
|
887
|
-
runs.find((run) => run.status === "draft" || run.status === "paused") ??
|
|
888
|
-
runs[0] ??
|
|
889
|
-
null);
|
|
890
|
-
}
|
|
891
|
-
export async function appendGoalDecision(cwd, runId, decision) {
|
|
892
|
-
const parts = [`kind=${decision.kind}`];
|
|
893
|
-
if ("reason" in decision && decision.reason)
|
|
894
|
-
parts.push(`reason=${decision.reason}`);
|
|
895
|
-
if ("content" in decision && decision.content)
|
|
896
|
-
parts.push(decision.content);
|
|
897
|
-
if ("task" in decision && decision.task) {
|
|
898
|
-
const task = decision.task;
|
|
899
|
-
parts.push(`task=${task.id}`, `title=${task.title}`);
|
|
900
|
-
if (task.workerId)
|
|
901
|
-
parts.push(`worker=${task.workerId}`);
|
|
902
|
-
}
|
|
903
|
-
if ("attempts" in decision && typeof decision.attempts === "number")
|
|
904
|
-
parts.push(`attempts=${decision.attempts}`);
|
|
905
|
-
if ("workerId" in decision && decision.workerId)
|
|
906
|
-
parts.push(`worker=${decision.workerId}`);
|
|
907
|
-
if ("command" in decision && decision.command)
|
|
908
|
-
parts.push(`verifier=${decision.command}`);
|
|
909
|
-
if ("status" in decision && decision.status)
|
|
910
|
-
parts.push(`status=${decision.status}`);
|
|
911
|
-
parts.push(`timestamp=${nowIso()}`);
|
|
912
|
-
return appendGoalEvidence(cwd, runId, {
|
|
913
|
-
kind: "summary",
|
|
914
|
-
label: `Goal decision: ${decision.kind}`,
|
|
915
|
-
content: parts.join("; "),
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
export async function appendGoalEvidence(cwd, runId, input) {
|
|
919
|
-
const discovered = await getGoalRun(cwd, runId);
|
|
920
|
-
const writeCwd = discovered?.projectPath ?? cwd;
|
|
921
|
-
return mutateGoalRunsLocked(writeCwd, (runs) => {
|
|
922
|
-
const index = runs.findIndex((run) => run.id === runId || run.id.startsWith(runId));
|
|
923
|
-
if (index === -1)
|
|
924
|
-
return { runs, result: null, write: false };
|
|
925
|
-
const run = runs[index];
|
|
926
|
-
const updated = {
|
|
927
|
-
...run,
|
|
928
|
-
evidence: [...run.evidence, createGoalEvidence(input)],
|
|
929
|
-
updatedAt: nowIso(),
|
|
930
|
-
};
|
|
931
|
-
const nextRuns = [...runs];
|
|
932
|
-
nextRuns[index] = updated;
|
|
933
|
-
return { runs: nextRuns, result: updated };
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
export async function updateGoalTask(cwd, runId, taskId, patch) {
|
|
937
|
-
const discovered = await getGoalRun(cwd, runId);
|
|
938
|
-
const writeCwd = discovered?.projectPath ?? cwd;
|
|
939
|
-
return mutateGoalRunsLocked(writeCwd, (runs) => {
|
|
940
|
-
const runIndex = runs.findIndex((run) => run.id === runId || run.id.startsWith(runId));
|
|
941
|
-
if (runIndex === -1)
|
|
942
|
-
return { runs, result: null, write: false };
|
|
943
|
-
const run = runs[runIndex];
|
|
944
|
-
const taskIndex = run.tasks.findIndex((task) => task.id === taskId || task.id.startsWith(taskId));
|
|
945
|
-
const tasks = [...run.tasks];
|
|
946
|
-
if (taskIndex === -1) {
|
|
947
|
-
if ("title" in patch && "prompt" in patch && patch.title && patch.prompt) {
|
|
948
|
-
tasks.push(createGoalTask({
|
|
949
|
-
...patch,
|
|
950
|
-
title: patch.title,
|
|
951
|
-
prompt: patch.prompt,
|
|
952
|
-
}));
|
|
953
|
-
}
|
|
954
|
-
else {
|
|
955
|
-
return { runs, result: null, write: false };
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
else {
|
|
959
|
-
const existingTask = tasks[taskIndex];
|
|
960
|
-
tasks[taskIndex] = {
|
|
961
|
-
...existingTask,
|
|
962
|
-
...patch,
|
|
963
|
-
id: existingTask.id,
|
|
964
|
-
title: patch.title ?? existingTask.title,
|
|
965
|
-
prompt: patch.prompt ?? existingTask.prompt,
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
const updated = { ...run, tasks, updatedAt: nowIso() };
|
|
969
|
-
const nextRuns = [...runs];
|
|
970
|
-
nextRuns[runIndex] = updated;
|
|
971
|
-
return { runs: nextRuns, result: updated };
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
/**
|
|
975
|
-
* Record the typed integration state from code (git truth), so the controller
|
|
976
|
-
* never has to parse evidence labels to know whether candidates reached main.
|
|
977
|
-
*/
|
|
978
|
-
export async function setGoalIntegrationState(cwd, runId, state) {
|
|
979
|
-
const discovered = await getGoalRun(cwd, runId);
|
|
980
|
-
const writeCwd = discovered?.projectPath ?? cwd;
|
|
981
|
-
return mutateGoalRunsLocked(writeCwd, (runs) => {
|
|
982
|
-
const index = runs.findIndex((run) => run.id === runId || run.id.startsWith(runId));
|
|
983
|
-
if (index === -1)
|
|
984
|
-
return { runs, result: null, write: false };
|
|
985
|
-
const run = runs[index];
|
|
986
|
-
const updated = { ...run, integration: state, updatedAt: nowIso() };
|
|
987
|
-
const nextRuns = [...runs];
|
|
988
|
-
nextRuns[index] = updated;
|
|
989
|
-
return { runs: nextRuns, result: updated };
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Stamp the most recent substantive (non-audit, non-integration) worker
|
|
994
|
-
* completion time, driving verifier/audit staleness deterministically.
|
|
995
|
-
*/
|
|
996
|
-
export async function recordGoalSubstantiveWorker(cwd, runId, atIso) {
|
|
997
|
-
const discovered = await getGoalRun(cwd, runId);
|
|
998
|
-
const writeCwd = discovered?.projectPath ?? cwd;
|
|
999
|
-
return mutateGoalRunsLocked(writeCwd, (runs) => {
|
|
1000
|
-
const index = runs.findIndex((run) => run.id === runId || run.id.startsWith(runId));
|
|
1001
|
-
if (index === -1)
|
|
1002
|
-
return { runs, result: null, write: false };
|
|
1003
|
-
const run = runs[index];
|
|
1004
|
-
const updated = { ...run, lastSubstantiveWorkerAt: atIso, updatedAt: nowIso() };
|
|
1005
|
-
const nextRuns = [...runs];
|
|
1006
|
-
nextRuns[index] = updated;
|
|
1007
|
-
return { runs: nextRuns, result: updated };
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* Infer whether a prerequisite is locally resolvable by the agent or genuinely
|
|
1012
|
-
* external (user-supplied). An explicit `kind` wins; otherwise a runnable
|
|
1013
|
-
* `checkCommand` implies the agent can both check and satisfy it locally.
|
|
1014
|
-
*/
|
|
1015
|
-
export function prerequisiteKind(item) {
|
|
1016
|
-
if (item.kind)
|
|
1017
|
-
return item.kind;
|
|
1018
|
-
return item.checkCommand?.trim() ? "local" : "external";
|
|
1019
|
-
}
|
|
1020
|
-
function isUnmetGoalPrerequisite(item) {
|
|
1021
|
-
return item.status !== "met" || !item.evidence?.trim();
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Local unmet prerequisites no longer block the Goal — the controller schedules
|
|
1025
|
-
* a worker task to resolve them. Only unmet **external** prerequisites (true
|
|
1026
|
-
* user-supplied inputs) are blocking.
|
|
1027
|
-
*/
|
|
1028
|
-
export function isBlockingGoalPrerequisite(item) {
|
|
1029
|
-
return isUnmetGoalPrerequisite(item) && prerequisiteKind(item) === "external";
|
|
1030
|
-
}
|
|
1031
|
-
export function isUnmetLocalGoalPrerequisite(item) {
|
|
1032
|
-
return isUnmetGoalPrerequisite(item) && prerequisiteKind(item) === "local";
|
|
1033
|
-
}
|
|
1034
|
-
export function unmetLocalGoalPrerequisites(run) {
|
|
1035
|
-
return run.prerequisites.filter(isUnmetLocalGoalPrerequisite);
|
|
1036
|
-
}
|
|
1037
|
-
export function goalHasUnmetLocalPrerequisites(run) {
|
|
1038
|
-
return run.prerequisites.some(isUnmetLocalGoalPrerequisite);
|
|
1039
|
-
}
|
|
1040
|
-
export function hasBlockingGoalPrerequisites(prerequisites) {
|
|
1041
|
-
return prerequisites.some(isBlockingGoalPrerequisite);
|
|
1042
|
-
}
|
|
1043
|
-
export function goalHasBlockingPrerequisites(run) {
|
|
1044
|
-
return hasBlockingGoalPrerequisites(run.prerequisites);
|
|
1045
|
-
}
|
|
1046
|
-
export function formatGoalPrerequisiteInstruction(item) {
|
|
1047
|
-
const instructions = item.instructions?.trim();
|
|
1048
|
-
if (instructions)
|
|
1049
|
-
return instructions;
|
|
1050
|
-
if (item.status === "met" && !item.evidence?.trim()) {
|
|
1051
|
-
return "Prerequisite is marked met but has no recorded check evidence; verify it locally and record non-secret evidence.";
|
|
1052
|
-
}
|
|
1053
|
-
if (item.status === "unknown") {
|
|
1054
|
-
return "Check this prerequisite locally and record non-secret evidence before workers can start.";
|
|
1055
|
-
}
|
|
1056
|
-
return "User must provide this prerequisite.";
|
|
1057
|
-
}
|
|
1058
|
-
export function formatGoalBlockingPrerequisiteList(prerequisites) {
|
|
1059
|
-
const missing = prerequisites.filter(isBlockingGoalPrerequisite);
|
|
1060
|
-
if (missing.length === 0)
|
|
1061
|
-
return "Goal has no missing user prerequisites.";
|
|
1062
|
-
return missing
|
|
1063
|
-
.map((item) => `${item.label}: ${formatGoalPrerequisiteInstruction(item)}`)
|
|
1064
|
-
.join("; ");
|
|
1065
|
-
}
|
|
1066
|
-
export function formatGoalBlockingPrerequisites(run) {
|
|
1067
|
-
return formatGoalBlockingPrerequisiteList(run.prerequisites);
|
|
1068
|
-
}
|
|
1069
|
-
export function summarizeGoalCountsFromRuns(runs) {
|
|
1070
|
-
const counts = {
|
|
1071
|
-
total: runs.length,
|
|
1072
|
-
active: 0,
|
|
1073
|
-
blocked: 0,
|
|
1074
|
-
pending: 0,
|
|
1075
|
-
running: 0,
|
|
1076
|
-
passed: 0,
|
|
1077
|
-
failed: 0,
|
|
1078
|
-
};
|
|
1079
|
-
for (const run of runs) {
|
|
1080
|
-
if (run.status === "blocked")
|
|
1081
|
-
counts.blocked++;
|
|
1082
|
-
if (run.status === "running" || run.status === "verifying")
|
|
1083
|
-
counts.running++;
|
|
1084
|
-
if (run.status === "passed")
|
|
1085
|
-
counts.passed++;
|
|
1086
|
-
if (run.status === "failed")
|
|
1087
|
-
counts.failed++;
|
|
1088
|
-
if (run.status === "draft" || run.status === "ready" || run.status === "paused")
|
|
1089
|
-
counts.pending++;
|
|
1090
|
-
if (run.status !== "passed" && run.status !== "failed")
|
|
1091
|
-
counts.active++;
|
|
1092
|
-
}
|
|
1093
|
-
return counts;
|
|
1094
|
-
}
|
|
1095
|
-
export async function summarizeGoalCounts(cwd) {
|
|
1096
|
-
return summarizeGoalCountsFromRuns(await loadGoalRuns(cwd));
|
|
1097
|
-
}
|
|
1098
|
-
async function writeGoalProgressJournalFromRun(cwd, run) {
|
|
1099
|
-
const dir = join(projectDir(cwd), "journals");
|
|
1100
|
-
await mkdir(dir, { recursive: true });
|
|
1101
|
-
const path = join(dir, `${run.id}.md`);
|
|
1102
|
-
const lines = [
|
|
1103
|
-
`# ${run.title}`,
|
|
1104
|
-
"",
|
|
1105
|
-
`Status: ${run.status}`,
|
|
1106
|
-
`Goal: ${run.goal}`,
|
|
1107
|
-
"",
|
|
1108
|
-
"## Success criteria",
|
|
1109
|
-
...(run.successCriteria.length
|
|
1110
|
-
? run.successCriteria.map((item) => `- ${item}`)
|
|
1111
|
-
: ["- none recorded"]),
|
|
1112
|
-
"",
|
|
1113
|
-
"## Prerequisites",
|
|
1114
|
-
...(run.prerequisites.length
|
|
1115
|
-
? run.prerequisites.map((item) => `- [${item.status}] ${item.label}${item.evidence ? ` — ${item.evidence}` : ""}`)
|
|
1116
|
-
: ["- none"]),
|
|
1117
|
-
"",
|
|
1118
|
-
"## References",
|
|
1119
|
-
...(run.references?.length
|
|
1120
|
-
? run.references.map((item) => `- [${item.kind}] ${item.id}: ${item.label}${item.value ? ` — ${item.value}` : ""}${item.path ? ` (${item.path})` : ""}`)
|
|
1121
|
-
: ["- none"]),
|
|
1122
|
-
"",
|
|
1123
|
-
"## Tasks",
|
|
1124
|
-
...(run.tasks.length
|
|
1125
|
-
? run.tasks.map((task) => `- [${task.status}] ${task.title} (attempts: ${task.attempts})${task.lastSummary ? ` — ${task.lastSummary}` : ""}`)
|
|
1126
|
-
: ["- none"]),
|
|
1127
|
-
"",
|
|
1128
|
-
"## Verifier",
|
|
1129
|
-
run.verifier?.lastResult
|
|
1130
|
-
? `- ${run.verifier.lastResult.status}: ${run.verifier.lastResult.summary}${run.verifier.lastResult.outputPath ? ` (${run.verifier.lastResult.outputPath})` : ""}`
|
|
1131
|
-
: `- ${run.verifier?.command ?? "none"}`,
|
|
1132
|
-
"",
|
|
1133
|
-
"## Final completion audit",
|
|
1134
|
-
run.completionAudit
|
|
1135
|
-
? `- ${run.completionAudit.status}: ${run.completionAudit.summary}${run.completionAudit.outputPath ? ` (${run.completionAudit.outputPath})` : ""}`
|
|
1136
|
-
: "- none",
|
|
1137
|
-
"",
|
|
1138
|
-
"## Blockers",
|
|
1139
|
-
...(run.blockers.length ? run.blockers.map((item) => `- ${item}`) : ["- none"]),
|
|
1140
|
-
"",
|
|
1141
|
-
"## Recent evidence",
|
|
1142
|
-
...run.evidence
|
|
1143
|
-
.slice(-10)
|
|
1144
|
-
.map((item) => `- ${item.createdAt} [${item.kind}] ${item.label}${item.path ? ` (${item.path})` : ""}${item.content ? ` — ${item.content}` : ""}`),
|
|
1145
|
-
"",
|
|
1146
|
-
];
|
|
1147
|
-
await writeFile(path, lines.join("\n"), "utf-8");
|
|
1148
|
-
return path;
|
|
1149
|
-
}
|
|
1150
|
-
export async function writeGoalProgressJournal(cwd, runId) {
|
|
1151
|
-
const run = await getGoalRun(cwd, runId);
|
|
1152
|
-
if (!run)
|
|
1153
|
-
return null;
|
|
1154
|
-
return writeGoalProgressJournalFromRun(run.projectPath, run);
|
|
1155
|
-
}
|
|
1156
|
-
//# sourceMappingURL=goal-store.js.map
|