agent-composer 0.3.1 → 0.4.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.
Files changed (180) hide show
  1. package/README.md +495 -180
  2. package/composer.config.schema.json +206 -2
  3. package/dist/cli/cleanup.d.ts +24 -0
  4. package/dist/cli/cleanup.js +151 -0
  5. package/dist/cli/cleanup.js.map +1 -0
  6. package/dist/cli/doctor.d.ts +12 -0
  7. package/dist/cli/doctor.js +244 -4
  8. package/dist/cli/doctor.js.map +1 -1
  9. package/dist/cli/goal.d.ts +28 -0
  10. package/dist/cli/goal.js +251 -0
  11. package/dist/cli/goal.js.map +1 -0
  12. package/dist/cli/help.d.ts +3 -0
  13. package/dist/cli/help.js +31 -0
  14. package/dist/cli/help.js.map +1 -0
  15. package/dist/cli/init.d.ts +5 -0
  16. package/dist/cli/init.js +116 -21
  17. package/dist/cli/init.js.map +1 -1
  18. package/dist/cli/initArgs.d.ts +16 -0
  19. package/dist/cli/initArgs.js +19 -0
  20. package/dist/cli/initArgs.js.map +1 -0
  21. package/dist/cli/installGitHook.d.ts +7 -0
  22. package/dist/cli/installGitHook.js +61 -0
  23. package/dist/cli/installGitHook.js.map +1 -0
  24. package/dist/cli/mode.d.ts +6 -0
  25. package/dist/cli/mode.js +25 -0
  26. package/dist/cli/mode.js.map +1 -0
  27. package/dist/cli/status.d.ts +105 -0
  28. package/dist/cli/status.js +400 -0
  29. package/dist/cli/status.js.map +1 -0
  30. package/dist/config/env.d.ts +1 -1
  31. package/dist/config/modes.d.ts +10 -0
  32. package/dist/config/modes.js +26 -0
  33. package/dist/config/modes.js.map +1 -0
  34. package/dist/config/oracleRole.d.ts +10 -0
  35. package/dist/config/oracleRole.js +11 -0
  36. package/dist/config/oracleRole.js.map +1 -0
  37. package/dist/config/schema.d.ts +246 -0
  38. package/dist/config/schema.js +127 -2
  39. package/dist/config/schema.js.map +1 -1
  40. package/dist/evolve/reflection.d.ts +9 -0
  41. package/dist/evolve/reflection.js +14 -0
  42. package/dist/evolve/reflection.js.map +1 -1
  43. package/dist/evolve/runner.d.ts +2 -1
  44. package/dist/evolve/runner.js +2 -1
  45. package/dist/evolve/runner.js.map +1 -1
  46. package/dist/index.js +115 -6
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers/AnthropicCompatibleProvider.d.ts +13 -1
  49. package/dist/providers/AnthropicCompatibleProvider.js +115 -9
  50. package/dist/providers/AnthropicCompatibleProvider.js.map +1 -1
  51. package/dist/providers/CLIProvider.d.ts +18 -0
  52. package/dist/providers/CLIProvider.js +265 -62
  53. package/dist/providers/CLIProvider.js.map +1 -1
  54. package/dist/providers/IProvider.d.ts +12 -0
  55. package/dist/providers/SpendGuardProvider.d.ts +32 -0
  56. package/dist/providers/SpendGuardProvider.js +98 -0
  57. package/dist/providers/SpendGuardProvider.js.map +1 -0
  58. package/dist/registry.d.ts +5 -2
  59. package/dist/registry.js +17 -2
  60. package/dist/registry.js.map +1 -1
  61. package/dist/server/activeRuns.d.ts +17 -0
  62. package/dist/server/activeRuns.js +114 -0
  63. package/dist/server/activeRuns.js.map +1 -0
  64. package/dist/server/codexLifecycleRunner.d.ts +29 -0
  65. package/dist/server/codexLifecycleRunner.js +188 -0
  66. package/dist/server/codexLifecycleRunner.js.map +1 -0
  67. package/dist/server/configMutation.d.ts +22 -0
  68. package/dist/server/configMutation.js +121 -0
  69. package/dist/server/configMutation.js.map +1 -0
  70. package/dist/server/handoffContext.d.ts +1 -0
  71. package/dist/server/handoffContext.js +12 -0
  72. package/dist/server/handoffContext.js.map +1 -0
  73. package/dist/server/progress.d.ts +24 -0
  74. package/dist/server/progress.js +109 -0
  75. package/dist/server/progress.js.map +1 -0
  76. package/dist/server/toolDescriptions.d.ts +60 -0
  77. package/dist/server/toolDescriptions.js +134 -0
  78. package/dist/server/toolDescriptions.js.map +1 -0
  79. package/dist/server.d.ts +19 -25
  80. package/dist/server.js +87 -377
  81. package/dist/server.js.map +1 -1
  82. package/dist/tools/audit.d.ts +2 -0
  83. package/dist/tools/audit.js +66 -0
  84. package/dist/tools/audit.js.map +1 -0
  85. package/dist/tools/code.d.ts +2 -0
  86. package/dist/tools/code.js +160 -0
  87. package/dist/tools/code.js.map +1 -0
  88. package/dist/tools/codexLifecycle.d.ts +2 -0
  89. package/dist/tools/codexLifecycle.js +206 -0
  90. package/dist/tools/codexLifecycle.js.map +1 -0
  91. package/dist/tools/config.d.ts +2 -0
  92. package/dist/tools/config.js +183 -0
  93. package/dist/tools/config.js.map +1 -0
  94. package/dist/tools/context.d.ts +31 -0
  95. package/dist/tools/context.js +2 -0
  96. package/dist/tools/context.js.map +1 -0
  97. package/dist/tools/goal.d.ts +2 -0
  98. package/dist/tools/goal.js +159 -0
  99. package/dist/tools/goal.js.map +1 -0
  100. package/dist/tools/handoff.d.ts +2 -0
  101. package/dist/tools/handoff.js +57 -0
  102. package/dist/tools/handoff.js.map +1 -0
  103. package/dist/tools/oracle.d.ts +2 -0
  104. package/dist/tools/oracle.js +248 -0
  105. package/dist/tools/oracle.js.map +1 -0
  106. package/dist/tools/research.d.ts +2 -0
  107. package/dist/tools/research.js +51 -0
  108. package/dist/tools/research.js.map +1 -0
  109. package/dist/tools/review.d.ts +2 -0
  110. package/dist/tools/review.js +233 -0
  111. package/dist/tools/review.js.map +1 -0
  112. package/dist/tools/route.d.ts +2 -0
  113. package/dist/tools/route.js +69 -0
  114. package/dist/tools/route.js.map +1 -0
  115. package/dist/tools/session.d.ts +2 -0
  116. package/dist/tools/session.js +37 -0
  117. package/dist/tools/session.js.map +1 -0
  118. package/dist/tools/status.d.ts +2 -0
  119. package/dist/tools/status.js +34 -0
  120. package/dist/tools/status.js.map +1 -0
  121. package/dist/tools/workflow.d.ts +2 -0
  122. package/dist/tools/workflow.js +27 -0
  123. package/dist/tools/workflow.js.map +1 -0
  124. package/dist/util/applyFileBlocks.d.ts +18 -0
  125. package/dist/util/applyFileBlocks.js +163 -0
  126. package/dist/util/applyFileBlocks.js.map +1 -0
  127. package/dist/util/asyncControl.d.ts +14 -0
  128. package/dist/util/asyncControl.js +106 -0
  129. package/dist/util/asyncControl.js.map +1 -0
  130. package/dist/util/auditLog.d.ts +56 -0
  131. package/dist/util/auditLog.js +232 -0
  132. package/dist/util/auditLog.js.map +1 -0
  133. package/dist/util/codexLifecycle.d.ts +55 -0
  134. package/dist/util/codexLifecycle.js +102 -0
  135. package/dist/util/codexLifecycle.js.map +1 -0
  136. package/dist/util/codexLifecycleJob.d.ts +209 -0
  137. package/dist/util/codexLifecycleJob.js +360 -0
  138. package/dist/util/codexLifecycleJob.js.map +1 -0
  139. package/dist/util/composerDisabled.d.ts +6 -0
  140. package/dist/util/composerDisabled.js +27 -0
  141. package/dist/util/composerDisabled.js.map +1 -0
  142. package/dist/util/dispatchHint.d.ts +5 -3
  143. package/dist/util/dispatchHint.js +62 -2
  144. package/dist/util/dispatchHint.js.map +1 -1
  145. package/dist/util/goal.d.ts +132 -0
  146. package/dist/util/goal.js +616 -0
  147. package/dist/util/goal.js.map +1 -0
  148. package/dist/util/goalReport.d.ts +51 -0
  149. package/dist/util/goalReport.js +164 -0
  150. package/dist/util/goalReport.js.map +1 -0
  151. package/dist/util/jobPolling.d.ts +9 -0
  152. package/dist/util/jobPolling.js +17 -0
  153. package/dist/util/jobPolling.js.map +1 -0
  154. package/dist/util/oracleJob.d.ts +66 -0
  155. package/dist/util/oracleJob.js +295 -0
  156. package/dist/util/oracleJob.js.map +1 -0
  157. package/dist/util/oracleLock.d.ts +38 -0
  158. package/dist/util/oracleLock.js +182 -0
  159. package/dist/util/oracleLock.js.map +1 -0
  160. package/dist/util/reviewDiff.d.ts +8 -0
  161. package/dist/util/reviewDiff.js +29 -0
  162. package/dist/util/reviewDiff.js.map +1 -0
  163. package/dist/util/reviewJob.d.ts +57 -0
  164. package/dist/util/reviewJob.js +207 -0
  165. package/dist/util/reviewJob.js.map +1 -0
  166. package/dist/util/workflowPlan.d.ts +24 -0
  167. package/dist/util/workflowPlan.js +49 -0
  168. package/dist/util/workflowPlan.js.map +1 -0
  169. package/package.json +8 -1
  170. package/plugin/composer-mastermind/commands/evolve.md +4 -0
  171. package/plugin/composer-mastermind/hooks/boundary_guard.sh +43 -2
  172. package/plugin/composer-mastermind/hooks/codex_warm_review.sh +161 -9
  173. package/plugin/composer-mastermind/hooks/learn.sh +172 -32
  174. package/plugin/composer-mastermind/hooks/precommit_codex_review.sh +438 -64
  175. package/plugin/composer-mastermind/skills/composer-mastermind/SKILL.md +190 -4
  176. package/scripts/composer-oracle-router-safe.sh +47 -0
  177. package/scripts/composer-statusline-segment.mjs +40 -0
  178. package/scripts/oracle-codex-handoff-safe.sh +49 -0
  179. package/scripts/oracle-plan-mcp.sh +66 -0
  180. package/scripts/oracle-pro-safe.sh +572 -0
