vendor: import codex-plugins as packages/omo-codex/{plugin,scripts,marketplace.json,MARKETPLACE.md}
This commit is contained in:
@@ -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/ultragoal/` paths.
|
||||
- Environment variables use the `OMO_ULTRAGOAL_*` prefix.
|
||||
- CLI commands use the `omo ultragoal` 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,5 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.0] - unreleased
|
||||
|
||||
- Initial scaffold of codex-ultragoal plugin.
|
||||
@@ -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-ultragoal
|
||||
|
||||
This package ports the oh-my-codex ultragoal 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,78 @@
|
||||
# codex-ultragoal
|
||||
|
||||
[](#) [](LICENSE)
|
||||
|
||||
Codex plugin scaffold for durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.
|
||||
|
||||
## Behavior
|
||||
|
||||
| Subcommand | Purpose |
|
||||
|------------|---------|
|
||||
| `omo ultragoal create-goals` | Create repo-native goals from a brief and seed criteria. |
|
||||
| `omo ultragoal record-evidence` | Record observable evidence for the active criterion. |
|
||||
| `omo ultragoal criteria` | Inspect or revise goal success criteria. |
|
||||
| `omo ultragoal complete-goals` | Complete eligible goals after criteria pass. |
|
||||
| `omo ultragoal checkpoint` | Refuse completion until criteria and evidence gates pass. |
|
||||
| `omo ultragoal steer` | Apply steering updates to the plan. |
|
||||
| `omo ultragoal 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/ultragoal/` 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
|
||||
|
||||
From the marketplace root containing this plugin:
|
||||
|
||||
```bash
|
||||
codex plugin marketplace add /path/to/codex-plugins
|
||||
node /path/to/codex-plugins/scripts/install-local.mjs /path/to/codex-plugins
|
||||
```
|
||||
|
||||
If your local Codex build exposes plugin install commands, you can install from the UI or CLI instead. For older local builds, the marketplace installer builds and copies the plugin into `~/.codex/plugins/cache/<marketplace>/omo/0.1.0`, installs runtime dependencies there, and enables:
|
||||
|
||||
```toml
|
||||
[features]
|
||||
plugins = true
|
||||
plugin_hooks = true
|
||||
|
||||
[plugins."omo@code-yeongyu-codex-plugins"]
|
||||
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 ultragoal port.
|
||||
- [codex-plugins](https://github.com/code-yeongyu/codex-plugins) - local Codex plugin marketplace.
|
||||
@@ -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,16 @@
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit",
|
||||
"timeout": 10,
|
||||
"statusMessage": "checking ultragoal steering"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@code-yeongyu/codex-ultragoal",
|
||||
"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-ultragoal",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/code-yeongyu/codex-ultragoal.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/code-yeongyu/codex-ultragoal/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"codex",
|
||||
"codex-plugin",
|
||||
"ultragoal",
|
||||
"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,143 @@
|
||||
---
|
||||
name: ultragoal
|
||||
description: Durable repo-native multi-goal plans with embedded success criteria and evidence audit.
|
||||
---
|
||||
|
||||
## Role
|
||||
Expert goal orchestration agent. Plan multi-goal work that survives across turns and sessions.
|
||||
Use GPT-5.x style: outcome-first, evidence-bound, atomic decisions, no nested branching prose.
|
||||
|
||||
## Goal
|
||||
Deliver every goal in `.omo/ultragoal/goals.json` end-to-end.
|
||||
Prove EVERY success criterion with captured observable evidence from the real surface.
|
||||
Audit each pass, fail, block, steering change, and checkpoint in `.omo/ultragoal/ledger.jsonl`.
|
||||
|
||||
## Artifacts
|
||||
- `.omo/ultragoal/brief.md`: original brief and durable constraints.
|
||||
- `.omo/ultragoal/goals.json`: goals with embedded `successCriteria` per goal.
|
||||
- `.omo/ultragoal/ledger.jsonl`: append-only audit trail.
|
||||
- Read artifacts before resuming, steering, or checkpointing.
|
||||
- Never invent state outside `.omo/ultragoal` artifacts or `omo ultragoal status --json`.
|
||||
|
||||
## Bootstrap
|
||||
Do all three steps before execution. No edits, goal tools, or checkpointing before bootstrap completes.
|
||||
|
||||
### 1. Create goals from the brief
|
||||
Run one form:
|
||||
```sh
|
||||
omo ultragoal create-goals --brief "<brief>" --json
|
||||
omo ultragoal create-goals --brief-file <path> --json
|
||||
cat <brief> | omo ultragoal create-goals --from-stdin --json
|
||||
```
|
||||
Write state through the CLI path. Do not hand-edit state files.
|
||||
|
||||
### 2. Refine success criteria per goal
|
||||
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: `id`, `scenario`, `expectedEvidence`, adversarial classes, and stop condition.
|
||||
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, not vibes: tmux transcript, curl status+body, browser screenshot, Playwright assertion, CLI stdout, DB state diff, parsed config dump.
|
||||
"Tests pass" is supporting signal, not completion proof.
|
||||
Record manual QA notes when behavior is user-visible.
|
||||
Revise any criterion that lacks observable `expectedEvidence` before execution.
|
||||
|
||||
### 3. Inspect state
|
||||
Run `omo ultragoal 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 ultragoal 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 ultragoal story. |
|
||||
| different goal active | STOP. Checkpoint blocked and surface the conflict. |
|
||||
4. If retrying failed work, run `omo ultragoal 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.
|
||||
2. Register atomic todos: `path: <action> for <criterion> - verify by <check>`.
|
||||
3. EXECUTE: do one bounded change or check, then exercise the real surface named by the criterion.
|
||||
4. CAPTURE: collect actual observable evidence: transcript, stdout, screenshot, assertion, status+body, diff, or parsed dump.
|
||||
5. RECORD exactly one result:
|
||||
- PASS: `omo ultragoal record-evidence --goal-id <id> --criterion-id <id> --status pass --evidence "<observable>" --json`
|
||||
- FAIL: `omo ultragoal record-evidence --goal-id <id> --criterion-id <id> --status fail --evidence "<observable>" --notes "<diagnosis>" --json`
|
||||
- BLOCKED: `omo ultragoal record-evidence --goal-id <id> --criterion-id <id> --status blocked --evidence "<observable>" --notes "<safety/blocker>" --json`
|
||||
6. If actual does not match expected, diagnose, fix minimally, and rerun the SAME criterion.
|
||||
7. After 3 same-criterion failures, exit the goal with diagnosis.
|
||||
8. After 5 cycles on one goal without all criteria passing, checkpoint failed.
|
||||
9. Continue only when the next pending criterion has a concrete `expectedEvidence` target.
|
||||
|
||||
### Goal Completion
|
||||
1. Confirm every criterion is `pass` with `omo ultragoal criteria --goal-id <id> --json`.
|
||||
2. Call `get_goal` for a fresh snapshot.
|
||||
3. Run `omo ultragoal 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 ultragoal record-review-blockers --goal-id <id> --title "<...>" --objective "<...>" --evidence "<review findings>" --codex-goal-json <snapshot> --json`.
|
||||
7. If clean, checkpoint final completion:
|
||||
```sh
|
||||
omo ultragoal 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 ultragoal steer --kind <kind> [<kind-specific-fields>] --evidence "<...>" --rationale "<...>" --json`.
|
||||
Structured prompt directives accepted: `OMO_ULTRAGOAL_STEER: { ... }`, `omo.ultragoal.steer: {...}`, `omo ultragoal 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/ultragoal/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 ultragoal 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.
|
||||
|
||||
## 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.
|
||||
- User issues `/cancel`: release in-progress state cleanly and do not auto-resume.
|
||||
@@ -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 { ultragoalBriefPath } from "./paths.js";
|
||||
import { appendLedger, readUltragoalPlan, withUltragoalMutationLock, writePlan } from "./plan-io.js";
|
||||
import { classifyExternalAuthorizationBlocker, clearGoalBlockerFields, sameBlockerOccurrences, validateQualityGate } from "./quality-gate.js";
|
||||
import type { UltragoalAggregateCompletion, UltragoalItem, UltragoalLedgerEntry, UltragoalPlan, UltragoalQualityGate } from "./types.js";
|
||||
import { iso, ULTRAGOAL_DIR, ULTRAGOAL_GOALS, ULTRAGOAL_LEDGER, UltragoalError } from "./types.js";
|
||||
|
||||
export interface CheckpointUltragoalArgs { readonly goalId: string; readonly status: "complete" | "failed" | "blocked"; readonly evidence: string; readonly codexGoalJson?: string; readonly qualityGateJson?: string }
|
||||
export interface CheckpointUltragoalResult { readonly plan: UltragoalPlan; readonly goal: UltragoalItem; readonly ledgerEntry: UltragoalLedgerEntry; readonly aggregateCompletion?: UltragoalAggregateCompletion }
|
||||
|
||||
function ultragoalFail(message: string, code: string): never { throw new UltragoalError(message, code); }
|
||||
function normalizeObjective(value: string): string { return value.replace(/\s+/g, " ").trim(); }
|
||||
function nonEmptyEvidence(value: string): string { const trimmed = value.trim(); return trimmed || ultragoalFail("Evidence must be a non-empty string.", "ultragoal_evidence_required"); }
|
||||
function findGoal(plan: UltragoalPlan, goalId: string): UltragoalItem { const goal = plan.goals.find((candidate) => candidate.id === goalId); return goal ?? ultragoalFail(`Unknown ultragoal id: ${goalId}.`, "ultragoal_goal_not_found"); }
|
||||
|
||||
function textMentionsUltragoalPlanArtifact(value: string | undefined): boolean {
|
||||
const normalized = (value ?? "").toLowerCase();
|
||||
return normalized.includes(ULTRAGOAL_DIR.toLowerCase()) || normalized.includes(ULTRAGOAL_GOALS.toLowerCase()) || normalized.includes(ULTRAGOAL_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 snapshotObjectiveMapsToUltragoalPlan(repoRoot: string, snapshotObjective: string): Promise<boolean> {
|
||||
const actual = normalizeObjective(snapshotObjective).toLowerCase();
|
||||
if (textMentionsUltragoalPlanArtifact(actual)) return true;
|
||||
if (actual.length < 24 || !existsSync(ultragoalBriefPath(repoRoot))) return false;
|
||||
try {
|
||||
const brief = normalizeObjective(await readFile(ultragoalBriefPath(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: UltragoalPlan, goal: UltragoalItem, 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 snapshotObjectiveMapsToUltragoalPlan(repoRoot, snapshotObjective);
|
||||
if (!textMentionsUltragoalPlanArtifact(evidence) || !textMentionsGoalId(evidence, goal.id)) return false;
|
||||
if (!textHasCompletionValidationEvidence(evidence)) return false;
|
||||
return snapshotObjectiveMapsToUltragoalPlan(repoRoot, snapshotObjective);
|
||||
}
|
||||
|
||||
function buildCompletedLegacyGoalRemediation(goal: UltragoalItem): 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 ultragoal 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: UltragoalItem, 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 ultragoal 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/ultragoal/goals.json or ledger.jsonl, includes completed implementation plus validation/review evidence, and a get_goal objective that maps to the ultragoal 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 ultragoalFail("Quality gate JSON is neither valid JSON nor a readable path.", "ultragoal_json_input_invalid");
|
||||
try { return JSON.parse(await readFile(path, "utf8")); } catch (error) { return ultragoalFail(`Quality gate path does not contain valid JSON${error instanceof Error ? `: ${error.message}` : "."}`, "ultragoal_json_input_invalid"); }
|
||||
}
|
||||
|
||||
function makeAggregateCompletion(now: string, evidence: string, codexGoal: unknown): UltragoalAggregateCompletion {
|
||||
return { status: "complete", completedAt: now, evidence, codexGoal };
|
||||
}
|
||||
|
||||
function applyBlockedOrFailed(goal: UltragoalItem, plan: UltragoalPlan, 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: CheckpointUltragoalArgs["status"], goal: UltragoalItem, aggregateCompletion: UltragoalAggregateCompletion | undefined): UltragoalLedgerEntry["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: CheckpointUltragoalArgs, goal: UltragoalItem, qualityGate: UltragoalQualityGate | undefined, codexGoal: unknown, aggregateCompletion: UltragoalAggregateCompletion | undefined): UltragoalLedgerEntry {
|
||||
const entry: UltragoalLedgerEntry = { 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 checkpointUltragoal(repoRoot: string, args: CheckpointUltragoalArgs): Promise<CheckpointUltragoalResult> {
|
||||
return withUltragoalMutationLock(repoRoot, async () => {
|
||||
const plan = await readUltragoalPlan(repoRoot);
|
||||
const goal = findGoal(plan, args.goalId);
|
||||
if (args.status === "complete") requireAllCriteriaPass(goal);
|
||||
const evidence = nonEmptyEvidence(args.evidence);
|
||||
const now = iso();
|
||||
let aggregateCompletion: UltragoalAggregateCompletion | undefined;
|
||||
let qualityGate: UltragoalQualityGate | 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 UltragoalError(`${formatCodexGoalReconciliation(reconciliation)}${aggregate && snapshot?.status === "complete" && objective !== undefined ? buildTaskScopedAggregateReconciliationHint(goal, final) : ""}`, "ultragoal_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 { UltragoalError } 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 UltragoalError(`Invalid JSON input: ${message}`, "ULTRAGOAL_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 UltragoalError(`Invalid --codex-goal-json: ${message}`, "ULTRAGOAL_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 UltragoalError(`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 UltragoalError("Invalid --status; expected pass, fail, or blocked.", "ULTRAGOAL_EVIDENCE_STATUS_INVALID", { details: { status: value } });
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRecordEvidenceArgs(argv: readonly string[]): RecordEvidenceCliArgs {
|
||||
const result = { goalId: required(argv, "--goal-id", "ULTRAGOAL_GOAL_ID_REQUIRED"), criterionId: required(argv, "--criterion-id", "ULTRAGOAL_CRITERION_ID_REQUIRED"), status: evidenceStatus(required(argv, "--status", "ULTRAGOAL_EVIDENCE_STATUS_REQUIRED")), evidence: required(argv, "--evidence", "ULTRAGOAL_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 { checkpointUltragoal } from "./checkpoint.js";
|
||||
import { hasFlag, parseCodexGoalJson, parseRecordEvidenceArgs, positionalText, readStdin, readValue } from "./cli-arg-parser.js";
|
||||
import { blockedDecisionHandoff, normalizeCodexGoalMode, printJson, printStatus, ULTRAGOAL_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 { addUltragoalGoal, createUltragoalPlan, startNextUltragoal, summarizeUltragoalPlan } from "./plan-crud.js";
|
||||
import { readUltragoalPlan } from "./plan-io.js";
|
||||
import { recordFinalReviewBlockers } from "./review-blockers.js";
|
||||
import { steerUltragoal } from "./steering.js";
|
||||
import type { UltragoalItem } from "./types.js";
|
||||
import { UltragoalError } from "./types.js";
|
||||
|
||||
type CheckpointStatus = "complete" | "failed" | "blocked";
|
||||
|
||||
export async function ultragoalCommand(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(`${ULTRAGOAL_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(`${ULTRAGOAL_HELP}\n`); return 1;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof UltragoalError) process.stderr.write(`[ultragoal] ${error.message}\n`);
|
||||
else if (error instanceof Error) process.stderr.write(`[ultragoal] unexpected: ${error.message}\n`);
|
||||
else process.stderr.write("[ultragoal] 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 UltragoalError("Missing brief text. Pass --brief, --brief-file, --from-stdin, or positional text.", "ULTRAGOAL_BRIEF_REQUIRED");
|
||||
const plan = await createUltragoalPlan(repoRoot, { brief, codexGoalMode: normalizeCodexGoalMode(readValue(argv, "--codex-goal-mode")), force: hasFlag(argv, "--force") });
|
||||
if (json) printJson({ ok: true, plan, summary: summarizeUltragoalPlan(plan) });
|
||||
else process.stdout.write(`ultragoal 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 readUltragoalPlan(repoRoot);
|
||||
if (json) printJson({ ok: true, plan, summary: summarizeUltragoalPlan(plan) });
|
||||
else printStatus(plan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function completeGoals(repoRoot: string, argv: readonly string[], json: boolean): Promise<number> {
|
||||
const result = await startNextUltragoal(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: summarizeUltragoalPlan(result.plan), plan: result.plan });
|
||||
else process.stdout.write(`${handoff || "ultragoal: 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 UltragoalError("Missing --codex-goal-json.", "ULTRAGOAL_CODEX_GOAL_JSON_REQUIRED");
|
||||
const qualityGateJson = readValue(argv, "--quality-gate-json");
|
||||
const result = await checkpointUltragoal(repoRoot, qualityGateJson === undefined ? { goalId, status: statusValue, evidence, codexGoalJson } : { goalId, status: statusValue, evidence, codexGoalJson, qualityGateJson });
|
||||
if (json) printJson({ ok: true, ...result, summary: summarizeUltragoalPlan(result.plan) });
|
||||
else process.stdout.write(`ultragoal 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 steerUltragoal(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 addUltragoalGoal(repoRoot, { title: required(argv, "--title"), objective: required(argv, "--objective") });
|
||||
if (json) printJson({ ok: true, plan: result.plan, goal: result.goal, summary: summarizeUltragoalPlan(result.plan) });
|
||||
else { process.stdout.write(`ultragoal 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 readUltragoalPlan(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: summarizeUltragoalPlan(result.plan) });
|
||||
else process.stdout.write(`ultragoal 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 UltragoalError("Missing --codex-goal-json.", "ULTRAGOAL_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: summarizeUltragoalPlan(result.plan) });
|
||||
else process.stdout.write(`ultragoal 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 UltragoalError(`Missing ${flag}.`, "ULTRAGOAL_ARGUMENT_MISSING", { details: { flag } });
|
||||
}
|
||||
|
||||
function checkpointStatus(value: string): CheckpointStatus {
|
||||
if (value === "complete" || value === "failed" || value === "blocked") return value;
|
||||
throw new UltragoalError("Missing or invalid --status; expected complete, failed, or blocked.", "ULTRAGOAL_STATUS_INVALID", { details: { status: value } });
|
||||
}
|
||||
|
||||
function findGoal(plan: { readonly goals: readonly UltragoalItem[] }, goalId: string): UltragoalItem {
|
||||
const goal = plan.goals.find((candidate) => candidate.id === goalId);
|
||||
if (goal !== undefined) return goal;
|
||||
throw new UltragoalError(`Unknown ultragoal id: ${goalId}.`, "ULTRAGOAL_GOAL_NOT_FOUND", { details: { goalId } });
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { UltragoalCodexGoalMode, UltragoalItem, UltragoalPlan } from "./types.js";
|
||||
import { UltragoalError } from "./types.js";
|
||||
|
||||
export const ULTRAGOAL_HELP = `Usage:
|
||||
omo ultragoal create-goals --brief "..." [--brief-file <path>] [--from-stdin] [--codex-goal-mode aggregate|per_story] [--force] [--json]
|
||||
omo ultragoal status [--json]
|
||||
omo ultragoal complete-goals [--retry-failed] [--json]
|
||||
omo ultragoal criteria --goal-id <id> [--json]
|
||||
omo ultragoal record-evidence --goal-id <id> --criterion-id <id> --status pass|fail|blocked --evidence "..." [--notes "..."] [--json]
|
||||
omo ultragoal checkpoint --goal-id <id> --status complete|failed|blocked --evidence "..." --codex-goal-json <...> [--quality-gate-json <...>] [--json]
|
||||
omo ultragoal steer --kind <kind> ... --evidence "..." --rationale "..." [--json]
|
||||
omo ultragoal add-goal --title "..." --objective "..." [--json]
|
||||
omo ultragoal 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: UltragoalItem): 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: UltragoalPlan): void {
|
||||
let totalCriteria = 0;
|
||||
let passCriteria = 0;
|
||||
const lines = ["ultragoal 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: UltragoalPlan): string {
|
||||
const blocked = plan.goals.find((goal) => goal.status === "needs_user_decision" && goal.nonRetriable);
|
||||
if (blocked === undefined) return "";
|
||||
return [
|
||||
"ultragoal: 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): UltragoalCodexGoalMode {
|
||||
if (value === undefined) return "aggregate";
|
||||
if (value === "aggregate" || value === "per_story") return value;
|
||||
throw new UltragoalError(
|
||||
"Invalid --codex-goal-mode; expected aggregate or per_story.",
|
||||
"ULTRAGOAL_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 { SteerUltragoalResult, UltragoalSteeringChildGoal, UltragoalSteeringMutationKind, UltragoalSteeringProposal, UltragoalSteeringSource, UltragoalSuccessCriterionUserModel } from "./types.js";
|
||||
import { ULTRAGOAL_STEERING_MUTATION_KINDS, ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS, UltragoalError } from "./types.js";
|
||||
|
||||
const SOURCES = ["user_prompt_submit", "finding", "cli"] as const satisfies readonly UltragoalSteeringSource[];
|
||||
|
||||
export type CliSteeringProposal = UltragoalSteeringProposal & { readonly goalId?: string; readonly scenario?: string; readonly expectedEvidence?: string; readonly userModel?: UltragoalSuccessCriterionUserModel };
|
||||
|
||||
function isKind(value: string | undefined): value is UltragoalSteeringMutationKind { return value !== undefined && ULTRAGOAL_STEERING_MUTATION_KINDS.some((kind) => kind === value); }
|
||||
function isSource(value: string | undefined): value is UltragoalSteeringSource { return value !== undefined && SOURCES.some((source) => source === value); }
|
||||
function isModel(value: string): value is UltragoalSuccessCriterionUserModel { return ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS.some((model) => model === value); }
|
||||
function fail(message: string, code: string, details: Record<string, unknown>): never { throw new UltragoalError(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}.`, "ULTRAGOAL_STEERING_FIELD_EMPTY", { field }); }
|
||||
function required(argv: readonly string[], flag: string): string { const value = text(readValue(argv, flag), flag); return value ?? fail(`Missing ${flag}.`, "ULTRAGOAL_STEERING_FIELD_REQUIRED", { flag }); }
|
||||
function requiredGoal(argv: readonly string[]): string { const value = text(parseGoalArg(argv), "--goal-id"); return value ?? fail("Missing --goal-id.", "ULTRAGOAL_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[]): UltragoalSteeringMutationKind {
|
||||
const value = readValue(argv, "--kind");
|
||||
if (isKind(value)) return value;
|
||||
return value === undefined ? fail("Missing --kind.", "ULTRAGOAL_STEERING_KIND_REQUIRED", { flag: "--kind" }) : fail(`Invalid --kind: ${value}.`, "ULTRAGOAL_STEERING_KIND_INVALID", { value, expected: ULTRAGOAL_STEERING_MUTATION_KINDS });
|
||||
}
|
||||
|
||||
export function parseSteeringSource(argv: readonly string[]): UltragoalSteeringSource {
|
||||
const value = readValue(argv, "--source");
|
||||
if (value === undefined) return "cli";
|
||||
return isSource(value) ? value : fail(`Invalid --source: ${value}.`, "ULTRAGOAL_STEERING_SOURCE_INVALID", { value, expected: SOURCES });
|
||||
}
|
||||
|
||||
function child(value: unknown): UltragoalSteeringChildGoal | 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<UltragoalSteeringChildGoal[]> {
|
||||
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.`, "ULTRAGOAL_STEERING_JSON_ARRAY_REQUIRED", { flag });
|
||||
const parsed: UltragoalSteeringChildGoal[] = [];
|
||||
for (const item of raw) { const next = child(item); if (next === null) return fail(`${flag} entries require title/objective.`, "ULTRAGOAL_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.`, "ULTRAGOAL_STEERING_JSON_ARRAY_REQUIRED", { flag });
|
||||
const values: string[] = [];
|
||||
for (const item of raw) { if (typeof item !== "string") return fail(`${flag} entries must be strings.`, "ULTRAGOAL_STEERING_STRING_ARRAY_REQUIRED", { flag }); values.push(text(item, flag) ?? ""); }
|
||||
return values;
|
||||
}
|
||||
|
||||
function model(value: string | undefined): UltragoalSuccessCriterionUserModel | undefined { const trimmed = text(value, "--user-model"); if (trimmed === undefined) return undefined; return isModel(trimmed) ? trimmed : fail(`Invalid --user-model: ${trimmed}.`, "ULTRAGOAL_STEERING_USER_MODEL_INVALID", { value: trimmed, expected: ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS }); }
|
||||
function neverKind(kind: never): never { return fail(`Unsupported steering kind: ${String(kind)}.`, "ULTRAGOAL_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.", "ULTRAGOAL_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.", "ULTRAGOAL_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 UltragoalSteeringChildGoal[] | undefined): UltragoalSteeringChildGoal[] | 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: SteerUltragoalResult, 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(`ultragoal 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,36 @@
|
||||
#!/usr/bin/env node
|
||||
import { ultragoalCommand } from "./cli-commands.js";
|
||||
import { runUltragoalHookCli } from "./codex-hook.js";
|
||||
|
||||
const TOP_LEVEL_HELP =
|
||||
"Usage:\n omo ultragoal <subcommand> [args]\n omo hook user-prompt-submit (Codex UserPromptSubmit hook)\n omo help | --help | -h (this message)\n\nRun `omo ultragoal help` for ultragoal 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 === "ultragoal") return ultragoalCommand(argv.slice(1));
|
||||
if (command === "hook") {
|
||||
const sub = argv[1];
|
||||
if (sub === "user-prompt-submit") {
|
||||
await runUltragoalHookCli(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 { UltragoalCodexGoalMode, UltragoalItem, UltragoalPlan, UltragoalSuccessCriterion } from "./types.js";
|
||||
|
||||
export interface CodexCreateGoalPayload {
|
||||
readonly objective: string;
|
||||
readonly status: "active";
|
||||
}
|
||||
|
||||
export interface UltragoalGoalInstruction {
|
||||
readonly text: string;
|
||||
readonly json: CodexCreateGoalPayload;
|
||||
}
|
||||
|
||||
export function buildCodexGoalInstruction(args: {
|
||||
readonly plan: UltragoalPlan;
|
||||
readonly goal: UltragoalItem;
|
||||
readonly isFinal?: boolean;
|
||||
}): UltragoalGoalInstruction {
|
||||
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: UltragoalPlan, goal: UltragoalItem): CodexCreateGoalPayload {
|
||||
return { objective: expectedCodexObjective(plan, goal), status: "active" };
|
||||
}
|
||||
|
||||
function buildText(
|
||||
mode: UltragoalCodexGoalMode,
|
||||
plan: UltragoalPlan,
|
||||
goal: UltragoalItem,
|
||||
createGoal: CodexCreateGoalPayload,
|
||||
isFinal: boolean,
|
||||
): string {
|
||||
return joinLines([
|
||||
mode === "aggregate" ? "Ultragoal aggregate-goal handoff" : "Ultragoal 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: UltragoalCodexGoalMode, 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 ultragoal.",
|
||||
"- Work only this goal until its completion audit passes.",
|
||||
];
|
||||
}
|
||||
return [
|
||||
"- Codex goal = the whole omo ultragoal 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 ultragoal.",
|
||||
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: UltragoalCodexGoalMode): 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: UltragoalItem): readonly string[] {
|
||||
return ["Active goal:", `- id: ${goal.id}`, `- title: ${goal.title}`, `- objective: ${goal.objective}`];
|
||||
}
|
||||
|
||||
function successCriteriaLines(criteria: readonly UltragoalSuccessCriterion[]): 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: UltragoalSuccessCriterion): 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: UltragoalItem, isFinal: boolean, aggregate: boolean): string {
|
||||
if (!isFinal)
|
||||
return "- This is not the final ultragoal story; do not run the final ai-slop-cleaner/$code-review gate yet.";
|
||||
const blockerCommand = `omo ultragoal 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 ultragoal 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,85 @@
|
||||
import { parseUltragoalSteeringDirective, steerUltragoal } 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 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 async function applyUserPromptUltragoalSteering(payload: UserPromptSubmitPayload): Promise<string> {
|
||||
try {
|
||||
if (payload.hook_event_name !== "UserPromptSubmit") return "";
|
||||
const proposal = parseUltragoalSteeringDirective(payload.prompt);
|
||||
if (proposal === null) return "";
|
||||
const result = await steerUltragoal(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 async function runUltragoalHookCli(stdin: NodeJS.ReadableStream, stdout: NodeJS.WritableStream): Promise<void> {
|
||||
try {
|
||||
const payload = parseUserPromptSubmitPayload(await readAll(stdin));
|
||||
if (payload === null) return;
|
||||
const output = await applyUserPromptUltragoalSteering(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 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, readUltragoalPlan, withUltragoalMutationLock, writePlan } from "./plan-io.js";
|
||||
import type { UltragoalItem, UltragoalLedgerEntry, UltragoalPlan, UltragoalSuccessCriterion } from "./types.js";
|
||||
import { iso, UltragoalError } 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 ultragoalFail(message: string, code: string, details: Record<string, unknown>): never { throw new UltragoalError(message, code, { details }); }
|
||||
|
||||
function ledgerKind(status: EvidenceStatus): UltragoalLedgerEntry["kind"] {
|
||||
switch (status) {
|
||||
case "pass":
|
||||
return "evidence_captured";
|
||||
case "fail":
|
||||
return "criterion_failed";
|
||||
case "blocked":
|
||||
return "criterion_blocked";
|
||||
default:
|
||||
return ultragoalFail("Invalid criterion status.", "ULTRAGOAL_CRITERION_STATUS_INVALID", { status });
|
||||
}
|
||||
}
|
||||
|
||||
function findGoal(plan: UltragoalPlan, goalId: string): UltragoalItem {
|
||||
const goal = plan.goals.find((candidate) => candidate.id === goalId);
|
||||
return goal ?? ultragoalFail(`Ultragoal goal not found: ${goalId}.`, "ULTRAGOAL_GOAL_NOT_FOUND", { goalId });
|
||||
}
|
||||
|
||||
function findCriterion(goal: UltragoalItem, criterionId: string): UltragoalSuccessCriterion {
|
||||
const criterion = goal.successCriteria.find((candidate) => candidate.id === criterionId);
|
||||
return criterion ?? ultragoalFail(`Success criterion not found: ${criterionId}.`, "ULTRAGOAL_CRITERION_NOT_FOUND", { goalId: goal.id, criterionId });
|
||||
}
|
||||
|
||||
function nonEmptyEvidence(evidence: string): string { const trimmed = evidence.trim(); return trimmed || ultragoalFail("Evidence must be a non-empty string.", "ULTRAGOAL_EVIDENCE_REQUIRED", {}); }
|
||||
|
||||
export async function recordEvidence(repoRoot: string, args: RecordEvidenceArgs): Promise<{ plan: UltragoalPlan; goal: UltragoalItem; criterion: UltragoalSuccessCriterion; ledgerEntry: UltragoalLedgerEntry }> {
|
||||
return withUltragoalMutationLock(repoRoot, async () => {
|
||||
const plan = await readUltragoalPlan(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: UltragoalLedgerEntry = {
|
||||
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: UltragoalPlan; resetCount: number }> {
|
||||
return withUltragoalMutationLock(repoRoot, async () => {
|
||||
const plan = await readUltragoalPlan(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: UltragoalPlan): { 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: ultragoalFail("Invalid criterion status.", "ULTRAGOAL_CRITERION_STATUS_INVALID", { status: criterion.status });
|
||||
}
|
||||
}
|
||||
if (unresolved) goalsWithUnresolvedCriteria.push(goal.id);
|
||||
}
|
||||
return { totalCriteria, passCount, pendingCount, failCount, blockedCount, goalsWithUnresolvedCriteria };
|
||||
}
|
||||
|
||||
export function unresolvedCriteriaOf(goal: UltragoalItem): UltragoalSuccessCriterion[] { return goal.successCriteria.filter((criterion) => criterion.status !== "pass"); }
|
||||
|
||||
export function requireAllCriteriaPass(goal: UltragoalItem): void {
|
||||
if (hasAllCriteriaPass(goal)) return;
|
||||
throw new UltragoalError(`Goal ${goal.id} has unresolved success criteria.`, "ultragoal_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 {
|
||||
UltragoalCodexGoalMode,
|
||||
UltragoalItem,
|
||||
UltragoalPlan,
|
||||
UltragoalStatus,
|
||||
UltragoalSuccessCriterion,
|
||||
} from "./types.js";
|
||||
|
||||
export const ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE: string =
|
||||
"Complete the durable ultragoal plan in .omo/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omo/ultragoal/ledger.jsonl as the audit trail.";
|
||||
|
||||
export function codexGoalMode(plan: UltragoalPlan): UltragoalCodexGoalMode {
|
||||
return plan.codexGoalMode ?? "per_story";
|
||||
}
|
||||
|
||||
function isResolvedStatus(status: UltragoalStatus): boolean {
|
||||
return status === "complete";
|
||||
}
|
||||
|
||||
function isSupersededResolved(goal: UltragoalItem, plan: UltragoalPlan): 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: UltragoalItem, plan: UltragoalPlan): boolean {
|
||||
if (goal.steeringStatus === "superseded") return !isSupersededResolved(goal, plan);
|
||||
if (goal.steeringStatus === "blocked") return true;
|
||||
return !isResolvedStatus(goal.status);
|
||||
}
|
||||
|
||||
function isCompletionBlockingForFinalCandidate(
|
||||
candidate: UltragoalItem,
|
||||
finalCandidate: UltragoalItem,
|
||||
plan: UltragoalPlan,
|
||||
): 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 isUltragoalDone(plan: UltragoalPlan): boolean {
|
||||
if (plan.aggregateCompletion?.status === "complete") return true;
|
||||
return plan.goals.every((goal) => !isCompletionBlocking(goal, plan));
|
||||
}
|
||||
|
||||
export function isFinalRunCompletionCandidate(plan: UltragoalPlan, goal: UltragoalItem): boolean {
|
||||
return (
|
||||
isCompletionBlocking(goal, plan) &&
|
||||
plan.goals.every((candidate) => !isCompletionBlockingForFinalCandidate(candidate, goal, plan))
|
||||
);
|
||||
}
|
||||
|
||||
export function aggregateCodexObjective(plan: UltragoalPlan): string {
|
||||
return plan.codexObjective ?? ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE;
|
||||
}
|
||||
|
||||
export function expectedCodexObjective(plan: UltragoalPlan, goal: UltragoalItem): string {
|
||||
return codexGoalMode(plan) === "aggregate" ? aggregateCodexObjective(plan) : goal.objective;
|
||||
}
|
||||
|
||||
export function compatibleCodexObjectives(plan: UltragoalPlan): readonly string[] {
|
||||
return [aggregateCodexObjective(plan), ...(plan.codexObjectiveAliases ?? [])];
|
||||
}
|
||||
|
||||
export function hasAllCriteriaPass(goal: UltragoalItem): boolean {
|
||||
return goal.successCriteria.length > 0 && goal.successCriteria.every((criterion) => criterion.status === "pass");
|
||||
}
|
||||
|
||||
export function firstUnresolvedCriterion(goal: UltragoalItem): UltragoalSuccessCriterion | undefined {
|
||||
return goal.successCriteria.find((criterion) => criterion.status !== "pass");
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { join } from "node:path";
|
||||
import { ULTRAGOAL_BRIEF, ULTRAGOAL_DIR, ULTRAGOAL_GOALS, ULTRAGOAL_LEDGER } from "./types.js";
|
||||
|
||||
export function ultragoalDir(repoRoot: string): string {
|
||||
return join(repoRoot, ULTRAGOAL_DIR);
|
||||
}
|
||||
|
||||
export function ultragoalBriefPath(repoRoot: string): string {
|
||||
return join(ultragoalDir(repoRoot), ULTRAGOAL_BRIEF);
|
||||
}
|
||||
|
||||
export function ultragoalGoalsPath(repoRoot: string): string {
|
||||
return join(ultragoalDir(repoRoot), ULTRAGOAL_GOALS);
|
||||
}
|
||||
|
||||
export function ultragoalLedgerPath(repoRoot: string): string {
|
||||
return join(ultragoalDir(repoRoot), ULTRAGOAL_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 { ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE } from "./goal-status.js";
|
||||
import { ultragoalBriefPath, ultragoalDir, ultragoalGoalsPath, ultragoalLedgerPath } from "./paths.js";
|
||||
import { appendLedger, readUltragoalPlan, withUltragoalMutationLock, writePlan } from "./plan-io.js";
|
||||
import type { UltragoalCodexGoalMode, UltragoalItem, UltragoalPlan, UltragoalSuccessCriterion } from "./types.js";
|
||||
import { iso, ULTRAGOAL_BRIEF, ULTRAGOAL_DIR, ULTRAGOAL_GOALS, ULTRAGOAL_LEDGER, UltragoalError } from "./types.js";
|
||||
|
||||
export type UltragoalPlanSummary = { 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 UltragoalError(`Missing ${label}.`, "ULTRAGOAL_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): UltragoalSuccessCriterion[] {
|
||||
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): UltragoalItem {
|
||||
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: UltragoalPlan, title: string, objective: string, now: string): UltragoalItem {
|
||||
const goal = makeGoal(title, objective, plan.goals.length, now);
|
||||
plan.goals.push(goal);
|
||||
plan.updatedAt = now;
|
||||
return goal;
|
||||
}
|
||||
|
||||
function isScheduleEligible(goal: UltragoalItem): boolean { return goal.steeringStatus !== "superseded" && goal.steeringStatus !== "blocked"; }
|
||||
|
||||
function clearGoalBlockerFields(goal: UltragoalItem): void {
|
||||
for (const key of ["blockedReason", "blockerSignature", "blockerOccurrenceCount", "requiredExternalDecision", "nonRetriable", "failedAt", "failureReason"] as const) delete goal[key];
|
||||
}
|
||||
|
||||
export async function createUltragoalPlan(repoRoot: string, args: { brief: string; codexGoalMode?: UltragoalCodexGoalMode; force?: boolean }): Promise<UltragoalPlan> {
|
||||
return withUltragoalMutationLock(repoRoot, async () => {
|
||||
if (!args.force && existsSync(ultragoalGoalsPath(repoRoot))) throw new UltragoalError(`Refusing to overwrite existing ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}; pass --force to recreate it.`, "ULTRAGOAL_PLAN_EXISTS");
|
||||
const now = iso();
|
||||
const goals = deriveGoalCandidates(args.brief).map((goal, index) => makeGoal(goal.title, goal.objective, index, now));
|
||||
const plan: UltragoalPlan = { version: 1, createdAt: now, updatedAt: now, briefPath: `${ULTRAGOAL_DIR}/${ULTRAGOAL_BRIEF}`, goalsPath: `${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}`, ledgerPath: `${ULTRAGOAL_DIR}/${ULTRAGOAL_LEDGER}`, codexGoalMode: args.codexGoalMode ?? "aggregate", goals };
|
||||
if (plan.codexGoalMode === "aggregate") plan.codexObjective = ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE;
|
||||
await mkdir(ultragoalDir(repoRoot), { recursive: true });
|
||||
await writeFile(ultragoalBriefPath(repoRoot), args.brief.endsWith("\n") ? args.brief : `${args.brief}\n`, "utf8");
|
||||
await writePlan(repoRoot, plan);
|
||||
await writeFile(ultragoalLedgerPath(repoRoot), "", "utf8");
|
||||
await appendLedger(repoRoot, { at: now, kind: "plan_created", message: `${goals.length} goal(s) created` });
|
||||
return plan;
|
||||
});
|
||||
}
|
||||
|
||||
export async function addUltragoalGoal(repoRoot: string, args: { title: string; objective: string }): Promise<{ plan: UltragoalPlan; goal: UltragoalItem }> {
|
||||
return withUltragoalMutationLock(repoRoot, async () => {
|
||||
const plan = await readUltragoalPlan(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 startNextUltragoal(repoRoot: string, args: { retryFailed?: boolean } = {}): Promise<{ plan: UltragoalPlan; goal: UltragoalItem; resumed: boolean } | { done: true; plan: UltragoalPlan }> {
|
||||
return withUltragoalMutationLock(repoRoot, async () => {
|
||||
const plan = await readUltragoalPlan(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 ultragoal" }); 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 summarizeUltragoalPlan(plan: UltragoalPlan): UltragoalPlanSummary {
|
||||
const countStatus = (status: UltragoalItem["status"]): number => plan.goals.filter((goal) => goal.status === status).length;
|
||||
const countCriteria = (status: UltragoalSuccessCriterion["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, ultragoalDir, ultragoalGoalsPath, ultragoalLedgerPath } from "./paths.js";
|
||||
import type { UltragoalLedgerEntry, UltragoalPlan } from "./types.js";
|
||||
import { iso, ULTRAGOAL_DIR, ULTRAGOAL_GOALS, ULTRAGOAL_LEDGER, UltragoalError } from "./types.js";
|
||||
|
||||
const AGGREGATE_CODEX_OBJECTIVE = `Complete the durable ultragoal plan in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}, including later accepted/appended stories, under the original brief constraints; use ${ULTRAGOAL_DIR}/${ULTRAGOAL_LEDGER} as the audit trail.`;
|
||||
const LEGACY_OBJECTIVE_PREFIX = `Complete all ultragoal stories in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}: `;
|
||||
const LEGACY_OBJECTIVE = `Complete all ultragoal stories listed in ${ULTRAGOAL_DIR}/${ULTRAGOAL_GOALS}. Use ${ULTRAGOAL_DIR}/${ULTRAGOAL_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 UltragoalLedgerEntry["kind"] {
|
||||
return value === "steering_accepted" || value === "steering_rejected" || value === "criteria_revised";
|
||||
}
|
||||
|
||||
export async function withUltragoalMutationLock<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 readUltragoalPlan(repoRoot: string): Promise<UltragoalPlan> {
|
||||
const path = ultragoalGoalsPath(repoRoot);
|
||||
let raw: string;
|
||||
try {
|
||||
raw = await readFile(path, "utf8");
|
||||
} catch (error) {
|
||||
if (!hasCode(error, "ENOENT")) throw error;
|
||||
throw new UltragoalError(
|
||||
`No ultragoal plan found at ${repoRelative(path, repoRoot)}. Run \`omo ultragoal create-goals ...\` first.`,
|
||||
"ULTRAGOAL_PLAN_MISSING",
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
const parsed: UltragoalPlan = JSON.parse(raw);
|
||||
if (parsed.version !== 1 || !Array.isArray(parsed.goals)) {
|
||||
throw new UltragoalError(`Invalid ultragoal plan at ${repoRelative(path, repoRoot)}.`, "ULTRAGOAL_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: UltragoalPlan): Promise<void> {
|
||||
await mkdir(ultragoalDir(repoRoot), { recursive: true });
|
||||
const path = ultragoalGoalsPath(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: UltragoalLedgerEntry): Promise<void> {
|
||||
await mkdir(ultragoalDir(repoRoot), { recursive: true });
|
||||
await appendFile(ultragoalLedgerPath(repoRoot), `${JSON.stringify(entry)}\n`, "utf8");
|
||||
}
|
||||
|
||||
export async function readSteeringLedgerEntries(repoRoot: string): Promise<UltragoalLedgerEntry[]> {
|
||||
let raw: string;
|
||||
try {
|
||||
raw = await readFile(ultragoalLedgerPath(repoRoot), "utf8");
|
||||
} catch (error) {
|
||||
if (hasCode(error, "ENOENT")) return [];
|
||||
throw error;
|
||||
}
|
||||
const entries: UltragoalLedgerEntry[] = [];
|
||||
for (const line of raw.split(/\r?\n/).filter(Boolean)) {
|
||||
const entry: UltragoalLedgerEntry = JSON.parse(line);
|
||||
if (isSteeringKind(entry.kind)) entries.push(entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import type { UltragoalItem, UltragoalPlan, UltragoalQualityGate } from "./types.js";
|
||||
import { UltragoalError } 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 UltragoalError(message, "ULTRAGOAL_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): UltragoalQualityGate {
|
||||
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: UltragoalQualityGate = {
|
||||
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: UltragoalItem): 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: UltragoalPlan, signature: string): number {
|
||||
return plan.goals.filter((goal) => goal.blockerSignature === signature || nestedBlockerSignature(goal) === signature)
|
||||
.length;
|
||||
}
|
||||
|
||||
export function clearGoalBlockerFields(goal: UltragoalItem): 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, readUltragoalPlan, withUltragoalMutationLock, writePlan } from "./plan-io.js";
|
||||
import type { UltragoalItem, UltragoalLedgerEntry, UltragoalPlan } from "./types.js";
|
||||
import { iso, UltragoalError } 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: UltragoalPlan; readonly blockedGoal: UltragoalItem; readonly newGoal: UltragoalItem; readonly ledgerEntries: UltragoalLedgerEntry[] }
|
||||
|
||||
const BLOCKER_FIELDS = "blockedReason blockerSignature blockerOccurrenceCount requiredExternalDecision nonRetriable failedAt failureReason completedAt blocker blockerEvidence blockerOccurrences blockedAt".split(" ");
|
||||
|
||||
function ultragoalError(message: string, code: string): never {
|
||||
throw new UltragoalError(message, code);
|
||||
}
|
||||
|
||||
function nextGoalId(plan: UltragoalPlan): 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: UltragoalPlan, args: RecordFinalReviewBlockersArgs, now: string): UltragoalItem {
|
||||
const index = plan.goals.length;
|
||||
const goal: UltragoalItem = {
|
||||
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 withUltragoalMutationLock(repoRoot, async () => {
|
||||
const plan = await readUltragoalPlan(repoRoot);
|
||||
const goal = plan.goals.find((candidate) => candidate.id === args.goalId);
|
||||
if (goal === undefined) ultragoalError(`Unknown ultragoal id: ${args.goalId}`, "ultragoal_goal_not_found");
|
||||
if (goal.status !== "in_progress") ultragoalError(`${goal.id} is ${goal.status}.`, "ultragoal_goal_not_in_progress");
|
||||
if (!isFinalRunCompletionCandidate(plan, goal)) ultragoalError(`${goal.id} is not final.`, "ultragoal_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) ultragoalError(reconciliation.errors.join(" "), "ultragoal_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: UltragoalLedgerEntry = { at: now, kind: "goal_review_blocked", goalId: goal.id, status: goal.status, evidence: args.evidence, codexGoal };
|
||||
const addedEntry: UltragoalLedgerEntry = { at: now, kind: "goal_added", goalId: newGoal.id, status: newGoal.status, evidence: args.evidence, message: newGoal.title };
|
||||
const summaryEntry: UltragoalLedgerEntry = { 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 { isUltragoalDone } from "./goal-status.js";
|
||||
import { appendLedger, readSteeringLedgerEntries, readUltragoalPlan, withUltragoalMutationLock, writePlan } from "./plan-io.js";
|
||||
import type {
|
||||
SteerUltragoalResult,
|
||||
UltragoalItem,
|
||||
UltragoalLedgerEntry,
|
||||
UltragoalPlan,
|
||||
UltragoalSteeringAudit,
|
||||
UltragoalSteeringChildGoal,
|
||||
UltragoalSteeringMutationKind,
|
||||
UltragoalSteeringProposal,
|
||||
UltragoalSteeringSource,
|
||||
UltragoalSuccessCriterionUserModel,
|
||||
} from "./types.js";
|
||||
import { iso, ULTRAGOAL_STEERING_MUTATION_KINDS, ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS } from "./types.js";
|
||||
|
||||
const SOURCES = ["user_prompt_submit", "finding", "cli"] as const satisfies readonly UltragoalSteeringSource[];
|
||||
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 UltragoalSteeringMutationKind => typeof value === "string" && ULTRAGOAL_STEERING_MUTATION_KINDS.some((kind) => kind === value);
|
||||
const isSource = (value: unknown): value is UltragoalSteeringSource => typeof value === "string" && SOURCES.some((source) => source === value);
|
||||
const isModel = (value: unknown): value is UltragoalSuccessCriterionUserModel => typeof value === "string" && ULTRAGOAL_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): UltragoalSteeringChildGoal | 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): UltragoalSteeringChildGoal[] => childValues(proposal).map(child).filter((item): item is UltragoalSteeringChildGoal => 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[]): UltragoalSteeringAudit {
|
||||
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: UltragoalSteeringAudit = { 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 validateUltragoalSteeringProposal(plan: UltragoalPlan, proposal: unknown): UltragoalSteeringAudit {
|
||||
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 (isUltragoalDone(plan)) reasons.push("plan already complete");
|
||||
if (isKind(kind)) validateKind(plan, object, kind, reasons);
|
||||
return auditFor(proposal, reasons);
|
||||
}
|
||||
|
||||
function goal(plan: UltragoalPlan, id: string | undefined): UltragoalItem | undefined {
|
||||
return id === undefined ? undefined : plan.goals.find((item) => item.id === id);
|
||||
}
|
||||
|
||||
function validateKind(plan: UltragoalPlan, proposal: object, kind: UltragoalSteeringMutationKind, 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: UltragoalPlan, 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: UltragoalPlan, 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: UltragoalPlan, 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: UltragoalPlan, childGoal: UltragoalSteeringChildGoal, evidence: string, now: string, offset: number): UltragoalItem {
|
||||
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: UltragoalPlan, proposal: UltragoalSteeringProposal, audit: UltragoalSteeringAudit): UltragoalPlan {
|
||||
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 UltragoalItem => 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: UltragoalPlan, proposal: UltragoalSteeringProposal, 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: UltragoalPlan, proposal: UltragoalSteeringProposal, 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: UltragoalPlan, proposal: UltragoalSteeringProposal, 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 UltragoalSteeringProposal {
|
||||
return isPlain(value) && isKind(read(value, "kind")) && isSource(read(value, "source")) && isText(read(value, "evidence")) && isText(read(value, "rationale"));
|
||||
}
|
||||
|
||||
export function parseUltragoalSteeringDirective(text: string): UltragoalSteeringProposal | null {
|
||||
const match = /(?:^|\s)(?:OMO_ULTRAGOAL_STEER|omo\.ultragoal\.steer|omo ultragoal 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 steerUltragoal(repoRoot: string, proposal: UltragoalSteeringProposal): Promise<SteerUltragoalResult> {
|
||||
return withUltragoalMutationLock(repoRoot, async () => {
|
||||
const plan = await readUltragoalPlan(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 = validateUltragoalSteeringProposal(plan, proposal);
|
||||
const accepted = audit.invariant.accepted;
|
||||
const next = accepted ? applySteeringMutation(plan, proposal, audit) : plan;
|
||||
const finalAudit: UltragoalSteeringAudit = { ...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: UltragoalSteeringProposal, audit: UltragoalSteeringAudit, at: string): UltragoalLedgerEntry {
|
||||
const entry: UltragoalLedgerEntry = { 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 ULTRAGOAL_DIR = ".omo/ultragoal";
|
||||
export const ULTRAGOAL_BRIEF = "brief.md";
|
||||
export const ULTRAGOAL_GOALS = "goals.json";
|
||||
export const ULTRAGOAL_LEDGER = "ledger.jsonl";
|
||||
|
||||
export type UltragoalStatus =
|
||||
| "pending"
|
||||
| "in_progress"
|
||||
| "complete"
|
||||
| "failed"
|
||||
| "blocked"
|
||||
| "review_blocked"
|
||||
| "needs_user_decision";
|
||||
|
||||
export type UltragoalCodexGoalMode = "aggregate" | "per_story";
|
||||
|
||||
export type UltragoalSteeringStatus = "superseded" | "blocked";
|
||||
|
||||
export const ULTRAGOAL_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 UltragoalSteeringMutationKind = (typeof ULTRAGOAL_STEERING_MUTATION_KINDS)[number];
|
||||
|
||||
export type UltragoalSteeringSource = "user_prompt_submit" | "finding" | "cli";
|
||||
|
||||
export const ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS = [
|
||||
"happy",
|
||||
"edge",
|
||||
"regression",
|
||||
"adversarial",
|
||||
] as const satisfies readonly string[];
|
||||
export type UltragoalSuccessCriterionUserModel = (typeof ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS)[number];
|
||||
|
||||
export const ULTRAGOAL_CRITERION_STATUSES = ["pending", "pass", "fail", "blocked"] as const satisfies readonly string[];
|
||||
export type UltragoalCriterionStatus = (typeof ULTRAGOAL_CRITERION_STATUSES)[number];
|
||||
|
||||
export const ULTRAGOAL_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 UltragoalLedgerEventKind = (typeof ULTRAGOAL_LEDGER_EVENT_KINDS)[number];
|
||||
|
||||
export interface UltragoalSuccessCriterion {
|
||||
readonly id: string;
|
||||
readonly scenario: string;
|
||||
readonly userModel: UltragoalSuccessCriterionUserModel;
|
||||
readonly expectedEvidence: string;
|
||||
capturedEvidence: string | null;
|
||||
status: UltragoalCriterionStatus;
|
||||
capturedAt?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface UltragoalSteeringInvariantResult {
|
||||
accepted: boolean;
|
||||
structuralInvariantAccepted: boolean;
|
||||
evidenceBackedNecessity: boolean;
|
||||
noEasierCompletion: boolean;
|
||||
rejectedReasons: string[];
|
||||
reasons?: string[];
|
||||
}
|
||||
|
||||
export interface UltragoalSteeringChildGoal {
|
||||
title: string;
|
||||
objective: string;
|
||||
}
|
||||
|
||||
export interface UltragoalSteeringAfterPayload {
|
||||
title?: string;
|
||||
objective?: string;
|
||||
pendingGoalIds?: string[];
|
||||
children?: UltragoalSteeringChildGoal[];
|
||||
}
|
||||
|
||||
export interface UltragoalSteeringProposal {
|
||||
kind: UltragoalSteeringMutationKind;
|
||||
source: UltragoalSteeringSource;
|
||||
targetGoalId?: string;
|
||||
targetGoalIds?: string[];
|
||||
criterionId?: string;
|
||||
evidence: string;
|
||||
rationale: string;
|
||||
title?: string;
|
||||
objective?: string;
|
||||
childGoals?: UltragoalSteeringChildGoal[];
|
||||
revisedTitle?: string;
|
||||
revisedObjective?: string;
|
||||
pendingOrder?: string[];
|
||||
blockedReason?: string;
|
||||
after?: UltragoalSteeringAfterPayload;
|
||||
directiveText?: string;
|
||||
promptSignature?: string;
|
||||
idempotencyKey?: string;
|
||||
now?: Date;
|
||||
}
|
||||
|
||||
export interface UltragoalSteeringAudit {
|
||||
kind: UltragoalSteeringMutationKind;
|
||||
source: UltragoalSteeringSource;
|
||||
targetGoalIds: string[];
|
||||
criterionId?: string;
|
||||
before?: unknown;
|
||||
after?: unknown;
|
||||
evidence: string;
|
||||
rationale: string;
|
||||
invariant: UltragoalSteeringInvariantResult;
|
||||
directiveText?: string;
|
||||
promptSignature?: string;
|
||||
idempotencyKey?: string;
|
||||
deduped?: boolean;
|
||||
}
|
||||
|
||||
export interface SteerUltragoalResult {
|
||||
plan: UltragoalPlan;
|
||||
accepted: boolean;
|
||||
audit: UltragoalSteeringAudit;
|
||||
rejectedReasons: string[];
|
||||
deduped: boolean;
|
||||
}
|
||||
|
||||
export interface UltragoalItem {
|
||||
id: string;
|
||||
title: string;
|
||||
objective: string;
|
||||
status: UltragoalStatus;
|
||||
successCriteria: UltragoalSuccessCriterion[];
|
||||
attempt: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
startedAt?: string;
|
||||
completedAt?: string;
|
||||
failedAt?: string;
|
||||
reviewBlockedAt?: string;
|
||||
evidence?: string;
|
||||
failureReason?: string;
|
||||
steeringStatus?: UltragoalSteeringStatus;
|
||||
supersededBy?: string[];
|
||||
supersedes?: string[];
|
||||
blockedReason?: string;
|
||||
blockerSignature?: string;
|
||||
blockerOccurrenceCount?: number;
|
||||
requiredExternalDecision?: string;
|
||||
nonRetriable?: boolean;
|
||||
steeringEvidence?: string;
|
||||
steeringRationale?: string;
|
||||
}
|
||||
|
||||
export interface UltragoalAggregateCompletion {
|
||||
status: "complete";
|
||||
completedAt: string;
|
||||
evidence: string;
|
||||
codexGoal?: unknown;
|
||||
}
|
||||
|
||||
export interface UltragoalPlan {
|
||||
version: 1;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
briefPath: string;
|
||||
goalsPath: string;
|
||||
ledgerPath: string;
|
||||
codexGoalMode?: UltragoalCodexGoalMode;
|
||||
codexObjective?: string;
|
||||
codexObjectiveAliases?: string[];
|
||||
aggregateCompletion?: UltragoalAggregateCompletion;
|
||||
activeGoalId?: string;
|
||||
goals: UltragoalItem[];
|
||||
}
|
||||
|
||||
export interface UltragoalLedgerEntry {
|
||||
at: string;
|
||||
kind: UltragoalLedgerEventKind;
|
||||
goalId?: string;
|
||||
criterionId?: string;
|
||||
status?: UltragoalStatus;
|
||||
criterionStatus?: UltragoalCriterionStatus;
|
||||
message?: string;
|
||||
codexGoal?: unknown;
|
||||
evidence?: string;
|
||||
capturedEvidence?: string;
|
||||
qualityGate?: UltragoalQualityGate;
|
||||
steering?: UltragoalSteeringAudit;
|
||||
before?: unknown;
|
||||
after?: unknown;
|
||||
mutationKind?: UltragoalSteeringMutationKind;
|
||||
idempotencyKey?: string;
|
||||
blockerSignature?: string;
|
||||
blockerOccurrenceCount?: number;
|
||||
requiredExternalDecision?: string;
|
||||
}
|
||||
|
||||
export interface CreateUltragoalOptions {
|
||||
brief: string;
|
||||
goals?: Array<{ title?: string; objective: string }>;
|
||||
codexGoalMode?: UltragoalCodexGoalMode;
|
||||
now?: Date;
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export interface StartNextOptions {
|
||||
now?: Date;
|
||||
retryFailed?: boolean;
|
||||
}
|
||||
|
||||
export interface CheckpointOptions {
|
||||
goalId: string;
|
||||
status: Extract<UltragoalStatus, "complete" | "failed"> | "blocked";
|
||||
evidence?: string;
|
||||
codexGoal?: unknown;
|
||||
qualityGate?: unknown;
|
||||
allowActiveFinalCodexGoal?: boolean;
|
||||
now?: Date;
|
||||
}
|
||||
|
||||
export interface AddUltragoalGoalOptions {
|
||||
title: string;
|
||||
objective: string;
|
||||
evidence?: string;
|
||||
now?: Date;
|
||||
}
|
||||
|
||||
export interface RecordFinalReviewBlockersOptions extends AddUltragoalGoalOptions {
|
||||
goalId: string;
|
||||
codexGoal?: unknown;
|
||||
}
|
||||
|
||||
export interface UltragoalQualityGate {
|
||||
aiSlopCleaner: { status: "passed"; evidence: string };
|
||||
verification: { status: "passed"; commands: string[]; evidence: string };
|
||||
codeReview: { recommendation: "APPROVE"; architectStatus: "CLEAR"; evidence: string };
|
||||
}
|
||||
|
||||
export interface UltragoalErrorOptions {
|
||||
readonly cause?: unknown;
|
||||
readonly details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class UltragoalError extends Error {
|
||||
readonly code: string;
|
||||
readonly details?: Record<string, unknown>;
|
||||
|
||||
constructor(message: string, code: string, opts?: UltragoalErrorOptions) {
|
||||
super(message, opts?.cause === undefined ? undefined : { cause: opts.cause });
|
||||
this.name = "UltragoalError";
|
||||
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 { checkpointUltragoal } from "../src/checkpoint.js";
|
||||
import { ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE } from "../src/goal-status.js";
|
||||
import { ultragoalBriefPath, ultragoalDir, ultragoalLedgerPath } from "../src/paths.js";
|
||||
import { writePlan } from "../src/plan-io.js";
|
||||
import type { UltragoalItem, UltragoalLedgerEntry, UltragoalPlan, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
import { UltragoalError } 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: UltragoalSuccessCriterion["status"]): UltragoalSuccessCriterion {
|
||||
return { id, scenario: `${id} scenario`, userModel: "happy", expectedEvidence: `${id} proof`, capturedEvidence: status === "pass" ? `${id} passed` : null, status };
|
||||
}
|
||||
|
||||
function goal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
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: UltragoalItem[], overrides: Partial<UltragoalPlan> = {}): UltragoalPlan {
|
||||
const result: UltragoalPlan = { version: 1, createdAt: NOW, updatedAt: NOW, briefPath: ".omo/ultragoal/brief.md", goalsPath: ".omo/ultragoal/goals.json", ledgerPath: ".omo/ultragoal/ledger.jsonl", codexGoalMode: "aggregate", codexObjective: ULTRAGOAL_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<UltragoalPlan> = {}): Promise<UltragoalPlan> {
|
||||
const fixture: UltragoalPlan = 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: UltragoalPlan): Promise<string> {
|
||||
const repo = await mkdtemp(join(tmpdir(), "ug-checkpoint-"));
|
||||
await mkdir(ultragoalDir(repo), { recursive: true });
|
||||
await writePlan(repo, seed);
|
||||
return repo;
|
||||
}
|
||||
|
||||
function snapshot(status: "active" | "complete", objective = ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE): string {
|
||||
return JSON.stringify({ goal: { objective, status } });
|
||||
}
|
||||
|
||||
async function lastLedger(repo: string): Promise<UltragoalLedgerEntry> {
|
||||
const last = (await readFile(ultragoalLedgerPath(repo), "utf8")).trim().split(/\r?\n/).at(-1);
|
||||
if (last === undefined) throw new Error("expected ledger entry");
|
||||
const entry: UltragoalLedgerEntry = JSON.parse(last);
|
||||
return entry;
|
||||
}
|
||||
|
||||
async function expectCode(action: () => Promise<unknown>, code: string): Promise<void> {
|
||||
try {
|
||||
await action();
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(UltragoalError);
|
||||
if (!(error instanceof UltragoalError)) throw error;
|
||||
expect(error.code).toBe(code);
|
||||
return;
|
||||
}
|
||||
throw new Error("Expected UltragoalError");
|
||||
}
|
||||
|
||||
function passGoal(id: string, overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
return goal({ id, successCriteria: [criterion("C001", "pass"), criterion("C002", "pass"), criterion("C003", "pass")], ...overrides });
|
||||
}
|
||||
|
||||
describe("checkpointUltragoal status=complete criteria gate", () => {
|
||||
it("THROWS ultragoal_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(() => checkpointUltragoal(repo, { goalId: "G001", status: "complete", evidence: "done" }), "ultragoal_criteria_not_all_pass");
|
||||
});
|
||||
|
||||
it("THROWS when any criterion is fail or blocked", async () => {
|
||||
for (const status of ["fail", "blocked"] satisfies UltragoalSuccessCriterion["status"][]) {
|
||||
const repo = await repoWith(plan([goal({ successCriteria: [criterion("C001", "pass"), criterion("C002", status), criterion("C003", "pass")] })]));
|
||||
await expectCode(() => checkpointUltragoal(repo, { goalId: "G001", status: "complete", evidence: "done" }), "ultragoal_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(() => checkpointUltragoal(repo, { goalId: "G001", status: "complete", evidence: "done", codexGoalJson: snapshot("active") }), "ultragoal_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 checkpointUltragoal(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("checkpointUltragoal 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(checkpointUltragoal(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(() => checkpointUltragoal(repo, { goalId: "G001", status: "complete", evidence: "work complete and validation passed", codexGoalJson: snapshot("active", "wrong objective") }), "ultragoal_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(() => checkpointUltragoal(repo, { goalId: "G001", status: "complete", evidence: "work complete and validation passed", codexGoalJson: snapshot("complete") }), "ultragoal_codex_snapshot_mismatch");
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkpointUltragoal 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(() => checkpointUltragoal(repo, { goalId: "G002", status: "complete", evidence: "final work complete and validation passed", codexGoalJson: snapshot("complete") }), "ULTRAGOAL_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 checkpointUltragoal(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 ultragoal brief", async () => {
|
||||
const taskObjective = "Fix ultragoal objective mismatch and install local ulw";
|
||||
const repo = await repoWith(plan([passGoal("G001")], { activeGoalId: "G001" }));
|
||||
await writeFile(ultragoalBriefPath(repo), `${taskObjective}\n`, "utf8");
|
||||
|
||||
const result = await checkpointUltragoal(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(ultragoalBriefPath(repo), "Fix ultragoal objective mismatch and install local ulw\n", "utf8");
|
||||
|
||||
await expect(
|
||||
checkpointUltragoal(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("checkpointUltragoal 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 checkpointUltragoal(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 checkpointUltragoal(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 checkpointUltragoal(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(checkpointUltragoal(repo, { goalId: "G001", status: "failed", evidence: "not done" })).resolves.toMatchObject({ goal: { status: "failed" } });
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkpointUltragoal status=blocked", () => {
|
||||
it("preserves blocker fields + appends ledger", async () => {
|
||||
const repo = await repoWith(plan([goal()]));
|
||||
const result = await checkpointUltragoal(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(checkpointUltragoal(repo, { goalId: "G001", status: "blocked", evidence: "waiting for approval" })).resolves.toMatchObject({ goal: { status: "blocked" } });
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkpointUltragoal 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 checkpointUltragoal(repo, { goalId: "G001", status: "complete", evidence: "implementation done in .omo/ultragoal/goals.json for G001 and validation passed", codexGoalJson: snapshot("active") });
|
||||
const forbidden = ["o", "m", "x"].join("");
|
||||
const payload = `${JSON.stringify(result)}\n${await readFile(ultragoalLedgerPath(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 { ultragoalCommand } from "../src/cli-commands.ts";
|
||||
import { ULTRAGOAL_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: ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE, status } });
|
||||
}
|
||||
|
||||
async function createPlan(brief = "- Goal A\n- Goal B"): Promise<Record<string, unknown>> {
|
||||
resetOutput();
|
||||
expect(await ultragoalCommand(["create-goals", "--brief", brief, "--json"])).toBe(0);
|
||||
const parsed = stdoutJson();
|
||||
resetOutput();
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async function passCriterion(goalId: string, criterionId: string): Promise<void> {
|
||||
expect(
|
||||
await ultragoalCommand([
|
||||
"record-evidence",
|
||||
"--goal-id",
|
||||
goalId,
|
||||
"--criterion-id",
|
||||
criterionId,
|
||||
"--status",
|
||||
"pass",
|
||||
"--evidence",
|
||||
`${criterionId} observable proof`,
|
||||
]),
|
||||
).toBe(0);
|
||||
resetOutput();
|
||||
}
|
||||
|
||||
describe("ultragoalCommand help", () => {
|
||||
it("prints usage when no subcommand", async () => {
|
||||
expect(await ultragoalCommand([])).toBe(0);
|
||||
expect(out.join("")).toContain("omo ultragoal");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoalCommand create-goals", () => {
|
||||
it("creates plan + writes 3 artifacts + seeds criteria per goal", async () => {
|
||||
const code = await ultragoalCommand(["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/ultragoal/brief.md"), "utf8")).toContain("Goal A");
|
||||
expect(await readFile(join(testDir, ".omo/ultragoal/goals.json"), "utf8")).toContain("successCriteria");
|
||||
expect(await readFile(join(testDir, ".omo/ultragoal/ledger.jsonl"), "utf8")).toContain("plan_created");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoalCommand status", () => {
|
||||
it("prints plan summary including criteria counts", async () => {
|
||||
await createPlan();
|
||||
|
||||
expect(await ultragoalCommand(["status"])).toBe(0);
|
||||
expect(out.join("")).toContain("criteria: 0/6 pass");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoalCommand complete-goals", () => {
|
||||
it("starts the next goal and returns a Codex instruction", async () => {
|
||||
await createPlan();
|
||||
|
||||
expect(await ultragoalCommand(["complete-goals", "--json"])).toBe(0);
|
||||
expect(stdoutJson()).toMatchObject({
|
||||
ok: true,
|
||||
goal: { status: "in_progress" },
|
||||
instruction: { json: { status: "active" } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoalCommand record-evidence", () => {
|
||||
it("records evidence + returns updated criterion", async () => {
|
||||
await createPlan();
|
||||
|
||||
expect(
|
||||
await ultragoalCommand([
|
||||
"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 ultragoalCommand([
|
||||
"record-evidence",
|
||||
"--goal-id",
|
||||
"G404",
|
||||
"--criterion-id",
|
||||
"C001",
|
||||
"--status",
|
||||
"pass",
|
||||
"--evidence",
|
||||
"x",
|
||||
]),
|
||||
).toBe(1);
|
||||
expect(err.join("")).toContain("[ultragoal]");
|
||||
});
|
||||
|
||||
it("returns 1 + error on missing flags", async () => {
|
||||
expect(
|
||||
await ultragoalCommand(["record-evidence", "--criterion-id", "C001", "--status", "pass", "--evidence", "x"]),
|
||||
).toBe(1);
|
||||
expect(err.join("")).toContain("Missing --goal-id");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoalCommand criteria", () => {
|
||||
it("lists criteria for a goal", async () => {
|
||||
await createPlan();
|
||||
|
||||
expect(await ultragoalCommand(["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 ultragoalCommand(["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("ultragoalCommand checkpoint", () => {
|
||||
it("REJECTS status=complete when criteria pending", async () => {
|
||||
await createPlan();
|
||||
|
||||
expect(
|
||||
await ultragoalCommand([
|
||||
"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 ultragoalCommand([
|
||||
"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("ultragoalCommand steer", () => {
|
||||
it("dispatches to the steering engine", async () => {
|
||||
await createPlan();
|
||||
|
||||
expect(
|
||||
await ultragoalCommand([
|
||||
"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("ultragoalCommand add-goal", () => {
|
||||
it("appends a pending goal", async () => {
|
||||
await createPlan();
|
||||
|
||||
expect(await ultragoalCommand(["add-goal", "--title", "Later", "--objective", "Do later", "--json"])).toBe(0);
|
||||
expect(stdoutJson()).toMatchObject({ ok: true, goal: { title: "Later", status: "pending" } });
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoalCommand unknown", () => {
|
||||
it("returns 1 + prints help on unknown subcommand", async () => {
|
||||
expect(await ultragoalCommand(["wat"])).toBe(1);
|
||||
expect(out.join("")).toContain("omo ultragoal");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoalCommand error handling", () => {
|
||||
it("returns 1 + prints [ultragoal] prefix on UltragoalError", async () => {
|
||||
expect(await ultragoalCommand(["status"])).toBe(1);
|
||||
expect(err.join("")).toContain("[ultragoal]");
|
||||
});
|
||||
});
|
||||
@@ -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, ULTRAGOAL_HELP } from "../src/cli-output.js";
|
||||
import type { UltragoalItem, UltragoalPlan, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
import { UltragoalError } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
|
||||
function criterion(overrides: Partial<UltragoalSuccessCriterion> = {}): UltragoalSuccessCriterion {
|
||||
return {
|
||||
id: "C001",
|
||||
scenario: "happy path returns 200",
|
||||
userModel: "happy",
|
||||
expectedEvidence: "HTTP 200",
|
||||
capturedEvidence: null,
|
||||
status: "pending",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function goal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
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<UltragoalPlan> = {}): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/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(UltragoalError);
|
||||
});
|
||||
|
||||
it("throws when status is not pass|fail|blocked", () => {
|
||||
expect(() =>
|
||||
parseRecordEvidenceArgs([
|
||||
"record-evidence",
|
||||
"--goal-id",
|
||||
"G001",
|
||||
"--criterion-id",
|
||||
"C001",
|
||||
"--status",
|
||||
"skip",
|
||||
"--evidence",
|
||||
"x",
|
||||
]),
|
||||
).toThrow(UltragoalError);
|
||||
});
|
||||
|
||||
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("ULTRAGOAL_HELP", () => {
|
||||
it("mentions omo ultragoal + every subcommand", () => {
|
||||
expect(ULTRAGOAL_HELP).toContain("omo ultragoal");
|
||||
expect(ULTRAGOAL_HELP).toContain("create-goals");
|
||||
expect(ULTRAGOAL_HELP).toContain("complete-goals");
|
||||
expect(ULTRAGOAL_HELP).toContain("status");
|
||||
expect(ULTRAGOAL_HELP).toContain("checkpoint");
|
||||
expect(ULTRAGOAL_HELP).toContain("steer");
|
||||
expect(ULTRAGOAL_HELP).toContain("record-evidence");
|
||||
expect(ULTRAGOAL_HELP).toContain("criteria");
|
||||
expect(ULTRAGOAL_HELP).toContain("add-goal");
|
||||
expect(ULTRAGOAL_HELP).toContain("record-review-blockers");
|
||||
});
|
||||
|
||||
it("never mentions the legacy typo", () => {
|
||||
const typo = ["o", "m", "x"].join("");
|
||||
|
||||
expect(ULTRAGOAL_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 UltragoalError when invalid", () => {
|
||||
expect(() => normalizeCodexGoalMode("per-story")).toThrow(UltragoalError);
|
||||
});
|
||||
});
|
||||
@@ -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 { SteerUltragoalResult, UltragoalPlan } from "../src/types.js";
|
||||
import { UltragoalError } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
|
||||
function plan(): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/ledger.jsonl",
|
||||
goals: [],
|
||||
};
|
||||
}
|
||||
|
||||
function steerResult(overrides: Partial<SteerUltragoalResult> = {}): SteerUltragoalResult {
|
||||
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(UltragoalError);
|
||||
});
|
||||
|
||||
it("throws when kind unknown", () => {
|
||||
expect(() => parseSteeringKind(["--kind", "bogus"])).toThrow(UltragoalError);
|
||||
});
|
||||
});
|
||||
|
||||
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(UltragoalError);
|
||||
});
|
||||
|
||||
it("throws when --evidence missing", async () => {
|
||||
await expect(
|
||||
parseSteeringProposal(["--kind", "add_subgoal", "--title", "New", "--objective", "Build", "--rationale", "y"]),
|
||||
).rejects.toThrow(UltragoalError);
|
||||
});
|
||||
});
|
||||
|
||||
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(UltragoalError);
|
||||
});
|
||||
|
||||
it("throws when goal-id missing", async () => {
|
||||
await expect(
|
||||
parseSteeringProposal([
|
||||
"--kind",
|
||||
"revise_criterion",
|
||||
"--criterion-id",
|
||||
"C002",
|
||||
"--scenario",
|
||||
"s",
|
||||
"--evidence",
|
||||
"x",
|
||||
"--rationale",
|
||||
"y",
|
||||
]),
|
||||
).rejects.toThrow(UltragoalError);
|
||||
});
|
||||
|
||||
it("throws when criterion-id missing", async () => {
|
||||
await expect(
|
||||
parseSteeringProposal([
|
||||
"--kind",
|
||||
"revise_criterion",
|
||||
"--goal-id",
|
||||
"G001",
|
||||
"--scenario",
|
||||
"s",
|
||||
"--evidence",
|
||||
"x",
|
||||
"--rationale",
|
||||
"y",
|
||||
]),
|
||||
).rejects.toThrow(UltragoalError);
|
||||
});
|
||||
});
|
||||
|
||||
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(UltragoalError);
|
||||
});
|
||||
});
|
||||
|
||||
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("ultragoal steer: accepted add_subgoal");
|
||||
expect(output).toContain("ultragoal status");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { buildCodexGoalInstruction } from "../src/codex-goal-instruction.js";
|
||||
import { ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE } from "../src/goal-status.js";
|
||||
import type { UltragoalItem, UltragoalPlan, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
|
||||
function makeCriterion(overrides: Partial<UltragoalSuccessCriterion> = {}): UltragoalSuccessCriterion {
|
||||
return {
|
||||
id: "C001",
|
||||
scenario: "happy path",
|
||||
userModel: "happy",
|
||||
expectedEvidence: "observable proof",
|
||||
capturedEvidence: null,
|
||||
status: "pending",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeGoal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
return {
|
||||
id: "G001",
|
||||
title: "Goal one",
|
||||
objective: "Complete goal one",
|
||||
status: "pending",
|
||||
successCriteria: [],
|
||||
attempt: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makePlan(overrides: Partial<UltragoalPlan> = {}): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/ledger.jsonl",
|
||||
goals: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildCodexGoalInstruction aggregate mode", () => {
|
||||
it("references the aggregate handoff and the .omo/ultragoal/goals.json artifact", () => {
|
||||
const { text } = buildCodexGoalInstruction({ plan: makePlan({ codexGoalMode: "aggregate" }), goal: makeGoal() });
|
||||
expect(text).toContain("aggregate");
|
||||
expect(text).toContain(".omo/ultragoal/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: ULTRAGOAL_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/ultragoal in artifact paths", () => {
|
||||
const { text } = buildCodexGoalInstruction({ plan: makePlan({ codexGoalMode: "aggregate" }), goal: makeGoal() });
|
||||
expect(text).toContain(".omo/ultragoal");
|
||||
});
|
||||
});
|
||||
@@ -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 ultragoal 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,187 @@
|
||||
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 {
|
||||
applyUserPromptUltragoalSteering,
|
||||
parseUserPromptSubmitPayload,
|
||||
runUltragoalHookCli,
|
||||
type UserPromptSubmitPayload,
|
||||
} from "../src/codex-hook.js";
|
||||
import { ultragoalDir } from "../src/paths.js";
|
||||
import { writePlan } from "../src/plan-io.js";
|
||||
import type { UltragoalPlan } 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(ultragoalDir(repoRoot), { recursive: true });
|
||||
await writePlan(repoRoot, samplePlan());
|
||||
return repoRoot;
|
||||
}
|
||||
|
||||
function samplePlan(): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/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 payloadWithRuntimeEvent(hookEventName: string): UserPromptSubmitPayload {
|
||||
const input = payload(
|
||||
'OMO_ULTRAGOAL_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_ULTRAGOAL_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("applyUserPromptUltragoalSteering - OMO directive patterns", () => {
|
||||
it("processes OMO_ULTRAGOAL_STEER: prompt and returns audit text on success", async () => {
|
||||
const repoRoot = await bootstrapPlanRepo();
|
||||
const out = await applyUserPromptUltragoalSteering(
|
||||
payload(
|
||||
'OMO_ULTRAGOAL_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.ultragoal.steer: pattern", async () => {
|
||||
const repoRoot = await bootstrapPlanRepo();
|
||||
const out = await applyUserPromptUltragoalSteering(
|
||||
payload(
|
||||
'omo.ultragoal.steer: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
|
||||
repoRoot,
|
||||
),
|
||||
);
|
||||
expect(out).toContain("accepted");
|
||||
});
|
||||
|
||||
it("processes omo ultragoal steer: pattern", async () => {
|
||||
const repoRoot = await bootstrapPlanRepo();
|
||||
const out = await applyUserPromptUltragoalSteering(
|
||||
payload(
|
||||
'omo ultragoal steer: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
|
||||
repoRoot,
|
||||
),
|
||||
);
|
||||
expect(out).toContain("annotate_ledger");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyUserPromptUltragoalSteering - non-matching prompts", () => {
|
||||
it("returns empty string when no directive in prompt", async () => {
|
||||
expect(await applyUserPromptUltragoalSteering(payload("just a normal user message", "/tmp"))).toBe("");
|
||||
});
|
||||
|
||||
it("returns empty for OMX_ULTRAGOAL_STEER (deprecated marker - must reject)", async () => {
|
||||
expect(
|
||||
await applyUserPromptUltragoalSteering(
|
||||
payload('OMX_ULTRAGOAL_STEER: {"kind":"annotate_ledger","evidence":"x","rationale":"y"}', "/tmp"),
|
||||
),
|
||||
).toBe("");
|
||||
});
|
||||
|
||||
it("returns empty when hook_event_name is not UserPromptSubmit", async () => {
|
||||
expect(await applyUserPromptUltragoalSteering(payloadWithRuntimeEvent("PostToolUse"))).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyUserPromptUltragoalSteering - error swallowing", () => {
|
||||
it("returns empty (never throws) when plan does not exist", async () => {
|
||||
const repoRoot = await mkdtemp(join(tmpdir(), "ug-nohook-"));
|
||||
const out = await applyUserPromptUltragoalSteering(
|
||||
payload(
|
||||
'OMO_ULTRAGOAL_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 applyUserPromptUltragoalSteering(payload("OMO_ULTRAGOAL_STEER: {bad", "/tmp"));
|
||||
expect(out).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("runUltragoalHookCli (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_ULTRAGOAL_STEER: {"kind":"annotate_ledger","source":"user_prompt_submit","evidence":"x","rationale":"y"}',
|
||||
repoRoot,
|
||||
),
|
||||
),
|
||||
]);
|
||||
const capture = captureStdout();
|
||||
await runUltragoalHookCli(stdin, capture.stdout);
|
||||
expect(capture.read().length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("writes nothing when stdin is empty", async () => {
|
||||
const capture = captureStdout();
|
||||
await runUltragoalHookCli(Readable.from([""]), capture.stdout);
|
||||
expect(capture.read()).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { requireAllCriteriaPass } from "../src/evidence.js";
|
||||
import type { UltragoalItem, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
import { UltragoalError } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
|
||||
function makeCriterion(overrides: Partial<UltragoalSuccessCriterion> = {}): UltragoalSuccessCriterion {
|
||||
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<UltragoalItem> = {}): UltragoalItem {
|
||||
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 UltragoalError 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(UltragoalError);
|
||||
});
|
||||
|
||||
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(UltragoalError);
|
||||
expect(() => requireAllCriteriaPass(goal2)).toThrow(UltragoalError);
|
||||
});
|
||||
|
||||
it("UltragoalError 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(UltragoalError);
|
||||
if (!(error instanceof UltragoalError)) throw error;
|
||||
expect(error.code).toBe("ultragoal_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 { ultragoalDir } from "../src/paths.js";
|
||||
import { readUltragoalPlan, writePlan } from "../src/plan-io.js";
|
||||
import type { UltragoalItem, UltragoalLedgerEntry, UltragoalPlan, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
import { UltragoalError } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
|
||||
async function bootstrapRepo(plan: UltragoalPlan): Promise<string> {
|
||||
const repo = await mkdtemp(join(tmpdir(), "ug-evidence-"));
|
||||
await mkdir(ultragoalDir(repo), { recursive: true });
|
||||
await writePlan(repo, plan);
|
||||
return repo;
|
||||
}
|
||||
|
||||
async function readLastLedgerEntry(repo: string): Promise<UltragoalLedgerEntry> {
|
||||
const lines = (await readFile(join(repo, ".omo/ultragoal/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: UltragoalPlan): UltragoalItem {
|
||||
const goal = plan.goals.at(0);
|
||||
if (goal === undefined) throw new Error("expected goal");
|
||||
return goal;
|
||||
}
|
||||
|
||||
function makeCriterion(overrides: Partial<UltragoalSuccessCriterion> = {}): UltragoalSuccessCriterion {
|
||||
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<UltragoalItem> = {}): UltragoalItem {
|
||||
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<UltragoalPlan> = {}): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/ledger.jsonl",
|
||||
codexGoalMode: "aggregate",
|
||||
codexObjective: "Complete the durable ultragoal plan in .omo/ultragoal/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 readUltragoalPlan(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(UltragoalError);
|
||||
});
|
||||
|
||||
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(UltragoalError);
|
||||
});
|
||||
|
||||
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(UltragoalError);
|
||||
});
|
||||
});
|
||||
|
||||
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"]);
|
||||
});
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
{ "goal": { "objective": "Complete the durable ultragoal 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
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"version": 1,
|
||||
"createdAt": "2026-05-23T00:00:00.000Z",
|
||||
"codexGoalMode": "aggregate",
|
||||
"codexObjective": "Complete the durable ultragoal plan in .omo/ultragoal/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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+18
@@ -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"]
|
||||
}
|
||||
}
|
||||
+8
@@ -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"
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"cwd": "/repo",
|
||||
"hook_event_name": "UserPromptSubmit",
|
||||
"model": "gpt-5.5",
|
||||
"permission_mode": "default",
|
||||
"prompt": "OMO_ULTRAGOAL_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,
|
||||
isUltragoalDone,
|
||||
ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE,
|
||||
} from "../src/goal-status.js";
|
||||
import type { UltragoalItem, UltragoalPlan, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
|
||||
function makeCriterion(overrides: Partial<UltragoalSuccessCriterion> = {}): UltragoalSuccessCriterion {
|
||||
return {
|
||||
id: "C001",
|
||||
scenario: "happy path",
|
||||
userModel: "happy",
|
||||
expectedEvidence: "observable proof",
|
||||
capturedEvidence: null,
|
||||
status: "pending",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeGoal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
return {
|
||||
id: "G001",
|
||||
title: "Goal one",
|
||||
objective: "Complete goal one",
|
||||
status: "pending",
|
||||
successCriteria: [],
|
||||
attempt: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makePlan(overrides: Partial<UltragoalPlan> = {}): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/ledger.jsonl",
|
||||
goals: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("isUltragoalDone", () => {
|
||||
it("returns true when all goals complete", () => {
|
||||
// given
|
||||
const plan = makePlan({
|
||||
goals: [makeGoal({ status: "complete" }), makeGoal({ id: "G002", status: "complete" })],
|
||||
});
|
||||
|
||||
// when
|
||||
const done = isUltragoalDone(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 = isUltragoalDone(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 = isUltragoalDone(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 ULTRAGOAL_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(ULTRAGOAL_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 ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE", () => {
|
||||
// when
|
||||
const objective = aggregateCodexObjective(makePlan());
|
||||
|
||||
// then
|
||||
expect(objective).toBe(ULTRAGOAL_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("ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE", () => {
|
||||
it("references the .omo/ultragoal path and excludes the legacy workspace", () => {
|
||||
const legacyWorkspace = [".", "om", "x"].join("");
|
||||
|
||||
expect(ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE).toContain(".omo/ultragoal");
|
||||
expect(ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE).not.toContain(legacyWorkspace);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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");
|
||||
});
|
||||
});
|
||||
|
||||
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/ultragoal/SKILL.md", () => {
|
||||
it("exists", async () => {
|
||||
const info = await stat(join(repoRoot, "skills/ultragoal/SKILL.md"));
|
||||
expect(info.isFile()).toBe(true);
|
||||
});
|
||||
|
||||
it("contains no omx references", async () => {
|
||||
const text = await readText("skills/ultragoal/SKILL.md");
|
||||
expect(text.toLowerCase()).not.toContain("omx");
|
||||
});
|
||||
|
||||
it("references the success criteria and record-evidence vocabulary", async () => {
|
||||
const text = await readText("skills/ultragoal/SKILL.md");
|
||||
expect(text.toLowerCase()).toMatch(/success criteria|successcriteria/);
|
||||
expect(text.toLowerCase()).toContain("record-evidence");
|
||||
});
|
||||
|
||||
it("uses the .omo workspace path", async () => {
|
||||
const text = await readText("skills/ultragoal/SKILL.md");
|
||||
expect(text).toContain(".omo/ultragoal");
|
||||
});
|
||||
});
|
||||
|
||||
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,37 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
repoRelative,
|
||||
ultragoalBriefPath,
|
||||
ultragoalDir,
|
||||
ultragoalGoalsPath,
|
||||
ultragoalLedgerPath,
|
||||
} from "../src/paths.ts";
|
||||
|
||||
describe("ultragoalDir(repo)", () => {
|
||||
it("returns repo + '/.omo/ultragoal'", () => {
|
||||
// when/then
|
||||
expect(ultragoalDir("/repo")).toBe("/repo/.omo/ultragoal");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ultragoal*Path helpers", () => {
|
||||
it("compose artifact filenames under ultragoalDir", () => {
|
||||
// when/then
|
||||
expect(ultragoalBriefPath("/r")).toBe("/r/.omo/ultragoal/brief.md");
|
||||
expect(ultragoalGoalsPath("/r")).toBe("/r/.omo/ultragoal/goals.json");
|
||||
expect(ultragoalLedgerPath("/r")).toBe("/r/.omo/ultragoal/ledger.jsonl");
|
||||
});
|
||||
});
|
||||
|
||||
describe("repoRelative", () => {
|
||||
it("strips repo prefix when path is inside repo", () => {
|
||||
// when/then
|
||||
expect(repoRelative("/repo/.omo/ultragoal/goals.json", "/repo")).toBe(".omo/ultragoal/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 { ultragoalBriefPath, ultragoalGoalsPath, ultragoalLedgerPath } from "../src/paths.js";
|
||||
import {
|
||||
addUltragoalGoal,
|
||||
createUltragoalPlan,
|
||||
deriveGoalCandidates,
|
||||
seedDefaultSuccessCriteria,
|
||||
startNextUltragoal,
|
||||
summarizeUltragoalPlan,
|
||||
} from "../src/plan-crud.js";
|
||||
import { writePlan } from "../src/plan-io.js";
|
||||
import type { UltragoalItem, UltragoalPlan, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
import { UltragoalError } 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(ultragoalLedgerPath(repoRoot), "utf8");
|
||||
return raw
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line).kind);
|
||||
}
|
||||
|
||||
function criterion(status: UltragoalSuccessCriterion["status"]): UltragoalSuccessCriterion {
|
||||
const [base] = seedDefaultSuccessCriteria(0, "Implement auth endpoint");
|
||||
if (base === undefined) throw new Error("expected seeded criterion");
|
||||
return { ...base, status };
|
||||
}
|
||||
|
||||
function makeGoal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
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: UltragoalItem[]): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/ledger.jsonl",
|
||||
codexGoalMode: "aggregate",
|
||||
goals,
|
||||
};
|
||||
}
|
||||
|
||||
function scheduled(result: Awaited<ReturnType<typeof startNextUltragoal>>) {
|
||||
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("createUltragoalPlan", () => {
|
||||
it("creates .omo/ultragoal/{brief.md, goals.json, ledger.jsonl} in repoRoot", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
const brief = await readBriefFixture();
|
||||
|
||||
await createUltragoalPlan(repoRoot, { brief });
|
||||
|
||||
expect(await readFile(ultragoalBriefPath(repoRoot), "utf8")).toBe(brief.endsWith("\n") ? brief : `${brief}\n`);
|
||||
expect(await readFile(ultragoalGoalsPath(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 createUltragoalPlan(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 createUltragoalPlan(repoRoot, { brief: "first" });
|
||||
|
||||
await expect(createUltragoalPlan(repoRoot, { brief: "second" })).rejects.toThrow(UltragoalError);
|
||||
await expect(createUltragoalPlan(repoRoot, { brief: "second" })).rejects.toThrow("Refusing to overwrite");
|
||||
});
|
||||
|
||||
it("aggregate is the default codexGoalMode", async () => {
|
||||
const plan = await createUltragoalPlan(await makeRepo(), { brief: "Ship the feature" });
|
||||
|
||||
expect(plan.codexGoalMode).toBe("aggregate");
|
||||
expect(plan.codexObjective).toContain(".omo/ultragoal/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("addUltragoalGoal", () => {
|
||||
it("appends a new goal to plan with seeded successCriteria", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
await createUltragoalPlan(repoRoot, { brief: "Build auth" });
|
||||
|
||||
const { plan, goal } = await addUltragoalGoal(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 createUltragoalPlan(repoRoot, { brief: "Build auth" });
|
||||
|
||||
await addUltragoalGoal(repoRoot, { title: "Add rate limit", objective: "Throttle login" });
|
||||
|
||||
expect(await ledgerKinds(repoRoot)).toEqual(["plan_created", "goal_added"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("startNextUltragoal", () => {
|
||||
it("picks the first pending goal", async () => {
|
||||
const repoRoot = await makeRepo();
|
||||
await createUltragoalPlan(repoRoot, { brief: "- First\n- Second" });
|
||||
|
||||
const result = scheduled(await startNextUltragoal(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 createUltragoalPlan(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 startNextUltragoal(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", "ultragoal"), { recursive: true });
|
||||
await writePlan(repoRoot, makePlan([failed]));
|
||||
|
||||
const result = scheduled(await startNextUltragoal(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", "ultragoal"), { recursive: true });
|
||||
await writePlan(repoRoot, makePlan([makeGoal({ status: "complete" })]));
|
||||
|
||||
const result = await startNextUltragoal(repoRoot, {});
|
||||
|
||||
expect(result).toMatchObject({ done: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("summarizeUltragoalPlan", () => {
|
||||
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(summarizeUltragoalPlan(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(summarizeUltragoalPlan(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 { ultragoalDir, ultragoalGoalsPath, ultragoalLedgerPath } from "../src/paths.js";
|
||||
import {
|
||||
appendLedger,
|
||||
readSteeringLedgerEntries,
|
||||
readUltragoalPlan,
|
||||
withUltragoalMutationLock,
|
||||
writePlan,
|
||||
} from "../src/plan-io.js";
|
||||
import type { UltragoalItem, UltragoalLedgerEntry, UltragoalPlan } from "../src/types.js";
|
||||
import { UltragoalError } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
const STABLE_OBJECTIVE =
|
||||
"Complete the durable ultragoal plan in .omo/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omo/ultragoal/ledger.jsonl as the audit trail.";
|
||||
|
||||
function makeGoal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
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<UltragoalPlan> = {}): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/ledger.jsonl",
|
||||
codexGoalMode: "aggregate",
|
||||
codexObjective: STABLE_OBJECTIVE,
|
||||
codexObjectiveAliases: [],
|
||||
goals: [makeGoal()],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function entry(kind: UltragoalLedgerEntry["kind"], goalId = "G001"): UltragoalLedgerEntry {
|
||||
return { at: NOW, kind, goalId };
|
||||
}
|
||||
|
||||
async function makeRepo(): Promise<string> {
|
||||
return mkdtemp(join(tmpdir(), "ug-io-"));
|
||||
}
|
||||
|
||||
async function writeRawPlan(repoRoot: string, plan: UltragoalPlan): Promise<void> {
|
||||
await mkdir(ultragoalDir(repoRoot), { recursive: true });
|
||||
await writeFile(ultragoalGoalsPath(repoRoot), `${JSON.stringify(plan, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
async function readLedgerLines(repoRoot: string): Promise<string[]> {
|
||||
const raw = await readFile(ultragoalLedgerPath(repoRoot), "utf8");
|
||||
return raw.split(/\r?\n/).filter(Boolean);
|
||||
}
|
||||
|
||||
describe("readUltragoalPlan", () => {
|
||||
let repoRoot = "";
|
||||
|
||||
beforeEach(async () => {
|
||||
// given
|
||||
repoRoot = await makeRepo();
|
||||
});
|
||||
|
||||
it("throws UltragoalError when goals.json is missing", async () => {
|
||||
// when/then
|
||||
await expect(readUltragoalPlan(repoRoot)).rejects.toThrow(UltragoalError);
|
||||
await expect(readUltragoalPlan(repoRoot)).rejects.toThrow("omo ultragoal create-goals");
|
||||
});
|
||||
|
||||
it("returns parsed plan when fixture is present", async () => {
|
||||
// given
|
||||
await mkdir(ultragoalDir(repoRoot), { recursive: true });
|
||||
await copyFile(join(process.cwd(), "test", "fixtures", "sample-plan.json"), ultragoalGoalsPath(repoRoot));
|
||||
|
||||
// when
|
||||
const plan = await readUltragoalPlan(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 ultragoal stories in .omo/ultragoal/goals.json: G001 Build auth service";
|
||||
await writeRawPlan(repoRoot, makePlan({ codexObjective: legacyObjective }));
|
||||
|
||||
// when
|
||||
const plan = await readUltragoalPlan(repoRoot);
|
||||
|
||||
// then
|
||||
expect(plan.codexObjective).toBe(STABLE_OBJECTIVE);
|
||||
expect(plan.codexObjectiveAliases).toContain(legacyObjective);
|
||||
const persisted = JSON.parse(await readFile(ultragoalGoalsPath(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(ultragoalGoalsPath(repoRoot), "utf8");
|
||||
expect(JSON.parse(raw)).toMatchObject({ version: 1, goals: [{ id: "G001" }] });
|
||||
expect((await readdir(ultragoalDir(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(ultragoalGoalsPath(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(ultragoalLedgerPath(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("withUltragoalMutationLock", () => {
|
||||
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((_) =>
|
||||
withUltragoalMutationLock(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 { UltragoalItem, UltragoalPlan } from "../src/types.js";
|
||||
import { UltragoalError } 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 UltragoalItem {
|
||||
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): UltragoalError {
|
||||
try {
|
||||
validateQualityGate(input);
|
||||
} catch (error) {
|
||||
if (error instanceof UltragoalError) return error;
|
||||
throw error;
|
||||
}
|
||||
throw new Error("Expected UltragoalError");
|
||||
}
|
||||
|
||||
function makeGoal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
return {
|
||||
id: "G001",
|
||||
title: "Goal one",
|
||||
objective: "Complete goal one",
|
||||
status: "pending",
|
||||
successCriteria: [],
|
||||
attempt: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makePlan(goals: UltragoalItem[]): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/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 UltragoalError when aiSlopCleaner missing", () => {
|
||||
// when
|
||||
const error = getQualityGateError(makeGate({ aiSlopCleaner: undefined }));
|
||||
|
||||
// then
|
||||
expect(error.code).toBe("ULTRAGOAL_QUALITY_GATE_INVALID");
|
||||
});
|
||||
|
||||
it("throws UltragoalError when verification missing", () => {
|
||||
// when
|
||||
const error = getQualityGateError(makeGate({ verification: undefined }));
|
||||
|
||||
// then
|
||||
expect(error.code).toBe("ULTRAGOAL_QUALITY_GATE_INVALID");
|
||||
});
|
||||
|
||||
it("throws UltragoalError when codeReview missing", () => {
|
||||
// when
|
||||
const error = getQualityGateError(makeGate({ codeReview: undefined }));
|
||||
|
||||
// then
|
||||
expect(error.code).toBe("ULTRAGOAL_QUALITY_GATE_INVALID");
|
||||
});
|
||||
|
||||
it("throws UltragoalError when criteriaCoverage missing (NEW)", () => {
|
||||
// when
|
||||
const error = getQualityGateError(makeGate({ criteriaCoverage: undefined }));
|
||||
|
||||
// then
|
||||
expect(error.code).toBe("ULTRAGOAL_QUALITY_GATE_INVALID");
|
||||
});
|
||||
|
||||
it("throws UltragoalError 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 UltragoalError 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 UltragoalError 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 { ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE } from "../src/goal-status.js";
|
||||
import { ultragoalDir, ultragoalLedgerPath } from "../src/paths.js";
|
||||
import { writePlan } from "../src/plan-io.js";
|
||||
import { recordFinalReviewBlockers } from "../src/review-blockers.js";
|
||||
import type { UltragoalItem, UltragoalPlan, UltragoalSuccessCriterion } from "../src/types.js";
|
||||
import { UltragoalError } from "../src/types.js";
|
||||
|
||||
const NOW = "2026-05-23T00:00:00.000Z";
|
||||
const VALID_SNAPSHOT_JSON = JSON.stringify({
|
||||
goal: { objective: ULTRAGOAL_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<UltragoalSuccessCriterion> = {}): UltragoalSuccessCriterion {
|
||||
return {
|
||||
id: "C001",
|
||||
scenario: "happy path",
|
||||
userModel: "happy",
|
||||
expectedEvidence: "observable proof",
|
||||
capturedEvidence: null,
|
||||
status: "pending",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeGoal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
return {
|
||||
id: "G001",
|
||||
title: "Build durable plan",
|
||||
objective: "Complete one ultragoal story",
|
||||
status: "pending",
|
||||
successCriteria: [makeCriterion()],
|
||||
attempt: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makePlan(overrides: Partial<UltragoalPlan> = {}): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/ledger.jsonl",
|
||||
codexGoalMode: "aggregate",
|
||||
codexObjective: ULTRAGOAL_AGGREGATE_CODEX_OBJECTIVE,
|
||||
goals: [makeGoal({ status: "in_progress" })],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
async function bootstrapRepo(plan: UltragoalPlan): Promise<string> {
|
||||
const repo = await mkdtemp(join(tmpdir(), "ug-review-blockers-"));
|
||||
await mkdir(ultragoalDir(repo), { recursive: true });
|
||||
await writePlan(repo, plan);
|
||||
return repo;
|
||||
}
|
||||
|
||||
async function ledgerKinds(repo: string): Promise<string[]> {
|
||||
const raw = await readFile(ultragoalLedgerPath(repo), "utf8");
|
||||
return raw
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line).kind);
|
||||
}
|
||||
|
||||
async function expectUltragoalCode(action: () => Promise<unknown>, code: string): Promise<void> {
|
||||
try {
|
||||
await action();
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(UltragoalError);
|
||||
if (!(error instanceof UltragoalError)) throw error;
|
||||
expect(error.code).toBe(code);
|
||||
return;
|
||||
}
|
||||
throw new Error("Expected UltragoalError");
|
||||
}
|
||||
|
||||
function finalPlan(): UltragoalPlan {
|
||||
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 ultragoal_goal_not_found for unknown goalId", async () => {
|
||||
const repo = await bootstrapRepo(finalPlan());
|
||||
await expectUltragoalCode(
|
||||
() => recordFinalReviewBlockers(repo, { ...validArgs, goalId: "G999" }),
|
||||
"ultragoal_goal_not_found",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws ultragoal_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 expectUltragoalCode(() => recordFinalReviewBlockers(repo, validArgs), "ultragoal_goal_not_in_progress");
|
||||
});
|
||||
|
||||
it("throws ultragoal_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 expectUltragoalCode(
|
||||
() => recordFinalReviewBlockers(repo, { ...validArgs, goalId: "G001" }),
|
||||
"ultragoal_not_final_story",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws ultragoal_codex_snapshot_mismatch when objective mismatches", async () => {
|
||||
const repo = await bootstrapRepo(finalPlan());
|
||||
const codexGoalJson = JSON.stringify({ goal: { objective: "wrong", status: "active" } });
|
||||
|
||||
await expectUltragoalCode(
|
||||
() => recordFinalReviewBlockers(repo, { ...validArgs, codexGoalJson }),
|
||||
"ultragoal_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,304 @@
|
||||
import { mkdtemp, readFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ultragoalGoalsPath } from "../src/paths.js";
|
||||
import { readSteeringLedgerEntries, readUltragoalPlan, writePlan } from "../src/plan-io.js";
|
||||
import {
|
||||
applySteeringMutation,
|
||||
parseUltragoalSteeringDirective,
|
||||
steerUltragoal,
|
||||
validateUltragoalSteeringProposal,
|
||||
} from "../src/steering.js";
|
||||
import type {
|
||||
UltragoalItem,
|
||||
UltragoalPlan,
|
||||
UltragoalSteeringProposal,
|
||||
UltragoalSuccessCriterion,
|
||||
UltragoalSuccessCriterionUserModel,
|
||||
} 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?: UltragoalSuccessCriterionUserModel;
|
||||
};
|
||||
type SteeringInput = UltragoalSteeringProposal & CriterionSteeringFields;
|
||||
|
||||
function criterion(overrides: Partial<UltragoalSuccessCriterion> = {}): UltragoalSuccessCriterion {
|
||||
return {
|
||||
id: "C001",
|
||||
scenario: "old scenario",
|
||||
userModel: "happy",
|
||||
expectedEvidence: "vague evidence",
|
||||
capturedEvidence: null,
|
||||
status: "pending",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function goal(overrides: Partial<UltragoalItem> = {}): UltragoalItem {
|
||||
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<UltragoalPlan> = {}): UltragoalPlan {
|
||||
return {
|
||||
version: 1,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
briefPath: ".omo/ultragoal/brief.md",
|
||||
goalsPath: ".omo/ultragoal/goals.json",
|
||||
ledgerPath: ".omo/ultragoal/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: UltragoalPlan = plan()): Promise<string> {
|
||||
const repoRoot = await mkdtemp(join(tmpdir(), "ug-steer-"));
|
||||
await writePlan(repoRoot, seed);
|
||||
return repoRoot;
|
||||
}
|
||||
|
||||
describe("validateUltragoalSteeringProposal", () => {
|
||||
it("accepts valid add_subgoal", async () => {
|
||||
const proposal: unknown = JSON.parse(
|
||||
await readFile(join(process.cwd(), "test/fixtures/steering-proposal.json"), "utf8"),
|
||||
);
|
||||
expect(validateUltragoalSteeringProposal(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 = validateUltragoalSteeringProposal(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(validateUltragoalSteeringProposal(done, steering()).invariant.accepted).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects split_subgoal without children", () => {
|
||||
const audit = validateUltragoalSteeringProposal(
|
||||
plan(),
|
||||
steering({ kind: "split_subgoal", targetGoalId: "G001" }),
|
||||
);
|
||||
expect(audit.invariant.accepted).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects reorder_pending with unknown goal id", () => {
|
||||
const audit = validateUltragoalSteeringProposal(
|
||||
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 = validateUltragoalSteeringProposal(
|
||||
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 = validateUltragoalSteeringProposal(plan(), steering({ kind: "revise_criterion", ...overrides }));
|
||||
expect(audit.invariant.accepted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("steerUltragoal", () => {
|
||||
it("add_subgoal: appends goal + ledger entry", async () => {
|
||||
const repoRoot = await repoWithPlan();
|
||||
const result = await steerUltragoal(repoRoot, steering({ idempotencyKey: "add" }));
|
||||
const persisted = await readUltragoalPlan(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 steerUltragoal(
|
||||
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 steerUltragoal(
|
||||
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 steerUltragoal(
|
||||
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 steerUltragoal(repoRoot, steering({ kind: "annotate_ledger" }));
|
||||
expect(result.plan.goals).toEqual(seed.goals);
|
||||
expect(await readFile(ultragoalGoalsPath(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 steerUltragoal(
|
||||
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 steerUltragoal(
|
||||
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 steerUltragoal(
|
||||
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 = validateUltragoalSteeringProposal(
|
||||
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 steerUltragoal(repoRoot, steering({ idempotencyKey: "same-key" }));
|
||||
const second = await steerUltragoal(repoRoot, steering({ idempotencyKey: "same-key" }));
|
||||
expect(second.deduped).toBe(true);
|
||||
expect((await readUltragoalPlan(repoRoot)).goals).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseUltragoalSteeringDirective", () => {
|
||||
it.each(["OMO_ULTRAGOAL_STEER", "omo.ultragoal.steer", "omo ultragoal steer"])("parses %s pattern", (marker) => {
|
||||
expect(parseUltragoalSteeringDirective(`${marker}: ${JSON.stringify(steering())}`)).toMatchObject({
|
||||
kind: "add_subgoal",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null when no marker", () => {
|
||||
expect(parseUltragoalSteeringDirective(JSON.stringify(steering()))).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when JSON malformed after marker", () => {
|
||||
expect(parseUltragoalSteeringDirective("OMO_ULTRAGOAL_STEER: {bad json")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for deprecated markers", () => {
|
||||
const marker = ["OM", "X_ULTRAGOAL_STEER"].join("");
|
||||
expect(parseUltragoalSteeringDirective(`${marker}: ${JSON.stringify(steering())}`)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
iso,
|
||||
ULTRAGOAL_BRIEF,
|
||||
ULTRAGOAL_CRITERION_STATUSES,
|
||||
ULTRAGOAL_DIR,
|
||||
ULTRAGOAL_GOALS,
|
||||
ULTRAGOAL_LEDGER,
|
||||
ULTRAGOAL_STEERING_MUTATION_KINDS,
|
||||
ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS,
|
||||
UltragoalError,
|
||||
} from "../src/types.ts";
|
||||
|
||||
describe("ultragoal domain constants", () => {
|
||||
describe("when checking workspace paths", () => {
|
||||
it("then ULTRAGOAL_DIR points to the omo workspace", () => {
|
||||
expect(ULTRAGOAL_DIR).toBe(".omo/ultragoal");
|
||||
});
|
||||
|
||||
it("then artifact filenames are stable", () => {
|
||||
expect(ULTRAGOAL_BRIEF).toBe("brief.md");
|
||||
expect(ULTRAGOAL_GOALS).toBe("goals.json");
|
||||
expect(ULTRAGOAL_LEDGER).toBe("ledger.jsonl");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when checking steering mutation kinds", () => {
|
||||
it("then includes the new revise_criterion kind", () => {
|
||||
expect(ULTRAGOAL_STEERING_MUTATION_KINDS).toContain("revise_criterion");
|
||||
});
|
||||
|
||||
it("then totals 7 kinds", () => {
|
||||
expect(ULTRAGOAL_STEERING_MUTATION_KINDS).toHaveLength(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when checking criterion user models", () => {
|
||||
it("then exposes 4 user models including adversarial", () => {
|
||||
expect(ULTRAGOAL_SUCCESS_CRITERION_USER_MODELS).toEqual(["happy", "edge", "regression", "adversarial"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when checking criterion statuses", () => {
|
||||
it("then exposes pending/pass/fail/blocked", () => {
|
||||
expect(ULTRAGOAL_CRITERION_STATUSES).toEqual(["pending", "pass", "fail", "blocked"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("UltragoalError", () => {
|
||||
describe("when constructed with code", () => {
|
||||
it("then is an Error instance carrying the code", () => {
|
||||
const err = new UltragoalError("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 UltragoalError("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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user