lazycodex-ai 5.0.0-beta.62 → 5.0.0-beta.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/doctor/checks/browser-provider.d.ts +2 -0
- package/dist/cli/index.js +70 -41
- package/dist/cli-node/index.js +70 -41
- package/package.json +2 -6
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -2
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +5 -4
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +5 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +7 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +7 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +7 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +931 -442
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +20 -12
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +8 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.d.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.js +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.js +37 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.js +24 -12
- package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.d.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.js +34 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.d.ts +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.js +107 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +6 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +54 -25
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.d.ts +10 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.js +38 -31
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +9 -7
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +105 -114
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.d.ts +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.js +86 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +15 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +11 -8
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.js +28 -12
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +335 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +109 -10
- package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-budget-io.d.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-budget-io.js +67 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +8 -62
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +11 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +120 -88
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +18 -10
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.js +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-types.d.ts +9 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.d.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +11 -11
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +8 -17
- package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.js +41 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +15 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +7 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +26 -14
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +8 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/driver-objective-ack.ts +20 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/evidence-artifacts.ts +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts +23 -16
- package/packages/omo-codex/plugin/components/ulw-loop/src/ledger.ts +33 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-commit.ts +124 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +67 -54
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts +63 -31
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +126 -140
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-log.ts +84 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +26 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +11 -7
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +47 -12
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +124 -10
- package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-budget-io.ts +59 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +8 -59
- package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +129 -111
- package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +19 -10
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +5 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering-types.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +12 -10
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +8 -16
- package/packages/omo-codex/plugin/components/ulw-loop/src/success-criteria-input.ts +56 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +37 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/barrier.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-child.ts +65 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-cli-qa.mjs +76 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-crash.ts +23 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +58 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-writer.ts +65 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/lease-clock.ts +23 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-commit-log.test.ts +252 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-commit-recovery.test.ts +190 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io.test.ts +50 -35
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-newest.test.ts +72 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-completed-plan.test.ts +43 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-driver-states.test.ts +33 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-dx.test.ts +219 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-session-context.test.ts +21 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +92 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/status-next-actions-surface.test.ts +133 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering-batch.test.ts +12 -12
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +4 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.d.mts +4 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +15 -7
- package/packages/omo-codex/plugin/skills/ast-grep/SKILL.md +12 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/playwright-cli.md +79 -161
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/aside.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +1 -82
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +17 -29
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +86 -100
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -3
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
- package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +5 -4
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -3
- package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +75 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +6 -4
- package/packages/prompts-core/prompts/ultrawork/codex.md +5 -4
- package/packages/shared-skills/skills/ast-grep/SKILL.md +12 -0
- package/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md +79 -161
- package/packages/shared-skills/skills/frontend/SKILL.md +1 -1
- package/packages/shared-skills/skills/frontend/references/design/aside.md +1 -1
- package/packages/shared-skills/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/shared-skills/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
- package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +1 -82
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +17 -29
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -1
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
- package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
- package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
- package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +86 -100
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -3
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
- package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
- package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -3
- package/packages/shared-skills/skills/visual-qa/AGENTS.md +1 -1
- package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
- package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +75 -0
- package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +0 -45
- package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +0 -45
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// biome-ignore-all format: compact steering module must stay below the 240 pure-LOC budget
|
|
2
2
|
import { isUlwLoopDone } from "./goal-status.js";
|
|
3
|
-
import {
|
|
3
|
+
import { commit } from "./plan-commit.js";
|
|
4
|
+
import { findAcceptedSteeringLedgerEntry, readUlwLoopPlan, withUlwLoopMutationLock } from "./plan-io.js";
|
|
4
5
|
import { makeGoal, reviseCriterion, reviseWording, splitOrBlock } from "./steering-mutations.js";
|
|
5
6
|
import { buildSteeringPlanSnapshot, changedGoalIdsBetween } from "./steering-snapshot.js";
|
|
6
7
|
import { iso, ULW_LOOP_STEERING_MUTATION_KINDS, ULW_LOOP_SUCCESS_CRITERION_USER_MODELS } from "./types.js";
|
|
7
8
|
import { batchUpdateLedgerEntry } from "./validation-batch.js";
|
|
8
9
|
const SOURCES = ["user_prompt_submit", "finding", "cli"];
|
|
9
|
-
const PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
10
|
+
const PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "acknowledgedDriverObjectives", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
10
11
|
const isObject = (value) => typeof value === "object" && value !== null;
|
|
11
12
|
const isPlain = (value) => isObject(value) && !Array.isArray(value);
|
|
12
13
|
const read = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
@@ -163,13 +164,13 @@ function validateCriterion(plan, proposal, reasons) {
|
|
|
163
164
|
if (model !== undefined && !isModel(model))
|
|
164
165
|
reasons.push("invalid userModel");
|
|
165
166
|
}
|
|
166
|
-
export function applySteeringMutation(plan, proposal, audit) {
|
|
167
|
+
export function applySteeringMutation(plan, proposal, audit, surface = "lazycodex") {
|
|
167
168
|
const next = structuredClone(plan);
|
|
168
169
|
if (!audit.invariant.accepted)
|
|
169
170
|
return next;
|
|
170
171
|
const now = proposal.now?.toISOString() ?? iso();
|
|
171
172
|
if (proposal.kind === "add_subgoal")
|
|
172
|
-
next.goals.push(makeGoal(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1));
|
|
173
|
+
next.goals.push(makeGoal(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1, surface));
|
|
173
174
|
if (proposal.kind === "reorder_pending") {
|
|
174
175
|
const order = pendingOrder(proposal);
|
|
175
176
|
next.goals = [...order.map((id) => goal(next, id)).filter((item) => item !== undefined), ...next.goals.filter((item) => !order.includes(item.id))];
|
|
@@ -177,7 +178,7 @@ export function applySteeringMutation(plan, proposal, audit) {
|
|
|
177
178
|
if (proposal.kind === "revise_pending_wording")
|
|
178
179
|
reviseWording(next, proposal, now);
|
|
179
180
|
if (proposal.kind === "split_subgoal" || proposal.kind === "mark_blocked_superseded")
|
|
180
|
-
splitOrBlock(next, proposal, now);
|
|
181
|
+
splitOrBlock(next, proposal, now, surface);
|
|
181
182
|
if (proposal.kind === "revise_criterion")
|
|
182
183
|
reviseCriterion(next, proposal, now);
|
|
183
184
|
if (proposal.kind !== "annotate_ledger")
|
|
@@ -201,7 +202,7 @@ export function parseUlwLoopSteeringDirective(text) {
|
|
|
201
202
|
throw error;
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
|
-
export async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
205
|
+
export async function steerUlwLoop(repoRoot, proposal, scope, surface = "lazycodex") {
|
|
205
206
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
206
207
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
207
208
|
const key = proposal.idempotencyKey ?? proposal.promptSignature;
|
|
@@ -214,7 +215,7 @@ export async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
|
214
215
|
}
|
|
215
216
|
const audit = validateUlwLoopSteeringProposal(plan, proposal);
|
|
216
217
|
const accepted = audit.invariant.accepted;
|
|
217
|
-
const next = accepted ? applySteeringMutation(plan, proposal, audit) : plan;
|
|
218
|
+
const next = accepted ? applySteeringMutation(plan, proposal, audit, surface) : plan;
|
|
218
219
|
const finalAudit = { ...audit };
|
|
219
220
|
if (accepted) {
|
|
220
221
|
const changed = changedGoalIdsBetween(plan, next);
|
|
@@ -223,11 +224,10 @@ export async function steerUlwLoop(repoRoot, proposal, scope) {
|
|
|
223
224
|
}
|
|
224
225
|
const at = proposal.now?.toISOString() ?? iso();
|
|
225
226
|
const batchEntry = accepted ? batchUpdateLedgerEntry(plan, next, at) : null;
|
|
226
|
-
|
|
227
|
-
await writePlan(repoRoot, next, scope);
|
|
228
|
-
await appendLedger(repoRoot, ledgerEntry(proposal, finalAudit, at), scope);
|
|
227
|
+
const entries = [ledgerEntry(proposal, finalAudit, at)];
|
|
229
228
|
if (batchEntry !== null)
|
|
230
|
-
|
|
229
|
+
entries.push(batchEntry);
|
|
230
|
+
await commit(repoRoot, scope, { plan: next, entries });
|
|
231
231
|
return { plan: next, accepted, audit: finalAudit, rejectedReasons: audit.invariant.rejectedReasons, deduped: false };
|
|
232
232
|
});
|
|
233
233
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
3
|
+
import { readLedgerAt } from "./ledger.js";
|
|
3
4
|
import { normalizeUlwLoopSessionId, ulwLoopDir, ulwLoopStateLockPath } from "./paths.js";
|
|
5
|
+
import { readUlwLoopPlanSync } from "./plan-io.js";
|
|
4
6
|
import { isStateLockTimeout, withStateLockSync } from "./state-lock.js";
|
|
5
7
|
// Turn-death recovery only: Codex emits Stop when a turn ends, so a run that
|
|
6
8
|
// dies mid-turn (crash, kill, context blowup before any Stop) never reaches
|
|
@@ -27,7 +29,7 @@ export function runStopResumeHook(input) {
|
|
|
27
29
|
return "";
|
|
28
30
|
const scope = { sessionId: payload.session_id };
|
|
29
31
|
const stateDir = ulwLoopDir(payload.cwd, scope);
|
|
30
|
-
const plan = readPlan(
|
|
32
|
+
const plan = readPlan(payload.cwd, payload.session_id);
|
|
31
33
|
if (plan === null || plan.aggregateCompletion?.status === "complete")
|
|
32
34
|
return "";
|
|
33
35
|
const goal = resumableGoal(plan);
|
|
@@ -76,11 +78,10 @@ function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
|
|
|
76
78
|
throw error;
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// a separate file — a ledger append would change the count and self-reset.
|
|
81
|
+
// Hook budget counters are exempt from plan/audit commits. Count reconciled
|
|
82
|
+
// entries, not cache lines: a writer dying after link still made progress.
|
|
82
83
|
function consumeResumeBudget(stateDir, goalId) {
|
|
83
|
-
const ledgerLineCount =
|
|
84
|
+
const ledgerLineCount = readLedgerAt(stateDir).length;
|
|
84
85
|
const counterPath = resolve(stateDir, `auto-resume-${goalId}.json`);
|
|
85
86
|
const stuckPath = resolve(stateDir, `auto-resume-${goalId}.stuck`);
|
|
86
87
|
// goals.json is untrusted input: a crafted goal id (e.g. `../../x`) must
|
|
@@ -111,9 +112,9 @@ function renderResumeDirective(plan, goal, sessionId) {
|
|
|
111
112
|
"If the loop is genuinely blocked on the user, checkpoint the goal as blocked with the reason instead.",
|
|
112
113
|
].join("\n");
|
|
113
114
|
}
|
|
114
|
-
function readPlan(
|
|
115
|
+
function readPlan(repoRoot, sessionId) {
|
|
115
116
|
try {
|
|
116
|
-
return
|
|
117
|
+
return readUlwLoopPlanSync(repoRoot, { sessionId });
|
|
117
118
|
}
|
|
118
119
|
catch (error) {
|
|
119
120
|
if (error instanceof Error)
|
|
@@ -121,16 +122,6 @@ function readPlan(goalsPath) {
|
|
|
121
122
|
throw error;
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
|
-
function countLedgerLines(ledgerPath) {
|
|
125
|
-
try {
|
|
126
|
-
return readFileSync(ledgerPath, "utf8").split("\n").filter(Boolean).length;
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
if (error instanceof Error)
|
|
130
|
-
return 0;
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
125
|
function readCounter(counterPath) {
|
|
135
126
|
try {
|
|
136
127
|
if (!existsSync(counterPath))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UlwLoopSuccessCriterion, UlwLoopSuccessCriterionUserModel } from "./types.js";
|
|
2
|
+
export interface SuccessCriterionInput {
|
|
3
|
+
readonly scenario: string;
|
|
4
|
+
readonly expectedEvidence: string;
|
|
5
|
+
readonly userModel?: UlwLoopSuccessCriterionUserModel;
|
|
6
|
+
readonly essential?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function criterionId(index: number): string;
|
|
9
|
+
export declare function criteriaFromInput(input: readonly unknown[]): UlwLoopSuccessCriterion[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ULW_LOOP_SUCCESS_CRITERION_USER_MODELS, UlwLoopError } from "./types.js";
|
|
2
|
+
function invalid(message, details) {
|
|
3
|
+
throw new UlwLoopError(`Invalid successCriteria: ${message}`, "ULW_LOOP_ARGUMENT_INVALID", { details });
|
|
4
|
+
}
|
|
5
|
+
function isUserModel(value) {
|
|
6
|
+
return typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
|
|
7
|
+
}
|
|
8
|
+
function requireText(value, field, index) {
|
|
9
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
10
|
+
return trimmed || invalid(`entry ${index + 1} needs a non-empty ${field}.`, { index, field });
|
|
11
|
+
}
|
|
12
|
+
export function criterionId(index) {
|
|
13
|
+
return `C${String(index + 1).padStart(3, "0")}`;
|
|
14
|
+
}
|
|
15
|
+
export function criteriaFromInput(input) {
|
|
16
|
+
if (input.length === 0)
|
|
17
|
+
invalid("provide at least one criterion or omit the field for placeholders.", { count: 0 });
|
|
18
|
+
return input.map((entry, index) => {
|
|
19
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
20
|
+
return invalid(`entry ${index + 1} must be an object.`, { index });
|
|
21
|
+
const record = { ...entry };
|
|
22
|
+
const userModel = record["userModel"] ?? "happy";
|
|
23
|
+
if (!isUserModel(userModel))
|
|
24
|
+
return invalid(`entry ${index + 1} userModel must be one of ${ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.join(", ")}.`, {
|
|
25
|
+
index,
|
|
26
|
+
userModel: String(userModel),
|
|
27
|
+
});
|
|
28
|
+
const essential = record["essential"];
|
|
29
|
+
if (essential !== undefined && typeof essential !== "boolean")
|
|
30
|
+
return invalid(`entry ${index + 1} essential must be a boolean.`, { index });
|
|
31
|
+
return {
|
|
32
|
+
id: criterionId(index),
|
|
33
|
+
scenario: requireText(record["scenario"], "scenario", index),
|
|
34
|
+
userModel,
|
|
35
|
+
expectedEvidence: requireText(record["expectedEvidence"], "expectedEvidence", index),
|
|
36
|
+
essential: essential ?? true,
|
|
37
|
+
capturedEvidence: null,
|
|
38
|
+
status: "pending",
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit --with-ultrawork",
|
|
9
9
|
"timeout": 10,
|
|
10
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
10
|
+
"statusMessage": "(OmO 5.0.0-beta.64) Checking Ulw-Loop Steering"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"type": "command",
|
|
21
21
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use-spawn",
|
|
22
22
|
"timeout": 5,
|
|
23
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
23
|
+
"statusMessage": "(OmO 5.0.0-beta.64) Recording Spawn Admission"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"type": "command",
|
|
34
34
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
|
|
35
35
|
"timeout": 5,
|
|
36
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
36
|
+
"statusMessage": "(OmO 5.0.0-beta.64) Enforcing Unlimited Ulw-Loop Budget"
|
|
37
37
|
}
|
|
38
38
|
]
|
|
39
39
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"type": "command",
|
|
45
45
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use-spawn",
|
|
46
46
|
"timeout": 5,
|
|
47
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
47
|
+
"statusMessage": "(OmO 5.0.0-beta.64) Guarding Ulw-Loop Spawns"
|
|
48
48
|
}
|
|
49
49
|
]
|
|
50
50
|
}
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"type": "command",
|
|
57
57
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook stop",
|
|
58
58
|
"timeout": 10,
|
|
59
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
59
|
+
"statusMessage": "(OmO 5.0.0-beta.64) Checking Ulw-Loop Resume"
|
|
60
60
|
}
|
|
61
61
|
]
|
|
62
62
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-ulw-loop",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.64",
|
|
4
4
|
"description": "Codex plugin: durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|
package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md
CHANGED
|
@@ -20,7 +20,7 @@ Run each criterion's real-surface proof yourself through the channel that faithf
|
|
|
20
20
|
|
|
21
21
|
1. **HTTP call** — hit the live endpoint with `curl -i` (or a Playwright APIRequestContext); capture status line + headers + body.
|
|
22
22
|
2. **Terminal / TUI** - prove it through the xterm.js web terminal; tmux `send-keys` is fine for a boot smoke, but NEVER `tmux capture-pane` for color/layout/CJK evidence (it degrades truecolor).
|
|
23
|
-
3. **Browser use** — in Codex,
|
|
23
|
+
3. **Browser use** — in Codex, prefer `browser:control-in-app-browser`. Otherwise, or for Chrome semantics, stealth, trace, or auth, WRITE a `playwright-core` script and run it from js eval against local Chrome (`channel: "chrome"`; persistent context on a CLONED profile, never the live one). Capture action log + screenshot path. Never downgrade a browser-facing criterion.
|
|
24
24
|
4. **Computer use** — for desktop/GUI apps, drive the running app via OS automation (computer-use, AppleScript, xdotool, etc.); capture action log + screenshot.
|
|
25
25
|
|
|
26
26
|
For TUI visual QA (mandatory when a PR or review must inspect the terminal screen),
|
|
@@ -165,7 +165,7 @@ Loop per goal. Cap at 5 cycles per goal. Cap identical same-criterion failures a
|
|
|
165
165
|
4. INTEGRATE + CRITICAL SELF-QA + GIT CHECKPOINT (EVERY WORKER RETURN): do NOT trust the worker's report. Read the diff yourself, re-run its tests, and run LSP diagnostics on the changed files. Treat "done" as a claim to disprove. If the diff drifts, the test is hollow, or evidence is missing, RESPAWN the worker with the specific failure context. Once the work unit is verified, use `git-master` before staging: inspect recent repository commits and touched-path history to infer commit language, Conventional Commit scope, message shape, and unit size. Stage only that unit's files and commit in the observed style; do not carry verified work forward into a later omnibus commit. If no git-tracked files changed or committing is unsafe, record the no-commit reason as evidence. Forward every finding/learning to subsequent workers.
|
|
166
166
|
5. EXECUTE-AS-SCENARIO: ACTUALLY run the Manual-QA scenario the criterion named (channel table above). Run it yourself for the orchestrator check; for heavier flows dispatch a dedicated QA execution worker (`lazycodex-worker-medium` by default; `lazycodex-worker-high` when the QA flow itself is hard) whose ONLY job is to drive the channel and write the artifact to the named evidence path. If the scenario FAILS, respawn the implementing worker with the captured failure — do not hand-patch around it.
|
|
167
167
|
6. CAPTURE: collect the observable artifact path: transcript, stdout, screenshot, assertion, status+body, diff, or parsed dump. No artifact written at the evidence path — not done; record BLOCKED and respawn QA.
|
|
168
|
-
7. CLEAN (PAIRED, NEVER SKIP): tear down every runtime artifact step 5 spawned BEFORE recording — server PIDs (`kill`, verify `kill -0` fails), `tmux` sessions (`tmux kill-session -t ulw-qa-<criterion>`; confirm `tmux ls`), browser / Playwright contexts (`.close()`), containers (`docker rm -f`), bound ports (`lsof -i :<port>` empty), temp sockets / files / dirs (`rm -rf` the `mktemp` paths), QA-only env vars, AND close every finished worker (v1 `close_agent`; on V2 finished workers end on their own — `interrupt_agent` any still running). Register each teardown as its own todo the moment the QA spawns the resource (scripts, tmux assets,
|
|
168
|
+
7. CLEAN (PAIRED, NEVER SKIP): tear down every runtime artifact step 5 spawned BEFORE recording — server PIDs (`kill`, verify `kill -0` fails), `tmux` sessions (`tmux kill-session -t ulw-qa-<criterion>`; confirm `tmux ls`), browser / Playwright contexts (`.close()`), containers (`docker rm -f`), bound ports (`lsof -i :<port>` empty), temp sockets / files / dirs (`rm -rf` the `mktemp` paths), QA-only env vars, AND close every finished worker (v1 `close_agent`; on V2 finished workers end on their own — `interrupt_agent` any still running). Register each teardown as its own todo the moment the QA spawns the resource (scripts, tmux assets, browser contexts, PIDs, ports) so none is forgotten. Embed a one-line cleanup receipt in the evidence string, e.g. `cleanup: killed 12345; tmux kill-session ulw-qa-foo; rm -rf /tmp/ulw.aB12cD; interrupt_agent w-3`. Missing receipt → record BLOCKED, not PASS.
|
|
169
169
|
8. RECORD one result immediately from the artifact you just wrote — never from memory or a later turn — stamping the capture tree `$(git rev-parse --short "HEAD^{tree}")` into the evidence:
|
|
170
170
|
- PASS: `omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass --evidence "<observable> @tree:<short-tree> | <cleanup receipt>" --json`
|
|
171
171
|
- FAIL: `omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status fail --evidence "<observable> @tree:<short-tree> | <cleanup receipt>" --notes "<diagnosis>" --json`
|
|
@@ -4,11 +4,19 @@ import {
|
|
|
4
4
|
readCodexGoalSnapshotInput,
|
|
5
5
|
reconcileCodexGoalSnapshot,
|
|
6
6
|
} from "./codex-goal-snapshot.js";
|
|
7
|
+
import { acknowledgedDriverObjectives } from "./driver-objective-ack.js";
|
|
7
8
|
import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective } from "./goal-status.js";
|
|
8
9
|
import type { UlwLoopScope } from "./paths.js";
|
|
9
10
|
import type { UlwLoopItem, UlwLoopPlan } from "./types.js";
|
|
10
11
|
import { UlwLoopError } from "./types.js";
|
|
11
12
|
|
|
13
|
+
export interface CheckpointCodexGoalValidation {
|
|
14
|
+
readonly raw: unknown;
|
|
15
|
+
readonly nextActions: readonly string[];
|
|
16
|
+
readonly warnings: readonly string[];
|
|
17
|
+
readonly unacknowledgedObjective?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
export async function validateCheckpointCodexGoal(input: {
|
|
13
21
|
readonly repoRoot: string;
|
|
14
22
|
readonly plan: UlwLoopPlan;
|
|
@@ -16,11 +24,12 @@ export async function validateCheckpointCodexGoal(input: {
|
|
|
16
24
|
readonly raw: string | undefined;
|
|
17
25
|
readonly evidence: string;
|
|
18
26
|
readonly scope?: UlwLoopScope;
|
|
19
|
-
}): Promise<
|
|
27
|
+
}): Promise<CheckpointCodexGoalValidation> {
|
|
20
28
|
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
21
29
|
const expected = expectedCodexObjective(input.plan, input.goal);
|
|
22
30
|
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
23
31
|
expectedObjective: expected,
|
|
32
|
+
acknowledgedObjectives: acknowledgedDriverObjectives(input.plan),
|
|
24
33
|
...(codexGoalMode(input.plan) === "aggregate"
|
|
25
34
|
? { acceptedObjectives: compatibleCodexObjectives(input.plan) }
|
|
26
35
|
: {}),
|
|
@@ -28,8 +37,11 @@ export async function validateCheckpointCodexGoal(input: {
|
|
|
28
37
|
if (!reconciliation.ok) throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
29
38
|
return {
|
|
30
39
|
raw: snapshot?.raw,
|
|
31
|
-
nextActions: reconciliation.
|
|
32
|
-
warnings: reconciliation.warnings
|
|
40
|
+
nextActions: reconciliation.nextActions,
|
|
41
|
+
warnings: reconciliation.warnings,
|
|
42
|
+
...(reconciliation.unacknowledgedObjective === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: { unacknowledgedObjective: reconciliation.unacknowledgedObjective }),
|
|
33
45
|
};
|
|
34
46
|
}
|
|
35
47
|
|
|
@@ -6,11 +6,13 @@ import {
|
|
|
6
6
|
combineCheckpointValidationErrors,
|
|
7
7
|
validateCheckpointCodexGoal,
|
|
8
8
|
} from "./checkpoint-codex-validation.js";
|
|
9
|
+
import { acknowledgeDriverObjective } from "./driver-objective-ack.js";
|
|
9
10
|
import { requireAllCriteriaPass, requireAllPlanCriteriaPass, requireEssentialCriteriaPass } from "./evidence.js";
|
|
10
11
|
import { codexGoalMode, isFinalRunCompletionCandidate } from "./goal-status.js";
|
|
11
12
|
import type { UlwLoopScope } from "./paths.js";
|
|
12
13
|
import { ulwLoopAttemptEvidenceDir } from "./paths.js";
|
|
13
|
-
import {
|
|
14
|
+
import { commit } from "./plan-commit.js";
|
|
15
|
+
import { readUlwLoopPlan, withUlwLoopMutationLock } from "./plan-io.js";
|
|
14
16
|
import {
|
|
15
17
|
classifyExternalAuthorizationBlocker,
|
|
16
18
|
clearGoalBlockerFields,
|
|
@@ -189,6 +191,7 @@ export async function checkpointUlwLoop(
|
|
|
189
191
|
codexGoal = validation.raw;
|
|
190
192
|
nextActions = validation.nextActions;
|
|
191
193
|
warnings = validation.warnings;
|
|
194
|
+
acknowledgeDriverObjective(plan, validation.unacknowledgedObjective);
|
|
192
195
|
} catch (error) {
|
|
193
196
|
if (!(error instanceof UlwLoopError)) throw error;
|
|
194
197
|
codexValidationError = error;
|
|
@@ -226,11 +229,11 @@ export async function checkpointUlwLoop(
|
|
|
226
229
|
if (aggregateCompletion !== undefined) plan.aggregateCompletion = aggregateCompletion;
|
|
227
230
|
if (aggregateCompletion !== undefined) nextActions = [...nextActions, 'aggregate complete — now update_goal({status:"complete"})'];
|
|
228
231
|
plan.updatedAt = now;
|
|
229
|
-
await writePlan(repoRoot, plan, scope);
|
|
230
232
|
const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
|
|
231
|
-
|
|
233
|
+
const entries = [ledgerEntry];
|
|
232
234
|
const closedBatch = args.status === "complete" ? batchClosedBy(plan, goal.id) : undefined;
|
|
233
|
-
if (closedBatch !== undefined)
|
|
235
|
+
if (closedBatch !== undefined) entries.push({ at: now, kind: "batch_closed", goalId: goal.id, message: closedBatch.batchId });
|
|
236
|
+
await commit(repoRoot, scope, { plan, entries });
|
|
234
237
|
return aggregateCompletion === undefined
|
|
235
238
|
? { plan, goal, ledgerEntry, nextActions, warnings }
|
|
236
239
|
: { plan, goal, ledgerEntry, aggregateCompletion, nextActions, warnings };
|
|
@@ -22,13 +22,19 @@ export interface CodexGoalSnapshot {
|
|
|
22
22
|
export interface CodexGoalReconciliation {
|
|
23
23
|
ok: boolean;
|
|
24
24
|
snapshot: CodexGoalSnapshot;
|
|
25
|
+
/** Facts to know (a differing driver objective); never something to do. */
|
|
25
26
|
warnings: string[];
|
|
27
|
+
/** Things to do next (create, re-create, or resume the driver goal). */
|
|
28
|
+
nextActions: string[];
|
|
26
29
|
errors: string[];
|
|
30
|
+
/** The differing objective this reconciliation reported for the first time, for the caller to acknowledge. */
|
|
31
|
+
unacknowledgedObjective?: string;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
export interface ReconcileCodexGoalOptions {
|
|
30
35
|
expectedObjective: string;
|
|
31
36
|
readonly acceptedObjectives?: readonly string[];
|
|
37
|
+
readonly acknowledgedObjectives?: readonly string[];
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export class CodexGoalSnapshotError extends Error {}
|
|
@@ -111,35 +117,41 @@ export function reconcileCodexGoalSnapshot(
|
|
|
111
117
|
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
112
118
|
const errors: string[] = [];
|
|
113
119
|
const warnings: string[] = [];
|
|
120
|
+
const nextActions: string[] = [];
|
|
114
121
|
|
|
115
122
|
const expected = options.expectedObjective;
|
|
116
|
-
const normalizedExpected = normalizeObjective(expected);
|
|
117
123
|
if (!effectiveSnapshot.available) {
|
|
118
|
-
|
|
119
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
124
|
+
nextActions.push(`call get_goal; if none, create_goal with codexObjective "${expected}" verbatim`);
|
|
125
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, nextActions, errors };
|
|
120
126
|
}
|
|
121
127
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective)),
|
|
126
|
-
].filter(Boolean),
|
|
127
|
-
);
|
|
128
|
+
const normalized = (objectives: readonly string[]) => new Set(objectives.map(normalizeObjective).filter(Boolean));
|
|
129
|
+
const accepted = normalized([expected, ...(options.acceptedObjectives ?? [])]);
|
|
130
|
+
const acknowledged = normalized(options.acknowledgedObjectives ?? []);
|
|
128
131
|
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
129
|
-
|
|
132
|
+
let unacknowledgedObjective: string | undefined;
|
|
133
|
+
if (actual && !accepted.has(actual) && !acknowledged.has(actual)) {
|
|
130
134
|
warnings.push(`driver_objective_differs: expected "${expected}", got "${actual}".`);
|
|
135
|
+
unacknowledgedObjective = actual;
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
134
139
|
if (actualStatus === "paused" || actualStatus === "usage_limited" || actualStatus === "budget_limited") {
|
|
135
|
-
|
|
140
|
+
nextActions.push("/goal resume or raise the budget");
|
|
136
141
|
}
|
|
137
142
|
if (actualStatus === "complete")
|
|
138
|
-
|
|
139
|
-
return {
|
|
143
|
+
nextActions.push(`driver closed early: call create_goal with codexObjective "${expected}" verbatim`);
|
|
144
|
+
return {
|
|
145
|
+
ok: errors.length === 0,
|
|
146
|
+
snapshot: effectiveSnapshot,
|
|
147
|
+
warnings,
|
|
148
|
+
nextActions,
|
|
149
|
+
errors,
|
|
150
|
+
...(unacknowledgedObjective === undefined ? {} : { unacknowledgedObjective }),
|
|
151
|
+
};
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
export function formatCodexGoalReconciliation(reconciliation: CodexGoalReconciliation): string {
|
|
143
|
-
const parts = [...reconciliation.errors, ...reconciliation.warnings];
|
|
155
|
+
const parts = [...reconciliation.errors, ...reconciliation.nextActions, ...reconciliation.warnings];
|
|
144
156
|
return parts.join(" ");
|
|
145
157
|
}
|
|
@@ -19,6 +19,7 @@ export interface UlwLoopSuccessCriterion {
|
|
|
19
19
|
status: UlwLoopCriterionStatus;
|
|
20
20
|
capturedAt?: string;
|
|
21
21
|
notes?: string;
|
|
22
|
+
artifacts?: string[];
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface UlwLoopItem {
|
|
@@ -63,6 +64,9 @@ export interface UlwLoopValidationBatch {
|
|
|
63
64
|
|
|
64
65
|
export interface UlwLoopPlan {
|
|
65
66
|
version: 1;
|
|
67
|
+
revision?: number;
|
|
68
|
+
brief?: string;
|
|
69
|
+
ledgerResetRevision?: number;
|
|
66
70
|
evidenceLayoutVersion?: 2;
|
|
67
71
|
createdAt: string;
|
|
68
72
|
updatedAt: string;
|
|
@@ -72,6 +76,7 @@ export interface UlwLoopPlan {
|
|
|
72
76
|
codexGoalMode?: UlwLoopCodexGoalMode;
|
|
73
77
|
codexObjective?: string;
|
|
74
78
|
codexObjectiveAliases?: string[];
|
|
79
|
+
acknowledgedDriverObjectives?: string[];
|
|
75
80
|
aggregateCompletion?: UlwLoopAggregateCompletion;
|
|
76
81
|
activeGoalId?: string;
|
|
77
82
|
validationBatches?: readonly UlwLoopValidationBatch[];
|
|
@@ -158,6 +163,8 @@ export interface UlwLoopQualityGateSenpi extends UlwLoopQualityGateCommon {
|
|
|
158
163
|
export type UlwLoopQualityGate = UlwLoopQualityGateLazycodex | UlwLoopQualityGateSenpi;
|
|
159
164
|
|
|
160
165
|
export interface UlwLoopLedgerEntry {
|
|
166
|
+
revision?: number;
|
|
167
|
+
id?: string;
|
|
161
168
|
at: string;
|
|
162
169
|
kind: UlwLoopLedgerEventKind;
|
|
163
170
|
goalId?: string;
|
|
@@ -168,6 +175,7 @@ export interface UlwLoopLedgerEntry {
|
|
|
168
175
|
codexGoal?: unknown;
|
|
169
176
|
evidence?: string;
|
|
170
177
|
capturedEvidence?: string;
|
|
178
|
+
artifacts?: string[];
|
|
171
179
|
qualityGate?: unknown;
|
|
172
180
|
steering?: UlwLoopSteeringAudit;
|
|
173
181
|
before?: unknown;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { UlwLoopPlan } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function normalizeDriverObjective(value: string): string {
|
|
4
|
+
return value.replace(/\s+/g, " ").trim();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function acknowledgedDriverObjectives(plan: UlwLoopPlan): readonly string[] {
|
|
8
|
+
return plan.acknowledgedDriverObjectives ?? [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// A differing driver objective is reported once; the plan remembers the exact text so every later
|
|
12
|
+
// checkpoint under the same driver stays quiet, while a driver rewritten to a third objective is
|
|
13
|
+
// reported again.
|
|
14
|
+
export function acknowledgeDriverObjective(plan: UlwLoopPlan, objective: string | undefined): boolean {
|
|
15
|
+
if (objective === undefined) return false;
|
|
16
|
+
const normalized = normalizeDriverObjective(objective);
|
|
17
|
+
if (!normalized || acknowledgedDriverObjectives(plan).includes(normalized)) return false;
|
|
18
|
+
plan.acknowledgedDriverObjectives = [...acknowledgedDriverObjectives(plan), normalized];
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { UlwLoopError } from "./types.js";
|
|
4
|
+
|
|
5
|
+
function fail(message: string, code: string, details: Record<string, unknown>): never {
|
|
6
|
+
throw new UlwLoopError(message, code, { details });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function stored(repoRoot: string, absolute: string): string {
|
|
10
|
+
const rel = relative(repoRoot, absolute);
|
|
11
|
+
const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
12
|
+
return inside ? rel.split(sep).join("/") : absolute;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Artifacts are bound at record time: a cited path that does not exist is a claim without a file,
|
|
16
|
+
// so the whole record is refused before any state changes. Paths inside the session cwd persist
|
|
17
|
+
// repo-relative (portable across machines); paths outside persist absolute.
|
|
18
|
+
export function resolveEvidenceArtifacts(
|
|
19
|
+
repoRoot: string,
|
|
20
|
+
artifacts: readonly unknown[] | undefined,
|
|
21
|
+
): string[] | undefined {
|
|
22
|
+
if (artifacts === undefined) return undefined;
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
const resolved: string[] = [];
|
|
25
|
+
for (const [index, candidate] of artifacts.entries()) {
|
|
26
|
+
const path = typeof candidate === "string" ? candidate.trim() : "";
|
|
27
|
+
if (!path) fail(`Artifact ${index + 1} must be a non-empty path.`, "ULW_LOOP_ARGUMENT_INVALID", { index });
|
|
28
|
+
const absolute = resolve(repoRoot, path);
|
|
29
|
+
if (!existsSync(absolute))
|
|
30
|
+
fail(
|
|
31
|
+
`Evidence artifact does not exist: ${path} (resolved to ${absolute}).`,
|
|
32
|
+
"ULW_LOOP_EVIDENCE_ARTIFACT_MISSING",
|
|
33
|
+
{
|
|
34
|
+
path,
|
|
35
|
+
resolved: absolute,
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
const value = stored(repoRoot, absolute);
|
|
39
|
+
if (!seen.has(value)) {
|
|
40
|
+
seen.add(value);
|
|
41
|
+
resolved.push(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { resolveEvidenceArtifacts } from "./evidence-artifacts.js";
|
|
1
2
|
import { essentialCriteriaOf, hasAllCriteriaPass, hasEssentialCriteriaPass } from "./goal-status.js";
|
|
2
3
|
import type { UlwLoopScope } from "./paths.js";
|
|
3
|
-
import {
|
|
4
|
+
import { commit } from "./plan-commit.js";
|
|
5
|
+
import { readUlwLoopPlan, withUlwLoopMutationLock } from "./plan-io.js";
|
|
4
6
|
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
|
|
5
7
|
import { iso, UlwLoopError } from "./types.js";
|
|
6
8
|
|
|
@@ -11,6 +13,7 @@ type RecordEvidenceArgs = {
|
|
|
11
13
|
readonly status: EvidenceStatus;
|
|
12
14
|
readonly evidence: string;
|
|
13
15
|
readonly notes?: string;
|
|
16
|
+
readonly artifacts?: readonly string[];
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
function ulwLoopFail(message: string, code: string, details: Record<string, unknown>): never {
|
|
@@ -66,6 +69,7 @@ export async function recordEvidence(
|
|
|
66
69
|
const goal = findGoal(plan, args.goalId);
|
|
67
70
|
const criterion = findCriterion(goal, args.criterionId);
|
|
68
71
|
const evidence = nonEmptyEvidence(args.evidence);
|
|
72
|
+
const artifacts = resolveEvidenceArtifacts(repoRoot, args.artifacts);
|
|
69
73
|
const kind = ledgerKind(args.status);
|
|
70
74
|
const prevStatus = criterion.status;
|
|
71
75
|
const capturedAt = iso();
|
|
@@ -73,9 +77,10 @@ export async function recordEvidence(
|
|
|
73
77
|
criterion.capturedEvidence = evidence;
|
|
74
78
|
criterion.capturedAt = capturedAt;
|
|
75
79
|
if (args.notes !== undefined) criterion.notes = args.notes;
|
|
80
|
+
if (artifacts !== undefined) criterion.artifacts = artifacts;
|
|
81
|
+
else delete criterion.artifacts;
|
|
76
82
|
goal.updatedAt = capturedAt;
|
|
77
83
|
plan.updatedAt = capturedAt;
|
|
78
|
-
await writePlan(repoRoot, plan, scope);
|
|
79
84
|
const ledgerEntry: UlwLoopLedgerEntry = {
|
|
80
85
|
at: capturedAt,
|
|
81
86
|
kind,
|
|
@@ -84,10 +89,11 @@ export async function recordEvidence(
|
|
|
84
89
|
criterionStatus: args.status,
|
|
85
90
|
evidence,
|
|
86
91
|
capturedEvidence: evidence,
|
|
92
|
+
...(artifacts === undefined ? {} : { artifacts }),
|
|
87
93
|
before: { status: prevStatus },
|
|
88
94
|
after: { goalId: goal.id, criterionId: criterion.id, status: args.status, evidence, capturedAt, prevStatus },
|
|
89
95
|
};
|
|
90
|
-
await
|
|
96
|
+
await commit(repoRoot, scope, { plan, entries: [ledgerEntry] });
|
|
91
97
|
return { plan, goal, criterion, ledgerEntry };
|
|
92
98
|
});
|
|
93
99
|
}
|
|
@@ -112,22 +118,23 @@ export async function markCriteriaPendingResetForGoal(
|
|
|
112
118
|
criterion.capturedEvidence = null;
|
|
113
119
|
delete criterion.capturedAt;
|
|
114
120
|
delete criterion.notes;
|
|
121
|
+
delete criterion.artifacts;
|
|
115
122
|
}
|
|
116
123
|
goal.updatedAt = now;
|
|
117
124
|
plan.updatedAt = now;
|
|
118
|
-
await
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
125
|
+
await commit(repoRoot, scope, {
|
|
126
|
+
plan,
|
|
127
|
+
entries: [
|
|
128
|
+
{
|
|
129
|
+
at: now,
|
|
130
|
+
kind: "criteria_revised",
|
|
131
|
+
goalId,
|
|
132
|
+
message: `Reset ${goal.successCriteria.length} criteria to pending.`,
|
|
133
|
+
before,
|
|
134
|
+
after: { resetCount: goal.successCriteria.length },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
131
138
|
return { plan, resetCount: goal.successCriteria.length };
|
|
132
139
|
});
|
|
133
140
|
}
|