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,3 +1,4 @@
|
|
|
1
|
+
import { criteriaFromInput, criterionId } from "./success-criteria-input.js";
|
|
1
2
|
import { UlwLoopError } from "./types.js";
|
|
2
3
|
function cleanLine(line) {
|
|
3
4
|
return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
|
|
@@ -30,40 +31,39 @@ function assertNonEmpty(value, label) {
|
|
|
30
31
|
function truncateObjective(objective) {
|
|
31
32
|
return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
// The placeholder must name the exact call that replaces it on the surface the plan is driven from,
|
|
35
|
+
// so a goal seeded without criteria costs one documented call per criterion instead of a doc hunt.
|
|
36
|
+
function replaceVia(goalId, id, surface) {
|
|
37
|
+
return surface === "omo-senpi"
|
|
38
|
+
? `Replace via agentToolkit.steer({ kind: "revise_criterion", source: "finding", goalId: "${goalId}", criterionId: "${id}", scenario, expectedEvidence, evidence, rationale }) (or pass successCriteria to addGoal)`
|
|
39
|
+
: `Replace via omo-agent-toolkit ulw-loop steer --kind revise_criterion --goal-id ${goalId} --criterion-id ${id} --scenario "<scenario>" --expected-evidence "<proof>" --evidence "<why>" --rationale "<why>"`;
|
|
40
|
+
}
|
|
41
|
+
export function seedDefaultSuccessCriteria(goalIndex, objective, options = {}) {
|
|
34
42
|
const subject = truncateObjective(normalizeObjective(objective) || `Goal ${goalIndex + 1}`);
|
|
43
|
+
const goalId = options.goalId ?? `G${String(goalIndex + 1).padStart(3, "0")}`;
|
|
44
|
+
const surface = options.surface ?? "lazycodex";
|
|
35
45
|
const rows = [
|
|
46
|
+
["happy", `happy path for: ${subject}`, `observable happy-path proof for goal ${goalIndex + 1}`, true],
|
|
47
|
+
["edge", "edge case (boundary/empty/malformed)", `boundary or malformed-input proof for: ${subject}`, true],
|
|
36
48
|
[
|
|
37
|
-
"C001",
|
|
38
|
-
"happy",
|
|
39
|
-
`happy path for: ${subject}`,
|
|
40
|
-
`Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`,
|
|
41
|
-
true,
|
|
42
|
-
],
|
|
43
|
-
[
|
|
44
|
-
"C002",
|
|
45
|
-
"edge",
|
|
46
|
-
"edge case (boundary/empty/malformed)",
|
|
47
|
-
`Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`,
|
|
48
|
-
true,
|
|
49
|
-
],
|
|
50
|
-
[
|
|
51
|
-
"C003",
|
|
52
49
|
"regression",
|
|
53
50
|
"regression: adjacent surface still works",
|
|
54
|
-
`
|
|
51
|
+
`regression proof for neighboring behavior after: ${subject}`,
|
|
55
52
|
false,
|
|
56
53
|
],
|
|
57
54
|
];
|
|
58
|
-
return rows.map(([
|
|
59
|
-
id
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
return rows.map(([userModel, scenario, proof, essential], index) => {
|
|
56
|
+
const id = criterionId(index);
|
|
57
|
+
return {
|
|
58
|
+
id,
|
|
59
|
+
scenario,
|
|
60
|
+
userModel,
|
|
61
|
+
expectedEvidence: `${replaceVia(goalId, id, surface)} with ${proof}.`,
|
|
62
|
+
essential,
|
|
63
|
+
capturedEvidence: null,
|
|
64
|
+
status: "pending",
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
67
|
}
|
|
68
68
|
export function deriveGoalCandidates(brief) {
|
|
69
69
|
const bulletGoals = brief
|
|
@@ -87,22 +87,29 @@ export function deriveGoalCandidates(brief) {
|
|
|
87
87
|
objective,
|
|
88
88
|
}));
|
|
89
89
|
}
|
|
90
|
-
export function makeGoal(title, objective, index, now) {
|
|
90
|
+
export function makeGoal(title, objective, index, now, options = {}) {
|
|
91
91
|
const cleanTitle = assertNonEmpty(title, "title");
|
|
92
92
|
const cleanObjective = assertNonEmpty(objective, "objective");
|
|
93
|
+
const id = normalizeGoalId(cleanTitle, index);
|
|
94
|
+
const successCriteria = options.successCriteria === undefined
|
|
95
|
+
? seedDefaultSuccessCriteria(index, cleanObjective, {
|
|
96
|
+
goalId: id,
|
|
97
|
+
...(options.surface === undefined ? {} : { surface: options.surface }),
|
|
98
|
+
})
|
|
99
|
+
: criteriaFromInput(options.successCriteria);
|
|
93
100
|
return {
|
|
94
|
-
id
|
|
101
|
+
id,
|
|
95
102
|
title: cleanTitle,
|
|
96
103
|
objective: cleanObjective,
|
|
97
104
|
status: "pending",
|
|
98
|
-
successCriteria
|
|
105
|
+
successCriteria,
|
|
99
106
|
attempt: 0,
|
|
100
107
|
createdAt: now,
|
|
101
108
|
updatedAt: now,
|
|
102
109
|
};
|
|
103
110
|
}
|
|
104
|
-
export function appendGoalToPlan(plan, title, objective, now) {
|
|
105
|
-
const goal = makeGoal(title, objective, plan.goals.length, now);
|
|
111
|
+
export function appendGoalToPlan(plan, title, objective, now, options = {}) {
|
|
112
|
+
const goal = makeGoal(title, objective, plan.goals.length, now, options);
|
|
106
113
|
plan.goals.push(goal);
|
|
107
114
|
plan.updatedAt = now;
|
|
108
115
|
return goal;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { type UlwLoopScope } from "./paths.js";
|
|
2
|
-
import
|
|
2
|
+
import { type StateLockOptions } from "./state-lock.js";
|
|
3
|
+
import { type UlwLoopLedgerEntry, type UlwLoopPlan } from "./types.js";
|
|
4
|
+
export declare function readLedger(repoRoot: string, scope?: UlwLoopScope): UlwLoopLedgerEntry[];
|
|
5
|
+
export { planExists } from "./plan-log.js";
|
|
6
|
+
export declare function withMutationLockOptions<T>(options: StateLockOptions, fn: () => Promise<T>): Promise<T>;
|
|
7
|
+
export declare function assertStateLockOwned(lockPath: string): void;
|
|
8
|
+
export declare function migrationEntries(plan: UlwLoopPlan): readonly UlwLoopLedgerEntry[];
|
|
3
9
|
export declare function withUlwLoopMutationLock<T>(repoRoot: string, fn: () => Promise<T>): Promise<T>;
|
|
4
|
-
export declare function withUlwLoopMutationLock<T>(repoRoot: string, scope: UlwLoopScope | undefined, fn: () => Promise<T
|
|
10
|
+
export declare function withUlwLoopMutationLock<T>(repoRoot: string, scope: UlwLoopScope | undefined, fn: () => Promise<T>, options?: StateLockOptions): Promise<T>;
|
|
11
|
+
export declare function readUlwLoopPlanSync(repoRoot: string, scope?: UlwLoopScope): UlwLoopPlan;
|
|
5
12
|
export declare function readUlwLoopPlan(repoRoot: string, scope?: UlwLoopScope): Promise<UlwLoopPlan>;
|
|
6
13
|
export declare function listUlwLoopSessionIds(repoRoot: string): readonly string[];
|
|
7
14
|
export declare function writePlan(repoRoot: string, plan: UlwLoopPlan, scope?: UlwLoopScope): Promise<void>;
|
|
8
15
|
export declare function appendLedger(repoRoot: string, entry: UlwLoopLedgerEntry, scope?: UlwLoopScope): Promise<void>;
|
|
9
16
|
export declare function appendLedgerEntries(repoRoot: string, entries: readonly UlwLoopLedgerEntry[], scope?: UlwLoopScope): Promise<void>;
|
|
10
17
|
export declare function readSteeringLedgerEntries(repoRoot: string, scope?: UlwLoopScope): Promise<UlwLoopLedgerEntry[]>;
|
|
11
|
-
/**
|
|
12
|
-
* First accepted steering entry matching an idempotency key/prompt signature.
|
|
13
|
-
* A cheap substring probe on the raw line skips JSON.parse for the vast
|
|
14
|
-
* majority of entries, so dedup stays flat even on legacy multi-MB ledgers.
|
|
15
|
-
*/
|
|
16
18
|
export declare function findAcceptedSteeringLedgerEntry(repoRoot: string, key: string, scope?: UlwLoopScope): Promise<UlwLoopLedgerEntry | undefined>;
|
|
@@ -1,46 +1,88 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import {
|
|
3
|
-
import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
-
import { createInterface } from "node:readline";
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
5
3
|
import { aggregateCodexObjectiveForScope } from "./goal-status.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { readLedger as reconcileLedger } from "./ledger.js";
|
|
5
|
+
import { repoRelative, ulwLoopDir, ulwLoopGoalsPath, ulwLoopRelativeDir, ulwLoopStateLockPath, } from "./paths.js";
|
|
6
|
+
import { commit, materialize, materializeSync } from "./plan-commit.js";
|
|
7
|
+
import { readOptional, reconcilePlan } from "./plan-log.js";
|
|
8
|
+
import { planMissingError } from "./plan-missing-recovery.js";
|
|
8
9
|
import { withStateLock } from "./state-lock.js";
|
|
9
|
-
import { iso, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
|
|
10
|
+
import { iso, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError, } from "./types.js";
|
|
11
|
+
export function readLedger(repoRoot, scope) {
|
|
12
|
+
const lockPath = ulwLoopStateLockPath(repoRoot, scope);
|
|
13
|
+
if (heldLocks.getStore()?.has(lockPath)) {
|
|
14
|
+
assertStateLockOwned(lockPath);
|
|
15
|
+
materializeSync(ulwLoopDir(repoRoot, scope));
|
|
16
|
+
}
|
|
17
|
+
return reconcileLedger(repoRoot, scope);
|
|
18
|
+
}
|
|
19
|
+
export { planExists } from "./plan-log.js";
|
|
10
20
|
const LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
|
|
11
21
|
const LEGACY_OBJECTIVE = `Complete all ulw-loop stories listed in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}. Use ${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER} as the durable audit trail.`;
|
|
12
22
|
const locks = new Map();
|
|
13
|
-
// Tracks which state dirs the CURRENT async continuation holds, so a read nested
|
|
14
|
-
// inside a locked mutation can tell itself apart from an unlocked read elsewhere
|
|
15
|
-
// in the same process.
|
|
16
23
|
const heldLocks = new AsyncLocalStorage();
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
const lockOptions = new AsyncLocalStorage();
|
|
25
|
+
export function withMutationLockOptions(options, fn) {
|
|
26
|
+
return lockOptions.run(options, fn);
|
|
19
27
|
}
|
|
20
|
-
function
|
|
21
|
-
|
|
28
|
+
function tokenAt(lockPath) {
|
|
29
|
+
const raw = readOptional(lockPath);
|
|
30
|
+
if (raw === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
try {
|
|
33
|
+
const record = JSON.parse(raw);
|
|
34
|
+
return typeof record === "object" && record !== null && "token" in record && typeof record.token === "string"
|
|
35
|
+
? record.token
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error instanceof SyntaxError)
|
|
40
|
+
return undefined;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
22
43
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
export function assertStateLockOwned(lockPath) {
|
|
45
|
+
const context = heldLocks.getStore()?.get(lockPath);
|
|
46
|
+
if (context === undefined)
|
|
47
|
+
return;
|
|
48
|
+
if (tokenAt(lockPath) !== context.token)
|
|
49
|
+
throw new UlwLoopError("The ulw-loop mutation lock changed owners.", "ULW_LOOP_LOCK_LOST");
|
|
50
|
+
}
|
|
51
|
+
export function migrationEntries(plan) {
|
|
52
|
+
for (const context of heldLocks.getStore()?.values() ?? []) {
|
|
53
|
+
const entries = context.migrations.get(plan.goalsPath);
|
|
54
|
+
if (entries !== undefined) {
|
|
55
|
+
context.migrations.delete(plan.goalsPath);
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
28
60
|
}
|
|
29
|
-
export async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
|
|
61
|
+
export async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn, options = {}) {
|
|
30
62
|
const scope = typeof scopeOrFn === "function" ? undefined : scopeOrFn;
|
|
31
63
|
const fn = typeof scopeOrFn === "function" ? scopeOrFn : maybeFn;
|
|
32
64
|
if (fn === undefined)
|
|
33
65
|
throw new UlwLoopError("Missing ulw-loop mutation body.", "ULW_LOOP_LOCK_BODY_MISSING");
|
|
34
66
|
const lockKey = `${repoRoot}\0${ulwLoopRelativeDir(scope)}`;
|
|
35
67
|
const lockPath = ulwLoopStateLockPath(repoRoot, scope);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
68
|
+
const locked = () => withStateLock(lockPath, async (token) => {
|
|
69
|
+
return heldLocks.run(new Map([...(heldLocks.getStore() ?? []), [lockPath, { token, migrations: new Map() }]]), async () => {
|
|
70
|
+
for (let attempt = 0;; attempt += 1) {
|
|
71
|
+
try {
|
|
72
|
+
return await fn();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (!(error instanceof UlwLoopError) || error.code !== "ULW_LOOP_PUBLISH_CONFLICT")
|
|
76
|
+
throw error;
|
|
77
|
+
assertStateLockOwned(lockPath);
|
|
78
|
+
if (attempt !== 0)
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}, { ...lockOptions.getStore(), ...options });
|
|
39
84
|
const prior = locks.get(lockKey) ?? Promise.resolve(undefined);
|
|
40
85
|
const run = prior.then(locked, locked);
|
|
41
|
-
// The stored gate resolves to undefined so the map never retains fn's result
|
|
42
|
-
// (plans/audits), and it removes itself once no newer waiter replaced it —
|
|
43
|
-
// otherwise a long-lived host leaks one entry per (repo, scope) forever.
|
|
44
86
|
const gate = run.then(() => undefined, () => undefined);
|
|
45
87
|
locks.set(lockKey, gate);
|
|
46
88
|
void gate.then(() => {
|
|
@@ -49,47 +91,40 @@ export async function withUlwLoopMutationLock(repoRoot, scopeOrFn, maybeFn) {
|
|
|
49
91
|
});
|
|
50
92
|
return run;
|
|
51
93
|
}
|
|
52
|
-
export
|
|
94
|
+
export function readUlwLoopPlanSync(repoRoot, scope) {
|
|
53
95
|
const path = ulwLoopGoalsPath(repoRoot, scope);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
catch (error) {
|
|
59
|
-
if (!hasCode(error, "ENOENT"))
|
|
60
|
-
throw error;
|
|
61
|
-
const recovery = planMissingRecovery(listUlwLoopSessionIds(repoRoot));
|
|
62
|
-
throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.\n${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...(recovery.details === undefined ? {} : { details: recovery.details }) });
|
|
63
|
-
}
|
|
64
|
-
const parsed = JSON.parse(raw);
|
|
65
|
-
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
|
|
96
|
+
const parsed = reconcilePlan(ulwLoopDir(repoRoot, scope));
|
|
97
|
+
if (parsed === undefined)
|
|
98
|
+
throw planMissingError(repoRelative(path, repoRoot), listUlwLoopSessionIds(repoRoot));
|
|
99
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.goals))
|
|
66
100
|
throw new UlwLoopError(`Invalid ulw-loop plan at ${repoRelative(path, repoRoot)}.`, "ULW_LOOP_PLAN_INVALID");
|
|
67
|
-
}
|
|
68
101
|
const previousObjective = parsed.codexObjective;
|
|
69
102
|
if ((parsed.codexGoalMode ?? "per_story") === "aggregate" &&
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// A read path (status/criteria) must not mutate state: mutating here runs
|
|
73
|
-
// unlocked and a second reader could write a partially-migrated plan.
|
|
74
|
-
throw new UlwLoopError(`The ulw-loop plan at ${repoRelative(path, repoRoot)} carries a legacy enumerated aggregate objective that must be migrated before reads continue. Run any state-mutating ulw-loop command once (e.g. \`record-evidence\`, \`steer\`, \`checkpoint\`) to migrate it under the state lock, then retry.`, "ULW_LOOP_MIGRATION_REQUIRED");
|
|
75
|
-
}
|
|
76
|
-
const now = iso();
|
|
103
|
+
previousObjective !== undefined &&
|
|
104
|
+
(previousObjective === LEGACY_OBJECTIVE || previousObjective.startsWith(LEGACY_OBJECTIVE_PREFIX))) {
|
|
77
105
|
parsed.codexObjective = aggregateCodexObjectiveForScope(scope);
|
|
78
106
|
parsed.codexObjectiveAliases = [...new Set([...(parsed.codexObjectiveAliases ?? []), previousObjective])];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
107
|
+
heldLocks
|
|
108
|
+
.getStore()
|
|
109
|
+
?.get(ulwLoopStateLockPath(repoRoot, scope))
|
|
110
|
+
?.migrations.set(parsed.goalsPath, [
|
|
111
|
+
{
|
|
112
|
+
at: iso(),
|
|
113
|
+
kind: "aggregate_objective_migrated",
|
|
114
|
+
before: { codexObjective: previousObjective },
|
|
115
|
+
after: { codexObjective: parsed.codexObjective },
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
88
118
|
}
|
|
89
119
|
return parsed;
|
|
90
120
|
}
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
export async function readUlwLoopPlan(repoRoot, scope) {
|
|
122
|
+
if (heldLocks.getStore()?.has(ulwLoopStateLockPath(repoRoot, scope))) {
|
|
123
|
+
assertStateLockOwned(ulwLoopStateLockPath(repoRoot, scope));
|
|
124
|
+
await materialize(ulwLoopDir(repoRoot, scope));
|
|
125
|
+
}
|
|
126
|
+
return readUlwLoopPlanSync(repoRoot, scope);
|
|
127
|
+
}
|
|
93
128
|
export function listUlwLoopSessionIds(repoRoot) {
|
|
94
129
|
try {
|
|
95
130
|
return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true })
|
|
@@ -102,72 +137,28 @@ export function listUlwLoopSessionIds(repoRoot) {
|
|
|
102
137
|
}
|
|
103
138
|
}
|
|
104
139
|
export async function writePlan(repoRoot, plan, scope) {
|
|
105
|
-
await
|
|
106
|
-
const path = ulwLoopGoalsPath(repoRoot, scope);
|
|
107
|
-
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
108
|
-
await writeFile(tmpPath, `${JSON.stringify(plan, null, 2)}\n`, "utf8");
|
|
109
|
-
await rename(tmpPath, path);
|
|
140
|
+
await commit(repoRoot, scope, { plan, entries: [] });
|
|
110
141
|
}
|
|
111
142
|
export async function appendLedger(repoRoot, entry, scope) {
|
|
112
143
|
await appendLedgerEntries(repoRoot, [entry], scope);
|
|
113
144
|
}
|
|
114
145
|
export async function appendLedgerEntries(repoRoot, entries, scope) {
|
|
115
|
-
if (entries.length
|
|
116
|
-
|
|
117
|
-
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
|
|
118
|
-
await appendFile(ulwLoopLedgerPath(repoRoot, scope), `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf8");
|
|
146
|
+
if (entries.length > 0)
|
|
147
|
+
await commit(repoRoot, scope, { plan: await readUlwLoopPlan(repoRoot, scope), entries });
|
|
119
148
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
async function* ledgerLines(repoRoot, scope) {
|
|
126
|
-
const stream = createReadStream(ulwLoopLedgerPath(repoRoot, scope), { encoding: "utf8" });
|
|
127
|
-
const lines = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
128
|
-
try {
|
|
129
|
-
for await (const line of lines) {
|
|
130
|
-
if (line.trim().length > 0)
|
|
131
|
-
yield line;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
if (!hasCode(error, "ENOENT"))
|
|
136
|
-
throw error;
|
|
137
|
-
}
|
|
138
|
-
finally {
|
|
139
|
-
lines.close();
|
|
140
|
-
stream.destroy();
|
|
141
|
-
}
|
|
149
|
+
function isSteeringKind(value) {
|
|
150
|
+
return (value === "steering_accepted" ||
|
|
151
|
+
value === "steering_rejected" ||
|
|
152
|
+
value === "criteria_revised" ||
|
|
153
|
+
value === "batch_updated");
|
|
142
154
|
}
|
|
143
155
|
export async function readSteeringLedgerEntries(repoRoot, scope) {
|
|
144
|
-
|
|
145
|
-
for await (const line of ledgerLines(repoRoot, scope)) {
|
|
146
|
-
const entry = JSON.parse(line);
|
|
147
|
-
if (isSteeringKind(entry.kind))
|
|
148
|
-
entries.push(entry);
|
|
149
|
-
}
|
|
150
|
-
return entries;
|
|
156
|
+
return readLedger(repoRoot, scope).filter((entry) => isSteeringKind(entry.kind));
|
|
151
157
|
}
|
|
152
|
-
/**
|
|
153
|
-
* First accepted steering entry matching an idempotency key/prompt signature.
|
|
154
|
-
* A cheap substring probe on the raw line skips JSON.parse for the vast
|
|
155
|
-
* majority of entries, so dedup stays flat even on legacy multi-MB ledgers.
|
|
156
|
-
*/
|
|
157
158
|
export async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
continue;
|
|
162
|
-
const entry = JSON.parse(line);
|
|
163
|
-
if (!isSteeringKind(entry.kind))
|
|
164
|
-
continue;
|
|
165
|
-
if (entry.steering?.invariant.accepted !== true)
|
|
166
|
-
continue;
|
|
167
|
-
if (entry.idempotencyKey === key ||
|
|
159
|
+
return readLedger(repoRoot, scope).find((entry) => isSteeringKind(entry.kind) &&
|
|
160
|
+
entry.steering?.invariant.accepted === true &&
|
|
161
|
+
(entry.idempotencyKey === key ||
|
|
168
162
|
entry.steering.idempotencyKey === key ||
|
|
169
|
-
entry.steering.promptSignature === key)
|
|
170
|
-
return entry;
|
|
171
|
-
}
|
|
172
|
-
return undefined;
|
|
163
|
+
entry.steering.promptSignature === key));
|
|
173
164
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type UlwLoopScope } from "./paths.js";
|
|
2
|
+
import type { UlwLoopLedgerEntry, UlwLoopPlan } from "./types.js";
|
|
3
|
+
export interface PlanCommitRecord {
|
|
4
|
+
readonly version: 1;
|
|
5
|
+
readonly revision: number;
|
|
6
|
+
readonly plan: UlwLoopPlan;
|
|
7
|
+
readonly ledger: readonly UlwLoopLedgerEntry[];
|
|
8
|
+
}
|
|
9
|
+
export declare function hasCode(error: unknown, code: string): boolean;
|
|
10
|
+
export declare function readOptional(path: string): string | undefined;
|
|
11
|
+
export declare function logNames(dir: string): string[];
|
|
12
|
+
export declare function readRecords(dir: string): PlanCommitRecord[];
|
|
13
|
+
export declare function readNewestRecord(dir: string): PlanCommitRecord | undefined;
|
|
14
|
+
export declare function reconcilePlan(dir: string): UlwLoopPlan | undefined;
|
|
15
|
+
export declare function planExists(repoRoot: string, scope?: UlwLoopScope): boolean;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ulwLoopDir } from "./paths.js";
|
|
4
|
+
export function hasCode(error, code) {
|
|
5
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
6
|
+
}
|
|
7
|
+
export function readOptional(path) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(path, "utf8");
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (hasCode(error, "ENOENT"))
|
|
13
|
+
return undefined;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function logNames(dir) {
|
|
18
|
+
try {
|
|
19
|
+
return readdirSync(join(dir, "revisions"))
|
|
20
|
+
.filter((name) => /^\d{8,}\.json$/.test(name))
|
|
21
|
+
.sort();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (hasCode(error, "ENOENT"))
|
|
25
|
+
return [];
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function readRecord(dir, name) {
|
|
30
|
+
try {
|
|
31
|
+
const record = JSON.parse(readFileSync(join(dir, "revisions", name), "utf8"));
|
|
32
|
+
return record.version === 1 &&
|
|
33
|
+
Number.isInteger(record.revision) &&
|
|
34
|
+
record.plan?.version === 1 &&
|
|
35
|
+
Array.isArray(record.plan.goals) &&
|
|
36
|
+
Array.isArray(record.ledger)
|
|
37
|
+
? record
|
|
38
|
+
: undefined;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (!(error instanceof SyntaxError))
|
|
42
|
+
throw error;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Only the audit trail needs every record; plan reads use readNewestRecord.
|
|
47
|
+
export function readRecords(dir) {
|
|
48
|
+
const records = [];
|
|
49
|
+
for (const name of logNames(dir)) {
|
|
50
|
+
const record = readRecord(dir, name);
|
|
51
|
+
if (record !== undefined)
|
|
52
|
+
records.push(record);
|
|
53
|
+
}
|
|
54
|
+
return records.sort((a, b) => a.revision - b.revision);
|
|
55
|
+
}
|
|
56
|
+
// Every ulw-loop status probe reads the plan, each record embeds a full plan copy, and records are never
|
|
57
|
+
// deleted - so a plan read parses the newest record only, newest first, and stops there. Names are ordered
|
|
58
|
+
// by their parsed revision because a wider zero-padding would break lexicographic order.
|
|
59
|
+
export function readNewestRecord(dir) {
|
|
60
|
+
const names = logNames(dir).sort((a, b) => Number.parseInt(b, 10) - Number.parseInt(a, 10));
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
const record = readRecord(dir, name);
|
|
63
|
+
if (record !== undefined)
|
|
64
|
+
return record;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
export function reconcilePlan(dir) {
|
|
69
|
+
const raw = readOptional(join(dir, "goals.json"));
|
|
70
|
+
let cached;
|
|
71
|
+
if (raw !== undefined) {
|
|
72
|
+
try {
|
|
73
|
+
cached = JSON.parse(raw);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (!(error instanceof SyntaxError))
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const latest = readNewestRecord(dir);
|
|
81
|
+
return latest !== undefined && latest.revision >= (cached?.revision ?? 0) ? latest.plan : cached;
|
|
82
|
+
}
|
|
83
|
+
export function planExists(repoRoot, scope) {
|
|
84
|
+
const dir = ulwLoopDir(repoRoot, scope);
|
|
85
|
+
return existsSync(join(dir, "goals.json")) || logNames(dir).length > 0;
|
|
86
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { UlwLoopError } from "./runtime.js";
|
|
2
|
+
import type { UlwLoopToolkitSurface } from "./surface.js";
|
|
1
3
|
export declare const ULW_LOOP_CREATE_GOALS_COMMAND = "omo-agent-toolkit ulw-loop create-goals --brief \"<brief>\" --json";
|
|
4
|
+
export declare function createGoalsAction(surface: UlwLoopToolkitSurface): string;
|
|
2
5
|
export interface PlanMissingRecovery {
|
|
3
6
|
readonly message: string;
|
|
4
7
|
readonly details?: {
|
|
@@ -9,6 +12,7 @@ export interface PlanMissingRecovery {
|
|
|
9
12
|
* A missing plan is either "never bootstrapped" or "bootstrapped under a different
|
|
10
13
|
* session id"; the recovery text has to answer both without the caller guessing.
|
|
11
14
|
*/
|
|
12
|
-
export declare function planMissingRecovery(existingSessionIds: readonly string[]): PlanMissingRecovery;
|
|
15
|
+
export declare function planMissingRecovery(existingSessionIds: readonly string[], surface?: UlwLoopToolkitSurface): PlanMissingRecovery;
|
|
16
|
+
export declare function planMissingError(planPath: string, existingSessionIds: readonly string[], surface?: UlwLoopToolkitSurface): UlwLoopError;
|
|
13
17
|
export declare function sessionScopeRequiredMessage(flag: string, existingSessionIds: readonly string[]): string;
|
|
14
18
|
export declare function sessionIdRequiredMessage(flag: string): string;
|
|
@@ -1,15 +1,27 @@
|
|
|
1
|
+
import { UlwLoopError } from "./runtime.js";
|
|
1
2
|
export const ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
|
|
3
|
+
export function createGoalsAction(surface) {
|
|
4
|
+
return surface === "omo-senpi" ? 'agentToolkit.createGoals({ brief: "<brief>" })' : ULW_LOOP_CREATE_GOALS_COMMAND;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* A missing plan is either "never bootstrapped" or "bootstrapped under a different
|
|
4
8
|
* session id"; the recovery text has to answer both without the caller guessing.
|
|
5
9
|
*/
|
|
6
|
-
export function planMissingRecovery(existingSessionIds) {
|
|
7
|
-
const lines = [`Recovery: bootstrap the plan with \`${
|
|
10
|
+
export function planMissingRecovery(existingSessionIds, surface = "lazycodex") {
|
|
11
|
+
const lines = [`Recovery: bootstrap the plan with \`${createGoalsAction(surface)}\`.`];
|
|
8
12
|
if (existingSessionIds.length === 0)
|
|
9
13
|
return { message: lines.join("\n") };
|
|
10
|
-
lines.push(
|
|
14
|
+
lines.push(surface === "omo-senpi"
|
|
15
|
+
? `Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. The SDK is bound to the current session; resume the owning session to target its plan.`
|
|
16
|
+
: `Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
|
|
11
17
|
return { message: lines.join("\n"), details: { existingSessionIds } };
|
|
12
18
|
}
|
|
19
|
+
export function planMissingError(planPath, existingSessionIds, surface = "lazycodex") {
|
|
20
|
+
const recovery = planMissingRecovery(existingSessionIds, surface);
|
|
21
|
+
return new UlwLoopError(`No ulw-loop plan found at ${planPath}.\n${recovery.message}`, "ULW_LOOP_PLAN_MISSING", {
|
|
22
|
+
...(recovery.details === undefined ? {} : { details: recovery.details }),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
13
25
|
export function sessionScopeRequiredMessage(flag, existingSessionIds) {
|
|
14
26
|
const lines = [
|
|
15
27
|
"No ulw-loop session scope: neither the session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID) nor the flag names this run, and the shared .omo/ulw-loop root is never used implicitly because every session in this directory would read and overwrite it.",
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// biome-ignore-all format: compact port must stay within the requested pure LOC budget.
|
|
2
2
|
import { CodexGoalSnapshotError, formatCodexGoalReconciliation, readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot, } from "./codex-goal-snapshot.js";
|
|
3
|
+
import { acknowledgeDriverObjective, acknowledgedDriverObjectives } from "./driver-objective-ack.js";
|
|
3
4
|
import { compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
|
|
5
|
+
import { commit } from "./plan-commit.js";
|
|
4
6
|
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
|
|
5
|
-
import {
|
|
7
|
+
import { readUlwLoopPlan, withUlwLoopMutationLock } from "./plan-io.js";
|
|
6
8
|
import { iso, UlwLoopError } from "./types.js";
|
|
7
9
|
const BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
8
10
|
function ulwLoopError(message, code) {
|
|
@@ -17,12 +19,13 @@ function nextGoalId(plan) {
|
|
|
17
19
|
}
|
|
18
20
|
function appendBlockerGoal(plan, args, now) {
|
|
19
21
|
const index = plan.goals.length;
|
|
22
|
+
const id = nextGoalId(plan);
|
|
20
23
|
const goal = {
|
|
21
|
-
id
|
|
24
|
+
id,
|
|
22
25
|
title: args.title,
|
|
23
26
|
objective: args.objective,
|
|
24
27
|
status: "pending",
|
|
25
|
-
successCriteria: seedDefaultSuccessCriteria(index, args.objective),
|
|
28
|
+
successCriteria: seedDefaultSuccessCriteria(index, args.objective, { goalId: id }),
|
|
26
29
|
attempt: 0,
|
|
27
30
|
createdAt: now,
|
|
28
31
|
updatedAt: now,
|
|
@@ -44,9 +47,11 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
44
47
|
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
45
48
|
expectedObjective: expectedCodexObjective(plan, goal),
|
|
46
49
|
acceptedObjectives: compatibleCodexObjectives(plan),
|
|
50
|
+
acknowledgedObjectives: acknowledgedDriverObjectives(plan),
|
|
47
51
|
});
|
|
48
52
|
if (!reconciliation.ok)
|
|
49
53
|
throw new CodexGoalSnapshotError(formatCodexGoalReconciliation(reconciliation));
|
|
54
|
+
acknowledgeDriverObjective(plan, reconciliation.unacknowledgedObjective);
|
|
50
55
|
const now = iso();
|
|
51
56
|
for (const field of BLOCKER_FIELDS)
|
|
52
57
|
Reflect.deleteProperty(goal, field);
|
|
@@ -64,16 +69,14 @@ export async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
64
69
|
const summaryEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal, message: `Review blockers recorded; appended ${newGoal.id}.` };
|
|
65
70
|
Reflect.set(summaryEntry, "kind", "blocker_recorded");
|
|
66
71
|
const ledgerEntries = [blockedEntry, addedEntry, summaryEntry];
|
|
67
|
-
await
|
|
68
|
-
for (const entry of ledgerEntries)
|
|
69
|
-
await appendLedger(repoRoot, entry, scope);
|
|
72
|
+
await commit(repoRoot, scope, { plan, entries: ledgerEntries });
|
|
70
73
|
return {
|
|
71
74
|
plan,
|
|
72
75
|
blockedGoal: goal,
|
|
73
76
|
newGoal,
|
|
74
77
|
ledgerEntries,
|
|
75
|
-
nextActions: reconciliation.
|
|
76
|
-
warnings: reconciliation.warnings
|
|
78
|
+
nextActions: reconciliation.nextActions,
|
|
79
|
+
warnings: reconciliation.warnings,
|
|
77
80
|
};
|
|
78
81
|
});
|
|
79
82
|
}
|