@sabaiway/agent-workflow-kit 1.16.0 → 1.18.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.
@@ -5,27 +5,78 @@
5
5
  # decides what to act on; codex never applies them itself.
6
6
  #
7
7
  # Project-agnostic wrapper for the codex-cli-bridge skill. Codex reads the target
8
- # project's Hard Constraints itself, from the root AGENTS.md in its cwd.
8
+ # project's Hard Constraints from the root AGENTS.md auto-merged into its context.
9
9
  #
10
10
  # Modes:
11
11
  # codex-review plan <plan-file> # critique an implementation plan
12
12
  # codex-review code [extra focus...] # review the current working-tree diff
13
13
  #
14
- # Auth/policy: subscription-only, identical to codex-exec.sh. The read-only
15
- # sandbox grants no writes and no network in v0.140.0, so review needs no separate
16
- # network flag (the sandbox_workspace_write.* config applies only to workspace-write).
14
+ # For `code`, the wrapper PRECOMPUTES the full change set (repo map, git status,
15
+ # staged + unstaged diffs, untracked file CONTENTS binaries skipped, symlinks
16
+ # shown as targets, other non-regular paths skipped) and feeds it in, so codex does
17
+ # not burn a run rediscovering it by roaming the filesystem. A clean tree exits 0
18
+ # BEFORE a run is spent; an oversized payload goes via a git-dir-local temp file
19
+ # (never silently truncated). Optionally (CODEX_REVIEW_SCHEMA=1) the findings come
20
+ # back as a validated JSON object, with a raw-text fallback.
21
+ #
22
+ # Auth/policy: subscription-only, identical to codex-exec.sh. Quality-first: the
23
+ # review runs on the frontier model at max effort (advisory findings still bear on
24
+ # what ships, so the same no-downgrade guard applies; CODEX_PROBE=1 relaxes it for
25
+ # a throwaway probe only).
26
+ #
27
+ # Best-effort read hygiene: the review runs under a temporary HOME / XDG_* with an
28
+ # ABSOLUTE CODEX_HOME so codex auth + history still resolve, but its default config
29
+ # / cache / skill-scan roots point at a throwaway dir instead of the real $HOME.
30
+ # This trims roaming noise; it is NOT a security boundary (absolute paths anywhere
31
+ # on disk remain readable under read-only — read-scoping is a prompt concern).
17
32
  set -euo pipefail
18
33
 
19
- CODEX_MODEL="${CODEX_MODEL:-gpt-5.5}"
20
- CODEX_EFFORT="${CODEX_EFFORT:-xhigh}"
34
+ DEFAULT_CODEX_MODEL="gpt-5.5"
35
+ DEFAULT_CODEX_EFFORT="xhigh"
36
+ CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
37
+ CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
38
+ # Generous hard cap for a slow xhigh review (subscription latency varies).
39
+ CODEX_HARD_TIMEOUT="${CODEX_HARD_TIMEOUT:-1800}"
40
+ # Above this assembled-payload size (bytes), the diff goes via a git-dir-local temp
41
+ # file instead of inline — never truncated.
42
+ CODEX_REVIEW_MAX_TOTAL_BYTES="${CODEX_REVIEW_MAX_TOTAL_BYTES:-1500000}"
21
43
  CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
22
44
 
45
+ # --- Quality-first guard: refuse a silent model/effort downgrade ---------------
46
+ if [[ "${CODEX_PROBE:-}" == "1" ]]; then
47
+ echo "warning: CODEX_PROBE=1 — THROWAWAY PROBE MODE. Quality guards relaxed; do NOT use this run's" >&2
48
+ echo " output as a real review (model='$CODEX_MODEL' effort='$CODEX_EFFORT')." >&2
49
+ else
50
+ if [[ "$CODEX_MODEL" != "$DEFAULT_CODEX_MODEL" ]]; then
51
+ echo "error: CODEX_MODEL='$CODEX_MODEL' is not the pinned frontier model '$DEFAULT_CODEX_MODEL'." >&2
52
+ echo " A delegated review must run on the frontier model at max effort (quality-first)." >&2
53
+ echo " For a throwaway probe whose result is effort-independent, set CODEX_PROBE=1." >&2
54
+ exit 2
55
+ fi
56
+ if [[ "$CODEX_EFFORT" != "$DEFAULT_CODEX_EFFORT" ]]; then
57
+ echo "error: CODEX_EFFORT='$CODEX_EFFORT' is not the pinned max effort '$DEFAULT_CODEX_EFFORT'." >&2
58
+ echo " A delegated review must run at max reasoning effort (quality-first)." >&2
59
+ echo " For a throwaway probe whose result is effort-independent, set CODEX_PROBE=1." >&2
60
+ exit 2
61
+ fi
62
+ fi
63
+
23
64
  # --- Subscription-only guard (see codex-exec.sh) -----------------------------
