oh-my-codex 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +2 -2
- package/Cargo.toml +1 -1
- package/README.ja.md +1 -0
- package/README.ko.md +1 -0
- package/README.md +32 -6
- package/README.vi.md +1 -0
- package/README.zh-TW.md +1 -0
- package/README.zh.md +1 -0
- package/dist/autoresearch/__tests__/contracts.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/contracts.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/contracts.test.js +91 -0
- package/dist/autoresearch/__tests__/contracts.test.js.map +1 -0
- package/dist/autoresearch/__tests__/runtime-parity-extra.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/runtime-parity-extra.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/runtime-parity-extra.test.js +349 -0
- package/dist/autoresearch/__tests__/runtime-parity-extra.test.js.map +1 -0
- package/dist/autoresearch/__tests__/runtime.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/runtime.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/runtime.test.js +211 -0
- package/dist/autoresearch/__tests__/runtime.test.js.map +1 -0
- package/dist/autoresearch/contracts.d.ts +31 -0
- package/dist/autoresearch/contracts.d.ts.map +1 -0
- package/dist/autoresearch/contracts.js +189 -0
- package/dist/autoresearch/contracts.js.map +1 -0
- package/dist/autoresearch/runtime.d.ts +132 -0
- package/dist/autoresearch/runtime.d.ts.map +1 -0
- package/dist/autoresearch/runtime.js +1028 -0
- package/dist/autoresearch/runtime.js.map +1 -0
- package/dist/cli/__tests__/autoresearch-guided.test.d.ts +2 -0
- package/dist/cli/__tests__/autoresearch-guided.test.d.ts.map +1 -0
- package/dist/cli/__tests__/autoresearch-guided.test.js +151 -0
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -0
- package/dist/cli/__tests__/autoresearch.test.d.ts +2 -0
- package/dist/cli/__tests__/autoresearch.test.d.ts.map +1 -0
- package/dist/cli/__tests__/autoresearch.test.js +306 -0
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -0
- package/dist/cli/__tests__/exec.test.d.ts +2 -0
- package/dist/cli/__tests__/exec.test.d.ts.map +1 -0
- package/dist/cli/__tests__/exec.test.js +107 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -0
- package/dist/cli/__tests__/explore.test.js +25 -0
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +73 -11
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js +1 -0
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +2 -0
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/runtime-native-resolution.test.d.ts +2 -0
- package/dist/cli/__tests__/runtime-native-resolution.test.d.ts.map +1 -0
- package/dist/cli/__tests__/runtime-native-resolution.test.js.map +1 -0
- package/dist/cli/__tests__/runtime-native.test.d.ts +2 -0
- package/dist/cli/__tests__/runtime-native.test.d.ts.map +1 -0
- package/dist/cli/__tests__/runtime-native.test.js.map +1 -0
- package/dist/cli/__tests__/session-search-help.test.js +1 -0
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +25 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +53 -3
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skill-validation.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-skill-validation.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-skill-validation.test.js +44 -0
- package/dist/cli/__tests__/setup-skill-validation.test.js.map +1 -0
- package/dist/cli/__tests__/sparkshell-cli.test.js +53 -2
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +13 -4
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +127 -5
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +19 -0
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/autoresearch-guided.d.ts +18 -0
- package/dist/cli/autoresearch-guided.d.ts.map +1 -0
- package/dist/cli/autoresearch-guided.js +141 -0
- package/dist/cli/autoresearch-guided.js.map +1 -0
- package/dist/cli/autoresearch.d.ts +11 -0
- package/dist/cli/autoresearch.d.ts.map +1 -0
- package/dist/cli/autoresearch.js +188 -0
- package/dist/cli/autoresearch.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +36 -8
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +8 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +11 -5
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +161 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/runtime-native.d.ts +23 -0
- package/dist/cli/runtime-native.d.ts.map +1 -0
- package/dist/cli/runtime-native.js +86 -0
- package/dist/cli/runtime-native.js.map +1 -0
- package/dist/cli/setup.d.ts +22 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +167 -19
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts +1 -0
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +24 -3
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +101 -10
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +4 -2
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +37 -8
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +79 -7
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +27 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +35 -3
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +30 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +19 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +51 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +6 -0
- package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +34 -13
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +5 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +16 -7
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/mcp/__tests__/runtime-run-native-cutover.test.d.ts +2 -0
- package/dist/mcp/__tests__/runtime-run-native-cutover.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/runtime-run-native-cutover.test.js.map +1 -0
- package/dist/modes/__tests__/base-autoresearch-contract.test.d.ts +2 -0
- package/dist/modes/__tests__/base-autoresearch-contract.test.d.ts.map +1 -0
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +34 -0
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -0
- package/dist/modes/base.d.ts +2 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +28 -26
- package/dist/modes/base.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +19 -1
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +9 -32
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.d.ts +2 -0
- package/dist/planning/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/planning/__tests__/artifacts.test.js +42 -0
- package/dist/planning/__tests__/artifacts.test.js.map +1 -0
- package/dist/planning/artifacts.d.ts +20 -0
- package/dist/planning/artifacts.d.ts.map +1 -0
- package/dist/planning/artifacts.js +105 -0
- package/dist/planning/artifacts.js.map +1 -0
- package/dist/team/__tests__/allocation-policy.test.js +72 -0
- package/dist/team/__tests__/allocation-policy.test.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +12 -1
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/events.test.js +118 -0
- package/dist/team/__tests__/events.test.js.map +1 -1
- package/dist/team/__tests__/followup-planner.test.js +11 -1
- package/dist/team/__tests__/followup-planner.test.js.map +1 -1
- package/dist/team/__tests__/rebalance-policy.test.js +43 -0
- package/dist/team/__tests__/rebalance-policy.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +522 -6
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/shutdown-fallback.test.d.ts +2 -0
- package/dist/team/__tests__/shutdown-fallback.test.d.ts.map +1 -0
- package/dist/team/__tests__/shutdown-fallback.test.js +102 -0
- package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session.test.js +52 -42
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +27 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worktree.test.js +26 -0
- package/dist/team/__tests__/worktree.test.js.map +1 -1
- package/dist/team/allocation-policy.d.ts +6 -0
- package/dist/team/allocation-policy.d.ts.map +1 -1
- package/dist/team/allocation-policy.js +79 -2
- package/dist/team/allocation-policy.js.map +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +9 -0
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/contracts.d.ts +3 -1
- package/dist/team/contracts.d.ts.map +1 -1
- package/dist/team/contracts.js +31 -0
- package/dist/team/contracts.js.map +1 -1
- package/dist/team/followup-planner.d.ts +7 -0
- package/dist/team/followup-planner.d.ts.map +1 -1
- package/dist/team/followup-planner.js +30 -0
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +511 -9
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state/events.d.ts.map +1 -1
- package/dist/team/state/events.js +2 -11
- package/dist/team/state/events.js.map +1 -1
- package/dist/team/state/monitor.d.ts +11 -0
- package/dist/team/state/monitor.d.ts.map +1 -1
- package/dist/team/state/monitor.js +1 -0
- package/dist/team/state/monitor.js.map +1 -1
- package/dist/team/state.d.ts +11 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-ops.d.ts +1 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/tmux-session.d.ts +4 -32
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +13 -66
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +50 -16
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.d.ts +6 -2
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +40 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +69 -3
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/paths.d.ts +14 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +42 -1
- package/dist/utils/paths.js.map +1 -1
- package/package.json +2 -2
- package/scripts/__tests__/smoke-packed-install.test.mjs +51 -0
- package/scripts/build-sparkshell.mjs +4 -1
- package/scripts/eval-candidate-handoff.js +8 -0
- package/scripts/eval-cli-discoverability.js +40 -0
- package/scripts/eval-fresh-run-tagging.js +8 -0
- package/scripts/eval-help-consistency.js +11 -0
- package/scripts/eval-parity-smoke.js +20 -0
- package/scripts/eval-parity-sweep.js +26 -0
- package/scripts/eval-resume-dirty-guard.js +8 -0
- package/scripts/eval-security-path-traversal.js +38 -0
- package/scripts/notify-hook.js +41 -11
- package/scripts/smoke-packed-install.mjs +28 -6
- package/scripts/tmux-hook-engine.js +3 -0
- package/skills/deep-interview/SKILL.md +93 -31
- package/skills/pipeline/SKILL.md +1 -1
- package/skills/team/SKILL.md +7 -0
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/README.ja.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/oh-my-codex)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
|
+
[](https://discord.gg/qRJw62Gvh7)
|
|
12
13
|
|
|
13
14
|
> **[Website](https://yeachan-heo.github.io/oh-my-codex-website/)** | **[Documentation](https://yeachan-heo.github.io/oh-my-codex-website/docs.html)** | **[CLI Reference](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#cli-reference)** | **[Workflows](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#workflows)** | **[OpenClaw 統合ガイド](./docs/openclaw-integration.ja.md)** | **[GitHub](https://github.com/Yeachan-Heo/oh-my-codex)** | **[npm](https://www.npmjs.com/package/oh-my-codex)**
|
|
14
15
|
|
package/README.ko.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/oh-my-codex)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
|
+
[](https://discord.gg/qRJw62Gvh7)
|
|
12
13
|
|
|
13
14
|
> **[Website](https://yeachan-heo.github.io/oh-my-codex-website/)** | **[Documentation](https://yeachan-heo.github.io/oh-my-codex-website/docs.html)** | **[CLI Reference](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#cli-reference)** | **[Workflows](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#workflows)** | **[OpenClaw 통합 가이드](./docs/openclaw-integration.ko.md)** | **[GitHub](https://github.com/Yeachan-Heo/oh-my-codex)** | **[npm](https://www.npmjs.com/package/oh-my-codex)**
|
|
14
15
|
|
package/README.md
CHANGED
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/oh-my-codex)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
|
+
[](https://discord.gg/qRJw62Gvh7)
|
|
12
13
|
|
|
13
|
-
> **[Website](https://yeachan-heo.github.io/oh-my-codex-website/)** | **[Documentation](https://yeachan-heo.github.io/oh-my-codex-website/docs.html)** | **[CLI Reference](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#cli-reference)** | **[Workflows](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#workflows)** | **[OpenClaw Integration Guide](./docs/openclaw-integration.md)** | **[GitHub](https://github.com/Yeachan-Heo/oh-my-codex)** | **[npm](https://www.npmjs.com/package/oh-my-codex)**
|
|
14
|
+
> **[Website](https://yeachan-heo.github.io/oh-my-codex-website/)** | **[Documentation](https://yeachan-heo.github.io/oh-my-codex-website/docs.html)** | **[CLI Reference](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#cli-reference)** | **[Workflows](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#workflows)** | **[OpenClaw Integration Guide](./docs/openclaw-integration.md)** | **[GitHub](https://github.com/Yeachan-Heo/oh-my-codex)** | **[npm](https://www.npmjs.com/package/oh-my-codex)** | **[Discord](https://discord.gg/qRJw62Gvh7)**
|
|
14
15
|
|
|
15
16
|
Operational runtime for [OpenAI Codex CLI](https://github.com/openai/codex).
|
|
16
17
|
|
|
17
18
|
## Featured Guides
|
|
18
19
|
|
|
19
20
|
- [OpenClaw / Generic Notification Gateway Integration Guide](./docs/openclaw-integration.md)
|
|
20
|
-
- [Spark Initiative release notes (v0.9.
|
|
21
|
-
- [Spark Initiative release body (v0.9.
|
|
21
|
+
- [Spark Initiative hotfix release notes (v0.9.1)](./docs/release-notes-0.9.1.md)
|
|
22
|
+
- [Spark Initiative hotfix release body (v0.9.1)](./docs/release-body-0.9.1.md)
|
|
22
23
|
|
|
23
24
|
## Languages
|
|
24
25
|
|
|
@@ -148,6 +149,8 @@ omx --xhigh --madmax
|
|
|
148
149
|
|
|
149
150
|
`0.9.0` is the **Spark Initiative** release: OMX now ships a stronger native fast path for read-only repository discovery, shell-native inspection, and cross-platform native distribution.
|
|
150
151
|
|
|
152
|
+
`0.9.1` is the clean superseding hotfix release for the Spark Initiative line: it carries the packed-install smoke hydration fix that was merged into `dev` after `v0.9.0`, while `v0.9.0` remains historically red.
|
|
153
|
+
|
|
151
154
|
- **`omx explore` native harness** — qualifying read-only exploration runs through a constrained native Rust helper with explicit allowlists and fallback behavior.
|
|
152
155
|
- **`omx sparkshell`** — a first-class operator surface for fast shell-native inspection, adaptive summaries, and explicit tmux-pane capture.
|
|
153
156
|
- **Cross-platform native release assets** — tagged releases now publish native archives for both `omx-explore-harness` and `omx-sparkshell`, plus `native-release-manifest.json` for hydration and checksum verification.
|
|
@@ -156,9 +159,9 @@ omx --xhigh --madmax
|
|
|
156
159
|
|
|
157
160
|
Spark Initiative references:
|
|
158
161
|
|
|
159
|
-
- [Release notes: `v0.9.
|
|
160
|
-
- [Release body: `v0.9.
|
|
161
|
-
- [Release readiness draft: `v0.9.
|
|
162
|
+
- [Release notes: `v0.9.1`](./docs/release-notes-0.9.1.md)
|
|
163
|
+
- [Release body: `v0.9.1`](./docs/release-body-0.9.1.md)
|
|
164
|
+
- [Release readiness draft: `v0.9.1`](./docs/qa/release-readiness-0.9.1.md)
|
|
162
165
|
|
|
163
166
|
Quick Spark Initiative smoke path:
|
|
164
167
|
|
|
@@ -269,6 +272,7 @@ omx ask ... # Ask local provider advisor (claude|gemini), writes .omx/art
|
|
|
269
272
|
omx resume # Resume a previous interactive Codex session
|
|
270
273
|
omx explore ... # Default read-only exploration entrypoint (may use sparkshell backend)
|
|
271
274
|
omx ralph # Launch Codex with ralph persistence mode active
|
|
275
|
+
omx autoresearch <mission-dir> # Launch thin-supervisor autoresearch with keep/discard/reset parity
|
|
272
276
|
omx status # Show active modes
|
|
273
277
|
omx cancel # Cancel active execution modes
|
|
274
278
|
omx reasoning <mode> # low|medium|high|xhigh
|
|
@@ -299,12 +303,34 @@ omx explore --prompt-file prompts/explore-task.md
|
|
|
299
303
|
USE_OMX_EXPLORE_CMD=1 omx # advisory preference for simple read-only exploration prompts
|
|
300
304
|
```
|
|
301
305
|
|
|
306
|
+
Autoresearch command example:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
mkdir -p missions/demo
|
|
310
|
+
cat > missions/demo/mission.md <<'EOF'
|
|
311
|
+
# Mission
|
|
312
|
+
Solve the scoped task in this repository.
|
|
313
|
+
EOF
|
|
314
|
+
cat > missions/demo/sandbox.md <<'EOF'
|
|
315
|
+
---
|
|
316
|
+
evaluator:
|
|
317
|
+
command: node scripts/eval.js
|
|
318
|
+
format: json
|
|
319
|
+
---
|
|
320
|
+
Stay inside the mission boundary and stop when the evaluator passes.
|
|
321
|
+
EOF
|
|
322
|
+
omx autoresearch missions/demo
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`omx autoresearch` now runs as a thin supervisor around one Codex experiment session at a time. A fresh launch creates a run-tagged `autoresearch/<slug>/<run-tag>` worktree lane, seeds the baseline evaluator row, writes authoritative per-run artifacts under `.omx/logs/autoresearch/<run-id>/`, and expects the session to hand back a repo-root `candidate.json` artifact. Each iteration's bootstrap instructions include the current baseline/last-kept state plus a bounded recent ledger summary, and `status=candidate` artifacts must point at the current worktree `HEAD` and last-kept base commit. After each session exit, OMX evaluates the candidate, records keep/discard/reset state, hard-resets discarded or ambiguous experiments back to the last kept commit, and relaunches the next iteration unless the run aborts. Use `omx autoresearch --resume <run-id>` to continue an existing run from its manifest/worktree.
|
|
326
|
+
|
|
302
327
|
`omx explore` is the default OMX surface for simple read-only exploration. It stays intentionally read-only and shell-only, and qualifying shell-native read-only tasks may be routed through `omx sparkshell` as a backend when that is the cheaper/more direct fit. The routing flag only adds advisory steering in generated session instructions; ambiguous or implementation-heavy requests stay on the normal Codex path, and OMX falls back normally if the explore harness is unavailable. The harness constrains Codex through a temporary allowlisted shell/bin layer so only approved repository-inspection command families are available during the offloaded run.
|
|
303
328
|
|
|
304
329
|
- Current shell allowlist: `rg`, `grep`, `ls`, `find`, `wc`, `cat`, `head`, `tail`, `pwd`, `printf`
|
|
305
330
|
- Current shell restrictions: no pipes, redirection, `&&`, `||`, `;`, subshells, path-qualified binaries, non-allowlisted commands, stdin-fed inspection, or path escapes outside the target repository (including existing symlink-resolved escapes)
|
|
306
331
|
- `omx explore` is **not** a full parity surface for modern Codex read-only mode: it does not promise web search, MCP, images, or general-purpose tool access
|
|
307
332
|
|
|
333
|
+
|
|
308
334
|
Packaging / install notes:
|
|
309
335
|
|
|
310
336
|
- Published npm packages now include the Rust workspace files for the explore harness (`Cargo.toml`, `Cargo.lock`, `crates/`).
|
package/README.vi.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/oh-my-codex)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
|
+
[](https://discord.gg/qRJw62Gvh7)
|
|
12
13
|
|
|
13
14
|
> **[Website](https://yeachan-heo.github.io/oh-my-codex-website/)** | **[Documentation](https://yeachan-heo.github.io/oh-my-codex-website/docs.html)** | **[CLI Reference](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#cli-reference)** | **[Workflows](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#workflows)** | **[Hướng dẫn tích hợp OpenClaw](./docs/openclaw-integration.vi.md)** | **[GitHub](https://github.com/Yeachan-Heo/oh-my-codex)** | **[npm](https://www.npmjs.com/package/oh-my-codex)**
|
|
14
15
|
|
package/README.zh-TW.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/oh-my-codex)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
|
+
[](https://discord.gg/qRJw62Gvh7)
|
|
12
13
|
|
|
13
14
|
> **[官方網站](https://yeachan-heo.github.io/oh-my-codex-website/)** | **[說明文件](https://yeachan-heo.github.io/oh-my-codex-website/docs.html)** | **[CLI 參考手冊](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#cli-reference)** | **[工作流程](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#workflows)** | **[OpenClaw 整合指南](./docs/openclaw-integration.zh-TW.md)** | **[GitHub](https://github.com/Yeachan-Heo/oh-my-codex)** | **[npm](https://www.npmjs.com/package/oh-my-codex)**
|
|
14
15
|
|
package/README.zh.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/oh-my-codex)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
|
+
[](https://discord.gg/qRJw62Gvh7)
|
|
12
13
|
|
|
13
14
|
> **[Website](https://yeachan-heo.github.io/oh-my-codex-website/)** | **[Documentation](https://yeachan-heo.github.io/oh-my-codex-website/docs.html)** | **[CLI Reference](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#cli-reference)** | **[Workflows](https://yeachan-heo.github.io/oh-my-codex-website/docs.html#workflows)** | **[OpenClaw 集成指南](./docs/openclaw-integration.zh.md)** | **[GitHub](https://github.com/Yeachan-Heo/oh-my-codex)** | **[npm](https://www.npmjs.com/package/oh-my-codex)**
|
|
14
15
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.test.d.ts","sourceRoot":"","sources":["../../../src/autoresearch/__tests__/contracts.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { loadAutoresearchMissionContract, parseEvaluatorResult, parseSandboxContract, slugifyMissionName, } from '../contracts.js';
|
|
8
|
+
async function initRepo() {
|
|
9
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-autoresearch-contracts-'));
|
|
10
|
+
execFileSync('git', ['init'], { cwd, stdio: 'ignore' });
|
|
11
|
+
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd, stdio: 'ignore' });
|
|
12
|
+
execFileSync('git', ['config', 'user.name', 'Test User'], { cwd, stdio: 'ignore' });
|
|
13
|
+
await writeFile(join(cwd, 'README.md'), 'hello\n', 'utf-8');
|
|
14
|
+
execFileSync('git', ['add', 'README.md'], { cwd, stdio: 'ignore' });
|
|
15
|
+
execFileSync('git', ['commit', '-m', 'init'], { cwd, stdio: 'ignore' });
|
|
16
|
+
return cwd;
|
|
17
|
+
}
|
|
18
|
+
describe('autoresearch contracts', () => {
|
|
19
|
+
it('slugifies mission names deterministically', () => {
|
|
20
|
+
assert.equal(slugifyMissionName('Missions/My Demo Mission'), 'missions-my-demo-mission');
|
|
21
|
+
});
|
|
22
|
+
it('parses sandbox contract with evaluator command and json format', () => {
|
|
23
|
+
const parsed = parseSandboxContract(`---\nevaluator:\n command: node scripts/eval.js\n format: json\n---\nStay in bounds.\n`);
|
|
24
|
+
assert.equal(parsed.evaluator.command, 'node scripts/eval.js');
|
|
25
|
+
assert.equal(parsed.evaluator.format, 'json');
|
|
26
|
+
assert.equal(parsed.body, 'Stay in bounds.');
|
|
27
|
+
});
|
|
28
|
+
it('rejects sandbox contract without frontmatter', () => {
|
|
29
|
+
assert.throws(() => parseSandboxContract('No frontmatter here'), /sandbox\.md must start with YAML frontmatter/i);
|
|
30
|
+
});
|
|
31
|
+
it('rejects sandbox contract without evaluator command', () => {
|
|
32
|
+
assert.throws(() => parseSandboxContract(`---\nevaluator:\n format: json\n---\nPolicy\n`), /evaluator\.command is required/i);
|
|
33
|
+
});
|
|
34
|
+
it('rejects sandbox contract without evaluator format', () => {
|
|
35
|
+
assert.throws(() => parseSandboxContract(`---\nevaluator:\n command: node eval.js\n---\nPolicy\n`), /evaluator\.format is required/i);
|
|
36
|
+
});
|
|
37
|
+
it('rejects sandbox contract with non-json evaluator format', () => {
|
|
38
|
+
assert.throws(() => parseSandboxContract(`---\nevaluator:\n command: node eval.js\n format: text\n---\nPolicy\n`), /evaluator\.format must be json/i);
|
|
39
|
+
});
|
|
40
|
+
it('parses optional evaluator keep_policy', () => {
|
|
41
|
+
const parsed = parseSandboxContract(`---
|
|
42
|
+
evaluator:
|
|
43
|
+
command: node scripts/eval.js
|
|
44
|
+
format: json
|
|
45
|
+
keep_policy: pass_only
|
|
46
|
+
---
|
|
47
|
+
Stay in bounds.
|
|
48
|
+
`);
|
|
49
|
+
assert.equal(parsed.evaluator.keep_policy, 'pass_only');
|
|
50
|
+
});
|
|
51
|
+
it('rejects unsupported evaluator keep_policy', () => {
|
|
52
|
+
assert.throws(() => parseSandboxContract(`---
|
|
53
|
+
evaluator:
|
|
54
|
+
command: node scripts/eval.js
|
|
55
|
+
format: json
|
|
56
|
+
keep_policy: maybe
|
|
57
|
+
---
|
|
58
|
+
Stay in bounds.
|
|
59
|
+
`), /keep_policy must be one of/i);
|
|
60
|
+
});
|
|
61
|
+
it('accepts evaluator result with pass only', () => {
|
|
62
|
+
assert.deepEqual(parseEvaluatorResult('{"pass":true}'), { pass: true });
|
|
63
|
+
});
|
|
64
|
+
it('accepts evaluator result with pass and score', () => {
|
|
65
|
+
assert.deepEqual(parseEvaluatorResult('{"pass":false,"score":61}'), { pass: false, score: 61 });
|
|
66
|
+
});
|
|
67
|
+
it('rejects evaluator result without pass', () => {
|
|
68
|
+
assert.throws(() => parseEvaluatorResult('{"score":61}'), /must include boolean pass/i);
|
|
69
|
+
});
|
|
70
|
+
it('rejects evaluator result with non-numeric score', () => {
|
|
71
|
+
assert.throws(() => parseEvaluatorResult('{"pass":true,"score":"high"}'), /score must be numeric/i);
|
|
72
|
+
});
|
|
73
|
+
it('loads mission contract from in-repo mission directory', async () => {
|
|
74
|
+
const repo = await initRepo();
|
|
75
|
+
try {
|
|
76
|
+
const missionDir = join(repo, 'missions', 'demo');
|
|
77
|
+
await mkdir(missionDir, { recursive: true });
|
|
78
|
+
await writeFile(join(missionDir, 'mission.md'), '# Mission\nShip it\n', 'utf-8');
|
|
79
|
+
await writeFile(join(missionDir, 'sandbox.md'), `---\nevaluator:\n command: node scripts/eval.js\n format: json\n---\nStay in bounds.\n`, 'utf-8');
|
|
80
|
+
const contract = await loadAutoresearchMissionContract(missionDir);
|
|
81
|
+
assert.equal(contract.repoRoot, repo);
|
|
82
|
+
assert.equal(contract.missionRelativeDir.replace(/\\/g, '/'), 'missions/demo');
|
|
83
|
+
assert.equal(contract.missionSlug, 'missions-demo');
|
|
84
|
+
assert.equal(contract.sandbox.evaluator.command, 'node scripts/eval.js');
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await rm(repo, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=contracts.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.test.js","sourceRoot":"","sources":["../../../src/autoresearch/__tests__/contracts.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EACL,+BAA+B,EAC/B,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAEzB,KAAK,UAAU,QAAQ;IACrB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,6BAA6B,CAAC,CAAC,CAAC;IACzE,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACxD,YAAY,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,kBAAkB,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC5F,YAAY,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACpF,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAC5D,YAAY,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACpE,YAAY,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACxE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,0BAA0B,CAAC,EAAE,0BAA0B,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,MAAM,GAAG,oBAAoB,CAAC,0FAA0F,CAAC,CAAC;QAChI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,sBAAsB,CAAC,CAAC;QAC/D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,EACjD,+CAA+C,CAChD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,oBAAoB,CAAC,gDAAgD,CAAC,EAC5E,iCAAiC,CAClC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,oBAAoB,CAAC,yDAAyD,CAAC,EACrF,gCAAgC,CACjC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,oBAAoB,CAAC,yEAAyE,CAAC,EACrG,iCAAiC,CAClC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,MAAM,GAAG,oBAAoB,CAAC;;;;;;;CAOvC,CAAC,CAAC;QACC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,oBAAoB,CAAC;;;;;;;CAOhC,CAAC,EACI,6BAA6B,CAC9B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,2BAA2B,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,oBAAoB,CAAC,cAAc,CAAC,EAC1C,4BAA4B,CAC7B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,oBAAoB,CAAC,8BAA8B,CAAC,EAC1D,wBAAwB,CACzB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,IAAI,GAAG,MAAM,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;YAClD,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7C,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,sBAAsB,EAAE,OAAO,CAAC,CAAC;YACjF,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAC9B,0FAA0F,EAC1F,OAAO,CACR,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,+BAA+B,CAAC,UAAU,CAAC,CAAC;YACnE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,eAAe,CAAC,CAAC;YAC/E,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;YACpD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,sBAAsB,CAAC,CAAC;QAC3E,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-parity-extra.test.d.ts","sourceRoot":"","sources":["../../../src/autoresearch/__tests__/runtime-parity-extra.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { assertResetSafeWorktree, decideAutoresearchOutcome, loadAutoresearchRunManifest, materializeAutoresearchMissionToWorktree, prepareAutoresearchRuntime, processAutoresearchCandidate, resumeAutoresearchRuntime, } from '../runtime.js';
|
|
9
|
+
async function initRepo() {
|
|
10
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-autoresearch-parity-extra-'));
|
|
11
|
+
execFileSync('git', ['init'], { cwd, stdio: 'ignore' });
|
|
12
|
+
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd, stdio: 'ignore' });
|
|
13
|
+
execFileSync('git', ['config', 'user.name', 'Test User'], { cwd, stdio: 'ignore' });
|
|
14
|
+
await writeFile(join(cwd, 'README.md'), 'hello\n', 'utf-8');
|
|
15
|
+
execFileSync('git', ['add', 'README.md'], { cwd, stdio: 'ignore' });
|
|
16
|
+
execFileSync('git', ['commit', '-m', 'init'], { cwd, stdio: 'ignore' });
|
|
17
|
+
return cwd;
|
|
18
|
+
}
|
|
19
|
+
async function makeContract(repo, keepPolicy) {
|
|
20
|
+
const missionDir = join(repo, 'missions', 'demo');
|
|
21
|
+
await mkdir(missionDir, { recursive: true });
|
|
22
|
+
await mkdir(join(repo, 'scripts'), { recursive: true });
|
|
23
|
+
const missionFile = join(missionDir, 'mission.md');
|
|
24
|
+
const sandboxFile = join(missionDir, 'sandbox.md');
|
|
25
|
+
const missionContent = '# Mission\nSolve the task.\n';
|
|
26
|
+
const keepPolicyLine = keepPolicy ? ` keep_policy: ${keepPolicy}\n` : '';
|
|
27
|
+
const sandboxContent = `---\nevaluator:\n command: node scripts/eval.js\n format: json\n${keepPolicyLine}---\nStay inside the mission boundary.\n`;
|
|
28
|
+
await writeFile(missionFile, missionContent, 'utf-8');
|
|
29
|
+
await writeFile(sandboxFile, sandboxContent, 'utf-8');
|
|
30
|
+
await writeFile(join(repo, 'score.txt'), '1\n', 'utf-8');
|
|
31
|
+
await writeFile(join(repo, 'scripts', 'eval.js'), "process.stdout.write(JSON.stringify({ pass: true, score: 1 }));\n", 'utf-8');
|
|
32
|
+
execFileSync('git', ['add', 'missions/demo/mission.md', 'missions/demo/sandbox.md', 'scripts/eval.js', 'score.txt'], { cwd: repo, stdio: 'ignore' });
|
|
33
|
+
execFileSync('git', ['commit', '-m', 'add autoresearch fixtures'], { cwd: repo, stdio: 'ignore' });
|
|
34
|
+
return {
|
|
35
|
+
missionDir,
|
|
36
|
+
repoRoot: repo,
|
|
37
|
+
missionFile,
|
|
38
|
+
sandboxFile,
|
|
39
|
+
missionRelativeDir: 'missions/demo',
|
|
40
|
+
missionContent,
|
|
41
|
+
sandboxContent,
|
|
42
|
+
sandbox: {
|
|
43
|
+
frontmatter: { evaluator: { command: 'node scripts/eval.js', format: 'json', ...(keepPolicy ? { keep_policy: keepPolicy } : {}) } },
|
|
44
|
+
evaluator: { command: 'node scripts/eval.js', format: 'json', ...(keepPolicy ? { keep_policy: keepPolicy } : {}) },
|
|
45
|
+
body: 'Stay inside the mission boundary.',
|
|
46
|
+
},
|
|
47
|
+
missionSlug: 'missions-demo',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
describe('autoresearch runtime parity extras', () => {
|
|
51
|
+
it('treats allowed runtime files as reset-safe and blocks unrelated dirt', async () => {
|
|
52
|
+
const repo = await initRepo();
|
|
53
|
+
try {
|
|
54
|
+
const contract = await makeContract(repo);
|
|
55
|
+
const worktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t020000z');
|
|
56
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t020000z', worktreePath, 'HEAD'], {
|
|
57
|
+
cwd: repo,
|
|
58
|
+
stdio: 'ignore',
|
|
59
|
+
});
|
|
60
|
+
const worktreeContract = await materializeAutoresearchMissionToWorktree(contract, worktreePath);
|
|
61
|
+
const runtime = await prepareAutoresearchRuntime(worktreeContract, repo, worktreePath, { runTag: '20260314T020000Z' });
|
|
62
|
+
await writeFile(join(worktreePath, 'results.tsv'), 'iteration\tcommit\tpass\tscore\tstatus\tdescription\n', 'utf-8');
|
|
63
|
+
await writeFile(join(worktreePath, 'run.log'), 'ok\n', 'utf-8');
|
|
64
|
+
assert.doesNotThrow(() => assertResetSafeWorktree(worktreePath));
|
|
65
|
+
await writeFile(join(worktreePath, 'scratch.tmp'), 'nope\n', 'utf-8');
|
|
66
|
+
assert.throws(() => assertResetSafeWorktree(worktreePath), /autoresearch_reset_requires_clean_worktree/i);
|
|
67
|
+
const manifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
68
|
+
assert.equal(manifest.results_file, join(worktreePath, 'results.tsv'));
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await rm(repo, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
it('rejects concurrent fresh runs via the repo-root active-run lock', async () => {
|
|
75
|
+
const repo = await initRepo();
|
|
76
|
+
try {
|
|
77
|
+
const contract = await makeContract(repo);
|
|
78
|
+
const worktreePathA = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t030000z');
|
|
79
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t030000z', worktreePathA, 'HEAD'], {
|
|
80
|
+
cwd: repo,
|
|
81
|
+
stdio: 'ignore',
|
|
82
|
+
});
|
|
83
|
+
const worktreeContractA = await materializeAutoresearchMissionToWorktree(contract, worktreePathA);
|
|
84
|
+
const runtimeA = await prepareAutoresearchRuntime(worktreeContractA, repo, worktreePathA, { runTag: '20260314T030000Z' });
|
|
85
|
+
const worktreePathB = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t030500z');
|
|
86
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t030500z', worktreePathB, 'HEAD'], {
|
|
87
|
+
cwd: repo,
|
|
88
|
+
stdio: 'ignore',
|
|
89
|
+
});
|
|
90
|
+
const worktreeContractB = await materializeAutoresearchMissionToWorktree(contract, worktreePathB);
|
|
91
|
+
await assert.rejects(() => prepareAutoresearchRuntime(worktreeContractB, repo, worktreePathB, { runTag: '20260314T030500Z' }), /autoresearch_active_run_exists/i);
|
|
92
|
+
assert.equal(existsSync(runtimeA.manifestFile), true);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await rm(repo, { recursive: true, force: true });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
it('resumes a running manifest and rejects missing worktrees', async () => {
|
|
99
|
+
const repo = await initRepo();
|
|
100
|
+
try {
|
|
101
|
+
const contract = await makeContract(repo);
|
|
102
|
+
const worktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t040000z');
|
|
103
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t040000z', worktreePath, 'HEAD'], {
|
|
104
|
+
cwd: repo,
|
|
105
|
+
stdio: 'ignore',
|
|
106
|
+
});
|
|
107
|
+
const worktreeContract = await materializeAutoresearchMissionToWorktree(contract, worktreePath);
|
|
108
|
+
const runtime = await prepareAutoresearchRuntime(worktreeContract, repo, worktreePath, { runTag: '20260314T040000Z' });
|
|
109
|
+
const statePath = join(repo, '.omx', 'state', 'autoresearch-state.json');
|
|
110
|
+
const idleState = {
|
|
111
|
+
schema_version: 1,
|
|
112
|
+
active: false,
|
|
113
|
+
run_id: runtime.runId,
|
|
114
|
+
mission_slug: contract.missionSlug,
|
|
115
|
+
repo_root: repo,
|
|
116
|
+
worktree_path: worktreePath,
|
|
117
|
+
status: 'idle',
|
|
118
|
+
updated_at: '2026-03-14T04:05:00.000Z',
|
|
119
|
+
};
|
|
120
|
+
await writeFile(statePath, `${JSON.stringify(idleState, null, 2)}\n`, 'utf-8');
|
|
121
|
+
const resumed = await resumeAutoresearchRuntime(repo, runtime.runId);
|
|
122
|
+
assert.equal(resumed.runId, runtime.runId);
|
|
123
|
+
assert.equal(resumed.worktreePath, worktreePath);
|
|
124
|
+
await writeFile(statePath, `${JSON.stringify(idleState, null, 2)}\n`, 'utf-8');
|
|
125
|
+
await rm(worktreePath, { recursive: true, force: true });
|
|
126
|
+
await assert.rejects(() => resumeAutoresearchRuntime(repo, runtime.runId), /autoresearch_resume_missing_worktree/i);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
await rm(repo, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
it('decides ambiguous vs keep based on keep_policy semantics', () => {
|
|
133
|
+
const candidate = {
|
|
134
|
+
status: 'candidate',
|
|
135
|
+
candidate_commit: 'abc1234',
|
|
136
|
+
base_commit: 'base1234',
|
|
137
|
+
description: 'candidate',
|
|
138
|
+
notes: [],
|
|
139
|
+
created_at: '2026-03-14T05:00:00.000Z',
|
|
140
|
+
};
|
|
141
|
+
const ambiguous = decideAutoresearchOutcome({ keep_policy: 'score_improvement', last_kept_score: null }, candidate, { command: 'node eval.js', ran_at: '2026-03-14T05:00:01.000Z', status: 'pass', pass: true, exit_code: 0 });
|
|
142
|
+
assert.equal(ambiguous.decision, 'ambiguous');
|
|
143
|
+
assert.equal(ambiguous.keep, false);
|
|
144
|
+
const kept = decideAutoresearchOutcome({ keep_policy: 'pass_only', last_kept_score: null }, candidate, { command: 'node eval.js', ran_at: '2026-03-14T05:00:01.000Z', status: 'pass', pass: true, exit_code: 0 });
|
|
145
|
+
assert.equal(kept.decision, 'keep');
|
|
146
|
+
assert.equal(kept.keep, true);
|
|
147
|
+
});
|
|
148
|
+
it('resume rejects terminal manifests', async () => {
|
|
149
|
+
const repo = await initRepo();
|
|
150
|
+
try {
|
|
151
|
+
const contract = await makeContract(repo);
|
|
152
|
+
const worktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t050000z');
|
|
153
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t050000z', worktreePath, 'HEAD'], {
|
|
154
|
+
cwd: repo,
|
|
155
|
+
stdio: 'ignore',
|
|
156
|
+
});
|
|
157
|
+
const worktreeContract = await materializeAutoresearchMissionToWorktree(contract, worktreePath);
|
|
158
|
+
const runtime = await prepareAutoresearchRuntime(worktreeContract, repo, worktreePath, { runTag: '20260314T050000Z' });
|
|
159
|
+
const manifest = JSON.parse(await readFile(runtime.manifestFile, 'utf-8'));
|
|
160
|
+
manifest.status = 'completed';
|
|
161
|
+
await writeFile(runtime.manifestFile, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
162
|
+
await writeFile(join(repo, '.omx', 'state', 'autoresearch-state.json'), `${JSON.stringify({
|
|
163
|
+
schema_version: 1,
|
|
164
|
+
active: false,
|
|
165
|
+
run_id: runtime.runId,
|
|
166
|
+
mission_slug: contract.missionSlug,
|
|
167
|
+
repo_root: repo,
|
|
168
|
+
worktree_path: worktreePath,
|
|
169
|
+
status: 'completed',
|
|
170
|
+
updated_at: '2026-03-14T05:05:00.000Z',
|
|
171
|
+
}, null, 2)}\n`, 'utf-8');
|
|
172
|
+
await assert.rejects(() => resumeAutoresearchRuntime(repo, runtime.runId), /autoresearch_resume_terminal_run/i);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
await rm(repo, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
it('records noop and abort candidate branches explicitly', async () => {
|
|
179
|
+
const repo = await initRepo();
|
|
180
|
+
try {
|
|
181
|
+
const contract = await makeContract(repo);
|
|
182
|
+
const worktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t060000z');
|
|
183
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t060000z', worktreePath, 'HEAD'], {
|
|
184
|
+
cwd: repo,
|
|
185
|
+
stdio: 'ignore',
|
|
186
|
+
});
|
|
187
|
+
const worktreeContract = await materializeAutoresearchMissionToWorktree(contract, worktreePath);
|
|
188
|
+
const runtime = await prepareAutoresearchRuntime(worktreeContract, repo, worktreePath, { runTag: '20260314T060000Z' });
|
|
189
|
+
let manifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
190
|
+
await writeFile(runtime.candidateFile, `${JSON.stringify({
|
|
191
|
+
status: 'noop',
|
|
192
|
+
candidate_commit: null,
|
|
193
|
+
base_commit: manifest.last_kept_commit,
|
|
194
|
+
description: 'no useful change',
|
|
195
|
+
notes: ['noop branch'],
|
|
196
|
+
created_at: '2026-03-14T06:01:00.000Z',
|
|
197
|
+
}, null, 2)}\n`, 'utf-8');
|
|
198
|
+
assert.equal(await processAutoresearchCandidate(worktreeContract, manifest, repo), 'noop');
|
|
199
|
+
manifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
200
|
+
await writeFile(runtime.candidateFile, `${JSON.stringify({
|
|
201
|
+
status: 'abort',
|
|
202
|
+
candidate_commit: null,
|
|
203
|
+
base_commit: manifest.last_kept_commit,
|
|
204
|
+
description: 'operator stop',
|
|
205
|
+
notes: ['abort branch'],
|
|
206
|
+
created_at: '2026-03-14T06:02:00.000Z',
|
|
207
|
+
}, null, 2)}\n`, 'utf-8');
|
|
208
|
+
assert.equal(await processAutoresearchCandidate(worktreeContract, manifest, repo), 'abort');
|
|
209
|
+
const results = await readFile(runtime.resultsFile, 'utf-8');
|
|
210
|
+
assert.match(results, /^1\t.+\t\t\tnoop\tno useful change$/m);
|
|
211
|
+
assert.match(results, /^2\t.+\t\t\tabort\toperator stop$/m);
|
|
212
|
+
const finalManifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
213
|
+
assert.equal(finalManifest.status, 'stopped');
|
|
214
|
+
assert.equal(finalManifest.stop_reason, 'candidate abort');
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
await rm(repo, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
it('rejects candidate integrity mismatches and missing candidate artifacts with actionable failure state', async () => {
|
|
221
|
+
const repo = await initRepo();
|
|
222
|
+
try {
|
|
223
|
+
const contract = await makeContract(repo);
|
|
224
|
+
const worktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t070000z');
|
|
225
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t070000z', worktreePath, 'HEAD'], {
|
|
226
|
+
cwd: repo,
|
|
227
|
+
stdio: 'ignore',
|
|
228
|
+
});
|
|
229
|
+
const worktreeContract = await materializeAutoresearchMissionToWorktree(contract, worktreePath);
|
|
230
|
+
const runtime = await prepareAutoresearchRuntime(worktreeContract, repo, worktreePath, { runTag: '20260314T070000Z' });
|
|
231
|
+
let manifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
232
|
+
await writeFile(runtime.candidateFile, `${JSON.stringify({
|
|
233
|
+
status: 'candidate',
|
|
234
|
+
candidate_commit: null,
|
|
235
|
+
base_commit: manifest.last_kept_commit,
|
|
236
|
+
description: 'invalid candidate',
|
|
237
|
+
notes: ['missing commit'],
|
|
238
|
+
created_at: '2026-03-14T07:01:00.000Z',
|
|
239
|
+
}, null, 2)}\n`, 'utf-8');
|
|
240
|
+
assert.equal(await processAutoresearchCandidate(worktreeContract, manifest, repo), 'error');
|
|
241
|
+
let failedManifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
242
|
+
assert.equal(failedManifest.status, 'failed');
|
|
243
|
+
assert.match(failedManifest.stop_reason || '', /non-null candidate_commit/i);
|
|
244
|
+
const failureResults = await readFile(runtime.resultsFile, 'utf-8');
|
|
245
|
+
assert.match(failureResults, /^1\t.+\t\t\terror\tinvalid candidate$/m);
|
|
246
|
+
const secondWorktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t071000z');
|
|
247
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t071000z', secondWorktreePath, 'HEAD'], {
|
|
248
|
+
cwd: repo,
|
|
249
|
+
stdio: 'ignore',
|
|
250
|
+
});
|
|
251
|
+
const secondContract = await materializeAutoresearchMissionToWorktree(contract, secondWorktreePath);
|
|
252
|
+
const secondRuntime = await prepareAutoresearchRuntime(secondContract, repo, secondWorktreePath, { runTag: '20260314T071000Z' });
|
|
253
|
+
manifest = await loadAutoresearchRunManifest(repo, secondRuntime.runId);
|
|
254
|
+
await writeFile(secondRuntime.candidateFile, `${JSON.stringify({
|
|
255
|
+
status: 'candidate',
|
|
256
|
+
candidate_commit: manifest.last_kept_commit,
|
|
257
|
+
base_commit: 'deadbeef',
|
|
258
|
+
description: 'mismatched base',
|
|
259
|
+
notes: ['bad base'],
|
|
260
|
+
created_at: '2026-03-14T07:02:00.000Z',
|
|
261
|
+
}, null, 2)}\n`, 'utf-8');
|
|
262
|
+
assert.equal(await processAutoresearchCandidate(secondContract, manifest, repo), 'error');
|
|
263
|
+
failedManifest = await loadAutoresearchRunManifest(repo, secondRuntime.runId);
|
|
264
|
+
assert.equal(failedManifest.status, 'failed');
|
|
265
|
+
assert.match(failedManifest.stop_reason || '', /base_commit/i);
|
|
266
|
+
const thirdWorktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t072000z');
|
|
267
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t072000z', thirdWorktreePath, 'HEAD'], {
|
|
268
|
+
cwd: repo,
|
|
269
|
+
stdio: 'ignore',
|
|
270
|
+
});
|
|
271
|
+
const thirdContract = await materializeAutoresearchMissionToWorktree(contract, thirdWorktreePath);
|
|
272
|
+
const thirdRuntime = await prepareAutoresearchRuntime(thirdContract, repo, thirdWorktreePath, { runTag: '20260314T072000Z' });
|
|
273
|
+
manifest = await loadAutoresearchRunManifest(repo, thirdRuntime.runId);
|
|
274
|
+
await rm(thirdRuntime.candidateFile, { force: true });
|
|
275
|
+
assert.equal(await processAutoresearchCandidate(thirdContract, manifest, repo), 'error');
|
|
276
|
+
failedManifest = await loadAutoresearchRunManifest(repo, thirdRuntime.runId);
|
|
277
|
+
assert.equal(failedManifest.status, 'failed');
|
|
278
|
+
assert.match(failedManifest.stop_reason || '', /autoresearch_candidate_missing/i);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
await rm(repo, { recursive: true, force: true });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
it('handles interrupted, evaluator failure, and evaluator parse-error branches', async () => {
|
|
285
|
+
const repo = await initRepo();
|
|
286
|
+
try {
|
|
287
|
+
const contract = await makeContract(repo);
|
|
288
|
+
const worktreePath = join(repo, '..', `${repo.split('/').pop()}.omx-worktrees`, 'autoresearch-missions-demo-20260314t080000z');
|
|
289
|
+
execFileSync('git', ['worktree', 'add', '-b', 'autoresearch/missions-demo/20260314t080000z', worktreePath, 'HEAD'], {
|
|
290
|
+
cwd: repo,
|
|
291
|
+
stdio: 'ignore',
|
|
292
|
+
});
|
|
293
|
+
const worktreeContract = await materializeAutoresearchMissionToWorktree(contract, worktreePath);
|
|
294
|
+
const runtime = await prepareAutoresearchRuntime(worktreeContract, repo, worktreePath, { runTag: '20260314T080000Z' });
|
|
295
|
+
let manifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
296
|
+
await writeFile(runtime.candidateFile, `${JSON.stringify({
|
|
297
|
+
status: 'interrupted',
|
|
298
|
+
candidate_commit: null,
|
|
299
|
+
base_commit: manifest.last_kept_commit,
|
|
300
|
+
description: 'clean interrupt',
|
|
301
|
+
notes: ['ctrl-c'],
|
|
302
|
+
created_at: '2026-03-14T08:01:00.000Z',
|
|
303
|
+
}, null, 2)}\n`, 'utf-8');
|
|
304
|
+
assert.equal(await processAutoresearchCandidate(worktreeContract, manifest, repo), 'interrupted');
|
|
305
|
+
manifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
306
|
+
await writeFile(join(worktreePath, 'scripts', 'eval.js'), "process.stdout.write(JSON.stringify({ pass: false, score: 0 }));\n", 'utf-8');
|
|
307
|
+
await writeFile(join(worktreePath, 'score.txt'), '0\n', 'utf-8');
|
|
308
|
+
execFileSync('git', ['add', 'scripts/eval.js', 'score.txt'], { cwd: worktreePath, stdio: 'ignore' });
|
|
309
|
+
execFileSync('git', ['commit', '-m', 'make evaluator fail'], { cwd: worktreePath, stdio: 'ignore' });
|
|
310
|
+
const failingCommit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: worktreePath, encoding: 'utf-8' }).trim();
|
|
311
|
+
await writeFile(runtime.candidateFile, `${JSON.stringify({
|
|
312
|
+
status: 'candidate',
|
|
313
|
+
candidate_commit: failingCommit,
|
|
314
|
+
base_commit: manifest.last_kept_commit,
|
|
315
|
+
description: 'failing evaluator branch',
|
|
316
|
+
notes: ['pass false'],
|
|
317
|
+
created_at: '2026-03-14T08:02:00.000Z',
|
|
318
|
+
}, null, 2)}\n`, 'utf-8');
|
|
319
|
+
assert.equal(await processAutoresearchCandidate(worktreeContract, manifest, repo), 'discard');
|
|
320
|
+
manifest = await loadAutoresearchRunManifest(repo, runtime.runId);
|
|
321
|
+
await writeFile(join(worktreePath, 'scripts', 'eval.js'), "process.stdout.write('not json');\n", 'utf-8');
|
|
322
|
+
execFileSync('git', ['add', 'scripts/eval.js'], { cwd: worktreePath, stdio: 'ignore' });
|
|
323
|
+
execFileSync('git', ['commit', '-m', 'break evaluator json'], { cwd: worktreePath, stdio: 'ignore' });
|
|
324
|
+
const parseErrorCommit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: worktreePath, encoding: 'utf-8' }).trim();
|
|
325
|
+
await writeFile(runtime.candidateFile, `${JSON.stringify({
|
|
326
|
+
status: 'candidate',
|
|
327
|
+
candidate_commit: parseErrorCommit,
|
|
328
|
+
base_commit: manifest.last_kept_commit,
|
|
329
|
+
description: 'parse error branch',
|
|
330
|
+
notes: ['invalid json'],
|
|
331
|
+
created_at: '2026-03-14T08:03:00.000Z',
|
|
332
|
+
}, null, 2)}\n`, 'utf-8');
|
|
333
|
+
assert.equal(await processAutoresearchCandidate(worktreeContract, manifest, repo), 'discard');
|
|
334
|
+
const results = await readFile(runtime.resultsFile, 'utf-8');
|
|
335
|
+
assert.match(results, /^1\t.+\t\t\tinterrupted\tclean interrupt$/m);
|
|
336
|
+
assert.match(results, /^2\t.+\tfalse\t0\tdiscard\tfailing evaluator branch$/m);
|
|
337
|
+
assert.match(results, /^3\t.+\t\t\tdiscard\tparse error branch$/m);
|
|
338
|
+
const ledger = JSON.parse(await readFile(runtime.ledgerFile, 'utf-8'));
|
|
339
|
+
assert.equal(ledger.entries[1]?.decision, 'interrupted');
|
|
340
|
+
assert.equal(ledger.entries[2]?.decision, 'discard');
|
|
341
|
+
assert.equal(ledger.entries[3]?.decision, 'discard');
|
|
342
|
+
assert.match(ledger.entries[3]?.decision_reason || '', /evaluator error/i);
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
await rm(repo, { recursive: true, force: true });
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
//# sourceMappingURL=runtime-parity-extra.test.js.map
|