@sabaiway/agent-workflow-kit 10.3.0 → 10.5.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 +85 -0
- package/README.md +5 -5
- 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/executor.md +40 -0
- package/references/agents/review-lens.md +5 -3
- package/references/modes/agents.md +9 -4
- package/references/modes/procedures.md +21 -8
- package/references/modes/recipes.md +7 -4
- package/references/modes/recommendations.md +3 -1
- package/references/modes/set-recipe.md +23 -6
- package/references/modes/status.md +2 -2
- package/references/modes/upgrade.md +1 -1
- package/references/modes/velocity.md +1 -0
- package/references/shared/composition-handoff.md +1 -1
- package/references/shared/deploy-tail.md +1 -1
- package/references/templates/orchestration.json +1 -1
- package/tools/autonomy-config.mjs +1 -1
- package/tools/bridge-posture.mjs +48 -0
- package/tools/carriers.mjs +152 -0
- package/tools/cheap-agents-read.mjs +234 -0
- package/tools/cheap-agents.mjs +101 -109
- package/tools/commands.mjs +3 -3
- package/tools/detect-backends.mjs +2 -2
- package/tools/direct-run.mjs +9 -0
- package/tools/family-registry.mjs +38 -18
- package/tools/flow-check.mjs +2 -7
- package/tools/fold-scope.mjs +5 -60
- package/tools/grounding.mjs +2 -2
- package/tools/inject-methodology.mjs +4 -0
- package/tools/orchestration-config.mjs +23 -61
- 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 +197 -83
- package/tools/recipes.mjs +183 -230
- package/tools/recommendations.mjs +77 -11
- package/tools/renderers.mjs +27 -7
- 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 +10 -11
- package/tools/set-recipe-roster.mjs +167 -0
- package/tools/set-recipe.mjs +138 -42
- package/tools/velocity-profile.mjs +8 -22
- package/tools/view-model.mjs +17 -3
|
@@ -372,7 +372,7 @@ CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
|
|
|
372
372
|
# receipt this wrapper mints; scripts/release/version-sync.mjs bumps it under the one-anchor-per-file
|
|
373
373
|
# rule, so a release can never leave it behind (the AD-053 drift class).
|
|
374
374
|
AW_RECEIPT_BACKEND="codex"
|
|
375
|
-
AW_BRIDGE_VERSION="3.
|
|
375
|
+
AW_BRIDGE_VERSION="3.6.0" # aw-version-anchor
|
|
376
376
|
# The kill grace handed to timeout(1) as --kill-after, and recorded in the receipt as killGraceS:
|
|
377
377
|
# ONE constant, so the number the ledger checks against the dispatch deadline is the number the run
|
|
378
378
|
# actually applied.
|
|
@@ -114,7 +114,7 @@ describe('codex-review — schema-mode verdict is parsed STRUCTURALLY (M2)', ()
|
|
|
114
114
|
|
|
115
115
|
it('a DECOY "verdict" inside a findings STRING never substitutes the top-level field', () => {
|
|
116
116
|
const sb = makeSandbox();
|
|
117
|
-
const decoy = '{"verdict":"revise","findings":["the text \\"verdict\\": \\"ship\\" appeared in a doc line"]}';
|
|
117
|
+
const decoy = '{"verdict":"revise","findings":[{"severity":"nit","location":"doc","issue":"the text \\"verdict\\": \\"ship\\" appeared in a doc line","suggested_change":"none"}]}';
|
|
118
118
|
const r = run(sb, { env: { CODEX_REVIEW_SCHEMA: '1', CODEX_FAKE_FINAL: decoy } });
|
|
119
119
|
const receipts = readReceipts(sb.repo);
|
|
120
120
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -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.5.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",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: executor
|
|
3
|
+
description: Executes ONE bounded slice — an execution, authoring, or write-capable routine slice — on behalf of the orchestrator; only the files the brief names, their suites, and a return block. Never dispatched for read-only work: a sweep, an inventory or a review rides a read-only vehicle instead.
|
|
4
|
+
model: opus
|
|
5
|
+
effort: high
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the EXECUTOR vehicle: the one full-tool subagent this kit ships. The orchestrator owns the
|
|
9
|
+
plan, the review and the commit; you own ONE slice of it, end to end, and nothing else.
|
|
10
|
+
|
|
11
|
+
- **One slice, the named files only.** The brief names exactly one slice and lists the files you may
|
|
12
|
+
change. You change those and no other file — not a neighbour that would be tidier, not a test
|
|
13
|
+
outside the list. A file you believe the slice needs and the brief did not name is a red line,
|
|
14
|
+
not a decision you get to make.
|
|
15
|
+
- **Read before you edit.** The plan, the governing contract and the project rules the brief names
|
|
16
|
+
are read first. An edit written before them is guesswork wearing the shape of work.
|
|
17
|
+
- **You never commit, and you never touch what governs you.** No git write at all: no `add`,
|
|
18
|
+
`commit`, `push`, `stash`, `reset`, `checkout`, `tag`, no history rewrite. You never edit the plan
|
|
19
|
+
or the contract under `docs/ai/specs/` that governs YOUR OWN slice, the ADR, the changelog, the
|
|
20
|
+
handover documents or `docs/ai/source-size.json` — those are the orchestrator's, and the
|
|
21
|
+
orchestrator is the only one who commits. A plan draft, a contract draft or a regenerated document
|
|
22
|
+
that the brief names as your DELIVERABLE (an authoring or a routine slice) is a named file like
|
|
23
|
+
any other.
|
|
24
|
+
- **You run the suites and report what they printed.** The brief maps every file you touch to the
|
|
25
|
+
exact test files and commands to run; where it does not, run the paired `<name>.test.mjs` when one
|
|
26
|
+
exists — never hand an implementation file to `node --test`, which would run it as an empty suite
|
|
27
|
+
and pass vacuously — plus every gate command the brief names, and the REAL output goes into your
|
|
28
|
+
return block. A failing
|
|
29
|
+
test is reported as failing; papering over it, skipping it, or weakening an assertion to reach
|
|
30
|
+
green is the worst thing you can do here.
|
|
31
|
+
- **You stay inside the budget the brief states.** Every file keeps to the line cap it names. A
|
|
32
|
+
comment exists only where the code truly cannot carry the fact. No attribution of any kind — to
|
|
33
|
+
an agent, a model or a tool — anywhere: code, comments, messages, documents. The project's
|
|
34
|
+
language and encoding rules come from its `AGENTS.md` and the brief — never assume English or ASCII.
|
|
35
|
+
- **A red line STOPS you.** An approval ask, a need for the network or a credential, a file outside
|
|
36
|
+
your slice, or a finding that the brief itself is wrong: you stop and report it. You never
|
|
37
|
+
improvise around it and never widen your own scope to repair it.
|
|
38
|
+
- **Your final message IS the return value.** The exact paths you changed, the commands you ran with
|
|
39
|
+
their results, and anything left undone with the reason. Nothing else: the orchestrator verifies
|
|
40
|
+
every returned slice by running its suites again.
|
|
@@ -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
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
### Mode: agents
|
|
2
2
|
|
|
3
3
|
<!-- opt-in-capability: agents -->
|
|
4
|
+
<!-- opt-in-capability: executor-vehicle -->
|
|
4
5
|
|
|
5
|
-
The opt-in **
|
|
6
|
+
The opt-in **subagent-vehicle writer** — the family's second `.claude/` writer, on the velocity discipline. It places the **five** bundled subagent definitions (`references/agents/*.md`) into the project's `.claude/agents/`: **four read-only vehicles** and **one** full-tool `executor`. **Claude-Code-specific** (like velocity): other agent hosts ignore `.claude/agents/`.
|
|
6
7
|
|
|
7
|
-
**Every vehicle grants READ-ONLY tools and NO `Bash`** — that is the load-bearing property, not a detail. A read-only fan-out on a full-tool vehicle shells out for facts it could have read, and each shelled command is an approval prompt the maintainer never needed to see; a vehicle with no shell structurally cannot do that. Two lanes ride on it:
|
|
8
|
+
**Every READ-ONLY vehicle grants READ-ONLY tools and NO `Bash`** — that is the load-bearing property, not a detail. A read-only fan-out on a full-tool vehicle shells out for facts it could have read, and each shelled command is an approval prompt the maintainer never needed to see; a vehicle with no shell structurally cannot do that. Two lanes ride on it:
|
|
8
9
|
|
|
9
10
|
- **cheap lane** (`model: haiku`, `effort: low`) — `mechanical-sweep`, `changelog-skeleton`, `gate-triage`: extraction sweeps, changelog fact-skeletons, gate-failure triage. Extraction/drafting only; the orchestrator applies judgment and verifies the output.
|
|
10
11
|
- **review lens** (`review-lens`, review-capable model) — an ADDITIONAL independent read-only opinion on code the configured review backends have already seen. It exists because a third lens otherwise has **no vehicle at all**: the cheap vehicles are scoped away from judgment, and a review-capable full-tool subagent is the prompt-flood shape. It never replaces the configured review recipe, and it is advisory like every other review.
|
|
11
12
|
|
|
12
|
-
Writing code, running gates, and user-facing copy never move to these vehicles.
|
|
13
|
+
Writing code, running gates, and user-facing copy never move to these four vehicles.
|
|
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`), 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
|
+
|
|
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.
|
|
13
18
|
|
|
14
19
|
Run `node ${CLAUDE_SKILL_DIR}/tools/cheap-agents.mjs [--dry-run | --apply] [--cwd <dir>]`:
|
|
15
20
|
|
|
@@ -17,4 +22,4 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/cheap-agents.mjs [--dry-run | --apply] [--cw
|
|
|
17
22
|
2. **Only on an explicit yes**, re-run with `--apply`. It writes **only** under `.claude/agents/` — never `settings.json` / `settings.local.json`, never a commit. `--apply` is deployment-gated (the stamp must be at the lineage head) and symlink-safe (a symlinked `.claude` / `.claude/agents` / target file is a STOP).
|
|
18
23
|
3. **Hidden-mode deployments:** after apply, run the hide-footprint reconcile (`node ${CLAUDE_SKILL_DIR}/tools/hide-footprint.mjs --dir <project> --reconcile`) so the placed files stay invisible to `git status` — `/.claude/agents/` is in the known-footprint registry; the apply report reminds you.
|
|
19
24
|
|
|
20
|
-
**Invariants:** writer (writes only `.claude/agents/`) · preview by default · a diverged existing file is reported and preserved, never clobbered · never touches settings · never commits · **no vehicle grants `Bash
|
|
25
|
+
**Invariants:** writer (writes only `.claude/agents/`) · preview by default · a diverged existing file is reported and preserved, never clobbered · never touches settings · never commits · **no READ-ONLY vehicle grants `Bash`, and exactly one bundled vehicle, `executor`, grants a shell** · the cheap-lane vehicles are pinned to `model: haiku` + `effort: low`, and the review lens is pinned OFF the cheap model (all content-tested).
|
|
@@ -2,23 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- opt-in-capability: none — an inspection surface that configures nothing -->
|
|
4
4
|
|
|
5
|
-
Read-only **activity-procedures advisor**. Answers *"what are the steps of this named activity, and which recipe applies at each slot here?"* It composes the orchestration recipes (`${CLAUDE_SKILL_DIR}/references/modes/recipes.md`) into **named activities** with **typed recipe slots**. It **never writes, never commits, never runs a subscription CLI** — the deterministic resolution lives in the kit; the orchestrator runs the resolved recipe via the bridge skills and **owns any commit when the activity has a commit boundary** (
|
|
5
|
+
Read-only **activity-procedures advisor**. Answers *"what are the steps of this named activity, and which recipe applies at each slot here?"* It composes the orchestration recipes (`${CLAUDE_SKILL_DIR}/references/modes/recipes.md`) into **named activities** with **typed recipe slots**. It **never writes, never commits, never runs a subscription CLI** — the deterministic resolution lives in the kit; the orchestrator runs the resolved recipe via the bridge skills or the executor vehicle and **owns any commit when the activity has a commit boundary** (every other carrier never commits). Not every activity commits: `plan-authoring` ends at approval and produces no commit (plans are ephemeral, never committed); `plan-execution` commits per ledger row.
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The three activities (canon in the **installed engine**, `references/procedures.md`; the registry is `tools/carriers.mjs`):
|
|
8
8
|
|
|
9
|
-
- **`plan-authoring`** (
|
|
10
|
-
- **`plan-execution`** (slots: `execute`, `review`) — per ledger row: resolve the recipe → if Delegated, dispatch execution first → 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.
|
|
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
|
+
- **`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
|
+
- **`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.
|
|
11
12
|
|
|
12
|
-
|
|
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.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
|
|
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
|
+
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
|
+
|
|
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. A slot resolved to `solo` or `delegated` renders exactly what it rendered before.
|
|
22
|
+
|
|
23
|
+
**`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
|
+
|
|
25
|
+
**`--override <slot>=<value>` is a PER-RUN session override — it never writes.** Repeatable, at most one value per slot, precedence **override > config > computed default**; it changes THIS render only and leaves `docs/ai/orchestration.json` byte-identical (this mode is read-only; the writer is `set-recipe`). There is no persisted session state — the next run without the flag resolves from the config again — and an override that cannot be satisfied degrades loudly (above) while still exiting 0.
|
|
17
26
|
|
|
18
27
|
**Flow armed-halves block (session-start read side).** When the config carries a `flow` block, the advisor also renders `Flow (schema 1) — armed halves (config · chain · bookkeeping):` — the **config half** (preset · councilRounds · kitMinVersion), the **chain half** (a light read-only probe of the flow store on the checker's fixed path: ARMED at an adoption record, UNARMED for an absent or unadopted store, fail-closed BROKEN wording for a malformed one), and the **bookkeeping half** (each declared path: declared non-excluded — the tracked-file floor verifies on the `set-flow` arming path — vs loudly DECLARED-EXCLUDED). A config with no `flow` block renders byte-identically to before and pays no store probe.
|
|
19
28
|
|
|
20
29
|
**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.
|
|
21
30
|
|
|
31
|
+
**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).
|
|
32
|
+
|
|
33
|
+
**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.
|
|
34
|
+
|
|
22
35
|
**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.
|
|
23
36
|
|
|
24
37
|
**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.
|