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.
- package/README.md +495 -180
- package/composer.config.schema.json +206 -2
- package/dist/cli/cleanup.d.ts +24 -0
- package/dist/cli/cleanup.js +151 -0
- package/dist/cli/cleanup.js.map +1 -0
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +244 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/goal.d.ts +28 -0
- package/dist/cli/goal.js +251 -0
- package/dist/cli/goal.js.map +1 -0
- package/dist/cli/help.d.ts +3 -0
- package/dist/cli/help.js +31 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/init.d.ts +5 -0
- package/dist/cli/init.js +116 -21
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/initArgs.d.ts +16 -0
- package/dist/cli/initArgs.js +19 -0
- package/dist/cli/initArgs.js.map +1 -0
- package/dist/cli/installGitHook.d.ts +7 -0
- package/dist/cli/installGitHook.js +61 -0
- package/dist/cli/installGitHook.js.map +1 -0
- package/dist/cli/mode.d.ts +6 -0
- package/dist/cli/mode.js +25 -0
- package/dist/cli/mode.js.map +1 -0
- package/dist/cli/status.d.ts +105 -0
- package/dist/cli/status.js +400 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/config/env.d.ts +1 -1
- package/dist/config/modes.d.ts +10 -0
- package/dist/config/modes.js +26 -0
- package/dist/config/modes.js.map +1 -0
- package/dist/config/oracleRole.d.ts +10 -0
- package/dist/config/oracleRole.js +11 -0
- package/dist/config/oracleRole.js.map +1 -0
- package/dist/config/schema.d.ts +246 -0
- package/dist/config/schema.js +127 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/evolve/reflection.d.ts +9 -0
- package/dist/evolve/reflection.js +14 -0
- package/dist/evolve/reflection.js.map +1 -1
- package/dist/evolve/runner.d.ts +2 -1
- package/dist/evolve/runner.js +2 -1
- package/dist/evolve/runner.js.map +1 -1
- package/dist/index.js +115 -6
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicCompatibleProvider.d.ts +13 -1
- package/dist/providers/AnthropicCompatibleProvider.js +115 -9
- package/dist/providers/AnthropicCompatibleProvider.js.map +1 -1
- package/dist/providers/CLIProvider.d.ts +18 -0
- package/dist/providers/CLIProvider.js +265 -62
- package/dist/providers/CLIProvider.js.map +1 -1
- package/dist/providers/IProvider.d.ts +12 -0
- package/dist/providers/SpendGuardProvider.d.ts +32 -0
- package/dist/providers/SpendGuardProvider.js +98 -0
- package/dist/providers/SpendGuardProvider.js.map +1 -0
- package/dist/registry.d.ts +5 -2
- package/dist/registry.js +17 -2
- package/dist/registry.js.map +1 -1
- package/dist/server/activeRuns.d.ts +17 -0
- package/dist/server/activeRuns.js +114 -0
- package/dist/server/activeRuns.js.map +1 -0
- package/dist/server/codexLifecycleRunner.d.ts +29 -0
- package/dist/server/codexLifecycleRunner.js +188 -0
- package/dist/server/codexLifecycleRunner.js.map +1 -0
- package/dist/server/configMutation.d.ts +22 -0
- package/dist/server/configMutation.js +121 -0
- package/dist/server/configMutation.js.map +1 -0
- package/dist/server/handoffContext.d.ts +1 -0
- package/dist/server/handoffContext.js +12 -0
- package/dist/server/handoffContext.js.map +1 -0
- package/dist/server/progress.d.ts +24 -0
- package/dist/server/progress.js +109 -0
- package/dist/server/progress.js.map +1 -0
- package/dist/server/toolDescriptions.d.ts +60 -0
- package/dist/server/toolDescriptions.js +134 -0
- package/dist/server/toolDescriptions.js.map +1 -0
- package/dist/server.d.ts +19 -25
- package/dist/server.js +87 -377
- package/dist/server.js.map +1 -1
- package/dist/tools/audit.d.ts +2 -0
- package/dist/tools/audit.js +66 -0
- package/dist/tools/audit.js.map +1 -0
- package/dist/tools/code.d.ts +2 -0
- package/dist/tools/code.js +160 -0
- package/dist/tools/code.js.map +1 -0
- package/dist/tools/codexLifecycle.d.ts +2 -0
- package/dist/tools/codexLifecycle.js +206 -0
- package/dist/tools/codexLifecycle.js.map +1 -0
- package/dist/tools/config.d.ts +2 -0
- package/dist/tools/config.js +183 -0
- package/dist/tools/config.js.map +1 -0
- package/dist/tools/context.d.ts +31 -0
- package/dist/tools/context.js +2 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/goal.d.ts +2 -0
- package/dist/tools/goal.js +159 -0
- package/dist/tools/goal.js.map +1 -0
- package/dist/tools/handoff.d.ts +2 -0
- package/dist/tools/handoff.js +57 -0
- package/dist/tools/handoff.js.map +1 -0
- package/dist/tools/oracle.d.ts +2 -0
- package/dist/tools/oracle.js +248 -0
- package/dist/tools/oracle.js.map +1 -0
- package/dist/tools/research.d.ts +2 -0
- package/dist/tools/research.js +51 -0
- package/dist/tools/research.js.map +1 -0
- package/dist/tools/review.d.ts +2 -0
- package/dist/tools/review.js +233 -0
- package/dist/tools/review.js.map +1 -0
- package/dist/tools/route.d.ts +2 -0
- package/dist/tools/route.js +69 -0
- package/dist/tools/route.js.map +1 -0
- package/dist/tools/session.d.ts +2 -0
- package/dist/tools/session.js +37 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/status.d.ts +2 -0
- package/dist/tools/status.js +34 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/workflow.d.ts +2 -0
- package/dist/tools/workflow.js +27 -0
- package/dist/tools/workflow.js.map +1 -0
- package/dist/util/applyFileBlocks.d.ts +18 -0
- package/dist/util/applyFileBlocks.js +163 -0
- package/dist/util/applyFileBlocks.js.map +1 -0
- package/dist/util/asyncControl.d.ts +14 -0
- package/dist/util/asyncControl.js +106 -0
- package/dist/util/asyncControl.js.map +1 -0
- package/dist/util/auditLog.d.ts +56 -0
- package/dist/util/auditLog.js +232 -0
- package/dist/util/auditLog.js.map +1 -0
- package/dist/util/codexLifecycle.d.ts +55 -0
- package/dist/util/codexLifecycle.js +102 -0
- package/dist/util/codexLifecycle.js.map +1 -0
- package/dist/util/codexLifecycleJob.d.ts +209 -0
- package/dist/util/codexLifecycleJob.js +360 -0
- package/dist/util/codexLifecycleJob.js.map +1 -0
- package/dist/util/composerDisabled.d.ts +6 -0
- package/dist/util/composerDisabled.js +27 -0
- package/dist/util/composerDisabled.js.map +1 -0
- package/dist/util/dispatchHint.d.ts +5 -3
- package/dist/util/dispatchHint.js +62 -2
- package/dist/util/dispatchHint.js.map +1 -1
- package/dist/util/goal.d.ts +132 -0
- package/dist/util/goal.js +616 -0
- package/dist/util/goal.js.map +1 -0
- package/dist/util/goalReport.d.ts +51 -0
- package/dist/util/goalReport.js +164 -0
- package/dist/util/goalReport.js.map +1 -0
- package/dist/util/jobPolling.d.ts +9 -0
- package/dist/util/jobPolling.js +17 -0
- package/dist/util/jobPolling.js.map +1 -0
- package/dist/util/oracleJob.d.ts +66 -0
- package/dist/util/oracleJob.js +295 -0
- package/dist/util/oracleJob.js.map +1 -0
- package/dist/util/oracleLock.d.ts +38 -0
- package/dist/util/oracleLock.js +182 -0
- package/dist/util/oracleLock.js.map +1 -0
- package/dist/util/reviewDiff.d.ts +8 -0
- package/dist/util/reviewDiff.js +29 -0
- package/dist/util/reviewDiff.js.map +1 -0
- package/dist/util/reviewJob.d.ts +57 -0
- package/dist/util/reviewJob.js +207 -0
- package/dist/util/reviewJob.js.map +1 -0
- package/dist/util/workflowPlan.d.ts +24 -0
- package/dist/util/workflowPlan.js +49 -0
- package/dist/util/workflowPlan.js.map +1 -0
- package/package.json +8 -1
- package/plugin/composer-mastermind/commands/evolve.md +4 -0
- package/plugin/composer-mastermind/hooks/boundary_guard.sh +43 -2
- package/plugin/composer-mastermind/hooks/codex_warm_review.sh +161 -9
- package/plugin/composer-mastermind/hooks/learn.sh +172 -32
- package/plugin/composer-mastermind/hooks/precommit_codex_review.sh +438 -64
- package/plugin/composer-mastermind/skills/composer-mastermind/SKILL.md +190 -4
- package/scripts/composer-oracle-router-safe.sh +47 -0
- package/scripts/composer-statusline-segment.mjs +40 -0
- package/scripts/oracle-codex-handoff-safe.sh +49 -0
- package/scripts/oracle-plan-mcp.sh +66 -0
- package/scripts/oracle-pro-safe.sh +572 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# oracle-pro-safe.sh
|
|
5
|
+
# Safe ChatGPT Pro browser adapter for steipete/oracle.
|
|
6
|
+
# Key design: never assume optional/hidden Oracle flags. Probe them with a dry-run first,
|
|
7
|
+
# then include only flags accepted by the installed local binary.
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
cat <<'USAGE'
|
|
11
|
+
Usage:
|
|
12
|
+
scripts/oracle-pro-safe.sh [options] -- "prompt"
|
|
13
|
+
scripts/oracle-pro-safe.sh [options] -p "prompt"
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--mode <auto|quick|standard|deep|plan|review|debug|research>
|
|
17
|
+
--file <path-or-glob> Repeatable; passed to oracle --file
|
|
18
|
+
--slug <slug> Output slug prefix
|
|
19
|
+
--dry-run Run Oracle dry-run/preview only
|
|
20
|
+
--no-context Do not auto-attach lightweight repo context
|
|
21
|
+
--no-thinking-flag Do not pass --browser-thinking-time even if supported
|
|
22
|
+
--no-manual-login Do not pass --browser-manual-login even if supported
|
|
23
|
+
--model <model> Override selected Oracle model
|
|
24
|
+
--thinking <level> Override browser thinking level: light|standard|extended|heavy
|
|
25
|
+
--base <ref> Override branch-diff base ref for review-class modes
|
|
26
|
+
--research deep|off Override browser research mode
|
|
27
|
+
-p, --prompt <prompt> Prompt text
|
|
28
|
+
-h, --help
|
|
29
|
+
|
|
30
|
+
Environment overrides:
|
|
31
|
+
ORACLE_PRO_QUICK_MODEL default: gpt-5.2-instant
|
|
32
|
+
ORACLE_PRO_STANDARD_MODEL default: gpt-5.5
|
|
33
|
+
ORACLE_PRO_DEEP_MODEL default: gpt-5.5-pro
|
|
34
|
+
ORACLE_PRO_QUICK_THINKING default: light
|
|
35
|
+
ORACLE_PRO_STANDARD_THINKING default: standard
|
|
36
|
+
ORACLE_PRO_DEEP_THINKING default: extended
|
|
37
|
+
ORACLE_PRO_BROWSER_STRATEGY default: select
|
|
38
|
+
ORACLE_PRO_OUTPUT_DIR default: .composer/oracle/answers
|
|
39
|
+
ORACLE_PRO_CONTEXT_DIR default: .composer/oracle/context
|
|
40
|
+
ORACLE_PRO_TIMEOUT default: 20m
|
|
41
|
+
ORACLE_PRO_INPUT_TIMEOUT default: 60s
|
|
42
|
+
ORACLE_PRO_REATTACH_DELAY default: 30s
|
|
43
|
+
ORACLE_PRO_REATTACH_INTERVAL default: 2m
|
|
44
|
+
ORACLE_PRO_REATTACH_TIMEOUT default: 2m
|
|
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
|
|
49
|
+
|
|
50
|
+
Secret file protection:
|
|
51
|
+
--file paths matching known secret patterns (.env, *.pem, *.key, id_rsa, .aws/credentials,
|
|
52
|
+
*secret*, *token*, *credential*, *password*, etc.) are rejected before upload.
|
|
53
|
+
Set ORACLE_PRO_ALLOW_SECRET_FILES=1 to override (use with caution).
|
|
54
|
+
USAGE
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
log() { printf '[oracle-pro] %s\n' "$*" >&2; }
|
|
58
|
+
warn() { printf '[oracle-pro][warn] %s\n' "$*" >&2; }
|
|
59
|
+
die() { printf '[oracle-pro][error] %s\n' "$*" >&2; exit 1; }
|
|
60
|
+
|
|
61
|
+
node_major() {
|
|
62
|
+
{ "$1" --version 2>/dev/null || true; } | sed -E 's/^v?([0-9]+).*/\1/'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
is_bad_node_major() {
|
|
66
|
+
# Keep this in sync with ORACLE_BAD_NODE_MAJORS in src/cli/doctor.ts.
|
|
67
|
+
case "$1" in
|
|
68
|
+
26) return 0 ;;
|
|
69
|
+
*) return 1 ;;
|
|
70
|
+
esac
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
select_good_node() {
|
|
74
|
+
local node_bin major c version dir
|
|
75
|
+
if node_bin="$(command -v node 2>/dev/null)"; then
|
|
76
|
+
major="$(node_major "$node_bin")"
|
|
77
|
+
if [[ -n "$major" ]] && ! is_bad_node_major "$major"; then
|
|
78
|
+
return 0
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
shopt -u failglob nullglob
|
|
83
|
+
|
|
84
|
+
local candidates=(
|
|
85
|
+
"${ORACLE_NODE_BIN:-}"
|
|
86
|
+
/opt/homebrew/opt/node@24/bin/node
|
|
87
|
+
/usr/local/opt/node@24/bin/node
|
|
88
|
+
"$HOME"/.nvm/versions/node/v24*/bin/node
|
|
89
|
+
"$HOME"/.nvm/versions/node/v25*/bin/node
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
for c in "${candidates[@]}"; do
|
|
93
|
+
[[ -n "$c" && -x "$c" ]] || continue
|
|
94
|
+
major="$(node_major "$c")"
|
|
95
|
+
[[ -n "$major" ]] || continue
|
|
96
|
+
is_bad_node_major "$major" && continue
|
|
97
|
+
dir="$(dirname "$c")"
|
|
98
|
+
PATH="$dir:$PATH"
|
|
99
|
+
export PATH
|
|
100
|
+
version="$("$c" --version 2>/dev/null || true)"
|
|
101
|
+
log "pinned node $version from $dir (avoids undici setTypeOfService EINVAL)"
|
|
102
|
+
return 0
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
warn "no known-good node found; oracle may crash under bad node majors (undici setTypeOfService EINVAL)"
|
|
106
|
+
return 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
MODE="auto"
|
|
110
|
+
PROMPT=""
|
|
111
|
+
SLUG=""
|
|
112
|
+
DRY_RUN=0
|
|
113
|
+
AUTO_CONTEXT=1
|
|
114
|
+
USE_THINKING_FLAG=1
|
|
115
|
+
USE_MANUAL_LOGIN=1
|
|
116
|
+
MODEL_OVERRIDE=""
|
|
117
|
+
THINKING_OVERRIDE=""
|
|
118
|
+
RESEARCH_OVERRIDE=""
|
|
119
|
+
FILES=()
|
|
120
|
+
DIFF_BASE="${ORACLE_PRO_DIFF_BASE:-}"
|
|
121
|
+
|
|
122
|
+
while [[ $# -gt 0 ]]; do
|
|
123
|
+
case "$1" in
|
|
124
|
+
--mode) MODE="${2:-}"; shift 2 ;;
|
|
125
|
+
--mode=*) MODE="${1#*=}"; shift ;;
|
|
126
|
+
--file|-f) FILES+=("${2:-}"); shift 2 ;;
|
|
127
|
+
--file=*) FILES+=("${1#*=}"); shift ;;
|
|
128
|
+
--slug) SLUG="${2:-}"; shift 2 ;;
|
|
129
|
+
--slug=*) SLUG="${1#*=}"; shift ;;
|
|
130
|
+
--dry-run|--preview) DRY_RUN=1; shift ;;
|
|
131
|
+
--no-context) AUTO_CONTEXT=0; shift ;;
|
|
132
|
+
--no-thinking-flag) USE_THINKING_FLAG=0; shift ;;
|
|
133
|
+
--no-manual-login) USE_MANUAL_LOGIN=0; shift ;;
|
|
134
|
+
--model|-m) MODEL_OVERRIDE="${2:-}"; shift 2 ;;
|
|
135
|
+
--model=*) MODEL_OVERRIDE="${1#*=}"; shift ;;
|
|
136
|
+
--thinking) THINKING_OVERRIDE="${2:-}"; shift 2 ;;
|
|
137
|
+
--thinking=*) THINKING_OVERRIDE="${1#*=}"; shift ;;
|
|
138
|
+
--base) DIFF_BASE="${2:-}"; shift 2 ;;
|
|
139
|
+
--base=*) DIFF_BASE="${1#*=}"; shift ;;
|
|
140
|
+
--research) RESEARCH_OVERRIDE="${2:-}"; shift 2 ;;
|
|
141
|
+
--research=*) RESEARCH_OVERRIDE="${1#*=}"; shift ;;
|
|
142
|
+
-p|--prompt) PROMPT="${2:-}"; shift 2 ;;
|
|
143
|
+
--prompt=*) PROMPT="${1#*=}"; shift ;;
|
|
144
|
+
-h|--help) usage; exit 0 ;;
|
|
145
|
+
--) shift; PROMPT="${*:-}"; break ;;
|
|
146
|
+
*)
|
|
147
|
+
if [[ -z "$PROMPT" ]]; then PROMPT="$1"; else PROMPT="$PROMPT $1"; fi
|
|
148
|
+
shift
|
|
149
|
+
;;
|
|
150
|
+
esac
|
|
151
|
+
done
|
|
152
|
+
|
|
153
|
+
[[ -n "$PROMPT" ]] || die "prompt is required"
|
|
154
|
+
command -v oracle >/dev/null 2>&1 || die "oracle not found in PATH"
|
|
155
|
+
select_good_node
|
|
156
|
+
|
|
157
|
+
case "$MODE" in
|
|
158
|
+
auto|quick|standard|deep|plan|review|debug|research) ;;
|
|
159
|
+
*) die "unknown mode: $MODE" ;;
|
|
160
|
+
esac
|
|
161
|
+
|
|
162
|
+
classify_mode() {
|
|
163
|
+
local text_lc
|
|
164
|
+
text_lc="$(printf '%s' "$PROMPT" | tr '[:upper:]' '[:lower:]')"
|
|
165
|
+
case "$text_lc" in
|
|
166
|
+
*'[oracle:quick]'*) echo quick; return ;;
|
|
167
|
+
*'[oracle:standard]'*) echo standard; return ;;
|
|
168
|
+
*'[oracle:deep]'*|*'[oracle:plan]'*) echo deep; return ;;
|
|
169
|
+
*'[oracle:review]'*) echo review; return ;;
|
|
170
|
+
*'[oracle:debug]'*) echo debug; return ;;
|
|
171
|
+
*'[oracle:research]'*) echo research; return ;;
|
|
172
|
+
esac
|
|
173
|
+
if [[ ${#PROMPT} -gt 2500 ]]; then echo deep; return; fi
|
|
174
|
+
if [[ "$text_lc" =~ (architecture|architectural|design|plan|planning|proposal|migration|refactor|roadmap|tradeoff|trade-off|spec|handoff|implementation[[:space:]]+plan) ]]; then echo deep; return; fi
|
|
175
|
+
if [[ "$text_lc" =~ (review|audit|regression|security|compatibility|api[[:space:]]+break|edge[[:space:]]+case|risk) ]]; then echo review; return; fi
|
|
176
|
+
if [[ "$text_lc" =~ (debug|root[ -]?cause|failing|failure|flaky|bug|stack[[:space:]]+trace|exception|crash|deadlock|race) ]]; then echo debug; return; fi
|
|
177
|
+
if [[ "$text_lc" =~ (research|compare[[:space:]]+options|survey|citations|latest|web) ]]; then echo research; return; fi
|
|
178
|
+
if [[ "$text_lc" =~ (quick|simple|small|syntax|command|explain) ]]; then echo quick; return; fi
|
|
179
|
+
echo standard
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if [[ "$MODE" == "auto" ]]; then
|
|
183
|
+
MODE="$(classify_mode)"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Dispatch table. Model is the primary selector. Thinking flag is additive when the local binary supports it.
|
|
187
|
+
QUICK_MODEL="${ORACLE_PRO_QUICK_MODEL:-gpt-5.2-instant}"
|
|
188
|
+
STANDARD_MODEL="${ORACLE_PRO_STANDARD_MODEL:-gpt-5.5}"
|
|
189
|
+
DEEP_MODEL="${ORACLE_PRO_DEEP_MODEL:-gpt-5.5-pro}"
|
|
190
|
+
QUICK_THINKING="${ORACLE_PRO_QUICK_THINKING:-light}"
|
|
191
|
+
STANDARD_THINKING="${ORACLE_PRO_STANDARD_THINKING:-standard}"
|
|
192
|
+
DEEP_THINKING="${ORACLE_PRO_DEEP_THINKING:-extended}"
|
|
193
|
+
RESEARCH_MODE="off"
|
|
194
|
+
DEFAULT_STRATEGY="select"
|
|
195
|
+
|
|
196
|
+
case "$MODE" in
|
|
197
|
+
quick) MODEL="$QUICK_MODEL"; THINKING="$QUICK_THINKING"; DEFAULT_STRATEGY="current" ;;
|
|
198
|
+
standard) MODEL="$STANDARD_MODEL"; THINKING="$STANDARD_THINKING"; DEFAULT_STRATEGY="current" ;;
|
|
199
|
+
deep|plan|review|debug) MODEL="$DEEP_MODEL"; THINKING="$DEEP_THINKING"; DEFAULT_STRATEGY="select" ;;
|
|
200
|
+
research) MODEL="$DEEP_MODEL"; THINKING="$DEEP_THINKING"; RESEARCH_MODE="deep"; DEFAULT_STRATEGY="select" ;;
|
|
201
|
+
esac
|
|
202
|
+
|
|
203
|
+
[[ -z "$MODEL_OVERRIDE" ]] || MODEL="$MODEL_OVERRIDE"
|
|
204
|
+
[[ -z "$THINKING_OVERRIDE" ]] || THINKING="$THINKING_OVERRIDE"
|
|
205
|
+
[[ -z "$RESEARCH_OVERRIDE" ]] || RESEARCH_MODE="$RESEARCH_OVERRIDE"
|
|
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
|
+
|
|
231
|
+
OUT_DIR="${ORACLE_PRO_OUTPUT_DIR:-.composer/oracle/answers}"
|
|
232
|
+
CTX_DIR="${ORACLE_PRO_CONTEXT_DIR:-.composer/oracle/context}"
|
|
233
|
+
mkdir -p "$OUT_DIR" "$CTX_DIR"
|
|
234
|
+
|
|
235
|
+
is_secret_file() {
|
|
236
|
+
# Returns 0 (true) if $1 looks like a secret/credential file we must not upload.
|
|
237
|
+
local p base lc
|
|
238
|
+
p="$1"
|
|
239
|
+
base="${p##*/}"
|
|
240
|
+
lc="$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')"
|
|
241
|
+
case "$lc" in
|
|
242
|
+
.env|.env.*|*.pem|*.key|*.p12|*.pfx|*.keystore|*.jks|*.kdbx|*.ppk|id_rsa|id_dsa|id_ecdsa|id_ed25519|.npmrc|.netrc|.pgpass)
|
|
243
|
+
return 0 ;;
|
|
244
|
+
*secret*|*token*|*credential*|*password*)
|
|
245
|
+
return 0 ;;
|
|
246
|
+
esac
|
|
247
|
+
case "$p" in
|
|
248
|
+
*/.ssh/*|*/.aws/credentials|*/.gnupg/*)
|
|
249
|
+
return 0 ;;
|
|
250
|
+
esac
|
|
251
|
+
return 1
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
safe_slug() {
|
|
255
|
+
local s="$1"
|
|
256
|
+
s="$(printf '%s' "$s" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/^-+|-+$//g; s/-+/-/g')"
|
|
257
|
+
[[ -n "$s" ]] || s="oracle"
|
|
258
|
+
printf '%s' "${s:0:80}"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if [[ -z "$SLUG" ]]; then
|
|
262
|
+
SLUG="$(date +%Y%m%d-%H%M%S)-${MODE}"
|
|
263
|
+
else
|
|
264
|
+
SLUG="$(date +%Y%m%d-%H%M%S)-$(safe_slug "$SLUG")"
|
|
265
|
+
fi
|
|
266
|
+
OUT_FILE="$OUT_DIR/$SLUG.md"
|
|
267
|
+
|
|
268
|
+
# Probe support for optional flags. A dry-run should parse options without touching Chrome.
|
|
269
|
+
# Cache per-version-ish in the context dir to avoid repeated probes.
|
|
270
|
+
ORACLE_VERSION="$(oracle --version 2>/dev/null | head -1 | tr -d '\r' || true)"
|
|
271
|
+
CACHE_DIR="$CTX_DIR/.flag-cache"
|
|
272
|
+
mkdir -p "$CACHE_DIR"
|
|
273
|
+
cache_key="$(printf '%s' "$(command -v oracle)::${ORACLE_VERSION}" | shasum 2>/dev/null | awk '{print $1}')"
|
|
274
|
+
[[ -n "$cache_key" ]] || cache_key="default"
|
|
275
|
+
|
|
276
|
+
supports_option() {
|
|
277
|
+
local flag="$1"
|
|
278
|
+
local value="${2-}"
|
|
279
|
+
local key="$CACHE_DIR/${cache_key}.$(printf '%s' "$flag" | tr -c 'a-zA-Z0-9_' '_')"
|
|
280
|
+
if [[ -f "$key" ]]; then
|
|
281
|
+
[[ "$(cat "$key")" == "yes" ]]
|
|
282
|
+
return
|
|
283
|
+
fi
|
|
284
|
+
local args=(--engine browser --dry-run summary -p "oracle flag probe")
|
|
285
|
+
if [[ -n "$value" ]]; then args+=("$flag" "$value"); else args+=("$flag"); fi
|
|
286
|
+
if oracle "${args[@]}" >/dev/null 2>"$key.err"; then
|
|
287
|
+
printf 'yes' > "$key"
|
|
288
|
+
return 0
|
|
289
|
+
fi
|
|
290
|
+
if grep -qiE 'unknown option|unknown argument|invalid option' "$key.err" 2>/dev/null; then
|
|
291
|
+
printf 'no' > "$key"
|
|
292
|
+
return 1
|
|
293
|
+
fi
|
|
294
|
+
# Conservative: if dry-run failed for some non-parse reason, do not use the optional flag.
|
|
295
|
+
printf 'no' > "$key"
|
|
296
|
+
return 1
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
add_supported_flag() {
|
|
300
|
+
# $1 is the target array name (historically a nameref); all callers use ARGS,
|
|
301
|
+
# so append to ARGS directly to stay compatible with Bash 3.2 (no `local -n`).
|
|
302
|
+
local flag="$2"
|
|
303
|
+
local value="${3-}"
|
|
304
|
+
if supports_option "$flag" "$value"; then
|
|
305
|
+
if [[ -n "$value" ]]; then ARGS+=("$flag" "$value"); else ARGS+=("$flag"); fi
|
|
306
|
+
else
|
|
307
|
+
warn "installed oracle does not accept $flag; skipping"
|
|
308
|
+
fi
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
# Auto context: small, local, non-secret evidence that helps ChatGPT plan/review without dumping the repo.
|
|
312
|
+
AUTO_FILES=()
|
|
313
|
+
write_context_file() {
|
|
314
|
+
local name="$1"
|
|
315
|
+
local content_cmd="$2"
|
|
316
|
+
local path="$CTX_DIR/$SLUG.$name"
|
|
317
|
+
bash -lc "$content_cmd" > "$path" 2>/dev/null || true
|
|
318
|
+
if [[ -s "$path" ]]; then AUTO_FILES+=("$path"); fi
|
|
319
|
+
}
|
|
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
|
+
|
|
346
|
+
if [[ "$AUTO_CONTEXT" -eq 1 ]]; then
|
|
347
|
+
# Authority class B — current policy/context (safe as source-of-truth).
|
|
348
|
+
policy_files=(CLAUDE.md composer.config.json docs/STATUS.md)
|
|
349
|
+
# Authority class C — background/history (risky as source-of-truth; only for planning/research).
|
|
350
|
+
background_files=(README.md AGENTS.md)
|
|
351
|
+
# Project manifest files (dependency intent + language).
|
|
352
|
+
base_files=(package.json pyproject.toml Cargo.toml go.mod)
|
|
353
|
+
|
|
354
|
+
# Task-aware attach set: minimal for trivial modes, no stale background for review/debug.
|
|
355
|
+
attach_candidates=()
|
|
356
|
+
case "$MODE" in
|
|
357
|
+
quick|standard)
|
|
358
|
+
attach_candidates=(CLAUDE.md docs/STATUS.md)
|
|
359
|
+
;;
|
|
360
|
+
review|debug)
|
|
361
|
+
attach_candidates=("${policy_files[@]}" "${base_files[@]}")
|
|
362
|
+
;;
|
|
363
|
+
deep|plan|research|*)
|
|
364
|
+
attach_candidates=("${policy_files[@]}" "${background_files[@]}" "${base_files[@]}")
|
|
365
|
+
;;
|
|
366
|
+
esac
|
|
367
|
+
|
|
368
|
+
for candidate in "${attach_candidates[@]}"; do
|
|
369
|
+
[[ -f "$candidate" ]] && AUTO_FILES+=("$candidate")
|
|
370
|
+
done
|
|
371
|
+
|
|
372
|
+
if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
373
|
+
write_context_file "git-status.txt" "git status --short"
|
|
374
|
+
write_context_file "git-diff-stat.txt" "git diff --stat"
|
|
375
|
+
if [[ "$MODE" != "quick" && "$MODE" != "standard" ]]; then
|
|
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")
|
|
383
|
+
fi
|
|
384
|
+
# Exact installed top-level deps (package.json shows ranges, not the installed tree).
|
|
385
|
+
if [[ "$MODE" != "quick" && "$MODE" != "standard" ]] && [[ -f package.json ]]; then
|
|
386
|
+
write_context_file "deps.txt" "npm ls --depth=0 2>/dev/null || true"
|
|
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
|
|
422
|
+
fi
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
ARGS=(--engine browser -m "$MODEL" --write-output "$OUT_FILE")
|
|
426
|
+
|
|
427
|
+
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
428
|
+
ARGS+=(--dry-run summary)
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
# Official v0.13.0 accepts these, but many are hidden from --help; still probe to survive forks/older installs.
|
|
432
|
+
add_supported_flag ARGS --browser-model-strategy "${ORACLE_PRO_BROWSER_STRATEGY:-$DEFAULT_STRATEGY}"
|
|
433
|
+
|
|
434
|
+
if [[ "$USE_THINKING_FLAG" -eq 1 ]]; then
|
|
435
|
+
add_supported_flag ARGS --browser-thinking-time "$THINKING"
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
if [[ "$RESEARCH_MODE" == "deep" ]]; then
|
|
439
|
+
add_supported_flag ARGS --browser-research deep
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
if [[ "$USE_MANUAL_LOGIN" -eq 1 ]]; then
|
|
443
|
+
add_supported_flag ARGS --browser-manual-login
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
add_supported_flag ARGS --browser-timeout "${ORACLE_PRO_TIMEOUT:-20m}"
|
|
447
|
+
add_supported_flag ARGS --browser-input-timeout "${ORACLE_PRO_INPUT_TIMEOUT:-60s}"
|
|
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
|
|
452
|
+
add_supported_flag ARGS --browser-auto-reattach-delay "${ORACLE_PRO_REATTACH_DELAY:-30s}"
|
|
453
|
+
add_supported_flag ARGS --browser-auto-reattach-interval "${ORACLE_PRO_REATTACH_INTERVAL:-2m}"
|
|
454
|
+
add_supported_flag ARGS --browser-auto-reattach-timeout "${ORACLE_PRO_REATTACH_TIMEOUT:-2m}"
|
|
455
|
+
add_supported_flag ARGS --heartbeat "${ORACLE_PRO_HEARTBEAT:-30}"
|
|
456
|
+
|
|
457
|
+
# Validate + collect the exact attachment set (user files first, then auto context).
|
|
458
|
+
ATTACH=()
|
|
459
|
+
for f in "${FILES[@]}"; do
|
|
460
|
+
[[ -n "$f" ]] || continue
|
|
461
|
+
if is_secret_file "$f" && [[ "${ORACLE_PRO_ALLOW_SECRET_FILES:-0}" != "1" ]]; then
|
|
462
|
+
die "refusing to upload potential secret file: $f (matches secret denylist). Rename/relocate it, or set ORACLE_PRO_ALLOW_SECRET_FILES=1 to override."
|
|
463
|
+
fi
|
|
464
|
+
ATTACH+=("$f")
|
|
465
|
+
done
|
|
466
|
+
for f in "${AUTO_FILES[@]}"; do
|
|
467
|
+
[[ -n "$f" ]] && ATTACH+=("$f")
|
|
468
|
+
done
|
|
469
|
+
|
|
470
|
+
# Snapshot manifest: authoritative identity of the repo state this call may discuss.
|
|
471
|
+
# Lets the model (and us) detect drift between turns and bounds it to current disk state.
|
|
472
|
+
captured_at="$(date +%Y-%m-%dT%H:%M:%S%z)"
|
|
473
|
+
repo_root="$(pwd)"
|
|
474
|
+
git_branch="n/a"; git_head="n/a"; dirty="false"; status_hash="n/a"; diff_hash="n/a"
|
|
475
|
+
if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
476
|
+
git_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo n/a)"
|
|
477
|
+
git_head="$(git rev-parse HEAD 2>/dev/null || echo n/a)"
|
|
478
|
+
[[ -n "$(git status --porcelain 2>/dev/null)" ]] && dirty="true"
|
|
479
|
+
status_hash="$(git status --short 2>/dev/null | shasum -a 256 2>/dev/null | awk '{print $1}')"
|
|
480
|
+
diff_hash="$(git diff 2>/dev/null | shasum -a 256 2>/dev/null | awk '{print $1}')"
|
|
481
|
+
fi
|
|
482
|
+
[[ -n "$status_hash" ]] || status_hash="n/a"
|
|
483
|
+
[[ -n "$diff_hash" ]] || diff_hash="n/a"
|
|
484
|
+
node_ver="$(node --version 2>/dev/null || echo n/a)"
|
|
485
|
+
npm_ver="$(npm --version 2>/dev/null || echo n/a)"
|
|
486
|
+
os_ver="$(uname -sr 2>/dev/null || echo n/a)"
|
|
487
|
+
arch_ver="$(uname -m 2>/dev/null || echo n/a)"
|
|
488
|
+
short_head="${git_head:0:12}"
|
|
489
|
+
repo_state_hash="$(printf '%s' "${git_head}${status_hash}${diff_hash}${node_ver}${npm_ver}" | shasum -a 256 2>/dev/null | awk '{print $1}')"
|
|
490
|
+
[[ -n "$repo_state_hash" ]] || repo_state_hash="n/a"
|
|
491
|
+
snapshot_id="${SLUG}-${short_head}-${repo_state_hash:0:8}"
|
|
492
|
+
|
|
493
|
+
MANIFEST_PATH="$CTX_DIR/$SLUG.manifest.json"
|
|
494
|
+
# JSON-escape a string value (backslash and double-quote only; inputs are paths/hashes/versions).
|
|
495
|
+
json_escape() { printf '%s' "${1-}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'; }
|
|
496
|
+
{
|
|
497
|
+
printf '{\n'
|
|
498
|
+
printf ' "snapshotId": "%s",\n' "$(json_escape "$snapshot_id")"
|
|
499
|
+
printf ' "capturedAt": "%s",\n' "$(json_escape "$captured_at")"
|
|
500
|
+
printf ' "repoRoot": "%s",\n' "$(json_escape "$repo_root")"
|
|
501
|
+
printf ' "branch": "%s",\n' "$(json_escape "$git_branch")"
|
|
502
|
+
printf ' "head": "%s",\n' "$(json_escape "$git_head")"
|
|
503
|
+
printf ' "dirty": %s,\n' "$dirty"
|
|
504
|
+
printf ' "gitStatusHash": "%s",\n' "$(json_escape "$status_hash")"
|
|
505
|
+
printf ' "diffHash": "%s",\n' "$(json_escape "$diff_hash")"
|
|
506
|
+
printf ' "repoStateHash": "%s",\n' "$(json_escape "$repo_state_hash")"
|
|
507
|
+
printf ' "mode": "%s",\n' "$(json_escape "$MODE")"
|
|
508
|
+
printf ' "runtime": { "node": "%s", "npm": "%s", "os": "%s", "arch": "%s" },\n' \
|
|
509
|
+
"$(json_escape "$node_ver")" "$(json_escape "$npm_ver")" "$(json_escape "$os_ver")" "$(json_escape "$arch_ver")"
|
|
510
|
+
printf ' "attachments": [\n'
|
|
511
|
+
manifest_n=${#ATTACH[@]}
|
|
512
|
+
manifest_i=0
|
|
513
|
+
for f in "${ATTACH[@]}"; do
|
|
514
|
+
manifest_i=$((manifest_i + 1))
|
|
515
|
+
sha="$(shasum -a 256 "$f" 2>/dev/null | awk '{print $1}')"
|
|
516
|
+
bytes="$(wc -c < "$f" 2>/dev/null | tr -d ' ')"
|
|
517
|
+
sep=","
|
|
518
|
+
[[ "$manifest_i" -eq "$manifest_n" ]] && sep=""
|
|
519
|
+
printf ' { "path": "%s", "sha256": "%s", "bytes": %s }%s\n' \
|
|
520
|
+
"$(json_escape "$f")" "${sha:-}" "${bytes:-0}" "$sep"
|
|
521
|
+
done
|
|
522
|
+
printf ' ]\n'
|
|
523
|
+
printf '}\n'
|
|
524
|
+
} > "$MANIFEST_PATH" 2>/dev/null || warn "manifest generation failed (continuing without manifest)"
|
|
525
|
+
[[ -f "$MANIFEST_PATH" ]] && ATTACH+=("$MANIFEST_PATH")
|
|
526
|
+
|
|
527
|
+
# CONTEXT CONTRACT: bound the model to this snapshot; defeat stale memory/attachments.
|
|
528
|
+
CONTRACT="$(cat <<EOF
|
|
529
|
+
CONTEXT CONTRACT
|
|
530
|
+
Authoritative snapshot: ${snapshot_id}
|
|
531
|
+
Captured at: ${captured_at}
|
|
532
|
+
Branch / HEAD: ${git_branch} / ${git_head}
|
|
533
|
+
Dirty tree: ${dirty}
|
|
534
|
+
Repo-state hash: ${repo_state_hash}
|
|
535
|
+
Runtime: node=${node_ver} npm=${npm_ver} os=${os_ver} arch=${arch_ver}
|
|
536
|
+
Task: ${MODE}
|
|
537
|
+
Authority order:
|
|
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
|
|
539
|
+
B. Current policy/context: CLAUDE.md, composer.config.json, docs/STATUS.md, relevant ADRs
|
|
540
|
+
C. Background/history: README.md, AGENTS.md
|
|
541
|
+
Rules:
|
|
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.
|
|
544
|
+
- Ignore prior chat memory, project memory, and earlier attachments if they conflict with this snapshot.
|
|
545
|
+
- For each substantive claim, tag it [attached], [runtime], [web], or [inference].
|
|
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.
|
|
549
|
+
- For current API/library claims not proven by attached files, verify on the web against primary docs.
|
|
550
|
+
- If evidence is insufficient, say: "unknown from provided context".
|
|
551
|
+
EOF
|
|
552
|
+
)"
|
|
553
|
+
PROMPT="${CONTRACT}
|
|
554
|
+
|
|
555
|
+
${PROMPT}"
|
|
556
|
+
|
|
557
|
+
# Emit all attachments (incl. the manifest) as --file inputs.
|
|
558
|
+
for f in "${ATTACH[@]}"; do
|
|
559
|
+
[[ -n "$f" ]] && ARGS+=(--file "$f")
|
|
560
|
+
done
|
|
561
|
+
|
|
562
|
+
log "oracle version: ${ORACLE_VERSION:-unknown}"
|
|
563
|
+
log "mode=$MODE model=$MODEL thinking=$THINKING research=$RESEARCH_MODE output=$OUT_FILE"
|
|
564
|
+
log "files: user=${#FILES[@]} auto=${#AUTO_FILES[@]} total=${#ATTACH[@]} snapshot=${snapshot_id}"
|
|
565
|
+
|
|
566
|
+
oracle "${ARGS[@]}" -p "$PROMPT"
|
|
567
|
+
|
|
568
|
+
# Maintain a stable latest file for downstream Codex/Composer commands.
|
|
569
|
+
if [[ -f "$OUT_FILE" ]]; then
|
|
570
|
+
cp "$OUT_FILE" "$OUT_DIR/latest.md"
|
|
571
|
+
printf '%s\n' "$OUT_FILE"
|
|
572
|
+
fi
|