lazycodex-ai 5.0.0-beta.4 → 5.0.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +25 -21
- package/README.ko.md +25 -21
- package/README.md +38 -23
- package/README.ru.md +24 -20
- package/README.zh-cn.md +25 -21
- package/dist/cli/config-manager/parse-opencode-config-file.d.ts +2 -1
- package/dist/cli/doctor/checks/system-plugin.d.ts +4 -3
- package/dist/cli/fallback-lane-policy.d.ts +1 -0
- package/dist/cli/index.js +66932 -64746
- package/dist/cli/install-codex/install-codex-test-fixtures.d.ts +1 -1
- package/dist/cli/run/on-complete-hook.d.ts +2 -0
- package/dist/cli/runtime-commands.d.ts +1 -1
- package/dist/cli/worktree-sweep/classify.d.ts +16 -0
- package/dist/cli/worktree-sweep/format.d.ts +8 -0
- package/dist/cli/worktree-sweep/git.d.ts +19 -0
- package/dist/cli/worktree-sweep/index.d.ts +7 -0
- package/dist/cli/worktree-sweep/options.d.ts +2 -0
- package/dist/cli/worktree-sweep/parse-worktree-list.d.ts +8 -0
- package/dist/cli/worktree-sweep/sweep.d.ts +2 -0
- package/dist/cli/worktree-sweep/types.d.ts +65 -0
- package/dist/cli/worktree-sweep/worktree-sweep.d.ts +2 -0
- package/dist/cli-node/index.js +66795 -64586
- package/package.json +9 -9
- package/packages/git-bash-mcp/dist/cli.js +6 -1
- package/packages/lsp-daemon/dist/cli.js +793 -323
- package/packages/lsp-daemon/dist/client.d.ts +10 -0
- package/packages/lsp-daemon/dist/client.js +753 -280
- package/packages/lsp-daemon/dist/daemon-client.d.ts +1 -0
- package/packages/lsp-daemon/dist/daemon-client.js +3 -0
- package/packages/lsp-daemon/dist/ensure-daemon.js +1 -0
- package/packages/lsp-daemon/dist/index.js +757 -289
- package/packages/lsp-daemon/dist/proxy.js +3 -0
- package/packages/lsp-daemon/dist/version-reap.js +1 -1
- package/packages/lsp-daemon/package.json +1 -1
- package/packages/lsp-tools-mcp/dist/cli.js +712 -244
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +248 -119
- package/packages/lsp-tools-mcp/dist/mcp.js +714 -246
- package/packages/lsp-tools-mcp/dist/request-context.js +12 -13
- package/packages/lsp-tools-mcp/dist/tools.js +718 -254
- package/packages/lsp-tools-mcp/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +4 -6
- package/packages/omo-codex/plugin/.mcp.json +0 -6
- package/packages/omo-codex/plugin/AGENTS.md +47 -0
- package/packages/omo-codex/plugin/README.md +2 -2
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +45 -49
- 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/bootstrap/src/hook.ts +1 -0
- package/packages/omo-codex/plugin/components/comment-checker/AGENTS.md +9 -0
- package/packages/omo-codex/plugin/components/comment-checker/biome.json +2 -2
- package/packages/omo-codex/plugin/components/comment-checker/dist/cli.js +2 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +5 -5
- package/packages/omo-codex/plugin/components/comment-checker/src/apply-patch.ts +2 -8
- package/packages/omo-codex/plugin/components/comment-checker/src/core.ts +1 -2
- package/packages/omo-codex/plugin/components/comment-checker/src/request-extractor.ts +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/src/runner.ts +1 -0
- package/packages/omo-codex/plugin/components/git-bash/AGENTS.md +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 +3 -4
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +2 -2
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/cli.js +1 -5
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/codex-hook.js +1 -5
- 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 +5 -5
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/codex-hook.ts +1 -5
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +4 -21
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-doctor/SKILL.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/AGENTS.md +30 -15
- package/packages/omo-codex/plugin/components/lsp/biome.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +741 -294
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +1 -5
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +6 -7
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +1 -2
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +11 -1
- package/packages/omo-codex/plugin/components/rules/AGENTS.md +12 -0
- package/packages/omo-codex/plugin/components/rules/biome.json +2 -2
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +60 -45
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +7 -7
- package/packages/omo-codex/plugin/components/rules/src/config.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/src/persistent-cache.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +9 -5
- package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +1 -4
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -9
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules-priority.test.ts +11 -16
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +17 -25
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +9 -7
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-context.test.ts +0 -6
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +6 -4
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-directive.test.ts +12 -9
- package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +28 -37
- package/packages/omo-codex/plugin/components/rules/test/dynamic-target-fingerprints.test.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/test/engine.test.ts +7 -4
- package/packages/omo-codex/plugin/components/rules/test/finder.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +39 -72
- package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/test/hook-output.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/matcher.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +1 -1
- package/packages/omo-codex/plugin/components/rules/test/parser.test.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +5 -6
- package/packages/omo-codex/plugin/components/rules/test/rules-engine-consumption.test.ts +3 -6
- package/packages/omo-codex/plugin/components/rules/test/scanner.test.ts +1 -2
- package/packages/omo-codex/plugin/components/rules/test/sources.test.ts +2 -5
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +2 -17
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +5 -5
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +4 -10
- package/packages/omo-codex/plugin/components/telemetry/AGENTS.md +8 -0
- package/packages/omo-codex/plugin/components/telemetry/biome.json +2 -2
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +2463 -437
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +2467 -441
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +5 -5
- package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +1 -4
- package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -14
- package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +1 -1
- package/packages/omo-codex/plugin/components/telemetry/test/diagnostics.test.ts +2 -5
- package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -4
- package/packages/omo-codex/plugin/components/ultrawork/biome.json +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +489 -4
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +5 -6
- package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +6 -7
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +31 -7
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +2 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-unclear.md +5 -5
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
- package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +9 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +0 -136
- package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/skill-pointer.test.ts +0 -2
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/NOTICE +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/README.md +5 -5
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/biome.json +2 -2
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/directive.md +6 -6
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/dist/cli.js +12 -12
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +28 -0
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/package.json +12 -12
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/boulder-reader.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/cli.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/codex-hook.ts +3 -3
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/directive.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/index.ts +1 -1
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/boulder-reader.test.ts +3 -3
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/cli.test.ts +1 -4
- package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/codex-hook.test.ts +8 -22
- package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/biome.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +15 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +12 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +45 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +15 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +120 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +40 -39
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +7 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +958 -342
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +27 -6
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +15 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +17 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +12 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +104 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.d.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +65 -9
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +69 -111
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +185 -14
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +65 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +108 -0
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +22 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +81 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +31 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +134 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +38 -71
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +7 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +14 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +3 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +34 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +26 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
- package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +18 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +27 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +11 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +123 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +68 -15
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +6 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +82 -131
- package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +187 -12
- package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +95 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +0 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +133 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +137 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-create-goals.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +26 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +183 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +37 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +0 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +5 -37
- package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate-basics.test.ts +258 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-cap-and-dedupe.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-poisoning-cascades.test.ts +97 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +46 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-single-report.test.ts +241 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +48 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +318 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +8 -5
- package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +13 -8
- package/packages/omo-codex/plugin/components/ulw-loop/vitest.config.ts +1 -0
- 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/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 +17 -0
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +17 -0
- 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/model-catalog.json +3 -3
- package/packages/omo-codex/plugin/package-lock.json +676 -524
- package/packages/omo-codex/plugin/package.json +2 -3
- package/packages/omo-codex/plugin/scripts/AGENTS.md +39 -0
- package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +1 -2
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +0 -9
- package/packages/omo-codex/plugin/scripts/canonical-ultrawork-directive.mjs +17 -0
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +0 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +3 -3
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +36 -13
- package/packages/omo-codex/plugin/shared/package.json +3 -4
- package/packages/omo-codex/plugin/shared/src/config-loader.ts +2 -102
- package/packages/omo-codex/plugin/shared/src/config-migration.ts +0 -1
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +41 -130
- package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +51 -0
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +62 -0
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +3 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
- package/packages/omo-codex/plugin/skills/data-scientist/SKILL.md +99 -239
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/placement.md +74 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/omo-codex/plugin/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/omo-codex/plugin/skills/data-scientist/references/visualization.md +64 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/omo-codex/plugin/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +11 -7
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +1 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/print-paged-media.md +76 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +80 -0
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/README.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/orchestration.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/designpowers/routing.md +5 -5
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +13 -13
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +4 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +37 -10
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +13 -11
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +15 -3
- package/packages/omo-codex/plugin/skills/{start-work → ulw-execute}/SKILL.md +19 -26
- package/packages/omo-codex/plugin/skills/ulw-execute/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +3 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +108 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +22 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +6 -7
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +31 -7
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-unclear.md +5 -5
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +4 -2
- package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +58 -0
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +3 -2
- package/packages/omo-codex/plugin/test/AGENTS.md +47 -0
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +19 -173
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +9 -48
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -3
- package/packages/omo-codex/plugin/test/aggregate-mcp.test.mjs +2 -7
- package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +175 -13
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +78 -2
- package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +19 -33
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +2 -27
- package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +0 -31
- package/packages/omo-codex/plugin/test/canonical-ultrawork-directive.test.mjs +53 -0
- package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -51
- package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +2 -2
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +4 -5
- package/packages/omo-codex/plugin/test/lcx-contribute-bug-fix-template.test.mjs +21 -27
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -3
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +8 -8
- package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +0 -36
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +101 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +23 -13
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +14 -119
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +0 -40
- package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +6 -62
- package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +3 -36
- package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +0 -44
- package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +2 -6
- package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -5
- package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +0 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2817 -913
- package/packages/{omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md → prompts-core/prompts/ultrawork/codex.md} +15 -10
- package/packages/shared-skills/skills/ast-grep/AGENTS.md +51 -0
- package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +62 -0
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +3 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
- package/packages/shared-skills/skills/data-scientist/SKILL.md +99 -239
- package/packages/shared-skills/skills/data-scientist/references/execution-surfaces.md +91 -0
- package/packages/shared-skills/skills/data-scientist/references/placement.md +74 -0
- package/packages/shared-skills/skills/data-scientist/references/polars-lane.md +95 -0
- package/packages/shared-skills/skills/data-scientist/references/uv-setup.md +1 -1
- package/packages/shared-skills/skills/data-scientist/references/visualization.md +64 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-js-deps.sh +28 -0
- package/packages/shared-skills/skills/data-scientist/scripts/ensure-py-deps.sh +37 -0
- package/packages/shared-skills/skills/debugging/SKILL.md +2 -0
- package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
- package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
- package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
- package/packages/shared-skills/skills/frontend/SKILL.md +11 -7
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +1 -0
- package/packages/shared-skills/skills/frontend/references/design/print-paged-media.md +76 -0
- package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +80 -0
- package/packages/shared-skills/skills/frontend/references/designpowers/README.md +1 -1
- package/packages/shared-skills/skills/frontend/references/designpowers/lane-a-direction.md +1 -1
- package/packages/shared-skills/skills/frontend/references/designpowers/lane-b-execution.md +11 -11
- package/packages/shared-skills/skills/frontend/references/designpowers/lane-d-memory.md +3 -3
- package/packages/shared-skills/skills/frontend/references/designpowers/orchestration.md +1 -1
- package/packages/shared-skills/skills/frontend/references/designpowers/routing.md +5 -5
- package/packages/shared-skills/skills/init-deep/SKILL.md +13 -13
- package/packages/shared-skills/skills/review-work/SKILL.md +3 -3
- package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +37 -10
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +2 -0
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +13 -11
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/packages/shared-skills/skills/{start-work → ulw-execute}/SKILL.md +17 -16
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +6 -7
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +31 -7
- package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +2 -1
- package/packages/shared-skills/skills/ulw-plan/references/intent-unclear.md +5 -5
- package/packages/shared-skills/skills/ulw-research/SKILL.md +4 -2
- package/packages/shared-skills/skills/visual-qa/AGENTS.md +58 -0
- package/packages/shared-skills/skills/visual-qa/SKILL.md +3 -2
- package/script/qa/web-terminal-visual-qa.mjs +2 -0
- package/script/qa/xterm-live-terminal.mjs +18 -2
- package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
- package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +0 -58
- package/packages/omo-codex/plugin/components/codegraph/NODE-RUNTIME-LICENSES.md +0 -2951
- package/packages/omo-codex/plugin/components/codegraph/NOTICE +0 -16
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +0 -12136
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +0 -9734
- package/packages/omo-codex/plugin/components/codegraph/package.json +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/cache-gc.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +0 -79
- package/packages/omo-codex/plugin/components/codegraph/src/hook-input.ts +0 -33
- package/packages/omo-codex/plugin/components/codegraph/src/hook-sweep.ts +0 -25
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +0 -131
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +0 -309
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +0 -80
- package/packages/omo-codex/plugin/components/codegraph/src/post-tool-use-hook.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +0 -263
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-command.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-cooldown.ts +0 -145
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-hook-runtime.ts +0 -21
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-lock.ts +0 -139
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-outcome.ts +0 -15
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-paths.ts +0 -32
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-project.ts +0 -109
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker-result.ts +0 -148
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/src/sweep-cli.ts +0 -81
- package/packages/omo-codex/plugin/components/codegraph/test/cache-gc.test.ts +0 -34
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +0 -112
- package/packages/omo-codex/plugin/components/codegraph/test/hook-registration.test.ts +0 -26
- package/packages/omo-codex/plugin/components/codegraph/test/hook-session-start-guard.test.ts +0 -160
- package/packages/omo-codex/plugin/components/codegraph/test/hook-store-upgrade.test.ts +0 -50
- package/packages/omo-codex/plugin/components/codegraph/test/hook-sweep.test.ts +0 -29
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +0 -320
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +0 -172
- package/packages/omo-codex/plugin/components/codegraph/test/package-runtime.test.ts +0 -91
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +0 -100
- package/packages/omo-codex/plugin/components/codegraph/test/serve-built-wrapper.test.ts +0 -76
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +0 -133
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +0 -245
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-facade.test.ts +0 -69
- package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +0 -43
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +0 -147
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable.test.ts +0 -175
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +0 -385
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +0 -206
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-project.test.ts +0 -58
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-state.test.ts +0 -104
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-trust-boundary.test.ts +0 -65
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-availability.test.ts +0 -106
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-cooldown.test.ts +0 -116
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +0 -258
- package/packages/omo-codex/plugin/components/codegraph/test/sweep-cli.test.ts +0 -56
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.build.json +0 -13
- package/packages/omo-codex/plugin/components/codegraph/tsconfig.json +0 -25
- package/packages/omo-codex/plugin/components/start-work-continuation/LICENSE +0 -21
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +0 -28
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -471
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +0 -18
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +0 -17
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +0 -17
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +0 -17
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/editor.mjs +0 -125
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/jsonc.mjs +0 -43
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot/scaffold.mjs +0 -29
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +0 -121
- package/packages/omo-codex/plugin/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/omo-codex/plugin/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/omo-codex/plugin/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/omo-codex/plugin/skills/data-scientist/references/performance-benchmarks.md +0 -37
- package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +0 -2
- package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +0 -92
- package/packages/omo-codex/plugin/test/component-codegraph-mcp-smoke.test.mjs +0 -76
- package/packages/omo-codex/plugin/test/migrate-omo-sot.test.mjs +0 -179
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +0 -314
- package/packages/omo-codex/plugin/test/ulw-plan-scope-contract.test.mjs +0 -24
- package/packages/shared-skills/skills/data-scientist/references/common-scenarios.md +0 -176
- package/packages/shared-skills/skills/data-scientist/references/execution-templates.md +0 -197
- package/packages/shared-skills/skills/data-scientist/references/integration-patterns.md +0 -153
- package/packages/shared-skills/skills/data-scientist/references/performance-benchmarks.md +0 -37
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/.gitattributes +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/CHANGELOG.md +0 -0
- /package/packages/omo-codex/plugin/components/{codegraph → ulw-execute-continuation}/LICENSE +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/plan-checklist.ts +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/src/types.ts +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-completed.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-mixed-platforms.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/boulder-single-codex-work.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-all-done.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-scaffold.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-nested-checkboxes.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/test/fixtures/plan-with-unchecked.md +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.build.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/tsconfig.json +0 -0
- /package/packages/omo-codex/plugin/components/{start-work-continuation → ulw-execute-continuation}/vitest.config.ts +0 -0
|
@@ -1,17 +1,124 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import { existsSync as
|
|
3
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
4
|
+
import { existsSync as existsSync4, statSync } from "node:fs";
|
|
5
5
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
6
6
|
import { resolve as resolve3 } from "node:path";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// components/ulw-loop/src/checkpoint-reconciliation.ts
|
|
9
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
10
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
11
|
+
|
|
12
|
+
// components/ulw-loop/src/codex-goal-snapshot.ts
|
|
9
13
|
import { existsSync } from "node:fs";
|
|
10
14
|
import { readFile } from "node:fs/promises";
|
|
15
|
+
import { resolve } from "node:path";
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
class CodexGoalSnapshotError extends Error {
|
|
18
|
+
}
|
|
19
|
+
function safeObject(value) {
|
|
20
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21
|
+
}
|
|
22
|
+
function safeString(value) {
|
|
23
|
+
return typeof value === "string" ? value.trim() : "";
|
|
24
|
+
}
|
|
25
|
+
function normalizeStatus(value) {
|
|
26
|
+
const status = safeString(value).toLowerCase();
|
|
27
|
+
if (status === "complete" || status === "completed" || status === "done")
|
|
28
|
+
return "complete";
|
|
29
|
+
if (status === "cancelled" || status === "canceled")
|
|
30
|
+
return "cancelled";
|
|
31
|
+
if (status === "failed" || status === "failure")
|
|
32
|
+
return "failed";
|
|
33
|
+
if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
|
|
34
|
+
return "active";
|
|
35
|
+
return "unknown";
|
|
36
|
+
}
|
|
37
|
+
function normalizeObjective(value) {
|
|
38
|
+
return value.replace(/\s+/g, " ").trim();
|
|
39
|
+
}
|
|
40
|
+
function parseCodexGoalSnapshot(value) {
|
|
41
|
+
const root = safeObject(value);
|
|
42
|
+
const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
|
|
43
|
+
if (goalValue === null || goalValue === undefined || goalValue === false) {
|
|
44
|
+
return { available: false, raw: value };
|
|
45
|
+
}
|
|
46
|
+
const goal = safeObject(goalValue);
|
|
47
|
+
const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? goal["title"] ?? root["objective"] ?? root["title"]);
|
|
48
|
+
const status = normalizeStatus(goal["status"] ?? root["status"]);
|
|
49
|
+
return {
|
|
50
|
+
available: Boolean(objective || status !== "unknown"),
|
|
51
|
+
...objective ? { objective } : {},
|
|
52
|
+
status,
|
|
53
|
+
raw: value
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function readCodexGoalSnapshotInput(raw, cwd = process.cwd()) {
|
|
57
|
+
if (!raw?.trim())
|
|
58
|
+
return null;
|
|
59
|
+
const trimmed = raw.trim();
|
|
60
|
+
try {
|
|
61
|
+
return parseCodexGoalSnapshot(JSON.parse(trimmed));
|
|
62
|
+
} catch {
|
|
63
|
+
const path = resolve(cwd, trimmed);
|
|
64
|
+
if (!existsSync(path)) {
|
|
65
|
+
throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function reconcileCodexGoalSnapshot(snapshot, options) {
|
|
75
|
+
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
76
|
+
const errors = [];
|
|
77
|
+
const warnings = [];
|
|
78
|
+
if (!effectiveSnapshot.available) {
|
|
79
|
+
const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
|
|
80
|
+
if (options.requireSnapshot)
|
|
81
|
+
errors.push(message);
|
|
82
|
+
else
|
|
83
|
+
warnings.push(message);
|
|
84
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
85
|
+
}
|
|
86
|
+
const expected = normalizeObjective(options.expectedObjective);
|
|
87
|
+
const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))].filter(Boolean));
|
|
88
|
+
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
|
|
89
|
+
if (!actual) {
|
|
90
|
+
errors.push("Codex goal snapshot is missing objective text.");
|
|
91
|
+
} else if (!accepted.has(actual)) {
|
|
92
|
+
errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
|
|
93
|
+
}
|
|
94
|
+
const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
|
|
95
|
+
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
96
|
+
if (!allowed.includes(actualStatus)) {
|
|
97
|
+
errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
|
|
98
|
+
}
|
|
99
|
+
if (options.requireComplete && actualStatus !== "complete") {
|
|
100
|
+
errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
|
|
101
|
+
}
|
|
102
|
+
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
103
|
+
}
|
|
104
|
+
function formatCodexGoalReconciliation(reconciliation) {
|
|
105
|
+
const parts = [...reconciliation.errors, ...reconciliation.warnings];
|
|
106
|
+
return parts.join(" ");
|
|
107
|
+
}
|
|
108
|
+
function codexGoalMismatchRecovery(expectedObjective, snapshot) {
|
|
109
|
+
const receivedObjective = snapshot?.objective ?? "";
|
|
110
|
+
const message = [
|
|
111
|
+
"Recovery: the Codex goal objective must equal the plan's codexObjective exactly — copy the expected value below into update_goal and re-run get_goal.",
|
|
112
|
+
`expected codexObjective: ${expectedObjective}`,
|
|
113
|
+
`received objective: ${receivedObjective || "(none)"}`
|
|
114
|
+
].join(`
|
|
115
|
+
`);
|
|
116
|
+
return { message, details: { expectedObjective, receivedObjective } };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// components/ulw-loop/src/paths.ts
|
|
13
120
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
14
|
-
//
|
|
121
|
+
// components/ulw-loop/src/constants.ts
|
|
15
122
|
var ULW_LOOP_DIR = ".omo/ulw-loop";
|
|
16
123
|
var ULW_LOOP_BRIEF = "brief.md";
|
|
17
124
|
var ULW_LOOP_GOALS = "goals.json";
|
|
@@ -31,10 +138,8 @@ var ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
|
|
|
31
138
|
"regression",
|
|
32
139
|
"adversarial"
|
|
33
140
|
];
|
|
34
|
-
//
|
|
141
|
+
// components/ulw-loop/src/runtime.ts
|
|
35
142
|
class UlwLoopError extends Error {
|
|
36
|
-
code;
|
|
37
|
-
details;
|
|
38
143
|
constructor(message, code, opts) {
|
|
39
144
|
super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
|
|
40
145
|
this.name = "UlwLoopError";
|
|
@@ -47,8 +152,8 @@ class UlwLoopError extends Error {
|
|
|
47
152
|
function iso() {
|
|
48
153
|
return new Date().toISOString();
|
|
49
154
|
}
|
|
50
|
-
//
|
|
51
|
-
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"];
|
|
155
|
+
// components/ulw-loop/src/paths.ts
|
|
156
|
+
var SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"];
|
|
52
157
|
function normalizeUlwLoopSessionId(sessionId) {
|
|
53
158
|
const trimmed = sessionId?.trim();
|
|
54
159
|
if (!trimmed)
|
|
@@ -113,7 +218,7 @@ function isWithinAttemptDir(absolutePath, attemptRoot, pathApi = PLATFORM_PATH_A
|
|
|
113
218
|
return !pathApi.isAbsolute(relativePath);
|
|
114
219
|
}
|
|
115
220
|
|
|
116
|
-
//
|
|
221
|
+
// components/ulw-loop/src/goal-status.ts
|
|
117
222
|
var ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE = aggregateCodexObjectiveForScope();
|
|
118
223
|
function aggregateCodexObjectiveForScope(scope) {
|
|
119
224
|
return `Complete the durable ulw-loop plan in ${ulwLoopGoalsRelativePath(scope)}, including later accepted/appended stories, under the original brief constraints; use ${ulwLoopLedgerRelativePath(scope)} as the audit trail.`;
|
|
@@ -196,8 +301,15 @@ function hasEssentialCriteriaPass(goal) {
|
|
|
196
301
|
return criteria.length > 0 && criteria.every((criterion) => criterion.status === "pass");
|
|
197
302
|
}
|
|
198
303
|
|
|
199
|
-
//
|
|
200
|
-
function
|
|
304
|
+
// components/ulw-loop/src/checkpoint-reconciliation.ts
|
|
305
|
+
function codexSnapshotMismatchError(input) {
|
|
306
|
+
const { reconciliation, snapshot, taskScopedHint } = input;
|
|
307
|
+
const hint = taskScopedHint?.aggregate === true && snapshot?.status === "complete" && snapshot.objective !== undefined ? buildTaskScopedAggregateReconciliationHint(taskScopedHint.goal, taskScopedHint.final) : "";
|
|
308
|
+
const recovery = codexGoalMismatchRecovery(input.expectedObjective, snapshot);
|
|
309
|
+
return new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${hint}
|
|
310
|
+
${recovery.message}`, "ulw_loop_codex_snapshot_mismatch", { details: recovery.details });
|
|
311
|
+
}
|
|
312
|
+
function normalizeObjective2(value) {
|
|
201
313
|
return value.replace(/\s+/g, " ").trim();
|
|
202
314
|
}
|
|
203
315
|
function textMentionsUlwLoopPlanArtifact(value) {
|
|
@@ -214,13 +326,13 @@ function textHasCompletionValidationEvidence(value) {
|
|
|
214
326
|
return done && verified;
|
|
215
327
|
}
|
|
216
328
|
async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective, scope) {
|
|
217
|
-
const actual =
|
|
329
|
+
const actual = normalizeObjective2(snapshotObjective).toLowerCase();
|
|
218
330
|
if (textMentionsUlwLoopPlanArtifact(actual))
|
|
219
331
|
return true;
|
|
220
|
-
if (actual.length < 24 || !
|
|
332
|
+
if (actual.length < 24 || !existsSync2(ulwLoopBriefPath(repoRoot, scope)))
|
|
221
333
|
return false;
|
|
222
334
|
try {
|
|
223
|
-
const brief =
|
|
335
|
+
const brief = normalizeObjective2(await readFile2(ulwLoopBriefPath(repoRoot, scope), "utf8")).toLowerCase();
|
|
224
336
|
return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
|
|
225
337
|
} catch (error) {
|
|
226
338
|
if (error instanceof Error)
|
|
@@ -267,107 +379,70 @@ function buildTaskScopedAggregateReconciliationHint(goal, final) {
|
|
|
267
379
|
return ` Completed task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress OMO goal, evidence that names that active OMO goal id, names .omo/ulw-loop/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ulw-loop brief/artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
|
|
268
380
|
}
|
|
269
381
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
273
|
-
import { resolve } from "node:path";
|
|
274
|
-
|
|
275
|
-
class CodexGoalSnapshotError extends Error {
|
|
276
|
-
}
|
|
277
|
-
function safeObject(value) {
|
|
278
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
279
|
-
}
|
|
280
|
-
function safeString(value) {
|
|
281
|
-
return typeof value === "string" ? value.trim() : "";
|
|
282
|
-
}
|
|
283
|
-
function normalizeStatus(value) {
|
|
284
|
-
const status = safeString(value).toLowerCase();
|
|
285
|
-
if (status === "complete" || status === "completed" || status === "done")
|
|
286
|
-
return "complete";
|
|
287
|
-
if (status === "cancelled" || status === "canceled")
|
|
288
|
-
return "cancelled";
|
|
289
|
-
if (status === "failed" || status === "failure")
|
|
290
|
-
return "failed";
|
|
291
|
-
if (status === "active" || status === "in_progress" || status === "pending" || status === "running")
|
|
292
|
-
return "active";
|
|
293
|
-
return "unknown";
|
|
294
|
-
}
|
|
295
|
-
function normalizeObjective2(value) {
|
|
382
|
+
// components/ulw-loop/src/checkpoint-codex-validation.ts
|
|
383
|
+
function normalizeObjective3(value) {
|
|
296
384
|
return value.replace(/\s+/g, " ").trim();
|
|
297
385
|
}
|
|
298
|
-
function
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
raw
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
try {
|
|
326
|
-
return parseCodexGoalSnapshot(JSON.parse(await readFile2(path, "utf-8")));
|
|
327
|
-
} catch (error) {
|
|
328
|
-
throw new CodexGoalSnapshotError(`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
function reconcileCodexGoalSnapshot(snapshot, options) {
|
|
333
|
-
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
|
|
334
|
-
const errors = [];
|
|
335
|
-
const warnings = [];
|
|
336
|
-
if (!effectiveSnapshot.available) {
|
|
337
|
-
const message = "Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
|
|
338
|
-
if (options.requireSnapshot)
|
|
339
|
-
errors.push(message);
|
|
340
|
-
else
|
|
341
|
-
warnings.push(message);
|
|
342
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
343
|
-
}
|
|
344
|
-
const expected = normalizeObjective2(options.expectedObjective);
|
|
345
|
-
const accepted = new Set([expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective2(objective))].filter(Boolean));
|
|
346
|
-
const actual = normalizeObjective2(effectiveSnapshot.objective ?? "");
|
|
347
|
-
if (!actual) {
|
|
348
|
-
errors.push("Codex goal snapshot is missing objective text.");
|
|
349
|
-
} else if (!accepted.has(actual)) {
|
|
350
|
-
errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
|
|
351
|
-
}
|
|
352
|
-
const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
|
|
353
|
-
const actualStatus = effectiveSnapshot.status ?? "unknown";
|
|
354
|
-
if (!allowed.includes(actualStatus)) {
|
|
355
|
-
errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
|
|
356
|
-
}
|
|
357
|
-
if (options.requireComplete && actualStatus !== "complete") {
|
|
358
|
-
errors.push('Codex goal is not complete; call update_goal({status: "complete"}) only after the objective is actually complete, then pass the fresh get_goal JSON.');
|
|
359
|
-
}
|
|
360
|
-
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
|
|
386
|
+
async function validateCheckpointCodexGoal(input) {
|
|
387
|
+
const aggregate = codexGoalMode(input.plan) === "aggregate";
|
|
388
|
+
const final = isFinalRunCompletionCandidate(input.plan, input.goal);
|
|
389
|
+
const snapshot = await readCodexGoalSnapshotInput(input.raw, input.repoRoot);
|
|
390
|
+
const expectedObjective = expectedCodexObjective(input.plan, input.goal);
|
|
391
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, {
|
|
392
|
+
expectedObjective,
|
|
393
|
+
...aggregate ? { acceptedObjectives: compatibleCodexObjectives(input.plan) } : {},
|
|
394
|
+
allowedStatuses: aggregate ? final ? ["complete"] : ["active"] : ["complete"],
|
|
395
|
+
requireSnapshot: true,
|
|
396
|
+
requireComplete: !aggregate || final
|
|
397
|
+
});
|
|
398
|
+
if (reconciliation.ok)
|
|
399
|
+
return reconciliation.snapshot.raw;
|
|
400
|
+
const objective = snapshot?.objective;
|
|
401
|
+
const mismatchedTaskObjective = snapshot?.available === true && objective !== undefined && normalizeObjective3(objective) !== normalizeObjective3(expectedObjective);
|
|
402
|
+
const completedTaskScoped = mismatchedTaskObjective && snapshot.status === "complete" && await canReconcileCompletedTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
|
|
403
|
+
const activeFinalTaskScoped = mismatchedTaskObjective && snapshot.status === "active" && await canReconcileActiveFinalTaskScopedAggregateSnapshot(input.repoRoot, input.plan, input.goal, objective, input.evidence, input.scope);
|
|
404
|
+
if (completedTaskScoped || activeFinalTaskScoped)
|
|
405
|
+
return reconciliation.snapshot.raw;
|
|
406
|
+
throw codexSnapshotMismatchError({
|
|
407
|
+
reconciliation,
|
|
408
|
+
snapshot,
|
|
409
|
+
expectedObjective,
|
|
410
|
+
taskScopedHint: { goal: input.goal, aggregate, final }
|
|
411
|
+
});
|
|
361
412
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
|
|
413
|
+
function combineCheckpointValidationErrors(codexError, gateError) {
|
|
414
|
+
return new UlwLoopError(`${codexError.message}
|
|
415
|
+
${gateError.message}`, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
416
|
+
details: { ...codexError.details ?? {}, ...gateError.details ?? {} }
|
|
417
|
+
});
|
|
365
418
|
}
|
|
366
419
|
|
|
367
|
-
//
|
|
368
|
-
import { createReadStream } from "node:fs";
|
|
420
|
+
// components/ulw-loop/src/plan-io.ts
|
|
421
|
+
import { createReadStream, readdirSync } from "node:fs";
|
|
369
422
|
import { appendFile, mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
|
|
370
423
|
import { createInterface } from "node:readline";
|
|
424
|
+
|
|
425
|
+
// components/ulw-loop/src/plan-missing-recovery.ts
|
|
426
|
+
var ULW_LOOP_CREATE_GOALS_COMMAND = 'omo-agent-toolkit ulw-loop create-goals --brief "<brief>" --json';
|
|
427
|
+
function planMissingRecovery(existingSessionIds) {
|
|
428
|
+
const lines = [`Recovery: bootstrap the plan with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`];
|
|
429
|
+
if (existingSessionIds.length === 0)
|
|
430
|
+
return { message: lines.join(`
|
|
431
|
+
`) };
|
|
432
|
+
lines.push(`Existing ulw-loop session ids under .omo/ulw-loop/: ${existingSessionIds.join(", ")}. Re-run with \`--session-id <id>\` to target one of them.`);
|
|
433
|
+
return { message: lines.join(`
|
|
434
|
+
`), details: { existingSessionIds } };
|
|
435
|
+
}
|
|
436
|
+
function sessionIdRequiredMessage(flag) {
|
|
437
|
+
return [
|
|
438
|
+
`${flag} requires a non-empty value.`,
|
|
439
|
+
"Recovery: subprocess, eval, and hook contexts do not inherit the ulw-loop session env (OMO_ULW_LOOP_SESSION_ID / CODEX_SESSION_ID / CODEX_THREAD_ID / PI_SESSION_ID),",
|
|
440
|
+
`so pass the scope explicitly: \`${flag} <id>\` (for example \`${flag} 01a05b8a-6763-7780-834f-319423b071ce\`).`
|
|
441
|
+
].join(`
|
|
442
|
+
`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// components/ulw-loop/src/plan-io.ts
|
|
371
446
|
var LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
|
|
372
447
|
var 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.`;
|
|
373
448
|
var locks = new Map;
|
|
@@ -408,7 +483,9 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
408
483
|
} catch (error) {
|
|
409
484
|
if (!hasCode(error, "ENOENT"))
|
|
410
485
|
throw error;
|
|
411
|
-
|
|
486
|
+
const recovery = planMissingRecovery(readSessionDirs(repoRoot));
|
|
487
|
+
throw new UlwLoopError(`No ulw-loop plan found at ${repoRelative(path, repoRoot)}.
|
|
488
|
+
${recovery.message}`, "ULW_LOOP_PLAN_MISSING", { cause: error, ...recovery.details === undefined ? {} : { details: recovery.details } });
|
|
412
489
|
}
|
|
413
490
|
const parsed = JSON.parse(raw);
|
|
414
491
|
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
|
|
@@ -431,6 +508,13 @@ async function readUlwLoopPlan(repoRoot, scope) {
|
|
|
431
508
|
}
|
|
432
509
|
return parsed;
|
|
433
510
|
}
|
|
511
|
+
function readSessionDirs(repoRoot) {
|
|
512
|
+
try {
|
|
513
|
+
return readdirSync(ulwLoopDir(repoRoot), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
514
|
+
} catch {
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
517
|
+
}
|
|
434
518
|
async function writePlan(repoRoot, plan, scope) {
|
|
435
519
|
await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
|
|
436
520
|
const path = ulwLoopGoalsPath(repoRoot, scope);
|
|
@@ -482,7 +566,7 @@ async function findAcceptedSteeringLedgerEntry(repoRoot, key, scope) {
|
|
|
482
566
|
return;
|
|
483
567
|
}
|
|
484
568
|
|
|
485
|
-
//
|
|
569
|
+
// components/ulw-loop/src/evidence.ts
|
|
486
570
|
function ulwLoopFail(message, code, details) {
|
|
487
571
|
throw new UlwLoopError(message, code, { details });
|
|
488
572
|
}
|
|
@@ -588,57 +672,216 @@ function requireEssentialCriteriaPass(goal) {
|
|
|
588
672
|
});
|
|
589
673
|
}
|
|
590
674
|
|
|
591
|
-
//
|
|
675
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
592
676
|
import { resolve as resolve2 } from "node:path";
|
|
593
677
|
|
|
594
|
-
//
|
|
595
|
-
var PLACEHOLDER_PATTERN = /^(
|
|
678
|
+
// components/ulw-loop/src/quality-gate-fields.ts
|
|
679
|
+
var PLACEHOLDER_PATTERN = /^(?:<replace:[^>]+>|placeholder|todo|tbd|n\/a|stub)$/i;
|
|
680
|
+
var activeCollector;
|
|
681
|
+
function withQualityGateCollector(operation) {
|
|
682
|
+
const previous = activeCollector;
|
|
683
|
+
const collector = { defects: [], poisonedFields: new Set, poisonedArtifactKinds: new Set };
|
|
684
|
+
activeCollector = collector;
|
|
685
|
+
try {
|
|
686
|
+
const result = operation();
|
|
687
|
+
if (collector.defects.length === 0)
|
|
688
|
+
return result;
|
|
689
|
+
throwQualityGateDefects(collector.defects);
|
|
690
|
+
} finally {
|
|
691
|
+
activeCollector = previous;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function throwQualityGateDefects(defects) {
|
|
695
|
+
const uniqueDefects = [
|
|
696
|
+
...new Map(defects.map((defect) => [`${defect.field}\x00${defect.message}`, defect])).values()
|
|
697
|
+
];
|
|
698
|
+
const fields = uniqueDefects.slice(0, 25);
|
|
699
|
+
const truncated = uniqueDefects.length > fields.length;
|
|
700
|
+
const message = [
|
|
701
|
+
`Final quality gate has ${fields.length}${truncated ? "+" : ""} validation defects:`,
|
|
702
|
+
...fields.map((item) => `- ${item.field}: ${item.message}`)
|
|
703
|
+
].join(`
|
|
704
|
+
`);
|
|
705
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", {
|
|
706
|
+
details: { field: fields[0]?.field, fields, ...truncated ? { truncated: true } : {} }
|
|
707
|
+
});
|
|
708
|
+
}
|
|
596
709
|
function invalid(message, field) {
|
|
597
|
-
|
|
710
|
+
if (activeCollector === undefined)
|
|
711
|
+
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
|
|
712
|
+
activeCollector.defects.push({ field, message });
|
|
713
|
+
activeCollector.poisonedFields.add(field);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
function isPoisoned(field) {
|
|
717
|
+
return activeCollector?.poisonedFields.has(field) ?? false;
|
|
718
|
+
}
|
|
719
|
+
function markPoisonedArtifactKind(id) {
|
|
720
|
+
activeCollector?.poisonedArtifactKinds.add(id);
|
|
721
|
+
}
|
|
722
|
+
function isPoisonedArtifactKind(id) {
|
|
723
|
+
return activeCollector?.poisonedArtifactKinds.has(id) ?? false;
|
|
598
724
|
}
|
|
599
725
|
function isRecord(value) {
|
|
600
726
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
601
727
|
}
|
|
602
728
|
function section(value, field) {
|
|
603
|
-
|
|
729
|
+
if (isRecord(value))
|
|
730
|
+
return value;
|
|
731
|
+
invalid(`Final quality gate is missing ${field} evidence.`, field);
|
|
732
|
+
return {};
|
|
604
733
|
}
|
|
605
734
|
function textField(value, field) {
|
|
606
|
-
if (typeof value !== "string" || value.trim() === "")
|
|
735
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
607
736
|
invalid(`Final quality gate requires non-empty ${field}.`, field);
|
|
737
|
+
return "";
|
|
738
|
+
}
|
|
608
739
|
const trimmed = value.trim();
|
|
609
740
|
if (PLACEHOLDER_PATTERN.test(trimmed))
|
|
610
741
|
invalid(`Final quality gate rejects placeholder ${field}.`, field);
|
|
611
742
|
return trimmed;
|
|
612
743
|
}
|
|
613
744
|
function numberField(value, field) {
|
|
614
|
-
|
|
745
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
746
|
+
return value;
|
|
747
|
+
invalid(`Final quality gate requires numeric ${field}.`, field);
|
|
748
|
+
return 0;
|
|
615
749
|
}
|
|
616
750
|
function stringArray(value, field) {
|
|
617
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
618
|
-
|
|
751
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
752
|
+
invalid(`Final quality gate requires ${field}.`, field);
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
619
755
|
return value.map((item) => textField(item, field));
|
|
620
756
|
}
|
|
621
757
|
function emptyBlockers(value, field) {
|
|
622
758
|
if (Array.isArray(value) && value.length === 0)
|
|
623
759
|
return [];
|
|
624
760
|
invalid(`${field} must be empty.`, field);
|
|
761
|
+
return [];
|
|
625
762
|
}
|
|
626
763
|
function literal(value, expected, field) {
|
|
627
764
|
if (value === expected)
|
|
628
765
|
return expected;
|
|
629
766
|
invalid(`${field} must be ${String(expected)}.`, field);
|
|
767
|
+
return expected;
|
|
630
768
|
}
|
|
631
769
|
|
|
632
|
-
//
|
|
770
|
+
// components/ulw-loop/src/quality-gate-artifacts.ts
|
|
771
|
+
var SUPPORTED_SURFACES = ["cli", "http", "tmux", "browser", "gui", "data"];
|
|
772
|
+
var SUPPORTED_KINDS = [
|
|
773
|
+
"cli-transcript",
|
|
774
|
+
"log",
|
|
775
|
+
"screenshot",
|
|
776
|
+
"image",
|
|
777
|
+
"http-dump",
|
|
778
|
+
"data-diff"
|
|
779
|
+
];
|
|
780
|
+
var COMPATIBLE_KINDS = {
|
|
781
|
+
cli: ["cli-transcript", "log"],
|
|
782
|
+
tmux: ["cli-transcript", "log"],
|
|
783
|
+
http: ["http-dump"],
|
|
784
|
+
browser: ["screenshot", "image"],
|
|
785
|
+
gui: ["screenshot", "image"],
|
|
786
|
+
data: ["data-diff"]
|
|
787
|
+
};
|
|
788
|
+
function isSupportedSurface(value) {
|
|
789
|
+
return SUPPORTED_SURFACES.some((surface) => surface === value);
|
|
790
|
+
}
|
|
791
|
+
function isSupportedKind(value) {
|
|
792
|
+
return SUPPORTED_KINDS.some((kind) => kind === value);
|
|
793
|
+
}
|
|
794
|
+
function surfaceField(value, field) {
|
|
795
|
+
if (isSupportedSurface(value))
|
|
796
|
+
return value;
|
|
797
|
+
invalid(`${field} must be a supported manual QA surface (${SUPPORTED_SURFACES.join(", ")}).`, field);
|
|
798
|
+
return "cli";
|
|
799
|
+
}
|
|
800
|
+
function kindField(value, field) {
|
|
801
|
+
if (isSupportedKind(value))
|
|
802
|
+
return value;
|
|
803
|
+
invalid(`${field} must be a supported artifact kind (${SUPPORTED_KINDS.join(", ")}); review/QA reports belong in codeReview.reportPath or gateReview.reportPath, not artifactRefs.`, field);
|
|
804
|
+
return "log";
|
|
805
|
+
}
|
|
806
|
+
function compatibleKindsFor(surface) {
|
|
807
|
+
return COMPATIBLE_KINDS[surface];
|
|
808
|
+
}
|
|
809
|
+
function artifactCompatible(surface, kind) {
|
|
810
|
+
return compatibleKindsFor(surface).includes(kind);
|
|
811
|
+
}
|
|
812
|
+
function checkFile(path, field, opts) {
|
|
813
|
+
if (opts?.repoRoot === undefined || opts.fs === undefined || isPoisoned(field))
|
|
814
|
+
return;
|
|
815
|
+
const absolute = resolve2(opts.repoRoot, path);
|
|
816
|
+
if (!opts.fs.existsSync(absolute)) {
|
|
817
|
+
invalid(`${field} must point to an existing artifact.`, field);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (opts.fs.statSync(absolute).size <= 0)
|
|
821
|
+
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
822
|
+
if (opts.currentAttemptDir !== undefined && !isWithinAttemptDir(absolute, resolve2(opts.repoRoot, opts.currentAttemptDir)))
|
|
823
|
+
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
824
|
+
}
|
|
825
|
+
function artifactMap(refs) {
|
|
826
|
+
const byId = new Map;
|
|
827
|
+
for (const ref of refs) {
|
|
828
|
+
if (byId.has(ref.id))
|
|
829
|
+
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
830
|
+
byId.set(ref.id, ref);
|
|
831
|
+
}
|
|
832
|
+
return byId;
|
|
833
|
+
}
|
|
834
|
+
function parseArtifactRefs(value, opts) {
|
|
835
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
836
|
+
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
837
|
+
return [];
|
|
838
|
+
}
|
|
839
|
+
return value.flatMap((item, index) => {
|
|
840
|
+
const prefix = `manualQa.artifactRefs[${index}]`;
|
|
841
|
+
const ref = section(item, prefix);
|
|
842
|
+
if (isPoisoned(prefix))
|
|
843
|
+
return [];
|
|
844
|
+
const pathField = `${prefix}.path`;
|
|
845
|
+
const idField = `${prefix}.id`;
|
|
846
|
+
const kindFieldName = `${prefix}.kind`;
|
|
847
|
+
const descriptionField = `${prefix}.description`;
|
|
848
|
+
const path = textField(ref["path"], pathField);
|
|
849
|
+
const id = textField(ref["id"], idField);
|
|
850
|
+
const kind = kindField(ref["kind"], kindFieldName);
|
|
851
|
+
if (isPoisoned(kindFieldName))
|
|
852
|
+
markPoisonedArtifactKind(id);
|
|
853
|
+
const description = textField(ref["description"], descriptionField);
|
|
854
|
+
checkFile(path, pathField, opts);
|
|
855
|
+
return [{ id, kind, description, path }];
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function referencedArtifacts(value, field, byId) {
|
|
859
|
+
const ids = stringArray(value, field);
|
|
860
|
+
if (isPoisoned(field))
|
|
861
|
+
return [];
|
|
862
|
+
return ids.flatMap((id) => {
|
|
863
|
+
const artifact = byId.get(id);
|
|
864
|
+
if (artifact === undefined) {
|
|
865
|
+
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
return [artifact];
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// components/ulw-loop/src/quality-gate-verdicts.ts
|
|
633
873
|
function passedVerdict(value, field) {
|
|
634
|
-
if (value === "not_applicable")
|
|
874
|
+
if (value === "not_applicable") {
|
|
635
875
|
invalid(`${field} must not be not_applicable.`, field);
|
|
876
|
+
return "passed";
|
|
877
|
+
}
|
|
636
878
|
return literal(value, "passed", field);
|
|
637
879
|
}
|
|
638
880
|
function codeQualityStatusField(value, field) {
|
|
639
881
|
if (value === "CLEAR" || value === "WATCH")
|
|
640
882
|
return value;
|
|
641
883
|
invalid(`${field} must be CLEAR or WATCH.`, field);
|
|
884
|
+
return "CLEAR";
|
|
642
885
|
}
|
|
643
886
|
function adversarialVerdict(row, field) {
|
|
644
887
|
const value = row["verdict"];
|
|
@@ -648,9 +891,68 @@ function adversarialVerdict(row, field) {
|
|
|
648
891
|
return { verdict: "not_applicable", reason: textField(row["reason"], `${field}.reason`) };
|
|
649
892
|
}
|
|
650
893
|
invalid(`${field} must be passed or not_applicable with a reason.`, field);
|
|
894
|
+
return { verdict: "passed" };
|
|
651
895
|
}
|
|
652
896
|
|
|
653
|
-
//
|
|
897
|
+
// components/ulw-loop/src/surface.ts
|
|
898
|
+
import { existsSync as existsSync3, readFileSync } from "node:fs";
|
|
899
|
+
import { dirname, join as join2 } from "node:path";
|
|
900
|
+
import { fileURLToPath } from "node:url";
|
|
901
|
+
var REVIEWER_ROLES_BY_SURFACE = {
|
|
902
|
+
lazycodex: {
|
|
903
|
+
codeReview: "lazycodex-code-reviewer",
|
|
904
|
+
manualQa: "lazycodex-qa-executor",
|
|
905
|
+
gateReview: "lazycodex-gate-reviewer"
|
|
906
|
+
},
|
|
907
|
+
"omo-senpi": {
|
|
908
|
+
codeReview: "omo-senpi-code-reviewer",
|
|
909
|
+
manualQa: "omo-senpi-qa-executor",
|
|
910
|
+
gateReview: "omo-senpi-gate-reviewer"
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
var GATE_REVIEWER_AGENT_NAMES = new Set(Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview));
|
|
914
|
+
var GATE_SECTION_BY_ACCEPTOR = {
|
|
915
|
+
lazycodex: {
|
|
916
|
+
codeReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.codeReview],
|
|
917
|
+
manualQa: [REVIEWER_ROLES_BY_SURFACE.lazycodex.manualQa],
|
|
918
|
+
gateReview: [REVIEWER_ROLES_BY_SURFACE.lazycodex.gateReview]
|
|
919
|
+
},
|
|
920
|
+
"omo-senpi": {
|
|
921
|
+
manualQa: ["main-session"],
|
|
922
|
+
gateReview: ["category:deep", "category:unspecified-high", "category:unspecified-low"]
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
function reviewerRolesFor(surface) {
|
|
926
|
+
return REVIEWER_ROLES_BY_SURFACE[surface];
|
|
927
|
+
}
|
|
928
|
+
var SURFACE_MARKER_FILENAME = "surface.json";
|
|
929
|
+
var SURFACE_ENV_KEY = "OMO_AGENT_TOOLKIT_SURFACE";
|
|
930
|
+
function parseSurface(value) {
|
|
931
|
+
return value === "lazycodex" || value === "omo-senpi" ? value : null;
|
|
932
|
+
}
|
|
933
|
+
function resolveToolkitSurface(options) {
|
|
934
|
+
const env = options?.env ?? process.env;
|
|
935
|
+
const fromEnv = parseSurface(env[SURFACE_ENV_KEY]);
|
|
936
|
+
if (fromEnv !== null)
|
|
937
|
+
return fromEnv;
|
|
938
|
+
const entryDir = options?.entryDir ?? dirname(fileURLToPath(import.meta.url));
|
|
939
|
+
const markerPath = join2(entryDir, SURFACE_MARKER_FILENAME);
|
|
940
|
+
return readMarkerSurface(markerPath) ?? "lazycodex";
|
|
941
|
+
}
|
|
942
|
+
function readMarkerSurface(markerPath) {
|
|
943
|
+
try {
|
|
944
|
+
if (!existsSync3(markerPath))
|
|
945
|
+
return null;
|
|
946
|
+
const parsed = JSON.parse(readFileSync(markerPath, "utf8"));
|
|
947
|
+
return parseSurface(parsed["surface"]);
|
|
948
|
+
} catch (error) {
|
|
949
|
+
if (error instanceof Error)
|
|
950
|
+
return null;
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// components/ulw-loop/src/quality-gate-blockers.ts
|
|
654
956
|
var BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
655
957
|
var URL_PATTERN = /https?:\/\/\S+/g;
|
|
656
958
|
var PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
|
|
@@ -692,131 +994,73 @@ function clearGoalBlockerFields(goal) {
|
|
|
692
994
|
Reflect.deleteProperty(goal, key);
|
|
693
995
|
}
|
|
694
996
|
|
|
695
|
-
//
|
|
696
|
-
var REVIEWER_ROLES = {
|
|
697
|
-
codeReview: "lazycodex-code-reviewer",
|
|
698
|
-
manualQa: "lazycodex-qa-executor",
|
|
699
|
-
gateReview: "lazycodex-gate-reviewer"
|
|
700
|
-
};
|
|
997
|
+
// components/ulw-loop/src/quality-gate.ts
|
|
701
998
|
function reviewerRoleField(value, expected, field) {
|
|
999
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1000
|
+
textField(value, field);
|
|
1001
|
+
return expected;
|
|
1002
|
+
}
|
|
702
1003
|
const actual = textField(value, field);
|
|
703
1004
|
if (actual !== expected)
|
|
704
1005
|
invalid(`${field} must be ${expected}.`, field);
|
|
705
1006
|
return expected;
|
|
706
1007
|
}
|
|
707
|
-
function
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (value === "cli-transcript" || value === "log" || value === "screenshot" || value === "image" || value === "http-dump" || value === "data-diff")
|
|
714
|
-
return value;
|
|
715
|
-
invalid(`${field} must be a supported artifact kind.`, field);
|
|
716
|
-
}
|
|
717
|
-
function artifactCompatible(surface, kind) {
|
|
718
|
-
switch (surface) {
|
|
719
|
-
case "cli":
|
|
720
|
-
case "tmux":
|
|
721
|
-
return kind === "cli-transcript" || kind === "log";
|
|
722
|
-
case "http":
|
|
723
|
-
return kind === "http-dump";
|
|
724
|
-
case "browser":
|
|
725
|
-
case "gui":
|
|
726
|
-
return kind === "screenshot" || kind === "image";
|
|
727
|
-
case "data":
|
|
728
|
-
return kind === "data-diff";
|
|
729
|
-
default:
|
|
730
|
-
invalid("manualQa.surfaceEvidence has an unsupported surface.", "manualQa.surfaceEvidence.surface");
|
|
1008
|
+
function reviewerAcceptorField(value, surface, sectionName) {
|
|
1009
|
+
const field = `${sectionName}.by`;
|
|
1010
|
+
const accepted = GATE_SECTION_BY_ACCEPTOR[surface][sectionName];
|
|
1011
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1012
|
+
textField(value, field);
|
|
1013
|
+
return accepted?.[0] ?? "";
|
|
731
1014
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const absolute = resolve2(opts.repoRoot, path);
|
|
737
|
-
if (!opts.fs.existsSync(absolute))
|
|
738
|
-
invalid(`${field} must point to an existing artifact.`, field);
|
|
739
|
-
if (opts.fs.statSync(absolute).size <= 0)
|
|
740
|
-
invalid(`${field} must point to a non-empty artifact.`, field);
|
|
741
|
-
if (opts.currentAttemptDir !== undefined) {
|
|
742
|
-
const attemptRoot = resolve2(opts.repoRoot, opts.currentAttemptDir);
|
|
743
|
-
if (!isWithinAttemptDir(absolute, attemptRoot))
|
|
744
|
-
invalid(`${field} (${path}) must point to an artifact from the current attempt (${opts.currentAttemptDir}).`, field);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
function artifactMap(refs) {
|
|
748
|
-
const byId = new Map;
|
|
749
|
-
for (const ref of refs) {
|
|
750
|
-
if (byId.has(ref.id))
|
|
751
|
-
invalid(`manualQa.artifactRefs contains duplicate ${ref.id}.`, "manualQa.artifactRefs");
|
|
752
|
-
byId.set(ref.id, ref);
|
|
753
|
-
}
|
|
754
|
-
return byId;
|
|
755
|
-
}
|
|
756
|
-
function parseArtifactRefs(value, opts) {
|
|
757
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
758
|
-
invalid("manualQa.artifactRefs must not be empty.", "manualQa.artifactRefs");
|
|
759
|
-
return value.map((item, index) => {
|
|
760
|
-
const ref = section(item, `manualQa.artifactRefs[${index}]`);
|
|
761
|
-
const path = textField(ref["path"], `manualQa.artifactRefs[${index}].path`);
|
|
762
|
-
checkFile(path, `manualQa.artifactRefs[${index}].path`, opts);
|
|
763
|
-
return {
|
|
764
|
-
id: textField(ref["id"], `manualQa.artifactRefs[${index}].id`),
|
|
765
|
-
kind: kindField(ref["kind"], `manualQa.artifactRefs[${index}].kind`),
|
|
766
|
-
description: textField(ref["description"], `manualQa.artifactRefs[${index}].description`),
|
|
767
|
-
path
|
|
768
|
-
};
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
function referencedArtifacts(value, field, byId) {
|
|
772
|
-
return stringArray(value, field).map((id) => {
|
|
773
|
-
const artifact = byId.get(id);
|
|
774
|
-
if (artifact === undefined)
|
|
775
|
-
invalid(`${field} references unknown artifact ${id}.`, field);
|
|
776
|
-
return artifact;
|
|
777
|
-
});
|
|
1015
|
+
const actual = textField(value, field);
|
|
1016
|
+
if (accepted === undefined || !accepted.includes(actual))
|
|
1017
|
+
invalid(`${field} must be one of ${accepted?.join(", ") ?? "the configured reviewers"}.`, field);
|
|
1018
|
+
return actual;
|
|
778
1019
|
}
|
|
779
1020
|
function validateQualityGate(input, opts) {
|
|
1021
|
+
return withQualityGateCollector(() => validateQualityGateUncollected(input, opts));
|
|
1022
|
+
}
|
|
1023
|
+
function validateQualityGateUncollected(input, opts) {
|
|
1024
|
+
const surface = opts?.reviewerSurface ?? "lazycodex";
|
|
780
1025
|
const gate = section(input, "qualityGate");
|
|
781
|
-
|
|
1026
|
+
if (surface === "omo-senpi" && gate["codeReview"] !== undefined)
|
|
1027
|
+
invalid("omo-senpi gate has no codeReview lane.", "codeReview");
|
|
782
1028
|
const manualQa = section(gate["manualQa"], "manualQa");
|
|
783
1029
|
const gateReview = section(gate["gateReview"], "gateReview");
|
|
784
1030
|
const iteration = section(gate["iteration"], "iteration");
|
|
785
1031
|
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
|
|
1032
|
+
const codeReview = surface === "lazycodex" ? section(gate["codeReview"], "codeReview") : {};
|
|
1033
|
+
const manualQaBy = reviewerAcceptorField(manualQa["by"], surface, "manualQa");
|
|
1034
|
+
const gateReviewBy = reviewerAcceptorField(gateReview["by"], surface, "gateReview");
|
|
1035
|
+
const manualQaEvidence = textField(manualQa["evidence"], "manualQa.evidence");
|
|
1036
|
+
const gateReviewEvidence = textField(gateReview["evidence"], "gateReview.evidence");
|
|
1037
|
+
if (surface === "lazycodex")
|
|
1038
|
+
reviewerRoleField(codeReview?.["by"], "lazycodex-code-reviewer", "codeReview.by");
|
|
786
1039
|
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
|
|
787
1040
|
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
|
|
788
|
-
if (passCount < totalCriteria)
|
|
1041
|
+
if (!isPoisoned("criteriaCoverage.passCount") && passCount < totalCriteria)
|
|
789
1042
|
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
|
|
790
1043
|
const artifactRefs = parseArtifactRefs(manualQa["artifactRefs"], opts);
|
|
791
1044
|
const byId = artifactMap(artifactRefs);
|
|
792
1045
|
const surfaceEvidence = parseSurfaceEvidence(manualQa["surfaceEvidence"], byId);
|
|
793
1046
|
const adversarialCases = parseAdversarialCases(manualQa["adversarialCases"], byId);
|
|
794
|
-
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
795
1047
|
const gateReportPath = textField(gateReview["reportPath"], "gateReview.reportPath");
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
codeReview: {
|
|
800
|
-
by: reviewerRoleField(codeReview["by"], REVIEWER_ROLES.codeReview, "codeReview.by"),
|
|
801
|
-
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
802
|
-
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
803
|
-
reportPath: codeReportPath,
|
|
804
|
-
evidence: textField(codeReview["evidence"], "codeReview.evidence"),
|
|
805
|
-
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
|
|
806
|
-
},
|
|
1048
|
+
if (!isPoisoned("gateReview.reportPath"))
|
|
1049
|
+
checkFile(gateReportPath, "gateReview.reportPath", opts);
|
|
1050
|
+
const common = {
|
|
807
1051
|
manualQa: {
|
|
808
|
-
by:
|
|
1052
|
+
by: manualQaBy,
|
|
809
1053
|
status: literal(manualQa["status"], "passed", "manualQa.status"),
|
|
810
|
-
evidence:
|
|
1054
|
+
evidence: manualQaEvidence,
|
|
811
1055
|
surfaceEvidence,
|
|
812
1056
|
adversarialCases,
|
|
813
1057
|
artifactRefs
|
|
814
1058
|
},
|
|
815
1059
|
gateReview: {
|
|
816
|
-
by:
|
|
1060
|
+
by: gateReviewBy,
|
|
817
1061
|
recommendation: literal(gateReview["recommendation"], "APPROVE", "gateReview.recommendation"),
|
|
818
1062
|
reportPath: gateReportPath,
|
|
819
|
-
evidence:
|
|
1063
|
+
evidence: gateReviewEvidence,
|
|
820
1064
|
blockers: emptyBlockers(gateReview["blockers"], "gateReview.blockers")
|
|
821
1065
|
},
|
|
822
1066
|
iteration: {
|
|
@@ -834,17 +1078,39 @@ function validateQualityGate(input, opts) {
|
|
|
834
1078
|
adversarialClassesCovered: stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered")
|
|
835
1079
|
}
|
|
836
1080
|
};
|
|
1081
|
+
if (surface === "omo-senpi")
|
|
1082
|
+
return { surface, ...common };
|
|
1083
|
+
const codeReportPath = textField(codeReview["reportPath"], "codeReview.reportPath");
|
|
1084
|
+
checkFile(codeReportPath, "codeReview.reportPath", opts);
|
|
1085
|
+
return {
|
|
1086
|
+
surface,
|
|
1087
|
+
...common,
|
|
1088
|
+
codeReview: {
|
|
1089
|
+
by: "lazycodex-code-reviewer",
|
|
1090
|
+
recommendation: literal(codeReview["recommendation"], "APPROVE", "codeReview.recommendation"),
|
|
1091
|
+
codeQualityStatus: codeQualityStatusField(codeReview["codeQualityStatus"], "codeReview.codeQualityStatus"),
|
|
1092
|
+
reportPath: codeReportPath,
|
|
1093
|
+
evidence: textField(codeReview["evidence"], "codeReview.evidence"),
|
|
1094
|
+
blockers: emptyBlockers(codeReview["blockers"], "codeReview.blockers")
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
837
1097
|
}
|
|
838
1098
|
function parseSurfaceEvidence(value, byId) {
|
|
839
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1099
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
840
1100
|
invalid("manualQa.surfaceEvidence must not be empty.", "manualQa.surfaceEvidence");
|
|
841
|
-
|
|
1101
|
+
return [];
|
|
1102
|
+
}
|
|
1103
|
+
return value.flatMap((item, index) => {
|
|
842
1104
|
const row = section(item, `manualQa.surfaceEvidence[${index}]`);
|
|
1105
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}]`))
|
|
1106
|
+
return [];
|
|
843
1107
|
const surface = surfaceField(row["surface"], `manualQa.surfaceEvidence[${index}].surface`);
|
|
844
1108
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.surfaceEvidence[${index}].artifactRefs`, byId);
|
|
845
1109
|
for (const artifact of artifacts) {
|
|
1110
|
+
if (isPoisoned(`manualQa.surfaceEvidence[${index}].surface`) || isPoisonedArtifactKind(artifact.id))
|
|
1111
|
+
continue;
|
|
846
1112
|
if (!artifactCompatible(surface, artifact.kind)) {
|
|
847
|
-
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible.`, "manualQa.surfaceEvidence");
|
|
1113
|
+
invalid(`manualQa.surfaceEvidence ${surface} artifact ${artifact.kind} is incompatible; surface "${surface}" accepts artifact kinds: ${compatibleKindsFor(surface).join(", ")}.`, "manualQa.surfaceEvidence");
|
|
848
1114
|
}
|
|
849
1115
|
}
|
|
850
1116
|
return {
|
|
@@ -858,10 +1124,14 @@ function parseSurfaceEvidence(value, byId) {
|
|
|
858
1124
|
});
|
|
859
1125
|
}
|
|
860
1126
|
function parseAdversarialCases(value, byId) {
|
|
861
|
-
if (!Array.isArray(value) || value.length === 0)
|
|
1127
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
862
1128
|
invalid("manualQa.adversarialCases must not be empty.", "manualQa.adversarialCases");
|
|
863
|
-
|
|
1129
|
+
return [];
|
|
1130
|
+
}
|
|
1131
|
+
return value.flatMap((item, index) => {
|
|
864
1132
|
const row = section(item, `manualQa.adversarialCases[${index}]`);
|
|
1133
|
+
if (isPoisoned(`manualQa.adversarialCases[${index}]`))
|
|
1134
|
+
return [];
|
|
865
1135
|
const artifacts = referencedArtifacts(row["artifactRefs"], `manualQa.adversarialCases[${index}].artifactRefs`, byId);
|
|
866
1136
|
const verdictInfo = adversarialVerdict(row, `manualQa.adversarialCases[${index}]`);
|
|
867
1137
|
return {
|
|
@@ -876,7 +1146,7 @@ function parseAdversarialCases(value, byId) {
|
|
|
876
1146
|
});
|
|
877
1147
|
}
|
|
878
1148
|
|
|
879
|
-
//
|
|
1149
|
+
// components/ulw-loop/src/cli-arg-parser.ts
|
|
880
1150
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
881
1151
|
var VALUE_FLAGS = new Set("--brief --brief-file --session-id --codex-goal-mode --validation-batch-json --goal --goal-id --criterion-id --status --evidence --notes --codex-goal-json --quality-gate-json --kind --rationale --title --objective --target-goal-id --source --after-json --directive-json --directive-file --idempotency-key --proposals-json".split(" "));
|
|
882
1152
|
var SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
|
|
@@ -967,7 +1237,7 @@ function parseRecordEvidenceArgs(argv) {
|
|
|
967
1237
|
return notes ? { ...result, notes } : result;
|
|
968
1238
|
}
|
|
969
1239
|
|
|
970
|
-
//
|
|
1240
|
+
// components/ulw-loop/src/validation-batch.ts
|
|
971
1241
|
function isObject(value) {
|
|
972
1242
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
973
1243
|
}
|
|
@@ -1085,14 +1355,11 @@ function fail(message, code = "ULW_LOOP_VALIDATION_BATCH_INVALID") {
|
|
|
1085
1355
|
throw new UlwLoopError(message, code);
|
|
1086
1356
|
}
|
|
1087
1357
|
|
|
1088
|
-
//
|
|
1089
|
-
var QUALITY_GATE_FS = { existsSync:
|
|
1358
|
+
// components/ulw-loop/src/checkpoint.ts
|
|
1359
|
+
var QUALITY_GATE_FS = { existsSync: existsSync4, statSync };
|
|
1090
1360
|
function ulwLoopFail2(message, code) {
|
|
1091
1361
|
throw new UlwLoopError(message, code);
|
|
1092
1362
|
}
|
|
1093
|
-
function normalizeObjective3(value) {
|
|
1094
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1095
|
-
}
|
|
1096
1363
|
function nonEmptyEvidence2(value) {
|
|
1097
1364
|
const trimmed = value.trim();
|
|
1098
1365
|
return trimmed || ulwLoopFail2("Evidence must be a non-empty string.", "ulw_loop_evidence_required");
|
|
@@ -1112,7 +1379,7 @@ async function readJsonInput2(raw, repoRoot) {
|
|
|
1112
1379
|
throw error;
|
|
1113
1380
|
}
|
|
1114
1381
|
const path = resolve3(repoRoot, trimmed);
|
|
1115
|
-
if (!
|
|
1382
|
+
if (!existsSync4(path))
|
|
1116
1383
|
return ulwLoopFail2("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
|
|
1117
1384
|
try {
|
|
1118
1385
|
return JSON.parse(await readFile5(path, "utf8"));
|
|
@@ -1155,13 +1422,13 @@ function ledgerKind2(status, goal, aggregateCompletion) {
|
|
|
1155
1422
|
return status === "blocked" ? "goal_blocked" : "goal_failed";
|
|
1156
1423
|
}
|
|
1157
1424
|
function buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion) {
|
|
1158
|
-
const watch = qualityGate?.codeReview.codeQualityStatus === "WATCH";
|
|
1425
|
+
const watch = qualityGate?.surface === "lazycodex" && qualityGate.codeReview.codeQualityStatus === "WATCH";
|
|
1159
1426
|
const entry = {
|
|
1160
1427
|
at: now,
|
|
1161
1428
|
kind: ledgerKind2(args.status, goal, aggregateCompletion),
|
|
1162
1429
|
goalId: goal.id,
|
|
1163
1430
|
status: goal.status,
|
|
1164
|
-
evidence: watch ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
|
|
1431
|
+
evidence: watch && qualityGate.surface === "lazycodex" ? `${args.evidence} | codeQuality=WATCH: ${qualityGate.codeReview.evidence}` : args.evidence
|
|
1165
1432
|
};
|
|
1166
1433
|
if (codexGoal !== undefined)
|
|
1167
1434
|
entry.codexGoal = codexGoal;
|
|
@@ -1196,23 +1463,20 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1196
1463
|
requireEssentialCriteriaPass(goal);
|
|
1197
1464
|
else
|
|
1198
1465
|
requireAllCriteriaPass(goal);
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
const taskScoped = completedTaskScoped || activeFinalTaskScoped;
|
|
1214
|
-
if (!taskScoped)
|
|
1215
|
-
throw new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ulw_loop_codex_snapshot_mismatch");
|
|
1466
|
+
let codexValidationError;
|
|
1467
|
+
try {
|
|
1468
|
+
codexGoal = await validateCheckpointCodexGoal({
|
|
1469
|
+
repoRoot,
|
|
1470
|
+
plan,
|
|
1471
|
+
goal,
|
|
1472
|
+
raw: args.codexGoalJson,
|
|
1473
|
+
evidence,
|
|
1474
|
+
...scope === undefined ? {} : { scope }
|
|
1475
|
+
});
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
if (!(error instanceof UlwLoopError))
|
|
1478
|
+
throw error;
|
|
1479
|
+
codexValidationError = error;
|
|
1216
1480
|
}
|
|
1217
1481
|
if (closesBatch)
|
|
1218
1482
|
requireBatchFinalReady(plan, goal);
|
|
@@ -1221,13 +1485,22 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1221
1485
|
if (final)
|
|
1222
1486
|
aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
|
|
1223
1487
|
if (final || aggregateCompletion !== undefined || closesBatch) {
|
|
1224
|
-
|
|
1225
|
-
repoRoot,
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1488
|
+
try {
|
|
1489
|
+
qualityGate = validateQualityGate(await readJsonInput2(args.qualityGateJson, repoRoot), {
|
|
1490
|
+
repoRoot,
|
|
1491
|
+
fs: QUALITY_GATE_FS,
|
|
1492
|
+
reviewerSurface: resolveToolkitSurface(),
|
|
1493
|
+
...plan.evidenceLayoutVersion === 2 ? { currentAttemptDir: ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope) } : {}
|
|
1494
|
+
});
|
|
1495
|
+
requireBatchGate(plan, goal, qualityGate);
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
if (!(error instanceof UlwLoopError) || codexValidationError === undefined)
|
|
1498
|
+
throw error;
|
|
1499
|
+
throw combineCheckpointValidationErrors(codexValidationError, error);
|
|
1500
|
+
}
|
|
1230
1501
|
}
|
|
1502
|
+
if (codexValidationError !== undefined)
|
|
1503
|
+
throw codexValidationError;
|
|
1231
1504
|
goal.status = "complete";
|
|
1232
1505
|
goal.completedAt = now;
|
|
1233
1506
|
goal.evidence = evidence;
|
|
@@ -1252,19 +1525,150 @@ async function checkpointUlwLoop(repoRoot, args, scope) {
|
|
|
1252
1525
|
});
|
|
1253
1526
|
}
|
|
1254
1527
|
|
|
1255
|
-
//
|
|
1528
|
+
// components/ulw-loop/src/checkpoint-template.ts
|
|
1529
|
+
function artifactPath(base, name) {
|
|
1530
|
+
return `${base}/${name}`;
|
|
1531
|
+
}
|
|
1532
|
+
function gateTemplate(surface, base) {
|
|
1533
|
+
const artifacts = [
|
|
1534
|
+
{
|
|
1535
|
+
id: "artifact-cli",
|
|
1536
|
+
kind: "cli-transcript",
|
|
1537
|
+
description: "<replace:artifact description>",
|
|
1538
|
+
path: artifactPath(base, "cli-transcript.txt")
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
id: "artifact-data",
|
|
1542
|
+
kind: "data-diff",
|
|
1543
|
+
description: "<replace:artifact description>",
|
|
1544
|
+
path: artifactPath(base, "data-diff.txt")
|
|
1545
|
+
}
|
|
1546
|
+
];
|
|
1547
|
+
const manualQa = {
|
|
1548
|
+
by: surface === "omo-senpi" ? "main-session" : "lazycodex-qa-executor",
|
|
1549
|
+
status: "passed",
|
|
1550
|
+
evidence: "<replace:manual QA evidence>",
|
|
1551
|
+
surfaceEvidence: [
|
|
1552
|
+
{
|
|
1553
|
+
id: "surface-cli",
|
|
1554
|
+
criterionRef: "<replace:criterion id>",
|
|
1555
|
+
surface: "cli",
|
|
1556
|
+
invocation: "<replace:command>",
|
|
1557
|
+
verdict: "passed",
|
|
1558
|
+
artifactRefs: ["artifact-cli"]
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
id: "surface-data",
|
|
1562
|
+
criterionRef: "<replace:criterion id>",
|
|
1563
|
+
surface: "data",
|
|
1564
|
+
invocation: "<replace:command>",
|
|
1565
|
+
verdict: "passed",
|
|
1566
|
+
artifactRefs: ["artifact-data"]
|
|
1567
|
+
}
|
|
1568
|
+
],
|
|
1569
|
+
adversarialCases: [
|
|
1570
|
+
{
|
|
1571
|
+
id: "<replace:adversarial case id>",
|
|
1572
|
+
criterionRef: "<replace:criterion id>",
|
|
1573
|
+
scenario: "<replace:scenario>",
|
|
1574
|
+
expectedBehavior: "<replace:expected behavior>",
|
|
1575
|
+
verdict: "not_applicable",
|
|
1576
|
+
reason: "<replace:reason>",
|
|
1577
|
+
artifactRefs: ["artifact-cli"]
|
|
1578
|
+
}
|
|
1579
|
+
],
|
|
1580
|
+
artifactRefs: artifacts
|
|
1581
|
+
};
|
|
1582
|
+
const common = {
|
|
1583
|
+
manualQa,
|
|
1584
|
+
gateReview: {
|
|
1585
|
+
by: surface === "omo-senpi" ? "category:deep" : "lazycodex-gate-reviewer",
|
|
1586
|
+
recommendation: "APPROVE",
|
|
1587
|
+
reportPath: artifactPath(base, "gate-review.md"),
|
|
1588
|
+
evidence: "<replace:gate review evidence>",
|
|
1589
|
+
blockers: [],
|
|
1590
|
+
notes: []
|
|
1591
|
+
},
|
|
1592
|
+
iteration: {
|
|
1593
|
+
fullRerun: true,
|
|
1594
|
+
status: "passed",
|
|
1595
|
+
rerunCommands: ["<replace:verification command>"],
|
|
1596
|
+
evidence: "<replace:iteration evidence>"
|
|
1597
|
+
},
|
|
1598
|
+
criteriaCoverage: {
|
|
1599
|
+
totalCriteria: 0,
|
|
1600
|
+
passCount: 0,
|
|
1601
|
+
originalIntent: "<replace:original intent>",
|
|
1602
|
+
desiredOutcome: "<replace:desired outcome>",
|
|
1603
|
+
userOutcomeReview: "<replace:user outcome review>",
|
|
1604
|
+
adversarialClassesCovered: ["<replace:adversarial class>"]
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
if (surface === "omo-senpi")
|
|
1608
|
+
return common;
|
|
1609
|
+
return {
|
|
1610
|
+
codeReview: {
|
|
1611
|
+
by: "lazycodex-code-reviewer",
|
|
1612
|
+
recommendation: "APPROVE",
|
|
1613
|
+
codeQualityStatus: "CLEAR",
|
|
1614
|
+
reportPath: artifactPath(base, "code-review.md"),
|
|
1615
|
+
evidence: "<replace:code review evidence>",
|
|
1616
|
+
blockers: []
|
|
1617
|
+
},
|
|
1618
|
+
...common
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
async function checkpointTemplate(repoRoot, scope, goalId) {
|
|
1622
|
+
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
1623
|
+
const targetId = goalId ?? plan.activeGoalId;
|
|
1624
|
+
const active = plan.goals.find((goal) => goal.id === targetId);
|
|
1625
|
+
if (goalId !== undefined && active === undefined)
|
|
1626
|
+
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
1627
|
+
const hasAttempt = plan.evidenceLayoutVersion === 2 && active !== undefined;
|
|
1628
|
+
const attemptDir = hasAttempt ? ulwLoopAttemptEvidenceDir(active.id, active.attempt, scope) : ".omo/evidence";
|
|
1629
|
+
const guidance = [
|
|
1630
|
+
"codex-goal-json requires goal.objective to equal the plan's codexObjective verbatim; do not paraphrase it.",
|
|
1631
|
+
"Fill every <replace:...> value with plausible non-empty evidence and use real, non-empty artifact files.",
|
|
1632
|
+
'Passing codex-goal-json example: {"goal":{"objective":"<plan codexObjective verbatim>","status":"complete"}}.',
|
|
1633
|
+
'Passing quality-gate-json example requires gateReview {"by":"category:deep","recommendation":"APPROVE","evidence":"review passed","reportPath":"<attemptDir>/gate-review.md","blockers":[],"notes":[]}, manualQa.artifactRefs objects, iteration, and criteriaCoverage.',
|
|
1634
|
+
...hasAttempt ? [] : ["This plan is evidence-layout v1; artifacts go under .omo/evidence/."]
|
|
1635
|
+
].join(" ");
|
|
1636
|
+
return {
|
|
1637
|
+
qualityGateTemplate: gateTemplate(resolveToolkitSurface(), attemptDir),
|
|
1638
|
+
codexGoalTemplate: {
|
|
1639
|
+
goal: { objective: plan.codexObjective ?? "<replace:codex objective>", status: "complete" }
|
|
1640
|
+
},
|
|
1641
|
+
guidance,
|
|
1642
|
+
...hasAttempt ? { attemptDir } : {}
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// components/ulw-loop/src/cli-output.ts
|
|
1256
1647
|
var ULW_LOOP_HELP = `Usage:
|
|
1648
|
+
omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
|
|
1649
|
+
omo-agent-toolkit help | --help | -h (this message)
|
|
1650
|
+
omo-agent-toolkit ulw-loop help
|
|
1257
1651
|
omo-agent-toolkit ulw-loop create-goals --brief "..." [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--validation-batch-json <json-or-path>] [--force] [--json]
|
|
1258
1652
|
omo-agent-toolkit ulw-loop status [--json]
|
|
1259
1653
|
omo-agent-toolkit ulw-loop complete-goals [--retry-failed] [--json]
|
|
1260
1654
|
omo-agent-toolkit ulw-loop criteria --goal-id <id> [--json]
|
|
1261
1655
|
omo-agent-toolkit ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
|
|
1656
|
+
omo-agent-toolkit ulw-loop checkpoint --print-template [--goal-id <id>] [--json]
|
|
1262
1657
|
omo-agent-toolkit ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--no-advance] [--json]
|
|
1263
1658
|
omo-agent-toolkit ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--proposals-json <json-or-path>] [--json]
|
|
1264
1659
|
omo-agent-toolkit ulw-loop add-goal --title "..." --objective "..." [--json]
|
|
1265
1660
|
omo-agent-toolkit ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]
|
|
1266
1661
|
|
|
1267
|
-
All subcommands accept [--session-id <id>] to isolate state under .omo/ulw-loop/<id>/; without it, Codex session env is used when present
|
|
1662
|
+
All subcommands accept [--session-id <id>] to isolate state under .omo/ulw-loop/<id>/; without it, Codex session env is used when present.
|
|
1663
|
+
Every subcommand accepts --help | -h to print its own usage line.`;
|
|
1664
|
+
function subcommandHelp(subcommand) {
|
|
1665
|
+
const lines = ULW_LOOP_HELP.split(`
|
|
1666
|
+
`).filter((line) => line.trimStart().startsWith(`omo-agent-toolkit ulw-loop ${subcommand}`));
|
|
1667
|
+
if (lines.length === 0)
|
|
1668
|
+
return ULW_LOOP_HELP;
|
|
1669
|
+
return ["Usage:", ...lines].join(`
|
|
1670
|
+
`);
|
|
1671
|
+
}
|
|
1268
1672
|
function printJson(value) {
|
|
1269
1673
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
1270
1674
|
`);
|
|
@@ -1330,17 +1734,18 @@ function normalizeCodexGoalMode(value) {
|
|
|
1330
1734
|
throw new UlwLoopError("Invalid --codex-goal-mode; expected aggregate or per_story.", "ULW_LOOP_CODEX_GOAL_MODE_INVALID", { details: { value } });
|
|
1331
1735
|
}
|
|
1332
1736
|
|
|
1333
|
-
//
|
|
1737
|
+
// components/ulw-loop/src/codex-goal-instruction.ts
|
|
1334
1738
|
function buildCodexGoalInstruction(args) {
|
|
1335
1739
|
const mode = codexGoalMode(args.plan);
|
|
1336
1740
|
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
|
|
1337
1741
|
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
|
|
1338
|
-
|
|
1742
|
+
const surface = args.surface ?? resolveToolkitSurface();
|
|
1743
|
+
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal, surface), json: createGoal };
|
|
1339
1744
|
}
|
|
1340
1745
|
function buildCreateGoalPayload(plan, goal) {
|
|
1341
1746
|
return { objective: expectedCodexObjective(plan, goal) };
|
|
1342
1747
|
}
|
|
1343
|
-
function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
1748
|
+
function buildText(mode, plan, goal, createGoal, isFinal, surface) {
|
|
1344
1749
|
return joinLines([
|
|
1345
1750
|
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
|
|
1346
1751
|
`Mode: ${mode}`,
|
|
@@ -1357,7 +1762,7 @@ function buildText(mode, plan, goal, createGoal, isFinal) {
|
|
|
1357
1762
|
"- Goals are unlimited. Do not add numeric limits.",
|
|
1358
1763
|
...modeConstraintLines(mode, isFinal),
|
|
1359
1764
|
...evidenceLayoutLines(plan),
|
|
1360
|
-
finalSection(plan, goal, isFinal, mode === "aggregate"),
|
|
1765
|
+
finalSection(plan, goal, isFinal, mode === "aggregate", surface),
|
|
1361
1766
|
...checkpointLines(plan, mode),
|
|
1362
1767
|
"",
|
|
1363
1768
|
"create_goal payload:",
|
|
@@ -1409,18 +1814,21 @@ function evidenceLayoutLines(plan) {
|
|
|
1409
1814
|
"- Evidence layout v2: write every artifact for the active goal (QA matrix, review reports, receipts) under the current attempt directory — read currentAttemptDir from `omo-agent-toolkit ulw-loop status --json` (.omo/evidence/ulw/<session>/<goalId>/a<attempt>). The final checkpoint rejects quality-gate artifacts outside that directory."
|
|
1410
1815
|
];
|
|
1411
1816
|
}
|
|
1412
|
-
function finalSection(plan, goal, isFinal, aggregate) {
|
|
1817
|
+
function finalSection(plan, goal, isFinal, aggregate, surface) {
|
|
1818
|
+
const roles = reviewerRolesFor(surface);
|
|
1413
1819
|
if (!isFinal)
|
|
1414
1820
|
return "- This is not the final ulw-loop story; do not run the final reviewer/manual-QA/gate-review quality gate yet.";
|
|
1415
1821
|
const option = sessionOption(plan);
|
|
1822
|
+
if (surface === "omo-senpi")
|
|
1823
|
+
return senpiFinalSection(plan, goal, aggregate);
|
|
1416
1824
|
const blockerCommand = `omo-agent-toolkit ulw-loop record-review-blockers${option} --goal-id ${goal.id} --title "Resolve final code-review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>" --codex-goal-json "<active get_goal JSON or path>"`;
|
|
1417
1825
|
const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<targeted verification/manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
|
|
1418
1826
|
return joinLines([
|
|
1419
1827
|
"Final story — run mandatory quality gate before update_goal:",
|
|
1420
1828
|
"- Run targeted verification for changed behavior.",
|
|
1421
1829
|
"- Confirm every manualQa artifact path exists and has non-zero size.",
|
|
1422
|
-
|
|
1423
|
-
|
|
1830
|
+
`- First spawn ${roles.codeReview} and ${roles.manualQa} in parallel (fork_context: false on the v1 surface; fork_turns: "none" on v2). Include the original brief, goal objectives, desired user-visible outcome, diff, and evidence; wait for BOTH to return and confirm their report artifacts exist on disk (code-review report + manualQa matrix).`,
|
|
1831
|
+
`- Only then spawn ${roles.gateReview} (same fork settings), passing those artifact paths.`,
|
|
1424
1832
|
"- Require clean codeReview, manualQa, gateReview, iteration, and criteriaCoverage. criteriaCoverage must summarize originalIntent, desiredOutcome, and userOutcomeReview; counts alone are not approval.",
|
|
1425
1833
|
"- On a reviewer REJECT, fix only the cited blockers, rerun the affected verification/Manual-QA, and re-review the delta at most TWICE; if blockers remain, record them and surface to the user.",
|
|
1426
1834
|
"- If codeQualityStatus is WATCH, include the WATCH notes verbatim in your final user-facing message.",
|
|
@@ -1430,6 +1838,20 @@ function finalSection(plan, goal, isFinal, aggregate) {
|
|
|
1430
1838
|
` ${checkpointCommand}`
|
|
1431
1839
|
]);
|
|
1432
1840
|
}
|
|
1841
|
+
function senpiFinalSection(plan, goal, aggregate) {
|
|
1842
|
+
const option = sessionOption(plan);
|
|
1843
|
+
const checkpointCommand = `omo-agent-toolkit ulw-loop checkpoint${option} --goal-id ${goal.id} --status complete --evidence "<manualQa/gateReview evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "$(omo-agent-toolkit ulw-loop checkpoint${option} --print-template)"`;
|
|
1844
|
+
return joinLines([
|
|
1845
|
+
"Final story — run the single-reviewer quality gate before update_goal:",
|
|
1846
|
+
'- Run manual QA yourself and write the non-empty artifact under currentAttemptDir; set manualQa.by to "main-session".',
|
|
1847
|
+
'- Spawn exactly one gate reviewer with task(category: "deep").',
|
|
1848
|
+
"- If that task fails with any model_unavailable failure, retry with category:unspecified-high, then category:unspecified-low.",
|
|
1849
|
+
"- Set gateReview.by to the exact category:<name> literal used for the successful task.",
|
|
1850
|
+
"- Build the gate JSON with omo-agent-toolkit ulw-loop checkpoint --print-template, then fill manualQa, gateReview, iteration, and criteriaCoverage.",
|
|
1851
|
+
'- Require passed manualQa, approved gateReview, passed iteration, and complete criteriaCoverage before update_goal({status: "complete"}).',
|
|
1852
|
+
aggregate ? `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint the aggregate story: ${checkpointCommand}` : `- If the gate is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint: ${checkpointCommand}`
|
|
1853
|
+
]);
|
|
1854
|
+
}
|
|
1433
1855
|
function sessionOption(plan) {
|
|
1434
1856
|
const prefix = ".omo/ulw-loop/";
|
|
1435
1857
|
const suffix = "/goals.json";
|
|
@@ -1443,11 +1865,11 @@ function joinLines(lines) {
|
|
|
1443
1865
|
`);
|
|
1444
1866
|
}
|
|
1445
1867
|
|
|
1446
|
-
//
|
|
1447
|
-
import { existsSync as
|
|
1868
|
+
// components/ulw-loop/src/plan-crud.ts
|
|
1869
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1448
1870
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1449
1871
|
|
|
1450
|
-
//
|
|
1872
|
+
// components/ulw-loop/src/plan-goal-factory.ts
|
|
1451
1873
|
function cleanLine(line) {
|
|
1452
1874
|
return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
|
|
1453
1875
|
}
|
|
@@ -1536,7 +1958,7 @@ function appendGoalToPlan(plan, title, objective, now) {
|
|
|
1536
1958
|
return goal;
|
|
1537
1959
|
}
|
|
1538
1960
|
|
|
1539
|
-
//
|
|
1961
|
+
// components/ulw-loop/src/plan-crud.ts
|
|
1540
1962
|
function isScheduleEligible(goal) {
|
|
1541
1963
|
return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked";
|
|
1542
1964
|
}
|
|
@@ -1554,7 +1976,7 @@ function clearGoalBlockerFields2(goal) {
|
|
|
1554
1976
|
}
|
|
1555
1977
|
async function createUlwLoopPlan(repoRoot, args, scope) {
|
|
1556
1978
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
1557
|
-
if (!args.force &&
|
|
1979
|
+
if (!args.force && existsSync5(ulwLoopGoalsPath(repoRoot, scope))) {
|
|
1558
1980
|
const existing = await readUlwLoopPlan(repoRoot, scope);
|
|
1559
1981
|
if (isUlwLoopDone(existing))
|
|
1560
1982
|
throw completedPlanExistsError(scope);
|
|
@@ -1663,7 +2085,7 @@ function summarizeUlwLoopPlan(plan) {
|
|
|
1663
2085
|
};
|
|
1664
2086
|
}
|
|
1665
2087
|
|
|
1666
|
-
//
|
|
2088
|
+
// components/ulw-loop/src/checkpoint-continuation.ts
|
|
1667
2089
|
async function checkpointAndContinue(repoRoot, args, scope) {
|
|
1668
2090
|
const result = await checkpointUlwLoop(repoRoot, args, scope);
|
|
1669
2091
|
if (args.status !== "complete" || result.aggregateCompletion !== undefined || !args.advance)
|
|
@@ -1675,6 +2097,14 @@ async function checkpointAndContinue(repoRoot, args, scope) {
|
|
|
1675
2097
|
return { ...result, plan: next.plan, next: { resumed: next.resumed, goal: next.goal, instruction } };
|
|
1676
2098
|
}
|
|
1677
2099
|
async function checkpoint(repoRoot, argv, json, scope) {
|
|
2100
|
+
if (hasFlag(argv, "--print-template")) {
|
|
2101
|
+
const template = await checkpointTemplate(repoRoot, scope, readValue(argv, "--goal-id"));
|
|
2102
|
+
if (json)
|
|
2103
|
+
printJson({ ok: true, ...template });
|
|
2104
|
+
else
|
|
2105
|
+
printJson(template);
|
|
2106
|
+
return 0;
|
|
2107
|
+
}
|
|
1678
2108
|
const goalId = required2(argv, "--goal-id");
|
|
1679
2109
|
const statusValue = checkpointStatus(required2(argv, "--status"));
|
|
1680
2110
|
const evidence = required2(argv, "--evidence");
|
|
@@ -1726,10 +2156,10 @@ function checkpointStatus(value) {
|
|
|
1726
2156
|
throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
|
|
1727
2157
|
}
|
|
1728
2158
|
|
|
1729
|
-
//
|
|
2159
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
1730
2160
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
1731
2161
|
|
|
1732
|
-
//
|
|
2162
|
+
// components/ulw-loop/src/cli-steering.ts
|
|
1733
2163
|
var SOURCES = ["user_prompt_submit", "finding", "cli"];
|
|
1734
2164
|
var STEERING_KIND_HELP = [
|
|
1735
2165
|
`Allowed --kind values: ${ULW_LOOP_STEERING_MUTATION_KINDS.join(", ")}`,
|
|
@@ -2036,7 +2466,7 @@ function printSteerBatchResult(result, json) {
|
|
|
2036
2466
|
printStatus(result.plan);
|
|
2037
2467
|
}
|
|
2038
2468
|
|
|
2039
|
-
//
|
|
2469
|
+
// components/ulw-loop/src/review-blockers.ts
|
|
2040
2470
|
var BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
|
|
2041
2471
|
function ulwLoopError(message, code) {
|
|
2042
2472
|
throw new UlwLoopError(message, code);
|
|
@@ -2075,9 +2505,10 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2075
2505
|
ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
|
|
2076
2506
|
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
|
|
2077
2507
|
const aggregate = codexGoalMode(plan) === "aggregate";
|
|
2078
|
-
const
|
|
2508
|
+
const expected = expectedCodexObjective(plan, goal);
|
|
2509
|
+
const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expected, ...aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}, allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
|
|
2079
2510
|
if (!reconciliation.ok)
|
|
2080
|
-
|
|
2511
|
+
throw codexSnapshotMismatchError({ reconciliation, snapshot, expectedObjective: expected });
|
|
2081
2512
|
const now = iso();
|
|
2082
2513
|
for (const field of BLOCKER_FIELDS)
|
|
2083
2514
|
Reflect.deleteProperty(goal, field);
|
|
@@ -2102,7 +2533,36 @@ async function recordFinalReviewBlockers(repoRoot, args, scope) {
|
|
|
2102
2533
|
});
|
|
2103
2534
|
}
|
|
2104
2535
|
|
|
2105
|
-
//
|
|
2536
|
+
// components/ulw-loop/src/status-next-actions.ts
|
|
2537
|
+
function statusNextActions(plan) {
|
|
2538
|
+
const actions = [];
|
|
2539
|
+
const active = plan.goals.find((goal) => goal.id === plan.activeGoalId);
|
|
2540
|
+
if (plan.goals.length === 0)
|
|
2541
|
+
actions.push(`No goals yet: bootstrap with \`${ULW_LOOP_CREATE_GOALS_COMMAND}\`.`);
|
|
2542
|
+
else if (active !== undefined)
|
|
2543
|
+
actions.push(...activeGoalActions(plan, active));
|
|
2544
|
+
for (const goal of plan.goals.filter((candidate) => candidate.status === "review_blocked"))
|
|
2545
|
+
actions.push(`${goal.id} is review_blocked: capture the reviewer verdict with \`omo-agent-toolkit ulw-loop record-review-blockers --goal-id ${goal.id} --title "<title>" --objective "<objective>" --evidence "<verdict>" --codex-goal-json '<get_goal json>'\`.`);
|
|
2546
|
+
if (plan.evidenceLayoutVersion !== 2 || active === undefined)
|
|
2547
|
+
actions.push("plan is evidence-layout v1; artifacts go under .omo/evidence/");
|
|
2548
|
+
return actions;
|
|
2549
|
+
}
|
|
2550
|
+
function activeGoalActions(plan, active) {
|
|
2551
|
+
const unresolved = active.successCriteria.filter((criterion) => criterion.status !== "pass");
|
|
2552
|
+
if (unresolved.length > 0)
|
|
2553
|
+
return [
|
|
2554
|
+
`${active.id} has ${unresolved.length} unresolved criterion(s) (${unresolved.map((criterion) => criterion.id).join(", ")}): record proof with \`omo-agent-toolkit ulw-loop record-evidence --goal-id ${active.id} --criterion-id <id> --status pass --evidence "<observable proof>"\`.`
|
|
2555
|
+
];
|
|
2556
|
+
if (hasAllCriteriaPass(active) && isFinalRunCompletionCandidate(plan, active))
|
|
2557
|
+
return [
|
|
2558
|
+
`${active.id} passes every criterion and is the final story: update_goal complete, then checkpoint --print-template to build the final quality gate.`
|
|
2559
|
+
];
|
|
2560
|
+
return [
|
|
2561
|
+
`${active.id} passes every criterion: close it with \`omo-agent-toolkit ulw-loop checkpoint --goal-id ${active.id} --status complete --evidence "<proof>" --codex-goal-json '<get_goal json>'\`.`
|
|
2562
|
+
];
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// components/ulw-loop/src/steering-mutations.ts
|
|
2106
2566
|
var read2 = (value, key) => Object.entries(value).find(([name]) => name === key)?.[1];
|
|
2107
2567
|
var isText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
2108
2568
|
var text3 = (value, key) => {
|
|
@@ -2189,7 +2649,7 @@ function reviseCriterion(plan, proposal, now) {
|
|
|
2189
2649
|
target.updatedAt = now;
|
|
2190
2650
|
}
|
|
2191
2651
|
|
|
2192
|
-
//
|
|
2652
|
+
// components/ulw-loop/src/steering-snapshot.ts
|
|
2193
2653
|
function buildSteeringPlanSnapshot(plan, changedGoalIds) {
|
|
2194
2654
|
const snapshot = {
|
|
2195
2655
|
updatedAt: plan.updatedAt,
|
|
@@ -2213,7 +2673,7 @@ function changedGoalIdsBetween(before, after2) {
|
|
|
2213
2673
|
return changed;
|
|
2214
2674
|
}
|
|
2215
2675
|
|
|
2216
|
-
//
|
|
2676
|
+
// components/ulw-loop/src/steering.ts
|
|
2217
2677
|
var SOURCES2 = ["user_prompt_submit", "finding", "cli"];
|
|
2218
2678
|
var PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
|
|
2219
2679
|
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
@@ -2450,7 +2910,7 @@ function ledgerEntry(proposal, audit, at) {
|
|
|
2450
2910
|
return entry;
|
|
2451
2911
|
}
|
|
2452
2912
|
|
|
2453
|
-
//
|
|
2913
|
+
// components/ulw-loop/src/steering-batch.ts
|
|
2454
2914
|
async function steerUlwLoopBatch(repoRoot, proposals, scope) {
|
|
2455
2915
|
return withUlwLoopMutationLock(repoRoot, scope, async () => {
|
|
2456
2916
|
const plan = await readUlwLoopPlan(repoRoot, scope);
|
|
@@ -2531,7 +2991,7 @@ function ledgerEntry2(proposal, audit, at) {
|
|
|
2531
2991
|
return entry;
|
|
2532
2992
|
}
|
|
2533
2993
|
|
|
2534
|
-
//
|
|
2994
|
+
// components/ulw-loop/src/cli-subcommands.ts
|
|
2535
2995
|
async function createGoals(repoRoot, argv, json, scope) {
|
|
2536
2996
|
const briefFile = readValue(argv, "--brief-file");
|
|
2537
2997
|
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile6(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
|
|
@@ -2565,7 +3025,8 @@ async function status(repoRoot, json, scope) {
|
|
|
2565
3025
|
ok: true,
|
|
2566
3026
|
plan,
|
|
2567
3027
|
summary: summarizeUlwLoopPlan(plan),
|
|
2568
|
-
...currentAttemptDir === undefined ? {} : { currentAttemptDir }
|
|
3028
|
+
...currentAttemptDir === undefined ? {} : { currentAttemptDir },
|
|
3029
|
+
nextActions: statusNextActions(plan)
|
|
2569
3030
|
});
|
|
2570
3031
|
} else
|
|
2571
3032
|
printStatus(plan);
|
|
@@ -2687,7 +3148,7 @@ function findGoal3(plan, goalId) {
|
|
|
2687
3148
|
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
|
|
2688
3149
|
}
|
|
2689
3150
|
|
|
2690
|
-
//
|
|
3151
|
+
// components/ulw-loop/src/cli-commands.ts
|
|
2691
3152
|
var ULW_LOOP_SUBCOMMANDS = [
|
|
2692
3153
|
"help",
|
|
2693
3154
|
"create-goals",
|
|
@@ -2722,6 +3183,11 @@ async function ulwLoopCommand(argv) {
|
|
|
2722
3183
|
`);
|
|
2723
3184
|
return 1;
|
|
2724
3185
|
}
|
|
3186
|
+
if (command !== "help" && (hasFlag(rest, "--help") || hasFlag(rest, "-h"))) {
|
|
3187
|
+
process.stdout.write(`${subcommandHelp(command)}
|
|
3188
|
+
`);
|
|
3189
|
+
return 0;
|
|
3190
|
+
}
|
|
2725
3191
|
switch (command) {
|
|
2726
3192
|
case "help":
|
|
2727
3193
|
process.stdout.write(`${ULW_LOOP_HELP}
|
|
@@ -2776,7 +3242,7 @@ function commandScope(argv) {
|
|
|
2776
3242
|
if (sessionIdFlagPresent(argv)) {
|
|
2777
3243
|
const sessionId2 = readValue(argv, SESSION_ID_FLAG)?.trim();
|
|
2778
3244
|
if (!sessionId2) {
|
|
2779
|
-
throw new UlwLoopError(
|
|
3245
|
+
throw new UlwLoopError(sessionIdRequiredMessage(SESSION_ID_FLAG), "ULW_LOOP_SESSION_ID_REQUIRED", {
|
|
2780
3246
|
details: { flag: SESSION_ID_FLAG }
|
|
2781
3247
|
});
|
|
2782
3248
|
}
|
|
@@ -2786,12 +3252,12 @@ function commandScope(argv) {
|
|
|
2786
3252
|
return sessionId === null ? undefined : { sessionId };
|
|
2787
3253
|
}
|
|
2788
3254
|
|
|
2789
|
-
//
|
|
2790
|
-
import { readFileSync as
|
|
3255
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
3256
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2791
3257
|
|
|
2792
|
-
//
|
|
2793
|
-
import { existsSync as
|
|
2794
|
-
import { fileURLToPath } from "node:url";
|
|
3258
|
+
// components/ulw-loop/src/ultrawork-skill-pointer.ts
|
|
3259
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
3260
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2795
3261
|
var ULTRAWORK_SKILL_POINTER_TEMPLATE = `<ultrawork-mode>
|
|
2796
3262
|
ULTRAWORK MODE IS ACTIVE FOR THIS TASK.
|
|
2797
3263
|
|
|
@@ -2822,22 +3288,22 @@ Do not start the requested work until all three steps are complete.
|
|
|
2822
3288
|
`;
|
|
2823
3289
|
var ULTRAWORK_SKILL_PATH_PLACEHOLDER = "{{ULTRAWORK_SKILL_PATH}}";
|
|
2824
3290
|
var ULTRAWORK_SKILL_FILE_URL = new URL("../../../skills/ultrawork/SKILL.md", import.meta.url);
|
|
2825
|
-
var ULTRAWORK_DIRECTIVE =
|
|
3291
|
+
var ULTRAWORK_DIRECTIVE = readFileSync2(new URL("../directive.md", import.meta.url), "utf8");
|
|
2826
3292
|
function resolveUltraworkSkillFilePath() {
|
|
2827
|
-
return
|
|
3293
|
+
return fileURLToPath2(ULTRAWORK_SKILL_FILE_URL);
|
|
2828
3294
|
}
|
|
2829
3295
|
function buildUltraworkSkillPointer(skillFilePath) {
|
|
2830
3296
|
return ULTRAWORK_SKILL_POINTER_TEMPLATE.replace(ULTRAWORK_SKILL_PATH_PLACEHOLDER, skillFilePath);
|
|
2831
3297
|
}
|
|
2832
3298
|
function buildUltraworkAdditionalContext(options = {}) {
|
|
2833
3299
|
const skillFilePath = options.skillFilePath === undefined ? resolveUltraworkSkillFilePath() : options.skillFilePath;
|
|
2834
|
-
if (skillFilePath !== null &&
|
|
3300
|
+
if (skillFilePath !== null && existsSync6(skillFilePath)) {
|
|
2835
3301
|
return buildUltraworkSkillPointer(skillFilePath);
|
|
2836
3302
|
}
|
|
2837
3303
|
return ULTRAWORK_DIRECTIVE;
|
|
2838
3304
|
}
|
|
2839
3305
|
|
|
2840
|
-
//
|
|
3306
|
+
// components/ulw-loop/src/ultrawork-directive.ts
|
|
2841
3307
|
var ULTRAWORK_CURRENT_PROMPT_PATTERN = /(?:ultrawork|ulw)/i;
|
|
2842
3308
|
var ULTRAWORK_DIRECTIVE_MARKER = "<ultrawork-mode>";
|
|
2843
3309
|
var TRANSCRIPT_SEARCH_BYTES = 512000;
|
|
@@ -2885,7 +3351,7 @@ function hasUltraworkDirectiveAlreadyInTranscript(transcriptPath) {
|
|
|
2885
3351
|
return false;
|
|
2886
3352
|
}
|
|
2887
3353
|
function readTranscriptTail(transcriptPath) {
|
|
2888
|
-
const rawTranscript =
|
|
3354
|
+
const rawTranscript = readFileSync3(transcriptPath);
|
|
2889
3355
|
return rawTranscript.subarray(Math.max(0, rawTranscript.byteLength - TRANSCRIPT_SEARCH_BYTES)).toString("utf8");
|
|
2890
3356
|
}
|
|
2891
3357
|
function isUltraworkPrompt(prompt) {
|
|
@@ -2940,7 +3406,7 @@ function isRecord3(value) {
|
|
|
2940
3406
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2941
3407
|
}
|
|
2942
3408
|
|
|
2943
|
-
//
|
|
3409
|
+
// components/ulw-loop/src/codex-hook.ts
|
|
2944
3410
|
var CREATE_GOAL_TOOL_NAME = "create_goal";
|
|
2945
3411
|
var CREATE_GOAL_PAYLOAD_WARNING = "Use create_goal with objective only. Omit token_budget so the goal stays unlimited, and put lifecycle status changes on update_goal.";
|
|
2946
3412
|
function parseUserPromptSubmitPayload(raw) {
|
|
@@ -3077,25 +3543,49 @@ function readAll(stdin) {
|
|
|
3077
3543
|
});
|
|
3078
3544
|
}
|
|
3079
3545
|
|
|
3080
|
-
//
|
|
3081
|
-
import {
|
|
3082
|
-
import {
|
|
3546
|
+
// components/ulw-loop/src/spawn-guard.ts
|
|
3547
|
+
import { randomBytes } from "node:crypto";
|
|
3548
|
+
import {
|
|
3549
|
+
existsSync as existsSync7,
|
|
3550
|
+
mkdirSync,
|
|
3551
|
+
openSync,
|
|
3552
|
+
readdirSync as readdirSync2,
|
|
3553
|
+
readFileSync as readFileSync4,
|
|
3554
|
+
renameSync,
|
|
3555
|
+
statSync as statSync2,
|
|
3556
|
+
unlinkSync,
|
|
3557
|
+
writeFileSync
|
|
3558
|
+
} from "node:fs";
|
|
3559
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
3083
3560
|
var SPAWN_TOOL_TOKENS = new Set(["spawn_agent", "collaborationspawn_agent", "collaboration.spawn_agent"]);
|
|
3084
3561
|
var DEFAULT_FANOUT_LIMIT = 60;
|
|
3085
|
-
var
|
|
3562
|
+
var DEFAULT_REVIEW_SPAWN_LIMIT = 3;
|
|
3563
|
+
var GATE_MESSAGE_PATTERN = /lazycodex-gate-reviewer|omo-senpi-gate-reviewer|final gate review/i;
|
|
3564
|
+
var REVIEW_AGENT_TYPES = [
|
|
3565
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.gateReview),
|
|
3566
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.codeReview),
|
|
3567
|
+
...Object.values(REVIEWER_ROLES_BY_SURFACE).map((roles) => roles.manualQa)
|
|
3568
|
+
];
|
|
3569
|
+
var REVIEW_AGENT_TYPE_SET = new Set(REVIEW_AGENT_TYPES);
|
|
3086
3570
|
function applySpawnGuards(payload) {
|
|
3087
3571
|
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
3088
3572
|
return "";
|
|
3089
3573
|
const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
|
|
3090
|
-
const plan = readPlan(
|
|
3574
|
+
const plan = readPlan(join3(stateDir, "goals.json"));
|
|
3091
3575
|
if (plan === null)
|
|
3092
3576
|
return "";
|
|
3093
|
-
const
|
|
3094
|
-
if (
|
|
3095
|
-
return deny(
|
|
3577
|
+
const fanOutPeek = peekFanOutBudget(stateDir);
|
|
3578
|
+
if (fanOutPeek !== null)
|
|
3579
|
+
return deny(fanOutPeek);
|
|
3096
3580
|
const missingArtifact = missingGateArtifact(payload, plan);
|
|
3097
3581
|
if (missingArtifact !== null)
|
|
3098
3582
|
return deny(`spawn code-review + QA first; gate audits their artifacts: missing ${missingArtifact}`);
|
|
3583
|
+
const reviewDenial = consumeReviewSpawnBudget(payload, plan, stateDir);
|
|
3584
|
+
if (reviewDenial !== null)
|
|
3585
|
+
return deny(reviewDenial);
|
|
3586
|
+
const fanOutDenial = consumeFanOutBudget(stateDir);
|
|
3587
|
+
if (fanOutDenial !== null)
|
|
3588
|
+
return deny(fanOutDenial);
|
|
3099
3589
|
return "";
|
|
3100
3590
|
}
|
|
3101
3591
|
async function runSpawnGuardCli(stdin, stdout) {
|
|
@@ -3114,15 +3604,44 @@ async function runSpawnGuardCli(stdin, stdout) {
|
|
|
3114
3604
|
return;
|
|
3115
3605
|
}
|
|
3116
3606
|
}
|
|
3607
|
+
function peekFanOutBudget(stateDir) {
|
|
3608
|
+
const counterPath = join3(stateDir, "spawn-count.json");
|
|
3609
|
+
const count = readCount(counterPath) + 1;
|
|
3610
|
+
const limit = fanOutLimit();
|
|
3611
|
+
if (count <= limit)
|
|
3612
|
+
return null;
|
|
3613
|
+
return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
|
|
3614
|
+
}
|
|
3117
3615
|
function consumeFanOutBudget(stateDir) {
|
|
3118
|
-
const counterPath =
|
|
3616
|
+
const counterPath = join3(stateDir, "spawn-count.json");
|
|
3119
3617
|
const count = readCount(counterPath) + 1;
|
|
3120
|
-
|
|
3618
|
+
atomicWriteJson(counterPath, { count });
|
|
3121
3619
|
const limit = fanOutLimit();
|
|
3122
3620
|
if (count <= limit)
|
|
3123
3621
|
return null;
|
|
3124
3622
|
return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
|
|
3125
3623
|
}
|
|
3624
|
+
function consumeReviewSpawnBudget(payload, plan, stateDir) {
|
|
3625
|
+
const agentType = reviewAgentType(payload.tool_input);
|
|
3626
|
+
if (agentType === null)
|
|
3627
|
+
return null;
|
|
3628
|
+
const goal3 = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
3629
|
+
if (goal3 === undefined)
|
|
3630
|
+
return null;
|
|
3631
|
+
const counterPath = join3(stateDir, "review-spawn-counts.json");
|
|
3632
|
+
const lockPath = `${counterPath}.lock`;
|
|
3633
|
+
const limit = reviewSpawnLimit();
|
|
3634
|
+
return withExclusiveLock(lockPath, () => {
|
|
3635
|
+
const counts = readCounts(counterPath);
|
|
3636
|
+
const key = `${agentType}:${goal3.id}:a${goal3.attempt}`;
|
|
3637
|
+
const count = (counts[key] ?? 0) + 1;
|
|
3638
|
+
if (count > limit)
|
|
3639
|
+
return `ulw-loop reviewer no-progress cap reached (${agentType} ${count}/${limit}) for ${goal3.id} attempt ${goal3.attempt}. Consolidate existing review findings, or checkpoint and start a new attempt after concrete progress.`;
|
|
3640
|
+
counts[key] = count;
|
|
3641
|
+
atomicWriteJson(counterPath, counts);
|
|
3642
|
+
return null;
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3126
3645
|
function missingGateArtifact(payload, plan) {
|
|
3127
3646
|
if (!isGateReviewerSpawn(payload.tool_input))
|
|
3128
3647
|
return null;
|
|
@@ -3132,31 +3651,109 @@ function missingGateArtifact(payload, plan) {
|
|
|
3132
3651
|
if (!goal3.successCriteria.every((criterion) => criterion.status === "pass"))
|
|
3133
3652
|
return null;
|
|
3134
3653
|
const scope = { sessionId: payload.session_id };
|
|
3654
|
+
const surface = resolveToolkitSurface();
|
|
3655
|
+
const requiredArtifacts = surface === "omo-senpi" ? [`${goal3.id}-manual-qa.md`] : [`${goal3.id}-code-review.md`, `${goal3.id}-manual-qa.md`];
|
|
3135
3656
|
if (plan.evidenceLayoutVersion === 2) {
|
|
3136
3657
|
const attemptDir = ulwLoopAttemptEvidenceDir(goal3.id, goal3.attempt, scope);
|
|
3137
|
-
for (const name of
|
|
3658
|
+
for (const name of requiredArtifacts) {
|
|
3138
3659
|
const relative2 = `${attemptDir}/${name}`;
|
|
3139
|
-
if (!isNonEmptyFile(
|
|
3660
|
+
if (!isNonEmptyFile(join3(payload.cwd, relative2)))
|
|
3140
3661
|
return relative2;
|
|
3141
3662
|
}
|
|
3142
3663
|
return null;
|
|
3143
3664
|
}
|
|
3144
3665
|
const flatReport = `.omo/evidence/${goal3.id}-code-review.md`;
|
|
3145
|
-
if (!isNonEmptyFile(
|
|
3666
|
+
if (surface !== "omo-senpi" && !isNonEmptyFile(join3(payload.cwd, flatReport)))
|
|
3146
3667
|
return flatReport;
|
|
3147
|
-
if (
|
|
3148
|
-
|
|
3668
|
+
if (surface === "omo-senpi") {
|
|
3669
|
+
const manualQa = `.omo/evidence/${goal3.id}-manual-qa.md`;
|
|
3670
|
+
return isNonEmptyFile(join3(payload.cwd, manualQa)) ? null : manualQa;
|
|
3671
|
+
}
|
|
3672
|
+
if (!hasOtherEvidenceFile(join3(payload.cwd, ".omo", "evidence"), `${goal3.id}-code-review.md`))
|
|
3673
|
+
return `.omo/evidence/${goal3.id}-manual-qa.md`;
|
|
3149
3674
|
return null;
|
|
3150
3675
|
}
|
|
3151
3676
|
function isGateReviewerSpawn(toolInput) {
|
|
3677
|
+
const agentType = reviewAgentType(toolInput);
|
|
3678
|
+
return agentType !== null && GATE_REVIEWER_AGENT_NAMES.has(agentType);
|
|
3679
|
+
}
|
|
3680
|
+
function reviewAgentType(toolInput) {
|
|
3152
3681
|
if (typeof toolInput !== "object" || toolInput === null)
|
|
3153
|
-
return
|
|
3682
|
+
return null;
|
|
3154
3683
|
const record = toolInput;
|
|
3155
3684
|
const agentType = record["agent_type"];
|
|
3156
|
-
if (typeof agentType === "string")
|
|
3157
|
-
|
|
3685
|
+
if (typeof agentType === "string") {
|
|
3686
|
+
if (!REVIEW_AGENT_TYPE_SET.has(agentType))
|
|
3687
|
+
return null;
|
|
3688
|
+
return activeSurfaceReviewerAlias(agentType);
|
|
3689
|
+
}
|
|
3158
3690
|
const message = record["message"];
|
|
3159
|
-
|
|
3691
|
+
if (typeof message !== "string")
|
|
3692
|
+
return null;
|
|
3693
|
+
const normalizedMessage = message.toLowerCase();
|
|
3694
|
+
const allRoleNames = [...REVIEW_AGENT_TYPES];
|
|
3695
|
+
const explicitAssignment = allRoleNames.map((name) => ({
|
|
3696
|
+
name,
|
|
3697
|
+
index: normalizedMessage.search(new RegExp(`\\bact as (?:an? )?${name}\\b`))
|
|
3698
|
+
})).filter(({ index }) => index >= 0).sort((left, right) => left.index - right.index)[0];
|
|
3699
|
+
if (explicitAssignment !== undefined)
|
|
3700
|
+
return activeSurfaceReviewerAlias(explicitAssignment.name);
|
|
3701
|
+
const nonReviewerActAs = /\bact as (?:an? )?\S+/.test(normalizedMessage);
|
|
3702
|
+
if (nonReviewerActAs)
|
|
3703
|
+
return null;
|
|
3704
|
+
const namedReviewer = REVIEW_AGENT_TYPES.find((name) => normalizedMessage.includes(name));
|
|
3705
|
+
if (namedReviewer !== undefined)
|
|
3706
|
+
return activeSurfaceReviewerAlias(namedReviewer);
|
|
3707
|
+
return GATE_MESSAGE_PATTERN.test(message) ? reviewerRolesFor(resolveToolkitSurface()).gateReview : null;
|
|
3708
|
+
}
|
|
3709
|
+
function activeSurfaceReviewerAlias(reviewer) {
|
|
3710
|
+
const activeRoles = reviewerRolesFor(resolveToolkitSurface());
|
|
3711
|
+
for (const roles of Object.values(REVIEWER_ROLES_BY_SURFACE)) {
|
|
3712
|
+
if (reviewer === roles.codeReview)
|
|
3713
|
+
return activeRoles.codeReview;
|
|
3714
|
+
if (reviewer === roles.manualQa)
|
|
3715
|
+
return activeRoles.manualQa;
|
|
3716
|
+
if (reviewer === roles.gateReview)
|
|
3717
|
+
return activeRoles.gateReview;
|
|
3718
|
+
}
|
|
3719
|
+
return reviewer;
|
|
3720
|
+
}
|
|
3721
|
+
function withExclusiveLock(lockPath, fn) {
|
|
3722
|
+
mkdirSync(dirname2(lockPath), { recursive: true });
|
|
3723
|
+
const maxAttempts = 10;
|
|
3724
|
+
const baseDelayMs = 10;
|
|
3725
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
3726
|
+
let fd = null;
|
|
3727
|
+
try {
|
|
3728
|
+
fd = openSync(lockPath, "wx");
|
|
3729
|
+
writeFileSync(fd, process.pid.toString());
|
|
3730
|
+
try {
|
|
3731
|
+
return fn();
|
|
3732
|
+
} finally {
|
|
3733
|
+
try {
|
|
3734
|
+
unlinkSync(lockPath);
|
|
3735
|
+
} catch {}
|
|
3736
|
+
}
|
|
3737
|
+
} catch (error) {
|
|
3738
|
+
if (fd !== null) {
|
|
3739
|
+
try {
|
|
3740
|
+
unlinkSync(lockPath);
|
|
3741
|
+
} catch {}
|
|
3742
|
+
}
|
|
3743
|
+
if (error.code !== "EEXIST") {
|
|
3744
|
+
return fn();
|
|
3745
|
+
}
|
|
3746
|
+
const delayMs = baseDelayMs * 2 ** attempt + Math.random() * baseDelayMs;
|
|
3747
|
+
const deadline = Date.now() + delayMs;
|
|
3748
|
+
while (Date.now() < deadline) {}
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
return fn();
|
|
3752
|
+
}
|
|
3753
|
+
function atomicWriteJson(targetPath, data) {
|
|
3754
|
+
const tmp = join3(dirname2(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
|
|
3755
|
+
writeFileSync(tmp, JSON.stringify(data));
|
|
3756
|
+
renameSync(tmp, targetPath);
|
|
3160
3757
|
}
|
|
3161
3758
|
function deny(reason) {
|
|
3162
3759
|
return `${JSON.stringify({
|
|
@@ -3176,9 +3773,16 @@ function fanOutLimit() {
|
|
|
3176
3773
|
const parsed = Number.parseInt(raw, 10);
|
|
3177
3774
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FANOUT_LIMIT;
|
|
3178
3775
|
}
|
|
3776
|
+
function reviewSpawnLimit() {
|
|
3777
|
+
const raw = process.env["OMO_ULW_LOOP_REVIEW_SPAWN_LIMIT"];
|
|
3778
|
+
if (raw === undefined)
|
|
3779
|
+
return DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
3780
|
+
const parsed = Number.parseInt(raw, 10);
|
|
3781
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_REVIEW_SPAWN_LIMIT;
|
|
3782
|
+
}
|
|
3179
3783
|
function isNonEmptyFile(path) {
|
|
3180
3784
|
try {
|
|
3181
|
-
return
|
|
3785
|
+
return existsSync7(path) && statSync2(path).size > 0;
|
|
3182
3786
|
} catch (error) {
|
|
3183
3787
|
if (error instanceof Error)
|
|
3184
3788
|
return false;
|
|
@@ -3187,7 +3791,7 @@ function isNonEmptyFile(path) {
|
|
|
3187
3791
|
}
|
|
3188
3792
|
function hasOtherEvidenceFile(evidenceDir, excludedName) {
|
|
3189
3793
|
try {
|
|
3190
|
-
return
|
|
3794
|
+
return readdirSync2(evidenceDir).some((name) => name !== excludedName && isNonEmptyFile(join3(evidenceDir, name)));
|
|
3191
3795
|
} catch (error) {
|
|
3192
3796
|
if (error instanceof Error)
|
|
3193
3797
|
return false;
|
|
@@ -3196,7 +3800,7 @@ function hasOtherEvidenceFile(evidenceDir, excludedName) {
|
|
|
3196
3800
|
}
|
|
3197
3801
|
function readCount(counterPath) {
|
|
3198
3802
|
try {
|
|
3199
|
-
const parsed = JSON.parse(
|
|
3803
|
+
const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
|
|
3200
3804
|
return typeof parsed["count"] === "number" && parsed["count"] >= 0 ? parsed["count"] : 0;
|
|
3201
3805
|
} catch (error) {
|
|
3202
3806
|
if (error instanceof Error)
|
|
@@ -3204,9 +3808,26 @@ function readCount(counterPath) {
|
|
|
3204
3808
|
throw error;
|
|
3205
3809
|
}
|
|
3206
3810
|
}
|
|
3811
|
+
function readCounts(counterPath) {
|
|
3812
|
+
try {
|
|
3813
|
+
const parsed = JSON.parse(readFileSync4(counterPath, "utf8"));
|
|
3814
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
3815
|
+
return {};
|
|
3816
|
+
const counts = {};
|
|
3817
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
3818
|
+
if (typeof value === "number" && value >= 0)
|
|
3819
|
+
counts[key] = value;
|
|
3820
|
+
}
|
|
3821
|
+
return counts;
|
|
3822
|
+
} catch (error) {
|
|
3823
|
+
if (error instanceof Error)
|
|
3824
|
+
return {};
|
|
3825
|
+
throw error;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3207
3828
|
function readPlan(goalsPath) {
|
|
3208
3829
|
try {
|
|
3209
|
-
return JSON.parse(
|
|
3830
|
+
return JSON.parse(readFileSync4(goalsPath, "utf8"));
|
|
3210
3831
|
} catch (error) {
|
|
3211
3832
|
if (error instanceof Error)
|
|
3212
3833
|
return null;
|
|
@@ -3214,9 +3835,9 @@ function readPlan(goalsPath) {
|
|
|
3214
3835
|
}
|
|
3215
3836
|
}
|
|
3216
3837
|
|
|
3217
|
-
//
|
|
3218
|
-
import { existsSync as
|
|
3219
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3838
|
+
// components/ulw-loop/src/stop-resume-hook.ts
|
|
3839
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3840
|
+
import { isAbsolute as isAbsolute2, join as join4, resolve as resolve4, sep as sep2 } from "node:path";
|
|
3220
3841
|
var RESUME_CAP = 2;
|
|
3221
3842
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
3222
3843
|
"context compacted",
|
|
@@ -3236,7 +3857,7 @@ function runStopResumeHook(input) {
|
|
|
3236
3857
|
if (boulderContinuationWillFire(payload.cwd, payload.session_id))
|
|
3237
3858
|
return "";
|
|
3238
3859
|
const stateDir = ulwLoopDir(payload.cwd, { sessionId: payload.session_id });
|
|
3239
|
-
const plan = readPlan2(
|
|
3860
|
+
const plan = readPlan2(join4(stateDir, "goals.json"));
|
|
3240
3861
|
if (plan === null || plan.aggregateCompletion?.status === "complete")
|
|
3241
3862
|
return "";
|
|
3242
3863
|
const goal3 = resumableGoal(plan);
|
|
@@ -3273,7 +3894,7 @@ function isResumableStatus(status2) {
|
|
|
3273
3894
|
return status2 === "pending" || status2 === "in_progress";
|
|
3274
3895
|
}
|
|
3275
3896
|
function consumeResumeBudget(stateDir, goalId) {
|
|
3276
|
-
const ledgerLineCount = countLedgerLines(
|
|
3897
|
+
const ledgerLineCount = countLedgerLines(join4(stateDir, "ledger.jsonl"));
|
|
3277
3898
|
const counterPath = resolve4(stateDir, `auto-resume-${goalId}.json`);
|
|
3278
3899
|
const stuckPath = resolve4(stateDir, `auto-resume-${goalId}.stuck`);
|
|
3279
3900
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
@@ -3306,7 +3927,7 @@ function renderResumeDirective(plan, goal3, sessionId) {
|
|
|
3306
3927
|
}
|
|
3307
3928
|
function readPlan2(goalsPath) {
|
|
3308
3929
|
try {
|
|
3309
|
-
return JSON.parse(
|
|
3930
|
+
return JSON.parse(readFileSync5(goalsPath, "utf8"));
|
|
3310
3931
|
} catch (error) {
|
|
3311
3932
|
if (error instanceof Error)
|
|
3312
3933
|
return null;
|
|
@@ -3315,7 +3936,7 @@ function readPlan2(goalsPath) {
|
|
|
3315
3936
|
}
|
|
3316
3937
|
function countLedgerLines(ledgerPath) {
|
|
3317
3938
|
try {
|
|
3318
|
-
return
|
|
3939
|
+
return readFileSync5(ledgerPath, "utf8").split(`
|
|
3319
3940
|
`).filter(Boolean).length;
|
|
3320
3941
|
} catch (error) {
|
|
3321
3942
|
if (error instanceof Error)
|
|
@@ -3325,9 +3946,9 @@ function countLedgerLines(ledgerPath) {
|
|
|
3325
3946
|
}
|
|
3326
3947
|
function readCounter(counterPath) {
|
|
3327
3948
|
try {
|
|
3328
|
-
if (!
|
|
3949
|
+
if (!existsSync8(counterPath))
|
|
3329
3950
|
return null;
|
|
3330
|
-
const parsed = JSON.parse(
|
|
3951
|
+
const parsed = JSON.parse(readFileSync5(counterPath, "utf8"));
|
|
3331
3952
|
if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
|
|
3332
3953
|
return null;
|
|
3333
3954
|
return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
|
|
@@ -3339,7 +3960,7 @@ function readCounter(counterPath) {
|
|
|
3339
3960
|
}
|
|
3340
3961
|
function boulderContinuationWillFire(cwd, sessionId) {
|
|
3341
3962
|
try {
|
|
3342
|
-
const raw = JSON.parse(
|
|
3963
|
+
const raw = JSON.parse(readFileSync5(join4(cwd, ".omo", "boulder.json"), "utf8"));
|
|
3343
3964
|
const works = raw["works"];
|
|
3344
3965
|
const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
|
|
3345
3966
|
return entries.some((work) => {
|
|
@@ -3358,7 +3979,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
|
|
|
3358
3979
|
}
|
|
3359
3980
|
function transcriptShowsContextPressure(transcriptPath) {
|
|
3360
3981
|
try {
|
|
3361
|
-
const transcript =
|
|
3982
|
+
const transcript = readFileSync5(transcriptPath, "utf8").toLowerCase();
|
|
3362
3983
|
return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
|
|
3363
3984
|
} catch (error) {
|
|
3364
3985
|
if (error instanceof Error)
|
|
@@ -3370,12 +3991,12 @@ function boulderPlanHasChecklist(cwd, entry) {
|
|
|
3370
3991
|
const activePlan = entry["active_plan"];
|
|
3371
3992
|
if (typeof activePlan !== "string" || activePlan.trim().length === 0)
|
|
3372
3993
|
return false;
|
|
3373
|
-
const planPath = isAbsolute2(activePlan) ? activePlan :
|
|
3994
|
+
const planPath = isAbsolute2(activePlan) ? activePlan : join4(cwd, activePlan);
|
|
3374
3995
|
const worktree = entry["worktree_path"];
|
|
3375
|
-
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [
|
|
3996
|
+
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute2(activePlan) ? [join4(isAbsolute2(worktree) ? worktree : join4(cwd, worktree), activePlan), planPath] : [planPath];
|
|
3376
3997
|
for (const candidate of candidates) {
|
|
3377
3998
|
try {
|
|
3378
|
-
return
|
|
3999
|
+
return readFileSync5(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
|
|
3379
4000
|
} catch (error) {
|
|
3380
4001
|
if (!(error instanceof Error))
|
|
3381
4002
|
throw error;
|
|
@@ -3399,19 +4020,13 @@ function parseStopPayload(value) {
|
|
|
3399
4020
|
};
|
|
3400
4021
|
}
|
|
3401
4022
|
|
|
3402
|
-
//
|
|
3403
|
-
var TOP_LEVEL_HELP = `Usage:
|
|
3404
|
-
omo-agent-toolkit ulw-loop <subcommand> [args]
|
|
3405
|
-
omo-agent-toolkit hook user-prompt-submit [--with-ultrawork] (Codex UserPromptSubmit hook)
|
|
3406
|
-
omo-agent-toolkit help | --help | -h (this message)
|
|
3407
|
-
|
|
3408
|
-
Run \`omo-agent-toolkit ulw-loop help\` for ulw-loop subcommands.
|
|
3409
|
-
`;
|
|
4023
|
+
// components/ulw-loop/src/cli.ts
|
|
3410
4024
|
async function main() {
|
|
3411
4025
|
const argv = process.argv.slice(2);
|
|
3412
4026
|
const command = argv[0];
|
|
3413
4027
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
3414
|
-
process.stdout.write(
|
|
4028
|
+
process.stdout.write(`${ULW_LOOP_HELP}
|
|
4029
|
+
`);
|
|
3415
4030
|
return 0;
|
|
3416
4031
|
}
|
|
3417
4032
|
if (command === "ulw-loop")
|
|
@@ -3443,7 +4058,8 @@ async function main() {
|
|
|
3443
4058
|
if (isUlwLoopSubcommand(command))
|
|
3444
4059
|
return ulwLoopCommand(argv);
|
|
3445
4060
|
process.stderr.write(`[omo] unknown command: ${command}
|
|
3446
|
-
${
|
|
4061
|
+
${ULW_LOOP_HELP}
|
|
4062
|
+
`);
|
|
3447
4063
|
return 1;
|
|
3448
4064
|
}
|
|
3449
4065
|
main().then((code) => {
|