@sabaiway/agent-workflow-kit 10.4.0 → 10.6.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.
- package/CHANGELOG.md +109 -0
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +7 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +69 -17
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +73 -2
- package/bridges/antigravity-cli-bridge/capability.json +2 -2
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +3 -0
- package/bridges/codex-cli-bridge/SKILL.md +8 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +1 -1
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +89 -18
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +55 -2
- package/bridges/codex-cli-bridge/capability.json +2 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/agents/review-lens.md +5 -3
- package/references/modes/agents.md +1 -1
- package/references/modes/core-evidence.md +1 -1
- package/references/modes/procedures.md +11 -5
- package/references/modes/recipes.md +2 -2
- package/references/modes/review-state.md +3 -3
- package/references/modes/set-recipe.md +4 -4
- package/references/modes/status.md +1 -1
- package/references/modes/velocity.md +1 -0
- package/references/templates/orchestration.json +1 -1
- package/tools/bridge-posture.mjs +48 -0
- package/tools/carriers.mjs +21 -9
- package/tools/cheap-agents-read.mjs +86 -24
- package/tools/cheap-agents.mjs +47 -7
- package/tools/commit-guard.mjs +4 -3
- package/tools/detect-backends.mjs +2 -2
- package/tools/direct-run.mjs +3 -0
- package/tools/dispatch-store-read.mjs +269 -0
- package/tools/dispatch-store.mjs +21 -357
- package/tools/fold-scope.mjs +5 -60
- package/tools/grounding.mjs +2 -2
- package/tools/held-session.mjs +165 -0
- package/tools/orchestration-config.mjs +19 -78
- package/tools/orchestration-readme.mjs +70 -0
- package/tools/plan-shape-cli.mjs +112 -0
- package/tools/plan-shape-facts.mjs +204 -0
- package/tools/plan-shape.mjs +348 -0
- package/tools/procedures.mjs +161 -31
- package/tools/recipes.mjs +60 -79
- package/tools/repo-lex.mjs +40 -0
- package/tools/review-roster-resolve.mjs +104 -0
- package/tools/review-roster.mjs +128 -0
- package/tools/review-rounds-cli.mjs +92 -0
- package/tools/review-rounds.mjs +115 -0
- package/tools/review-state.mjs +85 -6
- package/tools/set-recipe-roster.mjs +167 -0
- package/tools/set-recipe.mjs +80 -23
- package/tools/velocity-profile.mjs +8 -22
|
@@ -60,8 +60,14 @@ Receipt:
|
|
|
60
60
|
untracked-not-ignored contents — the review-payload domain; never-committable untracked paths —
|
|
61
61
|
character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked
|
|
62
62
|
symlinks/directories ride as name-only notes) in code mode, the artifact-file
|
|
63
|
-
sha256 in plan mode;
|
|
64
|
-
|
|
63
|
+
sha256 in plan mode; durationS = integer wall-clock seconds from CLI start through verdict parsing
|
|
64
|
+
and the wrapper prints review duration: <n>s; blocking = the count of [blocker] and [major] lines
|
|
65
|
+
(schema mode: findings with either severity; a payload whose findings cannot be counted fails
|
|
66
|
+
the run, no receipt); artifactPath = the normalized realpath on plan receipts only (repo-relative
|
|
67
|
+
inside the work tree, absolute otherwise), while a double quote,
|
|
68
|
+
backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict
|
|
69
|
+
parsed from the mandated literal verdict line (schema mode: the verdict field); always
|
|
70
|
+
fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge,
|
|
65
71
|
factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on
|
|
66
72
|
EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a
|
|
67
73
|
probe review never attests) and equally rejects an unmarked one (silence is not a declaration);
|
|
@@ -107,7 +113,7 @@ Honesty + posture (D4/D5):
|
|
|
107
113
|
refuses pre-spend in every mode.
|
|
108
114
|
|
|
109
115
|
Environment: CODEX_REVIEW_SCHEMA=1 (structured JSON findings), CODEX_HARD_TIMEOUT (seconds, default 1800), CODEX_PROBE=1 (throwaway probe only), AW_REVIEW_RECEIPTS (receipt file override), AW_REVIEW_NONCE (dispatch nonce — mints the finding manifest beside the receipt; the --nonce <n> flag is its plain-argument equivalent).
|
|
110
|
-
Requires at run time: the codex CLI on PATH, a ChatGPT-subscription login, a git work tree with a root AGENTS.md (--help needs none of these).
|
|
116
|
+
Requires at run time: the codex CLI on PATH, a ChatGPT-subscription login, Node >= 22, a git work tree with a root AGENTS.md (--help needs none of these).
|
|
111
117
|
HELP
|
|
112
118
|
exit 0
|
|
113
119
|
;;
|
|
@@ -278,7 +284,7 @@ DEFAULT_CODEX_EFFORT="xhigh"
|
|
|
278
284
|
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
279
285
|
# version (drift-guarded by codex-review.test.mjs against capability.json).
|
|
280
286
|
AW_RECEIPT_BACKEND="codex"
|
|
281
|
-
AW_BRIDGE_VERSION="3.
|
|
287
|
+
AW_BRIDGE_VERSION="3.6.0" # aw-version-anchor
|
|
282
288
|
CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
|
|
283
289
|
CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
|
|
284
290
|
# Generous hard cap for a slow xhigh review (subscription latency varies).
|
|
@@ -550,6 +556,42 @@ receipt_json_scalar() {
|
|
|
550
556
|
if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
|
|
551
557
|
}
|
|
552
558
|
|
|
559
|
+
# refuse_uncarriable_artifact_byte <label> <value> — the receipt encoder escapes nothing beyond its
|
|
560
|
+
# grammar-bound fields, so an artifact path is BOUNDED, never escaped: a byte the line could not
|
|
561
|
+
# carry refuses pre-spend, by name.
|
|
562
|
+
refuse_uncarriable_artifact_byte() {
|
|
563
|
+
local label="$1" value="$2" byte=""
|
|
564
|
+
case "$value" in
|
|
565
|
+
*'"'*) byte="a double quote" ;;
|
|
566
|
+
*'\'*) byte="a backslash" ;;
|
|
567
|
+
*[$'\x01'-$'\x1f'$'\x7f']*) byte="a control" ;;
|
|
568
|
+
esac
|
|
569
|
+
if [[ -z "$byte" ]]; then return 0; fi
|
|
570
|
+
echo "error: $label contains $byte byte, which the receipt encoder cannot carry." >&2
|
|
571
|
+
return 2
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
normalize_artifact_path() {
|
|
575
|
+
local input="$1" normalized
|
|
576
|
+
refuse_uncarriable_artifact_byte "artifact path" "$input" || return 2
|
|
577
|
+
if ! normalized="$(node -e '
|
|
578
|
+
const { realpathSync } = require("node:fs");
|
|
579
|
+
const { isAbsolute, relative, sep } = require("node:path");
|
|
580
|
+
const { spawnSync } = require("node:child_process");
|
|
581
|
+
const absolute = realpathSync(process.argv[1]);
|
|
582
|
+
const git = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
583
|
+
const root = git.status === 0 ? realpathSync(git.stdout.replace(/\r?\n$/, "")) : null;
|
|
584
|
+
const rel = root === null ? null : relative(root, absolute);
|
|
585
|
+
const contained = rel !== null && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
586
|
+
process.stdout.write((contained ? rel : absolute).split(sep).join("/"));
|
|
587
|
+
' -- "$input" 2>/dev/null)"; then
|
|
588
|
+
echo "error: artifact path '$input' could not be normalized to a real path." >&2
|
|
589
|
+
return 2
|
|
590
|
+
fi
|
|
591
|
+
refuse_uncarriable_artifact_byte "normalized artifact path" "$normalized" || return 2
|
|
592
|
+
printf '%s' "$normalized"
|
|
593
|
+
}
|
|
594
|
+
|
|
553
595
|
# STRICT JSON string encoding for the free-form posture fields (model/effort ride env/settings):
|
|
554
596
|
# backslash then double-quote escaped. Control bytes never reach here — the D5 pre-spend gate
|
|
555
597
|
# refuses them, so these two escapes make the encoding total.
|
|
@@ -665,7 +707,7 @@ process.exit(code);
|
|
|
665
707
|
return 0
|
|
666
708
|
}
|
|
667
709
|
|
|
668
|
-
# write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false] [delivery|""] [findings-file]
|
|
710
|
+
# write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false] [delivery|""] [findings-file] [artifact-path] [durationS] [blocking]
|
|
669
711
|
# Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
|
|
670
712
|
# to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
|
|
671
713
|
# git dir by construction, so it is never committable). Fail-safe: every failure here warns loudly
|
|
@@ -679,6 +721,7 @@ process.exit(code);
|
|
|
679
721
|
# minted FIRST (atomic, no-clobber, ORDERED) and a failed mint EXCLUDES the receipt append.
|
|
680
722
|
write_review_receipt() {
|
|
681
723
|
local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6" probe="${7:-false}" delivery="${8:-}" findings_file="${9:-}"
|
|
724
|
+
local artifact_path="${10:-}" duration_s="${11:-0}" blocking="${12:-0}"
|
|
682
725
|
local receipts="${AW_REVIEW_RECEIPTS:-}"
|
|
683
726
|
if [[ -z "$receipts" ]]; then
|
|
684
727
|
local receipt_git_dir
|
|
@@ -696,16 +739,16 @@ write_review_receipt() {
|
|
|
696
739
|
fi
|
|
697
740
|
# A nonce-SUPPLIED dispatch stamps its nonce into the receipt too (the flow round-land matcher
|
|
698
741
|
# requires exact {backend, nonce} equality — dispatch identity end-to-end); the nonce is
|
|
699
|
-
# grammar-safe by the pre-spend check
|
|
700
|
-
|
|
701
|
-
local line probe_field=',"probe":false' delivery_field="" nonce_field=""
|
|
742
|
+
# grammar-safe by the pre-spend check; a nonce-less receipt adds NO nonce field.
|
|
743
|
+
local line probe_field=',"probe":false' artifact_path_field="" delivery_field="" nonce_field=""
|
|
702
744
|
if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
|
|
745
|
+
if [[ -n "$artifact_path" ]]; then artifact_path_field=",\"artifactPath\":\"$artifact_path\""; fi
|
|
703
746
|
if [[ -n "$delivery" ]]; then delivery_field=",\"delivery\":\"$delivery\""; fi
|
|
704
747
|
if [[ -n "${AW_REVIEW_NONCE:-}" ]]; then nonce_field=",\"nonce\":\"${AW_REVIEW_NONCE}\""; fi
|
|
705
|
-
line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s,"posture":%s%s%s}' \
|
|
748
|
+
line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s,"durationS":%s,"blocking":%s%s,"posture":%s%s%s}' \
|
|
706
749
|
"$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
|
|
707
750
|
"$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
|
|
708
|
-
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)" "$delivery_field" "$nonce_field")"
|
|
751
|
+
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$duration_s" "$blocking" "$artifact_path_field" "$(posture_json)" "$delivery_field" "$nonce_field")"
|
|
709
752
|
if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
|
|
710
753
|
echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
|
|
711
754
|
echo " the review-state gate will read the current tree as un-receipted." >&2
|
|
@@ -835,8 +878,27 @@ if [[ "$nonce_flag_set" == "1" ]]; then
|
|
|
835
878
|
fi
|
|
836
879
|
set -- "${scan_args[@]+"${scan_args[@]}"}"
|
|
837
880
|
|
|
881
|
+
# Pre-spend node probe (the agy twin's posture): the plan artifact-path normalization, the schema
|
|
882
|
+
# parse and the nonce finding-manifest mint run in node, so a missing or too-old node refuses by
|
|
883
|
+
# name BEFORE any codex run — never a spent run with the wrong diagnostic.
|
|
884
|
+
AW_MIN_NODE_MAJOR=22
|
|
885
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
886
|
+
echo "error: 'node' is not on PATH. This review runs node for the plan artifact-path normalization," >&2
|
|
887
|
+
echo " the CODEX_REVIEW_SCHEMA parse and the nonce finding-manifest mint (family floor: Node >= ${AW_MIN_NODE_MAJOR})." >&2
|
|
888
|
+
echo " Refusing BEFORE any run is spent. Install Node >= ${AW_MIN_NODE_MAJOR}, then re-run." >&2
|
|
889
|
+
exit 127
|
|
890
|
+
fi
|
|
891
|
+
aw_node_version="$(node --version 2>/dev/null || true)"
|
|
892
|
+
aw_node_major="${aw_node_version#v}"; aw_node_major="${aw_node_major%%.*}"
|
|
893
|
+
if [[ ! "$aw_node_major" =~ ^[0-9]+$ ]] || (( aw_node_major < AW_MIN_NODE_MAJOR )); then
|
|
894
|
+
echo "error: node '${aw_node_version:-<unreadable>}' is below the family floor (Node >= ${AW_MIN_NODE_MAJOR}) this review needs." >&2
|
|
895
|
+
echo " Refusing BEFORE any run is spent. Upgrade Node to >= ${AW_MIN_NODE_MAJOR}, then re-run." >&2
|
|
896
|
+
exit 127
|
|
897
|
+
fi
|
|
898
|
+
|
|
838
899
|
REVIEW_ARTIFACT=""
|
|
839
900
|
REVIEW_FINGERPRINT=""
|
|
901
|
+
REVIEW_ARTIFACT_PATH=""
|
|
840
902
|
|
|
841
903
|
case "$mode" in
|
|
842
904
|
plan)
|
|
@@ -850,6 +912,7 @@ case "$mode" in
|
|
|
850
912
|
echo "error: unexpected arguments after plan file: $*" >&2
|
|
851
913
|
exit 2
|
|
852
914
|
fi
|
|
915
|
+
if ! REVIEW_ARTIFACT_PATH="$(normalize_artifact_path "$target")"; then exit 2; fi
|
|
853
916
|
# Plan-mode receipt identity: the artifact-file sha256 (informational-only for the tree checker).
|
|
854
917
|
REVIEW_ARTIFACT="plan"
|
|
855
918
|
REVIEW_FINGERPRINT="$(sha256_stdin <"$target" || true)"
|
|
@@ -974,6 +1037,7 @@ invoke_codex() {
|
|
|
974
1037
|
}
|
|
975
1038
|
|
|
976
1039
|
codex_cmd=("${codex_flags[@]}" -)
|
|
1040
|
+
review_started_at="$(date +%s)"
|
|
977
1041
|
invoke_codex
|
|
978
1042
|
|
|
979
1043
|
# Raw-text fallback: if the structured-findings run failed (not a timeout), retry
|
|
@@ -1028,22 +1092,29 @@ fi
|
|
|
1028
1092
|
verdict=""
|
|
1029
1093
|
if [[ -f "$out" && -s "$out" ]]; then
|
|
1030
1094
|
if [[ "$verdict_parse_mode" == "schema" ]]; then
|
|
1031
|
-
# STRUCTURAL parse
|
|
1032
|
-
#
|
|
1033
|
-
# inside a findings string never substitutes, malformed/out-of-enum
|
|
1034
|
-
# failed-run arm)
|
|
1035
|
-
|
|
1095
|
+
# STRUCTURAL parse: the TOP-LEVEL `verdict` inside the closed enum AND `findings` an array whose
|
|
1096
|
+
# every item carries a severity inside the closed enum, read ONCE — a legal multiline layout
|
|
1097
|
+
# parses, a decoy "verdict" inside a findings string never substitutes, malformed / out-of-enum /
|
|
1098
|
+
# uncountable stays empty (the D4 failed-run arm): a payload the wrapper cannot count is never
|
|
1099
|
+
# receipted as "0 blocking". Node >=22 is a family floor, so the one-liner adds no dependency.
|
|
1100
|
+
schema_parse="$(node -e 'try{const v=JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8"));const sev=["blocker","major","minor","nit"];if(["ship","revise","rethink"].includes(v.verdict)&&Array.isArray(v.findings)&&v.findings.every((f)=>f!==null&&typeof f==="object"&&sev.includes(f.severity)))process.stdout.write(v.verdict+"\t"+v.findings.filter((f)=>f.severity==="blocker"||f.severity==="major").length);}catch{}' "$out" 2>/dev/null || true)"
|
|
1101
|
+
verdict="${schema_parse%%$'\t'*}"
|
|
1102
|
+
blocking_count="${schema_parse#*$'\t'}"
|
|
1036
1103
|
else
|
|
1037
1104
|
verdict="$(sed -nE 's/^Verdict: (ship|revise|rethink)[[:space:]]*$/\1/p' "$out" | tail -n1)"
|
|
1105
|
+
blocking_count="$(awk '/^\[(blocker|major)\]([[:space:]]|$)/{n++} END{print n+0}' "$out")"
|
|
1038
1106
|
fi
|
|
1039
1107
|
fi
|
|
1040
1108
|
if [[ -z "$verdict" ]]; then
|
|
1041
1109
|
echo "error: the review produced no recognized 'Verdict: <ship|revise|rethink>' line (empty or" >&2
|
|
1042
|
-
echo " verdict-less final message
|
|
1043
|
-
echo "
|
|
1110
|
+
echo " verdict-less final message; in schema mode also a findings array the wrapper cannot" >&2
|
|
1111
|
+
echo " count) — a FAILED review; NO receipt was written. Re-run the review; if it recurs," >&2
|
|
1112
|
+
echo " inspect the captured output for what the model produced." >&2
|
|
1044
1113
|
exit 4
|
|
1045
1114
|
fi
|
|
1115
|
+
review_duration_s=$(( $(date +%s) - review_started_at ))
|
|
1046
1116
|
# codex is grounded by construction (AGENTS.md auto-merge + the precomputed change set): grounded
|
|
1047
1117
|
# true, factsHash null (native grounding — no separate facts payload exists). Every codex run is a
|
|
1048
1118
|
# full fresh run (one-shot, no resume) → fresh:true. $REVIEW_PROBE marks a guards-relaxed run.
|
|
1049
|
-
write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" true "" "$REVIEW_PROBE" "" "$out"
|
|
1119
|
+
write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" true "" "$REVIEW_PROBE" "" "$out" "$REVIEW_ARTIFACT_PATH" "$review_duration_s" "$blocking_count"
|
|
1120
|
+
echo "review duration: ${review_duration_s}s" >&2
|
|
@@ -315,6 +315,25 @@ describe('codex-review.sh — hard timeout (1.3)', { concurrency: 2 }, () => {
|
|
|
315
315
|
assert.match(r.stderr, /hard-timeout preflight fails CLOSED/);
|
|
316
316
|
assert.equal(r.capStdin, '', 'codex must NOT be invoked when the preflight refuses');
|
|
317
317
|
});
|
|
318
|
+
|
|
319
|
+
it('refuses by name BEFORE any run when node is missing or below the family floor', async () => {
|
|
320
|
+
const missing = makeSandbox();
|
|
321
|
+
const r1 = await run(missing, { path: `${missing.bin}:${farmFor(['node'])}` });
|
|
322
|
+
rmSync(missing.root, { recursive: true, force: true });
|
|
323
|
+
assert.equal(r1.status, 127);
|
|
324
|
+
assert.match(r1.stderr, /'node' is not on PATH/);
|
|
325
|
+
assert.equal(r1.capStdin, '', 'codex must NOT be invoked without node');
|
|
326
|
+
|
|
327
|
+
const old = makeSandbox();
|
|
328
|
+
const shim = join(old.root, 'node-shim');
|
|
329
|
+
mkdirSync(shim);
|
|
330
|
+
writeFileSync(join(shim, 'node'), '#!/bin/sh\necho v18.0.0\n', { mode: 0o755 });
|
|
331
|
+
const r2 = await run(old, { path: `${shim}:${old.bin}:${farmFor(['node'])}` });
|
|
332
|
+
rmSync(old.root, { recursive: true, force: true });
|
|
333
|
+
assert.equal(r2.status, 127);
|
|
334
|
+
assert.match(r2.stderr, /node 'v18\.0\.0' is below the family floor/);
|
|
335
|
+
assert.equal(r2.capStdin, '', 'codex must NOT be invoked on a too-old node');
|
|
336
|
+
});
|
|
318
337
|
});
|
|
319
338
|
|
|
320
339
|
describe('codex-review.sh — precomputed diff for code mode (2.1)', { concurrency: 2 }, () => {
|
|
@@ -901,7 +920,7 @@ describe('codex-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)
|
|
|
901
920
|
// The normative fixture: the AD-038 shape + the D3 self-declaring probe marker (field VALUES with
|
|
902
921
|
// dynamic content are asserted by shape):
|
|
903
922
|
const RECEIPT_FIXTURE = JSON.parse(
|
|
904
|
-
'{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.3.0","timestamp":"2026-07-03T12:00:00Z","probe":false,"posture":{"model":"<m>","effort":"<e>","tier":null}}',
|
|
923
|
+
'{"schema":1,"artifact":"code","fresh":true,"fingerprint":"<sha256hex>","backend":"codex","verdict":"revise","grounded":true,"factsHash":null,"wrapperVersion":"2.3.0","timestamp":"2026-07-03T12:00:00Z","probe":false,"durationS":0,"blocking":0,"posture":{"model":"<m>","effort":"<e>","tier":null}}',
|
|
905
924
|
);
|
|
906
925
|
const RECEIPTS_REL = join('.git', 'agent-workflow-review-receipts.jsonl');
|
|
907
926
|
const readReceipts = (repo) => {
|
|
@@ -913,6 +932,7 @@ const sha256Hex = (buf) => createHash('sha256').update(buf).digest('hex');
|
|
|
913
932
|
|
|
914
933
|
describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, () => {
|
|
915
934
|
it('a successful code review appends ONE fixture-shaped receipt (text-mode verdict parse)', async () => {
|
|
935
|
+
// spec:plan-review-loop/S20
|
|
916
936
|
const sb = makeSandbox();
|
|
917
937
|
const r = await run(sb, { env: { CODEX_FAKE_FINAL: '[major] — a.txt:1 — x — y\nVerdict: revise' } });
|
|
918
938
|
const receipts = readReceipts(sb.repo);
|
|
@@ -931,6 +951,11 @@ describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, ()
|
|
|
931
951
|
assert.equal(receipt.factsHash, null, 'native grounding — no separate facts payload');
|
|
932
952
|
assert.equal(receipt.wrapperVersion, MANIFEST.version, 'receipt version ⟷ capability.json version');
|
|
933
953
|
assert.match(receipt.timestamp, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
|
|
954
|
+
assert.ok(Number.isInteger(receipt.durationS) && receipt.durationS >= 0);
|
|
955
|
+
assert.equal(receipt.blocking, 1, 'one [major] line is blocking in the codex vocabulary');
|
|
956
|
+
assert.equal(Object.hasOwn(receipt, 'artifactPath'), false, 'a code receipt carries no artifact path');
|
|
957
|
+
assert.match(r.stderr, /review duration: \d+s/);
|
|
958
|
+
for (const field of ['durationS', 'blocking', 'artifactPath']) assert.ok(REVIEW_CONTRACT.receipt.includes(`${field} = `), `the manifest receipt contract declares ${field}`);
|
|
934
959
|
});
|
|
935
960
|
|
|
936
961
|
// The probe marker (BRIDGE-MODES-CATALOG, D3): a CODEX_PROBE=1 review runs with the
|
|
@@ -986,12 +1011,26 @@ describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, ()
|
|
|
986
1011
|
it('CODEX_REVIEW_SCHEMA=1 reads the schema verdict field', async () => {
|
|
987
1012
|
const sb = makeSandbox();
|
|
988
1013
|
const r = await run(sb, {
|
|
989
|
-
env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{"findings":[],"verdict":"ship","notes":"ok"}' },
|
|
1014
|
+
env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: '{"findings":[{"severity":"blocker"},{"severity":"major"},{"severity":"minor"}],"verdict":"ship","notes":"ok"}' },
|
|
990
1015
|
});
|
|
991
1016
|
const receipts = readReceipts(sb.repo);
|
|
992
1017
|
rmSync(sb.root, { recursive: true, force: true });
|
|
993
1018
|
assert.equal(r.status, 0, r.stderr);
|
|
994
1019
|
assert.equal(receipts[0].verdict, 'ship');
|
|
1020
|
+
assert.equal(receipts[0].blocking, 2, 'schema mode counts blocker and major findings only');
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it('schema mode: findings the wrapper cannot count fail the run with NO receipt', async () => {
|
|
1024
|
+
// spec:plan-review-loop/S28
|
|
1025
|
+
for (const payload of ['{"findings":"nope","verdict":"ship"}', '{"findings":[{"severity":"huge"}],"verdict":"ship"}']) {
|
|
1026
|
+
const sb = makeSandbox();
|
|
1027
|
+
const r = await run(sb, { env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: payload } });
|
|
1028
|
+
const receipts = readReceipts(sb.repo);
|
|
1029
|
+
rmSync(sb.root, { recursive: true, force: true });
|
|
1030
|
+
assert.equal(r.status, 4, payload);
|
|
1031
|
+
assert.match(r.stderr, /findings array the wrapper cannot/, payload);
|
|
1032
|
+
assert.equal(receipts.length, 0, `${payload}: an uncountable payload never reaches the receipt store`);
|
|
1033
|
+
}
|
|
995
1034
|
});
|
|
996
1035
|
|
|
997
1036
|
it('no parseable verdict is a FAILED run — never recorded as "unknown" (D4 owns the arm)', async () => {
|
|
@@ -1004,6 +1043,7 @@ describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, ()
|
|
|
1004
1043
|
});
|
|
1005
1044
|
|
|
1006
1045
|
it('plan mode: artifact "plan", fingerprint = the artifact-file sha256', async () => {
|
|
1046
|
+
// spec:plan-review-loop/S21
|
|
1007
1047
|
const sb = makeSandbox();
|
|
1008
1048
|
const planBytes = readFileSync(join(sb.repo, 'plan.md'));
|
|
1009
1049
|
const r = await run(sb, { args: ['plan', 'plan.md'], env: { CODEX_FAKE_FINAL: 'Verdict: ship' } });
|
|
@@ -1012,6 +1052,19 @@ describe('codex-review.sh — review receipts (AD-038)', { concurrency: 2 }, ()
|
|
|
1012
1052
|
assert.equal(r.status, 0, r.stderr);
|
|
1013
1053
|
assert.equal(receipts[0].artifact, 'plan');
|
|
1014
1054
|
assert.equal(receipts[0].fingerprint, sha256Hex(planBytes), 'plan fingerprint = file sha256');
|
|
1055
|
+
assert.equal(receipts[0].artifactPath, 'plan.md', 'realpath inside the work tree normalizes repo-relative');
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
it('plan mode refuses an uncarriable artifact-path byte before codex runs', async () => {
|
|
1059
|
+
for (const [name, diagnostic] of [['bad"name.md', /double quote/], ['bad\\name.md', /backslash/], ['bad\nname.md', /control byte/]]) {
|
|
1060
|
+
const sb = makeSandbox();
|
|
1061
|
+
writeFileSync(join(sb.repo, name), '# plan\n');
|
|
1062
|
+
const r = await run(sb, { args: ['plan', name] });
|
|
1063
|
+
rmSync(sb.root, { recursive: true, force: true });
|
|
1064
|
+
assert.equal(r.status, 2, name);
|
|
1065
|
+
assert.match(r.stderr, diagnostic, name);
|
|
1066
|
+
assert.equal(r.capStdin, '', `${name}: refusal is pre-spend`);
|
|
1067
|
+
}
|
|
1015
1068
|
});
|
|
1016
1069
|
|
|
1017
1070
|
it('AW_REVIEW_RECEIPTS overrides the receipt destination', async () => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "codex-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.6.0",
|
|
7
7
|
"posture": { "model": "gpt-5.6-sol", "effort": "xhigh", "tier": null },
|
|
8
8
|
"provides": ["execute", "review"],
|
|
9
9
|
"roles": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"grounding": "automatic — the wrapper precomputes the full working-tree change set (repo map, status, diffs, untracked contents) and codex auto-merges the root AGENTS.md; no grounding flags",
|
|
51
51
|
"continue": [],
|
|
52
|
-
"receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, and a posture value carrying control bytes refuses pre-spend in every mode; a run whose final message carries NO recognized 'Verdict: <ship|revise|rethink>' line — empty or missing output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
|
|
52
|
+
"receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of [blocker] and [major] lines (schema mode: findings with either severity; a payload whose findings cannot be counted fails the run, no receipt); artifactPath = the normalized realpath on plan receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, and a posture value carrying control bytes refuses pre-spend in every mode; a run whose final message carries NO recognized 'Verdict: <ship|revise|rethink>' line — empty or missing output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
|
|
53
53
|
"notes": [
|
|
54
54
|
"the review posture banner appends a banner-only timeout=<duration> field — exactly the duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and the field never enters the receipt posture or the D5 banner↔receipt parity",
|
|
55
55
|
"quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts"
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-kit",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.6.0",
|
|
4
4
|
"description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: review-lens
|
|
3
|
-
description: Read-only ADDITIONAL review lens — an
|
|
3
|
+
description: Read-only ADDITIONAL review lens — an independent opinion additional to the orchestrator's own review and to whatever configured backends ran; never a substitute for either. Grants no shell, so it can never turn a review into a wave of approval prompts. Never for writing code or running gates.
|
|
4
4
|
model: sonnet
|
|
5
5
|
effort: high
|
|
6
6
|
tools: Read, Grep, Glob
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
You are an ADDITIONAL, INDEPENDENT review lens.
|
|
10
|
-
|
|
9
|
+
You are an ADDITIONAL, INDEPENDENT review lens. Your opinion is additional to the orchestrator's own
|
|
10
|
+
review and to whatever configured backends ran; never a substitute for either. Find what those
|
|
11
|
+
reviews MISSED, rather than restating what they found.
|
|
11
12
|
|
|
12
13
|
You have `Read`, `Grep` and `Glob` and **no `Bash`**. That is deliberate and it is the whole point of
|
|
13
14
|
this vehicle: a read-only fan-out that can reach for a shell turns one review into a wave of approval
|
|
@@ -37,3 +38,4 @@ How to review:
|
|
|
37
38
|
Output: a numbered list of findings, most severe first, each as
|
|
38
39
|
`[severity] — file:line — the defect — the failure scenario — the fix direction`.
|
|
39
40
|
Then one line: `no further findings` or the angles you deliberately did not cover.
|
|
41
|
+
Verdict: ship | ship with nits | revise | rethink
|
|
@@ -12,7 +12,7 @@ The opt-in **subagent-vehicle writer** — the family's second `.claude/` writer
|
|
|
12
12
|
|
|
13
13
|
Writing code, running gates, and user-facing copy never move to these four vehicles.
|
|
14
14
|
|
|
15
|
-
**The fifth vehicle, `executor`, is the ONE full-tool one** (`model: opus`, every tool — a shell included). It is the instrument behind the **Subagent** recipe, dispatched ONLY for a **bounded, file-disjoint slice** the orchestrator verifies afterwards by running its suites: a slice of execution (`plan-execution.execute`), a plan/contract authoring brief (`plan-authoring.author`), or a bounded chore (`routine.carrier`). It **never commits** (no git write at all), is **never a review backend**, is **never a bridge substitute**, and is **never dispatched for read-only work** — a sweep, an inventory or a review rides a read-only vehicle instead.
|
|
15
|
+
**The fifth vehicle, `executor`, is the ONE full-tool one** (`model: opus`, every tool — a shell included). It is the instrument behind the **Subagent** recipe, dispatched ONLY for a **bounded, file-disjoint slice** the orchestrator verifies afterwards by running its suites: a slice of execution (`plan-execution.execute`), a plan/contract authoring brief (`plan-authoring.author`), an authoring-round fold (`plan-authoring.fold`), or a bounded chore (`routine.carrier`). It **never commits** (no git write at all), is **never a review backend**, is **never a bridge substitute**, and is **never dispatched for read-only work** — a sweep, an inventory or a review rides a read-only vehicle instead.
|
|
16
16
|
|
|
17
17
|
**Its readiness is the vehicle FILE.** `surveyExecutorVehicle` answers exactly one of four states: **`placed`** (the bundled bytes at `.claude/agents/executor.md`), **`customized`** (a readable regular file whose frontmatter names `executor` and whose tools are not read-only), **`unusable`** (a symlink, a non-regular or unreadable file, or one failing that check — with the reason), **`missing`**. `placed` and `customized` are dispatchable; on `unusable` or `missing` a `subagent` slot **degrades to Solo** with a reason naming this mode's apply command. `/agent-workflow-kit status` shows the state in its `agents` block and the deployment advisor raises it when a configured slot names `subagent` and the vehicle is `unusable` or `missing` — that item is **`executor-vehicle`** (attention-class: a configured declaration that is broken), a DIFFERENT capability from this mode's placement offer (`agents`), which converges the moment nothing is left to place and therefore can never observe a present-but-unusable vehicle. It is a claim about the FILE, never about the host: a Claude Code lane, like this mode itself.
|
|
18
18
|
|
|
@@ -7,7 +7,7 @@ The ONE **loop-evidence writer** (strip-the-kit D6a/D7) — every core evidence
|
|
|
7
7
|
Run `node ${CLAUDE_SKILL_DIR}/tools/core-evidence.mjs <verb> …`:
|
|
8
8
|
|
|
9
9
|
1. **`red-proof "<test-file>#<test-name-pattern>"`** — the D3(c) observed-red DECLARATION, minted **BEFORE a bugfix is applied**: it runs the named test N times (default 3, `AW_CORE_EVIDENCE_RERUNS`; per-run timeout `AW_CORE_EVIDENCE_TIMEOUT_S`) on the CURRENT (pre-fix) tree, requires red N/N (observed green / unresolvable / mixed / timed-out are DISTINGUISHED refusals — nothing is written; mixed/timeout is QUARANTINE, no override lane), and records { testId · repo-relative file · content sha256 (custody) · N/N red · base = HEAD sha · the pre-fix tree fingerprint }. The final run (`coverage-check --check`) then requires every current-base record's test green N/N with the hash unchanged; a record whose pre-fix fingerprint EQUALS the current tree never satisfies (nothing changed — reuse/forgery); a commit expires records (base moves); editing a bound test file means re-observing red (the new record supersedes the old — same {base, testId} key).
|
|
10
|
-
2. **`degrade --backend <name> --reason "<why>"`** — the ONLY escape from a required review backend (D3(b)): an explicit per-backend record at the CURRENT fingerprint. Never all backends: the review gate still requires ≥1 non-degraded ship-class receipt whenever ≥1 backend is configured. Any tree edit that MOVES the fingerprint re-stales
|
|
10
|
+
2. **`degrade --backend <name> --reason "<why>"`** — the ONLY escape from a required review backend (D3(b)): an explicit per-backend record at the CURRENT fingerprint. The backend name also has a second reading: the execute wrapper name `codex-exec` is the accepted-replacement escape honoured by the held-session arm; for the held-session arm the record binds the substituted return's `postTreeDigest` and keeps standing after later folds move the tree; the record form and writer are unchanged, and the review receipt id `codex` never grants that escape. Never all review backends: the review gate still requires ≥1 non-degraded ship-class receipt whenever ≥1 backend is configured. Any tree edit that MOVES the fingerprint re-stales a review-backend degrade record — which is every edit the payload can see, and no more: a change the payload cannot see (a gitlink hidden from `git diff` by an ignore configuration) leaves the record standing. **The per-tree claim has one honest exception, and it is not a corner case:** a degrade minted while the work tree is CLEAN binds the CONTENT-FREE fingerprint — the one value every clean moment of every repository shares — so it identifies no working state and, since the record carries no base, no moment either. Such a record is therefore OUTSIDE the `#64` degrade-before-final ordering rung (`flow-check` records the skip as an advisory naming how many were skipped), because the ORDER of two records that cannot be shown to belong to the same moment states nothing about any tree. The writer still accepts it — refusing would strand the one lane that honestly records an unavailable backend during a plan round, which attests no tree at all. Closing the exception needs a durable `base` on the record, which is the queued store migration.
|
|
11
11
|
3. **`summary`** — the ONE stateless end-of-loop render (D6): gate result from the latest final attempt, per-backend verdicts from the review receipts, red-proof outcomes, degrade records. Computed from the stores at read time — no ledger, no rounds, nothing remembered. A malformed store exits non-zero and WITHHOLDS the affected section, never renders a partial as complete. The final-run line NAMES an absent coverage verdict from the `coverage` token the run RECORDED (an ADDITIVE optional field on the `final` record — the closed `certified | not-run | unknown` set, never `none`, and `certified` requires a bound `lcovSha256`): GREEN never renders unqualified over a run that issued no verdict. A LEGACY receipt carrying no token and no lcov digest is named as exactly that, never as a claim about what it read — the withheld verdict travels here too, as DETAIL beside the unchanged status word.
|
|
12
12
|
|
|
13
13
|
**Promptless writer lane (D7):** both verbs ride plain `node ${CLAUDE_SKILL_DIR}/tools/core-evidence.mjs …` invocations — surface the paste-ready allow entries once (`.claude/settings.json`), and a full loop transcript runs at zero writer approval prompts.
|
|
@@ -6,19 +6,21 @@ Read-only **activity-procedures advisor**. Answers *"what are the steps of this
|
|
|
6
6
|
|
|
7
7
|
The three activities (canon in the **installed engine**, `references/procedures.md`; the registry is `tools/carriers.mjs`):
|
|
8
8
|
|
|
9
|
-
- **`plan-authoring`** (slots: `author`, `review`) — research → draft (the resolved `author` carrier drafts: Solo writes it, Subagent drafts from the orchestrator's brief) → self-review → **review {recipe}** → fold/loop → present for approval; enforce the mandatory Cleanup.
|
|
9
|
+
- **`plan-authoring`** (slots: `author`, `fold`, `review`) — research → draft (the resolved `author` carrier drafts: Solo writes it, Subagent drafts from the orchestrator's brief) → self-review with the readers sweep first → **review {recipe}** → consult, then fold/loop through the resolved `fold` carrier → present for approval; enforce the mandatory Cleanup.
|
|
10
10
|
- **`plan-execution`** (slots: `execute`, `review`) — per ledger row: resolve the recipe → if Delegated, dispatch execution first; if Subagent, dispatch file-disjoint slices to the executor vehicle and verify each returned slice → implement → self-review → **review {recipe}** → gates → commit boundary; after the last row, the project-declared stages and Phase: Cleanup run as rows of their own.
|
|
11
11
|
- **`routine`** (slots: `carrier`, `parallel`) — name the chore and its file-disjoint slices → resolve → carry it (Solo, or Subagent: a read-only slice rides a placed read-only vehicle, a write-capable one rides the executor; `parallel` on → concurrently) → verify every returned slice; the commit boundary is unchanged.
|
|
12
12
|
|
|
13
|
-
Value sets per slot type: `review` accepts `solo | reviewed | council`; `execute` accepts `solo | delegated | subagent`; a carrier slot (`author`, `carrier`) accepts `solo | subagent`; `parallel` is a **switch**, `on | off`, resolved outside the recipe lattice. Defaults when the config is silent: `review` → Reviewed if a review-capable backend is ready, else Solo; `author`, `execute`, `carrier` → Solo (a placed executor never flips a default); `parallel` → on. The orchestrator owns every commit; every other carrier — a bridge or a subagent — never commits.
|
|
13
|
+
Value sets per slot type: `review` accepts `solo | reviewed | council`; `execute` accepts `solo | delegated | subagent`; a carrier slot (`author`, `fold`, `carrier`) accepts `solo | subagent`; `parallel` is a **switch**, `on | off`, resolved outside the recipe lattice. Defaults when the config is silent: `review` → Reviewed if a review-capable backend is ready, else Solo; `author`, `fold`, `execute`, `carrier` → Solo (a placed executor never flips a default); `parallel` → on. The orchestrator owns every commit; every other carrier — a bridge or a subagent — never commits.
|
|
14
14
|
|
|
15
15
|
Run **`node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity> [--override <slot>=<value>]… [--json]`**. It reads the activity's steps live from the engine and prints them **verbatim**, then the **resolved effective recipe per slot** from the per-project config + the read-only backend detector:
|
|
16
16
|
|
|
17
|
-
1. **Config = `docs/ai/orchestration.json`** — strict JSON, **agent-writable via `/agent-workflow-kit set-recipe` (`${CLAUDE_SKILL_DIR}/references/modes/set-recipe.md`) OR hand-edited** (the kit reads + validates it; `procedures`/`recipes` stay read-only — the writer is `set-recipe`). Shape: `{ "<activity>": { "<slot>": "<value>" } }`; all slots optional (an absent slot → its computed default, stated); an optional `"_README"` string is allowed + ignored; a `"flow"` object must carry the NUMERIC `"schema": 1` (the kit's accepted flow schema version) and validates against the CLOSED structural schema-1 key set (unknown flow keys and malformed per-key shapes refuse loudly; deep environment floors stay on the `set-flow` arming path). `review` accepts `solo|reviewed|council`; `execute` accepts `solo|delegated|subagent`; a carrier slot (`author`, `carrier`) accepts `solo|subagent`; `parallel` accepts `on|off`. Seeded by `init` (a user-editable template) — see `${CLAUDE_SKILL_DIR}/references/modes/bootstrap.md`.
|
|
18
|
-
2. **Default resolution (config silent):** `review` → Reviewed if any review-capable backend is `ready`, else Solo (never Council by default); `execute`, `author`, `carrier` → Solo (Delegated and Subagent are opt-in; a placed executor never flips a default); `parallel` → on. **Degradation:** a config/computed default degrades **gracefully with a stated reason** (Council → Reviewed → Solo; Delegated → Solo; Subagent → Solo when the executor vehicle is missing or unusable, the reason naming the apply command; a switch never degrades); a per-run **`--override <slot>=<value>`** that can't be satisfied degrades **loudly** (a flagged warning, so you tell the user) — but is **still exit 0** (a valid request that gracefully degraded).
|
|
17
|
+
1. **Config = `docs/ai/orchestration.json`** — strict JSON, **agent-writable via `/agent-workflow-kit set-recipe` (`${CLAUDE_SKILL_DIR}/references/modes/set-recipe.md`) OR hand-edited** (the kit reads + validates it; `procedures`/`recipes` stay read-only — the writer is `set-recipe`). Shape: `{ "<activity>": { "<slot>": "<value>" } }`; all slots optional (an absent slot → its computed default, stated); an optional `"_README"` string is allowed + ignored; a `"flow"` object must carry the NUMERIC `"schema": 1` (the kit's accepted flow schema version) and validates against the CLOSED structural schema-1 key set (unknown flow keys and malformed per-key shapes refuse loudly; deep environment floors stay on the `set-flow` arming path). `review` accepts `solo|reviewed|council`; `execute` accepts `solo|delegated|subagent`; a carrier slot (`author`, `fold`, `carrier`) accepts `solo|subagent`; `parallel` accepts `on|off`. Seeded by `init` (a user-editable template) — see `${CLAUDE_SKILL_DIR}/references/modes/bootstrap.md`.
|
|
18
|
+
2. **Default resolution (config silent):** `review` → Reviewed if any review-capable backend is `ready`, else Solo (never Council by default); `execute`, `author`, `fold`, `carrier` → Solo (Delegated and Subagent are opt-in; a placed executor never flips a default); `parallel` → on. **Degradation:** a config/computed default degrades **gracefully with a stated reason** (Council → Reviewed → Solo; Delegated → Solo; Subagent → Solo when the executor vehicle is missing or unusable, the reason naming the apply command; a switch never degrades); a per-run **`--override <slot>=<value>`** that can't be satisfied degrades **loudly** (a flagged warning, so you tell the user) — but is **still exit 0** (a valid request that gracefully degraded).
|
|
19
19
|
3. **Exit codes:** `0` success; `2` usage (unknown `<activity>` / bad `--override` — a bare `--override <recipe>`, an unknown slot, a value outside the slot's list, or a duplicate slot); `1` config error (malformed / schema-invalid / unreadable `orchestration.json`) **or** engine error (the installed engine is absent / invalid / **too old** to ship `references/procedures.md` — upgrade it with `npx @sabaiway/agent-workflow-engine@latest init`). A `1`/`2` failure is loud (`path: reason`), never a silent fallback. **Lagging-kit honesty:** a kit predating the `"flow"` key that reads a config carrying one fails this config load loudly (exit `1`, reddening its full gate matrix); the `set-flow` arming path now enforces the declared `kitMinVersion` floor with a null-guarded comparison (an unparseable version never passes), while tolerate-first ordering remains the only protection for readers older than the `"flow"` key itself — no in-config floor can reach a kit that dies on the unknown key.
|
|
20
20
|
|
|
21
|
-
**The dispatch form (a slot resolved to `subagent`).** A carrier that is only NAMED is a name, not an instruction, so after such a slot's resolved-recipe line the advisor prints — indented under it, in place of the one-line vehicle mention — the form the dispatch must take, in five parts: (1) what a SLICE is for THIS
|
|
21
|
+
**The dispatch form (a slot resolved to `subagent`).** A carrier that is only NAMED is a name, not an instruction, so after such a slot's resolved-recipe line the advisor prints — indented under it, in place of the one-line vehicle mention — the form the dispatch must take, in five parts: (1) what a SLICE is for THIS slot — for `execute`, a set of file-disjoint ledger rows, wording copied verbatim where wording is a red line; for `author`, a brief naming the goal, the governing spec(s) and the ledger constraints, whose returned draft the orchestrator reviews as its own; for `fold`, the round's findings with their dispositions, whose edits the orchestrator follows with the self-consistency read; for `routine.carrier`, a bounded mechanical task, where a read-only chore (a sweep, gate triage) rides its placed read-only vehicle — or is carried solo with a stated reason when that vehicle is absent — a write-capable one (a regeneration, a fixture build) rides the executor, and the changelog stays the orchestrator's; (2) the vehicle and its surveyed state — `.claude/agents/executor.md` (`placed` / `customized` / `unusable` / `missing`, from the same survey `status` and the advisor read) — dispatched **in the background**; (3) that the orchestrator **verifies every returned slice by running its suites itself**; (4) that the subagent is **never told to commit, never a review backend, never a bridge substitute**; (5) the **honest limit** — a Claude Code lane: on a host that cannot dispatch the vehicle, follow this form BY HAND and say so, never report it as a subagent dispatch. The wording is the kit's own (`tools/carriers.mjs`) and is printed, never re-worded. The dispatch-form branch is unchanged for a slot resolved to `solo` or `delegated`.
|
|
22
|
+
|
|
23
|
+
**Fold lane (an execute slot resolved to `delegated`).** Immediately after that slot's driving contract, the advisor renders `codex-exec --resume <held id> --nonce <nonce> <fold-brief>` under the populated `run:` prefix, with the audited ledger's held id shell-quoted. `<nonce>` and `<fold-brief>` stay placeholders. One `caveat:` line replaces the id in each no-id case: no ledger; no folded code session in the commit epoch; a recorded substitution; an unresolvable, unreadable, malformed or audit-refused ledger (the reader reason verbatim); a failed HEAD read; or an id a one-line render cannot carry. An unborn branch runs the judge and is not a caveat. The block is advisory and never changes the exit code; `foldLane` carries the identical lines under `--json`, and is `[]` outside `plan-execution` or when execute resolves to Solo or Subagent. The sidecar is never read.
|
|
22
24
|
|
|
23
25
|
**`routine`'s `parallel` switch renders against the EFFECTIVE carrier.** The flag always renders with its source (`parallel: on` / `parallel: off`); the concurrency claim — *file-disjoint slices dispatch concurrently* — renders **only** while the resolved `carrier` is `subagent`. Under a `solo` carrier the line states its own inertness instead, `(no effect while the carrier is solo)`: a switch never degrades, so the flag keeps the value you set and the render simply stops promising a concurrency nothing can perform. That makes four cells (`on`/`off` x `subagent`/`solo`), and only the `subagent`+`on` cell claims concurrency.
|
|
24
26
|
|
|
@@ -28,6 +30,10 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity> [--override <slo
|
|
|
28
30
|
|
|
29
31
|
**Declared source-size practice block (upfront knowledge, D-17 U1).** When the project declares a source-size practice in `docs/ai/source-size.json`, the advisor also renders `Declared source-size practice (docs/ai/source-size.json) — known BEFORE the code is written:` — the caps, what is recorded and the canonical WHY sentence (the plan-time layout itself is the planning canon's Module ledger). `--json` carries the SAME lines under **`declaredPractice`** (an empty array when nothing is declared), so the human and structured renders never diverge. The four config states each read as themselves: **ABSENT** renders nothing at all (a project declaring no practice is handed no invented limits), **AUTHORED** and **INCOMPLETE** are pre-mint (the second names the half-written machine key), **MINTED** carries the recorded counts. A declaration that cannot be read (malformed, unknown-keyed, a dangling symlink) renders ONE loud `UNREADABLE` line and the advisor **still exits 0** — the practice's own checker owns the exit code for its config (exit 2 there) and its declared gate reds the gate matrix. Read through the practice's pure read core, so this advisor never reaches its writer half.
|
|
30
32
|
|
|
33
|
+
**Readers-sweep block (`plan-authoring` only, unconditional).** Before the review-loop block the advisor renders `Readers sweep (before the first review) — …`: the search form for every config key, registry entry, exported constant, receipt field or canon sentence the plan changes, and the classification rule (a ledger row, a stated non-goal, or unchanged with its proof). The command line `run: node <kit>/tools/repo-search.mjs --pattern <the literal>` ALWAYS renders — bare wherever the `--kit-tools` tier could seed that path (the tier's own byte-form, the same predicate), and single-quoted otherwise (an unseedable path has no rule to match); `--pattern-file <f>` only for a pattern carrying a shell-significant byte; when the project's MCP registration is complete (`/agent-workflow-kit mcp`) a `use: repo_search {"pattern": "<the literal>"}` line renders ABOVE it — registration on disk, never a claim the tool is loaded in this session. `--json` carries the SAME lines under **`readersSweep`** (an empty array for the other activities).
|
|
34
|
+
|
|
35
|
+
**Slot-skew warning.** When the installed engine's canon `Slots:` line for the activity differs from this kit's registry, the advisor adds a `warnings` entry (a `⚠` line in the human render; exit 0; the resolved lines follow the registry) naming both lists — the engine and the kit are out of step; `/agent-workflow-kit status` names which member is behind. A section without a `Slots:` line is a customized canon and stays silent.
|
|
36
|
+
|
|
31
37
|
**Spec-store block (the feature-spec layer, `plan-execution` only).** After a `plan-execution` render the advisor also prints `Spec store (the feature-spec layer) — …`: the two POPULATED `spec-check` commands and the session register they read. **Session lane** — `--ops-file docs/plans/spec-ops.list` (or repeated `--op <add|modify|remove>=<path>` / `rename=<old>:<new>`) judges the closure of the declared change: the targets plus each one's listing parent, on their post-state, their reader verdict, their per-kind line cap, their scenario bindings and the containment of every path they name. **Store lane** — `--all` judges the whole store instead and adds the four cross-document invariants (an unlisted child, DISTINCT from an orphan · acyclicity · store-wide slug uniqueness · module overlap); an absent store root refuses the run rather than reporting an empty clean store. The two lanes are exclusive. **Register lifecycle:** `docs/plans/spec-ops.list` is *session scratch* — the session writes it, the plan's Phase Cleanup deletes it — and it is **never defaulted** by the tool, because an unnamed register would attest a post-state nobody declared (the `fold-scope --queue` precedent). `--json` carries the same lines under **`specCheck`** (an empty array outside `plan-execution`). Advisory like the finding-scope block: nothing records that it ran. Exit `0` clean · `1` findings · `2` usage.
|
|
32
38
|
|
|
33
39
|
**Cap-soft-skip degradation (the feature's only AUTO route).** The activity procedures are auto-discoverable only through the one-line **`workflow:methodology`** pointer (this kit + the engine carry `disable-model-invocation:true`, so NL like "write a plan" does **not** auto-load this skill). On a deployment whose methodology pointer was cap-soft-skipped — or whose pre-existing customized pointer lacks the procedures clause — the procedures are still reachable by **explicitly** invoking `/agent-workflow-kit procedures`; surface that plainly rather than treating it as a gap.
|
|
@@ -10,13 +10,13 @@ The five recipes (defined over each bridge's `provides` roles — `codex`: execu
|
|
|
10
10
|
- **Reviewed** — you execute; **one** backend reviews the result (advisory). Prefers `codex` when both are ready (`agy` carries a standing health caveat).
|
|
11
11
|
- **Council** — **both** backends review independently; you synthesize the two opinions.
|
|
12
12
|
- **Delegated** — you hand a **bounded** execution sub-task to a backend (`codex exec`), then review the returned diff and commit.
|
|
13
|
-
- **Subagent** — a **full-tool frontier subagent**, dispatched from the placed executor vehicle (`.claude/agents/executor.md`), carries a **bounded, file-disjoint** slice — of execution, of plan/contract authoring, or of routine work; you verify the returned slice by running its suites yourself and you make the commit. It is **never a review backend**, **never a bridge substitute**, and is **never told to commit**; it **degrades to Solo** when the vehicle is `missing` or `unusable`, with the reason. Readiness here is the **vehicle FILE, never the host** — a **Claude Code lane**, like the `agents` writer itself: on a host that cannot dispatch the vehicle, the rendered dispatch form is an instruction you follow by hand and report as such, never as a subagent dispatch.
|
|
13
|
+
- **Subagent** — a **full-tool frontier subagent**, dispatched from the placed executor vehicle (`.claude/agents/executor.md`), carries a **bounded, file-disjoint** slice — of execution, of plan/contract authoring, of an authoring fold, or of routine work; you verify the returned slice by running its suites yourself and you make the commit. It is **never a review backend**, **never a bridge substitute**, and is **never told to commit**; it **degrades to Solo** when the vehicle is `missing` or `unusable`, with the reason. Readiness here is the **vehicle FILE, never the host** — a **Claude Code lane**, like the `agents` writer itself: on a host that cannot dispatch the vehicle, the rendered dispatch form is an instruction you follow by hand and report as such, never as a subagent dispatch.
|
|
14
14
|
|
|
15
15
|
1. Run **`node ${CLAUDE_SKILL_DIR}/tools/recipes.mjs`** (the read-only planner; `--json` for the structured form). It runs the backend detector, lists the five recipes, and prints — for the current environment — a **per-recipe dispatch plan that degrades with a stated reason** when a carrier isn't `ready` (Council → Reviewed → Solo; Delegated → Solo; Subagent → Solo when the executor vehicle is `missing`/`unusable`), plus advisory **quota/health notes** (prefer the cheapest model; Council spends two backends' quota; `agy` may stall on substantive prompts — Issue-001, prefer `codex`).
|
|
16
16
|
2. **Offer the choice** via **`AskUserQuestion` where your agent supports it** (`AskUserQuestion` in Claude Code) — one option per recipe, the `recommendRecipe` choice listed **first** — otherwise in prose. Then print `planRecipe(chosen, detection)` (the per-stage dispatch + degradation reasons + quota/health notes) so the user sees exactly what running it entails.
|
|
17
17
|
3. **Availability = `readiness === ready`, full stop.** Every other readiness supplies the human reason (needs-skill → "not installed — `/agent-workflow-kit setup`"; needs-cli → "install the CLI"; needs-credentials → "log in"; degraded → "wrapper not on PATH — `/agent-workflow-kit setup`"). This is set-up state only — **never** a claim that a backend's service is responsive (the detector cannot observe a runtime stall; `agy`'s Issue-001 is a *standing advisory*, not a readiness signal).
|
|
18
18
|
|
|
19
|
-
**The activities and their slots.** One registry (`tools/carriers.mjs`, re-exported by `recipes.mjs`) names **three** activities and their typed slots: `plan-authoring` → `author`, `review`; `plan-execution` → `execute`, `review`; `routine` → `carrier`, `parallel`. Each slot's accepted values follow its TYPE — `review` accepts `solo|reviewed|council`; `execute` accepts `solo|delegated|subagent`; `carrier` accepts `solo|subagent`. **`parallel` is a flag (`on|off`), not a recipe:** it resolves OUTSIDE the recipe lattice, never degrades, and never reaches the planner. A silent slot takes its **computed default** — `solo` for `author`, `execute` and `carrier` (placing the executor vehicle never flips a default; the subagent carrier is opt-in exactly as `delegated` is), `reviewed`/`solo` by readiness for `review`, and `on` for `parallel`.
|
|
19
|
+
**The activities and their slots.** One registry (`tools/carriers.mjs`, re-exported by `recipes.mjs`) names **three** activities and their typed slots: `plan-authoring` → `author`, `fold`, `review`; `plan-execution` → `execute`, `review`; `routine` → `carrier`, `parallel`. Each slot's accepted values follow its TYPE — `review` accepts `solo|reviewed|council`; `execute` accepts `solo|delegated|subagent`; `carrier` accepts `solo|subagent`. **`parallel` is a flag (`on|off`), not a recipe:** it resolves OUTSIDE the recipe lattice, never degrades, and never reaches the planner. A silent slot takes its **computed default** — `solo` for `author`, `fold`, `execute` and `carrier` (placing the executor vehicle never flips a default; the subagent carrier is opt-in exactly as `delegated` is), `reviewed`/`solo` by readiness for `review`, and `on` for `parallel`.
|
|
20
20
|
|
|
21
21
|
**The configured-recipe line (`--active-line`, read-only).** `node ${CLAUDE_SKILL_DIR}/tools/recipes.mjs --active-line` prints exactly **one** machine-composed line: the **CONFIGURED** recipe of every activity/slot, resolved from the target project's `docs/ai/orchestration.json` (read from the current directory) + live readiness — each slot with its source (configured vs computed default), its degradation stated, and its dispatched wrapper set — explicitly contrasted with the readiness **recommendation** (which is informational; the configured recipes are what runs). Paste it verbatim: it fills the session-start discovery step (the deployed `agent_rules.md` §1.1) and the handover "Active recipes:" slot; `set-recipe` echoes the same line after every successful write. A malformed config fails loud (exit 1), never a silent fallback.
|
|
22
22
|
|
|
@@ -6,10 +6,10 @@ Read-only **review-receipt checker** (AD-038) — it makes *"reviewed ≠ shippe
|
|
|
6
6
|
|
|
7
7
|
Run `node ${CLAUDE_SKILL_DIR}/tools/review-state.mjs [--check] [--json]`:
|
|
8
8
|
|
|
9
|
-
1. Plain run → the human report: resolved recipe + source, plan-in-flight, tree fingerprint, per-backend receipt state (current / stale / ungrounded / probe / rejected / missing) with verdict + grounding + timestamp.
|
|
10
|
-
2. **`--check`** → the gate exit code. The **normative exit contract lives in the tool header** (the single home — do not re-enumerate it elsewhere): exit 0 for a CONFIGURED solo recipe (a computed readiness-degrade NEVER silently becomes solo — it needs the explicit degrade record below), no plan in flight (the `docs/plans` naming convention: `queue.md` and `EXECUTE-`/`FEEDBACK-`-prefixed or `PROMPT`/`prompt`/`handoff`-carrying names are scratch), a clean tree, a non-git cwd, or every recipe-named backend SATISFIED for the current tree. **A clean-tree PASS under a NON-SOLO review obligation is never silent about a latent arm:** when a `reviewed` or `council` recipe is configured, it NAMES every plan in flight and states that this gate arms as soon as the tree is dirty — the condition is discoverable BEFORE it blocks, instead of surfacing at the worst moment (a pending commit, or the landing of a feature worktree when main first turns dirty). Nothing is announced under a configured `solo` recipe or a non-git cwd, where the gate can never arm. **Satisfaction is ship-class-only on the LATEST NORMAL receipt (D3(b)):** per backend, the latest probe-free current-fingerprint receipt is selected FIRST and THEN verdict-checked — only the recognized ship-class vocabulary (`ship` / `ship with nits`) satisfies; a recognized NEGATIVE (`revise` / `rethink` / `rework`) is an authoritative VETO (an earlier ship never survives a later revise); an UNRECOGNIZED verdict (e.g. `unknown` from a dead run) fails CLOSED unconditionally — a later `unknown` never lets an earlier SHIP stand, and a fresh normal re-run supersedes it. **The ONLY escape is an explicit degrade RECORD** (`core-evidence.mjs degrade --backend <name> --reason "…"`, fingerprint-bound to the current tree) — and never all backends: ≥1 non-degraded ship-class receipt is required whenever ≥1 backend is configured; a malformed evidence store denies the escape fail-closed but never fails an independently-satisfied tree. Exit 1 otherwise — missing, **stale** (ANY edit after a review moves the fingerprint), ungrounded, vetoed, or unrecognized. Plan/diff receipts and continuations (`agy-review --continue`) are **informational-only**: after a fold, only a **fresh grounded re-run** (`codex-review code`; `agy-review code --facts @f`) restores green. **Probe receipts never attest either:** a `CODEX_PROBE=1` / `AGY_PROBE=1` review runs with the frontier-model/max-effort guard OFF, so the wrapper stamps `probe:true` and this checker excludes it — per receipt, so a real review at the same fingerprint still satisfies. **Silence is not a declaration:** a malformed *or* absent probe marker is rejected fail-closed and stated in the check line. Honest bound: receipts are **not authenticated** (a forger could write `probe:false` as easily as any other field) — like the rest of the receipt this is a self-discipline mechanism, not a security boundary.
|
|
9
|
+
1. Plain run → the human report: resolved recipe + source, plan-in-flight, tree fingerprint, the `held session: <id> — <n> fold(s) rode it` / `none` / substitution line when configured delegated execution arms it, and per-backend receipt state (current / stale / ungrounded / probe / rejected / missing) with verdict + grounding + timestamp.
|
|
10
|
+
2. **`--check`** → the gate exit code. Before the review-recipe early return, a configured `plan-execution.execute = delegated` with a plan in flight and a dirty, fingerprintable tree audits the delegation ledger and refuses a substituted held session; an absent ledger is inert, while an unreadable, malformed, foreign, audit-refused ledger or failed HEAD read fails closed with its own reason. The accepted-replacement escape is a `codex-exec` degrade at the substituted return's tree, standing after every later fold, never the review receipt id `codex`. The remaining **normative exit contract lives in the tool header** (the single home — do not re-enumerate it elsewhere): exit 0 for a CONFIGURED solo recipe (after the held-session arm above) (a computed readiness-degrade NEVER silently becomes solo — it needs the explicit degrade record below), no plan in flight (the `docs/plans` naming convention: `queue.md` and `EXECUTE-`/`FEEDBACK-`-prefixed or `PROMPT`/`prompt`/`handoff`-carrying names are scratch), a clean tree, a non-git cwd, or every recipe-named backend SATISFIED for the current tree. **A clean-tree PASS under a NON-SOLO review obligation is never silent about a latent arm:** when a `reviewed` or `council` recipe is configured, it NAMES every plan in flight and states that this gate arms as soon as the tree is dirty — the condition is discoverable BEFORE it blocks, instead of surfacing at the worst moment (a pending commit, or the landing of a feature worktree when main first turns dirty). Nothing is announced under a configured `solo` recipe or a non-git cwd, where the gate can never arm. **Satisfaction is ship-class-only on the LATEST NORMAL receipt (D3(b)):** per backend, the latest probe-free current-fingerprint receipt is selected FIRST and THEN verdict-checked — only the recognized ship-class vocabulary (`ship` / `ship with nits`) satisfies; a recognized NEGATIVE (`revise` / `rethink` / `rework`) is an authoritative VETO (an earlier ship never survives a later revise); an UNRECOGNIZED verdict (e.g. `unknown` from a dead run) fails CLOSED unconditionally — a later `unknown` never lets an earlier SHIP stand, and a fresh normal re-run supersedes it. **The ONLY review-backend escape is an explicit degrade RECORD** (`core-evidence.mjs degrade --backend <name> --reason "…"`, fingerprint-bound to the current tree) — and never all backends: ≥1 non-degraded ship-class receipt is required whenever ≥1 backend is configured; a malformed evidence store denies the escape fail-closed but never fails an independently-satisfied tree. Exit 1 otherwise — missing, **stale** (ANY edit after a review moves the fingerprint), ungrounded, vetoed, or unrecognized. Plan/diff receipts and continuations (`agy-review --continue`) are **informational-only**: after a fold, only a **fresh grounded re-run** (`codex-review code`; `agy-review code --facts @f`) restores green. **Probe receipts never attest either:** a `CODEX_PROBE=1` / `AGY_PROBE=1` review runs with the frontier-model/max-effort guard OFF, so the wrapper stamps `probe:true` and this checker excludes it — per receipt, so a real review at the same fingerprint still satisfies. **Silence is not a declaration:** a malformed *or* absent probe marker is rejected fail-closed and stated in the check line. Honest bound: receipts are **not authenticated** (a forger could write `probe:false` as easily as any other field) — like the rest of the receipt this is a self-discipline mechanism, not a security boundary.
|
|
11
11
|
3. **Wire it as a gate by hand OR via the explicit-consent init preview — never without consent (AD-021/D9).** The candidate line for your own `docs/ai/gates.json`: `{ "id": "review-state", "title": "Review receipts current for the uncommitted tree", "cmd": "node <path-to-this-skill>/tools/review-state.mjs --check" }` — with the path your project actually reaches the kit by, QUOTED so a path with spaces survives, executable from the project root. The consent-gated gates-init preview — `node ${CLAUDE_SKILL_DIR}/tools/gates-init.mjs --cwd <project>` (dry-run: it prints the derived entries and writes NOTHING; `--apply --only review-state` writes exactly this one on your explicit yes) — offers exactly this entry, path resolved and quoted, ONLY when your `docs/ai/orchestration.json` declares `reviewed`/`council` on `plan-execution.review` (the slot this checker enforces). The full consent-fill contract is in `${CLAUDE_SKILL_DIR}/references/modes/gates.md`. Once declared, the opt-in `${CLAUDE_SKILL_DIR}/references/modes/hook.md` auto-approves it like any other declared gate — and `run-gates --final` requires this check among its canonical core gates.
|
|
12
|
-
4. **`--await [--timeout <s>]`** (BUGFREE-3 / AD-049) → BLOCK until every recipe-named backend is SATISFIED for the current tree (i.e. until `--check` would PASS), or the bounded timeout elapses (a loud exit 1; default 900s). Run it after dispatching the review bridges to WAIT for their receipts to land instead of hand-polling a pid: the durable completion signal is the **receipt**, never a process event (a harness "completed" notification fires early; a bridge's output late-flushes). It inherits the `--check` decision whole — a backend with a current-tree degrade RECORD stops being waited on (the shared decideCheck), so you never hand-`--await` around a known degrade. **
|
|
12
|
+
4. **`--await [--timeout <s>]`** (BUGFREE-3 / AD-049) → BLOCK until every recipe-named backend is SATISFIED for the current tree (i.e. until `--check` would PASS), or the bounded timeout elapses (a loud exit 1; default 900s). Run it after dispatching the review bridges to WAIT for their receipts to land instead of hand-polling a pid: the durable completion signal is the **receipt**, never a process event (a harness "completed" notification fires early; a bridge's output late-flushes). It inherits the `--check` decision whole — a backend with a current-tree degrade RECORD stops being waited on (the shared decideCheck), so you never hand-`--await` around a known degrade. A held-session refusal and an **AUTHORITATIVE veto terminate the wait loudly BEFORE the deadline**: neither can be moved by waiting; only the held-session retry/degrade lane or a fresh review can move its corresponding refusal. Still read-only (it re-reads the receipts + the evidence store); solo / no-plan / clean-tree resolve instantly. For waiting on **one dispatch's arrival** (never obligation satisfaction) use the receipt-arrival deadline runner, `${CLAUDE_SKILL_DIR}/references/modes/receipt-deadline.md`.
|
|
13
13
|
|
|
14
14
|
**Human residual (stated, accepted):** `git commit --no-verify` and receipt-file deletion/forgery remain possible — this is a self-discipline mechanism against silent process drift, not a security boundary.
|
|
15
15
|
|
|
@@ -15,7 +15,7 @@ mapping their wording onto them is your job, not a shipped phrase list.
|
|
|
15
15
|
| "both review" | `--set <activity>.review=council` | **disambiguate**: which activity? If both, pass `--set plan-authoring.review=council --set plan-execution.review=council`. |
|
|
16
16
|
| "one reviewer" | `--set <activity>.review=reviewed` | per the named activity, else ask |
|
|
17
17
|
| "delegate execution" | `--set plan-execution.execute=delegated` | execution only (a **bridge** runs the change) |
|
|
18
|
-
| "let a subagent do it" | `--set <activity>.<slot>=subagent` | **disambiguate**: which work? `--set plan-execution.execute=subagent` (a slice of the change), `--set plan-authoring.author=subagent` (a plan/contract brief), `--set routine.carrier=subagent` (a bounded chore) |
|
|
18
|
+
| "let a subagent do it" | `--set <activity>.<slot>=subagent` | **disambiguate**: which work? `--set plan-execution.execute=subagent` (a slice of the change), `--set plan-authoring.author=subagent` (a plan/contract brief), `--set plan-authoring.fold=subagent` (the round's findings and dispositions), `--set routine.carrier=subagent` (a bounded chore) |
|
|
19
19
|
| "one slice at a time" | `--set routine.parallel=off` | `routine` only — `parallel` is a **flag**, not a recipe |
|
|
20
20
|
| "revert / do it myself" | `--unset <activity>.<slot>` | the named slot → its computed default |
|
|
21
21
|
|
|
@@ -25,7 +25,7 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/set-recipe.mjs [--set <activity>.<slot>=<v
|
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
Activities and their slots:
|
|
28
|
-
plan-authoring → author, review
|
|
28
|
+
plan-authoring → author, fold, review
|
|
29
29
|
plan-execution → execute, review
|
|
30
30
|
routine → carrier, parallel
|
|
31
31
|
|
|
@@ -36,10 +36,10 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/set-recipe.mjs [--set <activity>.<slot>=<v
|
|
|
36
36
|
switch slots accept on | off
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
`parallel` is a **flag**, not a recipe: it resolves outside the recipe lattice and is never degraded. Examples: `--set routine.carrier=subagent`, `--set routine.parallel=off`, `--set plan-authoring.author=subagent`. A value outside its slot's list is a **usage** error (`2`) naming the accepted values, never a coercion to a neighbour.
|
|
39
|
+
`parallel` is a **flag**, not a recipe: it resolves outside the recipe lattice and is never degraded. Examples: `--set routine.carrier=subagent`, `--set routine.parallel=off`, `--set plan-authoring.author=subagent`, `--set plan-authoring.fold=subagent`. A value outside its slot's list is a **usage** error (`2`) naming the accepted values, never a coercion to a neighbour.
|
|
40
40
|
2. **Preview by default** — prints `current → proposed` for the **changed** slots only, plus the **effective recipe resolved against live carrier readiness** (degradation stated honestly, e.g. *council requested, 1 ready reviewer → runs reviewed until a 2nd backend is ready*; *subagent requested, the executor vehicle is `missing` → runs solo until the vehicle is placed, naming the apply command*). A `parallel` value is reported as requested — a flag has nothing to degrade against. It writes **nothing**. Re-run with **`--write`** to apply (same effective/degradation note — a direct `--write` is never quieter than the preview). `--unset` returns a slot to its computed default (reverting needs no hand-edit either). A no-op `--set` (slot already equals) writes nothing and never re-seeds the onboarding note.
|
|
41
41
|
3. **`--write`** applies via a hardened, atomic write (deployment-gated — refuses to scatter a config into a repo with no `docs/ai`; exclusive-create temp + rename; symlink/TOCTOU-safe; last-writer-wins). It preserves the onboarding note + every untouched slot, normalizing to canonical 2-space JSON.
|
|
42
|
-
**Standing-consent advisory (after a successful `--write` only, and only when the written config names a `reviewed`/`council` recipe — a solo recipe gets NO advisory):** advise the ONE-TIME **hand-adds** to the maintainer's `.claude/settings.local.json` — `Bash(codex-review:*)` + `Bash(agy-review:*)` + `Bash(node <skill-dir>/tools/grounding.mjs:*)`, with the path your project actually reaches the kit by. State plainly: (a) auto-approving a review wrapper **spends subscription quota without a per-run prompt** — that is exactly what standing consent means here, so it is the maintainer's call; (b) the kit **never writes that file** — these stay hand-adds; (c) `grounding.mjs --out` writes ONE scratch facts file — that write is what the standing consent covers; (d) the entry must match the invocation **byte-form your project actually uses, INCLUDING quoting** — the procedures advisor renders a QUOTED
|
|
42
|
+
**Standing-consent advisory (after a successful `--write` only, and only when the written config names a `reviewed`/`council` recipe — a solo recipe gets NO advisory):** advise the ONE-TIME **hand-adds** to the maintainer's `.claude/settings.local.json` — `Bash(codex-review:*)` + `Bash(agy-review:*)` + `Bash(node <skill-dir>/tools/grounding.mjs:*)`, with the path your project actually reaches the kit by. State plainly: (a) auto-approving a review wrapper **spends subscription quota without a per-run prompt** — that is exactly what standing consent means here, so it is the maintainer's call; (b) the kit **never writes that file** — these stay hand-adds; (c) `grounding.mjs --out` writes ONE scratch facts file — that write is what the standing consent covers; (d) the entry must match the invocation **byte-form your project actually uses, INCLUDING quoting** — the procedures advisor renders a QUOTED path for the grounding pre-step (the readers-sweep command renders bare, matching the kit-tools tier), and a hand-add covering it must use the same spelling (a mismatched spelling is a dead rule that simply prompts).
|
|
43
43
|
4. **Exit codes:** `0` success (an explicit recipe that gracefully degrades is still `0`); `2` usage (a bare/duplicate op, or `--write` with no ops); `1` config error (malformed/unreadable config — the file is left **untouched**, never clobbered) or a write STOP (no deployment / a symlinked config). A `1`/`2` failure is loud; on a malformed config, offer to show the parse error so you can help the user fix the JSON.
|
|
44
44
|
|
|
45
45
|
Output is **English/structured** — **localize it to the user's conversational language** when you narrate. Surface the effective-recipe/degradation note plainly.
|