@lyupro/codex-bridge 0.6.12 → 0.6.15

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +10 -8
  3. package/cli/command-registry.mjs +59 -18
  4. package/cli/doctor-installation.mjs +132 -111
  5. package/cli/doctor-load-witness.mjs +75 -0
  6. package/cli/doctor-runs.mjs +41 -6
  7. package/cli/doctor-sandbox.mjs +64 -0
  8. package/cli/doctor.mjs +26 -8
  9. package/cli/guidance.mjs +42 -0
  10. package/cli/host-inspection.mjs +1 -0
  11. package/cli/hosts.mjs +4 -3
  12. package/cli/install-record.mjs +4 -2
  13. package/cli/manifest.mjs +6 -3
  14. package/cli/read.mjs +1 -2
  15. package/cli/removal-outcomes.mjs +5 -2
  16. package/cli/removal-plan.mjs +4 -1
  17. package/cli/remove-layout.mjs +4 -4
  18. package/cli/run-lookup.mjs +14 -2
  19. package/cli/runs-inventory.mjs +10 -2
  20. package/cli/runs-move-copy.mjs +123 -0
  21. package/cli/runs-move-history.mjs +78 -0
  22. package/cli/runs-move-remove.mjs +92 -0
  23. package/cli/runs-move.mjs +96 -0
  24. package/cli/stop.mjs +1 -2
  25. package/cli/uninstall-purge.mjs +1 -0
  26. package/cli/uninstall.mjs +2 -0
  27. package/cli/update.mjs +12 -7
  28. package/package.json +4 -2
  29. package/scripts/home-write-inventory.mjs +17 -2
  30. package/scripts/isolated-roots.mjs +2 -1
  31. package/src/{agents → claude/agents}/codex-advisor.md +8 -7
  32. package/src/{agents → claude/agents}/codex-build.md +7 -6
  33. package/src/{agents → claude/agents}/codex-review.md +6 -5
  34. package/src/{agents → claude/agents}/codex-scout.md +7 -6
  35. package/src/{commands → claude/commands}/usage.md +9 -5
  36. package/src/claude/rules/core.md +29 -0
  37. package/src/{rules → codex/rules}/codex-bridge.rules +1 -1
  38. package/src/home/guidance/advisor.md +42 -0
  39. package/src/home/guidance/budget.md +37 -0
  40. package/src/home/guidance/concurrency.md +17 -0
  41. package/src/home/guidance/order.md +68 -0
  42. package/src/home/guidance/records.md +32 -0
  43. package/src/home/hooks/live-runs.mjs +2 -1
  44. package/src/home/hooks/load-witness.mjs +23 -0
  45. package/src/home/lib/claude-layout.mjs +1 -0
  46. package/src/home/lib/codex-home.mjs +8 -0
  47. package/src/home/lib/home-registry.mjs +21 -0
  48. package/src/home/lib/hook-definitions.mjs +2 -0
  49. package/src/home/lib/host-version.mjs +33 -14
  50. package/src/home/lib/load-witness.mjs +113 -0
  51. package/src/home/lib/meta/paths.mjs +6 -1
  52. package/src/home/lib/meta/reply.mjs +1 -1
  53. package/src/home/lib/runner/codex-availability.mjs +11 -2
  54. package/src/home/lib/runner/exit-codes.mjs +1 -1
  55. package/src/home/lib/runner/launcher.mjs +10 -4
  56. package/src/home/lib/runner/preflight.mjs +12 -4
  57. package/src/home/lib/runner/prompts.mjs +2 -2
  58. package/src/home/lib/runner/retired-roots.mjs +81 -0
  59. package/src/home/lib/runner/run-autocommit.mjs +109 -0
  60. package/src/home/lib/runner/runs-root.mjs +47 -7
  61. package/src/home/lib/runner/sandbox-diagnosis.mjs +180 -0
  62. package/src/home/lib/runner/sandbox-probe.mjs +29 -1
  63. package/src/home/lib/runner/task-input.mjs +30 -0
  64. package/src/home/lib/runner/worker.mjs +5 -0
  65. /package/src/{commands → claude/commands}/env.md +0 -0
  66. /package/src/{commands → claude/commands}/model.md +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: codex-build
3
- description: Имплементация ВНЕ подписки Claude — правки кода, новые модули, рефакторинг по образцу, починка тестов. Работу выполняет Codex CLI (подписка ChatGPT) с правом записи в репозиторий строго в пределах обязательного `scope`, Claude платит только за постановку задачи и ≤5 строк выжимки. Отчёт и полный лог кладёт в ~/.claude/codex-runs/, в чат — короткая выжимка + путь. Ревью и приёмку делает отдельный агент Claude, не этот. {{CODEX_REQUIRED_INPUTS_SUMMARY}} {{CODEX_STOP_SUMMARY}}
3
+ description: Имплементация ВНЕ подписки Claude — правки кода, новые модули, рефакторинг по образцу, починка тестов. Работу выполняет Codex CLI (подписка ChatGPT) с правом записи в репозиторий строго в пределах обязательного `scope`, Claude платит только за постановку задачи и ≤5 строк выжимки. Отчёт и полный лог кладёт в корень прогонов (`codex-bridge doctor` → `runsRoot`; по умолчанию `~/.lyupro/.codex-bridge/runs/`), в чат — короткая выжимка + путь. Ревью и приёмку делает отдельный агент Claude, не этот. {{CODEX_REQUIRED_INPUTS_SUMMARY}} {{CODEX_STOP_SUMMARY}}
4
4
  model: haiku
