@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +5 -5
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +7 -1
  5. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +69 -17
  6. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +73 -2
  7. package/bridges/antigravity-cli-bridge/capability.json +2 -2
  8. package/bridges/antigravity-cli-bridge/references/review-prompt.md +3 -0
  9. package/bridges/codex-cli-bridge/SKILL.md +8 -1
  10. package/bridges/codex-cli-bridge/bin/codex-exec.sh +1 -1
  11. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -1
  12. package/bridges/codex-cli-bridge/bin/codex-review.sh +89 -18
  13. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +55 -2
  14. package/bridges/codex-cli-bridge/capability.json +2 -2
  15. package/capability.json +1 -1
  16. package/package.json +1 -1
  17. package/references/agents/executor.md +40 -0
  18. package/references/agents/review-lens.md +5 -3
  19. package/references/modes/agents.md +9 -4
  20. package/references/modes/procedures.md +21 -8
  21. package/references/modes/recipes.md +7 -4
  22. package/references/modes/recommendations.md +3 -1
  23. package/references/modes/set-recipe.md +23 -6
  24. package/references/modes/status.md +2 -2
  25. package/references/modes/upgrade.md +1 -1
  26. package/references/modes/velocity.md +1 -0
  27. package/references/shared/composition-handoff.md +1 -1
  28. package/references/shared/deploy-tail.md +1 -1
  29. package/references/templates/orchestration.json +1 -1
  30. package/tools/autonomy-config.mjs +1 -1
  31. package/tools/bridge-posture.mjs +48 -0
  32. package/tools/carriers.mjs +152 -0
  33. package/tools/cheap-agents-read.mjs +234 -0
  34. package/tools/cheap-agents.mjs +101 -109
  35. package/tools/commands.mjs +3 -3
  36. package/tools/detect-backends.mjs +2 -2
  37. package/tools/direct-run.mjs +9 -0
  38. package/tools/family-registry.mjs +38 -18
  39. package/tools/flow-check.mjs +2 -7
  40. package/tools/fold-scope.mjs +5 -60
  41. package/tools/grounding.mjs +2 -2
  42. package/tools/inject-methodology.mjs +4 -0
  43. package/tools/orchestration-config.mjs +23 -61
  44. package/tools/orchestration-readme.mjs +70 -0
  45. package/tools/plan-shape-cli.mjs +112 -0
  46. package/tools/plan-shape-facts.mjs +204 -0
  47. package/tools/plan-shape.mjs +348 -0
  48. package/tools/procedures.mjs +197 -83
  49. package/tools/recipes.mjs +183 -230
  50. package/tools/recommendations.mjs +77 -11
  51. package/tools/renderers.mjs +27 -7
  52. package/tools/repo-lex.mjs +40 -0
  53. package/tools/review-roster-resolve.mjs +104 -0
  54. package/tools/review-roster.mjs +128 -0
  55. package/tools/review-rounds-cli.mjs +92 -0
  56. package/tools/review-rounds.mjs +115 -0
  57. package/tools/review-state.mjs +10 -11
  58. package/tools/set-recipe-roster.mjs +167 -0
  59. package/tools/set-recipe.mjs +138 -42
  60. package/tools/velocity-profile.mjs +8 -22
  61. 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.5.0" # aw-version-anchor
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; verdict parsed from the mandated literal verdict line (schema mode: the
64
- verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge,
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.5.0" # aw-version-anchor
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, and a nonce-less receipt stays BYTE-EXACT (the frozen
700
- # compatibility floor).
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 (round-1 fold): the TOP-LEVEL `verdict` field of the schema payload,
1032
- # accepted only inside the closed enum — a legal multiline layout parses, a decoy "verdict"
1033
- # inside a findings string never substitutes, malformed/out-of-enum stays empty (the D4
1034
- # failed-run arm). Node ≥22 is a family floor, so the one-liner adds no dependency.
1035
- verdict="$(node -e 'try{const v=JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).verdict;if(["ship","revise","rethink"].includes(v))process.stdout.write(v);}catch{}' "$out" 2>/dev/null || true)"
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) a FAILED review; NO receipt was written. Re-run the" >&2
1043
- echo " review; if it recurs, inspect the captured output for what the model produced." >&2
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.5.0",
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
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-kit",
5
5
  "kind": "composition-root",
6
- "version": "10.3.0",
6
+ "version": "10.5.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-kit",
3
- "version": "10.3.0",
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 extra independent opinion on code the orchestrator already has, when the configured review backends have run and you want another angle. Grants no shell, so it can never turn a review into a wave of approval prompts. Never for writing code, running gates, or replacing the configured review recipe.
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. Something has already been reviewed by the project's
10
- configured backends; your job is to find what they MISSED, not to restate what they found.
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 **read-only subagent writer** — the family's second `.claude/` writer, on the velocity discipline. It places the bundled subagent definitions (`references/agents/*.md`) into the project's `.claude/agents/`. **Claude-Code-specific** (like velocity): other agent hosts ignore `.claude/agents/`.
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`** · the cheap-lane vehicles are pinned to `model: haiku` + `effort: low`, and the review lens is pinned OFF the cheap model (all content-tested).
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** (a backend 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.
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 two v1 activities (canon in the **installed engine**, `references/procedures.md`):
7
+ The three activities (canon in the **installed engine**, `references/procedures.md`; the registry is `tools/carriers.mjs`):
8
8
 
9
- - **`plan-authoring`** (slot: `review`) — research → draft → self-review → **review {recipe}** → fold/loop → present for approval; enforce the mandatory Cleanup.
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
- Run **`node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity> [--override <slot>=<recipe>]… [--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:
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
- 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>": "<recipe>" } }`; 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`. Seeded by `init` (a user-editable template) — see `${CLAUDE_SKILL_DIR}/references/modes/bootstrap.md`.
15
- 2. **Default resolution (config silent):** `review` → Reviewed if any review-capable backend is `ready`, else Solo (never Council by default); `execute` → Solo (Delegated is opt-in). **Degradation:** a config/computed default degrades **gracefully with a stated reason** (Council → Reviewed → Solo; Delegated → Solo); a per-run **`--override <slot>=<recipe>`** 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).
16
- 3. **Exit codes:** `0` success; `2` usage (unknown `<activity>` / bad `--override` a bare `--override <recipe>`, an unknown slot, an invalid recipe-for-slot, 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.
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.