agent-composer 0.3.0 → 0.4.0

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 (181) 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 +23 -1
  52. package/dist/providers/CLIProvider.js +283 -65
  53. package/dist/providers/CLIProvider.js.map +1 -1
  54. package/dist/providers/IProvider.d.ts +13 -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 -21
  80. package/dist/server.js +90 -330
  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 +430 -62
  175. package/plugin/composer-mastermind/plugin.json +1 -1
  176. package/plugin/composer-mastermind/skills/composer-mastermind/SKILL.md +190 -4
  177. package/scripts/composer-oracle-router-safe.sh +47 -0
  178. package/scripts/composer-statusline-segment.mjs +40 -0
  179. package/scripts/oracle-codex-handoff-safe.sh +49 -0
  180. package/scripts/oracle-plan-mcp.sh +66 -0
  181. package/scripts/oracle-pro-safe.sh +471 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "composer-mastermind",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Multi-agent orchestrator: Claude as brain, GLM/Codex/agy as executors. Dispatches code/research/review work to subagents wired through the @composer-mcp/server MCP server.",
5
5
  "claudeCodeVersion": ">=4.6",
6
6
  "requires": [
@@ -60,8 +60,43 @@ 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
 
66
+ When an edit targets a project other than the Composer MCP server cwd (for
67
+ example dotfiles or another repo), pass `projectDir: "<absolute path>"` to
68
+ `composer_code_cli` or `composer_code_chain`. Codex must trust that directory
69
+ (a git repo or a `config.toml` projects entry) or it will refuse; do not add
70
+ `--skip-git-repo-check`.
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
+ - Requires a one-time Oracle browser login and the `oraclePlanner` role in the
88
+ active config. Full answers persist under `.composer/oracle/answers/`
89
+ (gitignored); the tool returns a bounded summary.
90
+ - For long deep-research, large architectural reviews, or when the user says not
91
+ to block, use the async pair instead: `composer_oracle_job_start` returns a
92
+ `jobId` immediately, then `composer_oracle_job_result` (optionally with
93
+ `waitMs`) pulls the answer when the job reaches `succeeded`. Both run off-CC and
94
+ return bounded summaries.
95
+ - Default to the synchronous `composer_oracle_plan` whenever the next step
96
+ depends on the answer (most planning/review). Reach for the async job pair only
97
+ when the answer is useful but not immediately blocking, or on an explicit
98
+ `[oracle:async]` request. Do not make every deep/review/debug call async.
99
+
65
100
  ## Codex rescue (second-opinion lane)
66
101
 
67
102
  Use Codex rescue when the same bug has 2+ failed fix attempts, root-cause
@@ -81,6 +116,84 @@ or the user asks for a second opinion.
81
116
  latest failing command, changed files, and smallest repro. Do not include the
82
117
  whole transcript, secrets, or `.env.json`.
83
118
 
119
+ ## Codex lifecycle participation
120
+
121
+ Use `codexLifecycle` for ambient Codex involvement across the development
122
+ loop. This is separate from `codexReview` (mechanical diff gate) and
123
+ `codexRescue` (stuck-debug escalation). It answers one question: should Codex
124
+ join this lifecycle step now?
125
+
126
+ - Read root `composer.config.json` `codexLifecycle`.
127
+ - If omitted or `enabled:false`, skip lifecycle participation. Existing
128
+ `composer_code_cli`, `codexReview`, and `codexRescue` rules still apply.
129
+ - For non-trivial feature/debug work, call `composer_handoff_create` first so
130
+ Codex and reviewers share the same objective, constraints, files, and
131
+ acceptance criteria.
132
+ - Call `composer_codex_lifecycle_decide` at eligible points with compact
133
+ signals: `event`, `expectedOutputTokens`, `changedFiles`, `diffLines`,
134
+ `failedAttempts`, `failingTests`, `touchesSecurity`, `touchesInfra`,
135
+ `userRequestedCodex`, `hasHandoff`, `isTrivial`, `isDestructive`, and `risk`.
136
+ - Treat the decision literally:
137
+ - `skip`: do not involve Codex for this lifecycle step.
138
+ - `ask`: ask the user before involving Codex, naming the event and reason.
139
+ - `run`: Codex may participate automatically within the configured mode,
140
+ model, execution, and spend/safety constraints.
141
+ - When the decision is `run`, call `composer_codex_lifecycle_run` with the
142
+ same event, handoff, prompt, and signals.
143
+ - When the decision is `ask` and the user confirms, call
144
+ `composer_codex_lifecycle_run` with `confirmed:true`; do not set
145
+ `confirmed:true` for policy `skip` outcomes.
146
+ - If lifecycle execution is `foreground`, use the returned result before
147
+ continuing.
148
+ - If lifecycle execution is `background`, keep the returned `jobId` and call
149
+ `composer_codex_lifecycle_result` before reporting the lifecycle step done.
150
+ Background Codex output is not complete until its durable result record is
151
+ `succeeded`, `failed`, `unavailable`, or deliberately `skipped`.
152
+ - Treat `skipped` as a policy decision and `unavailable` as a provider/session
153
+ failure (`auth`, `quota`, `rate_limit`, `timeout`, `cancelled`, `provider`,
154
+ or `unknown`). If `codexLifecycle.fallback.enabled`, Composer tries
155
+ `coderCli` first and then configured fallback roles such as `reviewerClaude`,
156
+ `reviewer`, or `coder`; always surface `providerRole`, `fallbackUsed`, and
157
+ the attempts list when a fallback handled the checkpoint. Optional lifecycle
158
+ jobs may continue after surfacing this; forced pre-commit review gates must
159
+ fail closed through `codexReview`.
160
+ - Lifecycle Codex runs are companion/advisory passes. Do not let background
161
+ lifecycle runs silently mutate files; integrate any suggestions deliberately
162
+ and gate code changes through review.
163
+
164
+ ## Composer config tools
165
+
166
+ When the user asks to turn Composer/Codex lifecycle behavior on/off, set
167
+ fallback providers, or harden the pre-commit gate, prefer MCP config tools over
168
+ manual JSON editing:
169
+
170
+ - Call `composer_config_get` first with `scope:"active"` unless the user names
171
+ project or global config explicitly.
172
+ - Call `composer_config_set` with a narrow patch for `codexLifecycle` and/or
173
+ `codexReview`; the server validates before writing.
174
+ - Use `scope:"project"` for repo-local `composer.config.json` and
175
+ `scope:"global"` for `~/.config/composer/composer.config.json`.
176
+ - If `scope:"active"` resolves to the user-global fallback, call
177
+ `composer_config_set` again with `scope:"global"` explicitly; Composer
178
+ refuses implicit global writes.
179
+ - After config changes, run `agent-composer doctor` and report the effective
180
+ lifecycle mode, fallback order, and pre-commit gate state.
181
+
182
+ Default trigger intent:
183
+
184
+ | Event | Use when |
185
+ |---|---|
186
+ | `postPlan` | a plan is ready and design risk is non-trivial |
187
+ | `postCodeApply` | code was applied and the diff is broad or risky |
188
+ | `postTestFailure` | targeted checks fail after implementation |
189
+ | `afterFailedAttempts` | the same bug/fix has failed repeatedly |
190
+ | `postResearch` | research output needs a second synthesis pass |
191
+ | `preCommit` | optional policy layer before the mechanical gate |
192
+ | `stopWarm` | passive background/warm participation is configured |
193
+
194
+ Never use lifecycle participation to bypass user confirmation for destructive
195
+ actions, secret handling, deploys, pushes, or billing-affecting changes.
196
+
84
197
  **Class-based route policy:** route by task class, not by a blanket
85
198
  "always dispatch" rule.
86
199
 
@@ -121,6 +234,62 @@ expected-output estimate; if under 5 lines, just answer.
121
234
 
122
235
  **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.
123
236
 
237
+ # Control plane (status, route, workflow, session, audit)
238
+
239
+ These tools make routing explicit and cheap. Prefer them over re-deriving policy from this skill every turn.
240
+
241
+ - **`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.
242
+ - **`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.
243
+ - **`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.
244
+ - **`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`.
245
+ - **`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.
246
+
247
+ # Subagent speed contract
248
+
249
+ Fast path for quick inspection and minimal wait:
250
+
251
+ 1. Call `composer_route_decide` with `format:"compact"` — local, no model.
252
+ Do NOT request `format:"full"` unless debugging the classifier.
253
+ 2. Honor returned `contextBudget` (`inline` / `handoff` / `scoped-diff` /
254
+ `full-brief` / `oracle-brief`). Send only matching context, NEVER a raw
255
+ transcript.
256
+ 3. Prefer `composer_code_cli` for implementation; the executor applies off-CC.
257
+ 4. Use `reviewScope` (`staged` / `working-tree` / `branch`). NEVER paste broad
258
+ diffs.
259
+ 5. Background, don't block: use `composer_review_job_start` +
260
+ `composer_review_job_result` for routine/advisory review while continuing
261
+ work. Reserve synchronous `composer_review` for the pre-commit / merge gate
262
+ or explicit "review now". Likewise prefer background lifecycle
263
+ (`execution:"background"`).
264
+ 6. NEVER call Oracle unless explicitly tagged `[oracle:<mode>]` or the route
265
+ says premium escalation.
266
+ 7. For quick state checks use `agent-composer status --fast` (skips audit /
267
+ job / goal scans). Do NOT pull full status/audit unless debugging
268
+ orchestration.
269
+ 8. Reserve the synchronous fail-closed review gate for commits. If a greenfield
270
+ subsystem oscillates the gate, set
271
+ `codexReview.preCommitHook.maxConsecutiveBlocks` instead of grinding.
272
+
273
+ ## contextBudget (how much context to send)
274
+
275
+ `composer_route_decide` returns a `contextBudget`. Honor it — send ONLY the matching context, NEVER a raw transcript:
276
+
277
+ | contextBudget | Send |
278
+ |---|---|
279
+ | `inline` | the prompt only (or answer inline; no worker) |
280
+ | `handoff` | a `composer_handoff_create` `handoffPath` |
281
+ | `scoped-diff` | a `reviewScope` (let the server compute the diff) |
282
+ | `full-brief` | handoff + the exact files / constraints |
283
+ | `oracle-brief` | a compact problem / architecture brief, never a repo dump |
284
+
285
+ ## Scoped review (do not paste broad diffs)
286
+
287
+ `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.
288
+
289
+ ## Codex profiles
290
+
291
+ `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.
292
+
124
293
  # Explorer protocol (large-repo dispatches)
125
294
 
126
295
  For repos with >500 source files (or any unfamiliar codebase), prefer an
@@ -195,10 +364,12 @@ global stop-gate; it fires on every stop.
195
364
  A stronger, optional enforcement: `codexReview.preCommitHook.enabled` turns the
196
365
  PreToolUse hook `precommit_codex_review.sh` into a hard gate — a `git commit`
197
366
  is DENIED when Codex review returns `needs-attention` with a finding at or above
198
- `preCommitHook.blockOnSeverity` (default `high`). Fail-open by default
199
- (`failClosed:false`): if Codex is unavailable the commit proceeds. Run
200
- `agent-composer doctor` to see the gate state. This is mechanical (hook-enforced),
201
- unlike the orchestrator-driven triggers above.
367
+ `preCommitHook.blockOnSeverity` (default `high`). The default is fail-open
368
+ (`failClosed:false`), but strict local/project configs should set
369
+ `failClosed:true`; then Codex auth, quota, rate-limit, timeout, or parse
370
+ failures block the commit. Run `agent-composer doctor` to see the gate state.
371
+ This is mechanical (hook-enforced), unlike the orchestrator-driven triggers
372
+ above.
202
373
 
203
374
  # Headless invocation
204
375
 
@@ -225,6 +396,21 @@ Rules:
225
396
  - Override when the orchestration plan itself is non-trivial (>3
226
397
  dispatches, cross-file integration). Then Opus 4.7 earns its keep.
227
398
 
399
+ ## Authorizing file-writing workers
400
+
401
+ `boundary_guard.sh` enforces globally now: it denies main-thread
402
+ `Edit/Update/Write/NotebookEdit` in EVERY repo unless Composer is disabled.
403
+ A headless `claude -p` worker you spawn to AUTHOR files (multi-file
404
+ scaffolding, byte-exact reproduction) runs as its own main thread and will be
405
+ blocked. When you spawn such an authorized worker, set the
406
+ `COMPOSER_DANGEROUSLY_BYPASS_PERMISSIONS` env var to `1` in its environment
407
+ so its writes are allowed — prefix the variable on the worker's command line,
408
+ ahead of `claude -p ...` (the boundary_guard deny message prints the exact
409
+ form to use).
410
+ Do NOT export that variable in your own interactive shell — set it only on the
411
+ spawned worker, so the interactive Brain stays gated and routes through
412
+ `composer_code_cli`.
413
+
228
414
  # Token discipline
229
415
 
230
416
  - 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"