@@ -60,6 +60,7 @@ system — spend it on planning, not on raw worker output.
60
60
  | Generate a patch WITHOUT applying (rare) | `coder` subagent (`composer_code` → `coder` applies) |
61
61
  | Reviewing a candidate patch / diff / implementation | `composer_review` directly; use `reviewer` only for high-volume isolation |
62
62
  | Claude review explicitly requested, or high-risk/security-sensitive second opinion | `composer_review_claude` directly after the default review gate |
63
+ | Deep architecture / feature planning, migration design, hard root-cause debugging, or high-risk review where you want ChatGPT Pro's extended reasoning (opt-in) | `composer_oracle_plan` directly; advisory planning only — file edits still go through `composer_code_cli` |
63
64
  | Anything that mutates state outside the conversation (push, deploy, install) | Escalate to the user. Do not act. |
64
65
 
65
66
  When an edit targets a project other than the Composer MCP server cwd (for
@@ -68,6 +69,40 @@ example dotfiles or another repo), pass `projectDir: "<absolute path>"` to
68
69
  (a git repo or a `config.toml` projects entry) or it will refuse; do not add
69
70
  `--skip-git-repo-check`.
70
71
 
72
+ ## Oracle planning lane (ChatGPT Pro — opt-in)
73
+
74
+ `composer_oracle_plan` consults ChatGPT Pro through the Oracle browser
75
+ (`scripts/oracle-pro-safe.sh`) for extended-reasoning planning, design,
76
+ migration, hard debugging, or high-risk review. It is OPT-IN and NOT the
77
+ default research lane — `composer_research` stays on Codex to keep routine
78
+ lookups fast and cheap.
79
+
80
+ - Use it deliberately: a `deep` / `review` / `debug` run drives a real browser
81
+ ChatGPT Pro session (slow, supervised). Do not route routine research here.
82
+ - `mode` selects thinking depth: `quick` / `standard` keep a fast model;
83
+ `deep` / `plan` / `review` / `debug` select ChatGPT Pro; `research` adds Deep
84
+ Research. Omit (`auto`) to let the script classify.
85
+ - It is ADVISORY: the tool never edits files. Hand its plan to
86
+ `composer_code_cli` for implementation, then `composer_review`.
87
+ - **Ground it in real code, never a paraphrase.** When asking Oracle to review
88
+ committed branch work, do NOT describe the diff in prose —
89
+ `scripts/oracle-pro-safe.sh` auto-attaches the real branch diff (`base...HEAD`)
90
+ plus changed-file contents. Pass `--base <branch>` (or set
91
+ `ORACLE_PRO_DIFF_BASE`) when auto-detection (main/master/develop) would pick
92
+ the wrong base. Reviewing from a summary yields `[inference]` false positives.
93
+ - Requires a one-time Oracle browser login and the `oraclePlanner` role in the
94
+ active config. Full answers persist under `.composer/oracle/answers/`
95
+ (gitignored); the tool returns a bounded summary.
96
+ - For long deep-research, large architectural reviews, or when the user says not
97
+ to block, use the async pair instead: `composer_oracle_job_start` returns a
98
+ `jobId` immediately, then `composer_oracle_job_result` (optionally with
99
+ `waitMs`) pulls the answer when the job reaches `succeeded`. Both run off-CC and
100
+ return bounded summaries.
101
+ - Default to the synchronous `composer_oracle_plan` whenever the next step
102
+ depends on the answer (most planning/review). Reach for the async job pair only
103
+ when the answer is useful but not immediately blocking, or on an explicit
104
+ `[oracle:async]` request. Do not make every deep/review/debug call async.
105
+
71
106
  ## Codex rescue (second-opinion lane)
