agent-composer 0.4.0 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-composer",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "Multi-agent orchestration MCP server. Claude orchestrates; GLM, Codex, and agy do the work.",
6
6
  "bin": {
@@ -151,7 +151,7 @@ case "$TOOL" in
151
151
  Edit|Update|Write|NotebookEdit \
152
152
  | mcp__*__write_file | mcp__*__edit_file | mcp__*__bash \
153
153
  | mcp__*__write | mcp__*__edit | mcp__*__exec)
154
- emit_deny "Composer is handling file edits. Route this change through composer_code_cli (or composer_code_chain), or run /composer disable to edit directly. Bash inspection stays available."
154
+ emit_deny "Expected not a failure. Composer is routing this edit so changes stay reviewable: apply it with composer_code_cli (or composer_code_chain), or run /composer disable to edit directly. Bash inspection stays available."
155
155
  ;;
156
156
  esac
157
157
 
@@ -4,8 +4,10 @@
4
4
  # codexReview.preCommitHook.enabled are true, and the Codex review verdict
5
5
  # reaches the configured blockOnSeverity threshold.
6
6
  #
7
- # Fail-open by default: reviewer/JQ/plugin/timeout failures warn to stderr and
8
- # allow the commit unless codexReview.preCommitHook.failClosed is true.
7
+ # Fail-open by default: reviewer/JQ/plugin failures warn to stderr and allow the
8
+ # commit unless codexReview.preCommitHook.failClosed is true. EXCEPTION: a review
9
+ # timeout/hang ALWAYS fails closed (blocks the commit) so a slow or hung reviewer
10
+ # cannot bypass the gate; raise preCommitHook.timeoutMs if reviews need more time.
9
11
  # Config keys:
10
12
  # codexReview.preCommitCommand, scope, base, model
11
13
  # codexReview.preCommitHook.enabled, blockOnSeverity, timeoutMs, failClosed, maxConsecutiveBlocks
@@ -896,6 +898,10 @@ END_SECONDS="$(date +%s)"
896
898
  DURATION_MS=$(( (END_SECONDS - START_SECONDS) * 1000 ))
897
899
 
898
900
  if [[ "$REVIEW_STATUS" -eq 124 ]]; then
901
+ # A reviewer timeout/hang must NOT silently open the gate — otherwise a slow or
902
+ # hung reviewer could bypass review entirely. The timeout path always fails
903
+ # closed, regardless of the configured failClosed. If legitimate reviews need
904
+ # more time, raise codexReview.preCommitHook.timeoutMs instead.
899
905
  fail_review "true" "review timed out after ${TIMEOUT_SECONDS}s" "$DURATION_MS" "hook_timeout"
900
906
  fi
901
907
  if [[ "$REVIEW_STATUS" -ne 0 ]]; then
@@ -84,6 +84,12 @@ lookups fast and cheap.
84
84
  Research. Omit (`auto`) to let the script classify.
85
85
  - It is ADVISORY: the tool never edits files. Hand its plan to
86
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.
87
93
  - Requires a one-time Oracle browser login and the `oraclePlanner` role in the
88
94
  active config. Full answers persist under `.composer/oracle/answers/`
89
95
  (gitignored); the tool returns a bounded summary.
@@ -22,6 +22,7 @@ Options:
22
22
  --no-manual-login Do not pass --browser-manual-login even if supported
23
23
  --model <model> Override selected Oracle model
24
24
  --thinking <level> Override browser thinking level: light|standard|extended|heavy
25
+ --base <ref> Override branch-diff base ref for review-class modes
25
26
  --research deep|off Override browser research mode
26
27
  -p, --prompt <prompt> Prompt text
27
28
  -h, --help
@@ -42,6 +43,9 @@ Environment overrides:
42
43
  ORACLE_PRO_REATTACH_INTERVAL default: 2m
43
44
  ORACLE_PRO_REATTACH_TIMEOUT default: 2m
44
45
  ORACLE_PRO_ATTACHMENTS default: never (inline files; set to auto/bundle for large files)
46
+ ORACLE_PRO_DIFF_BASE default: auto-detect main/master/develop/origin HEAD
47
+ ORACLE_PRO_MAX_CHANGED_FILES default: 40
48
+ ORACLE_PRO_MAX_CHANGED_FILE_BYTES default: 120000
45
49
 
46
50
  Secret file protection:
