refactor(omo-codex): rename ultragoal component to ulw-loop

Fully rename the ultragoal component to ulw-loop so the identifier
matches the ulw-loop skill it powers. Renames the component directory,
nested skill, TS identifiers (UlwLoop / ULW_LOOP_* / ulwLoop*), the
omo ulw-loop CLI subcommand, the .omo/ulw-loop state directory, the
OMO_ULW_LOOP_STEER directive token, and the @code-yeongyu/codex-ulw-loop
package. Also threads Atlas-style right-sized parallel worker delegation
and a Prometheus-style QA + maximum-parallelism plan into the ulw-loop
skill, with a critical post-subagent QA gate.

Updates aggregate wiring (plugin package components, hooks.json,
sync-skills), the install-codex agent-link test fixture, and user docs.
This commit is contained in:
YeonGyu-Kim
2026-05-29 12:38:22 +09:00
parent 0edf087117
commit 8c6e8e4986
84 changed files with 1429 additions and 1392 deletions
@@ -0,0 +1,13 @@
# Normalize line endings: store LF in git, check out LF on every platform.
# Required so biome's --check passes on Windows (default core.autocrlf=true).
* text=auto eol=lf
# Explicit binary types
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.zip binary
*.tgz binary
*.gz binary
@@ -0,0 +1,6 @@
node_modules/
dist/
*.log
.DS_Store
coverage/
.vitest/
@@ -0,0 +1,48 @@
# Repository Conventions
Conventions for human contributors and AI agents working on this repository.
## Stack
- Node >=20 runtime.
- npm package manager.
- TypeScript 6 strict mode.
- Biome 2 linting and formatting.
- Vitest 4 test runner.
## Forbidden
- No `as any` or `as unknown`.
- No `@ts-ignore` or `@ts-expect-error`.
- No enums.
- No non-null assertions.
- No default exports. `vitest.config.ts` is exempt because the framework requires that shape.
## File Ceiling
- Keep each `src/` TypeScript file under 250 pure LOC.
- Split by responsibility before a file reaches the ceiling.
## Test Discipline
- Use Vitest with nested `describe` names in `#given`, `#when`, and `#then` form, or inline `// given`, `// when`, and `// then` comments.
- Never use Arrange-Act-Assert comments.
- Keep fixtures in `test/fixtures/`.
## Commit Style
- Use Conventional Commits.
- Keep commits atomic.
- Each commit's tests and build must pass on its own.
## Branding
- Repo artifacts live under `.omo/ulw-loop/` paths.
- Environment variables use the `OMO_ULW_LOOP_*` prefix.
- CLI commands use the `omo ulw-loop` form.
- Do not use any alternate legacy CLI alias anywhere.
## Build and Hooks
- Build output goes to `dist/`.
- `hooks/hooks.json` runs `node ${PLUGIN_ROOT}/dist/cli.js hook user-prompt-submit`.
@@ -0,0 +1,7 @@
# Changelog
## [0.1.0] - unreleased
- Initial scaffold of codex-ulw-loop plugin.
- Per-Criterion Cycle: `EXECUTE` is now **EXECUTE-AS-SCENARIO** — the agent must run the Manual-QA channel scenario the criterion named (HTTP call / tmux / browser use / computer use; see new `## Manual-QA channels` section). Inserted a new **CLEAN (PAIRED, NEVER SKIP)** step that tears down every QA-spawned process / `tmux` session / browser context / container / port / temp dir before recording evidence; the cleanup receipt is embedded in the `--evidence` string. Missing receipt → record BLOCKED, not PASS. Added Constraint #13 and a Stop Rule for leftover state.
- New top-level **`## Manual-QA channels`** section explicitly enumerates the four channels (HTTP call, tmux, Browser use, Computer use) with concrete commands and required artifacts. Goal section now declares **TESTS ALONE NEVER PROVE DONE**: a green test suite is supporting evidence, never completion proof. Criterion-refinement step 2 requires each criterion to name its channel up front.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Yeongyu Kim
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,6 @@
codex-ulw-loop
This package ports the oh-my-codex ulw-loop feature into a Codex plugin repository.
The plugin targets Codex plugin manifests and plugin-bundled lifecycle hooks.
The orchestration engine is added in later port waves.
@@ -0,0 +1,75 @@
# codex-ulw-loop
[![ci](https://img.shields.io/badge/ci-pending-lightgrey.svg)](#) [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
Codex plugin scaffold for durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.
## Behavior
| Subcommand | Purpose |
|------------|---------|
| `omo ulw-loop create-goals` | Create repo-native goals from a brief and seed criteria. |
| `omo ulw-loop record-evidence` | Record observable evidence for the active criterion. |
| `omo ulw-loop criteria` | Inspect or revise goal success criteria. |
| `omo ulw-loop complete-goals` | Complete eligible goals after criteria pass. |
| `omo ulw-loop checkpoint` | Refuse completion until criteria and evidence gates pass. |
| `omo ulw-loop steer` | Apply steering updates to the plan. |
| `omo ulw-loop status` | Report active goal, criteria, and evidence state. |
Wave 1 is scaffold only. Command behavior lands in later waves.
## Codex Plugin
The plugin ships:
- `.codex-plugin/plugin.json` for Codex plugin discovery.
- `hooks/hooks.json` for the `UserPromptSubmit` hook.
- `skills/ulw-loop/` as the future skill directory.
The hook command is:
```bash
node "${PLUGIN_ROOT}/dist/cli.js" hook user-prompt-submit
```
No MCP server or Codex tool is exposed in this scaffold.
## Local Development
```bash
npm install
npm test
npm run typecheck
npm run check
npm pack --dry-run
```
## Local Codex Installation
```bash
bunx lazycodex install
```
The installer builds and copies the plugin into `~/.codex/plugins/cache/sisyphuslabs/omo/0.1.0`, registers the `sisyphuslabs` marketplace from the `lazycodex` Git repository, installs runtime dependencies there, and enables:
```toml
[features]
plugins = true
plugin_hooks = true
[plugins."omo@sisyphuslabs"]
enabled = true
```
## Privacy
This plugin runs locally. The scaffold does not call a network service by itself.
## License
[MIT](LICENSE).
## Related
- [oh-my-codex](https://github.com/code-yeongyu/oh-my-codex) - source project for the ulw-loop port.
- [lazycodex](https://github.com/code-yeongyu/lazycodex) - Sisyphus Labs Codex marketplace repository.
@@ -0,0 +1,48 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noDefaultExport": "error",
"noEnum": "error",
"noNonNullAssertion": "error",
"useImportType": "error",
"useConst": "error",
"useNodejsImportProtocol": "off"
},
"complexity": {
"useLiteralKeys": "off"
},
"suspicious": {
"noExplicitAny": "error",
"noTsIgnore": "error",
"noControlCharactersInRegex": "off",
"noEmptyInterface": "off"
}
}
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "tab",
"indentWidth": 3,
"lineWidth": 120
},
"files": {
"includes": ["src/**/*.ts", "test/**/*.ts", "vitest.config.ts", "!**/node_modules/**/*", "!**/dist/**/*"]
},
"overrides": [
{
"includes": ["vitest.config.ts"],
"linter": {
"rules": {
"style": {
"noDefaultExport": "off"
}
}
}
}
]
}
@@ -0,0 +1,29 @@
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit",
"timeout": 10,
"statusMessage": "checking ulw-loop steering"
}
]
}
],
"PreToolUse": [
{
"matcher": "^create_goal$",
"hooks": [
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
"timeout": 5,
"statusMessage": "enforcing unlimited ulw-loop budget"
}
]
}
]
}
}
@@ -0,0 +1,55 @@
{
"name": "@code-yeongyu/codex-ulw-loop",
"version": "0.1.0",
"description": "Codex plugin: durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.",
"type": "module",
"packageManager": "npm@11.12.1",
"license": "MIT",
"homepage": "https://github.com/code-yeongyu/codex-ulw-loop",
"repository": {
"type": "git",
"url": "git+https://github.com/code-yeongyu/codex-ulw-loop.git"
},
"bugs": {
"url": "https://github.com/code-yeongyu/codex-ulw-loop/issues"
},
"keywords": [
"codex",
"codex-plugin",
"ulw-loop",
"goal-mode",
"orchestration",
"evidence",
"typescript"
],
"bin": {
"omo": "./dist/cli.js"
},
"files": [
"dist",
"hooks",
"skills",
"LICENSE",
"NOTICE",
"README.md",
"CHANGELOG.md"
],
"scripts": {
"build": "tsc -p tsconfig.build.json",
"test": "vitest --run",
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"check": "tsc --noEmit && biome check . && npm run build"
},
"devDependencies": {
"@biomejs/biome": "2.4.15",
"@types/node": "^25.7.0",
"typescript": "^6.0.3",
"vitest": "^4.1.5"
},
"engines": {
"node": ">=20.0.0"
}
}
@@ -0,0 +1,221 @@
---
name: ulw-loop
description: Goal-like loop that uses ultrawork mode to decompose work into systematic, evidence-bound steps.
metadata:
short-description: Goal-like ultrawork loop for systematic decomposition
---
## Role
Expert goal orchestration agent. You conduct; right-sized parallel subagents play. Plan multi-goal work that survives across turns and sessions, fan independent work out to workers, QA every result yourself, record only proven evidence.
Use GPT-5.x style: outcome-first, evidence-bound, atomic decisions, no nested branching prose.
## Goal
Deliver every goal in `.omo/ulw-loop/goals.json` end-to-end.
Prove EVERY success criterion with captured observable evidence from a real-usage scenario you actually ran (HTTP call / tmux / browser use / computer use — see the Manual-QA channels below).
TESTS ALONE NEVER PROVE DONE. A green test suite is supporting evidence, not completion proof.
Audit each pass, fail, block, steering change, and checkpoint in `.omo/ulw-loop/ledger.jsonl`.
## Manual-QA channels (PICK ONE PER CRITERION — ACTUALLY RUN IT)
For every criterion, build a real-usage scenario through ONE of these four channels and run it yourself before recording PASS. The full test suite being green is NEVER verification on its own.
1. **HTTP call** — hit the live endpoint with `curl -i` (or a Playwright APIRequestContext); capture status line + headers + body.
2. **tmux**`tmux new-session -d -s ulw-qa-<criterion>`, drive with `send-keys`, dump via `tmux capture-pane -pS -E -`; transcript is the artifact.
3. **Browser use** — drive the real page via Playwright / puppeteer / Chromium; capture action log + screenshot path.
4. **Computer use** — OS-level GUI automation (computer-use agent, AppleScript, xdotool, etc.) against the running app; capture action log + screenshot.
Auxiliary surfaces (pure CLI stdout / DB state diff / parsed config dump) satisfy CLI- or data-shaped criteria but NEVER replace a channel scenario for user-facing behavior. `--dry-run`, printing the command, "should respond", and "looks correct" never count.
## Delegation model (ATLAS-STYLE — YOU CONDUCT, WORKERS PLAY)
You read, search, plan, integrate, and QA. You DELEGATE every code edit, test write, bug fix, and QA execution to a right-sized `spawn_agent` worker, then verify what comes back. Fan out independent tasks in PARALLEL in a single response; serialize only on a NAMED dependency (one task consumes another's output or edits the same file).
Size each worker to the task — never spend `xhigh` on a one-liner, never send a race condition to a mini. Pass `model` + `reasoning_effort` per call (an override needs a non-full-history fork mode):
| Task shape | agent_type | model | reasoning_effort |
|---|---|---|---|
| Trivial / mechanical (rename, move, obvious one-liner, config edit) | `worker` | `gpt-5.4-mini` | `low` |
| Pure implementation against a clear spec (new function, endpoint, test from a named pattern) | `worker` | `gpt-5.3-codex` | `high` |
| Deep debugging / race / perf / subtle cross-module reasoning | `worker` | `gpt-5.5` | `xhigh` |
| QA execution (drive a channel, capture evidence) | `worker` | `gpt-5.3-codex` | `high` |
| Read-only codebase search | `explorer` | role default | role default |
| External library / docs research | `librarian` | role default | role default |
| Final verification audit | `codex-ultrawork-reviewer` | role default | role default |
Every worker message MUST carry: goal + exact files in scope; the failing test / reproduction required before production code; constraints + project rules; the verification commands to run; the ONE Manual-QA channel and the exact evidence artifact to capture. Workers have NO interview context — be exhaustive, and forward accumulated learnings to every next worker. Track running workers; `wait_agent` for results, `close_agent` when done.
## Artifacts
- `.omo/ulw-loop/brief.md`: original brief and durable constraints.
- `.omo/ulw-loop/goals.json`: goals with embedded `successCriteria` per goal.
- `.omo/ulw-loop/ledger.jsonl`: append-only audit trail.
- Read artifacts before resuming, steering, or checkpointing.
- Never invent state outside `.omo/ulw-loop` artifacts or `omo ulw-loop status --json`.
## Bootstrap
Do all three steps before execution. No edits, goal tools, or checkpointing before bootstrap completes.
### 1. Create goals from the brief
Resolve the CLI before the first command. If `omo` is absent from PATH, use the stable local installer bin or cached Codex component CLI. This is the same ulw-loop CLI, so PATH absence is not a blocker. If PATH is empty, the fallback uses shell builtins and absolute Node locations before reporting guidance, and records the failure in `.omo/ulw-loop/bootstrap-notepad.md`.
```sh
if command -v omo >/dev/null 2>&1; then
ULW_LOOP_CLI=omo
else
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
ULW_LOOP_CLI=
if [ -f "$CODEX_HOME/bin/omo" ] || [ -x "$CODEX_HOME/bin/omo" ]; then
ULW_LOOP_CLI="$CODEX_HOME/bin/omo"
else
for candidate in "$CODEX_HOME"/plugins/cache/sisyphuslabs/omo/*/components/ulw-loop/dist/cli.js; do
[ -f "$candidate" ] || continue
ULW_LOOP_CLI="$candidate"
done
fi
ULW_LOOP_NODE="$(command -v node 2>/dev/null || true)"
if [ -z "$ULW_LOOP_NODE" ]; then
for candidate in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do
[ -x "$candidate" ] || continue
ULW_LOOP_NODE="$candidate"
break
done
fi
if [ -n "$ULW_LOOP_CLI" ] && [ -n "$ULW_LOOP_NODE" ]; then
omo() { "$ULW_LOOP_NODE" "$ULW_LOOP_CLI" "$@"; }
fi
fi
if [ -z "${ULW_LOOP_CLI:-}" ]; then
/bin/mkdir -p .omo/ulw-loop 2>/dev/null || mkdir -p .omo/ulw-loop 2>/dev/null || true
NOTE="${NOTE:-.omo/ulw-loop/bootstrap-notepad.md}"
printf '%s\n' "omo executable missing from PATH; cached ulw-loop CLI not found under ${CODEX_HOME:-$HOME/.codex}." >> "$NOTE" 2>/dev/null || true
printf '%s\n' "Install with bunx omo install --platform=codex or set CODEX_LOCAL_BIN_DIR to a PATH directory." >&2
fi
```
If `ULW_LOOP_CLI` is empty, open the durable notepad first, record the missing CLI evidence, then surface the installer issue.
Run one form:
```sh
omo ulw-loop create-goals --brief "<brief>" --json
omo ulw-loop create-goals --brief-file <path> --json
cat <brief> | omo ulw-loop create-goals --from-stdin --json
```
Write state through the CLI path. Do not hand-edit state files.
### 2. Refine success criteria + a Prometheus-grade QA and parallelism plan per goal
Gather context BEFORE planning — fire parallel `explorer` / `librarian` workers plus your own read-only tools; never plan blind.
Define pass/fail acceptance criteria before launching execution lanes. Include the command, artifact, or manual check that will prove success.
Each goal MUST carry 3+ `successCriteria` covering happy path, edge, regression, and adversarial risk.
For each criterion set, concretely and upfront: `id`, `scenario` (the exact tool — curl / tmux / playwright / computer-use — plus exact steps with specific inputs and a binary pass/fail), `expectedEvidence` (the exact artifact path, e.g. `.omo/ulw-loop/evidence/<goal>-<criterion>.<ext>`), adversarial classes, stop condition, and the Manual-QA channel (HTTP call / tmux / browser use / computer use) that will exercise it. Vague QA ("verify it works") is a rejected criterion — revise it before execution.
Apply ultraqa classes where relevant: malformed input, repeated interruptions, prompt injection, cancel/resume, stale state, dirty worktree, hung or long commands, flaky tests, misleading success output.
Use evidence verbs from the channel table (tmux transcript, curl status+body, browser screenshot, computer-use action log, CLI stdout, DB diff, parsed config dump) — not vibes.
"Tests pass" is supporting signal, NEVER completion proof. Every criterion needs its own channel scenario, built fresh and exercised every time.
**Plan for maximum parallelism.** Decompose each goal's criteria into atomic tasks (Implementation + its Test = ONE task, never split) and group them into dependency waves. Target 58 tasks per wave; <3 per wave (except the final wave) means under-splitting — extract shared prerequisites into Wave 1. For each task record its wave, what it blocks, what blocks it, the worker tier from the Delegation table, and its QA scenario + evidence path. Build a dependency matrix (Task | Depends on | Blocks | Can parallelize with) and name the critical path. Anything not on a real dependency edge MUST share a wave and dispatch together.
Record manual QA notes when behavior is user-visible.
Revise any criterion that lacks observable `expectedEvidence` or a named channel before execution.
### 3. Inspect state
Run `omo ulw-loop status --json`.
Read pending goals, criteria IDs, current ledger head, blockers, and aggregate Codex objective.
## Execution Loop
Loop per goal. Cap at 5 cycles per goal. Cap identical same-criterion failures at 3.
### Acquire Next Goal
1. Run `omo ulw-loop complete-goals --json` and read the handoff, including criteria.
2. Call `get_goal` and inspect active Codex state.
3. Apply this table exactly:
| get_goal result | action |
|-----------------|--------|
| no active goal | Call `create_goal` with the handoff payload. |
| same aggregate objective active | Continue the current ulw-loop story. |
| different goal active | STOP. Checkpoint blocked and surface the conflict. |
4. If retrying failed work, run `omo ulw-loop complete-goals --retry-failed --json`.
5. Never create a second Codex goal for the same aggregate objective.
### Per-Criterion Cycle
1. PLAN: read `criterion.scenario`, `criterion.expectedEvidence`, prior ledger entries, and safety bounds. Identify which tasks in the current wave are independent.
2. Register atomic todos: `path: <action> for <criterion> - verify by <check>`.
3. DELEGATE-IN-PARALLEL: dispatch every independent task in the wave at once via right-sized `spawn_agent` workers (Delegation table). Each worker does strict TDD on its task: RED first (the failing assertion must fail for the RIGHT reason — no syntax/import error), then the SMALLEST GREEN change; a GREEN needing >~20 lines means the test was too coarse — instruct a split. Serialize only on a NAMED dependency.
4. INTEGRATE + CRITICAL SELF-QA (EVERY WORKER RETURN): do NOT trust the worker's report. Read the diff yourself, re-run its tests, and run LSP diagnostics on the changed files. Treat "done" as a claim to disprove. If the diff drifts, the test is hollow, or evidence is missing, RESPAWN the worker with the specific failure context. Forward every finding/learning to subsequent workers.
5. EXECUTE-AS-SCENARIO: ACTUALLY run the Manual-QA channel scenario the criterion named (HTTP call / tmux / browser use / computer use — see the channel table above). Run it yourself for the orchestrator check; for heavier flows dispatch a dedicated QA worker (`worker`, `gpt-5.3-codex`, `high`) whose ONLY job is to drive the channel and write the artifact to the named evidence path. The unit suite being green is NEVER substitute. If the scenario FAILS, respawn the implementing worker with the captured failure — do not hand-patch around it.
6. CAPTURE: collect the observable artifact path: transcript, stdout, screenshot, assertion, status+body, diff, or parsed dump. No artifact written at the evidence path — not done; record BLOCKED and respawn QA.
7. CLEAN (PAIRED, NEVER SKIP): tear down every runtime artifact step 5 spawned BEFORE recording — server PIDs (`kill`, verify `kill -0` fails), `tmux` sessions (`tmux kill-session -t ulw-qa-<criterion>`; confirm `tmux ls`), browser / Playwright contexts (`.close()`), containers (`docker rm -f`), bound ports (`lsof -i :<port>` empty), temp sockets / files / dirs (`rm -rf` the `mktemp` paths), QA-only env vars, AND `close_agent` on every finished worker. Embed a one-line cleanup receipt in the evidence string, e.g. `cleanup: killed 12345; tmux kill-session ulw-qa-foo; rm -rf /tmp/ulw.aB12cD; close_agent w-3`. Missing receipt → record BLOCKED, not PASS.
8. RECORD exactly one result:
- PASS: `omo ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass --evidence "<observable> | <cleanup receipt>" --json`
- FAIL: `omo ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status fail --evidence "<observable> | <cleanup receipt>" --notes "<diagnosis>" --json`
- BLOCKED: `omo ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status blocked --evidence "<observable>" --notes "<safety/blocker/leftover-state>" --json`
9. If actual does not match expected, diagnose, respawn the right-sized worker with the failure context to fix minimally, and rerun the SAME criterion (including a fresh cleanup).
10. After 3 same-criterion failures, exit the goal with diagnosis.
11. After 5 cycles on one goal without all criteria passing, checkpoint failed.
12. Continue only when the next pending criterion has a concrete `expectedEvidence` target.
### Goal Completion
1. Confirm every criterion is `pass` with `omo ulw-loop criteria --goal-id <id> --json`.
2. Call `get_goal` for a fresh snapshot.
3. Run `omo ulw-loop checkpoint --goal-id <id> --status complete --evidence "<criteria evidence summary>" --codex-goal-json <snapshot> --json`.
4. If blocked or failed, checkpoint with `--status blocked` or `--status failed` and include diagnosis evidence.
5. If this is the final goal, run the final quality gate first and pass `--quality-gate-json`.
## Final Quality Gate
Trigger only when one goal remains and all its criteria are passing.
1. Run targeted verification for changed behavior.
2. Run `ai-slop-cleaner` on changed files. If no relevant edits exist, record a passed no-op cleaner report.
3. Rerun verification after cleanup.
4. Run `$code-review`.
5. Clean review means `codeReview.recommendation == "APPROVE"` and `codeReview.architectStatus == "CLEAR"`.
6. If review is non-clean, run `omo ulw-loop record-review-blockers --goal-id <id> --title "<...>" --objective "<...>" --evidence "<review findings>" --codex-goal-json <snapshot> --json`.
7. If clean, checkpoint final completion:
```sh
omo ulw-loop checkpoint --goal-id <id> --status complete --evidence "<e2e evidence + manual QA notes>" --codex-goal-json <snapshot> --quality-gate-json <json-or-path> --json
```
`--quality-gate-json` shape:
```json
{
"aiSlopCleaner": { "status": "passed", "evidence": "cleaner report" },
"verification": { "status": "passed", "commands": ["npm test"], "evidence": "post-cleaner verification" },
"codeReview": { "recommendation": "APPROVE", "architectStatus": "CLEAR", "evidence": "review synthesis" },
"criteriaCoverage": { "totalCriteria": N, "passCount": N, "adversarialClassesCovered": ["malformed_input", "..."] }
}
```
## Dynamic Steering
Use steering only for structured evidence-backed mutation. Reject natural-language steering requests.
| Kind | When to use | Required fields |
|------|-------------|-----------------|
| add_subgoal | Real blocker found; new story required | `--title`, `--objective`, `--evidence`, `--rationale` |
| split_subgoal | Story too large; needs decomposition | `--goal-id`, `--children` JSON, `--evidence`, `--rationale` |
| reorder_pending | Discovered dependency order | `--order` JSON array of ids, `--evidence`, `--rationale` |
| revise_pending_wording | Title/objective ambiguous | `--goal-id`, `--title?`, `--objective?`, `--evidence`, `--rationale` |
| revise_criterion | Criterion lacks observable PASS evidence | `--goal-id`, `--criterion-id`, `--scenario?`, `--expected-evidence?`, `--evidence`, `--rationale` |
| annotate_ledger | Audit-only note | `--evidence`, `--rationale` |
| mark_blocked_superseded | Old story replaced by new evidence | `--goal-id`, `--replacements?`, `--evidence`, `--rationale` |
Command form: `omo ulw-loop steer --kind <kind> [<kind-specific-fields>] --evidence "<...>" --rationale "<...>" --json`.
Structured prompt directives accepted: `OMO_ULW_LOOP_STEER: { ... }`, `omo.ulw-loop.steer: {...}`, `omo ulw-loop steer: {...}`.
## Constraints
1. NEVER call `update_goal` mid-aggregate; only on final story after the quality gate passes.
2. NEVER call `create_goal` when `get_goal` shows a different active goal.
3. NEVER mark `criterion.status == "pass"` without captured observable evidence in `record-evidence`.
4. NEVER bypass the criteria gate at checkpoint; all criteria must be `pass` before `--status complete`.
5. Baseline build/lint/typecheck/test commands are necessary evidence, NOT SUFFICIENT completion proof. Criteria coverage with observable evidence is the gate.
6. Treat `.omo/ulw-loop/ledger.jsonl` as the durable audit trail; checkpoint after every success or failure.
7. Per-story Codex goal mode is opt-in only with `--codex-goal-mode per-story`; default is aggregate.
8. Structured steering directives mutate state through validation; normal prose does not.
9. Evidence MUST be observable from the real surface: tmux transcript, curl status+body, browser/Playwright assertion, CLI stdout, DB state diff, parsed config dump.
10. Apply ultraqa's 9 adversarial classes where relevant per goal: malformed input, prompt injection, cancel/resume, stale state, dirty worktree, hung commands, flaky tests, misleading success output, repeated interruptions.
11. After completing an aggregate ulw-loop run, clear the Codex goal manually with `/goal clear` before starting another in the same session.
12. The shell command emits a model-facing handoff; only the Codex agent calls `get_goal`, `create_goal`, or `update_goal` tools.
13. NEVER record `--status pass` while a QA-spawned process, `tmux` session, browser context, bound port, container, or temp file / dir is still alive, or while any worker is still open. The evidence string MUST include the cleanup receipt. Leftover runtime state = BLOCKED, not PASS.
14. DELEGATE all code edits, test writes, fixes, and QA execution to right-sized `spawn_agent` workers (Delegation table); you read, search, plan, integrate, and QA. NEVER record `--status pass` from a worker's self-report — only from evidence you re-verified yourself. Dispatch independent tasks in parallel; serialize only on a NAMED dependency.
## Stop Rules
- All goals complete plus all criteria `pass` plus final quality gate clean: DONE.
- 3x same criterion failure: checkpoint failed, surface diagnosis.
- 5 cycles on one goal without all-pass: checkpoint failed, surface.
- Safety boundary such as destructive command, secret exfiltration, or production write: block and surface a safe substitute.
- Codex `get_goal` reports a different active goal: checkpoint blocker, stop, surface.
- Leftover state from QA (live process, `tmux` session, browser context, bound port, temp dir): NOT pass. Clean up, append the receipt, then continue.
- User issues `/cancel`: release in-progress state cleanly and do not auto-resume.
@@ -0,0 +1,6 @@
interface:
display_name: "ulw loop"
short_description: "Goal-like ultrawork loop for systematic decomposition"
search_terms:
- "ulw-loop"
default_prompt: "Use $ulw-loop to break this work into a systematic ultrawork loop with evidence-backed checkpoints."
@@ -0,0 +1,155 @@
// biome-ignore-all format: keep checkpoint orchestration below the pure LOC budget.
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { formatCodexGoalReconciliation, readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot } from "./codex-goal-snapshot.js";
import { requireAllCriteriaPass } from "./evidence.js";
import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
import { ulwLoopBriefPath } from "./paths.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, sameBlockerOccurrences, validateQualityGate } from "./quality-gate.js";
import type { UlwLoopAggregateCompletion, UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopQualityGate } from "./types.js";
import { iso, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
export interface CheckpointUlwLoopArgs { readonly goalId: string; readonly status: "complete" | "failed" | "blocked"; readonly evidence: string; readonly codexGoalJson?: string; readonly qualityGateJson?: string }
export interface CheckpointUlwLoopResult { readonly plan: UlwLoopPlan; readonly goal: UlwLoopItem; readonly ledgerEntry: UlwLoopLedgerEntry; readonly aggregateCompletion?: UlwLoopAggregateCompletion }
function ulwLoopFail(message: string, code: string): never { throw new UlwLoopError(message, code); }
function normalizeObjective(value: string): string { return value.replace(/\s+/g, " ").trim(); }
function nonEmptyEvidence(value: string): string { const trimmed = value.trim(); return trimmed || ulwLoopFail("Evidence must be a non-empty string.", "ulw_loop_evidence_required"); }
function findGoal(plan: UlwLoopPlan, goalId: string): UlwLoopItem { const goal = plan.goals.find((candidate) => candidate.id === goalId); return goal ?? ulwLoopFail(`Unknown ulw-loop id: ${goalId}.`, "ulw_loop_goal_not_found"); }
function textMentionsUlwLoopPlanArtifact(value: string | undefined): boolean {
const normalized = (value ?? "").toLowerCase();
return normalized.includes(ULW_LOOP_DIR.toLowerCase()) || normalized.includes(ULW_LOOP_GOALS.toLowerCase()) || normalized.includes(ULW_LOOP_LEDGER.toLowerCase());
}
function textMentionsGoalId(value: string | undefined, goalId: string): boolean { return (value ?? "").toLowerCase().includes(goalId.toLowerCase()); }
function textHasCompletionValidationEvidence(value: string | undefined): boolean {
const normalized = (value ?? "").toLowerCase();
const done = /\b(?:planned work|implementation|deliverables?|scope|task|work)\b/.test(normalized) && /\b(?:done|complete|completed|finished|shipped)\b/.test(normalized);
const verified = /\b(?:validation|verification|tests?|build|lint|review|quality gate|code-review)\b/.test(normalized) && /\b(?:passed|complete|completed|clean|green|approve|approved|clear)\b/.test(normalized);
return done && verified;
}
async function snapshotObjectiveMapsToUlwLoopPlan(repoRoot: string, snapshotObjective: string): Promise<boolean> {
const actual = normalizeObjective(snapshotObjective).toLowerCase();
if (textMentionsUlwLoopPlanArtifact(actual)) return true;
if (actual.length < 24 || !existsSync(ulwLoopBriefPath(repoRoot))) return false;
try {
const brief = normalizeObjective(await readFile(ulwLoopBriefPath(repoRoot), "utf8")).toLowerCase();
return brief.length >= 24 && (brief.includes(actual) || actual.includes(brief));
} catch (error) {
if (error instanceof Error) return false;
throw error;
}
}
async function canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot: string, plan: UlwLoopPlan, goal: UlwLoopItem, snapshotObjective: string, evidence: string): Promise<boolean> {
if (codexGoalMode(plan) !== "aggregate") return false;
if (goal.status !== "in_progress" || plan.activeGoalId !== goal.id) return false;
if (isFinalRunCompletionCandidate(plan, goal)) return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective);
if (!textMentionsUlwLoopPlanArtifact(evidence) || !textMentionsGoalId(evidence, goal.id)) return false;
if (!textHasCompletionValidationEvidence(evidence)) return false;
return snapshotObjectiveMapsToUlwLoopPlan(repoRoot, snapshotObjective);
}
function buildCompletedLegacyGoalRemediation(goal: UlwLoopItem): string {
return [
"If get_goal returns a different completed legacy/thread objective, do not repeat --status complete in this thread.",
`Record a non-terminal blocker with: omo ulw-loop checkpoint --goal-id ${goal.id} --status blocked --evidence "<completed legacy Codex goal blocks create_goal in this thread>" --codex-goal-json "<different completed get_goal JSON or path>".`,
"Then continue only from a Codex goal context with no active/completed conflicting goal, in the same repo/worktree, and create the intended goal there.",
].join(" ");
}
function buildTaskScopedAggregateReconciliationHint(goal: UlwLoopItem, final: boolean): string {
if (final) {
return ` Final task-scoped aggregate reconciliation requires the checkpoint goal to be the active in-progress final OMO goal and the completed get_goal objective to map to the ulw-loop brief or artifact. ${buildCompletedLegacyGoalRemediation(goal)}`;
}
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)}`;
}
async function readJsonInput(raw: string | undefined, repoRoot: string): Promise<unknown> {
if (raw === undefined || raw.trim() === "") return undefined;
const trimmed = raw.trim();
try { return JSON.parse(trimmed); } catch (error) { if (!(error instanceof SyntaxError)) throw error; }
const path = resolve(repoRoot, trimmed);
if (!existsSync(path)) return ulwLoopFail("Quality gate JSON is neither valid JSON nor a readable path.", "ulw_loop_json_input_invalid");
try { return JSON.parse(await readFile(path, "utf8")); } catch (error) { return ulwLoopFail(`Quality gate path does not contain valid JSON${error instanceof Error ? `: ${error.message}` : "."}`, "ulw_loop_json_input_invalid"); }
}
function makeAggregateCompletion(now: string, evidence: string, codexGoal: unknown): UlwLoopAggregateCompletion {
return { status: "complete", completedAt: now, evidence, codexGoal };
}
function applyBlockedOrFailed(goal: UlwLoopItem, plan: UlwLoopPlan, status: "failed" | "blocked", evidence: string, now: string): void {
const signature = classifyExternalAuthorizationBlocker(evidence);
const occurrences = signature === null ? 0 : sameBlockerOccurrences(plan, signature) + 1;
const needsDecision = signature !== null && occurrences >= 3;
goal.status = needsDecision ? "needs_user_decision" : status;
goal.updatedAt = now;
if (status === "failed" || needsDecision) { goal.failedAt = now; goal.failureReason = evidence; }
if (status === "blocked" || needsDecision) goal.blockedReason = evidence;
if (signature !== null) { goal.blockerSignature = signature; goal.blockerOccurrenceCount = occurrences; goal.requiredExternalDecision = `Resolve external authorization: ${signature}`; }
if (needsDecision) goal.nonRetriable = true;
if (plan.activeGoalId === goal.id) delete plan.activeGoalId;
}
function ledgerKind(status: CheckpointUlwLoopArgs["status"], goal: UlwLoopItem, aggregateCompletion: UlwLoopAggregateCompletion | undefined): UlwLoopLedgerEntry["kind"] {
if (aggregateCompletion !== undefined) return "aggregate_completed";
if (status === "complete") return "goal_completed";
if (goal.status === "needs_user_decision") return "goal_needs_user_decision";
return status === "blocked" ? "goal_blocked" : "goal_failed";
}
function buildLedger(now: string, args: CheckpointUlwLoopArgs, goal: UlwLoopItem, qualityGate: UlwLoopQualityGate | undefined, codexGoal: unknown, aggregateCompletion: UlwLoopAggregateCompletion | undefined): UlwLoopLedgerEntry {
const entry: UlwLoopLedgerEntry = { at: now, kind: ledgerKind(args.status, goal, aggregateCompletion), goalId: goal.id, status: goal.status, evidence: args.evidence };
if (codexGoal !== undefined) entry.codexGoal = codexGoal;
if (qualityGate !== undefined) entry.qualityGate = qualityGate;
if (goal.blockerSignature !== undefined) entry.blockerSignature = goal.blockerSignature;
if (goal.blockerOccurrenceCount !== undefined) entry.blockerOccurrenceCount = goal.blockerOccurrenceCount;
if (goal.requiredExternalDecision !== undefined) entry.requiredExternalDecision = goal.requiredExternalDecision;
return entry;
}
export async function checkpointUlwLoop(repoRoot: string, args: CheckpointUlwLoopArgs): Promise<CheckpointUlwLoopResult> {
return withUlwLoopMutationLock(repoRoot, async () => {
const plan = await readUlwLoopPlan(repoRoot);
const goal = findGoal(plan, args.goalId);
if (args.status === "complete") requireAllCriteriaPass(goal);
const evidence = nonEmptyEvidence(args.evidence);
const now = iso();
let aggregateCompletion: UlwLoopAggregateCompletion | undefined;
let qualityGate: UlwLoopQualityGate | undefined;
let codexGoal: unknown;
if (args.status === "complete") {
const aggregate = codexGoalMode(plan) === "aggregate";
const final = isFinalRunCompletionCandidate(plan, goal);
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...(aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}), allowedStatuses: aggregate ? (final ? ["complete"] : ["active"]) : ["complete"], requireSnapshot: true, requireComplete: !aggregate || final });
codexGoal = reconciliation.snapshot.raw;
if (!reconciliation.ok) {
const objective = snapshot?.objective;
const taskScoped = snapshot?.available === true && snapshot.status === "complete" && objective !== undefined && normalizeObjective(objective) !== normalizeObjective(expectedCodexObjective(plan, goal)) && await canReconcileCompletedTaskScopedAggregateSnapshot(repoRoot, plan, goal, objective, evidence);
if (!taskScoped) throw new UlwLoopError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ulw_loop_codex_snapshot_mismatch");
aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
}
if (final) aggregateCompletion = makeAggregateCompletion(now, evidence, codexGoal);
if (final || aggregateCompletion !== undefined) qualityGate = validateQualityGate(await readJsonInput(args.qualityGateJson, repoRoot));
goal.status = "complete";
goal.completedAt = now;
goal.evidence = evidence;
delete goal.failedAt;
delete goal.failureReason;
clearGoalBlockerFields(goal);
if (plan.activeGoalId === goal.id) delete plan.activeGoalId;
} else applyBlockedOrFailed(goal, plan, args.status, evidence, now);
goal.updatedAt = now;
if (aggregateCompletion !== undefined) plan.aggregateCompletion = aggregateCompletion;
plan.updatedAt = now;
await writePlan(repoRoot, plan);
const ledgerEntry = buildLedger(now, args, goal, qualityGate, codexGoal, aggregateCompletion);
await appendLedger(repoRoot, ledgerEntry);
return aggregateCompletion === undefined ? { plan, goal, ledgerEntry } : { plan, goal, ledgerEntry, aggregateCompletion };
});
}
@@ -0,0 +1,95 @@
// biome-ignore-all format: keep this module under the mandated pure LOC budget.
import { readFile } from "node:fs/promises";
import { UlwLoopError } from "./types.js";
type RecordEvidenceCliArgs = { readonly goalId: string; readonly criterionId: string; readonly status: "pass" | "fail" | "blocked"; readonly evidence: string; readonly notes?: string };
const VALUE_FLAGS = new Set("--brief --brief-file --codex-goal-mode --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".split(" "));
const SUBCOMMANDS = new Set("create-goals status complete-goals criteria record-evidence checkpoint steer add-goal record-review-blockers".split(" "));
export function hasFlag(argv: readonly string[], flag: string): boolean { return argv.includes(flag); }
export function readValue(argv: readonly string[], flag: string): string | undefined {
const index = argv.indexOf(flag);
if (index >= 0) {
const next = argv[index + 1];
return next === undefined || next.startsWith("--") ? undefined : next;
}
const prefix = `${flag}=`;
return argv.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
}
export function readRepeated(argv: readonly string[], flag: string): string[] {
const values: string[] = [];
const prefix = `${flag}=`;
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
const next = argv[index + 1];
if (arg === flag && next !== undefined && !next.startsWith("--")) { values.push(next); index += 1; }
else if (arg?.startsWith(prefix)) values.push(arg.slice(prefix.length));
}
return values;
}
export function parseGoalArg(argv: readonly string[]): string | undefined { return readValue(argv, "--goal-id") ?? readValue(argv, "--goal"); }
export async function readStdin(): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
return Buffer.concat(chunks).toString("utf8");
}
export function positionalText(argv: readonly string[]): string {
const words: string[] = [];
for (let index = SUBCOMMANDS.has(argv[0] ?? "") ? 1 : 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === undefined) continue;
if (VALUE_FLAGS.has(arg)) { index += 1; continue; }
if (arg.startsWith("--")) continue;
words.push(arg);
}
return words.join(" ").trim();
}
function looksLikeJson(value: string): boolean { const trimmed = value.trim(); return trimmed.startsWith("{") || trimmed.startsWith("["); }
export async function readJsonInput(value: string | undefined): Promise<unknown | undefined> {
if (value === undefined) return undefined;
try { return JSON.parse(looksLikeJson(value) ? value : await readFile(value, "utf8")); }
catch (error) {
const message = error instanceof Error ? error.message : "unknown error";
throw new UlwLoopError(`Invalid JSON input: ${message}`, "ULW_LOOP_JSON_INPUT_INVALID", { cause: error });
}
}
export async function parseCodexGoalJson(value: string | undefined): Promise<string | undefined> {
if (value === undefined) return undefined;
const raw = looksLikeJson(value) ? value : await readFile(value, "utf8");
try { JSON.parse(raw); return raw; }
catch (error) {
const message = error instanceof Error ? error.message : "unknown error";
throw new UlwLoopError(`Invalid --codex-goal-json: ${message}`, "ULW_LOOP_CODEX_GOAL_JSON_INVALID", { cause: error });
}
}
function required(argv: readonly string[], flag: string, code: string): string {
const value = readValue(argv, flag)?.trim();
if (value) return value;
throw new UlwLoopError(`Missing ${flag}.`, code, { details: { flag } });
}
function evidenceStatus(value: string): RecordEvidenceCliArgs["status"] {
switch (value) {
case "pass": return "pass";
case "fail": return "fail";
case "blocked": return "blocked";
default: throw new UlwLoopError("Invalid --status; expected pass, fail, or blocked.", "ULW_LOOP_EVIDENCE_STATUS_INVALID", { details: { status: value } });
}
}
export function parseRecordEvidenceArgs(argv: readonly string[]): RecordEvidenceCliArgs {
const result = { goalId: required(argv, "--goal-id", "ULW_LOOP_GOAL_ID_REQUIRED"), criterionId: required(argv, "--criterion-id", "ULW_LOOP_CRITERION_ID_REQUIRED"), status: evidenceStatus(required(argv, "--status", "ULW_LOOP_EVIDENCE_STATUS_REQUIRED")), evidence: required(argv, "--evidence", "ULW_LOOP_EVIDENCE_REQUIRED") };
const notes = readValue(argv, "--notes")?.trim();
return notes ? { ...result, notes } : result;
}
@@ -0,0 +1,142 @@
// biome-ignore-all format: keep cli-commands dispatcher under the 200 pure LOC budget.
import { readFile } from "node:fs/promises";
import { checkpointUlwLoop } from "./checkpoint.js";
import { hasFlag, parseCodexGoalJson, parseRecordEvidenceArgs, positionalText, readStdin, readValue } from "./cli-arg-parser.js";
import { blockedDecisionHandoff, normalizeCodexGoalMode, printJson, printStatus, ULW_LOOP_HELP } from "./cli-output.js";
import { parseSteeringProposal, printSteerResult } from "./cli-steering.js";
import { buildCodexGoalInstruction } from "./codex-goal-instruction.js";
import { recordEvidence } from "./evidence.js";
import { addUlwLoopGoal, createUlwLoopPlan, startNextUlwLoop, summarizeUlwLoopPlan } from "./plan-crud.js";
import { readUlwLoopPlan } from "./plan-io.js";
import { recordFinalReviewBlockers } from "./review-blockers.js";
import { steerUlwLoop } from "./steering.js";
import type { UlwLoopItem } from "./types.js";
import { UlwLoopError } from "./types.js";
type CheckpointStatus = "complete" | "failed" | "blocked";
export async function ulwLoopCommand(argv: readonly string[]): Promise<number> {
const command = argv[0] ?? "help";
const rest = argv.slice(1);
const repoRoot = process.cwd();
const json = hasFlag(rest, "--json");
try {
switch (command) {
case "help": case "--help": case "-h": process.stdout.write(`${ULW_LOOP_HELP}\n`); return 0;
case "create-goals": return await createGoals(repoRoot, rest, json);
case "status": return await status(repoRoot, json);
case "complete-goals": return await completeGoals(repoRoot, rest, json);
case "checkpoint": return await checkpoint(repoRoot, rest, json);
case "steer": return await steer(repoRoot, rest, json);
case "add-goal": return await addGoal(repoRoot, rest, json);
case "criteria": return await criteria(repoRoot, rest, json);
case "record-evidence": return await captureEvidence(repoRoot, rest, json);
case "record-review-blockers": return await reviewBlockers(repoRoot, rest, json);
default: process.stdout.write(`${ULW_LOOP_HELP}\n`); return 1;
}
} catch (error) {
if (error instanceof UlwLoopError) process.stderr.write(`[ulw-loop] ${error.message}\n`);
else if (error instanceof Error) process.stderr.write(`[ulw-loop] unexpected: ${error.message}\n`);
else process.stderr.write("[ulw-loop] unknown error\n");
return 1;
}
}
async function createGoals(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const briefFile = readValue(argv, "--brief-file");
const brief = readValue(argv, "--brief") ?? (briefFile === undefined ? undefined : await readFile(briefFile, "utf8")) ?? (hasFlag(argv, "--from-stdin") ? await readStdin() : undefined) ?? positionalText(argv);
if (!brief.trim()) throw new UlwLoopError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULW_LOOP_BRIEF_REQUIRED");
const plan = await createUlwLoopPlan(repoRoot, { brief, codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")), force: hasFlag(argv, "--force") });
if (json) printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
else process.stdout.write(`ulw-loop plan created: ${plan.goals.length} goal(s)\nbrief: ${plan.briefPath}\ngoals: ${plan.goalsPath}\nledger: ${plan.ledgerPath}\n`);
return 0;
}
async function status(repoRoot: string, json: boolean): Promise<number> {
const plan = await readUlwLoopPlan(repoRoot);
if (json) printJson({ ok: true, plan, summary: summarizeUlwLoopPlan(plan) });
else printStatus(plan);
return 0;
}
async function completeGoals(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const result = await startNextUlwLoop(repoRoot, { retryFailed: hasFlag(argv, "--retry-failed") });
if ("done" in result) {
const handoff = blockedDecisionHandoff(result.plan);
if (json) printJson({ ok: true, done: true, blocked: handoff.length > 0, handoff, summary: summarizeUlwLoopPlan(result.plan), plan: result.plan });
else process.stdout.write(`${handoff || "ulw-loop: all goals complete"}\n`);
return 0;
}
const instruction = buildCodexGoalInstruction({ plan: result.plan, goal: result.goal });
if (json) printJson({ ok: true, resumed: result.resumed, goal: result.goal, instruction, plan: result.plan });
else process.stdout.write(`${instruction.text}\n`);
return 0;
}
async function checkpoint(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const goalId = required(argv, "--goal-id");
const statusValue = checkpointStatus(required(argv, "--status"));
const evidence = required(argv, "--evidence");
const codexGoalJson = await parseCodexGoalJson(required(argv, "--codex-goal-json"));
if (codexGoalJson === undefined) throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
const qualityGateJson = readValue(argv, "--quality-gate-json");
const result = await checkpointUlwLoop(repoRoot, qualityGateJson === undefined ? { goalId, status: statusValue, evidence, codexGoalJson } : { goalId, status: statusValue, evidence, codexGoalJson, qualityGateJson });
if (json) printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
else process.stdout.write(`ulw-loop checkpoint: ${result.goal.id} -> ${result.goal.status}\n`);
return 0;
}
async function steer(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const proposal = await parseSteeringProposal(argv);
const result = await steerUlwLoop(repoRoot, proposal);
printSteerResult(result, json);
return result.accepted ? 0 : 1;
}
async function addGoal(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const result = await addUlwLoopGoal(repoRoot, { title: required(argv, "--title"), objective: required(argv, "--objective") });
if (json) printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUlwLoopPlan(result.plan) });
else { process.stdout.write(`ulw-loop added goal: ${result.goal.id}\n`); printStatus(result.plan); }
return 0;
}
async function criteria(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const goalId = required(argv, "--goal-id");
const goal = findGoal(await readUlwLoopPlan(repoRoot), goalId);
if (json) printJson({ ok: true, goalId: goal.id, criteria: goal.successCriteria });
else process.stdout.write(`criteria for ${goal.id}:\n${goal.successCriteria.map((c) => `- ${c.id} [${c.status}] (${c.userModel}) ${c.scenario} evidence: ${c.capturedEvidence ?? "pending"}`).join("\n")}\n`);
return 0;
}
async function captureEvidence(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const result = await recordEvidence(repoRoot, parseRecordEvidenceArgs(argv));
if (json) printJson({ ok: true, ...result, summary: summarizeUlwLoopPlan(result.plan) });
else process.stdout.write(`ulw-loop evidence recorded: ${result.goal.id}/${result.criterion.id} -> ${result.criterion.status}\n`);
return 0;
}
async function reviewBlockers(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
const codexGoalJson = await parseCodexGoalJson(required(argv, "--codex-goal-json"));
if (codexGoalJson === undefined) throw new UlwLoopError("Missing --codex-goal-json.", "ULW_LOOP_CODEX_GOAL_JSON_REQUIRED");
const result = await recordFinalReviewBlockers(repoRoot, { goalId: required(argv, "--goal-id"), title: required(argv, "--title"), objective: required(argv, "--objective"), evidence: required(argv, "--evidence"), codexGoalJson });
if (json) printJson({ ok: true, plan: result.plan, blockedGoal: result.blockedGoal, goal: result.newGoal, ledgerEntries: result.ledgerEntries, summary: summarizeUlwLoopPlan(result.plan) });
else process.stdout.write(`ulw-loop final review blockers recorded: ${result.blockedGoal.id} -> review_blocked; added ${result.newGoal.id}\n`);
return 0;
}
function required(argv: readonly string[], flag: string): string {
const value = readValue(argv, flag)?.trim();
if (value) return value;
throw new UlwLoopError(`Missing ${flag}.`, "ULW_LOOP_ARGUMENT_MISSING", { details: { flag } });
}
function checkpointStatus(value: string): CheckpointStatus {
if (value === "complete" || value === "failed" || value === "blocked") return value;
throw new UlwLoopError("Missing or invalid --status; expected complete, failed, or blocked.", "ULW_LOOP_STATUS_INVALID", { details: { status: value } });
}
function findGoal(plan: { readonly goals: readonly UlwLoopItem[] }, goalId: string): UlwLoopItem {
const goal = plan.goals.find((candidate) => candidate.id === goalId);
if (goal !== undefined) return goal;
throw new UlwLoopError(`Unknown ulw-loop id: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { details: { goalId } });
}
@@ -0,0 +1,61 @@
import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan } from "./types.js";
import { UlwLoopError } from "./types.js";
export const ULW_LOOP_HELP = `Usage:
omo ulw-loop create-goals --brief "..." [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--force] [--json]
omo ulw-loop status [--json]
omo ulw-loop complete-goals [--retry-failed] [--json]
omo ulw-loop criteria --goal-id <id> [--json]
omo ulw-loop record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
omo ulw-loop checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--json]
omo ulw-loop steer --kind <kind> ... --evidence "..." --rationale "..." [--json]
omo ulw-loop add-goal --title "..." --objective "..." [--json]
omo ulw-loop record-review-blockers --goal-id <id> --title "..." --objective "..." --evidence "..." --codex-goal-json <...> [--json]`;
type CriteriaCounts = { readonly pass: number; readonly total: number };
export function printJson(value: unknown): void {
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
}
function criteriaCounts(goal: UlwLoopItem): CriteriaCounts {
let pass = 0;
for (const criterion of goal.successCriteria) if (criterion.status === "pass") pass += 1;
return { pass, total: goal.successCriteria.length };
}
export function printStatus(plan: UlwLoopPlan): void {
let totalCriteria = 0;
let passCriteria = 0;
const lines = ["ulw-loop status", "", "goals:"];
for (const goal of plan.goals) {
const counts = criteriaCounts(goal);
totalCriteria += counts.total;
passCriteria += counts.pass;
const marker = goal.id === plan.activeGoalId ? "*" : "-";
lines.push(`${marker} ${goal.id} [${goal.status}] ${goal.title} (criteria: ${counts.pass}/${counts.total})`);
}
lines.push("", "summary:", `total goals: ${plan.goals.length}`, `criteria: ${passCriteria}/${totalCriteria} pass`);
process.stdout.write(`${lines.join("\n")}\n`);
}
export function blockedDecisionHandoff(plan: UlwLoopPlan): string {
const blocked = plan.goals.find((goal) => goal.status === "needs_user_decision" && goal.nonRetriable);
if (blocked === undefined) return "";
return [
"ulw-loop: blocked on repeated external authorization; no retryable failed goals remain.",
`Goal: ${blocked.id} - ${blocked.title}`,
`Required external decision: ${blocked.requiredExternalDecision ?? "provide the missing authorization or choose a different unblock path"}.`,
"Do not run complete-goals --retry-failed again until external state changes or the user authorizes an unblock path.",
].join("\n");
}
export function normalizeCodexGoalMode(value: string | undefined): UlwLoopCodexGoalMode {
if (value === undefined) return "aggregate";
if (value === "aggregate" || value === "per_story") return value;
throw new UlwLoopError(
"Invalid --codex-goal-mode; expected aggregate or per_story.",
"ULW_LOOP_CODEX_GOAL_MODE_INVALID",
{ details: { value } },
);
}
@@ -0,0 +1,94 @@
// biome-ignore-all format: keep this module under the mandated pure LOC budget.
import { parseGoalArg, readJsonInput, readValue } from "./cli-arg-parser.js";
import { printJson, printStatus } from "./cli-output.js";
import type { SteerUlwLoopResult, UlwLoopSteeringChildGoal, UlwLoopSteeringMutationKind, UlwLoopSteeringProposal, UlwLoopSteeringSource, UlwLoopSuccessCriterionUserModel } from "./types.js";
import { ULW_LOOP_STEERING_MUTATION_KINDS, ULW_LOOP_SUCCESS_CRITERION_USER_MODELS, UlwLoopError } from "./types.js";
const SOURCES = ["user_prompt_submit", "finding", "cli"] as const satisfies readonly UlwLoopSteeringSource[];
export type CliSteeringProposal = UlwLoopSteeringProposal & { readonly goalId?: string; readonly scenario?: string; readonly expectedEvidence?: string; readonly userModel?: UlwLoopSuccessCriterionUserModel };
function isKind(value: string | undefined): value is UlwLoopSteeringMutationKind { return value !== undefined && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value); }
function isSource(value: string | undefined): value is UlwLoopSteeringSource { return value !== undefined && SOURCES.some((source) => source === value); }
function isModel(value: string): value is UlwLoopSuccessCriterionUserModel { return ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value); }
function fail(message: string, code: string, details: Record<string, unknown>): never { throw new UlwLoopError(message, code, { details }); }
function text(value: string | undefined, field: string): string | undefined { if (value === undefined) return undefined; const trimmed = value.trim(); if (trimmed.length > 0) return trimmed; return fail(`Empty ${field}.`, "ULW_LOOP_STEERING_FIELD_EMPTY", { field }); }
function required(argv: readonly string[], flag: string): string { const value = text(readValue(argv, flag), flag); return value ?? fail(`Missing ${flag}.`, "ULW_LOOP_STEERING_FIELD_REQUIRED", { flag }); }
function requiredGoal(argv: readonly string[]): string { const value = text(parseGoalArg(argv), "--goal-id"); return value ?? fail("Missing --goal-id.", "ULW_LOOP_GOAL_ID_REQUIRED", { flag: "--goal-id" }); }
function readObject(value: object, key: string): unknown { return Object.entries(value).find(([name]) => name === key)?.[1]; }
function isPlain(value: unknown): value is object { return typeof value === "object" && value !== null && !Array.isArray(value); }
function objectText(value: object, key: string): string | undefined { const candidate = readObject(value, key); return typeof candidate === "string" ? candidate : undefined; }
export function parseSteeringKind(argv: readonly string[]): UlwLoopSteeringMutationKind {
const value = readValue(argv, "--kind");
if (isKind(value)) return value;
return value === undefined ? fail("Missing --kind.", "ULW_LOOP_STEERING_KIND_REQUIRED", { flag: "--kind" }) : fail(`Invalid --kind: ${value}.`, "ULW_LOOP_STEERING_KIND_INVALID", { value, expected: ULW_LOOP_STEERING_MUTATION_KINDS });
}
export function parseSteeringSource(argv: readonly string[]): UlwLoopSteeringSource {
const value = readValue(argv, "--source");
if (value === undefined) return "cli";
return isSource(value) ? value : fail(`Invalid --source: ${value}.`, "ULW_LOOP_STEERING_SOURCE_INVALID", { value, expected: SOURCES });
}
function child(value: unknown): UlwLoopSteeringChildGoal | null {
if (!isPlain(value)) return null;
const title = text(objectText(value, "title"), "title"); const objective = text(objectText(value, "objective"), "objective");
if (title === undefined || objective === undefined) return null;
return { title, objective };
}
async function children(argv: readonly string[], flag: string, needed: boolean): Promise<UlwLoopSteeringChildGoal[]> {
const input = needed ? required(argv, flag) : text(readValue(argv, flag), flag);
if (input === undefined) return [];
const raw = await readJsonInput(input);
if (!Array.isArray(raw)) return fail(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
const parsed: UlwLoopSteeringChildGoal[] = [];
for (const item of raw) { const next = child(item); if (next === null) return fail(`${flag} entries require title/objective.`, "ULW_LOOP_STEERING_CHILD_INVALID", { flag }); parsed.push(next); }
return parsed;
}
async function stringArray(argv: readonly string[], flag: string): Promise<string[]> {
const raw = await readJsonInput(required(argv, flag));
if (!Array.isArray(raw)) return fail(`${flag} must be a JSON array.`, "ULW_LOOP_STEERING_JSON_ARRAY_REQUIRED", { flag });
const values: string[] = [];
for (const item of raw) { if (typeof item !== "string") return fail(`${flag} entries must be strings.`, "ULW_LOOP_STEERING_STRING_ARRAY_REQUIRED", { flag }); values.push(text(item, flag) ?? ""); }
return values;
}
function model(value: string | undefined): UlwLoopSuccessCriterionUserModel | undefined { const trimmed = text(value, "--user-model"); if (trimmed === undefined) return undefined; return isModel(trimmed) ? trimmed : fail(`Invalid --user-model: ${trimmed}.`, "ULW_LOOP_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULW_LOOP_SUCCESS_CRITERION_USER_MODELS }); }
function neverKind(kind: never): never { return fail(`Unsupported steering kind: ${String(kind)}.`, "ULW_LOOP_STEERING_KIND_UNSUPPORTED", { kind }); }
export async function parseSteeringProposal(argv: readonly string[]): Promise<CliSteeringProposal> {
const kind = parseSteeringKind(argv); const source = parseSteeringSource(argv); const base = { kind, source, evidence: required(argv, "--evidence"), rationale: required(argv, "--rationale") };
switch (kind) {
case "add_subgoal": return normalizeSteeringProposal({ ...base, title: required(argv, "--title"), objective: required(argv, "--objective") });
case "split_subgoal": { const goalId = requiredGoal(argv); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, childGoals: await children(argv, "--children", true) }); }
case "reorder_pending": return normalizeSteeringProposal({ ...base, pendingOrder: await stringArray(argv, "--order") });
case "revise_pending_wording": { const goalId = requiredGoal(argv); const revisedTitle = readValue(argv, "--title"); const revisedObjective = readValue(argv, "--objective"); if (revisedTitle === undefined && revisedObjective === undefined) return fail("revise_pending_wording requires --title or --objective.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind }); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, ...(revisedTitle === undefined ? {} : { revisedTitle }), ...(revisedObjective === undefined ? {} : { revisedObjective }) }); }
case "revise_criterion": { const goalId = requiredGoal(argv); const criterionId = required(argv, "--criterion-id"); const scenario = readValue(argv, "--scenario"); const expectedEvidence = readValue(argv, "--expected-evidence"); const userModel = model(readValue(argv, "--user-model")); if (scenario === undefined && expectedEvidence === undefined && userModel === undefined) return fail("revise_criterion requires scenario, expected-evidence, or user-model.", "ULW_LOOP_STEERING_UPDATE_REQUIRED", { kind }); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, criterionId, ...(scenario === undefined ? {} : { scenario }), ...(expectedEvidence === undefined ? {} : { expectedEvidence }), ...(userModel === undefined ? {} : { userModel }) }); }
case "annotate_ledger": return normalizeSteeringProposal(base);
case "mark_blocked_superseded": { const goalId = requiredGoal(argv); const childGoals = await children(argv, "--replacements", false); return normalizeSteeringProposal({ ...base, goalId, targetGoalId: goalId, ...(childGoals.length === 0 ? {} : { childGoals }) }); }
default: return neverKind(kind);
}
}
function normalizedChildren(values: readonly UlwLoopSteeringChildGoal[] | undefined): UlwLoopSteeringChildGoal[] | undefined { if (values === undefined) return undefined; return values.map((item) => ({ title: text(item.title, "child.title") ?? "", objective: text(item.objective, "child.objective") ?? "" })); }
function normalizedStrings(values: readonly string[] | undefined, field: string): string[] | undefined { if (values === undefined) return undefined; return values.map((value) => text(value, field) ?? ""); }
export function normalizeSteeringProposal(proposal: CliSteeringProposal): CliSteeringProposal {
const evidence = text(proposal.evidence, "evidence") ?? ""; const rationale = text(proposal.rationale, "rationale") ?? ""; const goalId = text(proposal.goalId, "goalId"); const targetGoalId = text(proposal.targetGoalId, "targetGoalId"); const targetGoalIds = normalizedStrings(proposal.targetGoalIds, "targetGoalIds");
const criterionId = text(proposal.criterionId, "criterionId"); const title = text(proposal.title, "title"); const objective = text(proposal.objective, "objective"); const revisedTitle = text(proposal.revisedTitle, "revisedTitle"); const revisedObjective = text(proposal.revisedObjective, "revisedObjective");
const blockedReason = text(proposal.blockedReason, "blockedReason"); const directiveText = text(proposal.directiveText, "directiveText"); const promptSignature = text(proposal.promptSignature, "promptSignature"); const idempotencyKey = text(proposal.idempotencyKey, "idempotencyKey");
const scenario = text(proposal.scenario, "scenario"); const expectedEvidence = text(proposal.expectedEvidence, "expectedEvidence"); const childGoals = normalizedChildren(proposal.childGoals); const pendingOrder = normalizedStrings(proposal.pendingOrder, "pendingOrder");
return { kind: proposal.kind, source: proposal.source, evidence, rationale, ...(goalId === undefined ? {} : { goalId }), ...(targetGoalId === undefined ? {} : { targetGoalId }), ...(targetGoalIds === undefined ? {} : { targetGoalIds }), ...(criterionId === undefined ? {} : { criterionId }), ...(title === undefined ? {} : { title }), ...(objective === undefined ? {} : { objective }), ...(childGoals === undefined ? {} : { childGoals }), ...(revisedTitle === undefined ? {} : { revisedTitle }), ...(revisedObjective === undefined ? {} : { revisedObjective }), ...(pendingOrder === undefined ? {} : { pendingOrder }), ...(blockedReason === undefined ? {} : { blockedReason }), ...(proposal.after === undefined ? {} : { after: proposal.after }), ...(directiveText === undefined ? {} : { directiveText }), ...(promptSignature === undefined ? {} : { promptSignature }), ...(idempotencyKey === undefined ? {} : { idempotencyKey }), ...(proposal.now === undefined ? {} : { now: proposal.now }), ...(scenario === undefined ? {} : { scenario }), ...(expectedEvidence === undefined ? {} : { expectedEvidence }), ...(proposal.userModel === undefined ? {} : { userModel: proposal.userModel }) };
}
export function printSteerResult(result: SteerUlwLoopResult, json: boolean): void {
if (json) { printJson({ ok: result.accepted, accepted: result.accepted, rejectedReasons: result.rejectedReasons, deduped: result.deduped, audit: result.audit, plan: result.plan }); return; }
const outcome = result.deduped ? "deduped" : result.accepted ? "accepted" : "rejected";
process.stdout.write(`ulw-loop steer: ${outcome} ${result.audit.kind}\n`);
if (result.rejectedReasons.length > 0) process.stdout.write(`rejected: ${result.rejectedReasons.join("; ")}\n`);
if (result.audit.idempotencyKey !== undefined) process.stdout.write(`idempotency-key: ${result.audit.idempotencyKey}\n`);
printStatus(result.plan);
}
@@ -0,0 +1,40 @@
#!/usr/bin/env node
import { ulwLoopCommand } from "./cli-commands.js";
import { runPreToolUseGoalBudgetGuardCli, runUlwLoopHookCli } from "./codex-hook.js";
const TOP_LEVEL_HELP =
"Usage:\n omo ulw-loop <subcommand> [args]\n omo hook user-prompt-submit (Codex UserPromptSubmit hook)\n omo help | --help | -h (this message)\n\nRun `omo ulw-loop help` for ulw-loop subcommands.\n";
async function main(): Promise<number> {
const argv = process.argv.slice(2);
const command = argv[0];
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
process.stdout.write(TOP_LEVEL_HELP);
return 0;
}
if (command === "ulw-loop") return ulwLoopCommand(argv.slice(1));
if (command === "hook") {
const sub = argv[1];
if (sub === "user-prompt-submit") {
await runUlwLoopHookCli(process.stdin, process.stdout);
return 0;
}
if (sub === "pre-tool-use") {
await runPreToolUseGoalBudgetGuardCli(process.stdin, process.stdout);
return 0;
}
process.stderr.write(`[omo] unknown hook subcommand: ${sub ?? "(none)"}\n`);
return 1;
}
process.stderr.write(`[omo] unknown command: ${command}\n${TOP_LEVEL_HELP}`);
return 1;
}
main()
.then((code) => {
process.exit(code);
})
.catch((error: unknown) => {
process.stderr.write(`[omo] ${error instanceof Error ? error.message : String(error)}\n`);
process.exit(1);
});
@@ -0,0 +1,121 @@
import { codexGoalMode, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
export interface CodexCreateGoalPayload {
readonly objective: string;
readonly status: "active";
}
export interface UlwLoopGoalInstruction {
readonly text: string;
readonly json: CodexCreateGoalPayload;
}
export function buildCodexGoalInstruction(args: {
readonly plan: UlwLoopPlan;
readonly goal: UlwLoopItem;
readonly isFinal?: boolean;
}): UlwLoopGoalInstruction {
const mode = codexGoalMode(args.plan);
const createGoal = buildCreateGoalPayload(args.plan, args.goal);
const isFinal = args.isFinal ?? isFinalRunCompletionCandidate(args.plan, args.goal);
return { text: buildText(mode, args.plan, args.goal, createGoal, isFinal), json: createGoal };
}
function buildCreateGoalPayload(plan: UlwLoopPlan, goal: UlwLoopItem): CodexCreateGoalPayload {
return { objective: expectedCodexObjective(plan, goal), status: "active" };
}
function buildText(
mode: UlwLoopCodexGoalMode,
plan: UlwLoopPlan,
goal: UlwLoopItem,
createGoal: CodexCreateGoalPayload,
isFinal: boolean,
): string {
return joinLines([
mode === "aggregate" ? "UlwLoop aggregate-goal handoff" : "UlwLoop active-goal handoff",
`Mode: ${mode}`,
`Plan: ${plan.goalsPath}`,
`Ledger: ${plan.ledgerPath}`,
`Goal: ${goal.id}${goal.title}`,
"",
...activeGoalLines(goal),
"",
...successCriteriaLines(goal.successCriteria),
"",
"Codex goal integration constraints:",
"- Use the create_goal payload exactly as rendered: objective and status only.",
"- Goals are unlimited. Do not add numeric limits.",
...modeConstraintLines(mode, isFinal),
finalSection(goal, isFinal, mode === "aggregate"),
...checkpointLines(mode),
"",
"create_goal payload:",
JSON.stringify(createGoal, null, 2),
]);
}
function modeConstraintLines(mode: UlwLoopCodexGoalMode, isFinal: boolean): readonly string[] {
if (mode === "per_story") {
return [
"- First call get_goal. If no active goal exists, call create_goal with the payload below.",
"- If a different active Codex goal exists, finish/checkpoint that goal before starting this ulw-loop.",
"- Work only this goal until its completion audit passes.",
];
}
return [
"- Codex goal = the whole omo ulw-loop run; OMO G001/G002/etc. = ledger stories.",
"- First call get_goal. If no active goal exists, call create_goal with the aggregate payload below.",
"- If get_goal reports the same aggregate objective as active, continue this OMO story without creating a new Codex goal.",
"- If a different active or incomplete Codex goal exists, finish/checkpoint that goal before starting this ulw-loop.",
isFinal
? "- This is the final story; update_goal is allowed only after the mandatory quality gate passes."
: "- This is not the final story: do not call update_goal yet; the aggregate Codex goal must remain active while later OMO stories remain.",
];
}
function checkpointLines(mode: UlwLoopCodexGoalMode): readonly string[] {
const failureLine =
"- If blocked or failed, checkpoint with --status failed and the failure evidence; rerun complete-goals --retry-failed to resume.";
if (mode === "per_story") return [failureLine];
return [
"- Checkpoint this OMO story with a fresh get_goal snapshot whose objective matches the aggregate payload.",
failureLine,
];
}
function activeGoalLines(goal: UlwLoopItem): readonly string[] {
return ["Active goal:", `- id: ${goal.id}`, `- title: ${goal.title}`, `- objective: ${goal.objective}`];
}
function successCriteriaLines(criteria: readonly UlwLoopSuccessCriterion[]): readonly string[] {
if (criteria.length === 0) return ["Success criteria:", "- No success criteria recorded for this goal."];
return ["Success criteria:", ...criteria.map(formatCriterionLine)];
}
function formatCriterionLine(criterion: UlwLoopSuccessCriterion): string {
const remainingWork = criterion.status === "pending" ? " remaining work:" : "";
return `-${remainingWork} [${criterion.id}] (${criterion.userModel}) ${criterion.scenario} — expect: ${criterion.expectedEvidence} — status: ${criterion.status}`;
}
function finalSection(goal: UlwLoopItem, isFinal: boolean, aggregate: boolean): string {
if (!isFinal)
return "- This is not the final ulw-loop story; do not run the final ai-slop-cleaner/$code-review gate yet.";
const blockerCommand = `omo ulw-loop record-review-blockers --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>"`;
const checkpointCommand = `omo ulw-loop checkpoint --goal-id ${goal.id} --status complete --evidence "<tests/files/PR evidence>" --codex-goal-json "<fresh complete get_goal JSON or path>" --quality-gate-json "<quality gate JSON or path>"`;
return joinLines([
"Final story — run mandatory quality gate before update_goal:",
"- Run ai-slop-cleaner on changed files even when it is a no-op, rerun verification, then run $code-review.",
"- If final $code-review is not APPROVE with architect status CLEAR, do not call update_goal. Record blocker work first:",
` ${blockerCommand}`,
aggregate
? '- If final $code-review is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint the aggregate story:'
: '- If final $code-review is clean, call update_goal({status: "complete"}), call get_goal again, then checkpoint:',
` ${checkpointCommand}`,
]);
}
function joinLines(lines: readonly string[]): string {
return lines.join("\n");
}
@@ -0,0 +1,139 @@
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
export type CodexGoalSnapshotStatus = "active" | "complete" | "cancelled" | "failed" | "unknown";
export interface CodexGoalSnapshot {
available: boolean;
objective?: string;
status?: CodexGoalSnapshotStatus;
raw: unknown;
}
export interface CodexGoalReconciliation {
ok: boolean;
snapshot: CodexGoalSnapshot;
warnings: string[];
errors: string[];
}
export interface ReconcileCodexGoalOptions {
expectedObjective: string;
acceptedObjectives?: readonly string[];
allowedStatuses?: readonly CodexGoalSnapshotStatus[];
requireSnapshot?: boolean;
requireComplete?: boolean;
}
export class CodexGoalSnapshotError extends Error {}
function safeObject(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
}
function safeString(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}
function normalizeStatus(value: unknown): CodexGoalSnapshotStatus {
const status = safeString(value).toLowerCase();
if (status === "complete" || status === "completed" || status === "done") return "complete";
if (status === "cancelled" || status === "canceled") return "cancelled";
if (status === "failed" || status === "failure") return "failed";
if (status === "active" || status === "in_progress" || status === "pending" || status === "running") return "active";
return "unknown";
}
function normalizeObjective(value: string): string {
return value.replace(/\s+/g, " ").trim();
}
export function parseCodexGoalSnapshot(value: unknown): CodexGoalSnapshot {
const root = safeObject(value);
const goalValue = Object.hasOwn(root, "goal") ? root["goal"] : value;
if (goalValue === null || goalValue === undefined || goalValue === false) {
return { available: false, raw: value };
}
const goal = safeObject(goalValue);
const objective = safeString(goal["objective"] ?? goal["goal"] ?? goal["description"] ?? root["objective"]);
const status = normalizeStatus(goal["status"] ?? root["status"]);
return {
available: Boolean(objective || status !== "unknown"),
...(objective ? { objective } : {}),
status,
raw: value,
};
}
export async function readCodexGoalSnapshotInput(
raw: string | undefined,
cwd = process.cwd(),
): Promise<CodexGoalSnapshot | null> {
if (!raw?.trim()) return null;
const trimmed = raw.trim();
try {
return parseCodexGoalSnapshot(JSON.parse(trimmed));
} catch {
const path = resolve(cwd, trimmed);
if (!existsSync(path)) {
throw new CodexGoalSnapshotError(`Codex goal snapshot is neither valid JSON nor a readable path: ${trimmed}`);
}
try {
return parseCodexGoalSnapshot(JSON.parse(await readFile(path, "utf-8")));
} catch (error) {
throw new CodexGoalSnapshotError(
`Codex goal snapshot path does not contain valid JSON: ${trimmed}${error instanceof Error ? ` (${error.message})` : ""}`,
);
}
}
}
export function reconcileCodexGoalSnapshot(
snapshot: CodexGoalSnapshot | null | undefined,
options: ReconcileCodexGoalOptions,
): CodexGoalReconciliation {
const effectiveSnapshot = snapshot ?? { available: false, raw: null };
const errors: string[] = [];
const warnings: string[] = [];
if (!effectiveSnapshot.available) {
const message =
"Codex goal snapshot is absent or reports no active goal; call get_goal and pass its JSON with --codex-goal-json.";
if (options.requireSnapshot) errors.push(message);
else warnings.push(message);
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
}
const expected = normalizeObjective(options.expectedObjective);
const accepted = new Set(
[expected, ...(options.acceptedObjectives ?? []).map((objective) => normalizeObjective(objective))].filter(
Boolean,
),
);
const actual = normalizeObjective(effectiveSnapshot.objective ?? "");
if (!actual) {
errors.push("Codex goal snapshot is missing objective text.");
} else if (!accepted.has(actual)) {
errors.push(`Codex goal objective mismatch: expected "${expected}", got "${actual}".`);
}
const allowed = options.allowedStatuses ?? (options.requireComplete ? ["complete"] : ["active", "complete"]);
const actualStatus = effectiveSnapshot.status ?? "unknown";
if (!allowed.includes(actualStatus)) {
errors.push(`Codex goal status mismatch: expected ${allowed.join(" or ")}, got ${actualStatus}.`);
}
if (options.requireComplete && actualStatus !== "complete") {
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.',
);
}
return { ok: errors.length === 0, snapshot: effectiveSnapshot, warnings, errors };
}
export function formatCodexGoalReconciliation(reconciliation: CodexGoalReconciliation): string {
const parts = [...reconciliation.errors, ...reconciliation.warnings];
return parts.join(" ");
}
@@ -0,0 +1,172 @@
import { parseUlwLoopSteeringDirective, steerUlwLoop } from "./steering.js";
export interface UserPromptSubmitPayload {
readonly cwd: string;
readonly hook_event_name: "UserPromptSubmit";
readonly model?: string;
readonly permission_mode?: string;
readonly prompt: string;
readonly session_id: string;
readonly transcript_path?: string;
readonly turn_id?: string;
}
export interface PreToolUsePayload {
readonly cwd: string;
readonly hook_event_name: "PreToolUse";
readonly model: string;
readonly permission_mode: string;
readonly session_id: string;
readonly tool_input: unknown;
readonly tool_name: string;
readonly tool_use_id: string;
readonly transcript_path: string | null;
readonly turn_id: string;
}
interface PreToolUseHookOutput {
readonly hookSpecificOutput: {
readonly hookEventName: "PreToolUse";
readonly permissionDecision: "deny";
readonly permissionDecisionReason: string;
readonly additionalContext: string;
};
}
const CREATE_GOAL_TOOL_NAME = "create_goal";
const GOAL_BUDGET_WARNING =
"Do not set token_budget on create_goal. Omit the budget field so the goal stays unlimited; ultrawork and ulw-loop runs must always use unlimited goals.";
export function parseUserPromptSubmitPayload(raw: string): UserPromptSubmitPayload | null {
if (raw.trim().length === 0) return null;
try {
const parsed: unknown = JSON.parse(raw);
return isUserPromptSubmitPayload(parsed) ? parsed : null;
} catch (error) {
if (error instanceof SyntaxError) return null;
return null;
}
}
export function parsePreToolUsePayload(raw: string): PreToolUsePayload | null {
if (raw.trim().length === 0) return null;
try {
const parsed: unknown = JSON.parse(raw);
return isPreToolUsePayload(parsed) ? parsed : null;
} catch (error) {
if (error instanceof SyntaxError) return null;
return null;
}
}
export async function applyUserPromptUlwLoopSteering(payload: UserPromptSubmitPayload): Promise<string> {
try {
if (payload.hook_event_name !== "UserPromptSubmit") return "";
const proposal = parseUlwLoopSteeringDirective(payload.prompt);
if (proposal === null) return "";
const result = await steerUlwLoop(payload.cwd, proposal);
if (!result.accepted) return "";
return JSON.stringify({
status: "accepted",
kind: result.audit.kind,
source: result.audit.source,
deduped: result.deduped,
});
} catch (error) {
if (error instanceof Error) return "";
return "";
}
}
export function applyPreToolUseGoalBudgetGuard(payload: PreToolUsePayload): string {
if (payload.hook_event_name !== "PreToolUse") return "";
if (payload.tool_name !== CREATE_GOAL_TOOL_NAME) return "";
if (!hasGoalBudgetInput(payload.tool_input)) return "";
const output: PreToolUseHookOutput = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: GOAL_BUDGET_WARNING,
additionalContext: GOAL_BUDGET_WARNING,
},
};
return `${JSON.stringify(output)}\n`;
}
export async function runUlwLoopHookCli(stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream): Promise<void> {
try {
const payload = parseUserPromptSubmitPayload(await readAll(stdin));
if (payload === null) return;
const output = await applyUserPromptUlwLoopSteering(payload);
if (output.length > 0) stdout.write(output);
} catch (error) {
if (error instanceof Error) return;
return;
}
}
export async function runPreToolUseGoalBudgetGuardCli(
stdin: NodeJS.ReadableStream,
stdout: NodeJS.WritableStream,
): Promise<void> {
try {
const payload = parsePreToolUsePayload(await readAll(stdin));
if (payload === null) return;
const output = applyPreToolUseGoalBudgetGuard(payload);
if (output.length > 0) stdout.write(output);
} catch (error) {
if (error instanceof Error) return;
return;
}
}
function isUserPromptSubmitPayload(value: unknown): value is UserPromptSubmitPayload {
if (!isRecord(value)) return false;
return (
value["hook_event_name"] === "UserPromptSubmit" &&
typeof value["cwd"] === "string" &&
typeof value["prompt"] === "string" &&
typeof value["session_id"] === "string" &&
["model", "permission_mode", "transcript_path", "turn_id"].every((key) => optionalString(value[key]))
);
}
function isPreToolUsePayload(value: unknown): value is PreToolUsePayload {
if (!isRecord(value)) return false;
return (
value["hook_event_name"] === "PreToolUse" &&
typeof value["cwd"] === "string" &&
typeof value["model"] === "string" &&
typeof value["permission_mode"] === "string" &&
typeof value["session_id"] === "string" &&
typeof value["tool_name"] === "string" &&
typeof value["tool_use_id"] === "string" &&
(value["transcript_path"] === null || typeof value["transcript_path"] === "string") &&
typeof value["turn_id"] === "string" &&
Object.hasOwn(value, "tool_input")
);
}
function hasGoalBudgetInput(value: unknown): boolean {
return isRecord(value) && (Object.hasOwn(value, "token_budget") || Object.hasOwn(value, "tokenBudget"));
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function optionalString(value: unknown): boolean {
return value === undefined || typeof value === "string";
}
function readAll(stdin: NodeJS.ReadableStream): Promise<string> {
return new Promise((resolve, reject) => {
let data = "";
stdin.setEncoding("utf8");
stdin.on("data", (chunk: unknown) => {
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
});
stdin.once("error", reject);
stdin.once("end", () => resolve(data));
});
}
@@ -0,0 +1,121 @@
// biome-ignore-all format: keep this module under the mandated pure LOC budget.
import { hasAllCriteriaPass } from "./goal-status.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
import { iso, UlwLoopError } from "./types.js";
type EvidenceStatus = "pass" | "fail" | "blocked";
type RecordEvidenceArgs = { readonly goalId: string; readonly criterionId: string; readonly status: EvidenceStatus; readonly evidence: string; readonly notes?: string };
function ulwLoopFail(message: string, code: string, details: Record<string, unknown>): never { throw new UlwLoopError(message, code, { details }); }
function ledgerKind(status: EvidenceStatus): UlwLoopLedgerEntry["kind"] {
switch (status) {
case "pass":
return "evidence_captured";
case "fail":
return "criterion_failed";
case "blocked":
return "criterion_blocked";
default:
return ulwLoopFail("Invalid criterion status.", "ULW_LOOP_CRITERION_STATUS_INVALID", { status });
}
}
function findGoal(plan: UlwLoopPlan, goalId: string): UlwLoopItem {
const goal = plan.goals.find((candidate) => candidate.id === goalId);
return goal ?? ulwLoopFail(`UlwLoop goal not found: ${goalId}.`, "ULW_LOOP_GOAL_NOT_FOUND", { goalId });
}
function findCriterion(goal: UlwLoopItem, criterionId: string): UlwLoopSuccessCriterion {
const criterion = goal.successCriteria.find((candidate) => candidate.id === criterionId);
return criterion ?? ulwLoopFail(`Success criterion not found: ${criterionId}.`, "ULW_LOOP_CRITERION_NOT_FOUND", { goalId: goal.id, criterionId });
}
function nonEmptyEvidence(evidence: string): string { const trimmed = evidence.trim(); return trimmed || ulwLoopFail("Evidence must be a non-empty string.", "ULW_LOOP_EVIDENCE_REQUIRED", {}); }
export async function recordEvidence(repoRoot: string, args: RecordEvidenceArgs): Promise<{ plan: UlwLoopPlan; goal: UlwLoopItem; criterion: UlwLoopSuccessCriterion; ledgerEntry: UlwLoopLedgerEntry }> {
return withUlwLoopMutationLock(repoRoot, async () => {
const plan = await readUlwLoopPlan(repoRoot);
const goal = findGoal(plan, args.goalId);
const criterion = findCriterion(goal, args.criterionId);
const evidence = nonEmptyEvidence(args.evidence);
const kind = ledgerKind(args.status);
const prevStatus = criterion.status;
const capturedAt = iso();
criterion.status = args.status;
criterion.capturedEvidence = evidence;
criterion.capturedAt = capturedAt;
if (args.notes !== undefined) criterion.notes = args.notes;
goal.updatedAt = capturedAt;
plan.updatedAt = capturedAt;
await writePlan(repoRoot, plan);
const ledgerEntry: UlwLoopLedgerEntry = {
at: capturedAt,
kind,
goalId: goal.id,
criterionId: criterion.id,
criterionStatus: args.status,
evidence,
capturedEvidence: evidence,
before: { status: prevStatus },
after: { goalId: goal.id, criterionId: criterion.id, status: args.status, evidence, capturedAt, prevStatus },
};
await appendLedger(repoRoot, ledgerEntry);
return { plan, goal, criterion, ledgerEntry };
});
}
export async function markCriteriaPendingResetForGoal(repoRoot: string, goalId: string): Promise<{ plan: UlwLoopPlan; resetCount: number }> {
return withUlwLoopMutationLock(repoRoot, async () => {
const plan = await readUlwLoopPlan(repoRoot);
const goal = findGoal(plan, goalId);
const now = iso();
const before = goal.successCriteria.map((criterion) => ({ id: criterion.id, status: criterion.status, capturedEvidence: criterion.capturedEvidence, capturedAt: criterion.capturedAt ?? null }));
for (const criterion of goal.successCriteria) {
criterion.status = "pending";
criterion.capturedEvidence = null;
delete criterion.capturedAt;
delete criterion.notes;
}
goal.updatedAt = now;
plan.updatedAt = now;
await writePlan(repoRoot, plan);
await appendLedger(repoRoot, { at: now, kind: "criteria_revised", goalId, message: `Reset ${goal.successCriteria.length} criteria to pending.`, before, after: { resetCount: goal.successCriteria.length } });
return { plan, resetCount: goal.successCriteria.length };
});
}
export function criteriaSummary(plan: UlwLoopPlan): { totalCriteria: number; passCount: number; pendingCount: number; failCount: number; blockedCount: number; goalsWithUnresolvedCriteria: string[] } {
let totalCriteria = 0;
let passCount = 0;
let pendingCount = 0;
let failCount = 0;
let blockedCount = 0;
const goalsWithUnresolvedCriteria: string[] = [];
for (const goal of plan.goals) {
let unresolved = false;
for (const criterion of goal.successCriteria) {
totalCriteria += 1;
if (criterion.status !== "pass") unresolved = true;
switch (criterion.status) {
case "pass": passCount += 1; break;
case "pending": pendingCount += 1; break;
case "fail": failCount += 1; break;
case "blocked": blockedCount += 1; break;
default: ulwLoopFail("Invalid criterion status.", "ULW_LOOP_CRITERION_STATUS_INVALID", { status: criterion.status });
}
}
if (unresolved) goalsWithUnresolvedCriteria.push(goal.id);
}
return { totalCriteria, passCount, pendingCount, failCount, blockedCount, goalsWithUnresolvedCriteria };
}
export function unresolvedCriteriaOf(goal: UlwLoopItem): UlwLoopSuccessCriterion[] { return goal.successCriteria.filter((criterion) => criterion.status !== "pass"); }
export function requireAllCriteriaPass(goal: UlwLoopItem): void {
if (hasAllCriteriaPass(goal)) return;
throw new UlwLoopError(`Goal ${goal.id} has unresolved success criteria.`, "ulw_loop_criteria_not_all_pass", {
details: { goalId: goal.id, unresolved: unresolvedCriteriaOf(goal).map((criterion) => ({ id: criterion.id, status: criterion.status })) },
});
}
@@ -0,0 +1,84 @@
import type {
UlwLoopCodexGoalMode,
UlwLoopItem,
UlwLoopPlan,
UlwLoopStatus,
UlwLoopSuccessCriterion,
} from "./types.js";
export const ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE: string =
"Complete the durable ulw-loop plan in .omo/ulw-loop/goals.json, including later accepted/appended stories, under the original brief constraints; use .omo/ulw-loop/ledger.jsonl as the audit trail.";
export function codexGoalMode(plan: UlwLoopPlan): UlwLoopCodexGoalMode {
return plan.codexGoalMode ?? "per_story";
}
function isResolvedStatus(status: UlwLoopStatus): boolean {
return status === "complete";
}
function isSupersededResolved(goal: UlwLoopItem, plan: UlwLoopPlan): boolean {
if (goal.steeringStatus !== "superseded") return false;
const replacements = goal.supersededBy ?? [];
if (replacements.length === 0) return false;
return replacements.every((id) => {
const replacement = plan.goals.find((candidate) => candidate.id === id);
return replacement !== undefined && isResolvedStatus(replacement.status);
});
}
function isCompletionBlocking(goal: UlwLoopItem, plan: UlwLoopPlan): boolean {
if (goal.steeringStatus === "superseded") return !isSupersededResolved(goal, plan);
if (goal.steeringStatus === "blocked") return true;
return !isResolvedStatus(goal.status);
}
function isCompletionBlockingForFinalCandidate(
candidate: UlwLoopItem,
finalCandidate: UlwLoopItem,
plan: UlwLoopPlan,
): boolean {
if (candidate.id === finalCandidate.id) return false;
if (candidate.steeringStatus === "superseded") {
const replacements = candidate.supersededBy ?? [];
if (replacements.length === 0) return true;
return !replacements.every((id) => {
if (id === finalCandidate.id) return true;
const replacement = plan.goals.find((goal) => goal.id === id);
return replacement !== undefined && isResolvedStatus(replacement.status);
});
}
return isCompletionBlocking(candidate, plan);
}
export function isUlwLoopDone(plan: UlwLoopPlan): boolean {
if (plan.aggregateCompletion?.status === "complete") return true;
return plan.goals.every((goal) => !isCompletionBlocking(goal, plan));
}
export function isFinalRunCompletionCandidate(plan: UlwLoopPlan, goal: UlwLoopItem): boolean {
return (
isCompletionBlocking(goal, plan) &&
plan.goals.every((candidate) => !isCompletionBlockingForFinalCandidate(candidate, goal, plan))
);
}
export function aggregateCodexObjective(plan: UlwLoopPlan): string {
return plan.codexObjective ?? ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE;
}
export function expectedCodexObjective(plan: UlwLoopPlan, goal: UlwLoopItem): string {
return codexGoalMode(plan) === "aggregate" ? aggregateCodexObjective(plan) : goal.objective;
}
export function compatibleCodexObjectives(plan: UlwLoopPlan): readonly string[] {
return [aggregateCodexObjective(plan), ...(plan.codexObjectiveAliases ?? [])];
}
export function hasAllCriteriaPass(goal: UlwLoopItem): boolean {
return goal.successCriteria.length > 0 && goal.successCriteria.every((criterion) => criterion.status === "pass");
}
export function firstUnresolvedCriterion(goal: UlwLoopItem): UlwLoopSuccessCriterion | undefined {
return goal.successCriteria.find((criterion) => criterion.status !== "pass");
}
@@ -0,0 +1,27 @@
import { join } from "node:path";
import { ULW_LOOP_BRIEF, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER } from "./types.js";
export function ulwLoopDir(repoRoot: string): string {
return join(repoRoot, ULW_LOOP_DIR);
}
export function ulwLoopBriefPath(repoRoot: string): string {
return join(ulwLoopDir(repoRoot), ULW_LOOP_BRIEF);
}
export function ulwLoopGoalsPath(repoRoot: string): string {
return join(ulwLoopDir(repoRoot), ULW_LOOP_GOALS);
}
export function ulwLoopLedgerPath(repoRoot: string): string {
return join(ulwLoopDir(repoRoot), ULW_LOOP_LEDGER);
}
export function repoRelative(absolutePath: string, repoRoot: string): string {
const slashPrefix = `${repoRoot}/`;
const backslashPrefix = `${repoRoot}\\`;
if (absolutePath.startsWith(slashPrefix)) return absolutePath.slice(slashPrefix.length).split("\\").join("/");
if (absolutePath.startsWith(backslashPrefix))
return absolutePath.slice(backslashPrefix.length).split("\\").join("/");
return absolutePath.split("\\").join("/");
}
@@ -0,0 +1,113 @@
// biome-ignore-all format: keep this port under the mandated pure LOC budget.
import { existsSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import { ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE } from "./goal-status.js";
import { ulwLoopBriefPath, ulwLoopDir, ulwLoopGoalsPath, ulwLoopLedgerPath } from "./paths.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
import { iso, ULW_LOOP_BRIEF, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
export type UlwLoopPlanSummary = { readonly total: number; readonly pending: number; readonly in_progress: number; readonly complete: number; readonly failed: number; readonly blocked: number; readonly review_blocked: number; readonly needs_user_decision: number; readonly superseded: number; readonly criteria: { readonly total: number; readonly pass: number; readonly pending: number; readonly fail: number; readonly blocked: number } };
function cleanLine(line: string): string { return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim(); }
function normalizeObjective(value: string): string { return value.replace(/\s+/g, " ").trim(); }
function titleFromObjective(objective: string, fallback: string): string { const firstLine = objective.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? fallback; return firstLine.length > 72 ? `${firstLine.slice(0, 69).trimEnd()}...` : firstLine; }
function normalizeGoalId(title: string, index: number): string { const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 36).replace(/-+$/g, ""); return `G${String(index + 1).padStart(3, "0")}${slug ? `-${slug}` : ""}`; }
function assertNonEmpty(value: string | undefined, label: string): string { const trimmed = value?.trim(); if (!trimmed) throw new UlwLoopError(`Missing ${label}.`, "ULW_LOOP_ARGUMENT_MISSING"); return trimmed; }
function truncateObjective(objective: string): string { return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective; }
export function seedDefaultSuccessCriteria(goalIndex: number, objective: string): UlwLoopSuccessCriterion[] {
const subject = truncateObjective(normalizeObjective(objective) || `Goal ${goalIndex + 1}`);
const rows = [
["C001", "happy", `happy path for: ${subject}`, `Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`],
["C002", "edge", "edge case (boundary/empty/malformed)", `Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`],
["C003", "regression", "regression: adjacent surface still works", `Replace via revise_criterion with regression proof for neighboring behavior after: ${subject}.`],
] as const;
return rows.map(([id, userModel, scenario, expectedEvidence]) => ({ id, scenario, userModel, expectedEvidence, capturedEvidence: null, status: "pending" }));
}
export function deriveGoalCandidates(brief: string): Array<{ title: string; objective: string }> {
const bulletGoals = brief.split(/\r?\n/).map((line) => ({ original: line, cleaned: normalizeObjective(cleanLine(line)) })).filter(({ cleaned }) => cleaned.length > 0 && cleaned.length <= 1200).filter(({ original, cleaned }, index, all) => /^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(original) && all.findIndex((candidate) => candidate.cleaned === cleaned) === index).map(({ cleaned }) => cleaned);
const paragraphs = brief.split(/\n\s*\n/).map(normalizeObjective).filter((paragraph) => paragraph.length > 0 && !paragraph.startsWith("#"));
const selected = (bulletGoals.length > 0 ? bulletGoals : paragraphs).length > 0 ? (bulletGoals.length > 0 ? bulletGoals : paragraphs) : ["Complete the requested project objective."];
return selected.map((objective, index) => ({ title: titleFromObjective(objective, `Goal ${index + 1}`), objective }));
}
function makeGoal(title: string, objective: string, index: number, now: string): UlwLoopItem {
const cleanTitle = assertNonEmpty(title, "title");
const cleanObjective = assertNonEmpty(objective, "objective");
return { id: normalizeGoalId(cleanTitle, index), title: cleanTitle, objective: cleanObjective, status: "pending", successCriteria: seedDefaultSuccessCriteria(index, cleanObjective), attempt: 0, createdAt: now, updatedAt: now };
}
function appendGoalToPlan(plan: UlwLoopPlan, title: string, objective: string, now: string): UlwLoopItem {
const goal = makeGoal(title, objective, plan.goals.length, now);
plan.goals.push(goal);
plan.updatedAt = now;
return goal;
}
function isScheduleEligible(goal: UlwLoopItem): boolean { return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked"; }
function clearGoalBlockerFields(goal: UlwLoopItem): void {
for (const key of ["blockedReason", "blockerSignature", "blockerOccurrenceCount", "requiredExternalDecision", "nonRetriable", "failedAt", "failureReason"] as const) delete goal[key];
}
export async function createUlwLoopPlan(repoRoot: string, args: { brief: string; codexGoalMode?: UlwLoopCodexGoalMode; force?: boolean }): Promise<UlwLoopPlan> {
return withUlwLoopMutationLock(repoRoot, async () => {
if (!args.force && existsSync(ulwLoopGoalsPath(repoRoot))) throw new UlwLoopError(`Refusing to overwrite existing ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}; pass --force to recreate it.`, "ULW_LOOP_PLAN_EXISTS");
const now = iso();
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now));
const plan: UlwLoopPlan = { version: 1, createdAt: now, updatedAt: now, briefPath: `${ULW_LOOP_DIR}/${ULW_LOOP_BRIEF}`, goalsPath: `${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}`, ledgerPath: `${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER}`, codexGoalMode: args.codexGoalMode ?? "aggregate", goals };
if (plan.codexGoalMode === "aggregate") plan.codexObjective = ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE;
await mkdir(ulwLoopDir(repoRoot), { recursive: true });
await writeFile(ulwLoopBriefPath(repoRoot), args.brief.endsWith("\n") ? args.brief : `${args.brief}\n`, "utf8");
await writePlan(repoRoot, plan);
await writeFile(ulwLoopLedgerPath(repoRoot), "", "utf8");
await appendLedger(repoRoot, { at: now, kind: "plan_created", message: `${goals.length} goal(s) created` });
return plan;
});
}
export async function addUlwLoopGoal(repoRoot: string, args: { title: string; objective: string }): Promise<{ plan: UlwLoopPlan; goal: UlwLoopItem }> {
return withUlwLoopMutationLock(repoRoot, async () => {
const plan = await readUlwLoopPlan(repoRoot);
const now = iso();
const goal = appendGoalToPlan(plan, args.title, args.objective, now);
await writePlan(repoRoot, plan);
await appendLedger(repoRoot, { at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title });
return { plan, goal };
});
}
export async function startNextUlwLoop(repoRoot: string, args: { retryFailed?: boolean } = {}): Promise<{ plan: UlwLoopPlan; goal: UlwLoopItem; resumed: boolean } | { done: true; plan: UlwLoopPlan }> {
return withUlwLoopMutationLock(repoRoot, async () => {
const plan = await readUlwLoopPlan(repoRoot);
const now = iso();
if (plan.aggregateCompletion?.status === "complete") return { done: true, plan };
const existing = plan.goals.find((goal) => goal.status === "in_progress" && isScheduleEligible(goal));
if (existing) { await appendLedger(repoRoot, { at: now, kind: "goal_resumed", goalId: existing.id, status: existing.status, message: "Resuming active ulw-loop" }); return { plan, goal: existing, resumed: true }; }
let next = plan.goals.find((goal) => goal.status === "pending" && isScheduleEligible(goal));
if (!next && args.retryFailed) {
next = plan.goals.find((goal) => goal.status === "failed" && !goal.nonRetriable && isScheduleEligible(goal));
if (next) await appendLedger(repoRoot, { at: now, kind: "goal_retried", goalId: next.id, status: "pending", ...(next.failureReason ? { message: next.failureReason } : {}) });
}
if (!next) return { done: true, plan };
next.status = "in_progress";
next.attempt += 1;
next.startedAt = now;
clearGoalBlockerFields(next);
next.updatedAt = now;
plan.activeGoalId = next.id;
plan.updatedAt = now;
await writePlan(repoRoot, plan);
await appendLedger(repoRoot, { at: now, kind: "goal_started", goalId: next.id, status: next.status, message: `Attempt ${next.attempt}` });
return { plan, goal: next, resumed: false };
});
}
export function summarizeUlwLoopPlan(plan: UlwLoopPlan): UlwLoopPlanSummary {
const countStatus = (status: UlwLoopItem["status"]): number => plan.goals.filter((goal) => goal.status === status).length;
const countCriteria = (status: UlwLoopSuccessCriterion["status"]): number => plan.goals.reduce((sum, goal) => sum + goal.successCriteria.filter((criterion) => criterion.status === status).length, 0);
return { total: plan.goals.length, pending: countStatus("pending"), in_progress: countStatus("in_progress"), complete: countStatus("complete"), failed: countStatus("failed"), blocked: countStatus("blocked"), review_blocked: countStatus("review_blocked"), needs_user_decision: countStatus("needs_user_decision"), superseded: plan.goals.filter((goal) => goal.steeringStatus === "superseded").length, criteria: { total: plan.goals.reduce((sum, goal) => sum + goal.successCriteria.length, 0), pass: countCriteria("pass"), pending: countCriteria("pending"), fail: countCriteria("fail"), blocked: countCriteria("blocked") } };
}
@@ -0,0 +1,99 @@
import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { repoRelative, ulwLoopDir, ulwLoopGoalsPath, ulwLoopLedgerPath } from "./paths.js";
import type { UlwLoopLedgerEntry, UlwLoopPlan } from "./types.js";
import { iso, ULW_LOOP_DIR, ULW_LOOP_GOALS, ULW_LOOP_LEDGER, UlwLoopError } from "./types.js";
const AGGREGATE_CODEX_OBJECTIVE = `Complete the durable ulw-loop plan in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}, including later accepted/appended stories, under the original brief constraints; use ${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER} as the audit trail.`;
const LEGACY_OBJECTIVE_PREFIX = `Complete all ulw-loop stories in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}: `;
const LEGACY_OBJECTIVE = `Complete all ulw-loop stories listed in ${ULW_LOOP_DIR}/${ULW_LOOP_GOALS}. Use ${ULW_LOOP_DIR}/${ULW_LOOP_LEDGER} as the durable audit trail.`;
const locks = new Map<string, Promise<unknown>>();
function hasCode(error: unknown, code: string): boolean {
return error instanceof Error && "code" in error && error.code === code;
}
function isLegacyEnumeratedAggregateObjective(objective: string | undefined): objective is string {
return objective === LEGACY_OBJECTIVE || Boolean(objective?.startsWith(LEGACY_OBJECTIVE_PREFIX));
}
function isSteeringKind(value: unknown): value is UlwLoopLedgerEntry["kind"] {
return value === "steering_accepted" || value === "steering_rejected" || value === "criteria_revised";
}
export async function withUlwLoopMutationLock<T>(repoRoot: string, fn: () => Promise<T>): Promise<T> {
const prior = locks.get(repoRoot) ?? Promise.resolve();
const run = prior.then(fn, fn);
locks.set(
repoRoot,
run.catch(() => undefined),
);
return run;
}
export async function readUlwLoopPlan(repoRoot: string): Promise<UlwLoopPlan> {
const path = ulwLoopGoalsPath(repoRoot);
let raw: string;
try {
raw = await readFile(path, "utf8");
} catch (error) {
if (!hasCode(error, "ENOENT")) throw error;
throw new UlwLoopError(
`No ulw-loop plan found at ${repoRelative(path, repoRoot)}. Run \`omo ulw-loop create-goals ...\` first.`,
"ULW_LOOP_PLAN_MISSING",
{ cause: error },
);
}
const parsed: UlwLoopPlan = JSON.parse(raw);
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
throw new UlwLoopError(`Invalid ulw-loop plan at ${repoRelative(path, repoRoot)}.`, "ULW_LOOP_PLAN_INVALID");
}
const previousObjective = parsed.codexObjective;
if (
(parsed.codexGoalMode ?? "per_story") === "aggregate" &&
isLegacyEnumeratedAggregateObjective(previousObjective)
) {
const now = iso();
parsed.codexObjective = AGGREGATE_CODEX_OBJECTIVE;
parsed.codexObjectiveAliases = [...new Set([...(parsed.codexObjectiveAliases ?? []), previousObjective])];
parsed.updatedAt = now;
await writePlan(repoRoot, parsed);
await appendLedger(repoRoot, {
at: now,
kind: "aggregate_objective_migrated",
message: "Migrated legacy enumerated aggregate Codex objective to the stable pointer objective.",
before: { codexObjective: previousObjective },
after: { codexObjective: parsed.codexObjective },
});
}
return parsed;
}
export async function writePlan(repoRoot: string, plan: UlwLoopPlan): Promise<void> {
await mkdir(ulwLoopDir(repoRoot), { recursive: true });
const path = ulwLoopGoalsPath(repoRoot);
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
await writeFile(tmpPath, `${JSON.stringify(plan, null, 2)}\n`, "utf8");
await rename(tmpPath, path);
}
export async function appendLedger(repoRoot: string, entry: UlwLoopLedgerEntry): Promise<void> {
await mkdir(ulwLoopDir(repoRoot), { recursive: true });
await appendFile(ulwLoopLedgerPath(repoRoot), `${JSON.stringify(entry)}\n`, "utf8");
}
export async function readSteeringLedgerEntries(repoRoot: string): Promise<UlwLoopLedgerEntry[]> {
let raw: string;
try {
raw = await readFile(ulwLoopLedgerPath(repoRoot), "utf8");
} catch (error) {
if (hasCode(error, "ENOENT")) return [];
throw error;
}
const entries: UlwLoopLedgerEntry[] = [];
for (const line of raw.split(/\r?\n/).filter(Boolean)) {
const entry: UlwLoopLedgerEntry = JSON.parse(line);
if (isSteeringKind(entry.kind)) entries.push(entry);
}
return entries;
}
@@ -0,0 +1,102 @@
import type { UlwLoopItem, UlwLoopPlan, UlwLoopQualityGate } from "./types.js";
import { UlwLoopError } from "./types.js";
const BLOCKER_FIELD_KEYS = "blocker blockerSignature blockerEvidence blockerOccurrences blockedAt".split(" ");
const URL_PATTERN = /https?:\/\/\S+/g;
const PUNCTUATION_PATTERN = /[`"'()[\]{}:,;]/g;
const WHITESPACE_PATTERN = /\s+/g;
const AUTH_PATTERN = /\b(auth\w*|credential\w*|token|permission\w*|scope\w*|access|unauthorized|forbidden|401|403)\b/;
const MISSING_PATTERN =
/\b(unset|missing|required|requires|without|omit\w*|not set|not available|no read packages|read packages)\b/;
const GHCR_PATTERN =
/\b(ghcr|github container registry|read packages|imagepullsecret|package api|anonymous|container image)\b/;
const GHCR_401_PATTERN = /\b(401|unauthorized|anonymous pull|authentication required)\b/;
const GHCR_403_PATTERN = /\b(403|forbidden|read packages|package api)\b/;
function invalid(message: string, field: string): never {
throw new UlwLoopError(message, "ULW_LOOP_QUALITY_GATE_INVALID", { details: { field } });
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function section(value: unknown, field: string): Record<string, unknown> {
return isRecord(value) ? value : invalid(`Final quality gate is missing ${field} evidence.`, field);
}
function nonEmptyString(value: unknown, field: string): string {
return typeof value === "string" && value.trim() !== ""
? value
: invalid(`Final quality gate requires non-empty ${field}.`, field);
}
function numberField(value: unknown, field: string): number {
return typeof value === "number" && Number.isFinite(value)
? value
: invalid(`Final quality gate requires numeric ${field}.`, field);
}
function stringArray(value: unknown, field: string): string[] {
if (!Array.isArray(value) || value.length === 0) return invalid(`Final quality gate requires ${field}.`, field);
return value.map((item) => nonEmptyString(item, field));
}
export function validateQualityGate(input: unknown): UlwLoopQualityGate {
const gate = section(input, "qualityGate");
const cleaner = section(gate["aiSlopCleaner"], "aiSlopCleaner");
const verification = section(gate["verification"], "verification");
const review = section(gate["codeReview"], "codeReview");
const coverage = section(gate["criteriaCoverage"], "criteriaCoverage");
if (cleaner["status"] !== "passed") invalid("aiSlopCleaner.status must be passed.", "aiSlopCleaner.status");
if (verification["status"] !== "passed") invalid("verification.status must be passed.", "verification.status");
if (review["recommendation"] !== "APPROVE") invalid("recommendation must be APPROVE.", "codeReview.recommendation");
if (review["architectStatus"] !== "CLEAR") invalid("architectStatus must be CLEAR.", "codeReview.architectStatus");
const totalCriteria = numberField(coverage["totalCriteria"], "criteriaCoverage.totalCriteria");
const passCount = numberField(coverage["passCount"], "criteriaCoverage.passCount");
if (passCount < totalCriteria)
invalid("criteriaCoverage.passCount must cover totalCriteria.", "criteriaCoverage.passCount");
const commands = stringArray(verification["commands"], "verification.commands");
const covered = stringArray(coverage["adversarialClassesCovered"], "criteriaCoverage.adversarialClassesCovered");
const cleanerEvidence = nonEmptyString(cleaner["evidence"], "aiSlopCleaner.evidence");
const verificationEvidence = nonEmptyString(verification["evidence"], "verification.evidence");
const reviewEvidence = nonEmptyString(review["evidence"], "codeReview.evidence");
const result: UlwLoopQualityGate = {
aiSlopCleaner: { status: "passed", evidence: cleanerEvidence },
verification: { status: "passed", commands, evidence: verificationEvidence },
codeReview: { recommendation: "APPROVE", architectStatus: "CLEAR", evidence: reviewEvidence },
};
Object.assign(result, { criteriaCoverage: { totalCriteria, passCount, adversarialClassesCovered: covered } });
return result;
}
export function normalizeBlockerEvidence(evidence: string): string {
const withoutUrls = evidence.toLowerCase().replace(URL_PATTERN, " ");
const withoutPunctuation = withoutUrls.replace(PUNCTUATION_PATTERN, " ");
return withoutPunctuation.replace(WHITESPACE_PATTERN, " ").trim();
}
export function classifyExternalAuthorizationBlocker(evidence: string): string | null {
const normalized = normalizeBlockerEvidence(evidence);
if (!normalized || !AUTH_PATTERN.test(normalized) || !MISSING_PATTERN.test(normalized)) return null;
if (!GHCR_PATTERN.test(normalized)) return "EXTERNAL_AUTHORIZATION_REQUIRED";
const status401 = GHCR_401_PATTERN.test(normalized) ? "HTTP_401_ANONYMOUS" : null;
const status403 = GHCR_403_PATTERN.test(normalized) ? "HTTP_403_NO_READ_PACKAGES" : null;
const status = [status401, status403].filter((part): part is string => part !== null).join("+");
return `GHCR_PULL_ACCESS:${status || "AUTHORIZATION_REQUIRED"}:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED`;
}
function nestedBlockerSignature(goal: UlwLoopItem): string | null {
const blocker = Reflect.get(goal, "blocker");
const signature = isRecord(blocker) ? blocker["signature"] : null;
return typeof signature === "string" ? signature : null;
}
export function sameBlockerOccurrences(plan: UlwLoopPlan, signature: string): number {
return plan.goals.filter((goal) => goal.blockerSignature === signature || nestedBlockerSignature(goal) === signature)
.length;
}
export function clearGoalBlockerFields(goal: UlwLoopItem): void {
for (const key of BLOCKER_FIELD_KEYS) Reflect.deleteProperty(goal, key);
}
@@ -0,0 +1,79 @@
// biome-ignore-all format: compact port must stay within the requested pure LOC budget.
import { readCodexGoalSnapshotInput, reconcileCodexGoalSnapshot } from "./codex-goal-snapshot.js";
import { codexGoalMode, compatibleCodexObjectives, expectedCodexObjective, isFinalRunCompletionCandidate } from "./goal-status.js";
import { seedDefaultSuccessCriteria } from "./plan-crud.js";
import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan } from "./types.js";
import { iso, UlwLoopError } from "./types.js";
export interface RecordFinalReviewBlockersArgs { readonly goalId: string; readonly title: string; readonly objective: string; readonly evidence: string; readonly codexGoalJson: string }
export interface RecordFinalReviewBlockersResult { readonly plan: UlwLoopPlan; readonly blockedGoal: UlwLoopItem; readonly newGoal: UlwLoopItem; readonly ledgerEntries: UlwLoopLedgerEntry[] }
const BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
function ulwLoopError(message: string, code: string): never {
throw new UlwLoopError(message, code);
}
function nextGoalId(plan: UlwLoopPlan): string {
const max = plan.goals.reduce((current, goal) => {
const digits = /^G(\d+)/u.exec(goal.id)?.[1];
return digits === undefined ? current : Math.max(current, Number(digits));
}, 0);
return `G${String(max + 1).padStart(3, "0")}`;
}
function appendBlockerGoal(plan: UlwLoopPlan, args: RecordFinalReviewBlockersArgs, now: string): UlwLoopItem {
const index = plan.goals.length;
const goal: UlwLoopItem = {
id: nextGoalId(plan),
title: args.title,
objective: args.objective,
status: "pending",
successCriteria: seedDefaultSuccessCriteria(index, args.objective),
attempt: 0,
createdAt: now,
updatedAt: now,
};
plan.goals.push(goal);
return goal;
}
export async function recordFinalReviewBlockers(
repoRoot: string,
args: RecordFinalReviewBlockersArgs,
): Promise<RecordFinalReviewBlockersResult> {
return withUlwLoopMutationLock(repoRoot, async () => {
const plan = await readUlwLoopPlan(repoRoot);
const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
if (goal === undefined) ulwLoopError(`Unknown ulw-loop id: ${args.goalId}`, "ulw_loop_goal_not_found");
if (goal.status !== "in_progress") ulwLoopError(`${goal.id} is ${goal.status}.`, "ulw_loop_goal_not_in_progress");
if (!isFinalRunCompletionCandidate(plan, goal)) ulwLoopError(`${goal.id} is not final.`, "ulw_loop_not_final_story");
const snapshot = await readCodexGoalSnapshotInput(args.codexGoalJson, repoRoot);
const aggregate = codexGoalMode(plan) === "aggregate";
const reconciliation = reconcileCodexGoalSnapshot(snapshot, { expectedObjective: expectedCodexObjective(plan, goal), ...(aggregate ? { acceptedObjectives: compatibleCodexObjectives(plan) } : {}), allowedStatuses: ["active"], requireSnapshot: true, requireComplete: false });
if (!reconciliation.ok) ulwLoopError(reconciliation.errors.join(" "), "ulw_loop_codex_snapshot_mismatch");
const now = iso();
for (const field of BLOCKER_FIELDS) Reflect.deleteProperty(goal, field);
goal.status = "review_blocked";
goal.reviewBlockedAt = now;
goal.evidence = args.evidence;
goal.updatedAt = now;
if (plan.activeGoalId === goal.id) delete plan.activeGoalId;
const newGoal = appendBlockerGoal(plan, args, now);
plan.updatedAt = now;
const codexGoal = reconciliation.snapshot.raw;
const blockedEntry: UlwLoopLedgerEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
const addedEntry: UlwLoopLedgerEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
const summaryEntry: UlwLoopLedgerEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal, message: `Review blockers recorded; appended ${newGoal.id}.` };
Reflect.set(summaryEntry, "kind", "blocker_recorded");
const ledgerEntries = [blockedEntry, addedEntry, summaryEntry];
await writePlan(repoRoot, plan);
for (const entry of ledgerEntries) await appendLedger(repoRoot, entry);
return { plan, blockedGoal: goal, newGoal, ledgerEntries };
});
}
@@ -0,0 +1,265 @@
// biome-ignore-all format: compact steering module must stay below the 240 pure-LOC budget
import { isUlwLoopDone } from "./goal-status.js";
import { appendLedger, readSteeringLedgerEntries, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
import type {
SteerUlwLoopResult,
UlwLoopItem,
UlwLoopLedgerEntry,
UlwLoopPlan,
UlwLoopSteeringAudit,
UlwLoopSteeringChildGoal,
UlwLoopSteeringMutationKind,
UlwLoopSteeringProposal,
UlwLoopSteeringSource,
UlwLoopSuccessCriterionUserModel,
} from "./types.js";
import { iso, ULW_LOOP_STEERING_MUTATION_KINDS, ULW_LOOP_SUCCESS_CRITERION_USER_MODELS } from "./types.js";
const SOURCES = ["user_prompt_submit", "finding", "cli"] as const satisfies readonly UlwLoopSteeringSource[];
const PROTECTED = new Set(["aggregateCompletion", "codexObjective", "codexObjectiveAliases", "originalConstraints", "qualityGate", "status", "completedAt", "completionStatus"]);
const isObject = (value: unknown): value is object => typeof value === "object" && value !== null; const isPlain = (value: unknown): value is object => isObject(value) && !Array.isArray(value);
const read = (value: object, key: string): unknown => Object.entries(value).find(([name]) => name === key)?.[1];
const isText = (value: unknown): value is string => typeof value === "string" && value.trim().length > 0;
const text = (value: object, key: string): string | undefined => {
const candidate = read(value, key);
return isText(candidate) ? candidate.trim() : undefined;
};
const isKind = (value: unknown): value is UlwLoopSteeringMutationKind => typeof value === "string" && ULW_LOOP_STEERING_MUTATION_KINDS.some((kind) => kind === value);
const isSource = (value: unknown): value is UlwLoopSteeringSource => typeof value === "string" && SOURCES.some((source) => source === value);
const isModel = (value: unknown): value is UlwLoopSuccessCriterionUserModel => typeof value === "string" && ULW_LOOP_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value);
const texts = (value: object, key: string): string[] => {
const candidate = read(value, key);
return Array.isArray(candidate) && candidate.every((item) => typeof item === "string") ? candidate : [];
};
function targets(proposal: object): string[] {
const many = texts(proposal, "targetGoalIds");
const one = text(proposal, "targetGoalId") ?? text(proposal, "goalId");
return many.length > 0 ? many : one === undefined ? [] : [one];
}
const after = (proposal: object): object | undefined => {
const candidate = read(proposal, "after");
return isPlain(candidate) ? candidate : undefined;
};
const revised = (proposal: object, direct: string, nested: string): string | undefined => text(proposal, direct) ?? text(after(proposal) ?? proposal, nested);
function child(value: unknown): UlwLoopSteeringChildGoal | null {
if (!isPlain(value)) return null;
const title = text(value, "title");
const objective = text(value, "objective");
if (title === undefined || objective === undefined) return null;
return { title, objective };
}
function childValues(proposal: object): unknown[] {
const direct = read(proposal, "childGoals");
if (Array.isArray(direct) && direct.length > 0) return direct;
const nested = after(proposal);
const fromAfter = nested === undefined ? undefined : read(nested, "children");
return Array.isArray(fromAfter) ? fromAfter : [];
}
const children = (proposal: object): UlwLoopSteeringChildGoal[] => childValues(proposal).map(child).filter((item): item is UlwLoopSteeringChildGoal => item !== null);
const pendingOrder = (proposal: object): string[] => {
const direct = texts(proposal, "pendingOrder");
return direct.length > 0 ? direct : texts(after(proposal) ?? proposal, "pendingGoalIds");
};
function hasProtected(value: unknown): boolean {
if (!isObject(value)) return false;
for (const [key, childValue] of Object.entries(value)) if (PROTECTED.has(key) || key.toLowerCase().includes("complete") || hasProtected(childValue)) return true;
return false;
}
function allText(value: unknown): string {
if (typeof value === "string") return value;
return isObject(value) ? Object.values(value).map(allText).filter(Boolean).join("\n") : "";
}
function weakens(value: unknown): boolean {
const valueText = allText(value).toLowerCase();
return /\b(skip|bypass|weaken|remove|omit|auto[-\s]?complete|mark complete|complete faster)\b/.test(valueText) && /\b(test|tests|verification|review|quality gate|complete|completion)\b/.test(valueText);
}
function auditFor(proposal: unknown, reasons: string[]): UlwLoopSteeringAudit {
const object = isPlain(proposal) ? proposal : undefined;
const kindRaw = object === undefined ? undefined : read(object, "kind");
const sourceRaw = object === undefined ? undefined : read(object, "source");
const evidence = object === undefined ? "" : (text(object, "evidence") ?? "");
const rationale = object === undefined ? "" : (text(object, "rationale") ?? "");
const audit: UlwLoopSteeringAudit = { kind: isKind(kindRaw) ? kindRaw : "annotate_ledger", source: isSource(sourceRaw) ? sourceRaw : "cli", targetGoalIds: object === undefined ? [] : targets(object), evidence, rationale, invariant: { accepted: reasons.length === 0, structuralInvariantAccepted: reasons.length === 0, evidenceBackedNecessity: evidence.length > 0 && rationale.length > 0, noEasierCompletion: !weakens(proposal), rejectedReasons: reasons, reasons } };
if (object === undefined) return audit;
const criterionId = text(object, "criterionId");
const directiveText = text(object, "directiveText");
const promptSignature = text(object, "promptSignature");
const idempotencyKey = text(object, "idempotencyKey");
if (criterionId !== undefined) audit.criterionId = criterionId;
if (directiveText !== undefined) audit.directiveText = directiveText;
if (promptSignature !== undefined) audit.promptSignature = promptSignature;
if (idempotencyKey !== undefined) audit.idempotencyKey = idempotencyKey;
return audit;
}
export function validateUlwLoopSteeringProposal(plan: UlwLoopPlan, proposal: unknown): UlwLoopSteeringAudit {
const reasons: string[] = [];
if (!isPlain(proposal)) reasons.push("proposal must be an object");
const object = isPlain(proposal) ? proposal : {};
const kind = read(object, "kind");
if (!isKind(kind)) reasons.push(`invalid kind: ${String(kind)}`);
if (!isSource(read(object, "source"))) reasons.push(`invalid source: ${String(read(object, "source"))}`);
if (text(object, "evidence") === undefined) reasons.push("missing evidence");
if (text(object, "rationale") === undefined) reasons.push("missing rationale");
if (hasProtected(proposal)) reasons.push("protected payload");
if (weakens(proposal)) reasons.push("weakened completion");
if (isUlwLoopDone(plan)) reasons.push("plan already complete");
if (isKind(kind)) validateKind(plan, object, kind, reasons);
return auditFor(proposal, reasons);
}
function goal(plan: UlwLoopPlan, id: string | undefined): UlwLoopItem | undefined {
return id === undefined ? undefined : plan.goals.find((item) => item.id === id);
}
function validateKind(plan: UlwLoopPlan, proposal: object, kind: UlwLoopSteeringMutationKind, reasons: string[]): void {
const target = goal(plan, targets(proposal)[0]);
if (kind === "add_subgoal" && (text(proposal, "title") === undefined || text(proposal, "objective") === undefined)) reasons.push("add_subgoal requires title/objective");
if ((kind === "split_subgoal" || kind === "revise_pending_wording" || kind === "mark_blocked_superseded") && target === undefined) reasons.push(`${kind} requires target`);
if ((kind === "split_subgoal" || kind === "revise_pending_wording") && target !== undefined && target.status !== "pending") reasons.push(`${kind} requires pending target`);
const rawChildren = childValues(proposal);
if (kind === "split_subgoal" && rawChildren.length === 0) reasons.push("split_subgoal requires children");
if ((kind === "split_subgoal" || kind === "mark_blocked_superseded") && rawChildren.some((item) => child(item) === null)) reasons.push(`${kind} children require title/objective`);
if (kind === "reorder_pending") validateOrder(plan, proposal, reasons);
if (kind === "revise_pending_wording" && revised(proposal, "revisedTitle", "title") === undefined && revised(proposal, "revisedObjective", "objective") === undefined) reasons.push("revise_pending_wording requires update");
if (kind === "revise_criterion") validateCriterion(plan, proposal, reasons);
}
function validateOrder(plan: UlwLoopPlan, proposal: object, reasons: string[]): void {
const requested = pendingOrder(proposal);
const pending = plan.goals.filter((item) => item.status === "pending" && item.steeringStatus === undefined).map((item) => item.id);
if (requested.length === 0) reasons.push("reorder_pending requires ids");
if (new Set(requested).size !== requested.length) reasons.push("duplicate pending id");
if (requested.some((id) => !pending.includes(id))) reasons.push("unknown pending id");
}
function validateCriterion(plan: UlwLoopPlan, proposal: object, reasons: string[]): void {
const target = goal(plan, targets(proposal)[0]);
const criterionId = text(proposal, "criterionId");
if (target === undefined) reasons.push("revise_criterion requires goalId");
else if (criterionId === undefined || target.successCriteria.every((item) => item.id !== criterionId)) reasons.push("revise_criterion requires criterionId");
const model = read(proposal, "userModel");
if (read(proposal, "scenario") === undefined && read(proposal, "expectedEvidence") === undefined && model === undefined) reasons.push("revise_criterion requires update");
if (model !== undefined && !isModel(model)) reasons.push("invalid userModel");
}
function nextId(plan: UlwLoopPlan, offset: number): string {
const max = plan.goals.reduce((current, item) => {
const digits = /^G(\d+)$/u.exec(item.id)?.[1];
return digits === undefined ? current : Math.max(current, Number(digits));
}, 0);
return `G${String(max + offset).padStart(3, "0")}`;
}
function makeGoal(plan: UlwLoopPlan, childGoal: UlwLoopSteeringChildGoal, evidence: string, now: string, offset: number): UlwLoopItem {
return { id: nextId(plan, offset), title: childGoal.title, objective: childGoal.objective, status: "pending", successCriteria: [], attempt: 0, createdAt: now, updatedAt: now, evidence };
}
export function applySteeringMutation(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, audit: UlwLoopSteeringAudit): UlwLoopPlan {
const next = structuredClone(plan);
if (!audit.invariant.accepted) return next;
const now = proposal.now?.toISOString() ?? iso();
if (proposal.kind === "add_subgoal") next.goals.push(makeGoal(next, { title: proposal.title ?? "", objective: proposal.objective ?? "" }, proposal.evidence, now, 1));
if (proposal.kind === "reorder_pending") {
const order = pendingOrder(proposal);
next.goals = [...order.map((id) => goal(next, id)).filter((item): item is UlwLoopItem => item !== undefined), ...next.goals.filter((item) => !order.includes(item.id))];
}
if (proposal.kind === "revise_pending_wording") reviseWording(next, proposal, now);
if (proposal.kind === "split_subgoal" || proposal.kind === "mark_blocked_superseded") splitOrBlock(next, proposal, now);
if (proposal.kind === "revise_criterion") reviseCriterion(next, proposal, now);
if (proposal.kind !== "annotate_ledger") next.updatedAt = now;
return next;
}
function reviseWording(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void {
const target = goal(plan, targets(proposal)[0]);
if (target === undefined) return;
target.title = revised(proposal, "revisedTitle", "title") ?? target.title;
target.objective = revised(proposal, "revisedObjective", "objective") ?? target.objective;
target.steeringEvidence = proposal.evidence;
target.steeringRationale = proposal.rationale;
target.updatedAt = now;
}
function splitOrBlock(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void {
const target = goal(plan, targets(proposal)[0]);
if (target === undefined) return;
const replacements = children(proposal).map((item, index) => makeGoal(plan, item, proposal.evidence, now, index + 1));
target.steeringEvidence = proposal.evidence;
target.steeringRationale = proposal.rationale;
target.updatedAt = now;
if (replacements.length === 0) {
target.status = "blocked";
target.steeringStatus = "blocked";
target.blockedReason = proposal.blockedReason ?? proposal.rationale;
} else {
target.steeringStatus = "superseded";
target.supersededBy = replacements.map((item) => item.id);
for (const item of replacements) item.supersedes = [target.id];
plan.goals.splice(plan.goals.indexOf(target) + 1, 0, ...replacements);
}
if (plan.activeGoalId === target.id) delete plan.activeGoalId;
}
function reviseCriterion(plan: UlwLoopPlan, proposal: UlwLoopSteeringProposal, now: string): void {
const target = goal(plan, targets(proposal)[0]);
const index = target?.successCriteria.findIndex((item) => item.id === proposal.criterionId) ?? -1;
const current = target?.successCriteria[index];
if (target === undefined || current === undefined) return;
const model = read(proposal, "userModel");
target.successCriteria[index] = { ...current, scenario: text(proposal, "scenario") ?? current.scenario, expectedEvidence: text(proposal, "expectedEvidence") ?? current.expectedEvidence, userModel: isModel(model) ? model : current.userModel };
target.updatedAt = now;
}
function isProposal(value: unknown): value is UlwLoopSteeringProposal {
return isPlain(value) && isKind(read(value, "kind")) && isSource(read(value, "source")) && isText(read(value, "evidence")) && isText(read(value, "rationale"));
}
export function parseUlwLoopSteeringDirective(text: string): UlwLoopSteeringProposal | null {
const match = /(?:^|\s)(?:OMO_ULW_LOOP_STEER|omo\.ulw-loop\.steer|omo ulw-loop steer):\s*([\s\S]+)$/u.exec(text);
if (match?.[1] === undefined) return null;
try {
const parsed: unknown = JSON.parse(match[1].trim());
return isProposal(parsed) ? parsed : null;
} catch (error) {
if (error instanceof SyntaxError) return null;
throw error;
}
}
export async function steerUlwLoop(repoRoot: string, proposal: UlwLoopSteeringProposal): Promise<SteerUlwLoopResult> {
return withUlwLoopMutationLock(repoRoot, async () => {
const plan = await readUlwLoopPlan(repoRoot);
const key = proposal.idempotencyKey ?? proposal.promptSignature;
const prior = key === undefined ? undefined : (await readSteeringLedgerEntries(repoRoot)).find((entry) => entry.steering?.invariant.accepted === true && (entry.idempotencyKey === key || entry.steering.idempotencyKey === key || entry.steering.promptSignature === key));
if (prior?.steering !== undefined) return { plan, accepted: true, audit: { ...prior.steering, deduped: true }, rejectedReasons: [], deduped: true };
const audit = validateUlwLoopSteeringProposal(plan, proposal);
const accepted = audit.invariant.accepted;
const next = accepted ? applySteeringMutation(plan, proposal, audit) : plan;
const finalAudit: UlwLoopSteeringAudit = { ...audit, before: plan };
if (accepted) finalAudit.after = next;
if (accepted) await writePlan(repoRoot, next);
await appendLedger(repoRoot, ledgerEntry(proposal, finalAudit, proposal.now?.toISOString() ?? iso()));
return { plan: next, accepted, audit: finalAudit, rejectedReasons: audit.invariant.rejectedReasons, deduped: false };
});
}
function ledgerEntry(proposal: UlwLoopSteeringProposal, audit: UlwLoopSteeringAudit, at: string): UlwLoopLedgerEntry {
const entry: UlwLoopLedgerEntry = { at, kind: audit.invariant.accepted ? (proposal.kind === "revise_criterion" ? "criteria_revised" : "steering_accepted") : "steering_rejected", evidence: proposal.evidence, message: proposal.rationale, steering: audit, mutationKind: proposal.kind };
const goalId = audit.targetGoalIds[0];
if (goalId !== undefined) entry.goalId = goalId;
if (proposal.criterionId !== undefined) entry.criterionId = proposal.criterionId;
if (proposal.idempotencyKey !== undefined) entry.idempotencyKey = proposal.idempotencyKey;
if (audit.before !== undefined) entry.before = audit.before;
if (audit.after !== undefined) entry.after = audit.after;
return entry;
}
@@ -0,0 +1,277 @@
export const ULW_LOOP_DIR = ".omo/ulw-loop";
export const ULW_LOOP_BRIEF = "brief.md";
export const ULW_LOOP_GOALS = "goals.json";
export const ULW_LOOP_LEDGER = "ledger.jsonl";
export type UlwLoopStatus =
| "pending"
| "in_progress"
| "complete"
| "failed"
| "blocked"
| "review_blocked"
| "needs_user_decision";
export type UlwLoopCodexGoalMode = "aggregate" | "per_story";
export type UlwLoopSteeringStatus = "superseded" | "blocked";
export const ULW_LOOP_STEERING_MUTATION_KINDS = [
"add_subgoal",
"split_subgoal",
"reorder_pending",
"revise_pending_wording",
"revise_criterion",
"annotate_ledger",
"mark_blocked_superseded",
] as const satisfies readonly string[];
export type UlwLoopSteeringMutationKind = (typeof ULW_LOOP_STEERING_MUTATION_KINDS)[number];
export type UlwLoopSteeringSource = "user_prompt_submit" | "finding" | "cli";
export const ULW_LOOP_SUCCESS_CRITERION_USER_MODELS = [
"happy",
"edge",
"regression",
"adversarial",
] as const satisfies readonly string[];
export type UlwLoopSuccessCriterionUserModel = (typeof ULW_LOOP_SUCCESS_CRITERION_USER_MODELS)[number];
export const ULW_LOOP_CRITERION_STATUSES = ["pending", "pass", "fail", "blocked"] as const satisfies readonly string[];
export type UlwLoopCriterionStatus = (typeof ULW_LOOP_CRITERION_STATUSES)[number];
export const ULW_LOOP_LEDGER_EVENT_KINDS = [
"plan_created",
"goal_started",
"goal_resumed",
"goal_completed",
"goal_blocked",
"goal_failed",
"goal_needs_user_decision",
"goal_retried",
"aggregate_completed",
"aggregate_objective_migrated",
"goal_added",
"steering_accepted",
"steering_rejected",
"final_review_failed",
"goal_review_blocked",
"evidence_captured",
"criterion_failed",
"criterion_blocked",
"criteria_revised",
] as const satisfies readonly string[];
export type UlwLoopLedgerEventKind = (typeof ULW_LOOP_LEDGER_EVENT_KINDS)[number];
export interface UlwLoopSuccessCriterion {
readonly id: string;
readonly scenario: string;
readonly userModel: UlwLoopSuccessCriterionUserModel;
readonly expectedEvidence: string;
capturedEvidence: string | null;
status: UlwLoopCriterionStatus;
capturedAt?: string;
notes?: string;
}
export interface UlwLoopSteeringInvariantResult {
accepted: boolean;
structuralInvariantAccepted: boolean;
evidenceBackedNecessity: boolean;
noEasierCompletion: boolean;
rejectedReasons: string[];
reasons?: string[];
}
export interface UlwLoopSteeringChildGoal {
title: string;
objective: string;
}
export interface UlwLoopSteeringAfterPayload {
title?: string;
objective?: string;
pendingGoalIds?: string[];
children?: UlwLoopSteeringChildGoal[];
}
export interface UlwLoopSteeringProposal {
kind: UlwLoopSteeringMutationKind;
source: UlwLoopSteeringSource;
targetGoalId?: string;
targetGoalIds?: string[];
criterionId?: string;
evidence: string;
rationale: string;
title?: string;
objective?: string;
childGoals?: UlwLoopSteeringChildGoal[];
revisedTitle?: string;
revisedObjective?: string;
pendingOrder?: string[];
blockedReason?: string;
after?: UlwLoopSteeringAfterPayload;
directiveText?: string;
promptSignature?: string;
idempotencyKey?: string;
now?: Date;
}
export interface UlwLoopSteeringAudit {
kind: UlwLoopSteeringMutationKind;
source: UlwLoopSteeringSource;
targetGoalIds: string[];
criterionId?: string;
before?: unknown;
after?: unknown;
evidence: string;
rationale: string;
invariant: UlwLoopSteeringInvariantResult;
directiveText?: string;
promptSignature?: string;
idempotencyKey?: string;
deduped?: boolean;
}
export interface SteerUlwLoopResult {
plan: UlwLoopPlan;
accepted: boolean;
audit: UlwLoopSteeringAudit;
rejectedReasons: string[];
deduped: boolean;
}
export interface UlwLoopItem {
id: string;
title: string;
objective: string;
status: UlwLoopStatus;
successCriteria: UlwLoopSuccessCriterion[];
attempt: number;
createdAt: string;
updatedAt: string;
startedAt?: string;
completedAt?: string;
failedAt?: string;
reviewBlockedAt?: string;
evidence?: string;
failureReason?: string;
steeringStatus?: UlwLoopSteeringStatus;
supersededBy?: string[];
supersedes?: string[];
blockedReason?: string;
blockerSignature?: string;
blockerOccurrenceCount?: number;
requiredExternalDecision?: string;
nonRetriable?: boolean;
steeringEvidence?: string;
steeringRationale?: string;
}
export interface UlwLoopAggregateCompletion {
status: "complete";
completedAt: string;
evidence: string;
codexGoal?: unknown;
}
export interface UlwLoopPlan {
version: 1;
createdAt: string;
updatedAt: string;
briefPath: string;
goalsPath: string;
ledgerPath: string;
codexGoalMode?: UlwLoopCodexGoalMode;
codexObjective?: string;
codexObjectiveAliases?: string[];
aggregateCompletion?: UlwLoopAggregateCompletion;
activeGoalId?: string;
goals: UlwLoopItem[];
}
export interface UlwLoopLedgerEntry {
at: string;
kind: UlwLoopLedgerEventKind;
goalId?: string;
criterionId?: string;
status?: UlwLoopStatus;
criterionStatus?: UlwLoopCriterionStatus;
message?: string;
codexGoal?: unknown;
evidence?: string;
capturedEvidence?: string;
qualityGate?: UlwLoopQualityGate;
steering?: UlwLoopSteeringAudit;
before?: unknown;
after?: unknown;
mutationKind?: UlwLoopSteeringMutationKind;
idempotencyKey?: string;
blockerSignature?: string;
blockerOccurrenceCount?: number;
requiredExternalDecision?: string;
}
export interface CreateUlwLoopOptions {
brief: string;
goals?: Array<{ title?: string; objective: string }>;
codexGoalMode?: UlwLoopCodexGoalMode;
now?: Date;
force?: boolean;
}
export interface StartNextOptions {
now?: Date;
retryFailed?: boolean;
}
export interface CheckpointOptions {
goalId: string;
status: Extract<UlwLoopStatus, "complete" | "failed"> | "blocked";
evidence?: string;
codexGoal?: unknown;
qualityGate?: unknown;
allowActiveFinalCodexGoal?: boolean;
now?: Date;
}
export interface AddUlwLoopGoalOptions {
title: string;
objective: string;
evidence?: string;
now?: Date;
}
export interface RecordFinalReviewBlockersOptions extends AddUlwLoopGoalOptions {
goalId: string;
codexGoal?: unknown;
}
export interface UlwLoopQualityGate {
aiSlopCleaner: { status: "passed"; evidence: string };
verification: { status: "passed"; commands: string[]; evidence: string };
codeReview: { recommendation: "APPROVE"; architectStatus: "CLEAR"; evidence: string };
}
export interface UlwLoopErrorOptions {
readonly cause?: unknown;
readonly details?: Record<string, unknown>;
}
export class UlwLoopError extends Error {
readonly code: string;
readonly details?: Record<string, unknown>;
constructor(message: string, code: string, opts?: UlwLoopErrorOptions) {
super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
this.name = "UlwLoopError";
this.code = code;
if (opts?.details !== undefined) {
this.details = opts.details;
}
}
}
export function iso(): string {
return new Date().toISOString();
}
@@ -0,0 +1,213 @@
// biome-ignore-all format: keep the single mandated checkpoint spec under the pure LOC budget.
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { checkpointUlwLoop } from "../src/checkpoint.js";
import { ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE } from "../src/goal-status.js";
import { ulwLoopBriefPath, ulwLoopDir, ulwLoopLedgerPath } from "../src/paths.js";
import { writePlan } from "../src/plan-io.js";
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
const QUALITY_GATE_PATH = join(process.cwd(), "test", "fixtures", "sample-quality-gate.json");
function criterion(id: string, status: UlwLoopSuccessCriterion["status"]): UlwLoopSuccessCriterion {
return { id, scenario: `${id} scenario`, userModel: "happy", expectedEvidence: `${id} proof`, capturedEvidence: status === "pass" ? `${id} passed` : null, status };
}
function goal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return { id: "G001", title: "Build auth", objective: "Implement JWT auth endpoint", status: "in_progress", successCriteria: [criterion("C001", "pass"), criterion("C002", "pass"), criterion("C003", "pass")], attempt: 1, createdAt: NOW, updatedAt: NOW, ...overrides };
}
function plan(goals: UlwLoopItem[], overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
const result: UlwLoopPlan = { version: 1, createdAt: NOW, updatedAt: NOW, briefPath: ".omo/ulw-loop/brief.md", goalsPath: ".omo/ulw-loop/goals.json", ledgerPath: ".omo/ulw-loop/ledger.jsonl", codexGoalMode: "aggregate", codexObjective: ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE, goals };
Object.assign(result, overrides);
const activeGoalId = goals.find((candidate) => candidate.status === "in_progress")?.id;
if (result.activeGoalId === undefined && activeGoalId !== undefined) result.activeGoalId = activeGoalId;
return result;
}
async function samplePlan(overrides: Partial<UlwLoopPlan> = {}): Promise<UlwLoopPlan> {
const fixture: UlwLoopPlan = JSON.parse(await readFile(new URL("./fixtures/sample-plan.json", import.meta.url), "utf8"));
return plan(fixture.goals.map((item, index) => goal({ ...item, attempt: index + 1, createdAt: NOW, updatedAt: NOW })), overrides);
}
async function repoWith(seed: UlwLoopPlan): Promise<string> {
const repo = await mkdtemp(join(tmpdir(), "ug-checkpoint-"));
await mkdir(ulwLoopDir(repo), { recursive: true });
await writePlan(repo, seed);
return repo;
}
function snapshot(status: "active" | "complete", objective = ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE): string {
return JSON.stringify({ goal: { objective, status } });
}
async function lastLedger(repo: string): Promise<UlwLoopLedgerEntry> {
const last = (await readFile(ulwLoopLedgerPath(repo), "utf8")).trim().split(/\r?\n/).at(-1);
if (last === undefined) throw new Error("expected ledger entry");
const entry: UlwLoopLedgerEntry = JSON.parse(last);
return entry;
}
async function expectCode(action: () => Promise<unknown>, code: string): Promise<void> {
try {
await action();
} catch (error) {
expect(error).toBeInstanceOf(UlwLoopError);
if (!(error instanceof UlwLoopError)) throw error;
expect(error.code).toBe(code);
return;
}
throw new Error("Expected UlwLoopError");
}
function passGoal(id: string, overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return goal({ id, successCriteria: [criterion("C001", "pass"), criterion("C002", "pass"), criterion("C003", "pass")], ...overrides });
}
describe("checkpointUlwLoop status=complete criteria gate", () => {
it("THROWS ulw_loop_criteria_not_all_pass when any criterion is pending", async () => {
const repo = await repoWith(await samplePlan({ goals: [goal({ successCriteria: [criterion("C001", "pass"), criterion("C002", "pending"), criterion("C003", "pass")] })] }));
await expectCode(() => checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "done" }), "ulw_loop_criteria_not_all_pass");
});
it("THROWS when any criterion is fail or blocked", async () => {
for (const status of ["fail", "blocked"] satisfies UlwLoopSuccessCriterion["status"][]) {
const repo = await repoWith(plan([goal({ successCriteria: [criterion("C001", "pass"), criterion("C002", status), criterion("C003", "pass")] })]));
await expectCode(() => checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "done" }), "ulw_loop_criteria_not_all_pass");
}
});
it("THROWS when criteria list is empty", async () => {
const repo = await repoWith(plan([goal({ successCriteria: [] }), goal({ id: "G002", status: "pending" })]));
await expectCode(() => checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "done", codexGoalJson: snapshot("active") }), "ulw_loop_criteria_not_all_pass");
});
it("ACCEPTS complete when ALL criteria pass (with valid snapshot)", async () => {
const repo = await repoWith(plan([passGoal("G001"), goal({ id: "G002", status: "pending" })]));
const result = await checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "implementation done and tests passed", codexGoalJson: snapshot("active") });
expect(result.goal.status).toBe("complete");
expect((await lastLedger(repo)).kind).toBe("goal_completed");
});
});
describe("checkpointUlwLoop reconciliation (status=complete)", () => {
it("succeeds when snapshot objective matches expected (aggregate active)", async () => {
const repo = await repoWith(plan([passGoal("G001"), goal({ id: "G002", status: "pending" })]));
await expect(checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "work complete and validation passed", codexGoalJson: snapshot("active") })).resolves.toMatchObject({ goal: { status: "complete" } });
});
it("throws on mismatched objective", async () => {
const repo = await repoWith(plan([passGoal("G001"), goal({ id: "G002", status: "pending" })]));
await expectCode(() => checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "work complete and validation passed", codexGoalJson: snapshot("active", "wrong objective") }), "ulw_loop_codex_snapshot_mismatch");
});
it("throws on mismatched status (snapshot complete when expected active)", async () => {
const repo = await repoWith(plan([passGoal("G001"), goal({ id: "G002", status: "pending" })]));
await expectCode(() => checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "work complete and validation passed", codexGoalJson: snapshot("complete") }), "ulw_loop_codex_snapshot_mismatch");
});
});
describe("checkpointUlwLoop final story", () => {
it("requires quality-gate-json for the final goal complete", async () => {
const repo = await repoWith(plan([passGoal("G001", { status: "complete" }), passGoal("G002")], { activeGoalId: "G002" }));
await expectCode(() => checkpointUlwLoop(repo, { goalId: "G002", status: "complete", evidence: "final work complete and validation passed", codexGoalJson: snapshot("complete") }), "ULW_LOOP_QUALITY_GATE_INVALID");
});
it("accepts final story when quality gate JSON includes valid criteriaCoverage", async () => {
const repo = await repoWith(plan([passGoal("G001", { status: "complete" }), passGoal("G002")], { activeGoalId: "G002" }));
const result = await checkpointUlwLoop(repo, { goalId: "G002", status: "complete", evidence: "final work complete and validation passed", codexGoalJson: snapshot("complete"), qualityGateJson: QUALITY_GATE_PATH });
expect(result.aggregateCompletion?.status).toBe("complete");
expect(result.plan.aggregateCompletion?.status).toBe("complete");
});
it("ACCEPTS complete when task-scoped completed Codex objective maps to the ulw-loop brief", async () => {
const taskObjective = "Fix ulw-loop objective mismatch and install local ulw";
const repo = await repoWith(plan([passGoal("G001")], { activeGoalId: "G001" }));
await writeFile(ulwLoopBriefPath(repo), `${taskObjective}\n`, "utf8");
const result = await checkpointUlwLoop(repo, {
goalId: "G001",
status: "complete",
evidence: "final implementation complete and quality gate passed",
codexGoalJson: snapshot("complete", taskObjective),
qualityGateJson: QUALITY_GATE_PATH,
});
expect(result.aggregateCompletion?.status).toBe("complete");
expect(result.ledgerEntry.kind).toBe("aggregate_completed");
});
it("explains final task-scoped objective mapping when completed Codex objective is unrelated", async () => {
const repo = await repoWith(plan([passGoal("G001")], { activeGoalId: "G001" }));
await writeFile(ulwLoopBriefPath(repo), "Fix ulw-loop objective mismatch and install local ulw\n", "utf8");
await expect(
checkpointUlwLoop(repo, {
goalId: "G001",
status: "complete",
evidence: "final implementation complete and quality gate passed",
codexGoalJson: snapshot("complete", "unrelated completed task"),
qualityGateJson: QUALITY_GATE_PATH,
}),
).rejects.toThrow("Final task-scoped aggregate reconciliation");
});
});
describe("checkpointUlwLoop status=failed", () => {
it("sets goal.status=failed, goal.failedAt, appends ledger", async () => {
const repo = await repoWith(plan([goal({ successCriteria: [criterion("C001", "pending")] })]));
const result = await checkpointUlwLoop(repo, { goalId: "G001", status: "failed", evidence: "tests failed" });
expect(result.goal.status).toBe("failed");
expect(result.goal.failedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/u);
expect((await lastLedger(repo)).kind).toBe("goal_failed");
});
it("classifies external authorization blocker signatures", async () => {
const repo = await repoWith(plan([goal()]));
const result = await checkpointUlwLoop(repo, { goalId: "G001", status: "failed", evidence: "ghcr.io returned 401 authentication required because token missing" });
expect(result.goal.blockerSignature).toBe("GHCR_PULL_ACCESS:HTTP_401_ANONYMOUS:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED");
});
it("after 3 same-signature blockers, marks needs_user_decision + nonRetriable", async () => {
const repo = await repoWith(plan([goal({ id: "G001", status: "failed", blockerSignature: "EXTERNAL_AUTHORIZATION_REQUIRED" }), goal({ id: "G002", status: "blocked", blockerSignature: "EXTERNAL_AUTHORIZATION_REQUIRED" }), goal({ id: "G003" })], { activeGoalId: "G003" }));
const result = await checkpointUlwLoop(repo, { goalId: "G003", status: "failed", evidence: "Registry returned 401 because credentials are missing" });
expect(result.goal.status).toBe("needs_user_decision");
expect(result.goal.nonRetriable).toBe(true);
});
it("skips the criteria gate for failed status", async () => {
const repo = await repoWith(plan([goal({ successCriteria: [criterion("C001", "pending")] })]));
await expect(checkpointUlwLoop(repo, { goalId: "G001", status: "failed", evidence: "not done" })).resolves.toMatchObject({ goal: { status: "failed" } });
});
});
describe("checkpointUlwLoop status=blocked", () => {
it("preserves blocker fields + appends ledger", async () => {
const repo = await repoWith(plan([goal()]));
const result = await checkpointUlwLoop(repo, { goalId: "G001", status: "blocked", evidence: "ghcr.io requires token and credentials are missing" });
expect(result.goal.status).toBe("blocked");
expect(result.goal.blockedReason).toContain("ghcr.io");
expect(result.goal.blockerSignature).toContain("GHCR_PULL_ACCESS");
expect((await lastLedger(repo)).kind).toBe("goal_blocked");
});
it("skips the criteria gate for blocked status", async () => {
const repo = await repoWith(plan([goal({ successCriteria: [criterion("C001", "pending")] })]));
await expect(checkpointUlwLoop(repo, { goalId: "G001", status: "blocked", evidence: "waiting for approval" })).resolves.toMatchObject({ goal: { status: "blocked" } });
});
});
describe("checkpointUlwLoop rebrand", () => {
it("does not emit legacy brand token in any returned text or ledger payload", async () => {
const repo = await repoWith(plan([passGoal("G001"), goal({ id: "G002", status: "pending" })]));
const result = await checkpointUlwLoop(repo, { goalId: "G001", status: "complete", evidence: "implementation done in .omo/ulw-loop/goals.json for G001 and validation passed", codexGoalJson: snapshot("active") });
const forbidden = ["o", "m", "x"].join("");
const payload = `${JSON.stringify(result)}\n${await readFile(ulwLoopLedgerPath(repo), "utf8")}`.toLowerCase();
expect(payload).not.toContain(forbidden);
});
});
@@ -0,0 +1,274 @@
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ulwLoopCommand } from "../src/cli-commands.ts";
import { ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE } from "../src/goal-status.js";
let testDir: string;
let out: string[];
let err: string[];
beforeEach(async () => {
testDir = await mkdtemp(join(tmpdir(), "ug-cli-"));
out = [];
err = [];
vi.spyOn(process, "cwd").mockReturnValue(testDir);
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
out.push(chunk.toString());
return true;
});
vi.spyOn(process.stderr, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
err.push(chunk.toString());
return true;
});
});
afterEach(async () => {
vi.restoreAllMocks();
await rm(testDir, { recursive: true, force: true });
});
function resetOutput(): void {
out = [];
err = [];
}
function stdoutJson(): Record<string, unknown> {
return JSON.parse(out.join(""));
}
function codexSnapshot(status: "active" | "complete" = "active"): string {
return JSON.stringify({ goal: { objective: ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE, status } });
}
async function createPlan(brief = "- Goal A\n- Goal B"): Promise<Record<string, unknown>> {
resetOutput();
expect(await ulwLoopCommand(["create-goals", "--brief", brief, "--json"])).toBe(0);
const parsed = stdoutJson();
resetOutput();
return parsed;
}
async function passCriterion(goalId: string, criterionId: string): Promise<void> {
expect(
await ulwLoopCommand([
"record-evidence",
"--goal-id",
goalId,
"--criterion-id",
criterionId,
"--status",
"pass",
"--evidence",
`${criterionId} observable proof`,
]),
).toBe(0);
resetOutput();
}
describe("ulwLoopCommand help", () => {
it("prints usage when no subcommand", async () => {
expect(await ulwLoopCommand([])).toBe(0);
expect(out.join("")).toContain("omo ulw-loop");
});
});
describe("ulwLoopCommand create-goals", () => {
it("creates plan + writes 3 artifacts + seeds criteria per goal", async () => {
const code = await ulwLoopCommand(["create-goals", "--brief", "- Goal A\n- Goal B", "--json"]);
expect(code).toBe(0);
const parsed = stdoutJson();
expect(parsed).toMatchObject({ ok: true });
expect(parsed).toHaveProperty("plan.goals.0.successCriteria.0.id", "C001");
expect(await readFile(join(testDir, ".omo/ulw-loop/brief.md"), "utf8")).toContain("Goal A");
expect(await readFile(join(testDir, ".omo/ulw-loop/goals.json"), "utf8")).toContain("successCriteria");
expect(await readFile(join(testDir, ".omo/ulw-loop/ledger.jsonl"), "utf8")).toContain("plan_created");
});
});
describe("ulwLoopCommand status", () => {
it("prints plan summary including criteria counts", async () => {
await createPlan();
expect(await ulwLoopCommand(["status"])).toBe(0);
expect(out.join("")).toContain("criteria: 0/6 pass");
});
});
describe("ulwLoopCommand complete-goals", () => {
it("starts the next goal and returns a Codex instruction", async () => {
await createPlan();
expect(await ulwLoopCommand(["complete-goals", "--json"])).toBe(0);
expect(stdoutJson()).toMatchObject({
ok: true,
goal: { status: "in_progress" },
instruction: { json: { status: "active" } },
});
});
});
describe("ulwLoopCommand record-evidence", () => {
it("records evidence + returns updated criterion", async () => {
await createPlan();
expect(
await ulwLoopCommand([
"record-evidence",
"--goal-id",
"G001-goal-a",
"--criterion-id",
"C001",
"--status",
"pass",
"--evidence",
"curl passed",
"--json",
]),
).toBe(0);
expect(stdoutJson()).toMatchObject({
ok: true,
criterion: { id: "C001", status: "pass", capturedEvidence: "curl passed" },
});
});
it("returns 1 + error on unknown goal-id", async () => {
await createPlan();
expect(
await ulwLoopCommand([
"record-evidence",
"--goal-id",
"G404",
"--criterion-id",
"C001",
"--status",
"pass",
"--evidence",
"x",
]),
).toBe(1);
expect(err.join("")).toContain("[ulw-loop]");
});
it("returns 1 + error on missing flags", async () => {
expect(
await ulwLoopCommand(["record-evidence", "--criterion-id", "C001", "--status", "pass", "--evidence", "x"]),
).toBe(1);
expect(err.join("")).toContain("Missing --goal-id");
});
});
describe("ulwLoopCommand criteria", () => {
it("lists criteria for a goal", async () => {
await createPlan();
expect(await ulwLoopCommand(["criteria", "--goal-id", "G001-goal-a"])).toBe(0);
expect(out.join("")).toContain("C001");
expect(out.join("")).toContain("happy");
});
it("supports --json output", async () => {
await createPlan();
expect(await ulwLoopCommand(["criteria", "--goal-id", "G001-goal-a", "--json"])).toBe(0);
expect(stdoutJson()).toMatchObject({ ok: true, goalId: "G001-goal-a" });
expect(stdoutJson()).toHaveProperty("criteria.0.id", "C001");
});
});
describe("ulwLoopCommand checkpoint", () => {
it("REJECTS status=complete when criteria pending", async () => {
await createPlan();
expect(
await ulwLoopCommand([
"checkpoint",
"--goal-id",
"G001-goal-a",
"--status",
"complete",
"--evidence",
"x",
"--codex-goal-json",
codexSnapshot(),
]),
).toBe(1);
expect(err.join("").toLowerCase()).toContain("criteria");
});
it("ACCEPTS when all criteria pass", async () => {
await createPlan();
await passCriterion("G001-goal-a", "C001");
await passCriterion("G001-goal-a", "C002");
await passCriterion("G001-goal-a", "C003");
expect(
await ulwLoopCommand([
"checkpoint",
"--goal-id",
"G001-goal-a",
"--status",
"complete",
"--evidence",
"implementation done and validation passed",
"--codex-goal-json",
codexSnapshot(),
"--json",
]),
).toBe(0);
expect(stdoutJson()).toHaveProperty("goal.status", "complete");
});
});
describe("ulwLoopCommand steer", () => {
it("dispatches to the steering engine", async () => {
await createPlan();
expect(
await ulwLoopCommand([
"steer",
"--kind",
"add_subgoal",
"--title",
"Extra",
"--objective",
"Do extra",
"--evidence",
"user requested it",
"--rationale",
"keeps plan accurate",
"--json",
]),
).toBe(0);
expect(stdoutJson()).toMatchObject({
ok: true,
accepted: true,
plan: { goals: [{ id: "G001-goal-a" }, { id: "G002-goal-b" }, { title: "Extra" }] },
});
});
});
describe("ulwLoopCommand add-goal", () => {
it("appends a pending goal", async () => {
await createPlan();
expect(await ulwLoopCommand(["add-goal", "--title", "Later", "--objective", "Do later", "--json"])).toBe(0);
expect(stdoutJson()).toMatchObject({ ok: true, goal: { title: "Later", status: "pending" } });
});
});
describe("ulwLoopCommand unknown", () => {
it("returns 1 + prints help on unknown subcommand", async () => {
expect(await ulwLoopCommand(["wat"])).toBe(1);
expect(out.join("")).toContain("omo ulw-loop");
});
});
describe("ulwLoopCommand error handling", () => {
it("returns 1 + prints [ulw-loop] prefix on UlwLoopError", async () => {
expect(await ulwLoopCommand(["status"])).toBe(1);
expect(err.join("")).toContain("[ulw-loop]");
});
});
@@ -0,0 +1,250 @@
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import {
hasFlag,
parseGoalArg,
parseRecordEvidenceArgs,
positionalText,
readJsonInput,
readRepeated,
readValue,
} from "../src/cli-arg-parser.js";
import { normalizeCodexGoalMode, printStatus, ULW_LOOP_HELP } from "../src/cli-output.js";
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
function criterion(overrides: Partial<UlwLoopSuccessCriterion> = {}): UlwLoopSuccessCriterion {
return {
id: "C001",
scenario: "happy path returns 200",
userModel: "happy",
expectedEvidence: "HTTP 200",
capturedEvidence: null,
status: "pending",
...overrides,
};
}
function goal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Auth endpoint",
objective: "Build JWT auth",
status: "in_progress",
successCriteria: [
criterion({ id: "C001", status: "pass" }),
criterion({ id: "C002", status: "pass" }),
criterion({ id: "C003" }),
],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function plan(overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
activeGoalId: "G001",
goals: [goal()],
...overrides,
};
}
function captureStdout(action: () => void): string {
let output = "";
const write = vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
output += chunk.toString();
return true;
});
action();
write.mockRestore();
return output;
}
describe("hasFlag", () => {
it("returns true for present flag", () => {
expect(hasFlag(["status", "--json"], "--json")).toBe(true);
});
it("returns false otherwise", () => {
expect(hasFlag(["status"], "--json")).toBe(false);
});
});
describe("readValue", () => {
it("returns value after flag", () => {
expect(readValue(["criteria", "--goal-id", "G001"], "--goal-id")).toBe("G001");
});
it("returns undefined when absent", () => {
expect(readValue(["criteria"], "--goal-id")).toBeUndefined();
});
it("returns undefined when flag has no following value", () => {
expect(readValue(["criteria", "--goal-id"], "--goal-id")).toBeUndefined();
});
});
describe("readRepeated", () => {
it("collects all occurrences", () => {
expect(readRepeated(["create-goals", "--goal", "A", "--goal=B"], "--goal")).toEqual(["A", "B"]);
});
});
describe("parseGoalArg", () => {
it("returns value of --goal-id or --goal", () => {
expect(parseGoalArg(["criteria", "--goal", "G002"])).toBe("G002");
expect(parseGoalArg(["criteria", "--goal-id", "G001"])).toBe("G001");
});
});
describe("positionalText", () => {
it("returns joined positional args after subcommand", () => {
expect(positionalText(["create-goals", "Build", "auth", "--json", "--brief", "ignored"])).toBe("Build auth");
});
});
describe("readJsonInput", () => {
it("parses inline JSON when value looks like JSON", async () => {
await expect(readJsonInput('{"ok":true}')).resolves.toEqual({ ok: true });
});
it("reads from file path", async () => {
const dir = await mkdtemp(join(tmpdir(), "ug-cli-json-"));
try {
const file = join(dir, "input.json");
await writeFile(file, JSON.stringify({ fromFile: true }), "utf8");
await expect(readJsonInput(file)).resolves.toEqual({ fromFile: true });
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it("returns undefined when value is undefined", async () => {
await expect(readJsonInput(undefined)).resolves.toBeUndefined();
});
});
describe("parseRecordEvidenceArgs", () => {
it("parses --goal-id + --criterion-id + --status + --evidence", () => {
expect(
parseRecordEvidenceArgs([
"record-evidence",
"--goal-id",
"G001",
"--criterion-id",
"C001",
"--status",
"pass",
"--evidence",
"curl 200",
]),
).toEqual({ goalId: "G001", criterionId: "C001", status: "pass", evidence: "curl 200" });
});
it("throws when goal-id missing", () => {
expect(() =>
parseRecordEvidenceArgs(["record-evidence", "--criterion-id", "C001", "--status", "pass", "--evidence", "x"]),
).toThrow(UlwLoopError);
});
it("throws when status is not pass|fail|blocked", () => {
expect(() =>
parseRecordEvidenceArgs([
"record-evidence",
"--goal-id",
"G001",
"--criterion-id",
"C001",
"--status",
"skip",
"--evidence",
"x",
]),
).toThrow(UlwLoopError);
});
it("includes optional --notes when present", () => {
expect(
parseRecordEvidenceArgs([
"record-evidence",
"--goal-id",
"G001",
"--criterion-id",
"C001",
"--status",
"blocked",
"--evidence",
"auth missing",
"--notes",
"waiting",
]),
).toMatchObject({ notes: "waiting" });
});
});
describe("ULW_LOOP_HELP", () => {
it("mentions omo ulw-loop + every subcommand", () => {
expect(ULW_LOOP_HELP).toContain("omo ulw-loop");
expect(ULW_LOOP_HELP).toContain("create-goals");
expect(ULW_LOOP_HELP).toContain("complete-goals");
expect(ULW_LOOP_HELP).toContain("status");
expect(ULW_LOOP_HELP).toContain("checkpoint");
expect(ULW_LOOP_HELP).toContain("steer");
expect(ULW_LOOP_HELP).toContain("record-evidence");
expect(ULW_LOOP_HELP).toContain("criteria");
expect(ULW_LOOP_HELP).toContain("add-goal");
expect(ULW_LOOP_HELP).toContain("record-review-blockers");
});
it("never mentions the legacy typo", () => {
const typo = ["o", "m", "x"].join("");
expect(ULW_LOOP_HELP).not.toMatch(new RegExp(typo, "i"));
});
});
describe("printStatus", () => {
it("shows criteria P/T per goal", () => {
const output = captureStdout(() => printStatus(plan()));
expect(output).toContain("criteria: 2/3");
});
it("shows aggregate counts", () => {
const output = captureStdout(() =>
printStatus(plan({ goals: [goal(), goal({ id: "G002", successCriteria: [criterion({ status: "pass" })] })] })),
);
expect(output).toContain("total goals: 2");
expect(output).toContain("criteria: 3/4 pass");
});
});
describe("normalizeCodexGoalMode", () => {
it("returns aggregate when undefined", () => {
expect(normalizeCodexGoalMode(undefined)).toBe("aggregate");
});
it("returns the explicit value when valid", () => {
expect(normalizeCodexGoalMode("per_story")).toBe("per_story");
});
it("throws UlwLoopError when invalid", () => {
expect(() => normalizeCodexGoalMode("per-story")).toThrow(UlwLoopError);
});
});
@@ -0,0 +1,407 @@
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import {
normalizeSteeringProposal,
parseSteeringKind,
parseSteeringProposal,
parseSteeringSource,
printSteerResult,
} from "../src/cli-steering.js";
import type { SteerUlwLoopResult, UlwLoopPlan } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
function plan(): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
goals: [],
};
}
function steerResult(overrides: Partial<SteerUlwLoopResult> = {}): SteerUlwLoopResult {
return {
plan: plan(),
accepted: true,
audit: {
kind: "add_subgoal",
source: "cli",
targetGoalIds: ["G001"],
evidence: "x",
rationale: "y",
invariant: {
accepted: true,
structuralInvariantAccepted: true,
evidenceBackedNecessity: true,
noEasierCompletion: true,
rejectedReasons: [],
},
},
rejectedReasons: [],
deduped: false,
...overrides,
};
}
function captureStdout(action: () => void): string {
let output = "";
const write = vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
output += chunk.toString();
return true;
});
action();
write.mockRestore();
return output;
}
describe("parseSteeringKind", () => {
it("returns valid kind from --kind", () => {
expect(parseSteeringKind(["--kind", "add_subgoal"])).toBe("add_subgoal");
});
it("accepts revise_criterion", () => {
expect(parseSteeringKind(["--kind", "revise_criterion"])).toBe("revise_criterion");
});
it("throws when --kind missing", () => {
expect(() => parseSteeringKind([])).toThrow(UlwLoopError);
});
it("throws when kind unknown", () => {
expect(() => parseSteeringKind(["--kind", "bogus"])).toThrow(UlwLoopError);
});
});
describe("parseSteeringSource", () => {
it("defaults to cli", () => {
expect(parseSteeringSource([])).toBe("cli");
});
it("returns explicit value", () => {
expect(parseSteeringSource(["--source", "user_prompt_submit"])).toBe("user_prompt_submit");
});
});
describe("parseSteeringProposal add_subgoal", () => {
it("builds proposal from required flags", async () => {
const p = await parseSteeringProposal([
"--kind",
"add_subgoal",
"--title",
" New ",
"--objective",
" Build ",
"--evidence",
" x ",
"--rationale",
" y ",
]);
expect(p).toMatchObject({
kind: "add_subgoal",
source: "cli",
title: "New",
objective: "Build",
evidence: "x",
rationale: "y",
});
});
it("throws when --title missing", async () => {
await expect(
parseSteeringProposal([
"--kind",
"add_subgoal",
"--objective",
"Build",
"--evidence",
"x",
"--rationale",
"y",
]),
).rejects.toThrow(UlwLoopError);
});
it("throws when --evidence missing", async () => {
await expect(
parseSteeringProposal(["--kind", "add_subgoal", "--title", "New", "--objective", "Build", "--rationale", "y"]),
).rejects.toThrow(UlwLoopError);
});
});
describe("parseSteeringProposal revise_criterion", () => {
it("builds proposal with goal, criterion, scenario, evidence, and rationale", async () => {
const p = await parseSteeringProposal([
"--kind",
"revise_criterion",
"--goal-id",
"G001",
"--criterion-id",
"C002",
"--scenario",
"new scenario",
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p.kind).toBe("revise_criterion");
expect(p.goalId).toBe("G001");
expect(p.targetGoalId).toBe("G001");
expect(p.criterionId).toBe("C002");
expect(p.scenario).toBe("new scenario");
});
it("accepts --expected-evidence as an update field", async () => {
const p = await parseSteeringProposal([
"--kind",
"revise_criterion",
"--goal-id",
"G001",
"--criterion-id",
"C002",
"--expected-evidence",
"new evidence",
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p.expectedEvidence).toBe("new evidence");
});
it("accepts --user-model as an update field", async () => {
const p = await parseSteeringProposal([
"--kind",
"revise_criterion",
"--goal-id",
"G001",
"--criterion-id",
"C002",
"--user-model",
"edge",
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p.userModel).toBe("edge");
});
it("throws when none of scenario/expected-evidence/user-model provided", async () => {
await expect(
parseSteeringProposal([
"--kind",
"revise_criterion",
"--goal-id",
"G001",
"--criterion-id",
"C002",
"--evidence",
"x",
"--rationale",
"y",
]),
).rejects.toThrow(UlwLoopError);
});
it("throws when goal-id missing", async () => {
await expect(
parseSteeringProposal([
"--kind",
"revise_criterion",
"--criterion-id",
"C002",
"--scenario",
"s",
"--evidence",
"x",
"--rationale",
"y",
]),
).rejects.toThrow(UlwLoopError);
});
it("throws when criterion-id missing", async () => {
await expect(
parseSteeringProposal([
"--kind",
"revise_criterion",
"--goal-id",
"G001",
"--scenario",
"s",
"--evidence",
"x",
"--rationale",
"y",
]),
).rejects.toThrow(UlwLoopError);
});
});
describe("parseSteeringProposal split_subgoal", () => {
it("reads --children from inline JSON", async () => {
const p = await parseSteeringProposal([
"--kind",
"split_subgoal",
"--goal-id",
"G001",
"--children",
'[{"title":"A","objective":"Do A"}]',
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p.childGoals).toEqual([{ title: "A", objective: "Do A" }]);
});
it("reads --children from JSON file path", async () => {
const dir = await mkdtemp(join(tmpdir(), "ug-steer-"));
try {
const file = join(dir, "children.json");
await writeFile(file, '[{"title":"B","objective":"Do B"}]', "utf8");
const p = await parseSteeringProposal([
"--kind",
"split_subgoal",
"--goal-id",
"G001",
"--children",
file,
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p.childGoals).toEqual([{ title: "B", objective: "Do B" }]);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
});
describe("parseSteeringProposal reorder_pending", () => {
it("reads --order from inline JSON array", async () => {
const p = await parseSteeringProposal([
"--kind",
"reorder_pending",
"--order",
'["G002","G001"]',
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p.pendingOrder).toEqual(["G002", "G001"]);
});
});
describe("parseSteeringProposal remaining kinds", () => {
it("builds revise_pending_wording proposal", async () => {
const p = await parseSteeringProposal([
"--kind",
"revise_pending_wording",
"--goal-id",
"G001",
"--title",
"New",
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p).toMatchObject({ kind: "revise_pending_wording", targetGoalId: "G001", revisedTitle: "New" });
});
it("builds mark_blocked_superseded proposal with replacements", async () => {
const p = await parseSteeringProposal([
"--kind",
"mark_blocked_superseded",
"--goal-id",
"G001",
"--replacements",
'[{"title":"C","objective":"Do C"}]',
"--evidence",
"x",
"--rationale",
"y",
]);
expect(p).toMatchObject({
kind: "mark_blocked_superseded",
targetGoalId: "G001",
childGoals: [{ title: "C", objective: "Do C" }],
});
});
});
describe("parseSteeringProposal annotate_ledger", () => {
it("builds minimal proposal", async () => {
const p = await parseSteeringProposal(["--kind", "annotate_ledger", "--evidence", "x", "--rationale", "y"]);
expect(p).toMatchObject({ kind: "annotate_ledger", source: "cli", evidence: "x", rationale: "y" });
});
});
describe("normalizeSteeringProposal", () => {
it("trims string fields", () => {
const p = normalizeSteeringProposal({
kind: "revise_criterion",
source: "cli",
goalId: " G001 ",
targetGoalId: " G001 ",
criterionId: " C002 ",
evidence: " x ",
rationale: " y ",
scenario: " z ",
});
expect(p).toMatchObject({
goalId: "G001",
targetGoalId: "G001",
criterionId: "C002",
evidence: "x",
rationale: "y",
scenario: "z",
});
});
it("rejects empty evidence after trim", () => {
expect(() =>
normalizeSteeringProposal({ kind: "annotate_ledger", source: "cli", evidence: " ", rationale: "y" }),
).toThrow(UlwLoopError);
});
});
describe("printSteerResult", () => {
it("prints JSON when json=true", () => {
const output = captureStdout(() => printSteerResult(steerResult(), true));
expect(JSON.parse(output)).toMatchObject({ accepted: true, deduped: false, audit: { kind: "add_subgoal" } });
});
it("prints human-readable when json=false", () => {
const output = captureStdout(() => printSteerResult(steerResult(), false));
expect(output).toContain("ulw-loop steer: accepted add_subgoal");
expect(output).toContain("ulw-loop status");
});
});
@@ -0,0 +1,153 @@
import { describe, expect, it } from "vitest";
import { buildCodexGoalInstruction } from "../src/codex-goal-instruction.js";
import { ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE } from "../src/goal-status.js";
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
function makeCriterion(overrides: Partial<UlwLoopSuccessCriterion> = {}): UlwLoopSuccessCriterion {
return {
id: "C001",
scenario: "happy path",
userModel: "happy",
expectedEvidence: "observable proof",
capturedEvidence: null,
status: "pending",
...overrides,
};
}
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Goal one",
objective: "Complete goal one",
status: "pending",
successCriteria: [],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function makePlan(overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
goals: [],
...overrides,
};
}
describe("buildCodexGoalInstruction aggregate mode", () => {
it("references the aggregate handoff and the .omo/ulw-loop/goals.json artifact", () => {
const { text } = buildCodexGoalInstruction({ plan: makePlan({ codexGoalMode: "aggregate" }), goal: makeGoal() });
expect(text).toContain("aggregate");
expect(text).toContain(".omo/ulw-loop/goals.json");
});
it("given aggregate mode when rendering create_goal payload then omits numeric limits", () => {
const { json, text } = buildCodexGoalInstruction({
plan: makePlan({ codexGoalMode: "aggregate" }),
goal: makeGoal(),
});
expect(json).toEqual({
objective: ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE,
status: "active",
});
expect(text).toContain("objective and status only");
expect(text).toContain("Goals are unlimited");
expect(text).not.toMatch(/token[_-]?budget/i);
});
it("instructs not to call update_goal mid-aggregate when not final", () => {
const { text } = buildCodexGoalInstruction({
plan: makePlan({ codexGoalMode: "aggregate" }),
goal: makeGoal(),
isFinal: false,
});
expect(text).toMatch(/do not.*update_goal/i);
});
it("includes quality gate instruction when isFinal", () => {
const { text } = buildCodexGoalInstruction({
plan: makePlan({ codexGoalMode: "aggregate" }),
goal: makeGoal(),
isFinal: true,
});
expect(text).toMatch(/quality gate/i);
});
});
describe("buildCodexGoalInstruction per_story mode", () => {
it("uses the goal's own objective for create_goal", () => {
const goal = makeGoal({ objective: "Build the auth service" });
const { text } = buildCodexGoalInstruction({ plan: makePlan({ codexGoalMode: "per_story" }), goal });
expect(text).toContain("Build the auth service");
});
});
describe("buildCodexGoalInstruction criteria section", () => {
it("lists every successCriteria entry with id + scenario + status", () => {
const goal = makeGoal({
successCriteria: [
makeCriterion({
id: "C001",
scenario: "happy login",
userModel: "happy",
expectedEvidence: "200 OK",
status: "pending",
}),
makeCriterion({
id: "C002",
scenario: "invalid creds",
userModel: "edge",
expectedEvidence: "401",
status: "pass",
}),
makeCriterion({
id: "C003",
scenario: "no regression /health",
userModel: "regression",
expectedEvidence: "/health unaffected",
status: "fail",
}),
],
});
const { text } = buildCodexGoalInstruction({ plan: makePlan(), goal });
expect(text).toContain("C001");
expect(text).toContain("happy login");
expect(text).toContain("pending");
expect(text).toContain("C002");
expect(text).toContain("pass");
expect(text).toContain("C003");
expect(text).toContain("fail");
});
it("highlights pending criteria as remaining work", () => {
const goal = makeGoal({ successCriteria: [makeCriterion({ id: "C001", status: "pending" })] });
const { text } = buildCodexGoalInstruction({ plan: makePlan(), goal });
expect(text).toMatch(/remaining|pending/i);
});
});
describe("buildCodexGoalInstruction rebrand audit", () => {
it("emits no legacy brand references in any rendered string", () => {
const legacyBrand = ["o", "m", "x"].join("");
const { text } = buildCodexGoalInstruction({ plan: makePlan(), goal: makeGoal() });
expect(text).not.toMatch(new RegExp(legacyBrand, "i"));
});
it("references .omo/ulw-loop in artifact paths", () => {
const { text } = buildCodexGoalInstruction({ plan: makePlan({ codexGoalMode: "aggregate" }), goal: makeGoal() });
expect(text).toContain(".omo/ulw-loop");
});
});
@@ -0,0 +1,156 @@
import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { beforeEach, describe, expect, it } from "vitest";
import {
CodexGoalSnapshotError,
formatCodexGoalReconciliation,
parseCodexGoalSnapshot,
readCodexGoalSnapshotInput,
reconcileCodexGoalSnapshot,
} from "../src/codex-goal-snapshot.ts";
describe("parseCodexGoalSnapshot", () => {
it("returns available snapshot from { goal: { ... } } JSON", () => {
// given
const payload = { goal: { objective: "X", status: "active" } };
// when
const snapshot = parseCodexGoalSnapshot(payload);
// then
expect(snapshot.available).toBe(true);
expect(snapshot.objective).toBe("X");
expect(snapshot.status).toBe("active");
});
it("ignores remaining token budget fields from goal snapshots", () => {
// given
const payload = { goal: { objective: "X", status: "active" }, remainingTokens: 123 };
// when
const snapshot = parseCodexGoalSnapshot(payload);
// then
expect("remainingTokens" in snapshot).toBe(false);
});
it("returns unavailable snapshot from null", () => {
// when
const snapshot = parseCodexGoalSnapshot(null);
// then
expect(snapshot.available).toBe(false);
});
it("returns unavailable snapshot from malformed payload", () => {
// when
const snapshot = parseCodexGoalSnapshot({ wrong: "shape" });
// then
expect(snapshot.available).toBe(false);
expect(snapshot.status).toBe("unknown");
});
});
describe("readCodexGoalSnapshotInput", () => {
let dir = "";
beforeEach(async () => {
// given
dir = await mkdtemp(join(tmpdir(), "ug-snap-"));
});
it("parses inline JSON string", async () => {
// when
const snapshot = await readCodexGoalSnapshotInput('{"goal":{"objective":"X","status":"active"}}');
// then
expect(snapshot?.available).toBe(true);
expect(snapshot?.objective).toBe("X");
});
it("reads from file path", async () => {
// given
const filePath = join(dir, "snap.json");
await writeFile(filePath, '{"goal":{"objective":"X","status":"complete"}}', "utf8");
// when
const snapshot = await readCodexGoalSnapshotInput(filePath);
// then
expect(snapshot?.available).toBe(true);
expect(snapshot?.status).toBe("complete");
});
it("reads from sample fixture path", async () => {
// given
const filePath = join(process.cwd(), "test", "fixtures", "codex-goal-snapshot.json");
// when
const snapshot = await readCodexGoalSnapshotInput(filePath);
// then
expect(snapshot?.available).toBe(true);
expect(snapshot?.objective).toBe("Complete the durable ulw-loop plan");
});
it("throws CodexGoalSnapshotError when input is neither JSON nor a path", async () => {
// when/then
await expect(readCodexGoalSnapshotInput("not json and not a path")).rejects.toThrow(CodexGoalSnapshotError);
});
});
describe("reconcileCodexGoalSnapshot", () => {
it("returns ok=true when snapshot matches expected", () => {
// when
const reconciliation = reconcileCodexGoalSnapshot(
{ available: true, objective: "X", status: "active", raw: null },
{ expectedObjective: "X" },
);
// then
expect(reconciliation.ok).toBe(true);
expect(reconciliation.errors).toHaveLength(0);
});
it("reports error when objective mismatches", () => {
// when
const reconciliation = reconcileCodexGoalSnapshot(
{ available: true, objective: "X", status: "active", raw: null },
{ expectedObjective: "Y" },
);
// then
expect(reconciliation.ok).toBe(false);
expect(reconciliation.errors.length).toBeGreaterThan(0);
});
it("reports error when status mismatches", () => {
// when
const reconciliation = reconcileCodexGoalSnapshot(
{ available: true, objective: "X", status: "active", raw: null },
{ expectedObjective: "X", allowedStatuses: ["complete"] },
);
// then
expect(reconciliation.ok).toBe(false);
expect(reconciliation.errors.length).toBeGreaterThan(0);
});
});
describe("formatCodexGoalReconciliation", () => {
it("renders errors joined", () => {
// given
const reconciliation = reconcileCodexGoalSnapshot(
{ available: true, objective: "X", status: "active", raw: null },
{ expectedObjective: "Y", allowedStatuses: ["complete"] },
);
// when
const formatted = formatCodexGoalReconciliation(reconciliation);
// then
expect(formatted).toMatch(/objective|status/i);
});
});
@@ -0,0 +1,266 @@
import { mkdir, mkdtemp, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Readable, Writable } from "node:stream";
import { describe, expect, it } from "vitest";
import {
applyPreToolUseGoalBudgetGuard,
applyUserPromptUlwLoopSteering,
type PreToolUsePayload,
parseUserPromptSubmitPayload,
runPreToolUseGoalBudgetGuardCli,
runUlwLoopHookCli,
type UserPromptSubmitPayload,
} from "../src/codex-hook.js";
import { ulwLoopDir } from "../src/paths.js";
import { writePlan } from "../src/plan-io.js";
import type { UlwLoopPlan } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
async function bootstrapPlanRepo(): Promise<string> {
const repoRoot = await mkdtemp(join(tmpdir(), "ug-hook-"));
await mkdir(ulwLoopDir(repoRoot), { recursive: true });
await writePlan(repoRoot, samplePlan());
return repoRoot;
}
function samplePlan(): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
goals: [
{
id: "G001",
title: "Build hook",
objective: "Apply safe steering directives from Codex hooks.",
status: "pending",
successCriteria: [],
attempt: 0,
createdAt: NOW,
updatedAt: NOW,
},
],
};
}
function payload(prompt: string, cwd: string): UserPromptSubmitPayload {
return { cwd, hook_event_name: "UserPromptSubmit", prompt, session_id: "s1" };
}
function preToolPayload(toolName: string, toolInput: unknown): PreToolUsePayload {
return {
cwd: "/repo",
hook_event_name: "PreToolUse",
model: "gpt-5.5",
permission_mode: "default",
session_id: "s1",
tool_input: toolInput,
tool_name: toolName,
tool_use_id: "call-1",
transcript_path: null,
turn_id: "turn-1",
};
}
function payloadWithRuntimeEvent(hookEventName: string): UserPromptSubmitPayload {
const input = payload(
'OMO_ULW_LOOP_STEER: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
"/tmp",
);
Object.defineProperty(input, "hook_event_name", { value: hookEventName });
return input;
}
function captureStdout(): { readonly stdout: Writable; readonly read: () => string } {
let captured = "";
const stdout = new Writable({
write(chunk: unknown, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void {
captured += chunk instanceof Buffer ? chunk.toString() : String(chunk);
callback();
},
});
return { stdout, read: () => captured };
}
describe("parseUserPromptSubmitPayload", () => {
it("parses valid JSON payload", async () => {
const raw = await readFile("test/fixtures/user-prompt-submit.json", "utf8");
const parsed = parseUserPromptSubmitPayload(raw);
expect(parsed?.hook_event_name).toBe("UserPromptSubmit");
expect(parsed?.prompt).toContain("OMO_ULW_LOOP_STEER");
});
it("returns null for empty input", () => {
expect(parseUserPromptSubmitPayload("")).toBeNull();
});
it("returns null for invalid JSON", () => {
expect(parseUserPromptSubmitPayload("{bad")).toBeNull();
});
it("returns null when hook_event_name missing", () => {
expect(parseUserPromptSubmitPayload(JSON.stringify({ cwd: "/repo", prompt: "x", session_id: "s1" }))).toBeNull();
});
});
describe("applyUserPromptUlwLoopSteering - OMO directive patterns", () => {
it("processes OMO_ULW_LOOP_STEER: prompt and returns audit text on success", async () => {
const repoRoot = await bootstrapPlanRepo();
const out = await applyUserPromptUlwLoopSteering(
payload(
'OMO_ULW_LOOP_STEER: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
repoRoot,
),
);
expect(out.length).toBeGreaterThan(0);
expect(out).toContain("annotate_ledger");
});
it("processes omo.ulw-loop.steer: pattern", async () => {
const repoRoot = await bootstrapPlanRepo();
const out = await applyUserPromptUlwLoopSteering(
payload(
'omo.ulw-loop.steer: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
repoRoot,
),
);
expect(out).toContain("accepted");
});
it("processes omo ulw-loop steer: pattern", async () => {
const repoRoot = await bootstrapPlanRepo();
const out = await applyUserPromptUlwLoopSteering(
payload(
'omo ulw-loop steer: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
repoRoot,
),
);
expect(out).toContain("annotate_ledger");
});
});
describe("applyUserPromptUlwLoopSteering - non-matching prompts", () => {
it("returns empty string when no directive in prompt", async () => {
expect(await applyUserPromptUlwLoopSteering(payload("just a normal user message", "/tmp"))).toBe("");
});
it("returns empty for OMX_ULW_LOOP_STEER (deprecated marker - must reject)", async () => {
expect(
await applyUserPromptUlwLoopSteering(
payload('OMX_ULW_LOOP_STEER: {"kind":"annotate_ledger","evidence":"x","rationale":"y"}', "/tmp"),
),
).toBe("");
});
it("returns empty when hook_event_name is not UserPromptSubmit", async () => {
expect(await applyUserPromptUlwLoopSteering(payloadWithRuntimeEvent("PostToolUse"))).toBe("");
});
});
describe("applyUserPromptUlwLoopSteering - error swallowing", () => {
it("returns empty (never throws) when plan does not exist", async () => {
const repoRoot = await mkdtemp(join(tmpdir(), "ug-nohook-"));
const out = await applyUserPromptUlwLoopSteering(
payload(
'OMO_ULW_LOOP_STEER: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
repoRoot,
),
);
expect(out).toBe("");
});
it("returns empty when steering proposal is malformed JSON after marker", async () => {
const out = await applyUserPromptUlwLoopSteering(payload("OMO_ULW_LOOP_STEER: {bad", "/tmp"));
expect(out).toBe("");
});
});
describe("runUlwLoopHookCli (stdin/stdout integration)", () => {
it("reads stdin, applies steering, writes audit to stdout", async () => {
const repoRoot = await bootstrapPlanRepo();
const stdin = Readable.from([
JSON.stringify(
payload(
'OMO_ULW_LOOP_STEER: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
repoRoot,
),
),
]);
const capture = captureStdout();
await runUlwLoopHookCli(stdin, capture.stdout);
expect(capture.read().length).toBeGreaterThan(0);
});
it("writes nothing when stdin is empty", async () => {
const capture = captureStdout();
await runUlwLoopHookCli(Readable.from([""]), capture.stdout);
expect(capture.read()).toBe("");
});
});
describe("applyPreToolUseGoalBudgetGuard", () => {
it("#given create_goal sets token_budget #when PreToolUse runs #then it blocks with unlimited-goal warning", () => {
// given
const input = preToolPayload("create_goal", { objective: "Ship the feature", token_budget: 5000 });
// when
const output = applyPreToolUseGoalBudgetGuard(input);
// then
const parsed = JSON.parse(output);
expect(parsed).toMatchObject({
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
},
});
expect(parsed.hookSpecificOutput.permissionDecisionReason).toContain("Do not set token_budget on create_goal");
expect(parsed.hookSpecificOutput.permissionDecisionReason).toContain("unlimited");
});
it("#given create_goal omits token_budget #when PreToolUse runs #then it stays silent", () => {
// given
const input = preToolPayload("create_goal", { objective: "Ship the feature" });
// when
const output = applyPreToolUseGoalBudgetGuard(input);
// then
expect(output).toBe("");
});
it("#given a neighboring tool includes token_budget text #when PreToolUse runs #then it stays silent", () => {
// given
const input = preToolPayload("update_goal", { status: "complete", token_budget: 5000 });
// when
const output = applyPreToolUseGoalBudgetGuard(input);
// then
expect(output).toBe("");
});
});
describe("runPreToolUseGoalBudgetGuardCli", () => {
it("#given Codex PreToolUse stdin with budgeted create_goal #when CLI hook runs #then it writes blocking JSON", async () => {
// given
const stdin = Readable.from([
JSON.stringify(preToolPayload("create_goal", { objective: "Ship", token_budget: 1 })),
]);
const capture = captureStdout();
// when
await runPreToolUseGoalBudgetGuardCli(stdin, capture.stdout);
// then
const parsed = JSON.parse(capture.read());
expect(parsed.hookSpecificOutput.permissionDecision).toBe("deny");
expect(parsed.hookSpecificOutput.permissionDecisionReason).toContain("unlimited");
});
});
@@ -0,0 +1,100 @@
import { describe, expect, it } from "vitest";
import { requireAllCriteriaPass } from "../src/evidence.js";
import type { UlwLoopItem, UlwLoopSuccessCriterion } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
function makeCriterion(overrides: Partial<UlwLoopSuccessCriterion> = {}): UlwLoopSuccessCriterion {
return {
id: "C001",
scenario: "happy path login returns 200",
userModel: "happy",
expectedEvidence: "curl /login -d {valid} returns 200 + token",
capturedEvidence: null,
status: "pending",
...overrides,
};
}
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Auth endpoint",
objective: "Build JWT auth",
status: "in_progress",
successCriteria: [
makeCriterion({ id: "C001" }),
makeCriterion({ id: "C002", userModel: "edge" }),
makeCriterion({ id: "C003", userModel: "regression" }),
],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
describe("requireAllCriteriaPass", () => {
it("does NOT throw when all criteria pass", () => {
// given
const goal = makeGoal({
successCriteria: [
makeCriterion({ id: "C001", status: "pass" }),
makeCriterion({ id: "C002", status: "pass" }),
],
});
// when / then
expect(() => requireAllCriteriaPass(goal)).not.toThrow();
});
it("throws UlwLoopError when any criterion pending", () => {
// given
const goal = makeGoal({
successCriteria: [
makeCriterion({ id: "C001", status: "pass" }),
makeCriterion({ id: "C002", status: "pending" }),
makeCriterion({ id: "C003", status: "pass" }),
],
});
// when / then
expect(() => requireAllCriteriaPass(goal)).toThrow(UlwLoopError);
});
it("throws when any fail/blocked too", () => {
// given
const goal1 = makeGoal({ successCriteria: [makeCriterion({ id: "C001", status: "fail" })] });
const goal2 = makeGoal({ successCriteria: [makeCriterion({ id: "C001", status: "blocked" })] });
// when / then
expect(() => requireAllCriteriaPass(goal1)).toThrow(UlwLoopError);
expect(() => requireAllCriteriaPass(goal2)).toThrow(UlwLoopError);
});
it("UlwLoopError includes details.goalId + details.unresolved", () => {
// given
const goal = makeGoal({
id: "G001",
successCriteria: [
makeCriterion({ id: "C001", status: "pass" }),
makeCriterion({ id: "C002", status: "pending" }),
makeCriterion({ id: "C003", status: "pass" }),
],
});
// when / then
try {
requireAllCriteriaPass(goal);
expect.fail("expected throw");
} catch (error) {
expect(error).toBeInstanceOf(UlwLoopError);
if (!(error instanceof UlwLoopError)) throw error;
expect(error.code).toBe("ulw_loop_criteria_not_all_pass");
expect(error.details?.["goalId"]).toBe("G001");
expect(Array.isArray(error.details?.["unresolved"])).toBe(true);
}
});
});
@@ -0,0 +1,263 @@
import { mkdir, mkdtemp, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
criteriaSummary,
markCriteriaPendingResetForGoal,
recordEvidence,
unresolvedCriteriaOf,
} from "../src/evidence.js";
import { ulwLoopDir } from "../src/paths.js";
import { readUlwLoopPlan, writePlan } from "../src/plan-io.js";
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
async function bootstrapRepo(plan: UlwLoopPlan): Promise<string> {
const repo = await mkdtemp(join(tmpdir(), "ug-evidence-"));
await mkdir(ulwLoopDir(repo), { recursive: true });
await writePlan(repo, plan);
return repo;
}
async function readLastLedgerEntry(repo: string): Promise<UlwLoopLedgerEntry> {
const lines = (await readFile(join(repo, ".omo/ulw-loop/ledger.jsonl"), "utf8")).trim().split("\n");
const last = lines.at(-1);
if (last === undefined) throw new Error("expected ledger entry");
return JSON.parse(last);
}
function firstGoal(plan: UlwLoopPlan): UlwLoopItem {
const goal = plan.goals.at(0);
if (goal === undefined) throw new Error("expected goal");
return goal;
}
function makeCriterion(overrides: Partial<UlwLoopSuccessCriterion> = {}): UlwLoopSuccessCriterion {
return {
id: "C001",
scenario: "happy path login returns 200",
userModel: "happy",
expectedEvidence: "curl /login -d {valid} returns 200 + token",
capturedEvidence: null,
status: "pending",
...overrides,
};
}
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Auth endpoint",
objective: "Build JWT auth",
status: "in_progress",
successCriteria: [
makeCriterion({ id: "C001" }),
makeCriterion({ id: "C002", userModel: "edge" }),
makeCriterion({ id: "C003", userModel: "regression" }),
],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function makePlan(overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
codexGoalMode: "aggregate",
codexObjective: "Complete the durable ulw-loop plan in .omo/ulw-loop/goals.json",
codexObjectiveAliases: [],
goals: [makeGoal()],
...overrides,
};
}
describe("recordEvidence (status=pass)", () => {
it("sets criterion.status=pass + capturedEvidence + capturedAt", async () => {
const repo = await bootstrapRepo(makePlan());
const result = await recordEvidence(repo, {
goalId: "G001",
criterionId: "C001",
status: "pass",
evidence: "curl /login returns 200 + token verified",
});
expect(result.criterion.status).toBe("pass");
expect(result.criterion.capturedEvidence).toContain("curl /login returns 200");
expect(result.criterion.capturedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
});
it("appends evidence_captured ledger event", async () => {
const repo = await bootstrapRepo(makePlan());
await recordEvidence(repo, { goalId: "G001", criterionId: "C001", status: "pass", evidence: "observable proof" });
const last = await readLastLedgerEntry(repo);
expect(last.kind).toBe("evidence_captured");
expect(last.goalId).toBe("G001");
expect(last.criterionId).toBe("C001");
});
it("persists the change so a fresh read sees status=pass", async () => {
const repo = await bootstrapRepo(makePlan());
await recordEvidence(repo, { goalId: "G001", criterionId: "C001", status: "pass", evidence: "observable proof" });
const criterion = firstGoal(await readUlwLoopPlan(repo)).successCriteria.find((c) => c.id === "C001");
expect(criterion?.status).toBe("pass");
});
});
describe("recordEvidence (status=fail)", () => {
it("sets criterion.status=fail + appends criterion_failed event", async () => {
const repo = await bootstrapRepo(makePlan());
const result = await recordEvidence(repo, {
goalId: "G001",
criterionId: "C001",
status: "fail",
evidence: "got 500 not 200",
});
expect(result.criterion.status).toBe("fail");
expect((await readLastLedgerEntry(repo)).kind).toBe("criterion_failed");
});
});
describe("recordEvidence (status=blocked)", () => {
it("sets criterion.status=blocked + appends criterion_blocked event", async () => {
const repo = await bootstrapRepo(makePlan());
const result = await recordEvidence(repo, {
goalId: "G001",
criterionId: "C001",
status: "blocked",
evidence: "auth not in CI yet",
});
expect(result.criterion.status).toBe("blocked");
expect((await readLastLedgerEntry(repo)).kind).toBe("criterion_blocked");
});
});
describe("recordEvidence error cases", () => {
it("throws when goalId not found", async () => {
const repo = await bootstrapRepo(makePlan());
await expect(
recordEvidence(repo, { goalId: "GUNKNOWN", criterionId: "C001", status: "pass", evidence: "x" }),
).rejects.toBeInstanceOf(UlwLoopError);
});
it("throws when criterionId not found within goal", async () => {
const repo = await bootstrapRepo(makePlan());
await expect(
recordEvidence(repo, { goalId: "G001", criterionId: "CUNKNOWN", status: "pass", evidence: "x" }),
).rejects.toBeInstanceOf(UlwLoopError);
});
it("throws when evidence is empty/whitespace", async () => {
const repo = await bootstrapRepo(makePlan());
await expect(
recordEvidence(repo, { goalId: "G001", criterionId: "C001", status: "pass", evidence: " " }),
).rejects.toBeInstanceOf(UlwLoopError);
});
});
describe("markCriteriaPendingResetForGoal", () => {
it("resets every criterion of the goal to pending + capturedEvidence=null", async () => {
const goal = makeGoal({
successCriteria: [
makeCriterion({ id: "C001", status: "pass", capturedEvidence: "old" }),
makeCriterion({ id: "C002", status: "fail", capturedEvidence: "older" }),
makeCriterion({ id: "C003", status: "blocked", capturedEvidence: "oldest" }),
],
});
const repo = await bootstrapRepo(makePlan({ goals: [goal] }));
const result = await markCriteriaPendingResetForGoal(repo, "G001");
expect(result.resetCount).toBe(3);
for (const c of firstGoal(result.plan).successCriteria) {
expect(c.status).toBe("pending");
expect(c.capturedEvidence).toBeNull();
}
});
it("appends a single criteria_revised ledger event describing the reset", async () => {
const repo = await bootstrapRepo(makePlan());
await markCriteriaPendingResetForGoal(repo, "G001");
expect((await readLastLedgerEntry(repo)).kind).toBe("criteria_revised");
});
});
describe("criteriaSummary (pure)", () => {
it("aggregates counts across all goals", () => {
const plan = makePlan({
goals: [
makeGoal({
id: "G001",
successCriteria: [
makeCriterion({ id: "C001", status: "pass" }),
makeCriterion({ id: "C002", status: "pending" }),
],
}),
makeGoal({
id: "G002",
successCriteria: [
makeCriterion({ id: "C001", status: "fail" }),
makeCriterion({ id: "C002", status: "blocked" }),
makeCriterion({ id: "C003", status: "pass" }),
],
}),
],
});
const summary = criteriaSummary(plan);
expect(summary.totalCriteria).toBe(5);
expect(summary.passCount).toBe(2);
expect(summary.pendingCount).toBe(1);
expect(summary.failCount).toBe(1);
expect(summary.blockedCount).toBe(1);
expect(summary.goalsWithUnresolvedCriteria).toEqual(["G001", "G002"]);
});
it("returns empty when no criteria exist", () => {
const summary = criteriaSummary(makePlan({ goals: [makeGoal({ successCriteria: [] })] }));
expect(summary.totalCriteria).toBe(0);
expect(summary.goalsWithUnresolvedCriteria).toEqual([]);
});
});
describe("unresolvedCriteriaOf (pure)", () => {
it("returns only non-pass criteria", () => {
const goal = makeGoal({
successCriteria: [
makeCriterion({ id: "C001", status: "pass" }),
makeCriterion({ id: "C002", status: "pending" }),
makeCriterion({ id: "C003", status: "fail" }),
],
});
const unresolved = unresolvedCriteriaOf(goal);
expect(unresolved.map((c) => c.id)).toEqual(["C002", "C003"]);
});
});
@@ -0,0 +1 @@
{ "goal": { "objective": "Complete the durable ulw-loop plan", "status": "active" } }
@@ -0,0 +1,5 @@
# Auth service feature brief
- Build the JWT auth endpoint
- Add IP rate limiting on login
- Write the integration test suite
@@ -0,0 +1,108 @@
{
"version": 1,
"createdAt": "2026-05-23T00:00:00.000Z",
"codexGoalMode": "aggregate",
"codexObjective": "Complete the durable ulw-loop plan in .omo/ulw-loop/goals.json...",
"codexObjectiveAliases": [],
"goals": [
{
"id": "G001",
"title": "Build auth service",
"objective": "Implement JWT auth endpoint",
"status": "pending",
"successCriteria": [
{
"id": "C001",
"scenario": "valid login returns 200",
"userModel": "happy",
"expectedEvidence": "curl /login -d '{...}' returns 200 + token",
"capturedEvidence": null,
"status": "pending"
},
{
"id": "C002",
"scenario": "invalid creds return 401",
"userModel": "edge",
"expectedEvidence": "curl /login -d '{bad}' returns 401",
"capturedEvidence": null,
"status": "pending"
},
{
"id": "C003",
"scenario": "no regression in /health",
"userModel": "regression",
"expectedEvidence": "GET /health returns 200 OK after auth merge",
"capturedEvidence": null,
"status": "pending"
}
]
},
{
"id": "G002",
"title": "Add rate limiting",
"objective": "Throttle login by IP",
"status": "in_progress",
"successCriteria": [
{
"id": "C001",
"scenario": "limit kicks at N reqs",
"userModel": "happy",
"expectedEvidence": "100 reqs from same IP -> last is 429",
"capturedEvidence": null,
"status": "pending"
},
{
"id": "C002",
"scenario": "different IPs not affected",
"userModel": "edge",
"expectedEvidence": "concurrent 2 IPs both succeed",
"capturedEvidence": null,
"status": "pending"
},
{
"id": "C003",
"scenario": "limiter does not block /health",
"userModel": "regression",
"expectedEvidence": "/health unaffected during throttle",
"capturedEvidence": null,
"status": "pending"
}
]
},
{
"id": "G003",
"title": "Integration tests",
"objective": "End-to-end suite",
"status": "complete",
"successCriteria": [
{
"id": "C001",
"scenario": "all int tests green",
"userModel": "happy",
"expectedEvidence": "npm run test:integration exit 0",
"capturedEvidence": "npm run test:integration exit 0, 12/12 tests",
"status": "pass",
"capturedAt": "2026-05-23T00:30:00.000Z"
},
{
"id": "C002",
"scenario": "no flaky 3x rerun",
"userModel": "edge",
"expectedEvidence": "3 reruns all green",
"capturedEvidence": "3 reruns all green, no flakes",
"status": "pass",
"capturedAt": "2026-05-23T00:31:00.000Z"
},
{
"id": "C003",
"scenario": "no new console errors",
"userModel": "regression",
"expectedEvidence": "0 errors in build log",
"capturedEvidence": "no console errors",
"status": "pass",
"capturedAt": "2026-05-23T00:32:00.000Z"
}
]
}
]
}
@@ -0,0 +1,18 @@
{
"aiSlopCleaner": { "status": "passed", "evidence": "no slop detected after cleaner run" },
"verification": {
"status": "passed",
"commands": ["npm test", "npm run build"],
"evidence": "all tests pass + build green"
},
"codeReview": {
"recommendation": "APPROVE",
"architectStatus": "CLEAR",
"evidence": "review synthesis: ship it"
},
"criteriaCoverage": {
"totalCriteria": 9,
"passCount": 9,
"adversarialClassesCovered": ["malformed_input", "prompt_injection", "stale_state"]
}
}
@@ -0,0 +1,8 @@
{
"kind": "add_subgoal",
"title": "Investigate auth blocker",
"objective": "Validate the blocker, capture evidence, and report findings.",
"evidence": "log/test output showing the blocker",
"rationale": "blocker materially changes safe execution order",
"source": "cli"
}
@@ -0,0 +1,10 @@
{
"cwd": "/repo",
"hook_event_name": "UserPromptSubmit",
"model": "gpt-5.5",
"permission_mode": "default",
"prompt": "OMO_ULW_LOOP_STEER: {\"kind\":\"annotate_ledger\",\"source\":\"user_prompt_submit\",\"evidence\":\"test note\",\"rationale\":\"testing hook\"}",
"session_id": "s1",
"transcript_path": "/tmp/transcript.log",
"turn_id": "t1"
}
@@ -0,0 +1,327 @@
import { describe, expect, it } from "vitest";
import {
aggregateCodexObjective,
codexGoalMode,
compatibleCodexObjectives,
expectedCodexObjective,
firstUnresolvedCriterion,
hasAllCriteriaPass,
isFinalRunCompletionCandidate,
isUlwLoopDone,
ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE,
} from "../src/goal-status.js";
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
function makeCriterion(overrides: Partial<UlwLoopSuccessCriterion> = {}): UlwLoopSuccessCriterion {
return {
id: "C001",
scenario: "happy path",
userModel: "happy",
expectedEvidence: "observable proof",
capturedEvidence: null,
status: "pending",
...overrides,
};
}
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Goal one",
objective: "Complete goal one",
status: "pending",
successCriteria: [],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function makePlan(overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
goals: [],
...overrides,
};
}
describe("isUlwLoopDone", () => {
it("returns true when all goals complete", () => {
// given
const plan = makePlan({
goals: [makeGoal({ status: "complete" }), makeGoal({ id: "G002", status: "complete" })],
});
// when
const done = isUlwLoopDone(plan);
// then
expect(done).toBe(true);
});
it("returns false when any pending remains", () => {
// given
const plan = makePlan({ goals: [makeGoal({ status: "complete" }), makeGoal({ id: "G002", status: "pending" })] });
// when
const done = isUlwLoopDone(plan);
// then
expect(done).toBe(false);
});
it("treats superseded-with-complete-replacements as resolved", () => {
// given
const replacement = makeGoal({ id: "G002", status: "complete" });
const superseded = makeGoal({
id: "G001",
status: "pending",
steeringStatus: "superseded",
supersededBy: [replacement.id],
});
const plan = makePlan({ goals: [superseded, replacement] });
// when
const done = isUlwLoopDone(plan);
// then
expect(done).toBe(true);
});
});
describe("isFinalRunCompletionCandidate", () => {
it("returns true when only one unresolved goal remains", () => {
// given
const finalGoal = makeGoal({ id: "G002", status: "pending" });
const plan = makePlan({ goals: [makeGoal({ status: "complete" }), finalGoal] });
// when
const candidate = isFinalRunCompletionCandidate(plan, finalGoal);
// then
expect(candidate).toBe(true);
});
it("returns false when multiple unresolved", () => {
// given
const goal = makeGoal({ id: "G001", status: "pending" });
const plan = makePlan({ goals: [goal, makeGoal({ id: "G002", status: "pending" })] });
// when
const candidate = isFinalRunCompletionCandidate(plan, goal);
// then
expect(candidate).toBe(false);
});
});
describe("codexGoalMode", () => {
it("defaults to per_story when undefined", () => {
// when
const mode = codexGoalMode(makePlan());
// then
expect(mode).toBe("per_story");
});
it("returns aggregate when explicitly aggregate", () => {
// when
const mode = codexGoalMode(makePlan({ codexGoalMode: "aggregate" }));
// then
expect(mode).toBe("aggregate");
});
});
describe("expectedCodexObjective", () => {
it("aggregate mode returns plan.codexObjective", () => {
// given
const goal = makeGoal({ objective: "story objective" });
const plan = makePlan({ codexGoalMode: "aggregate", codexObjective: "aggregate objective" });
// when
const objective = expectedCodexObjective(plan, goal);
// then
expect(objective).toBe("aggregate objective");
});
it("aggregate mode falls back to ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE when codexObjective missing", () => {
// given
const goal = makeGoal({ objective: "story objective" });
const plan = makePlan({ codexGoalMode: "aggregate" });
// when
const objective = expectedCodexObjective(plan, goal);
// then
expect(objective).toBe(ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE);
});
it("per_story mode returns goal.objective", () => {
// given
const goal = makeGoal({ objective: "story objective" });
const plan = makePlan({ codexGoalMode: "per_story", codexObjective: "aggregate objective" });
// when
const objective = expectedCodexObjective(plan, goal);
// then
expect(objective).toBe("story objective");
});
});
describe("aggregateCodexObjective", () => {
it("returns plan.codexObjective when set", () => {
// when
const objective = aggregateCodexObjective(makePlan({ codexObjective: "aggregate objective" }));
// then
expect(objective).toBe("aggregate objective");
});
it("falls back to ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE", () => {
// when
const objective = aggregateCodexObjective(makePlan());
// then
expect(objective).toBe(ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE);
});
});
describe("compatibleCodexObjectives", () => {
it("includes aggregate objective + aliases", () => {
// given
const plan = makePlan({
codexObjective: "aggregate objective",
codexObjectiveAliases: ["legacy one", "legacy two"],
});
// when
const objectives = compatibleCodexObjectives(plan);
// then
expect(objectives).toEqual(["aggregate objective", "legacy one", "legacy two"]);
});
});
describe("hasAllCriteriaPass", () => {
it("returns true when all criteria pass", () => {
// given
const goal = makeGoal({
successCriteria: [makeCriterion({ status: "pass" }), makeCriterion({ id: "C002", status: "pass" })],
});
// when
const passed = hasAllCriteriaPass(goal);
// then
expect(passed).toBe(true);
});
it("returns false when any criterion pending", () => {
// given
const goal = makeGoal({
successCriteria: [makeCriterion({ status: "pass" }), makeCriterion({ id: "C002", status: "pending" })],
});
// when
const passed = hasAllCriteriaPass(goal);
// then
expect(passed).toBe(false);
});
it("returns false when any criterion fail", () => {
// given
const goal = makeGoal({
successCriteria: [makeCriterion({ status: "pass" }), makeCriterion({ id: "C002", status: "fail" })],
});
// when
const passed = hasAllCriteriaPass(goal);
// then
expect(passed).toBe(false);
});
it("returns false when any criterion blocked", () => {
// given
const goal = makeGoal({
successCriteria: [makeCriterion({ status: "pass" }), makeCriterion({ id: "C002", status: "blocked" })],
});
// when
const passed = hasAllCriteriaPass(goal);
// then
expect(passed).toBe(false);
});
it("returns false for empty criteria array", () => {
// when
const passed = hasAllCriteriaPass(makeGoal({ successCriteria: [] }));
// then
expect(passed).toBe(false);
});
});
describe("firstUnresolvedCriterion", () => {
it("returns first non-pass criterion", () => {
// given
const unresolved = makeCriterion({ id: "C002", status: "fail" });
const goal = makeGoal({ successCriteria: [makeCriterion({ status: "pass" }), unresolved] });
// when
const criterion = firstUnresolvedCriterion(goal);
// then
expect(criterion).toBe(unresolved);
});
it("returns undefined when all pass", () => {
// given
const goal = makeGoal({
successCriteria: [makeCriterion({ status: "pass" }), makeCriterion({ id: "C002", status: "pass" })],
});
// when
const criterion = firstUnresolvedCriterion(goal);
// then
expect(criterion).toBeUndefined();
});
it("returns first pending in mixed pass/pending/fail", () => {
// given
const pending = makeCriterion({ id: "C002", status: "pending" });
const goal = makeGoal({
successCriteria: [makeCriterion({ status: "pass" }), pending, makeCriterion({ id: "C003", status: "fail" })],
});
// when
const criterion = firstUnresolvedCriterion(goal);
// then
expect(criterion).toBe(pending);
});
});
describe("ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE", () => {
it("references the .omo/ulw-loop path and excludes the legacy workspace", () => {
const legacyWorkspace = [".", "om", "x"].join("");
expect(ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE).toContain(".omo/ulw-loop");
expect(ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE).not.toContain(legacyWorkspace);
});
});
@@ -0,0 +1,155 @@
// biome-ignore-all format: smoke test pulls verbatim JSON for structural assertion.
import { readFile, stat } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
async function readText(relative: string): Promise<string> {
return readFile(join(repoRoot, relative), "utf8");
}
async function readJson(relative: string): Promise<unknown> {
return JSON.parse(await readText(relative));
}
describe("package.json", () => {
it("declares ESM + npm + Node >=20", async () => {
const pkg = await readJson("package.json") as Record<string, unknown>;
expect(pkg["type"]).toBe("module");
expect(pkg["packageManager"]).toBe("npm@11.12.1");
expect((pkg["engines"] as Record<string, unknown>)["node"]).toBe(">=20.0.0");
});
it("exposes the omo binary pointing at dist/cli.js", async () => {
const pkg = await readJson("package.json") as Record<string, unknown>;
const bin = pkg["bin"] as Record<string, string>;
expect(bin["omo"]).toBe("./dist/cli.js");
});
it("ships the expected files for npm publish", async () => {
const pkg = await readJson("package.json") as Record<string, unknown>;
const files = pkg["files"] as readonly string[];
expect(files).toContain("dist");
expect(files).toContain("hooks");
expect(files).toContain("skills");
expect(files).not.toContain(".codex-plugin");
});
});
describe("component plugin identity", () => {
it("is owned by the aggregate OMO plugin root", async () => {
await expect(readText(".codex-plugin/plugin.json")).rejects.toMatchObject({ code: "ENOENT" });
});
});
describe("hooks/hooks.json", () => {
it("registers UserPromptSubmit with PLUGIN_ROOT interpolation", async () => {
const hooks = await readJson("hooks/hooks.json") as Record<string, unknown>;
const events = (hooks["hooks"] as Record<string, unknown>)["UserPromptSubmit"] as readonly Record<string, unknown>[];
expect(events.length).toBeGreaterThan(0);
const command = ((events[0]?.["hooks"] as readonly Record<string, unknown>[])[0]?.["command"]) as string;
expect(command).toContain(`$${"{PLUGIN_ROOT}"}`);
expect(command).toContain("dist/cli.js");
expect(command).toContain("hook user-prompt-submit");
});
it("#given ulw-loop component is enabled #when hooks are inspected #then create_goal PreToolUse guard is registered", async () => {
const text = await readText("hooks/hooks.json");
expect(text).toContain('"PreToolUse"');
expect(text).toContain('"matcher": "^create_goal$"');
expect(text).toContain("hook pre-tool-use");
});
});
describe("src/cli.ts", () => {
it("starts with #!/usr/bin/env node shebang", async () => {
const text = await readText("src/cli.ts");
expect(text.split("\n")[0]).toBe("#!/usr/bin/env node");
});
});
describe("skills/ulw-loop/SKILL.md", () => {
it("exists", async () => {
const info = await stat(join(repoRoot, "skills/ulw-loop/SKILL.md"));
expect(info.isFile()).toBe(true);
});
it("#given Codex skill hinting #when ulw-loop skill metadata is inspected #then ulw-loop is the primary mention name", async () => {
const text = await readText("skills/ulw-loop/SKILL.md");
expect(text).toMatch(/^---\nname: ulw-loop\n/m);
expect(text).toContain("Goal-like loop that uses ultrawork mode to decompose work into systematic, evidence-bound steps.");
expect(text).toContain("short-description: Goal-like ultrawork loop for systematic decomposition");
});
it("#given Codex dollar hinting #when querying ulw-loop #then ulw-loop surfaces the ulw-loop alias", async () => {
const text = await readText("skills/ulw-loop/agents/openai.yaml");
expect(text).toContain('display_name: "ulw loop"');
expect(text).not.toContain("ulw-loop / ulw-loop");
expect(text).toContain('short_description: "Goal-like ultrawork loop for systematic decomposition"');
expect(text).toContain("Use $ulw-loop");
});
it("#given Codex dollar hinting #when querying ulw-loop #then ulw-loop remains discoverable as an alias", async () => {
const text = await readText("skills/ulw-loop/agents/openai.yaml");
expect(text).toContain("search_terms:");
expect(text).toContain('- "ulw-loop"');
});
it("contains no omx references", async () => {
const text = await readText("skills/ulw-loop/SKILL.md");
expect(text.toLowerCase()).not.toContain("omx");
});
it("references the success criteria and record-evidence vocabulary", async () => {
const text = await readText("skills/ulw-loop/SKILL.md");
expect(text.toLowerCase()).toMatch(/success criteria|successcriteria/);
expect(text.toLowerCase()).toContain("record-evidence");
});
it("#given omo is absent from PATH #when bootstrap instructions are read #then local cached CLI fallback is documented", async () => {
const text = await readText("skills/ulw-loop/SKILL.md");
expect(text).toContain("If `omo` is absent from PATH");
expect(text).toContain("ULW_LOOP_CLI");
expect(text).toContain("components/ulw-loop/dist/cli.js");
});
it("#given empty PATH #when bootstrap instructions are read #then handles empty PATH without losing notepad bootstrap", async () => {
const text = await readText("skills/ulw-loop/SKILL.md");
expect(text).toContain("If PATH is empty");
expect(text).toContain("ULW_LOOP_NODE");
expect(text).toContain(".omo/ulw-loop/bootstrap-notepad.md");
expect(text).not.toContain("ls -1");
});
it("uses the .omo workspace path", async () => {
const text = await readText("skills/ulw-loop/SKILL.md");
expect(text).toContain(".omo/ulw-loop");
});
});
describe("source LOC budget", () => {
it("every source file stays at or under 250 pure LOC", async () => {
const files = [
"src/types.ts", "src/paths.ts", "src/plan-io.ts", "src/plan-crud.ts", "src/goal-status.ts",
"src/evidence.ts", "src/quality-gate.ts", "src/checkpoint.ts", "src/review-blockers.ts",
"src/steering.ts", "src/codex-goal-instruction.ts", "src/codex-goal-snapshot.ts", "src/codex-hook.ts",
"src/cli.ts", "src/cli-arg-parser.ts", "src/cli-output.ts", "src/cli-steering.ts", "src/cli-commands.ts",
];
for (const file of files) {
const text = await readText(file);
const pure = text.split("\n").filter((line) => {
const trimmed = line.trim();
return trimmed.length > 0 && !trimmed.startsWith("//");
}).length;
expect(pure, `${file} pure LOC`).toBeLessThanOrEqual(250);
}
});
});
@@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import { repoRelative, ulwLoopBriefPath, ulwLoopDir, ulwLoopGoalsPath, ulwLoopLedgerPath } from "../src/paths.ts";
describe("ulwLoopDir(repo)", () => {
it("returns repo + '/.omo/ulw-loop'", () => {
// when/then
expect(ulwLoopDir("/repo")).toBe("/repo/.omo/ulw-loop");
});
});
describe("ulw-loop*Path helpers", () => {
it("compose artifact filenames under ulwLoopDir", () => {
// when/then
expect(ulwLoopBriefPath("/r")).toBe("/r/.omo/ulw-loop/brief.md");
expect(ulwLoopGoalsPath("/r")).toBe("/r/.omo/ulw-loop/goals.json");
expect(ulwLoopLedgerPath("/r")).toBe("/r/.omo/ulw-loop/ledger.jsonl");
});
});
describe("repoRelative", () => {
it("strips repo prefix when path is inside repo", () => {
// when/then
expect(repoRelative("/repo/.omo/ulw-loop/goals.json", "/repo")).toBe(".omo/ulw-loop/goals.json");
});
it("returns absolute when path is outside repo", () => {
// when/then
expect(repoRelative("/elsewhere/file", "/repo")).toBe("/elsewhere/file");
});
});
@@ -0,0 +1,256 @@
import { mkdir, mkdtemp, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { ulwLoopBriefPath, ulwLoopGoalsPath, ulwLoopLedgerPath } from "../src/paths.js";
import {
addUlwLoopGoal,
createUlwLoopPlan,
deriveGoalCandidates,
seedDefaultSuccessCriteria,
startNextUlwLoop,
summarizeUlwLoopPlan,
} from "../src/plan-crud.js";
import { writePlan } from "../src/plan-io.js";
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
async function makeRepo(): Promise<string> {
return mkdtemp(join(tmpdir(), "ug-crud-"));
}
async function readBriefFixture(): Promise<string> {
return readFile(join(process.cwd(), "test", "fixtures", "sample-brief.md"), "utf8");
}
async function ledgerKinds(repoRoot: string): Promise<string[]> {
const raw = await readFile(ulwLoopLedgerPath(repoRoot), "utf8");
return raw
.split(/\r?\n/)
.filter(Boolean)
.map((line) => JSON.parse(line).kind);
}
function criterion(status: UlwLoopSuccessCriterion["status"]): UlwLoopSuccessCriterion {
const [base] = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
if (base === undefined) throw new Error("expected seeded criterion");
return { ...base, status };
}
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Build auth service",
objective: "Implement JWT auth endpoint",
status: "pending",
successCriteria: seedDefaultSuccessCriteria(0, "Implement JWT auth endpoint"),
attempt: 0,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function makePlan(goals: UlwLoopItem[]): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
codexGoalMode: "aggregate",
goals,
};
}
function scheduled(result: Awaited<ReturnType<typeof startNextUlwLoop>>) {
if ("done" in result) throw new Error("expected scheduled goal");
return result;
}
describe("seedDefaultSuccessCriteria", () => {
it("produces 3 criteria with C001/C002/C003 ids", () => {
const cs = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
expect(cs).toHaveLength(3);
expect(cs.map((c) => c.id)).toEqual(["C001", "C002", "C003"]);
});
it("covers happy + edge + regression user models", () => {
const cs = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
expect(cs.map((c) => c.userModel).sort()).toEqual(["edge", "happy", "regression"]);
});
it("seeds all criteria as pending with null capturedEvidence", () => {
const cs = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
for (const c of cs) {
expect(c.status).toBe("pending");
expect(c.capturedEvidence).toBeNull();
}
});
});
describe("createUlwLoopPlan", () => {
it("creates .omo/ulw-loop/{brief.md, goals.json, ledger.jsonl} in repoRoot", async () => {
const repoRoot = await makeRepo();
const brief = await readBriefFixture();
await createUlwLoopPlan(repoRoot, { brief });
expect(await readFile(ulwLoopBriefPath(repoRoot), "utf8")).toBe(brief.endsWith("\n") ? brief : `${brief}\n`);
expect(await readFile(ulwLoopGoalsPath(repoRoot), "utf8")).toContain("G001-build-the-jwt-auth-endpoint");
expect(await ledgerKinds(repoRoot)).toEqual(["plan_created"]);
});
it("seeds at least 3 successCriteria per goal", async () => {
const plan = await createUlwLoopPlan(await makeRepo(), { brief: await readBriefFixture() });
expect(plan.goals).toHaveLength(3);
expect(plan.goals.every((goal) => goal.successCriteria.length >= 3)).toBe(true);
});
it("refuses overwrite of an existing plan without --force", async () => {
const repoRoot = await makeRepo();
await createUlwLoopPlan(repoRoot, { brief: "first" });
await expect(createUlwLoopPlan(repoRoot, { brief: "second" })).rejects.toThrow(UlwLoopError);
await expect(createUlwLoopPlan(repoRoot, { brief: "second" })).rejects.toThrow("Refusing to overwrite");
});
it("aggregate is the default codexGoalMode", async () => {
const plan = await createUlwLoopPlan(await makeRepo(), { brief: "Ship the feature" });
expect(plan.codexGoalMode).toBe("aggregate");
expect(plan.codexObjective).toContain(".omo/ulw-loop/goals.json");
});
});
describe("deriveGoalCandidates", () => {
it("extracts bullets as goals", () => {
expect(deriveGoalCandidates("# Brief\n\n- Build auth\n- Add tests")).toEqual([
{ title: "Build auth", objective: "Build auth" },
{ title: "Add tests", objective: "Add tests" },
]);
});
it("falls back to paragraph parsing when no bullets", () => {
expect(deriveGoalCandidates("First objective.\n\nSecond objective.").map((goal) => goal.objective)).toEqual([
"First objective.",
"Second objective.",
]);
});
it("returns single default goal for empty/whitespace brief", () => {
expect(deriveGoalCandidates(" \n\t ")).toEqual([
{ title: "Complete the requested project objective.", objective: "Complete the requested project objective." },
]);
});
});
describe("addUlwLoopGoal", () => {
it("appends a new goal to plan with seeded successCriteria", async () => {
const repoRoot = await makeRepo();
await createUlwLoopPlan(repoRoot, { brief: "Build auth" });
const { plan, goal } = await addUlwLoopGoal(repoRoot, { title: "Add rate limit", objective: "Throttle login" });
expect(plan.goals).toHaveLength(2);
expect(goal.id).toBe("G002-add-rate-limit");
expect(goal.successCriteria).toHaveLength(3);
});
it("appends a ledger entry for goal_added", async () => {
const repoRoot = await makeRepo();
await createUlwLoopPlan(repoRoot, { brief: "Build auth" });
await addUlwLoopGoal(repoRoot, { title: "Add rate limit", objective: "Throttle login" });
expect(await ledgerKinds(repoRoot)).toEqual(["plan_created", "goal_added"]);
});
});
describe("startNextUlwLoop", () => {
it("picks the first pending goal", async () => {
const repoRoot = await makeRepo();
await createUlwLoopPlan(repoRoot, { brief: "- First\n- Second" });
const result = scheduled(await startNextUlwLoop(repoRoot, {}));
expect(result.goal.id).toBe("G001-first");
expect(result.goal.status).toBe("in_progress");
expect(result.resumed).toBe(false);
});
it("resumes the in_progress goal when one exists", async () => {
const repoRoot = await makeRepo();
const plan = await createUlwLoopPlan(repoRoot, { brief: "- First\n- Second" });
const active = makeGoal({ ...plan.goals[1], status: "in_progress" });
await writePlan(repoRoot, { ...plan, goals: [makeGoal({ ...plan.goals[0] }), active], activeGoalId: active.id });
const result = scheduled(await startNextUlwLoop(repoRoot, {}));
expect(result.goal.id).toBe(active.id);
expect(result.resumed).toBe(true);
});
it("with retryFailed picks first failed (non-blocked) goal", async () => {
const repoRoot = await makeRepo();
const failed = makeGoal({ status: "failed", failureReason: "flake" });
await mkdir(join(repoRoot, ".omo", "ulw-loop"), { recursive: true });
await writePlan(repoRoot, makePlan([failed]));
const result = scheduled(await startNextUlwLoop(repoRoot, { retryFailed: true }));
expect(result.goal.id).toBe("G001");
expect(result.goal.attempt).toBe(1);
expect(await ledgerKinds(repoRoot)).toEqual(["goal_retried", "goal_started"]);
});
it("returns { done: true } when no eligible goals remain", async () => {
const repoRoot = await makeRepo();
await mkdir(join(repoRoot, ".omo", "ulw-loop"), { recursive: true });
await writePlan(repoRoot, makePlan([makeGoal({ status: "complete" })]));
const result = await startNextUlwLoop(repoRoot, {});
expect(result).toMatchObject({ done: true });
});
});
describe("summarizeUlwLoopPlan", () => {
it("counts goals by status", () => {
const plan = makePlan([
makeGoal({ id: "G001", status: "pending" }),
makeGoal({ id: "G002", status: "in_progress" }),
makeGoal({ id: "G003", status: "complete" }),
makeGoal({ id: "G004", status: "failed" }),
makeGoal({ id: "G005", status: "blocked", steeringStatus: "blocked" }),
makeGoal({ id: "G006", status: "review_blocked" }),
makeGoal({ id: "G007", status: "needs_user_decision", steeringStatus: "superseded" }),
]);
expect(summarizeUlwLoopPlan(plan)).toMatchObject({
total: 7,
pending: 1,
in_progress: 1,
complete: 1,
failed: 1,
blocked: 1,
review_blocked: 1,
needs_user_decision: 1,
superseded: 1,
});
});
it("aggregates criteria pass/pending/fail/blocked across all goals", () => {
const plan = makePlan([
makeGoal({ successCriteria: [criterion("pass"), criterion("pending")] }),
makeGoal({ id: "G002", successCriteria: [criterion("fail"), criterion("blocked"), criterion("pending")] }),
]);
expect(summarizeUlwLoopPlan(plan).criteria).toEqual({ total: 5, pass: 1, pending: 2, fail: 1, blocked: 1 });
});
});
@@ -0,0 +1,239 @@
import { copyFile, mkdir, mkdtemp, readdir, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { beforeEach, describe, expect, it } from "vitest";
import { ulwLoopDir, ulwLoopGoalsPath, ulwLoopLedgerPath } from "../src/paths.js";
import {
appendLedger,
readSteeringLedgerEntries,
readUlwLoopPlan,
withUlwLoopMutationLock,
writePlan,
} from "../src/plan-io.js";
import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
const STABLE_OBJECTIVE =
"Complete the durable ulw-loop plan in .omo/ulw-loop/goals.json, including later accepted/appended stories, under the original brief constraints; use .omo/ulw-loop/ledger.jsonl as the audit trail.";
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Build auth service",
objective: "Implement JWT auth endpoint",
status: "pending",
successCriteria: [],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function makePlan(overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
codexGoalMode: "aggregate",
codexObjective: STABLE_OBJECTIVE,
codexObjectiveAliases: [],
goals: [makeGoal()],
...overrides,
};
}
function entry(kind: UlwLoopLedgerEntry["kind"], goalId = "G001"): UlwLoopLedgerEntry {
return { at: NOW, kind, goalId };
}
async function makeRepo(): Promise<string> {
return mkdtemp(join(tmpdir(), "ug-io-"));
}
async function writeRawPlan(repoRoot: string, plan: UlwLoopPlan): Promise<void> {
await mkdir(ulwLoopDir(repoRoot), { recursive: true });
await writeFile(ulwLoopGoalsPath(repoRoot), `${JSON.stringify(plan, null, 2)}\n`, "utf8");
}
async function readLedgerLines(repoRoot: string): Promise<string[]> {
const raw = await readFile(ulwLoopLedgerPath(repoRoot), "utf8");
return raw.split(/\r?\n/).filter(Boolean);
}
describe("readUlwLoopPlan", () => {
let repoRoot = "";
beforeEach(async () => {
// given
repoRoot = await makeRepo();
});
it("throws UlwLoopError when goals.json is missing", async () => {
// when/then
await expect(readUlwLoopPlan(repoRoot)).rejects.toThrow(UlwLoopError);
await expect(readUlwLoopPlan(repoRoot)).rejects.toThrow("omo ulw-loop create-goals");
});
it("returns parsed plan when fixture is present", async () => {
// given
await mkdir(ulwLoopDir(repoRoot), { recursive: true });
await copyFile(join(process.cwd(), "test", "fixtures", "sample-plan.json"), ulwLoopGoalsPath(repoRoot));
// when
const plan = await readUlwLoopPlan(repoRoot);
// then
expect(plan.version).toBe(1);
expect(plan.codexGoalMode).toBe("aggregate");
expect(plan.goals).toHaveLength(3);
expect(plan.goals[0]?.successCriteria).toHaveLength(3);
});
it("migrates legacy aggregate objective on read + writes aggregate_objective_migrated ledger entry + retains alias", async () => {
// given
const legacyObjective = "Complete all ulw-loop stories in .omo/ulw-loop/goals.json: G001 Build auth service";
await writeRawPlan(repoRoot, makePlan({ codexObjective: legacyObjective }));
// when
const plan = await readUlwLoopPlan(repoRoot);
// then
expect(plan.codexObjective).toBe(STABLE_OBJECTIVE);
expect(plan.codexObjectiveAliases).toContain(legacyObjective);
const persisted = JSON.parse(await readFile(ulwLoopGoalsPath(repoRoot), "utf8"));
expect(persisted).toMatchObject({ codexObjective: STABLE_OBJECTIVE, codexObjectiveAliases: [legacyObjective] });
const lines = await readLedgerLines(repoRoot);
expect(lines).toHaveLength(1);
expect(JSON.parse(lines[0] ?? "{}")).toMatchObject({
kind: "aggregate_objective_migrated",
before: { codexObjective: legacyObjective },
});
});
});
describe("writePlan", () => {
it("writes goals.json atomically with no temp file left behind", async () => {
// given
const repoRoot = await makeRepo();
// when
await writePlan(repoRoot, makePlan());
// then
const raw = await readFile(ulwLoopGoalsPath(repoRoot), "utf8");
expect(JSON.parse(raw)).toMatchObject({ version: 1, goals: [{ id: "G001" }] });
expect((await readdir(ulwLoopDir(repoRoot))).filter((name) => name.endsWith(".tmp"))).toEqual([]);
});
it("overwrites existing file", async () => {
// given
const repoRoot = await makeRepo();
await writePlan(repoRoot, makePlan({ codexObjective: "first" }));
// when
await writePlan(repoRoot, makePlan({ codexObjective: "second" }));
// then
expect(JSON.parse(await readFile(ulwLoopGoalsPath(repoRoot), "utf8"))).toMatchObject({
codexObjective: "second",
});
});
});
describe("appendLedger", () => {
it("appends a single JSONL line to ledger.jsonl", async () => {
// given
const repoRoot = await makeRepo();
const ledgerEntry = entry("goal_started");
// when
await appendLedger(repoRoot, ledgerEntry);
// then
expect(await readLedgerLines(repoRoot)).toEqual([JSON.stringify(ledgerEntry)]);
});
it("creates ledger.jsonl if missing", async () => {
// given
const repoRoot = await makeRepo();
// when
await appendLedger(repoRoot, entry("goal_completed"));
// then
expect(await readFile(ulwLoopLedgerPath(repoRoot), "utf8")).toContain("goal_completed");
});
it("preserves prior entries", async () => {
// given
const repoRoot = await makeRepo();
const first = entry("goal_started");
const second = entry("goal_completed");
// when
await appendLedger(repoRoot, first);
await appendLedger(repoRoot, second);
// then
expect(await readLedgerLines(repoRoot)).toEqual([JSON.stringify(first), JSON.stringify(second)]);
});
});
describe("readSteeringLedgerEntries", () => {
it("returns only steering-related event kinds", async () => {
// given
const repoRoot = await makeRepo();
await appendLedger(repoRoot, entry("steering_accepted"));
await appendLedger(repoRoot, entry("goal_started"));
await appendLedger(repoRoot, entry("steering_rejected"));
await appendLedger(repoRoot, entry("criteria_revised"));
// when
const entries = await readSteeringLedgerEntries(repoRoot);
// then
expect(entries.map((item) => item.kind)).toEqual(["steering_accepted", "steering_rejected", "criteria_revised"]);
});
it("returns empty array when ledger missing", async () => {
// given
const repoRoot = await makeRepo();
// when/then
await expect(readSteeringLedgerEntries(repoRoot)).resolves.toEqual([]);
});
});
describe("withUlwLoopMutationLock", () => {
it("serializes concurrent invocations", async () => {
// given
const repoRoot = await makeRepo();
const counterPath = join(repoRoot, "counter.txt");
let active = 0;
let maxActive = 0;
await writeFile(counterPath, "0", "utf8");
// when
await Promise.all(
[1, 2, 3].map((_) =>
withUlwLoopMutationLock(repoRoot, async () => {
active += 1;
maxActive = Math.max(maxActive, active);
const current = Number(await readFile(counterPath, "utf8"));
await Promise.resolve();
await writeFile(counterPath, String(current + 1), "utf8");
active -= 1;
}),
),
);
// then
expect(maxActive).toBe(1);
expect(await readFile(counterPath, "utf8")).toBe("3");
});
});
@@ -0,0 +1,203 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import {
classifyExternalAuthorizationBlocker,
clearGoalBlockerFields,
normalizeBlockerEvidence,
sameBlockerOccurrences,
validateQualityGate,
} from "../src/quality-gate.js";
import type { UlwLoopItem, UlwLoopPlan } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
const VALID_GATE = {
aiSlopCleaner: { status: "passed", evidence: "no slop detected after cleaner run" },
verification: { status: "passed", commands: ["npm test"], evidence: "all tests pass" },
codeReview: { recommendation: "APPROVE", architectStatus: "CLEAR", evidence: "ship it" },
criteriaCoverage: { totalCriteria: 2, passCount: 2, adversarialClassesCovered: ["malformed_input"] },
} as const;
interface GoalWithBlocker extends UlwLoopItem {
blocker?: { readonly signature: string };
blockerEvidence?: string;
blockerOccurrences?: number;
blockedAt?: string;
}
function makeGate(overrides: Record<string, unknown> = {}): Record<string, unknown> {
return { ...VALID_GATE, ...overrides };
}
function getQualityGateError(input: unknown): UlwLoopError {
try {
validateQualityGate(input);
} catch (error) {
if (error instanceof UlwLoopError) return error;
throw error;
}
throw new Error("Expected UlwLoopError");
}
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Goal one",
objective: "Complete goal one",
status: "pending",
successCriteria: [],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function makePlan(goals: UlwLoopItem[]): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
goals,
};
}
describe("validateQualityGate", () => {
it("accepts valid quality gate from fixture", async () => {
// given
const raw = await readFile(new URL("./fixtures/sample-quality-gate.json", import.meta.url), "utf8");
const parsed: unknown = JSON.parse(raw);
// when
const gate = validateQualityGate(parsed);
// then
expect(gate.aiSlopCleaner.status).toBe("passed");
expect(gate).toMatchObject({ criteriaCoverage: { totalCriteria: 9, passCount: 9 } });
});
it("throws UlwLoopError when aiSlopCleaner missing", () => {
// when
const error = getQualityGateError(makeGate({ aiSlopCleaner: undefined }));
// then
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
});
it("throws UlwLoopError when verification missing", () => {
// when
const error = getQualityGateError(makeGate({ verification: undefined }));
// then
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
});
it("throws UlwLoopError when codeReview missing", () => {
// when
const error = getQualityGateError(makeGate({ codeReview: undefined }));
// then
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
});
it("throws UlwLoopError when criteriaCoverage missing (NEW)", () => {
// when
const error = getQualityGateError(makeGate({ criteriaCoverage: undefined }));
// then
expect(error.code).toBe("ULW_LOOP_QUALITY_GATE_INVALID");
});
it("throws UlwLoopError when criteriaCoverage.passCount < totalCriteria (NEW)", () => {
// when
const error = getQualityGateError(
makeGate({ criteriaCoverage: { totalCriteria: 3, passCount: 2, adversarialClassesCovered: [] } }),
);
// then
expect(error.message).toContain("criteriaCoverage.passCount");
});
it("throws UlwLoopError when codeReview.recommendation is not APPROVE", () => {
// when
const error = getQualityGateError(
makeGate({ codeReview: { ...VALID_GATE.codeReview, recommendation: "COMMENT" } }),
);
// then
expect(error.message).toContain("recommendation");
});
it("throws UlwLoopError when architectStatus is not CLEAR", () => {
// when
const error = getQualityGateError(
makeGate({ codeReview: { ...VALID_GATE.codeReview, architectStatus: "WATCH" } }),
);
// then
expect(error.message).toContain("architectStatus");
});
});
describe("classifyExternalAuthorizationBlocker", () => {
it("returns GHCR signature when evidence mentions ghcr.io auth failure", () => {
expect(
classifyExternalAuthorizationBlocker("ghcr.io returned 401 authentication required for package pull"),
).toBe("GHCR_PULL_ACCESS:HTTP_401_ANONYMOUS:GHCR_VISIBILITY_OR_CREDENTIAL_REQUIRED");
});
it("returns generic auth signature for generic 401 evidence", () => {
expect(classifyExternalAuthorizationBlocker("Registry returned 401 because credentials are missing")).toBe(
"EXTERNAL_AUTHORIZATION_REQUIRED",
);
});
it("returns null when no auth keywords", () => {
expect(classifyExternalAuthorizationBlocker("build failed because tests failed")).toBeNull();
});
});
describe("normalizeBlockerEvidence", () => {
it("collapses whitespace + lowercases", () => {
expect(normalizeBlockerEvidence(" GHCR.IO\n\tNeeds TOKEN ")).toBe("ghcr.io needs token");
});
});
describe("sameBlockerOccurrences", () => {
it("counts goals matching signature", () => {
// given
const nested: GoalWithBlocker = { ...makeGoal({ id: "G002" }), blocker: { signature: "AUTH" } };
const plan = makePlan([makeGoal({ blockerSignature: "AUTH" }), nested, makeGoal({ id: "G003" })]);
// when/then
expect(sameBlockerOccurrences(plan, "AUTH")).toBe(2);
});
});
describe("clearGoalBlockerFields", () => {
it("clears all 5 blocker fields", () => {
// given
const goal: GoalWithBlocker = {
...makeGoal({ blockerSignature: "AUTH" }),
blocker: { signature: "AUTH" },
blockerEvidence: "401 unauthorized",
blockerOccurrences: 2,
blockedAt: NOW,
};
// when
clearGoalBlockerFields(goal);
// then
expect(goal).not.toHaveProperty("blocker");
expect(goal).not.toHaveProperty("blockerSignature");
expect(goal).not.toHaveProperty("blockerEvidence");
expect(goal).not.toHaveProperty("blockerOccurrences");
expect(goal).not.toHaveProperty("blockedAt");
});
});
@@ -0,0 +1,180 @@
import { mkdir, mkdtemp, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE } from "../src/goal-status.js";
import { ulwLoopDir, ulwLoopLedgerPath } from "../src/paths.js";
import { writePlan } from "../src/plan-io.js";
import { recordFinalReviewBlockers } from "../src/review-blockers.js";
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
import { UlwLoopError } from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
const VALID_SNAPSHOT_JSON = JSON.stringify({
goal: { objective: ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE, status: "active" },
});
const validArgs = {
goalId: "G002",
title: "Resolve final code-review blockers",
objective: "Address the BLOCK findings from the architect",
evidence: "review verdict: REQUEST_CHANGES (3 issues)",
codexGoalJson: VALID_SNAPSHOT_JSON,
};
function makeCriterion(overrides: Partial<UlwLoopSuccessCriterion> = {}): UlwLoopSuccessCriterion {
return {
id: "C001",
scenario: "happy path",
userModel: "happy",
expectedEvidence: "observable proof",
capturedEvidence: null,
status: "pending",
...overrides,
};
}
function makeGoal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Build durable plan",
objective: "Complete one ulw-loop story",
status: "pending",
successCriteria: [makeCriterion()],
attempt: 1,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function makePlan(overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
codexGoalMode: "aggregate",
codexObjective: ULW_LOOP_AGGREGATE_CODEX_OBJECTIVE,
goals: [makeGoal({ status: "in_progress" })],
...overrides,
};
}
async function bootstrapRepo(plan: UlwLoopPlan): Promise<string> {
const repo = await mkdtemp(join(tmpdir(), "ug-review-blockers-"));
await mkdir(ulwLoopDir(repo), { recursive: true });
await writePlan(repo, plan);
return repo;
}
async function ledgerKinds(repo: string): Promise<string[]> {
const raw = await readFile(ulwLoopLedgerPath(repo), "utf8");
return raw
.split(/\r?\n/)
.filter(Boolean)
.map((line) => JSON.parse(line).kind);
}
async function expectUlwLoopCode(action: () => Promise<unknown>, code: string): Promise<void> {
try {
await action();
} catch (error) {
expect(error).toBeInstanceOf(UlwLoopError);
if (!(error instanceof UlwLoopError)) throw error;
expect(error.code).toBe(code);
return;
}
throw new Error("Expected UlwLoopError");
}
function finalPlan(): UlwLoopPlan {
return makePlan({
activeGoalId: "G002",
goals: [
makeGoal({ id: "G001", status: "complete" }),
makeGoal({ id: "G002", status: "in_progress", title: "ship it", objective: "Finish final story" }),
],
});
}
describe("recordFinalReviewBlockers happy path", () => {
it("marks the final goal review_blocked + appends new pending goal", async () => {
const repo = await bootstrapRepo(finalPlan());
const result = await recordFinalReviewBlockers(repo, validArgs);
expect(result.blockedGoal.status).toBe("review_blocked");
expect(result.blockedGoal.evidence).toBe(validArgs.evidence);
expect(result.newGoal).toMatchObject({ id: "G003", status: "pending", title: validArgs.title });
expect(result.newGoal.successCriteria.length).toBeGreaterThanOrEqual(3);
expect(result.plan.activeGoalId).toBeUndefined();
expect(result.ledgerEntries.length).toBeGreaterThanOrEqual(3);
});
it("seeded successCriteria cover happy/edge/regression on the blocker-resolution goal", async () => {
const repo = await bootstrapRepo(finalPlan());
const result = await recordFinalReviewBlockers(repo, validArgs);
expect(result.newGoal.successCriteria.map((criterion) => criterion.userModel).sort()).toEqual([
"edge",
"happy",
"regression",
]);
});
});
describe("recordFinalReviewBlockers error cases", () => {
it("throws ulw_loop_goal_not_found for unknown goalId", async () => {
const repo = await bootstrapRepo(finalPlan());
await expectUlwLoopCode(
() => recordFinalReviewBlockers(repo, { ...validArgs, goalId: "G999" }),
"ulw_loop_goal_not_found",
);
});
it("throws ulw_loop_goal_not_in_progress when goal.status !== in_progress", async () => {
const repo = await bootstrapRepo(
makePlan({
goals: [makeGoal({ id: "G001", status: "in_progress" }), makeGoal({ id: "G002", status: "pending" })],
}),
);
await expectUlwLoopCode(() => recordFinalReviewBlockers(repo, validArgs), "ulw_loop_goal_not_in_progress");
});
it("throws ulw_loop_not_final_story when other unresolved goals remain", async () => {
const repo = await bootstrapRepo(
makePlan({
goals: [makeGoal({ id: "G001", status: "in_progress" }), makeGoal({ id: "G002", status: "pending" })],
}),
);
await expectUlwLoopCode(
() => recordFinalReviewBlockers(repo, { ...validArgs, goalId: "G001" }),
"ulw_loop_not_final_story",
);
});
it("throws ulw_loop_codex_snapshot_mismatch when objective mismatches", async () => {
const repo = await bootstrapRepo(finalPlan());
const codexGoalJson = JSON.stringify({ goal: { objective: "wrong", status: "active" } });
await expectUlwLoopCode(
() => recordFinalReviewBlockers(repo, { ...validArgs, codexGoalJson }),
"ulw_loop_codex_snapshot_mismatch",
);
});
});
describe("recordFinalReviewBlockers ledger entries", () => {
it("appends goal_review_blocked + goal_added + blocker_recorded events", async () => {
const repo = await bootstrapRepo(finalPlan());
await recordFinalReviewBlockers(repo, validArgs);
expect(await ledgerKinds(repo)).toEqual(["goal_review_blocked", "goal_added", "blocker_recorded"]);
});
});
@@ -0,0 +1,301 @@
import { mkdtemp, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { ulwLoopGoalsPath } from "../src/paths.js";
import { readSteeringLedgerEntries, readUlwLoopPlan, writePlan } from "../src/plan-io.js";
import {
applySteeringMutation,
parseUlwLoopSteeringDirective,
steerUlwLoop,
validateUlwLoopSteeringProposal,
} from "../src/steering.js";
import type {
UlwLoopItem,
UlwLoopPlan,
UlwLoopSteeringProposal,
UlwLoopSuccessCriterion,
UlwLoopSuccessCriterionUserModel,
} from "../src/types.js";
const NOW = "2026-05-23T00:00:00.000Z";
type CriterionSteeringFields = {
readonly goalId?: string;
readonly scenario?: string;
readonly expectedEvidence?: string;
readonly userModel?: UlwLoopSuccessCriterionUserModel;
};
type SteeringInput = UlwLoopSteeringProposal & CriterionSteeringFields;
function criterion(overrides: Partial<UlwLoopSuccessCriterion> = {}): UlwLoopSuccessCriterion {
return {
id: "C001",
scenario: "old scenario",
userModel: "happy",
expectedEvidence: "vague evidence",
capturedEvidence: null,
status: "pending",
...overrides,
};
}
function goal(overrides: Partial<UlwLoopItem> = {}): UlwLoopItem {
return {
id: "G001",
title: "Build auth service",
objective: "Implement JWT auth endpoint",
status: "pending",
successCriteria: [criterion(), criterion({ id: "C002", status: "pass" })],
attempt: 0,
createdAt: NOW,
updatedAt: NOW,
...overrides,
};
}
function plan(overrides: Partial<UlwLoopPlan> = {}): UlwLoopPlan {
return {
version: 1,
createdAt: NOW,
updatedAt: NOW,
briefPath: ".omo/ulw-loop/brief.md",
goalsPath: ".omo/ulw-loop/goals.json",
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
goals: [
goal(),
goal({ id: "G002", title: "Rate limit", objective: "Throttle login" }),
goal({ id: "G003", status: "complete" }),
],
...overrides,
};
}
function steering(overrides: Partial<SteeringInput> = {}): SteeringInput {
return {
kind: "add_subgoal",
source: "cli",
evidence: "observable blocker evidence",
rationale: "the plan must change to stay safe",
title: "Investigate auth blocker",
objective: "Validate the blocker, capture evidence, and report findings.",
...overrides,
};
}
async function repoWithPlan(seed: UlwLoopPlan = plan()): Promise<string> {
const repoRoot = await mkdtemp(join(tmpdir(), "ug-steer-"));
await writePlan(repoRoot, seed);
return repoRoot;
}
describe("validateUlwLoopSteeringProposal", () => {
it("accepts valid add_subgoal", async () => {
const proposal: unknown = JSON.parse(
await readFile(join(process.cwd(), "test/fixtures/steering-proposal.json"), "utf8"),
);
expect(validateUlwLoopSteeringProposal(plan(), proposal).invariant.accepted).toBe(true);
});
it.each([
["missing evidence", { evidence: "" }],
["missing rationale", { rationale: "" }],
["unknown kind", { kind: "teleport_goal" }],
["protected payload mutations", { after: { codexObjective: "replace", qualityGate: { status: "passed" } } }],
["weakened completion text", { objective: "skip tests and mark complete faster" }],
])("rejects %s", (_name, overrides) => {
const audit = validateUlwLoopSteeringProposal(plan(), { ...steering(), ...overrides });
expect(audit.invariant.accepted).toBe(false);
expect(audit.invariant.rejectedReasons.length).toBeGreaterThan(0);
});
it("rejects when plan already complete", () => {
const done = plan({ goals: [goal({ status: "complete" }), goal({ id: "G002", status: "complete" })] });
expect(validateUlwLoopSteeringProposal(done, steering()).invariant.accepted).toBe(false);
});
it("rejects split_subgoal without children", () => {
const audit = validateUlwLoopSteeringProposal(plan(), steering({ kind: "split_subgoal", targetGoalId: "G001" }));
expect(audit.invariant.accepted).toBe(false);
});
it("rejects reorder_pending with unknown goal id", () => {
const audit = validateUlwLoopSteeringProposal(
plan(),
steering({ kind: "reorder_pending", pendingOrder: ["missing"] }),
);
expect(audit.invariant.accepted).toBe(false);
});
it.each([
["new scenario", { scenario: "new precise scenario" }],
["new expectedEvidence", { expectedEvidence: "specific command output" }],
])("accepts valid revise_criterion with %s", (_name, update) => {
const audit = validateUlwLoopSteeringProposal(
plan(),
steering({ kind: "revise_criterion", goalId: "G001", criterionId: "C001", ...update }),
);
expect(audit.invariant.accepted).toBe(true);
});
it.each([
["unknown goalId", { goalId: "missing", criterionId: "C001", scenario: "new" }],
["unknown criterionId", { goalId: "G001", criterionId: "missing", scenario: "new" }],
["no updates", { goalId: "G001", criterionId: "C001" }],
])("rejects revise_criterion with %s", (_name, overrides) => {
const audit = validateUlwLoopSteeringProposal(plan(), steering({ kind: "revise_criterion", ...overrides }));
expect(audit.invariant.accepted).toBe(false);
});
});
describe("steerUlwLoop", () => {
it("add_subgoal: appends goal + ledger entry", async () => {
const repoRoot = await repoWithPlan();
const result = await steerUlwLoop(repoRoot, steering({ idempotencyKey: "add" }));
const persisted = await readUlwLoopPlan(repoRoot);
expect(result.accepted).toBe(true);
expect(persisted.goals.at(-1)).toMatchObject({ id: "G004", title: "Investigate auth blocker" });
expect((await readSteeringLedgerEntries(repoRoot)).at(-1)).toMatchObject({
kind: "steering_accepted",
mutationKind: "add_subgoal",
});
});
it("split_subgoal: creates children + supersedes parent", async () => {
const repoRoot = await repoWithPlan();
const result = await steerUlwLoop(
repoRoot,
steering({
kind: "split_subgoal",
targetGoalId: "G001",
childGoals: [{ title: "Child", objective: "Do child" }],
}),
);
expect(result.plan.goals.map((item) => item.id).slice(0, 2)).toEqual(["G001", "G004"]);
expect(result.plan.goals[0]).toMatchObject({ steeringStatus: "superseded", supersededBy: ["G004"] });
});
it("reorder_pending: changes goal order", async () => {
const repoRoot = await repoWithPlan();
const result = await steerUlwLoop(
repoRoot,
steering({ kind: "reorder_pending", pendingOrder: ["G002", "G001"] }),
);
expect(result.plan.goals.map((item) => item.id).slice(0, 2)).toEqual(["G002", "G001"]);
});
it("revise_pending_wording: updates title/objective", async () => {
const repoRoot = await repoWithPlan();
const result = await steerUlwLoop(
repoRoot,
steering({
kind: "revise_pending_wording",
targetGoalId: "G001",
revisedTitle: "Build safer auth",
revisedObjective: "Implement guarded JWT auth",
}),
);
expect(result.plan.goals[0]).toMatchObject({
title: "Build safer auth",
objective: "Implement guarded JWT auth",
});
});
it("annotate_ledger: ledger-only, no plan mutation", async () => {
const seed = plan();
const repoRoot = await repoWithPlan(seed);
const result = await steerUlwLoop(repoRoot, steering({ kind: "annotate_ledger" }));
expect(result.plan.goals).toEqual(seed.goals);
expect(await readFile(ulwLoopGoalsPath(repoRoot), "utf8")).toBe(`${JSON.stringify(seed, null, 2)}\n`);
});
it("mark_blocked_superseded with children: supersede + replace", async () => {
const repoRoot = await repoWithPlan();
const result = await steerUlwLoop(
repoRoot,
steering({
kind: "mark_blocked_superseded",
targetGoalId: "G001",
childGoals: [{ title: "Replacement", objective: "Replace blocked path" }],
}),
);
expect(result.plan.goals[0]).toMatchObject({ steeringStatus: "superseded", supersededBy: ["G004"] });
expect(result.plan.goals[1]).toMatchObject({ id: "G004", supersedes: ["G001"] });
});
it("mark_blocked_superseded without children: blocks goal", async () => {
const repoRoot = await repoWithPlan();
const result = await steerUlwLoop(
repoRoot,
steering({ kind: "mark_blocked_superseded", targetGoalId: "G001", blockedReason: "external blocker" }),
);
expect(result.plan.goals[0]).toMatchObject({
status: "blocked",
steeringStatus: "blocked",
blockedReason: "external blocker",
});
});
it.each(["pending", "pass"] as const)("revise_criterion: works on a %s criterion", async (status) => {
const repoRoot = await repoWithPlan();
const criterionId = status === "pending" ? "C001" : "C002";
const result = await steerUlwLoop(
repoRoot,
steering({
kind: "revise_criterion",
goalId: "G001",
criterionId,
scenario: "new scenario",
expectedEvidence: "precise evidence",
}),
);
const updated = result.plan.goals[0]?.successCriteria.find((item) => item.id === criterionId);
expect(updated).toMatchObject({ scenario: "new scenario", expectedEvidence: "precise evidence", status });
expect((await readSteeringLedgerEntries(repoRoot)).at(-1)).toMatchObject({
kind: "criteria_revised",
criterionId,
});
});
it("revise_criterion: updates the targeted criterion in plan", () => {
const audit = validateUlwLoopSteeringProposal(
plan(),
steering({ kind: "revise_criterion", goalId: "G001", criterionId: "C001", scenario: "new value" }),
);
const next = applySteeringMutation(
plan(),
steering({ kind: "revise_criterion", goalId: "G001", criterionId: "C001", scenario: "new value" }),
audit,
);
expect(next.goals[0]?.successCriteria[0]?.scenario).toBe("new value");
});
it("idempotency: same idempotencyKey produces deduped true second time", async () => {
const repoRoot = await repoWithPlan();
await steerUlwLoop(repoRoot, steering({ idempotencyKey: "same-key" }));
const second = await steerUlwLoop(repoRoot, steering({ idempotencyKey: "same-key" }));
expect(second.deduped).toBe(true);
expect((await readUlwLoopPlan(repoRoot)).goals).toHaveLength(4);
});
});
describe("parseUlwLoopSteeringDirective", () => {
it.each(["OMO_ULW_LOOP_STEER", "omo.ulw-loop.steer", "omo ulw-loop steer"])("parses %s pattern", (marker) => {
expect(parseUlwLoopSteeringDirective(`${marker}: ${JSON.stringify(steering())}`)).toMatchObject({
kind: "add_subgoal",
});
});
it("returns null when no marker", () => {
expect(parseUlwLoopSteeringDirective(JSON.stringify(steering()))).toBeNull();
});
it("returns null when JSON malformed after marker", () => {
expect(parseUlwLoopSteeringDirective("OMO_ULW_LOOP_STEER: {bad json")).toBeNull();
});
it("returns null for deprecated markers", () => {
const marker = ["OM", "X_ULW_LOOP_STEER"].join("");
expect(parseUlwLoopSteeringDirective(`${marker}: ${JSON.stringify(steering())}`)).toBeNull();
});
});
@@ -0,0 +1,79 @@
import { describe, expect, it } from "vitest";
import {
iso,
ULW_LOOP_BRIEF,
ULW_LOOP_CRITERION_STATUSES,
ULW_LOOP_DIR,
ULW_LOOP_GOALS,
ULW_LOOP_LEDGER,
ULW_LOOP_STEERING_MUTATION_KINDS,
ULW_LOOP_SUCCESS_CRITERION_USER_MODELS,
UlwLoopError,
} from "../src/types.ts";
describe("ulw-loop domain constants", () => {
describe("when checking workspace paths", () => {
it("then ULW_LOOP_DIR points to the omo workspace", () => {
expect(ULW_LOOP_DIR).toBe(".omo/ulw-loop");
});
it("then artifact filenames are stable", () => {
expect(ULW_LOOP_BRIEF).toBe("brief.md");
expect(ULW_LOOP_GOALS).toBe("goals.json");
expect(ULW_LOOP_LEDGER).toBe("ledger.jsonl");
});
});
describe("when checking steering mutation kinds", () => {
it("then includes the new revise_criterion kind", () => {
expect(ULW_LOOP_STEERING_MUTATION_KINDS).toContain("revise_criterion");
});
it("then totals 7 kinds", () => {
expect(ULW_LOOP_STEERING_MUTATION_KINDS).toHaveLength(7);
});
});
describe("when checking criterion user models", () => {
it("then exposes 4 user models including adversarial", () => {
expect(ULW_LOOP_SUCCESS_CRITERION_USER_MODELS).toEqual(["happy", "edge", "regression", "adversarial"]);
});
});
describe("when checking criterion statuses", () => {
it("then exposes pending/pass/fail/blocked", () => {
expect(ULW_LOOP_CRITERION_STATUSES).toEqual(["pending", "pass", "fail", "blocked"]);
});
});
});
describe("UlwLoopError", () => {
describe("when constructed with code", () => {
it("then is an Error instance carrying the code", () => {
const err = new UlwLoopError("bad", "TEST_CODE");
expect(err).toBeInstanceOf(Error);
expect(err.code).toBe("TEST_CODE");
expect(err.message).toBe("bad");
});
it("then accepts optional cause + details", () => {
const cause = new Error("upstream");
const err = new UlwLoopError("wrap", "WRAP", { cause, details: { goalId: "G001" } });
expect(err.cause).toBe(cause);
expect(err.details).toEqual({ goalId: "G001" });
});
});
});
describe("iso()", () => {
describe("when called", () => {
it("then returns an ISO 8601 string", () => {
const s = iso();
expect(s).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
});
});
});
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowImportingTsExtensions": false,
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"noEmit": false
},
"include": ["src/**/*"],
"exclude": ["test/**/*"]
}
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"verbatimModuleSyntax": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"allowImportingTsExtensions": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"useDefineForClassFields": false,
"types": ["node"],
"noEmit": true
},
"include": ["src/**/*", "test/**/*", "vitest.config.ts"]
}
@@ -0,0 +1,10 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["test/**/*.test.ts"],
environment: "node",
pool: "threads",
isolate: true,
},
});