72
107
 
73
108
  Use Codex rescue when the same bug has 2+ failed fix attempts, root-cause
@@ -87,6 +122,84 @@ or the user asks for a second opinion.
87
122
  latest failing command, changed files, and smallest repro. Do not include the
88
123
  whole transcript, secrets, or `.env.json`.
89
124
 
125
+ ## Codex lifecycle participation
126
+
127
+ Use `codexLifecycle` for ambient Codex involvement across the development
128
+ loop. This is separate from `codexReview` (mechanical diff gate) and
129
+ `codexRescue` (stuck-debug escalation). It answers one question: should Codex
130
+ join this lifecycle step now?
131
+
132
+ - Read root `composer.config.json` `codexLifecycle`.
133
+ - If omitted or `enabled:false`, skip lifecycle participation. Existing
134
+ `composer_code_cli`, `codexReview`, and `codexRescue` rules still apply.
135
+ - For non-trivial feature/debug work, call `composer_handoff_create` first so
136
+ Codex and reviewers share the same objective, constraints, files, and
137
+ acceptance criteria.
138
+ - Call `composer_codex_lifecycle_decide` at eligible points with compact
139
+ signals: `event`, `expectedOutputTokens`, `changedFiles`, `diffLines`,
140
+ `failedAttempts`, `failingTests`, `touchesSecurity`, `touchesInfra`,
141
+ `userRequestedCodex`, `hasHandoff`, `isTrivial`, `isDestructive`, and `risk`.
142
+ - Treat the decision literally:
143
+ - `skip`: do not involve Codex for this lifecycle step.
144
+ - `ask`: ask the user before involving Codex, naming the event and reason.
145
+ - `run`: Codex may participate automatically within the configured mode,
146
+ model, execution, and spend/safety constraints.
147
+ - When the decision is `run`, call `composer_codex_lifecycle_run` with the
148
+ same event, handoff, prompt, and signals.
149
+ - When the decision is `ask` and the user confirms, call
150
+ `composer_codex_lifecycle_run` with `confirmed:true`; do not set
151
+ `confirmed:true` for policy `skip` outcomes.
152
+ - If lifecycle execution is `foreground`, use the returned result before
153
+ continuing.
154
+ - If lifecycle execution is `background`, keep the returned `jobId` and call
155
+ `composer_codex_lifecycle_result` before reporting the lifecycle step done.
156
+ Background Codex output is not complete until its durable result record is
157
+ `succeeded`, `failed`, `unavailable`, or deliberately `skipped`.
158
+ - Treat `skipped` as a policy decision and `unavailable` as a provider/session
159
+ failure (`auth`, `quota`, `rate_limit`, `timeout`, `cancelled`, `provider`,
160
+ or `unknown`). If `codexLifecycle.fallback.enabled`, Composer tries
161
+ `coderCli` first and then configured fallback roles such as `reviewerClaude`,
162
+ `reviewer`, or `coder`; always surface `providerRole`, `fallbackUsed`, and
163
+ the attempts list when a fallback handled the checkpoint. Optional lifecycle
164
+ jobs may continue after surfacing this; forced pre-commit review gates must
165
+ fail closed through `codexReview`.
166
+ - Lifecycle Codex runs are companion/advisory passes. Do not let background
167
+ lifecycle runs silently mutate files; integrate any suggestions deliberately
168
+ and gate code changes through review.
169
+
170
+ ## Composer config tools
171
+
172
+ When the user asks to turn Composer/Codex lifecycle behavior on/off, set
173
+ fallback providers, or harden the pre-commit gate, prefer MCP config tools over
174
+ manual JSON editing:
175
+
176
+ - Call `composer_config_get` first with `scope:"active"` unless the user names
177
+ project or global config explicitly.
178
+ - Call `composer_config_set` with a narrow patch for `codexLifecycle` and/or
179
+ `codexReview`; the server validates before writing.
180
+ - Use `scope:"project"` for repo-local `composer.config.json` and
181
+ `scope:"global"` for `~/.config/composer/composer.config.json`.
182
+ - If `scope:"active"` resolves to the user-global fallback, call
183
+ `composer_config_set` again with `scope:"global"` explicitly; Composer
184
+ refuses implicit global writes.
185
+ - After config changes, run `agent-composer doctor` and report the effective
186
+ lifecycle mode, fallback order, and pre-commit gate state.
187
+
188
+ Default trigger intent:
189
+
190
+ | Event | Use when |
191
+ |---|---|
192
+ | `postPlan` | a plan is ready and design risk is non-trivial |
193
+ | `postCodeApply` | code was applied and the diff is broad or risky |
194
+ | `postTestFailure` | targeted checks fail after implementation |
195
+ | `afterFailedAttempts` | the same bug/fix has failed repeatedly |
196
+ | `postResearch` | research output needs a second synthesis pass |
197
+ | `preCommit` | optional policy layer before the mechanical gate |
198
+ | `stopWarm` | passive background/warm participation is configured |
199
+
200
+ Never use lifecycle participation to bypass user confirmation for destructive
201
+ actions, secret handling, deploys, pushes, or billing-affecting changes.
202
+
90
203
  **Class-based route policy:** route by task class, not by a blanket