47
51
  --file paths matching known secret patterns (.env, *.pem, *.key, id_rsa, .aws/credentials,
@@ -113,6 +117,7 @@ MODEL_OVERRIDE=""
113
117
  THINKING_OVERRIDE=""
114
118
  RESEARCH_OVERRIDE=""
115
119
  FILES=()
120
+ DIFF_BASE="${ORACLE_PRO_DIFF_BASE:-}"
116
121
 
117
122
  while [[ $# -gt 0 ]]; do
118
123
  case "$1" in
@@ -130,6 +135,8 @@ while [[ $# -gt 0 ]]; do
130
135
  --model=*) MODEL_OVERRIDE="${1#*=}"; shift ;;
131
136
  --thinking) THINKING_OVERRIDE="${2:-}"; shift 2 ;;
132
137
  --thinking=*) THINKING_OVERRIDE="${1#*=}"; shift ;;
138
+ --base) DIFF_BASE="${2:-}"; shift 2 ;;
139
+ --base=*) DIFF_BASE="${1#*=}"; shift ;;
133
140
  --research) RESEARCH_OVERRIDE="${2:-}"; shift 2 ;;
134
141
  --research=*) RESEARCH_OVERRIDE="${1#*=}"; shift ;;
135
142
  -p|--prompt) PROMPT="${2:-}"; shift 2 ;;
@@ -185,7 +192,6 @@ STANDARD_THINKING="${ORACLE_PRO_STANDARD_THINKING:-standard}"
185
192
  DEEP_THINKING="${ORACLE_PRO_DEEP_THINKING:-extended}"
186
193
  RESEARCH_MODE="off"
187
194
  DEFAULT_STRATEGY="select"
188
- ATTACHMENTS_MODE="${ORACLE_PRO_ATTACHMENTS:-never}"
189
195
 
190
196
  case "$MODE" in
191
197
  quick) MODEL="$QUICK_MODEL"; THINKING="$QUICK_THINKING"; DEFAULT_STRATEGY="current" ;;
@@ -198,6 +204,30 @@ esac
198
204
  [[ -z "$THINKING_OVERRIDE" ]] || THINKING="$THINKING_OVERRIDE"
199
205
  [[ -z "$RESEARCH_OVERRIDE" ]] || RESEARCH_MODE="$RESEARCH_OVERRIDE"
200
206
 
207
+ # Delivery: quick/standard stay inline (fast, no upload-timeout risk); review-class
208
+ # modes default to `auto` (inline up to oracle's ~60k-char limit, then upload) so large
209
+ # branch diffs are no longer silently truncated. Override with ORACLE_PRO_ATTACHMENTS.
210
+ ATTACHMENTS_MODE="${ORACLE_PRO_ATTACHMENTS:-}"
211
+ if [[ -z "$ATTACHMENTS_MODE" ]]; then
212
+ case "$MODE" in
213
+ quick|standard) ATTACHMENTS_MODE="never" ;;
214
+ *) ATTACHMENTS_MODE="auto" ;;
215
+ esac
216
+ fi
217
+
218
+ # ChatGPT's Pro model auto-selects "Pro Extended"; its picker no longer exposes a
219
+ # separate thinking-time submenu, so --browser-thinking-time errors out with
220
+ # "Thinking time: menu not found for pro (requested ...)" and oracle exits 1.
221
+ # The flag is redundant for the Pro model, so skip it there. Override with
222
+ # ORACLE_PRO_FORCE_THINKING_FLAG=1 if a future oracle/UI restores the menu.
223
+ case "$MODEL" in
224
+ *pro*)
225
+ if [[ "${ORACLE_PRO_FORCE_THINKING_FLAG:-0}" != "1" ]]; then
226
+ USE_THINKING_FLAG=0
227
+ fi
228
+ ;;
229
+ esac
230
+
201
231
  OUT_DIR="${ORACLE_PRO_OUTPUT_DIR:-.composer/oracle/answers}"
202
232
  CTX_DIR="${ORACLE_PRO_CONTEXT_DIR:-.composer/oracle/context}"
203
233
  mkdir -p "$OUT_DIR" "$CTX_DIR"
@@ -288,6 +318,31 @@ write_context_file() {
288
318
  if [[ -s "$path" ]]; then AUTO_FILES+=("$path"); fi
289
319
  }
290
320
 
321
+ detect_diff_base() {
322
+ local b
323
+ if [[ -n "$DIFF_BASE" ]]; then printf '%s' "$DIFF_BASE"; return; fi
324
+ for b in main master develop; do
325
+ git rev-parse --verify --quiet "refs/heads/$b" >/dev/null 2>&1 && { printf '%s' "$b"; return; }
326
+ done
327
+ for b in origin/main origin/master origin/develop; do
328
+ git rev-parse --verify --quiet "refs/remotes/$b" >/dev/null 2>&1 && { printf '%s' "$b"; return; }
329
+ done
330
+ git symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's#refs/remotes/##' || true
331
+ }
332
+
333
+ # Populates the global SECRET_EXCLUDES array with :(exclude,literal)<path> pathspecs
334
+ # for every changed file that is_secret_file rejects, giving git-diff patch generation
335
+ # the SAME denylist coverage as explicit --file uploads. Args: optional git diff range.
336
+ build_secret_excludes() {
337
+ SECRET_EXCLUDES=()
338
+ [[ "${ORACLE_PRO_ALLOW_SECRET_FILES:-0}" == "1" ]] && return 0
339
+ local cf
340
+ while IFS= read -r cf; do
341
+ [[ -n "$cf" ]] || continue
342
+ if is_secret_file "$cf"; then SECRET_EXCLUDES+=(":(exclude,literal)$cf"); fi
343
+ done < <(git diff --name-only "$@" 2>/dev/null)
344
+ }
345
+
291
346
  if [[ "$AUTO_CONTEXT" -eq 1 ]]; then