5
5
  tools: Bash
6
6
  ---
@@ -102,7 +102,7 @@ some files could not be judged.
102
102
  separate
103
103
  line, the runner reports whether the worktree was left touched, because a run interrupted
104
104
  halfway is more dangerous than one that never happened. Do not restart.
105
- - `UNAVAILABLE` — Codex is missing or signed out (checked before start, or recognised from the CLI's own
105
+ - `UNAVAILABLE` — Codex is signed out (checked before start, or recognised from the CLI's own
106
106
  sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
107
107
  not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
108
108
 
@@ -123,10 +123,11 @@ without the orchestrator's `continue:` or `retry:` grant in the task-file header
123
123
 
124
124
  ## Codex is unavailable
125
125
 
126
- The runner looks `codex` up on PATH and asks `codex login status` before starting. A missing binary
127
- or the measured "Not logged in" answer prints a ready-made `UNAVAILABLE` block (first line
128
- `UNAVAILABLE — `, exit 5, no run folder). An unclear probe prints an ordinary refusal starting
129
- `Codex CLI unavailable:` (exit 1). Return that output verbatim.
126
+ The runner looks `codex` up on PATH and retries once after ~1 s. If the lookup still fails, it prints
127
+ an ordinary refusal starting `Codex CLI readiness unconfirmed:` (exit 1, no run folder).
128
+ When found, it asks `codex login status` before starting. The measured "Not logged in" answer prints
129
+ a ready-made `UNAVAILABLE` block (first line `UNAVAILABLE — `, exit 5, no run folder). Any other unclear
130
+ probe prints an ordinary refusal starting `Codex CLI unavailable:` (exit 1). Return that output verbatim.
130
131
  Implementing the task instead of Codex is prohibited for any reason it fails.
131
132
 
132
133
  ## What a violation looks like
@@ -80,7 +80,7 @@ and the wait can be repeated for free, so neither has any reason to restart anyt
80
80
  - `LIMIT` — the result is empty and the log signals a limit. The ChatGPT quota is exhausted, and the
81
81
  review was not
82
82
  completed; this is not a review failure and not a reason to restart.
83
- - `UNAVAILABLE` — Codex is missing or signed out (checked before start, or recognised from the CLI's own
83
+ - `UNAVAILABLE` — Codex is signed out (checked before start, or recognised from the CLI's own
84
84
  sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
85
85
  not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
86
86
 
@@ -98,10 +98,11 @@ Token spending now goes into `meta.json` (the `review` subcommand did not print
98
98
 
99
99
  ## Codex is unavailable
100
100
 
101
- The runner looks `codex` up on PATH and asks `codex login status` before starting. A missing binary
102
- or the measured "Not logged in" answer prints a ready-made `UNAVAILABLE` block (first line
103
- `UNAVAILABLE — `, exit 5, no run folder). An unclear probe prints an ordinary refusal starting
104
- `Codex CLI unavailable:` (exit 1). Return that output verbatim.
101
+ The runner looks `codex` up on PATH and retries once after ~1 s. If the lookup still fails, it prints
102
+ an ordinary refusal starting `Codex CLI readiness unconfirmed:` (exit 1, no run folder).
103
+ When found, it asks `codex login status` before starting. The measured "Not logged in" answer prints
104
+ a ready-made `UNAVAILABLE` block (first line `UNAVAILABLE — `, exit 5, no run folder). Any other unclear
105
+ probe prints an ordinary refusal starting `Codex CLI unavailable:` (exit 1). Return that output verbatim.
105
106
 
106
107
  ## What a violation looks like
107
108
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: codex-scout
3
- description: Разведка и диагностика ВНЕ подписки Claude — исследование кодовой базы, поиск причины бага, ревью, сбор фактов. Работу выполняет Codex CLI (подписка ChatGPT), Claude платит только за постановку задачи и ≤5 строк выжимки. Строго read-only: писать в репозиторий физически не может. Подробный отчёт кладёт в ~/.claude/codex-runs/, в чат — короткая выжимка + путь. {{CODEX_REQUIRED_INPUTS_SUMMARY}} {{CODEX_STOP_SUMMARY}}
3
+ description: Разведка и диагностика ВНЕ подписки Claude — исследование кодовой базы, поиск причины бага, ревью, сбор фактов. Работу выполняет Codex CLI (подписка ChatGPT), Claude платит только за постановку задачи и ≤5 строк выжимки. Строго read-only: писать в репозиторий физически не может. Подробный отчёт кладёт в корень прогонов (`codex-bridge doctor` → `runsRoot`; по умолчанию `~/.lyupro/.codex-bridge/runs/`), в чат — короткая выжимка + путь. {{CODEX_REQUIRED_INPUTS_SUMMARY}} {{CODEX_STOP_SUMMARY}}
4
4
  model: haiku
5
5
  tools: Bash
6
6
  ---
@@ -67,7 +67,7 @@ An empty `answer` is FAIL with a path to the log, not a reason to choose differe
67
67
  - `LIMIT` — the result is empty and the log signals a limit. The ChatGPT quota is exhausted, and the
68
68
  task was not
69
69
  completed; this is not a task failure and not a reason to restart.
70
- - `UNAVAILABLE` — Codex is missing or signed out (checked before start, or recognised from the CLI's own
70
+ - `UNAVAILABLE` — Codex is signed out (checked before start, or recognised from the CLI's own
71
71
  sign-in refusal after start). The task was not attempted or could not run; this is not a task failure and
72
72
  not a reason to restart — return the block as is, the orchestrator hands the task to the next executor.
73
73
 
@@ -85,10 +85,11 @@ which is the runner telling you the answer is on disk already.
85
85
 
86
86
  ## Codex is unavailable
87
87
 
88
- The runner looks `codex` up on PATH and asks `codex login status` before starting. A missing binary
89
- or the measured "Not logged in" answer prints a ready-made `UNAVAILABLE` block (first line
90
- `UNAVAILABLE — `, exit 5, no run folder). An unclear probe prints an ordinary refusal starting
91
- `Codex CLI unavailable:` (exit 1). Return that output verbatim.
88
+ The runner looks `codex` up on PATH and retries once after ~1 s. If the lookup still fails, it prints
89
+ an ordinary refusal starting `Codex CLI readiness unconfirmed:` (exit 1, no run folder).
90
+ When found, it asks `codex login status` before starting. The measured "Not logged in" answer prints
91
+ a ready-made `UNAVAILABLE` block (first line `UNAVAILABLE — `, exit 5, no run folder). Any other unclear
92
+ probe prints an ordinary refusal starting `Codex CLI unavailable:` (exit 1). Return that output verbatim.
92
93
  Performing the task manually instead of Codex is prohibited for any reason it fails.
93
94
 
94
95
  ## What a violation looks like
@@ -11,9 +11,9 @@ allowed-tools: Bash
11
11
  Count how much ChatGPT quota the runs delegated to Codex burned. This is spending on the Codex side,
12
12
  not on the Claude side.
13
13
 
14
- The root of the run folders comes from `CODEX_RUNS_ROOT` when it is set, otherwise from
15
- `~/.claude/codex-runs` — the same order the runner itself uses, because otherwise the accounting
16
- would stop seeing runs whenever the root is overridden.
14
+ The root of the run folders comes from the runner's own resolver, so usage counts exactly where
15
+ runs are written: the `CODEX_RUNS_ROOT` override, `~/.lyupro/.codex-bridge/runs/` after the move,
16
+ or `~/.claude/codex-runs/` while the move is pending.
17
17
 
18
18
  The source is the `meta.json` in each run folder: it holds the spending, the model and the sandbox.
19
19
  Transport files are not kept in git and may be deleted, so parsing them is not allowed — the
@@ -33,11 +33,14 @@ Run exactly this command and recompute nothing by hand:
33
33
 
34
34
  ```bash
35
35
  node -e "
36
- const fs=require('fs'),path=require('path');
37
- const root=(process.env.CODEX_RUNS_ROOT||'').trim()||path.join(process.env.USERPROFILE||process.env.HOME,'.claude','codex-runs');
36
+ const fs=require('fs'),path=require('path'),os=require('os');
37
+ const home=(process.env.CODEX_BRIDGE_HOME||'').trim()||path.join(os.homedir(),'.lyupro','.codex-bridge');
38
+ import(require('url').pathToFileURL(path.join(home,'lib','runner','runs-root.mjs')).href).then(m=>{
39
+ const root=m.runsRoot();
38
40
  if(!fs.existsSync(root)){console.log('No runs yet.');process.exit(0)}
39
41
  const runs=[],unknown=[];
40
42
  for(const proj of fs.readdirSync(root)){
43
+ if(proj==='.git')continue;
41
44
  const pdir=path.join(root,proj);
42
45
  if(!fs.statSync(pdir).isDirectory())continue;
43
46
  let repo=null;
@@ -81,6 +84,7 @@ if(runs.length){
81
84
  console.log('');console.log('Most expensive runs:');
82
85
  runs.sort((a,b)=>b.tokens-a.tokens).slice(0,5).forEach(r=>console.log(' '+fmt(r.tokens).padStart(11)+' '+r.proj+'/'+r.run));
83
86
  }
87
+ }).catch(e=>{console.error(e.message);process.exit(1)});
84
88
  "
85
89
  ```
86
90
 
@@ -0,0 +1,29 @@
1
+ # codex-bridge — delegation core
2
+
3
+ Installed and removed by the `codex-bridge` package; `codex-bridge update` keeps it equal to the package. Change the
4
+ package, not this file.
5
+
6
+ Four dispatcher agents run work on a Codex CLI subscription: `codex-scout` (read-only research), `codex-build`
7
+ (writes code inside its `scope`), `codex-review` (second opinion on a diff) and `codex-advisor` (design advice before
8
+ code: a `scope` phase, then `advise`).
9
+
10
+ - The call is one line, `task file: <absolute path>`. Labels go in the task-file header, free text in its body. A
11
+ refused call costs nothing: read the refusal, fix the task file, call again.
12
+ - Before writing a task file, run `codex-bridge guidance <topic>` and follow it: `order` (header labels, scope),
13
+ `advisor` (two phases, the `continue:` grant), `budget` (run deadline, slicing), `records` (finding and reading
14
+ runs), `concurrency` (parallel and writing runs). `codex-bridge guidance` lists the topics. If it fails, stop and
15
+ show its output to the operator — never guess the contract.
16
+ - Every `codex-build` task file carries exactly one `advice:` line.
17
+ - A writing run has a hard deadline: slice the task before the launch, not after a failure. After a deadline `FAIL`,
18
+ look at `git status` before deciding anything.
19
+ - One writing run per working tree; read-only runs may go alongside it.
20
+ - `LIMIT` means the ChatGPT quota window is spent, `UNAVAILABLE` means Codex is signed out on this host. Neither is a
21
+ task error: do not retry the same bridge.
22
+ A refusal starting `Codex CLI readiness unconfirmed` is not `UNAVAILABLE`: do not skip the bridge; follow its second
23
+ line — check `codex --version` for up to ~2 minutes, then repeat the same order exactly once if it answers, otherwise
24
+ hand it to the next executor and name that in the summary. If the repeat gets the same refusal, hand it to the next
25
+ executor too; no second repeat.
26
+ - `OK — scope: insufficient` from `codex-advisor` is a success: it names the paths the `advise` phase needs.
27
+ - Stop a live run with `codex-bridge stop <run>` before `TaskStop`; `TaskStop` alone leaves the run writing.
28
+ - A run's folder is the `ATTACH=` path in the dispatcher's reply. Earlier runs: `codex-bridge projects [<project>]
29
+ --json`; render one with `codex-bridge read <run>`. Never derive the run root from where the package is installed.
@@ -1,6 +1,6 @@
1
1
  # codex-bridge installs this file to constrain delegated Codex runs.
2
2
  # It exists because a delegated run must not rewrite the operator's repository history or branches.
3
- # Check it with: codex execpolicy check --rules src/rules/codex-bridge.rules -- <command> <args>.
3
+ # Check it with: codex execpolicy check --rules src/codex/rules/codex-bridge.rules -- <command> <args>.
4
4
  # Codex combines this file with other active rule files, and the strictest matching decision wins.
5
5
  # Known limit: prefix rules cannot detect shell redirection into .git/HEAD or equivalent indirect writes.
6
6
 
@@ -0,0 +1,42 @@
1
+ # advisor
2
+
3
+ Use a scope pass and a granted advise pass under one order id to obtain an evidence-backed design recommendation.
4
+
5
+ ## Two phases, one order
6
+
7
+ Start with `phase: scope`. This read-only pass checks whether the supplied paths cover the question and names missing paths and expected risks.
8
+
9
+ Continue with `phase: advise` under the same order id. Its task-file header must contain a `continue:` grant naming the successful scope run by its bare run-folder name (the last segment of the `RUN=`/`ATTACH=` path, never a path), with a reason for proceeding: `continue: <run folder> — <reason>`. The grant authorizes the second phase; a successful scope result alone does not grant it.
10
+
11
+ `OK — scope: sufficient` means the supplied context covers the question. `OK — scope: insufficient; N paths named` with a `Missing:` line is also success: it identifies the context advise needs. It is not FAIL or LIMIT. The advise boundary includes the original paths and that scope result's `missing_paths`.
12
+
13
+ Never continue a successful scope as another scope: that would consume the continuation intended for advise. A failed scope or advise pass is repeated only with a `retry:` grant naming that failed run, under the same order id. A changed question needs a new scope pass.
14
+
15
+ ## Task-file bodies
16
+
17
+ Both phases use the following body form after the lowercase header and a blank line:
18
+
19
+ ```markdown
20
+ ## Question
21
+ Which boundary should own the validation contract?
22
+
23
+ ## Options
24
+ - local-boundary: Keep validation within the owning component.
25
+ - shared-contract: Share validation between the consuming components.
26
+
27
+ ## Paths
28
+ - src/component.mjs
29
+ - tests/component.test.mjs
30
+ ```
31
+
32
+ `## Question` contains the design question. `## Options` contains at least two unique option ids matching `[a-z0-9][a-z0-9-]*`, each on a `- <option_id>: <description>` line. Keep options neutral: no preference markers or wording such as `recommend`, `prefer`, `рекоменд`, `предпочт`, `(✓)` or `★`. Biased or malformed options are refused before quota is spent.
33
+
34
+ `## Paths` contains at least one repository-relative path, one per list line. In scope, these are the read boundary. In advise, the successful scope run's `missing_paths` are also available. Citations must be real `path:line` addresses within that boundary; invented or out-of-list addresses fail validation.
35
+
36
+ For the scope task, declare the scope phase in the header and supply this question, neutral options and initial paths. For the advise task, declare the advise phase and the continuation grant in the header, preserving the question and options and incorporating the scope findings.
37
+
38
+ ## Use the answer
39
+
40
+ An advise answer recommends one option id and explains the choice, rejects alternatives with their costs, gives the strongest counterargument and a pre-mortem of risks. Its short form begins `OK — recommend <option_id>: …` and includes `Rejected:`, `Counter:` and `Risks:`. The full structured result and report stay in the run folder.
41
+
42
+ To cite it in a build task, use exactly one `advice:` line whose value is the absolute folder of the successful advise run. Its `meta.json` must identify `codex-advisor`, phase advise, status OK. A scope folder, report filename, failed advise or recommendation text is not a valid advice reference.
@@ -0,0 +1,37 @@
1
+ # budget
2
+
3
+ Slice each order before launch so its work and verification fit the agent's hard wall-clock deadline.
4
+
5
+ ## Budgets
6
+
7
+ The agent registry defines these budgets:
8
+
9
+ | Agent | Phase | Minutes |
10
+ | --- | --- | --- |
11
+ | codex-scout | — | 15 |
12
+ | codex-build | — | 25 |
13
+ | codex-review | — | 20 |
14
+ | codex-advisor | scope | 10 |
15
+ | codex-advisor | advise | 25 |
16
+
17
+ Advisor phases have separate deadlines; scope and advise are not one combined writing budget. The deadline is enforced by the runner, not an estimate to extend while work is in progress.
18
+
19
+ ## Slice a writing run
20
+
21
+ Keep a build order to one layer and a few writing files, normally 1–3 plus their targeted tests. Split cross-layer changes into separately verifiable orders before launch. Account for reading, implementation and verification within the same 25 minutes.
22
+
23
+ The task file must say:
24
+
25
+ - The concrete result and exact write scope, including any new files.
26
+ - The budget, the small set of sources to read, and an instruction to implement immediately without exploring beyond those sources.
27
+ - The behavior to preserve and what remains outside this slice.
28
+ - The targeted check under `## Verify`, as one command line.
29
+ - No full suite, build or smoke inside the run; leave broad integration checks outside this slice.
30
+
31
+ Do not turn a small implementation order into a repository audit. Narrow the order rather than hoping the deadline will spare unfinished work.
32
+
33
+ ## After a deadline FAIL
34
+
35
+ Inspect `git status` and the working-tree diff first: deadline termination can leave useful or incomplete changes. Then inspect the available result, report and events, using `codex-bridge read <run>` to render the event stream. Check which requested work and verification actually completed before deciding the next order.
36
+
37
+ Never resend the same order unchanged. Preserve the existing work and define a smaller remaining slice with an appropriate `continue:` or `retry:` grant and its reason. A deadline FAIL is not evidence that nothing was written or that the work is complete.
@@ -0,0 +1,17 @@
1
+ # concurrency
2
+
3
+ Run readers in parallel while keeping every writing run exclusive to its working tree.
4
+
5
+ ## Readers and writers
6
+
7
+ Read-only `codex-scout`, `codex-review` and both `codex-advisor` phases may run in parallel freely, including alongside a writing run.
8
+
9
+ Only one writing run may be active in a working tree. A second writing run in that tree is refused, even when the two orders name different files. Parallel writers require separate worktrees, each with its own working tree and write boundary.
10
+
11
+ The writing runner snapshots the tree before and after its run, then compares the actual changes with the reported files and order scope. Another writer's edits enter that same snapshot and appear to belong to this run. Disjoint file lists therefore do not isolate writers in one tree. Keep other file-changing operations out of a live writer's tree as well.
12
+
13
+ ## Stop a live run
14
+
15
+ Use the run path from its receipt and call `codex-bridge stop <run>` before `TaskStop`. The bridge stop terminates the Codex run and records FAIL. `TaskStop` alone stops the dispatcher task while the Codex process can keep writing.
16
+
17
+ Inspect the resulting status and working-tree changes before launching another writer. Stopping a run does not undo the files it already changed.
@@ -0,0 +1,68 @@
1
+ # order
2
+ Write a task file that gives each dispatcher the complete order and the build's file boundary.
3
+
4
+ ## Call and header
5
+
6
+ The call is exactly one line: `task file: <task-file path from the orchestrator>`.
7
+ The path must be absolute and name a file the orchestrator wrote with its file tool.
8
+ The dispatcher never creates, reads or rewrites it. The whole order lives in that file's header.
9
+ The runner checks the header, never the dispatcher. Free text belongs in the task file.
10
+
11
+ Start at the first line with consecutive lowercase `label: value` lines, then a blank line and the task body.
12
+ Labels must use their exact spelling, start at column one and have a non-empty value; duplicates are refused.
13
+ The header ends at the first line that is not a recognized label line, including a blank line.
14
+ Do not put grants or advice below the header, even in a code fence; quote examples with `>` or reword them inside a sentence.
15
+
16
+ | Agent | Required labels | Conditional labels | Optional labels |
17
+ | --- | --- | --- | --- |
18
+ | codex-scout | `order id` | `continue`, `retry` | `repository`, `slug`, `effort` |
19
+ | codex-build | `order id`, `scope`, `advice` | `continue`, `retry` | `repository`, `scope new`, `slug`, `effort` |
20
+ | codex-review | `order id` | `continue`, `retry` | `repository`, `slug`, `effort`, `changeset` |
21
+ | codex-advisor | `order id`, `phase` | `continue`, `retry` | `repository`, `slug`, `effort` |
22
+
23
+ Both grant labels are conditional when this pass continues or repeats a named run. Keep exactly one grant label:
24
+ `continue` names the continued run; `retry` repeats a failed pass and does not authorize the next pass.
25
+ Each value names a bare run folder (not a path, `.` or `..`) and a non-placeholder reason, separated by ` — `, ` - ` or `:`.
26
+ For advisor, pass `scope` first, then `advise` with a `continue:` grant naming the scope run of the same order.
27
+
28
+ `repository` is the repository path; `slug` names this pass without replacing the order id.
29
+ The runner checks accepted `effort` values. `changeset` is `uncommitted`, `base:<branch>` or `commit:<sha>`.
30
+ Keep header values short and free of forbidden shell sequences. Empty or placeholder values are refused;
31
+ `phase: scope` and `effort: none` are real values.
32
+
33
+ ## File boundary
34
+
35
+ `scope`: Comma-separated globs relative to the repository root, listing every file the run may touch — including each caller of what changes, not only the file being edited. Anything outside the list fails the run.
36
+
37
+ `scope new`: Comma-separated globs for new files this build may create, relative to the repository root.
38
+ `scope` must declare at least one existing path pattern; `scope new` does not replace it.
39
+ <!-- service-folders:begin -->
40
+ Never name service folders in scope: `.git/`, `.claude/`, `.codex/`, `.omx/`, `.omc/`, `node_modules/`.
41
+ <!-- service-folders:end -->
42
+
43
+ Example build header:
44
+
45
+ ```text
46
+ order id: guidance-order-text
47
+ scope: tests/guidance-topics.test.mjs
48
+ scope new: src/home/guidance/order.md
49
+ advice: docs-only
50
+
51
+ ```
52
+
53
+ ## Advice
54
+
55
+ Every build task header contains exactly one `advice:` line. Its value is an absolute path to an existing run folder
56
+ whose `meta.json` identifies a `codex-advisor` advise pass with status `OK`, or exactly `mechanical`, `revert`,
57
+ `docs-only`, or `test-only`. Anything else is refused before the run starts.
58
+
59
+ ## Verify
60
+
61
+ Put the verification command in the task file under a `## Verify` heading, one line.
62
+ The dispatcher never reads that file and never puts the command on the command line.
63
+
64
+ ## Free refusals
65
+
66
+ A pre-start refusal creates no run folder and spends no quota; the same order id remains reusable for the corrected order.
67
+ A refusal is the dispatcher's whole answer, not a reason to change the call or task file and retry itself.
68
+ Repeating the same call for a run already in flight joins that run and costs no quota; a different piece of work needs a new label.
@@ -0,0 +1,32 @@
1
+ # records
2
+
3
+ Find run artifacts through runner receipts and the project inventory without naming or deriving a run-store root.
4
+
5
+ ## Find a run
6
+
7
+ Use the absolute run-folder path printed in the runner's `RUN=` or `ATTACH=` line. Keep that returned path as the run identity; do not reconstruct it from a timestamp, project name, package installation, home directory or another run.
8
+
9
+ For earlier runs, use `codex-bridge projects [<project>] --json`. Use the run object's `path` field, which is the absolute run-folder path. If no project is supplied, first find the project in the inventory; do not concatenate names to invent a path.
10
+
11
+ Render the selected run with `codex-bridge read <run>`, passing the returned run path. This renders its structured event stream. The inventory and receipt are the lookup contract; diagnostics are not a substitute for them.
12
+
13
+ ## Read the artifacts
14
+
15
+ A started run records its lifecycle and output in its run folder:
16
+
17
+ - `meta.json`: agent, phase and final status; use it to identify an advisor advise pass before citing it in `advice:`.
18
+ - `status.json`: lifecycle state (`running`, `finished`, `failed` or `abandoned`) and runner PID.
19
+ - `result.json`: structured scout, build or advisor result; build includes `summary`, `changes`, `verify_command`, `verify_passed`, `leftovers` and `report_markdown`.
20
+ - `review.json`: the review agent's structured result instead of `result.json`.
21
+ - `report.md`: the full report; the runner expands a build result's `report_markdown` into this file.
22
+ - `events.jsonl` and `stderr.log`: transport evidence for progress, deadline or startup failures.
23
+
24
+ A failed or interrupted run may have no complete result. Read the recorded status and available evidence rather than treating a missing report as an empty successful run.
25
+
26
+ ## Availability verdicts
27
+
28
+ `LIMIT` means the ChatGPT quota window is exhausted, not a task defect. For a writing run, also check the runner's indication of whether the tree was touched.
29
+
30
+ `UNAVAILABLE` means Codex is signed out on this host; the task could not run. A pre-start availability refusal may leave no run folder. Neither verdict is a reason to retry the same bridge. Preserve the verdict and any existing artifacts.
31
+
32
+ A refusal starting `Codex CLI readiness unconfirmed:` is not `UNAVAILABLE`: it leaves no run folder and costs no quota. Do not skip the bridge; follow its second line — check `codex --version` for up to ~2 minutes. When it answers, repeat the same order exactly once; if it does not, hand the order to the next executor and name that in the summary. If the repeat gets the same refusal, hand the order to the next executor as well; no second repeat.
@@ -107,7 +107,8 @@ export function allLiveRuns(runsRoot, options = {}) {
107
107
  }
108
108
  const result = [];
109
109
  for (const project of projects) {
110
- if (!project.isDirectory()) continue;
110
+ // Plan_77 B5b makes the moved store a git repository; its metadata is not a project.
111
+ if (!project.isDirectory() || project.name === '.git') continue;
111
112
  result.push(...liveRuns(path.join(runsRoot, project.name), options));
112
113
  }
113
114
  return result;
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Plan_64 D11: only InstructionsLoaded witnesses the host loading the rules core.
4
+ * The 2026-10-10 live run fired before the transcript existed, so persist now and resolve later.
5
+ * Observation must never refuse a host action, including doctor's empty-stdin dry run.
6
+ */
7
+ import fs from 'node:fs';
8
+ import { BRAND_STATE_DIR } from '../lib/brand-home.mjs';
9
+ import { parseJsonText } from '../lib/json-file.mjs';
10
+ import { recordCoreLoad } from '../lib/load-witness.mjs';
11
+
12
+ try {
13
+ const raw = fs.readFileSync(0, 'utf8');
14
+ if (raw.trim()) {
15
+ const input = parseJsonText('stdin', raw);
16
+ if (input && typeof input === 'object' && !Array.isArray(input)) {
17
+ await recordCoreLoad({ stateDir: BRAND_STATE_DIR, input });
18
+ }
19
+ }
20
+ } catch {
21
+ // D11: lost evidence is not a refusal; preserve corrupt state for inspection.
22
+ }
23
+ process.exit(0);
@@ -8,6 +8,7 @@ export function claudePaths(root) {
8
8
  return {
9
9
  agentsDir: path.join(root, 'agents', 'codex-bridge'),
10
10
  commandsDir: path.join(root, 'commands', 'codex-bridge'),
11
+ rulesDir: path.join(root, 'rules', 'codex-bridge'),
11
12
  legacyAgentsDir: path.join(root, 'agents', 'codex'),
12
13
  legacyCommandsDir: path.join(root, 'commands', 'codex'),
13
14
  };
@@ -0,0 +1,8 @@
1
+ /** Keeps one Codex home resolver for readers and the path currently built inline in cli/hosts.mjs:26. */
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ export function resolveCodexHome({ env = process.env, homedir = os.homedir() } = {}) {
6
+ return typeof env.CODEX_HOME === 'string' && env.CODEX_HOME.length > 0
7
+ ? env.CODEX_HOME : path.join(homedir, '.codex');
8
+ }
@@ -90,6 +90,13 @@ const entries = [
90
90
  primary: ['state/dispatcher-model.json'],
91
91
  sides: ['lock', 'atomic-temporary'],
92
92
  },
93
+ {
94
+ id: 'runs-move-record',
95
+ removal: 'purge-only',
96
+ consequence: 'The package forgets that run records moved, and new runs write to the old store again while it still exists.',
97
+ primary: ['state/runs-root.json'],
98
+ sides: ['atomic-temporary'],
99
+ },
93
100
  {
94
101
  id: 'host-observations',
95
102
  removal: 'purge-only',
@@ -97,6 +104,13 @@ const entries = [
97
104
  primary: ['state/host-observations.json'],
98
105
  sides: ['lock', 'atomic-temporary'],
99
106
  },
107
+ {
108
+ id: 'load-witness',
109
+ removal: 'purge-only',
110
+ consequence: 'Doctor forgets which sessions witnessed the rules core loading until the next session start.',
111
+ primary: ['state/load-witness.json'],
112
+ sides: ['lock', 'atomic-temporary'],
113
+ },
100
114
  {
101
115
  id: 'guard-tries',
102
116
  removal: 'purge-only',
@@ -124,6 +138,13 @@ export const HOME_ARTIFACTS = Object.freeze(entries.map((entry) => Object.freeze
124
138
  sides: Object.freeze([...entry.sides]),
125
139
  })));
126
140
 
141
+ /**
142
+ * Plan_77 D1: run records live in this folder of the home once they move out of ~/.claude. They are
143
+ * operator data, not an artifact of the package: the folder is never walked or removed, and naming it
144
+ * here keeps the resolver and the removal plan on one spelling.
145
+ */
146
+ export const RUN_RECORDS_DIR = 'runs';
147
+
127
148
  export const HOME_DIRECTORIES = Object.freeze([
128
149
  'state',
129
150
  'state/dispatchers',
@@ -67,6 +67,8 @@ export const LOCKED_TOOL_MATCHER = [...WRITE_TOOLS, ...SHELL_TOOLS].join('|');
67
67
  * dispatcher must consume this one list instead of guessing a name from a path at its call site.
68
68
  */
69
69
  export const HOOK_DEFINITIONS = Object.freeze([
70
+ // Plan_64 D11: loading evidence observes only; this hook can never refuse anything.
71
+ Object.freeze({ name: 'load-witness', event: 'InstructionsLoaded', matcher: '*', file: 'load-witness.mjs' }),
70
72
  Object.freeze({ name: 'reply-guard', event: 'SubagentStop', matcher: '*', file: 'reply-guard.mjs' }),
71
73
  Object.freeze({ name: 'order-gate', event: 'PreToolUse', matcher: SUBAGENT_TOOL_MATCHER, file: 'order-gate.mjs' }),
72
74
  Object.freeze({ name: 'worktree-lock', event: 'PreToolUse', matcher: LOCKED_TOOL_MATCHER, file: 'worktree-lock.mjs' }),
@@ -1,24 +1,43 @@
1
- /** Reads the host version from the transcript tail after the 2026-09-25 PATH host identity incident. */
1
+ /** Reads the host version from a session transcript after the 2026-09-25 PATH host identity incident. */
2
2
  import fs from 'node:fs/promises';
3
3
  import { parseJsonText } from './json-file.mjs';
4
4
 
5
- export async function transcriptHostVersion(transcriptPath, { tailBytes = 65536 } = {}) {
5
+ const VERSION = /^\d+\.\d+\.\d+/;
6
+
7
+ function versionIn(transcriptPath, lines, sessionId, newestFirst) {
8
+ const order = newestFirst ? [...lines.keys()].reverse() : lines.keys();
9
+ for (const index of order) {
10
+ try {
11
+ const entry = parseJsonText(transcriptPath, lines[index]);
12
+ // Plan_64 D11: a neighboring session's version cannot identify this load witness.
13
+ if (sessionId && entry?.sessionId !== sessionId) continue;
14
+ if (typeof entry?.version === 'string' && VERSION.test(entry.version)) return entry.version;
15
+ } catch {}
16
+ }
17
+ return null;
18
+ }
19
+
20
+ async function slice(handle, position, length) {
21
+ const buffer = Buffer.alloc(length);
22
+ await handle.read(buffer, 0, length, position);
23
+ return buffer.toString('utf8').split(/\r?\n/);
24
+ }
25
+
26
+ /**
27
+ * A live session's newest lines name its host, so the tail answers the shell-command observers. A finished session
28
+ * may end in a 135 KB attachment line and metadata without `version` (B6b live run, 2026-10-10), so a named session is
29
+ * also looked up from the head: one session runs on one host, and its first user line already carries the version.
30
+ */
31
+ export async function transcriptHostVersion(transcriptPath, { tailBytes = 65536, headBytes = 1048576, sessionId } = {}) {
32
+ const session = typeof sessionId === 'string' && sessionId.length > 0 ? sessionId : null;
6
33
  try {
7
34
  const handle = await fs.open(transcriptPath, 'r');
8
35
  try {
9
36
  const { size } = await handle.stat();
10
- const length = Math.min(size, tailBytes);
11
- const buffer = Buffer.alloc(length);
12
- await handle.read(buffer, 0, length, size - length);
13
- const lines = buffer.toString('utf8').split(/\r?\n/);
14
- for (let index = lines.length - 1; index >= 0; index -= 1) {
15
- try {
16
- const entry = parseJsonText(transcriptPath, lines[index]);
17
- if (typeof entry?.version === 'string' && /^\d+\.\d+\.\d+/.test(entry.version)) {
18
- return entry.version;
19
- }
20
- } catch {}
21
- }
37
+ const tailLength = Math.min(size, tailBytes);
38
+ const fromTail = versionIn(transcriptPath, await slice(handle, size - tailLength, tailLength), session, true);
39
+ if (fromTail !== null || !session) return fromTail;
40
+ return versionIn(transcriptPath, await slice(handle, 0, Math.min(size, headBytes)), session, false);
22
41
  } finally {
23
42
  await handle.close();
24
43
  }