91
204
  "always dispatch" rule.
92
205
 
@@ -127,6 +240,62 @@ expected-output estimate; if under 5 lines, just answer.
127
240
 
128
241
  **Fan-out cap:** Max 3 parallel worker dispatches per turn for repos with >500 source files. Beyond 3, the prompt-cache misses compound faster than the parallelism saves wall time. If file slices overlap across workers, dispatch SEQUENTIALLY — parallel workers on the same files duplicate every Read.
129
242
 
243
+ # Control plane (status, route, workflow, session, audit)
244
+
245
+ These tools make routing explicit and cheap. Prefer them over re-deriving policy from this skill every turn.
246
+
247
+ - **`composer_status`** — read-only state snapshot: mode, integrations (review / lifecycle / oracle / git-hook / disabled), the active FOREGROUND worker, active/latest async jobs, latest audit facts, and a recommended next action. Inside Claude Code prefer this over the `agent-composer status` CLI — only the MCP tool sees live in-session `composer_session_*` overrides.
248
+ - **`composer_route_decide`** — preview the route for a prompt WITHOUT executing: returns `target`, `taskClass`, `contextBudget`, and `recommendedNextTools`. Output is `compact` by default; pass `format:"full"` only when debugging the classifier. Call it before a worker dispatch when the right lane is unclear; it spends no worker tokens.
249
+ - **`composer_workflow_plan`** — recommend (do NOT execute) the ordered tool sequence for a goal (`feature` / `debug` / `review` / `research`, tuned by mode + risk). It returns steps; you still call each tool yourself.
250
+ - **`composer_session_get` / `composer_session_set`** — EPHEMERAL per-session overrides (mode, oracle, code_cli `profile`) that do NOT write `composer.config.json`. Use for "this session only" toggles; `clear:true` resets. For durable changes use `composer_config_set`.
251
+ - **`composer_audit_record` / `composer_audit_read` / `composer_audit_summary`** — durable route/outcome trail. After a dispatch, record what happened (route, provider, `reviewVerdict`, `testsPassed`, `userCorrection`, `status`) so `/evolve` learns from real failures, not transcripts. Recording is EXPLICIT — these tools are never auto-called; keep `note`/`objective` short (capped on write). Read/summary to inspect route accuracy.
252
+
253
+ # Subagent speed contract
254
+
255
+ Fast path for quick inspection and minimal wait:
256
+
257
+ 1. Call `composer_route_decide` with `format:"compact"` — local, no model.
258
+ Do NOT request `format:"full"` unless debugging the classifier.
259
+ 2. Honor returned `contextBudget` (`inline` / `handoff` / `scoped-diff` /
260
+ `full-brief` / `oracle-brief`). Send only matching context, NEVER a raw
261
+ transcript.
262
+ 3. Prefer `composer_code_cli` for implementation; the executor applies off-CC.
263
+ 4. Use `reviewScope` (`staged` / `working-tree` / `branch`). NEVER paste broad
264
+ diffs.
265
+ 5. Background, don't block: use `composer_review_job_start` +
266
+ `composer_review_job_result` for routine/advisory review while continuing
267
+ work. Reserve synchronous `composer_review` for the pre-commit / merge gate
268
+ or explicit "review now". Likewise prefer background lifecycle
269
+ (`execution:"background"`).
270
+ 6. NEVER call Oracle unless explicitly tagged `[oracle:<mode>]` or the route
271
+ says premium escalation.
272
+ 7. For quick state checks use `agent-composer status --fast` (skips audit /
273
+ job / goal scans). Do NOT pull full status/audit unless debugging
274
+ orchestration.
275
+ 8. Reserve the synchronous fail-closed review gate for commits. If a greenfield
276
+ subsystem oscillates the gate, set
277
+ `codexReview.preCommitHook.maxConsecutiveBlocks` instead of grinding.
278
+
279
+ ## contextBudget (how much context to send)
280
+
281
+ `composer_route_decide` returns a `contextBudget`. Honor it — send ONLY the matching context, NEVER a raw transcript:
282
+
283
+ | contextBudget | Send |
284
+ |---|---|
285
+ | `inline` | the prompt only (or answer inline; no worker) |
286
+ | `handoff` | a `composer_handoff_create` `handoffPath` |
287
+ | `scoped-diff` | a `reviewScope` (let the server compute the diff) |
288
+ | `full-brief` | handoff + the exact files / constraints |
289
+ | `oracle-brief` | a compact problem / architecture brief, never a repo dump |
290
+
291
+ ## Scoped review (do not paste broad diffs)
292
+
293
+ `composer_review` / `composer_review_claude` accept `reviewScope` (`staged` | `unstaged` | `working-tree` | `branch`) plus optional `base` and `reviewFiles`. Pass a scope and let the server compute the diff off-CC — do NOT paste a broad diff into the prompt. Use `staged` for pre-commit review, `working-tree` for routine review, `branch` (+ `base`) for a PR-sized review. A pasted `diff` still works but costs main-session tokens.
294
+
295
+ ## Codex profiles
296
+
297
+ `composer_code_cli` accepts `profile` to select a `codexProfiles` lane from config (model / reasoning effort / sandbox) — e.g. a cheap `fast` lane or a `deep` lane. A session `profile` set via `composer_session_set` applies when no explicit `profile` is passed.
298
+
130
299
  # Explorer protocol (large-repo dispatches)