292
347
  # Authority class B — current policy/context (safe as source-of-truth).
293
348
  policy_files=(CLAUDE.md composer.config.json docs/STATUS.md)
@@ -318,12 +373,52 @@ if [[ "$AUTO_CONTEXT" -eq 1 ]]; then
318
373
  write_context_file "git-status.txt" "git status --short"
319
374
  write_context_file "git-diff-stat.txt" "git diff --stat"
320
375
  if [[ "$MODE" != "quick" && "$MODE" != "standard" ]]; then
321
- write_context_file "git-diff.patch" "git diff -- . ':(exclude).env' ':(exclude).env.*' ':(exclude)*.pem' ':(exclude)*.key' ':(exclude)*.p12' | sed -E -e '/(api[_-]?key|secret|token|passwd|password|credential|authorization|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key)[^a-z0-9]{0,4}[:=]/Id' -e '/bearer[[:space:]]+[a-z0-9._-]{6,}/Id' | head -c 200000"
376
+ build_secret_excludes
377
+ wt_patch="$CTX_DIR/$SLUG.git-diff.patch"
378
+ git diff -- . ':(exclude).env' ':(exclude).env.*' ':(exclude)*.pem' ':(exclude)*.key' ':(exclude)*.p12' \
379
+ ${SECRET_EXCLUDES[@]+"${SECRET_EXCLUDES[@]}"} 2>/dev/null \
380
+ | sed -E -e '/(api[_-]?key|secret|token|passwd|password|credential|authorization|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key)[^a-z0-9]{0,4}[:=]/Id' -e '/bearer[[:space:]]+[a-z0-9._-]{6,}/Id' \
381
+ | head -c 200000 > "$wt_patch" 2>/dev/null || true
382
+ [[ -s "$wt_patch" ]] && AUTO_FILES+=("$wt_patch")
322
383
  fi
323
384
  # Exact installed top-level deps (package.json shows ranges, not the installed tree).
324
385
  if [[ "$MODE" != "quick" && "$MODE" != "standard" ]] && [[ -f package.json ]]; then
325
386
  write_context_file "deps.txt" "npm ls --depth=0 2>/dev/null || true"
326
387
  fi
388
+ if [[ "$MODE" != "quick" && "$MODE" != "standard" ]]; then
389
+ DIFF_BASE_RESOLVED="$(detect_diff_base)"
390
+ # Defense-in-depth: a git ref can legally contain shell metacharacters
391
+ # (`;`, `$()`, backticks). Since the ref is later interpolated into commands,
392
+ # reject anything outside a safe charset and forbid a leading dash (git option
393
+ # injection) before use.
394
+ if [[ -n "$DIFF_BASE_RESOLVED" && ! "$DIFF_BASE_RESOLVED" =~ ^[A-Za-z0-9._/][A-Za-z0-9._/-]*$ ]]; then
395
+ warn "ignoring unsafe diff-base ref (disallowed characters): $DIFF_BASE_RESOLVED"
396
+ DIFF_BASE_RESOLVED=""
397
+ fi
398
+ CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
399
+ if [[ -n "$DIFF_BASE_RESOLVED" && "$CUR_BRANCH" != "$DIFF_BASE_RESOLVED" ]] \
400
+ && git rev-parse --verify --quiet "$DIFF_BASE_RESOLVED" >/dev/null 2>&1; then
401
+ # The committed branch diff vs its base is the REAL review target (working-tree diff
402
+ # is empty once work is committed). This is the primary accuracy fix.
403
+ build_secret_excludes "$DIFF_BASE_RESOLVED...HEAD"
404
+ base_patch="$CTX_DIR/$SLUG.branch-diff.patch"
405
+ git diff "$DIFF_BASE_RESOLVED...HEAD" -- . ':(exclude).env' ':(exclude).env.*' ':(exclude)*.pem' ':(exclude)*.key' ':(exclude)*.p12' \
406
+ ${SECRET_EXCLUDES[@]+"${SECRET_EXCLUDES[@]}"} 2>/dev/null \
407
+ | sed -E -e '/(api[_-]?key|secret|token|passwd|password|credential|authorization|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key)[^a-z0-9]{0,4}[:=]/Id' -e '/bearer[[:space:]]+[a-z0-9._-]{6,}/Id' \
408
+ | head -c 400000 > "$base_patch" 2>/dev/null || true
409
+ [[ -s "$base_patch" ]] && AUTO_FILES+=("$base_patch")
410
+ write_context_file "branch-diff-stat.txt" "git diff ${DIFF_BASE_RESOLVED}...HEAD --stat"
411
+ # Attach the full content of changed files (enclosing scope for the diff hunks),
412
+ # capped by count and per-file size, secret-filtered.
413
+ while IFS= read -r changed; do
414
+ [[ -n "$changed" && -f "$changed" ]] || continue
415
+ if is_secret_file "$changed" && [[ "${ORACLE_PRO_ALLOW_SECRET_FILES:-0}" != "1" ]]; then continue; fi
416
+ csz="$(wc -c < "$changed" 2>/dev/null | tr -d ' ')"
417
+ [[ -n "$csz" && "$csz" -le "${ORACLE_PRO_MAX_CHANGED_FILE_BYTES:-120000}" ]] || continue
418
+ AUTO_FILES+=("$changed")
419
+ done < <(git diff --name-only "${DIFF_BASE_RESOLVED}...HEAD" -- . ':(exclude).env' ':(exclude).env.*' 2>/dev/null | head -n "${ORACLE_PRO_MAX_CHANGED_FILES:-40}")
420
+ fi
421
+ fi
327
422
  fi