24
65
  unset OPENAI_API_KEY CODEX_API_KEY OPENAI_BASE_URL 2>/dev/null || true
25
66
  while IFS= read -r _api_key_var; do
26
67
  unset "$_api_key_var" 2>/dev/null || true
27
68
  done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
28
69
 
70
+ # Resolve the real CODEX_HOME (where the cached ChatGPT login lives) to an ABSOLUTE
71
+ # path BEFORE the env fence repoints HOME — auth must keep resolving.
72
+ real_codex_home="${CODEX_HOME:-$HOME/.codex}"
73
+ case "$real_codex_home" in
74
+ "~") real_codex_home="$HOME" ;; # literal tilde
75
+ "~/"*) real_codex_home="$HOME/${real_codex_home#"~/"}" ;; # unexpanded ~/...
76
+ /*) : ;; # already absolute
77
+ *) real_codex_home="$PWD/$real_codex_home" ;; # relative → anchor
78
+ esac
79
+
29
80
  # --- Environment preflight (fail fast) ---------------------------------------
30
81
  if ! command -v codex >/dev/null 2>&1; then
31
82
  echo "error: 'codex' (OpenAI Codex CLI) not found on PATH. See this skill's setup/README.md." >&2
@@ -44,6 +95,71 @@ if [[ ! -f AGENTS.md ]]; then
44
95
  exit 2
45
96
  fi
46
97
 
98
+ # Register the cleanup trap up front (before allocating any temp path), so a
99
+ # failure mid-assembly cannot leak the diff temp file, the schema, or the fence.
100
+ fence_home=""
101
+ out=""
102
+ trace=""
103
+ schema_file=""
104
+ review_diff_file=""
105
+ trap 'rm -rf "$fence_home" 2>/dev/null; rm -f "$out" "$trace" "$schema_file" "$review_diff_file" 2>/dev/null; true' EXIT
106
+
107
+ # Output-format instruction — schema-aware so it never contradicts the structured
108
+ # mode. When CODEX_REVIEW_SCHEMA=1 the model must return schema-shaped JSON; else
109
+ # the classic one-finding-per-line text.
110
+ if [[ "${CODEX_REVIEW_SCHEMA:-}" == "1" ]]; then
111
+ _json_fmt="Return your review STRICTLY as a JSON object matching the provided output schema: a \"findings\" array (each item: \"severity\" one of blocker|major|minor|nit, plus \"location\", \"issue\", \"suggested_change\", and optional \"evidence\"), an overall \"verdict\" (ship|revise|rethink), and optional free-text \"notes\"."
112
+ OUTPUT_FORMAT_CODE="$_json_fmt"
113
+ OUTPUT_FORMAT_PLAN="$_json_fmt"
114
+ else
115
+ OUTPUT_FORMAT_CODE="Output findings ONLY, one per line, as: [blocker|major|minor|nit] — file:line — issue — suggested fix. End with a one-line overall verdict (ship / revise / rethink)."
116
+ OUTPUT_FORMAT_PLAN="Output findings ONLY, one per line, as: [blocker|major|minor|nit] — location — issue — suggested change. End with a one-line overall verdict (ship / revise / rethink)."
117
+ fi
118
+
119
+ # True (exit 0) when $1 looks BINARY: a NUL byte in the first 8 KiB (git's own
120
+ # heuristic). `tr -dc` keeps ONLY NUL bytes, `wc -c` counts them — never captures
121
+ # NUL into a variable. Empty / text files → not binary.
122
+ is_binary() {
123
+ local nul
124
+ nul="$(LC_ALL=C head -c 8192 -- "$1" 2>/dev/null | LC_ALL=C tr -dc '\000' | wc -c)"
125
+ [[ "${nul:-0}" -gt 0 ]]
126
+ }
127
+
128
+ # Emit the full review surface to stdout: repo map, status, staged + unstaged
129
+ # diffs, and the CONTENTS of every untracked REGULAR file (NUL-safe iteration).
130
+ # Symlinks are shown as their target (never followed — no out-of-repo leak); other
131
+ # non-regular paths (FIFO/socket/device) are skipped (a `cat` on a FIFO would hang
132
+ # BEFORE the codex hard timeout applies).
133
+ assemble_code_diff() {
134
+ echo "=== repo file map (git ls-files) ==="
135
+ git ls-files
136
+ echo
137
+ echo "=== git status (porcelain) ==="
138
+ git status --porcelain=v1
139
+ echo
140
+ echo "=== staged diff (git diff --cached) ==="
141
+ git diff --cached --no-ext-diff
142
+ echo
143
+ echo "=== unstaged diff (git diff) ==="
144
+ git diff --no-ext-diff
145
+ echo
146
+ echo "=== untracked file contents ==="
147
+ local path
148
+ while IFS= read -r -d '' path; do
149
+ if [[ -L "$path" ]]; then
150
+ printf '=== untracked (symlink): %s -> %s ===\n' "$path" "$(readlink -- "$path" 2>/dev/null || echo '?')"
151
+ elif [[ ! -f "$path" ]]; then
152
+ printf '=== untracked (non-regular, skipped): %s ===\n' "$path"
153
+ elif is_binary "$path"; then
154
+ printf '=== untracked (binary, skipped): %s ===\n' "$path"
155
+ else
156
+ printf '=== untracked: %s ===\n' "$path"
157
+ cat -- "$path"
158
+ printf '\n'
159
+ fi
160
+ done < <(git ls-files --others --exclude-standard -z)
161
+ }
162
+
47
163
  mode="${1:-}"
48
164
  shift || true
49
165
 
@@ -59,15 +175,36 @@ case "$mode" in
59
175
  echo "error: unexpected arguments after plan file: $*" >&2
60
176
  exit 2
61
177
  fi
62
- directive="You are REVIEWING an implementation plan — ADVISORY ONLY. You are in a read-only sandbox: do NOT edit, create, or delete any file, and do NOT rewrite the plan. Read the plan below, the project's root AGENTS.md, and the relevant repository code it references. Output findings ONLY, one per line, as: [blocker|major|minor|nit] — location — issue — suggested change. Cover: correctness risks, missing or mis-ordered steps, ambiguities a cold executor would trip on, violated project Hard Constraints (AGENTS.md), scope creep, and missing verification/gates. End with a one-line overall verdict (ship / revise / rethink)."
63
- prompt="${directive}"$'\n\nPLAN:\n'"$(cat "$target")"
178
+ fence_plan="Do not read files outside this git working tree; the plan above plus the in-repo code it references are your whole surface."
179
+ directive="You are REVIEWING an implementation plan — ADVISORY ONLY. You are in a read-only sandbox: do NOT edit, create, or delete any file, and do NOT rewrite the plan. Obey the project's Hard Constraints from its root AGENTS.md (already merged into your context). Read the plan below and the relevant repository code it references. ${fence_plan} ${OUTPUT_FORMAT_PLAN} Cover: correctness risks, missing or mis-ordered steps, ambiguities a cold executor would trip on, violated project Hard Constraints, scope creep, and missing verification/gates."
180
+ prompt="${directive}"$'\n\nPLAN:\n'"$(cat -- "$target")"
64
181
  ;;
65
182
  code)
66
- directive="You are REVIEWING the current uncommitted working-tree changesADVISORY ONLY. You are in a read-only sandbox: do NOT edit, create, or delete any file and do NOT run any git write command. Run \`git status --short\` to list ALL changes, \`git diff\` for the tracked changes, and for every path marked \`??\` by git status READ that file's full contents (plain \`git diff\` omits untracked files). Also read the project's root AGENTS.md. Then output findings ONLY, one per line, as: [blocker|major|minor|nit] — file:line — issue — suggested fix. Focus on correctness bugs, project Hard Constraints (AGENTS.md), behaviour drift vs the intended change, and test/gate gaps. End with a one-line overall verdict (ship / revise / rethink)."
67
- if [[ $# -gt 0 ]]; then
68
- directive="${directive} Extra focus: $*"
183
+ # No-diff preflightnever spend a subscription run on a clean tree.
184
+ if git diff --quiet && git diff --cached --quiet \
185
+ && [[ -z "$(git ls-files --others --exclude-standard)" ]]; then
186
+ echo "codex-review: no uncommitted changes to review — the working tree is clean." >&2
187
+ exit 0
188
+ fi
189
+ # Assemble DIRECTLY to a git-dir-local temp file (git-invisible + worktree /
190
+ # submodule safe; 600 perms). Measuring size from the file keeps the whole
191
+ # change set off the bash-variable path (no NUL drop, byte-accurate size).
192
+ git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null || git rev-parse --git-dir)"
193
+ review_diff_file="$git_dir/codex-review-diff.$$"
194
+ ( umask 077; assemble_code_diff >"$review_diff_file" )
195
+ payload_bytes="$(wc -c <"$review_diff_file")"
196
+ base_directive="You are REVIEWING the uncommitted working-tree changes of a git repository — ADVISORY ONLY. You are in a read-only sandbox: do NOT edit, create, or delete any file and do NOT run any git write command. The COMPLETE change set (repo file map, git status, staged + unstaged diffs, and untracked file contents — binaries noted but skipped, symlinks shown as targets) has been assembled FOR you; you do NOT need to run git yourself, though you MAY read other in-repo files for surrounding context. Obey the project's Hard Constraints from its root AGENTS.md (already merged into your context)."
197
+ if [[ "$payload_bytes" -gt "$CODEX_REVIEW_MAX_TOTAL_BYTES" ]]; then
198
+ fence_code="Do not read files outside this git working tree, with ONE exception — the precomputed-diff file at ${review_diff_file}; read it IN FULL before reviewing. That file plus the in-repo code are your whole surface."
199
+ directive="${base_directive} ${fence_code} ${OUTPUT_FORMAT_CODE}"
200
+ if [[ $# -gt 0 ]]; then directive="${directive} Extra focus: $*"; fi
201
+ prompt="$directive"
202
+ else
203
+ fence_code="Do not read files outside this git working tree; the assembled change set below plus the in-repo code are your whole surface."
204
+ directive="${base_directive} ${fence_code} ${OUTPUT_FORMAT_CODE}"
205
+ if [[ $# -gt 0 ]]; then directive="${directive} Extra focus: $*"; fi
206
+ prompt="${directive}"$'\n\nASSEMBLED CHANGE SET:\n'"$(cat -- "$review_diff_file")"
69
207
  fi
70
- prompt="$directive"
71
208
  ;;
72
209
  *)
73
210
  echo "usage: $0 plan <plan-file> | code [extra focus...]" >&2
@@ -75,10 +212,133 @@ case "$mode" in
75
212
  ;;
76
213
  esac
77
214
 
78
- printf '%s' "$prompt" | codex exec \
79
- --ignore-user-config \
80
- --sandbox read-only \
81
- -c approval_policy="never" \
82
- -c model_reasoning_effort="$CODEX_EFFORT" \
83
- -m "$CODEX_MODEL" \
84
- -
215
+ # --- Best-effort env read-fence ----------------------------------------------
216
+ # A throwaway HOME + XDG roots so codex's default config / cache / skill-scan land
217
+ # in an empty dir (trims the ~MB roaming + skill-scan noise), while an ABSOLUTE
218
+ # CODEX_HOME keeps the subscription login + history reachable (probe-confirmed).
219
+ fence_home="$(mktemp -d)"
220
+ out="$(mktemp)"
221
+ trace="$(mktemp)"
222
+ mkdir -p "$fence_home/.config" "$fence_home/.cache" "$fence_home/.local/share"
223
+
224
+ # --- Optional structured findings (CODEX_REVIEW_SCHEMA=1) ---------------------
225
+ # A FLEXIBLE schema (optional evidence, free-text notes) so codex can almost always
226
+ # conform (a live probe confirmed --output-schema CONSTRAINS the output rather than
227
+ # failing); a raw-text retry covers the rare validation/run failure. Default OFF.
228
+ codex_flags=(codex exec
229
+ --ignore-user-config
230
+ --sandbox read-only
231
+ -c approval_policy="never"
232
+ -c model_reasoning_effort="$CODEX_EFFORT"
233
+ -c hide_agent_reasoning=true
234
+ -c model_reasoning_summary=none
235
+ --color never
236
+ -o "$out"
237
+ --json
238
+ -m "$CODEX_MODEL")
239
+ if [[ "${CODEX_REVIEW_SCHEMA:-}" == "1" ]]; then
240
+ schema_file="$(mktemp)"
241
+ cat >"$schema_file" <<'SCHEMA'
242
+ {
243
+ "type": "object",
244
+ "additionalProperties": false,
245
+ "required": ["findings", "verdict"],
246
+ "properties": {
247
+ "findings": {
248
+ "type": "array",
249
+ "items": {
250
+ "type": "object",
251
+ "additionalProperties": false,
252
+ "required": ["severity", "location", "issue", "suggested_change"],
253
+ "properties": {
254
+ "severity": { "type": "string", "enum": ["blocker", "major", "minor", "nit"] },
255
+ "location": { "type": "string" },
256
+ "issue": { "type": "string" },
257
+ "suggested_change": { "type": "string" },
258
+ "evidence": { "type": "string" }
259
+ }
260
+ }
261
+ },
262
+ "verdict": { "type": "string", "enum": ["ship", "revise", "rethink"] },
263
+ "notes": { "type": "string" }
264
+ }
265
+ }
266
+ SCHEMA
267
+ codex_flags+=(--output-schema "$schema_file")
268
+ fi
269
+
270
+ fence_env=(env
271
+ HOME="$fence_home"
272
+ XDG_CONFIG_HOME="$fence_home/.config"
273
+ XDG_CACHE_HOME="$fence_home/.cache"
274
+ XDG_DATA_HOME="$fence_home/.local/share"
275
+ CODEX_HOME="$real_codex_home")
276
+
277
+ # --- Hard wall-clock cap via timeout(1) (gtimeout on macOS) -------------------
278
+ timeout_bin=""
279
+ if command -v timeout >/dev/null 2>&1; then
280
+ timeout_bin="timeout"
281
+ elif command -v gtimeout >/dev/null 2>&1; then
282
+ timeout_bin="gtimeout"
283
+ fi
284
+ if [[ -z "$timeout_bin" ]]; then
285
+ echo "warning: no 'timeout'/'gtimeout' on PATH — running codex WITHOUT a hard wall-clock cap" >&2
286
+ echo " (install coreutils to enable CODEX_HARD_TIMEOUT=$CODEX_HARD_TIMEOUT)." >&2
287
+ fi
288
+
289
+ # Run codex with the current ${codex_cmd[@]}; capture rc, stream → $trace.
290
+ invoke_codex() {
291
+ set +e
292
+ if [[ -n "$timeout_bin" ]]; then
293
+ printf '%s' "$prompt" \
294
+ | "${fence_env[@]}" "$timeout_bin" --kill-after=15s "$CODEX_HARD_TIMEOUT" "${codex_cmd[@]}" >"$trace" 2>&1
295
+ rc=$?
296
+ else
297
+ printf '%s' "$prompt" \
298
+ | "${fence_env[@]}" "${codex_cmd[@]}" >"$trace" 2>&1
299
+ rc=$?
300
+ fi
301
+ set -e
302
+ }
303
+
304
+ codex_cmd=("${codex_flags[@]}" -)
305
+ invoke_codex
306
+
307
+ # Raw-text fallback: if the structured-findings run failed (not a timeout), retry
308
+ # once WITHOUT the schema rather than lose the review — loud, never silent.
309
+ if [[ -n "$schema_file" && $rc -ne 0 && $rc -ne 124 && $rc -ne 137 ]]; then
310
+ echo "warning: the --output-schema run failed (exit $rc) — retrying once without the schema constraint." >&2
311
+ codex_cmd=()
312
+ for _f in "${codex_flags[@]}"; do
313
+ [[ "$_f" == "--output-schema" || "$_f" == "$schema_file" ]] && continue
314
+ codex_cmd+=("$_f")
315
+ done
316
+ codex_cmd+=(-)
317
+ invoke_codex
318
+ fi
319
+
320
+ if [[ $rc -eq 124 || $rc -eq 137 ]]; then
321
+ echo "error: codex review exceeded the hard cap CODEX_HARD_TIMEOUT=${CODEX_HARD_TIMEOUT}s and was terminated." >&2
322
+ echo " Raise CODEX_HARD_TIMEOUT for a known-healthy slow review, or narrow the focus, then retry." >&2
323
+ exit $rc
324
+ fi
325
+ if [[ $rc -ne 0 ]]; then
326
+ echo "error: codex review failed (exit $rc). Last lines of the run trace:" >&2
327
+ tail -n 40 "$trace" >&2
328
+ exit $rc
329
+ fi
330
+
331
+ # Surface the session id (stderr only — a review session is one-shot; never write
332
+ # the shared .codex-last-session sidecar, which is codex-exec's resume target).
333
+ session_id="$(grep -m1 '"type":"thread.started"' "$trace" 2>/dev/null \
334
+ | grep -o '"thread_id":"[^"]*"' | cut -d'"' -f4 || true)"
335
+ if [[ -n "$session_id" ]]; then
336
+ echo "session: $session_id" >&2
337
+ fi
338
+
339
+ if [[ -f "$out" && -s "$out" ]]; then
340
+ cat "$out"
341
+ else
342
+ echo "warning: codex produced no final-message file — printing the run-trace tail instead." >&2
343
+ tail -n 40 "$trace"
344
+ fi