131
300
 
132
301
  For repos with >500 source files (or any unfamiliar codebase), prefer an
@@ -201,10 +370,12 @@ global stop-gate; it fires on every stop.
201
370
  A stronger, optional enforcement: `codexReview.preCommitHook.enabled` turns the
202
371
  PreToolUse hook `precommit_codex_review.sh` into a hard gate — a `git commit`
203
372
  is DENIED when Codex review returns `needs-attention` with a finding at or above
204
- `preCommitHook.blockOnSeverity` (default `high`). Fail-open by default
205
- (`failClosed:false`): if Codex is unavailable the commit proceeds. Run
206
- `agent-composer doctor` to see the gate state. This is mechanical (hook-enforced),
207
- unlike the orchestrator-driven triggers above.
373
+ `preCommitHook.blockOnSeverity` (default `high`). The default is fail-open
374
+ (`failClosed:false`), but strict local/project configs should set
375
+ `failClosed:true`; then Codex auth, quota, rate-limit, timeout, or parse
376
+ failures block the commit. Run `agent-composer doctor` to see the gate state.
377
+ This is mechanical (hook-enforced), unlike the orchestrator-driven triggers
378
+ above.
208
379
 
209
380
  # Headless invocation
210
381
 
@@ -231,6 +402,21 @@ Rules:
231
402
  - Override when the orchestration plan itself is non-trivial (>3
232
403
  dispatches, cross-file integration). Then Opus 4.7 earns its keep.