328
423
  fi
329
424
 
@@ -351,6 +446,9 @@ fi
351
446
  add_supported_flag ARGS --browser-timeout "${ORACLE_PRO_TIMEOUT:-20m}"
352
447
  add_supported_flag ARGS --browser-input-timeout "${ORACLE_PRO_INPUT_TIMEOUT:-60s}"
353
448
  add_supported_flag ARGS --browser-attachments "$ATTACHMENTS_MODE"
449
+ if [[ "$ATTACHMENTS_MODE" != "never" ]]; then
450
+ add_supported_flag ARGS --browser-bundle-format "${ORACLE_PRO_BUNDLE_FORMAT:-text}"
451
+ fi
354
452
  add_supported_flag ARGS --browser-auto-reattach-delay "${ORACLE_PRO_REATTACH_DELAY:-30s}"
355
453
  add_supported_flag ARGS --browser-auto-reattach-interval "${ORACLE_PRO_REATTACH_INTERVAL:-2m}"
356
454
  add_supported_flag ARGS --browser-auto-reattach-timeout "${ORACLE_PRO_REATTACH_TIMEOUT:-2m}"
@@ -437,14 +535,17 @@ Repo-state hash: ${repo_state_hash}
437
535
  Runtime: node=${node_ver} npm=${npm_ver} os=${os_ver} arch=${arch_ver}
438
536
  Task: ${MODE}
439
537
  Authority order:
440
- A. Live source-of-truth: ${SLUG}.manifest.json, ${SLUG}.git-status.txt, ${SLUG}.git-diff.patch, ${SLUG}.deps.txt, targeted source/tests
538
+ A. Live source-of-truth: ${SLUG}.manifest.json, ${SLUG}.git-status.txt, ${SLUG}.git-diff.patch, ${SLUG}.branch-diff.patch, ${SLUG}.deps.txt, changed source files, targeted source/tests
441
539
  B. Current policy/context: CLAUDE.md, composer.config.json, docs/STATUS.md, relevant ADRs
442
540
  C. Background/history: README.md, AGENTS.md
443
541
  Rules:
444
542
  - Treat class A as authoritative for the local repo. If A conflicts with B or C, A wins.
543
+ - The attached files are the ONLY authoritative source for any code-level claim. Any description of the code in the TASK below is a hint about what to review, NOT source of truth — never treat prose as code.
445
544
  - Ignore prior chat memory, project memory, and earlier attachments if they conflict with this snapshot.
446
545
  - For each substantive claim, tag it [attached], [runtime], [web], or [inference].
447
546
  - Cite attached claims with file path and line span.
547
+ - For EACH finding: first quote the exact supporting line(s) verbatim from an attached file as \`path:line\`, THEN state the finding. If you cannot quote supporting lines from the attached files, label it "INSUFFICIENT EVIDENCE — not in provided context" and do not raise it as a blocker.
548
+ - Do not infer a bug from an absent detail; absence of code in the attachments means unknown, not broken.
448
549
  - For current API/library claims not proven by attached files, verify on the web against primary docs.
449
550
  - If evidence is insufficient, say: "unknown from provided context".
450
551
  EOF