233
404
 
405
+ ## Authorizing file-writing workers
406
+
407
+ `boundary_guard.sh` enforces globally now: it denies main-thread
408
+ `Edit/Update/Write/NotebookEdit` in EVERY repo unless Composer is disabled.
409
+ A headless `claude -p` worker you spawn to AUTHOR files (multi-file
410
+ scaffolding, byte-exact reproduction) runs as its own main thread and will be
411
+ blocked. When you spawn such an authorized worker, set the
412
+ `COMPOSER_DANGEROUSLY_BYPASS_PERMISSIONS` env var to `1` in its environment
413
+ so its writes are allowed — prefix the variable on the worker's command line,
414
+ ahead of `claude -p ...` (the boundary_guard deny message prints the exact
415
+ form to use).
416
+ Do NOT export that variable in your own interactive shell — set it only on the
417
+ spawned worker, so the interactive Brain stays gated and routes through
418
+ `composer_code_cli`.
419
+
234
420
  # Token discipline
235
421
 
236
422
  - You hold context, plans, and integration. Workers hold execution.
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Router intended for agent-composer's existing `researcher` cli provider.
5
+ # Composer appends the prompt as the final argv item; this wrapper joins args into one prompt.
6
+
7
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
+ ORACLE_SCRIPT="${ORACLE_COMPOSER_ORACLE_SCRIPT:-$ROOT_DIR/scripts/oracle-pro-safe.sh}"
9
+ CODEX_BIN="${ORACLE_COMPOSER_CODEX_BIN:-codex}"
10
+
11
+ prompt="$*"
12
+ [[ -n "$prompt" ]] || { echo "composer-oracle-router-safe: prompt required" >&2; exit 1; }
13
+
14
+ lower="$(printf '%s' "$prompt" | tr '[:upper:]' '[:lower:]')"
15
+
16
+ route="codex"
17
+ mode="standard"
18
+
19
+ case "$lower" in
20
+ *'[codex]'*) route="codex" ;;
21
+ *'[oracle:quick]'*) route="oracle"; mode="quick" ;;
22
+ *'[oracle:standard]'*) route="oracle"; mode="standard" ;;
23
+ *'[oracle:deep]'*|*'[oracle:plan]'*) route="oracle"; mode="deep" ;;
24
+ *'[oracle:review]'*) route="oracle"; mode="review" ;;
25
+ *'[oracle:debug]'*) route="oracle"; mode="debug" ;;
26
+ *'[oracle:research]'*) route="oracle"; mode="research" ;;
27
+ esac
28
+
29
+ if [[ "$route" != "codex" ]]; then
30
+ if [[ ${#prompt} -gt 2500 ]] || [[ "$lower" =~ (architecture|architectural|design|plan|planning|proposal|migration|refactor|roadmap|tradeoff|trade-off|spec|handoff|implementation[[:space:]]+plan) ]]; then mode="deep"; fi
31
+ if [[ "$lower" =~ (review|audit|regression|security|compatibility|api[[:space:]]+break|edge[[:space:]]+case|risk) ]]; then mode="review"; fi
32
+ if [[ "$lower" =~ (debug|root[ -]?cause|failing|failure|flaky|bug|stack[[:space:]]+trace|exception|crash|deadlock|race) ]]; then mode="debug"; fi
33
+ if [[ "$lower" =~ (research|compare[[:space:]]+options|survey|citations|latest|web) ]]; then mode="research"; fi
34
+ fi
35
+
36
+ if [[ "$route" == "codex" ]]; then
37
+ if ! command -v "$CODEX_BIN" >/dev/null 2>&1; then
38
+ echo "[router] Codex requested but '$CODEX_BIN' not found; falling back to Oracle quick." >&2
39
+ exec "$ORACLE_SCRIPT" --mode quick -- "$prompt"
40
+ fi
41
+ echo "[router] route=codex sandbox=read-only" >&2
42
+ printf "%s\n" "$prompt" | "$CODEX_BIN" exec --ephemeral --sandbox read-only --ask-for-approval never -
43
+ exit $?
44
+ else
45
+ echo "[router] route=oracle mode=$mode" >&2
46
+ exec "$ORACLE_SCRIPT" --mode "$mode" -- "$prompt"
47
+ fi
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+
6
+ const stateDir = process.env.COMPOSER_STATE_DIR?.trim()
7
+ ? path.resolve(process.env.COMPOSER_STATE_DIR)
8
+ : path.join(os.homedir(), ".composer", "state");
9
+ const filePath = path.join(stateDir, "active-runs.json");
10
+
11
+ try {
12
+ if (!fs.existsSync(filePath)) process.exit(0);
13
+ const runs = JSON.parse(fs.readFileSync(filePath, "utf8"));
14
+ if (!Array.isArray(runs) || runs.length === 0) process.exit(0);
15
+
16
+ const now = Date.now();
17
+ const active = runs
18
+ .map((run) => ({
19
+ tool: typeof run.tool === "string" ? run.tool : "",
20
+ providerLabel: typeof run.providerLabel === "string" ? run.providerLabel : undefined,
21
+ providerRole: typeof run.providerRole === "string" ? run.providerRole : undefined,
22
+ startedAtMs: Date.parse(run.startedAt),
23
+ }))
24
+ .filter((run) => run.tool && Number.isFinite(run.startedAtMs))
25
+ .sort((a, b) => a.startedAtMs - b.startedAtMs)[0];
26
+
27
+ if (!active) process.exit(0);
28
+ const tool = active.tool.replace(/^composer_/, "");
29
+ const provider = active.providerLabel ?? active.providerRole;
30
+ const elapsed = formatElapsed(now - active.startedAtMs);
31
+ process.stdout.write(`⚡composer: ${tool}${provider ? `(${provider})` : ""} ${elapsed}\n`);
32
+ } catch {
33
+ process.exit(0);
34
+ }
35
+
36
+ function formatElapsed(elapsedMs) {
37
+ const seconds = Math.max(0, Math.floor(elapsedMs / 1000));
38
+ if (seconds < 60) return `${seconds}s`;
39
+ return `${Math.floor(seconds / 60)}m`;
40
+ }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'USAGE'
6
+ Usage:
7
+ scripts/oracle-codex-handoff-safe.sh [--mode deep|review|debug|standard] [--exec] -- "feature/request"
8
+
9
+ Creates a ChatGPT Pro/Oracle plan under .composer/handoffs/ and optionally sends it to Codex.
10
+ USAGE
11
+ }
12
+
13
+ MODE="deep"
14
+ EXECUTE=0
15
+ PROMPT=""
16
+ while [[ $# -gt 0 ]]; do
17
+ case "$1" in
18
+ --mode) MODE="${2:-}"; shift 2 ;;
19
+ --mode=*) MODE="${1#*=}"; shift ;;
20
+ --exec) EXECUTE=1; shift ;;
21
+ -h|--help) usage; exit 0 ;;
22
+ --) shift; PROMPT="$*"; break ;;
23
+ *) if [[ -z "$PROMPT" ]]; then PROMPT="$1"; else PROMPT="$PROMPT $1"; fi; shift ;;
24
+ esac
25
+ done
26
+ [[ -n "$PROMPT" ]] || { usage >&2; exit 1; }
27
+
28
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
29
+ ORACLE_SCRIPT="$ROOT_DIR/scripts/oracle-pro-safe.sh"
30
+ mkdir -p .composer/handoffs .composer/results
31
+ slug="$(date +%Y%m%d-%H%M%S)-oracle-handoff"
32
+ plan_path=".composer/handoffs/$slug.md"
33
+
34
+ handoff_prompt="Create a compact Codex-ready implementation handoff for the following request. Include objective, constraints, likely files, implementation steps, tests, risks, and acceptance criteria. Do not include long code blocks unless essential. Request: $PROMPT"
35
+
36
+ answer_path="$($ORACLE_SCRIPT --mode "$MODE" --slug "$slug" -- "$handoff_prompt" | tail -1)"
37
+ [[ -f "$answer_path" ]] || { echo "Oracle did not produce an answer path" >&2; exit 1; }
38
+ cp "$answer_path" "$plan_path"
39
+ echo "$plan_path"
40
+
41
+ if [[ "$EXECUTE" -eq 1 ]]; then
42
+ result_path=".composer/results/$slug.codex-result.md"
43
+ {
44
+ echo "Implement the following plan in the current repository. Keep the diff focused. Run relevant tests. Return summary, changed files, tests run, and unresolved risks."
45
+ echo
46
+ cat "$plan_path"
47
+ } | codex exec --sandbox workspace-write --ask-for-approval on-request --output-last-message "$result_path" -
48
+ echo "$result_path"
49
+ fi
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ # oracle-plan-mcp.sh — thin MCP-facing wrapper around oracle-pro-safe.sh.
3
+ #
4
+ # Why: CLIProvider returns a role's STDOUT to the orchestrator. oracle-pro-safe.sh
5
+ # prints the answer FILE PATH on its last stdout line (a contract relied on by
6
+ # oracle-codex-handoff-safe.sh), and oracle's own render/progress is noisy. This
7
+ # wrapper runs oracle-pro-safe.sh, recovers that path, and emits ONLY the final
8
+ # answer file CONTENT on stdout, so composer_oracle_plan returns clean answer text.
9
+ set -euo pipefail
10
+
11
+ MODE="auto"
12
+ ARGS=()
13
+ while [[ $# -gt 0 ]]; do
14
+ case "$1" in
15
+ --mode) MODE="${2:?missing --mode value}"; shift 2 ;;
16
+ --mode=*) MODE="${1#*=}"; shift ;;
17
+ --) shift; ARGS+=("$@"); break ;;
18
+ *) ARGS+=("$1"); shift ;;
19
+ esac
20
+ done
21
+
22
+ PROMPT="${ARGS[*]:-}"
23
+ if [[ -z "$PROMPT" && ! -t 0 ]]; then
24
+ PROMPT="$(cat)"
25
+ fi
26
+ if [[ -z "$PROMPT" ]]; then
27
+ echo "oracle-plan-mcp: prompt required" >&2
28
+ exit 2
29
+ fi
30
+
31
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
32
+ ORACLE_SCRIPT="$ROOT_DIR/scripts/oracle-pro-safe.sh"
33
+
34
+ tmp="$(mktemp)"
35
+ trap 'rm -f "$tmp"' EXIT
36
+
37
+ # Capture oracle-pro-safe stdout (answer render + final path line) into tmp.
38
+ # Its stderr (logs/progress) is inherited -> our stderr -> not returned to MCP.
39
+ set +e
40
+ bash "$ORACLE_SCRIPT" --mode "$MODE" -- "$PROMPT" > "$tmp"
41
+ status=$?
42
+ set -e
43
+
44
+ if (( status != 0 )); then
45
+ echo "oracle-plan-mcp: oracle-pro-safe.sh failed (exit $status); captured output:" >&2
46
+ cat "$tmp" >&2 || true
47
+ exit "$status"
48
+ fi
49
+
50
+ answer_path="$(tail -n 1 "$tmp" | tr -d '\r')"
51
+ if [[ -z "$answer_path" || ! -s "$answer_path" ]]; then
52
+ echo "oracle-plan-mcp: oracle finished but no answer file at '$answer_path'; captured output:" >&2
53
+ cat "$tmp" >&2 || true
54
+ exit 1
55
+ fi
56
+
57
+ # Write a stable sidecar so async callers (composer_oracle_job_start) can
58
+ # recover the full-answer path even though only bounded text is returned.
59
+ meta_dir="$(dirname "$answer_path")"
60
+ slug="$(basename "$answer_path" .md)"
61
+ if [[ -d "$meta_dir" ]] && command -v node >/dev/null 2>&1; then
62
+ node -e 'const fs=require("fs");fs.writeFileSync(process.argv[1],JSON.stringify({answerPath:process.argv[2],oracleSlug:process.argv[3],mode:process.argv[4]})+"\n")' \
63
+ "$meta_dir/.last-plan-meta.json" "$answer_path" "$slug" "$MODE" 2>/dev/null || true
64
+ fi
65
+
66
+ cat "$answer_path"