@sabaiway/agent-workflow-kit 2.0.0 → 3.0.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 +74 -0
- package/README.md +6 -5
- package/SKILL.md +12 -8
- package/bin/install.mjs +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +16 -4
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +150 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +76 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +131 -23
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +193 -15
- package/bridges/antigravity-cli-bridge/capability.json +12 -8
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +6 -2
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +3 -2
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +1 -1
- package/bridges/codex-cli-bridge/SKILL.md +8 -1
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +143 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +93 -10
- package/bridges/codex-cli-bridge/capability.json +3 -2
- package/capability.json +1 -1
- package/migrations/3.0.0-hardened-core-loop.md +44 -0
- package/migrations/README.md +1 -1
- package/package.json +2 -2
- package/references/hooks/gate-approve.mjs +1 -1
- package/references/modes/bootstrap.md +3 -3
- package/references/modes/commit-guard.md +19 -0
- package/references/modes/core-evidence.md +15 -0
- package/references/modes/coverage-check.md +12 -0
- package/references/modes/doc-parity.md +7 -6
- package/references/modes/gates.md +7 -6
- package/references/modes/grounding.md +1 -2
- package/references/modes/hook.md +1 -1
- package/references/modes/review-state.md +3 -3
- package/references/modes/upgrade.md +6 -6
- package/references/modes/velocity.md +2 -2
- package/references/scripts/archive-decisions.mjs +1 -1
- package/references/scripts/install-git-hooks-repo-exec.test.mjs +82 -0
- package/references/scripts/install-git-hooks.mjs +90 -18
- package/references/scripts/install-git-hooks.test.mjs +102 -0
- package/references/scripts/migrate-gates-branches.test.mjs +157 -0
- package/references/scripts/migrate-gates.mjs +395 -0
- package/references/scripts/migrate-gates.test.mjs +284 -0
- package/references/templates/agent_rules.md +2 -2
- package/tools/ack-write.mjs +1 -1
- package/tools/atomic-write.mjs +2 -2
- package/tools/autonomy-config.mjs +1 -1
- package/tools/autonomy-doctor.mjs +1 -1
- package/tools/autonomy-write.mjs +1 -1
- package/tools/bridge-settings-read.mjs +1 -1
- package/tools/bridge-settings.mjs +1 -1
- package/tools/changed-surface.mjs +8 -69
- package/tools/cheap-agents.mjs +2 -2
- package/tools/commands.mjs +17 -10
- package/tools/commit-guard.mjs +167 -0
- package/tools/core-evidence.mjs +914 -0
- package/tools/coverage-check.mjs +260 -0
- package/tools/delegation.mjs +1 -1
- package/tools/detect-backends.mjs +7 -6
- package/tools/doc-parity.mjs +11 -27
- package/tools/engine-source.mjs +1 -1
- package/tools/family-members.mjs +1 -1
- package/tools/family-registry.mjs +1 -1
- package/tools/fs-safe.mjs +1 -1
- package/tools/gate-hook.mjs +1 -1
- package/tools/{seed-gates.mjs → gates-init.mjs} +58 -52
- package/tools/grounding.mjs +6 -83
- package/tools/hide-footprint.mjs +1 -1
- package/tools/inject-methodology.mjs +1 -1
- package/tools/known-footprint.mjs +1 -1
- package/tools/labels.mjs +1 -1
- package/tools/lcov.mjs +6 -10
- package/tools/lens-region.mjs +1 -1
- package/tools/manifest/validate.mjs +19 -0
- package/tools/migrate-adr-store.mjs +2 -2
- package/tools/orchestration-config.mjs +1 -1
- package/tools/orchestration-write.mjs +1 -1
- package/tools/presentation.mjs +1 -1
- package/tools/procedures.mjs +2 -2
- package/tools/recipes.mjs +60 -9
- package/tools/recommendations.mjs +63 -7
- package/tools/release-scan.mjs +1 -1
- package/tools/renderers.mjs +1 -1
- package/tools/review-state.mjs +288 -340
- package/tools/run-gates.mjs +216 -92
- package/tools/sandbox-masks.mjs +1 -1
- package/tools/semver-lite.mjs +1 -1
- package/tools/set-autonomy.mjs +1 -1
- package/tools/set-recipe.mjs +1 -1
- package/tools/setup-backends.mjs +1 -1
- package/tools/surface.mjs +1 -1
- package/tools/uninstall.mjs +1 -1
- package/tools/velocity-profile.mjs +2 -2
- package/tools/view-model.mjs +1 -1
- package/references/modes/fold-completeness.md +0 -30
- package/references/modes/review-ledger.md +0 -34
- package/references/templates/verification-profile.json +0 -10
- package/tools/fold-completeness-run.mjs +0 -1120
- package/tools/fold-completeness.mjs +0 -672
- package/tools/review-ledger-core.mjs +0 -428
- package/tools/review-ledger-write.mjs +0 -647
- package/tools/review-ledger.mjs +0 -630
- package/tools/sarif.mjs +0 -52
- package/tools/verification-profile.mjs +0 -219
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
# 1. POSTURE read-only second-opinion reviewer; findings only, no edits/commits
|
|
15
15
|
# 2. GUARD do NOT opine on AI model names/versions or your knowledge cutoff
|
|
16
16
|
# 3. FACTS "## Grounded facts — review AGAINST these, do NOT guess the code"
|
|
17
|
-
# (
|
|
17
|
+
# (code mode REFUSES pre-spend without a non-empty payload — escapes:
|
|
18
|
+
# --ungrounded / AGY_PROBE=1; plan/diff omitted -> a LOUD stderr warning)
|
|
18
19
|
# 4. DECIDED "## Decisions already made / already addressed — do NOT re-raise"
|
|
19
20
|
# (optional; the anti-circling lever — the round-2 payload)
|
|
20
21
|
# 5. FOCUS the merged --focus / trailing focus text (optional)
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
# facts (--facts), the already-decided list (--decided), and the focus (--focus).
|
|
28
29
|
#
|
|
29
30
|
# Usage (installed on PATH as `agy-review`):
|
|
30
|
-
# agy-review code [--facts @f] [--decided @f] [--focus "…"] [extra focus…]
|
|
31
|
+
# agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus "…"] [extra focus…]
|
|
31
32
|
# agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]
|
|
32
33
|
# agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]
|
|
33
34
|
# agy-review --continue [--decided @f] [--focus "…"] # round-2 delta (no mode, no re-assembly)
|
|
@@ -55,19 +56,22 @@ case "${1:-}" in
|
|
|
55
56
|
agy-review — grounded read-only ADVISORY review by Google's Antigravity CLI (agy; subscription-only).
|
|
56
57
|
|
|
57
58
|
Usage:
|
|
58
|
-
agy-review code [--facts @f] [--decided @f] [--focus "…"] [extra focus…]
|
|
59
|
+
agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus "…"] [extra focus…]
|
|
59
60
|
agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus "…"]
|
|
60
61
|
agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus "…"]
|
|
61
62
|
|
|
62
63
|
Flags:
|
|
63
|
-
--facts @f — verified facts the review runs AGAINST (
|
|
64
|
+
--facts @f — verified facts the review runs AGAINST (code mode REQUIRES a non-empty payload; plan/diff warn loudly when omitted)
|
|
65
|
+
--ungrounded — deliberately ungrounded CODE review, a throwaway opinion (code mode only, contradicts --facts; the receipt records grounded:false and never attests)
|
|
64
66
|
--decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)
|
|
65
67
|
--focus "…" — extra focus (repeatable; code mode also takes trailing focus words)
|
|
66
68
|
|
|
67
69
|
Grounding:
|
|
68
70
|
grounded review — agy reads NOTHING by default, an ungrounded review GUESSES:
|
|
69
71
|
--facts @f = the verified facts to review AGAINST; --decided @f = decisions
|
|
70
|
-
already made, do NOT re-raise (anti-circling)
|
|
72
|
+
already made, do NOT re-raise (anti-circling). code mode REQUIRES a non-empty
|
|
73
|
+
--facts payload and refuses BEFORE spending a run (escapes: --ungrounded,
|
|
74
|
+
AGY_PROBE=1); plan/diff proceed with a loud warning
|
|
71
75
|
|
|
72
76
|
Notes:
|
|
73
77
|
pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts
|
|
@@ -88,20 +92,35 @@ Receipt:
|
|
|
88
92
|
sockets — are excluded from the domain entirely, untracked symlinks/directories ride as
|
|
89
93
|
name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict
|
|
90
94
|
recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK);
|
|
91
|
-
grounded = whether a NON-EMPTY --facts payload was supplied (
|
|
95
|
+
grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without
|
|
96
|
+
one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records
|
|
92
97
|
grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts
|
|
93
98
|
payload; a continuation receipt is fresh:false (informational-only — it cannot attest the
|
|
94
99
|
folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY
|
|
95
100
|
receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe
|
|
96
|
-
review never attests) and equally rejects an unmarked one (silence is not a declaration);
|
|
97
|
-
|
|
101
|
+
review never attests) and equally rejects an unmarked one (silence is not a declaration);
|
|
102
|
+
posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the
|
|
103
|
+
gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the
|
|
104
|
+
review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL
|
|
105
|
+
explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses
|
|
106
|
+
pre-spend in every mode; a run whose output carries NO recognized '### Verdict' section — empty
|
|
107
|
+
output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session
|
|
108
|
+
error); a write failure warns, never fails the review
|
|
98
109
|
|
|
99
110
|
Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
|
|
100
111
|
${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
|
|
101
112
|
AGY_HARD_TIMEOUT — hard wall-clock cap, duration string like 5m/30m/90s (built-in default 30m)
|
|
102
113
|
AGY_REVIEW_ALLOW_ADDDIR — boolean 0/1: 1 arms the oversized --add-dir escape (re-enables the Issue-001 stall risk; default 0)
|
|
103
114
|
|
|
104
|
-
|
|
115
|
+
Honesty + posture (D4/D5):
|
|
116
|
+
a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4
|
|
117
|
+
with NO receipt: a FAILED review to RE-RUN, never a fatal session error. One stderr banner line
|
|
118
|
+
states the ACTUAL run posture (review posture: model=…) and the receipt records the same
|
|
119
|
+
posture {model} (agy has no tier). An ATTESTING review with AGY_MODEL explicitly emptied
|
|
120
|
+
refuses pre-spend (the actual model would be unknowable; AGY_PROBE=1 is exempt), and a model
|
|
121
|
+
string carrying control bytes refuses pre-spend in every mode.
|
|
122
|
+
|
|
123
|
+
Closed grammar: unknown flags are rejected; no '--' passthrough (the flag escape is --ungrounded; the env escapes are AGY_PROBE=1 and AGY_REVIEW_ALLOW_ADDDIR=1).
|
|
105
124
|
Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
|
|
106
125
|
HELP
|
|
107
126
|
exit 0
|
|
@@ -220,9 +239,15 @@ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
|
|
|
220
239
|
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
221
240
|
# version (drift-guarded by agy-review.test.mjs against capability.json).
|
|
222
241
|
AW_RECEIPT_BACKEND="agy"
|
|
223
|
-
AW_BRIDGE_VERSION="
|
|
242
|
+
AW_BRIDGE_VERSION="4.0.0"
|
|
224
243
|
# `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
|
|
225
244
|
AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
|
|
245
|
+
# D5 control-byte screen — IMMEDIATELY after resolution, BEFORE the off-frontier advisory (or any
|
|
246
|
+
# other interpolation) can echo raw newline/ESC bytes into stderr/the terminal (round-2 fold).
|
|
247
|
+
if [[ "$AGY_MODEL" == *[$'\x01'-$'\x1f']* ]]; then
|
|
248
|
+
echo "error: AGY_MODEL contains control bytes — fix the setting (env or bridge-settings.conf) and re-run." >&2
|
|
249
|
+
exit 2
|
|
250
|
+
fi
|
|
226
251
|
# Frontier review models. ANY model is allowed; a sub-frontier one only earns a soft, silenceable warning.
|
|
227
252
|
FRONTIER_SET=("Gemini 3.1 Pro (High)" "Claude Opus 4.6 (Thinking)" "Claude Sonnet 4.6 (Thinking)")
|
|
228
253
|
|
|
@@ -439,6 +464,26 @@ receipt_json_scalar() {
|
|
|
439
464
|
if [[ -z "${1:-}" ]]; then printf 'null'; else printf '"%s"' "$1"; fi
|
|
440
465
|
}
|
|
441
466
|
|
|
467
|
+
# STRICT JSON string encoding for the ONE free-form receipt field (the posture model display
|
|
468
|
+
# string): backslash then double-quote escaped. Control bytes never reach here — the posture
|
|
469
|
+
# resolution refuses them pre-spend (D5), so these two escapes make the encoding total.
|
|
470
|
+
json_string_escape() {
|
|
471
|
+
local s="${1//\\/\\\\}"
|
|
472
|
+
s="${s//\"/\\\"}"
|
|
473
|
+
printf '%s' "$s"
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
# The D5 posture object this wrapper writes into EVERY receipt (agy has no tier — the label is
|
|
477
|
+
# the model display string). An unknowable model (explicitly emptied on an exempt probe run) is
|
|
478
|
+
# recorded null, never guessed.
|
|
479
|
+
posture_json() {
|
|
480
|
+
if [[ -z "${AGY_MODEL:-}" ]]; then
|
|
481
|
+
printf '{"model":null}'
|
|
482
|
+
else
|
|
483
|
+
printf '{"model":"%s"}' "$(json_string_escape "$AGY_MODEL")"
|
|
484
|
+
fi
|
|
485
|
+
}
|
|
486
|
+
|
|
442
487
|
# write_review_receipt <artifact|""> <fresh: true|false> <fingerprint|""> <verdict> <grounded: true|false> <factsHash|""> [probe: true|false]
|
|
443
488
|
# Appends ONE receipt line (the AD-038 fixture shape) as a side effect of a SUCCESSFUL review —
|
|
444
489
|
# to $AW_REVIEW_RECEIPTS when set, else <git dir>/agent-workflow-review-receipts.jsonl (inside the
|
|
@@ -462,28 +507,29 @@ write_review_receipt() {
|
|
|
462
507
|
fi
|
|
463
508
|
local line probe_field=',"probe":false'
|
|
464
509
|
if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
|
|
465
|
-
line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s}' \
|
|
510
|
+
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}' \
|
|
466
511
|
"$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
|
|
467
512
|
"$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
|
|
468
|
-
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field")"
|
|
513
|
+
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)")"
|
|
469
514
|
if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
|
|
470
515
|
echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
|
|
471
516
|
echo " the review-state gate will read the current tree as un-receipted." >&2
|
|
472
517
|
fi
|
|
473
518
|
}
|
|
474
519
|
|
|
475
|
-
# Parse the mandated '### Verdict' section of a captured review: the first non-empty line after
|
|
476
|
-
#
|
|
477
|
-
#
|
|
520
|
+
# Parse the mandated '### Verdict' section of a captured review: the first non-empty line after
|
|
521
|
+
# the EXACT heading (`### Verdicts` or any suffix never matches), tested against the closed
|
|
522
|
+
# vocabulary as an ANCHORED PREFIX with a word boundary — the verdict line must START with the
|
|
523
|
+
# token and the next char must be non-word/EOL (SHIP WITH NITS before SHIP; `NOT SHIP` and
|
|
524
|
+
# `SHIPPING` never match). No heading / no match → "unknown" (the D4 failed-run arm owns it).
|
|
478
525
|
parse_agy_verdict() { # $1 = captured-output file
|
|
479
526
|
local line
|
|
480
|
-
line="$(awk '/^### Verdict
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
esac
|
|
527
|
+
line="$(awk '/^### Verdict[[:space:]]*$/{flag=1; next} flag && NF {print; exit}' "$1" 2>/dev/null)"
|
|
528
|
+
if [[ "$line" =~ ^SHIP\ WITH\ NITS([^[:alnum:]_]|$) ]]; then printf 'SHIP WITH NITS'
|
|
529
|
+
elif [[ "$line" =~ ^REWORK([^[:alnum:]_]|$) ]]; then printf 'REWORK'
|
|
530
|
+
elif [[ "$line" =~ ^SHIP([^[:alnum:]_]|$) ]]; then printf 'SHIP'
|
|
531
|
+
else printf 'unknown'
|
|
532
|
+
fi
|
|
487
533
|
}
|
|
488
534
|
|
|
489
535
|
# Emit the full review surface to stdout: repo map, status (never-committable untracked records
|
|
@@ -541,7 +587,7 @@ case "${1:-}" in
|
|
|
541
587
|
esac
|
|
542
588
|
|
|
543
589
|
usage() {
|
|
544
|
-
echo "usage: $0 code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]" >&2
|
|
590
|
+
echo "usage: $0 code [--facts @f] [--ungrounded] [--decided @f] [--focus \"…\"] [extra focus…]" >&2
|
|
545
591
|
echo " $0 plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
|
|
546
592
|
echo " $0 diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]" >&2
|
|
547
593
|
echo " $0 --continue [--decided @f] [--focus \"…\"]" >&2
|
|
@@ -580,6 +626,7 @@ fi
|
|
|
580
626
|
# --- Flag parse (--facts / --decided / --focus + trailing focus) -------------
|
|
581
627
|
FACTS_RAW=""
|
|
582
628
|
DECIDED_RAW=""
|
|
629
|
+
UNGROUNDED=0
|
|
583
630
|
FOCUS_PARTS=()
|
|
584
631
|
# A value-taking flag must be followed by a real value — never end-of-args and never another flag.
|
|
585
632
|
# Otherwise `agy-review code --facts --focus x` would silently take "--focus" as the facts and spend a
|
|
@@ -598,6 +645,12 @@ while [[ $# -gt 0 ]]; do
|
|
|
598
645
|
exit 2
|
|
599
646
|
fi
|
|
600
647
|
need_value "$1" "${2:-}"; FACTS_RAW="$2"; shift 2 ;;
|
|
648
|
+
--ungrounded)
|
|
649
|
+
if [[ -n "$resume_mode" ]]; then
|
|
650
|
+
echo "error: --ungrounded is not valid on a continuation (the original round already set its grounding)." >&2
|
|
651
|
+
exit 2
|
|
652
|
+
fi
|
|
653
|
+
UNGROUNDED=1; shift ;;
|
|
601
654
|
--decided)
|
|
602
655
|
need_value "$1" "${2:-}"; DECIDED_RAW="$2"; shift 2 ;;
|
|
603
656
|
--focus)
|
|
@@ -623,6 +676,18 @@ done
|
|
|
623
676
|
# Merge --focus values and trailing focus words, in parse order, into ONE focus block.
|
|
624
677
|
FOCUS="${FOCUS_PARTS[*]:-}"
|
|
625
678
|
|
|
679
|
+
# --ungrounded closed grammar (D4): code-mode only, and an explicit contradiction with --facts refuses.
|
|
680
|
+
if [[ "$UNGROUNDED" == "1" ]]; then
|
|
681
|
+
if [[ -n "$FACTS_RAW" ]]; then
|
|
682
|
+
echo "error: --ungrounded contradicts --facts — pass the verified facts (grounded) or drop them (--ungrounded), never both." >&2
|
|
683
|
+
exit 2
|
|
684
|
+
fi
|
|
685
|
+
if [[ "$mode" != "code" ]]; then
|
|
686
|
+
echo "error: --ungrounded is only valid in code mode — $mode mode already proceeds ungrounded with a loud warning." >&2
|
|
687
|
+
exit 2
|
|
688
|
+
fi
|
|
689
|
+
fi
|
|
690
|
+
|
|
626
691
|
# Resolve @file / literal for --facts and --decided NOW (cwd = invocation, before any code-mode cd).
|
|
627
692
|
# The `@file` existence check runs at TOP LEVEL (not inside a command substitution) so its exit-2 exits
|
|
628
693
|
# the whole script, not just a subshell.
|
|
@@ -648,6 +713,25 @@ if [[ -n "$DECIDED_RAW" ]]; then
|
|
|
648
713
|
fi
|
|
649
714
|
|
|
650
715
|
if [[ -z "$FACTS_CONTENT" && -z "$resume_mode" ]]; then
|
|
716
|
+
# code mode fails CLOSED before the spend (D4): an ungrounded CODE receipt records grounded:false,
|
|
717
|
+
# which the review-state gate rejects — the run would be paid for and attest nothing. Keyed on the
|
|
718
|
+
# resolved CONTENT, so --facts naming an empty payload refuses identically.
|
|
719
|
+
if [[ "$mode" == "code" && "$UNGROUNDED" != "1" && "$AGY_PROBE" != "1" ]]; then
|
|
720
|
+
# The recovery hint resolves the kit's grounding tool from THIS wrapper's location (monorepo
|
|
721
|
+
# canon / deployed skills sibling / kit-bundled mirror) — a repo-relative path would not exist
|
|
722
|
+
# for a globally installed kit. Quoted: an install path may carry spaces.
|
|
723
|
+
grounding_tool="node <agent-workflow-kit>/tools/grounding.mjs"
|
|
724
|
+
for _g in "$HERE/../../agent-workflow-kit/tools/grounding.mjs" "$HERE/../../../tools/grounding.mjs"; do
|
|
725
|
+
if [[ -f "$_g" ]]; then grounding_tool="node \"$_g\""; break; fi
|
|
726
|
+
done
|
|
727
|
+
echo "error: 'agy-review code' requires grounded facts and refuses BEFORE spending a run — an" >&2
|
|
728
|
+
echo " ungrounded code review GUESSES, and its receipt (grounded:false) never attests." >&2
|
|
729
|
+
echo " Assemble the verified facts, then re-run:" >&2
|
|
730
|
+
echo " $grounding_tool --constraints [--plan <plan-file>] --out <facts-file>" >&2
|
|
731
|
+
echo " agy-review code --facts @<facts-file>" >&2
|
|
732
|
+
echo " Explicit escapes: --ungrounded (throwaway opinion) · AGY_PROBE=1 (a probe never attests)." >&2
|
|
733
|
+
exit 2
|
|
734
|
+
fi
|
|
651
735
|
echo "warning: no --facts supplied. agy reads NOTHING by default, so an ungrounded review GUESSES" >&2
|
|
652
736
|
echo " (stale-model and partial-diff false positives). Pass --facts @file with the verified" >&2
|
|
653
737
|
echo " facts the model must review AGAINST. Proceeding without grounding." >&2
|
|
@@ -785,6 +869,21 @@ else
|
|
|
785
869
|
fi
|
|
786
870
|
fi
|
|
787
871
|
|
|
872
|
+
# --- D5 pre-spend posture gate + banner (one line, the ACTUAL run posture) ------------------
|
|
873
|
+
# The control-byte screen already ran at AGY_MODEL resolution (before any interpolation). An
|
|
874
|
+
# ATTESTING review (fresh code mode, guards on) whose wrapper cannot know the actual model
|
|
875
|
+
# (AGY_MODEL explicitly emptied → the CLI's own settings default decides) refuses pre-spend;
|
|
876
|
+
# AGY_PROBE=1 runs are exempt (their receipts never attest and record model null).
|
|
877
|
+
# Scoped to the ATTESTING branch only (grounded code, guards on): plan / diff / --ungrounded
|
|
878
|
+
# code mint receipts that never attest, so an emptied model just records posture.model null.
|
|
879
|
+
if [[ -z "$AGY_MODEL" && -z "$resume_mode" && "$REVIEW_PROBE" != "true" && "$REVIEW_ARTIFACT" == "code" && -n "$FACTS_CONTENT" ]]; then
|
|
880
|
+
echo "error: AGY_MODEL is explicitly empty, so the ACTUAL review model is unknowable (the agy CLI's" >&2
|
|
881
|
+
echo " own settings default decides) — an attesting review refuses pre-spend. Fix: unset" >&2
|
|
882
|
+
echo " AGY_MODEL (wrapper default), or set the real model display string; AGY_PROBE=1 is exempt." >&2
|
|
883
|
+
exit 2
|
|
884
|
+
fi
|
|
885
|
+
echo "review posture: model=${AGY_MODEL:-<agy settings default>}" >&2
|
|
886
|
+
|
|
788
887
|
# --- Execute via agy-run (single home of timeout + subscription + byte ceiling) ---
|
|
789
888
|
# The output is teed into the private staging dir so the mandated '### Verdict' section can be
|
|
790
889
|
# parsed into the review receipt — the user-facing stream is unchanged.
|
|
@@ -802,8 +901,17 @@ fi
|
|
|
802
901
|
set -e
|
|
803
902
|
|
|
804
903
|
# --- Review receipt (AD-038): only a SUCCESSFUL review attests --------------------
|
|
904
|
+
# D4 (wrapper honesty): a run that produced NO recognized '### Verdict' section — empty output
|
|
905
|
+
# included — is a FAILED review: non-zero exit, NO receipt. This is a failed review to RE-RUN,
|
|
906
|
+
# never a fatal session error (documented in --help).
|
|
805
907
|
if [[ $rc -eq 0 ]]; then
|
|
806
908
|
verdict="$(parse_agy_verdict "$review_out_file")"
|
|
909
|
+
if [[ "$verdict" == "unknown" ]]; then
|
|
910
|
+
echo "error: the review output carries no recognized '### Verdict' section (closed vocabulary:" >&2
|
|
911
|
+
echo " SHIP / SHIP WITH NITS / REWORK) — a FAILED review; NO receipt was written. Re-run" >&2
|
|
912
|
+
echo " the review; if it recurs, inspect the captured output for what the model produced." >&2
|
|
913
|
+
exit 4
|
|
914
|
+
fi
|
|
807
915
|
if [[ -n "$resume_mode" ]]; then
|
|
808
916
|
# A continuation never re-embeds the current artifact (agy holds the ORIGINAL round server-side;
|
|
809
917
|
# --facts is rejected above), so it cannot attest the folded tree: fresh:false, artifact /
|
|
@@ -38,7 +38,9 @@ const FAKE_AGY = [
|
|
|
38
38
|
' fi; prev="$a"',
|
|
39
39
|
'done',
|
|
40
40
|
'if [[ -n "${AGY_FAKE_SLEEP:-}" ]]; then sleep "$AGY_FAKE_SLEEP"; fi',
|
|
41
|
-
|
|
41
|
+
// Unset AGY_FAKE_OUTPUT → a verdict-carrying default (D4: a verdict-less run is a FAILURE, so
|
|
42
|
+
// the success-path tests need one); an EXPLICIT empty value exercises the empty-output failure.
|
|
43
|
+
'if [[ -z "${AGY_FAKE_OUTPUT+x}" ]]; then printf "FAKE_AGY_REVIEW_OUTPUT\\n### Verdict\\nSHIP\\n"; else printf "%s\\n" "$AGY_FAKE_OUTPUT"; fi',
|
|
42
44
|
'exit "${AGY_FAKE_EXIT:-0}"',
|
|
43
45
|
'',
|
|
44
46
|
].join('\n');
|
|
@@ -190,17 +192,100 @@ describe('agy-review.sh — guard + grounding (2, 3)', () => {
|
|
|
190
192
|
assert.match(r.prompt, /## Focus\nfirst second third/);
|
|
191
193
|
});
|
|
192
194
|
|
|
193
|
-
it('
|
|
195
|
+
it('plan mode with no --facts keeps the warning and proceeds (unchanged contract)', () => {
|
|
194
196
|
const sb = makeSandbox();
|
|
195
|
-
|
|
197
|
+
writeFileSync(join(sb.repo, 'p.md'), '# plan body\n');
|
|
198
|
+
const r = run(sb, { args: ['plan', 'p.md'] });
|
|
196
199
|
rmSync(sb.home, { recursive: true, force: true });
|
|
197
200
|
assert.equal(r.status, 0, r.stderr);
|
|
198
201
|
assert.match(r.stderr, /no --facts supplied/);
|
|
199
|
-
assert.equal(r.invoked, true, 'an ungrounded review still proceeds (warn, not block)');
|
|
202
|
+
assert.equal(r.invoked, true, 'an ungrounded plan review still proceeds (warn, not block)');
|
|
200
203
|
assert.match(r.prompt, /none supplied/, 'the prompt notes the missing facts in-band');
|
|
201
204
|
});
|
|
202
205
|
});
|
|
203
206
|
|
|
207
|
+
// ── code mode fails CLOSED without grounded facts (D4) ───────────────────────────
|
|
208
|
+
// An ungrounded CODE receipt records grounded:false, which the kit's review-state gate rejects —
|
|
209
|
+
// the run would be paid for and attest nothing. The wrapper refuses BEFORE the spend, keyed on the
|
|
210
|
+
// resolved CONTENT (an empty --facts payload refuses identically). Escapes: the explicit
|
|
211
|
+
// --ungrounded flag (throwaway opinion) and AGY_PROBE=1 (a probe receipt never attests anyway).
|
|
212
|
+
describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)', () => {
|
|
213
|
+
it('code mode with no --facts exits 2 before any agy invocation', () => {
|
|
214
|
+
const sb = makeSandbox();
|
|
215
|
+
const r = run(sb, { args: ['code'] });
|
|
216
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
217
|
+
assert.equal(r.status, 2, r.stderr);
|
|
218
|
+
assert.equal(r.invoked, false, 'the refusal must fire before any agy invocation — zero runs spent');
|
|
219
|
+
assert.match(r.stderr, /grounding\.mjs/, 'the refusal names the facts assembler');
|
|
220
|
+
assert.match(r.stderr, /agy-review code --facts @/, 'the refusal prints the exact re-run line');
|
|
221
|
+
const hint = r.stderr.match(/node "([^"]+grounding\.mjs)"/);
|
|
222
|
+
assert.ok(hint, 'the recovery hint resolves and QUOTES a real grounding.mjs path (an install path may carry spaces)');
|
|
223
|
+
assert.ok(existsSync(hint[1]), 'the resolved hint path exists on this layout');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('code mode with --facts naming an EMPTY payload exits 2 before any agy invocation', () => {
|
|
227
|
+
const sb = makeSandbox();
|
|
228
|
+
writeFileSync(join(sb.repo, 'empty-facts.md'), '');
|
|
229
|
+
const r = run(sb, { args: ['code', '--facts', '@empty-facts.md'] });
|
|
230
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
231
|
+
assert.equal(r.status, 2, 'the refusal keys on the CONTENT, not the flag');
|
|
232
|
+
assert.equal(r.invoked, false, 'an empty payload must not spend a run');
|
|
233
|
+
assert.match(r.stderr, /agy-review code --facts @/);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('code --ungrounded proceeds and the receipt records grounded:false', () => {
|
|
237
|
+
const sb = makeSandbox();
|
|
238
|
+
const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
239
|
+
const receipts = readReceipts(sb.repo);
|
|
240
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
241
|
+
assert.equal(r.status, 0, r.stderr);
|
|
242
|
+
assert.equal(r.invoked, true, 'the explicit escape lets the run proceed');
|
|
243
|
+
assert.equal(receipts.length, 1);
|
|
244
|
+
assert.equal(receipts[0].grounded, false, 'an --ungrounded run still records grounded:false');
|
|
245
|
+
assert.equal(receipts[0].factsHash, null);
|
|
246
|
+
assert.match(r.stderr, /no --facts supplied/, 'the escape path stays loud, never silent');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('AGY_PROBE=1 code with no --facts proceeds and the receipt records probe:true', () => {
|
|
250
|
+
const sb = makeSandbox();
|
|
251
|
+
const r = run(sb, { args: ['code'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
252
|
+
const receipts = readReceipts(sb.repo);
|
|
253
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
254
|
+
assert.equal(r.status, 0, r.stderr);
|
|
255
|
+
assert.equal(r.invoked, true, 'an ungrounded probe is coherent — a probe receipt never attests');
|
|
256
|
+
assert.equal(receipts[0].probe, true);
|
|
257
|
+
assert.equal(receipts[0].grounded, false);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('--ungrounded with --facts is a refusal (contradiction)', () => {
|
|
261
|
+
const sb = makeSandbox();
|
|
262
|
+
const r = run(sb, { args: ['code', '--ungrounded', '--facts', 'f'] });
|
|
263
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
264
|
+
assert.equal(r.status, 2);
|
|
265
|
+
assert.equal(r.invoked, false);
|
|
266
|
+
assert.match(r.stderr, /--ungrounded contradicts --facts/);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('--ungrounded outside code mode is a refusal', () => {
|
|
270
|
+
const sb = makeSandbox();
|
|
271
|
+
writeFileSync(join(sb.repo, 'p.md'), '# p\n');
|
|
272
|
+
const r = run(sb, { args: ['plan', 'p.md', '--ungrounded'] });
|
|
273
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
274
|
+
assert.equal(r.status, 2);
|
|
275
|
+
assert.equal(r.invoked, false);
|
|
276
|
+
assert.match(r.stderr, /--ungrounded is only valid in code mode/);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('--ungrounded on a continuation is a refusal', () => {
|
|
280
|
+
const sb = makeSandbox();
|
|
281
|
+
const r = run(sb, { args: ['--continue', '--ungrounded'] });
|
|
282
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
283
|
+
assert.equal(r.status, 2);
|
|
284
|
+
assert.equal(r.invoked, false);
|
|
285
|
+
assert.match(r.stderr, /--ungrounded is not valid on a continuation/);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
204
289
|
describe('agy-review.sh — code-mode precomputed diff (4, 5, 8)', () => {
|
|
205
290
|
it('assembles repo map + status + untracked CONTENTS', () => {
|
|
206
291
|
const sb = makeSandbox();
|
|
@@ -754,7 +839,12 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
|
|
|
754
839
|
for (const descriptor of REVIEW_CONTRACT.flags) {
|
|
755
840
|
const flag = leadingFlag(descriptor);
|
|
756
841
|
const sb = makeSandbox();
|
|
757
|
-
|
|
842
|
+
// D4: code mode refuses without grounded facts, so a non-facts value flag is driven on a
|
|
843
|
+
// grounded run; --ungrounded takes no value, contradicts --facts, and is driven alone.
|
|
844
|
+
const args = flag === '--facts' ? ['code', '--facts', 'f']
|
|
845
|
+
: flag === '--ungrounded' ? ['code', '--ungrounded']
|
|
846
|
+
: ['code', '--facts', 'f', flag, 'f'];
|
|
847
|
+
const r = run(sb, { args });
|
|
758
848
|
rmSync(sb.home, { recursive: true, force: true });
|
|
759
849
|
assert.equal(r.status, 0, `${flag}: ${r.stderr}`);
|
|
760
850
|
}
|
|
@@ -782,7 +872,7 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
|
|
|
782
872
|
// The normative fixture: the AD-038 shape + the D3 self-declaring probe marker (backend/verdict here
|
|
783
873
|
// carry this bridge's vocabulary; dynamic values are asserted by shape):
|
|
784
874
|
const RECEIPT_FIXTURE = JSON.parse(
|
|
785
|
-
'{"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}',
|
|
875
|
+
'{"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":"<display>"}}',
|
|
786
876
|
);
|
|
787
877
|
const RECEIPTS_REL = join('.git', 'agent-workflow-review-receipts.jsonl');
|
|
788
878
|
const readReceipts = (repo) => {
|
|
@@ -853,9 +943,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
853
943
|
assert.equal(receipts[0].probe, true, 'both write paths carry the marker — no unmarked probe lane');
|
|
854
944
|
});
|
|
855
945
|
|
|
856
|
-
it('an ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
|
|
946
|
+
it('an --ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
|
|
857
947
|
const sb = makeSandbox();
|
|
858
|
-
const r = run(sb, { args: ['code'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
948
|
+
const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
859
949
|
const receipts = readReceipts(sb.repo);
|
|
860
950
|
rmSync(sb.home, { recursive: true, force: true });
|
|
861
951
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -863,23 +953,21 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
863
953
|
assert.equal(receipts[0].factsHash, null);
|
|
864
954
|
});
|
|
865
955
|
|
|
866
|
-
it('an EMPTY --facts file
|
|
956
|
+
it('an EMPTY --facts file in code mode refuses pre-spend — no run, no receipt (D4 fail-closed)', () => {
|
|
867
957
|
const sb = makeSandbox();
|
|
868
958
|
writeFileSync(join(sb.home, 'empty-facts.md'), '');
|
|
869
959
|
const r = run(sb, { args: ['code', '--facts', `@${join(sb.home, 'empty-facts.md')}`], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
870
960
|
const receipts = readReceipts(sb.repo);
|
|
871
961
|
rmSync(sb.home, { recursive: true, force: true });
|
|
872
|
-
assert.equal(r.status,
|
|
873
|
-
assert.equal(
|
|
874
|
-
assert.equal(receipts
|
|
875
|
-
assert.match(r.stderr, /no --facts supplied|ungrounded review GUESSES/, 'the ungrounded warning fires for an empty payload');
|
|
962
|
+
assert.equal(r.status, 2, 'vacuous grounding no longer spends a run');
|
|
963
|
+
assert.equal(r.invoked, false);
|
|
964
|
+
assert.equal(receipts.length, 0, 'no run — no receipt');
|
|
876
965
|
});
|
|
877
966
|
|
|
878
|
-
it('parses REWORK and plain SHIP
|
|
967
|
+
it('parses REWORK and plain SHIP verbatim (an absent section is a FAILED run — the D4 describe owns that arm)', () => {
|
|
879
968
|
for (const [output, want] of [
|
|
880
969
|
['### Verdict\nREWORK — the contract is violated.', 'REWORK'],
|
|
881
970
|
['### Verdict\nSHIP — clean.', 'SHIP'],
|
|
882
|
-
['free-form text with no verdict heading', 'unknown'],
|
|
883
971
|
]) {
|
|
884
972
|
const sb = makeSandbox();
|
|
885
973
|
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: output } });
|
|
@@ -1177,3 +1265,93 @@ describe('agy-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
|
|
|
1177
1265
|
}
|
|
1178
1266
|
});
|
|
1179
1267
|
});
|
|
1268
|
+
|
|
1269
|
+
// ── strip-the-kit Phase 4: wrapper honesty (D4) + dispatch-posture labeling (D5) ────────────────
|
|
1270
|
+
describe('agy-review.sh — wrapper honesty: a verdict-less run is a FAILED review (D4)', () => {
|
|
1271
|
+
it('a VERDICT-LESS review output: non-zero exit, NO receipt, the stated re-run recovery', () => {
|
|
1272
|
+
const sb = makeSandbox();
|
|
1273
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: 'prose without the mandated section' } });
|
|
1274
|
+
const receipts = readReceipts(sb.repo);
|
|
1275
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1276
|
+
assert.notEqual(r.status, 0, 'a verdict-less review never exits 0');
|
|
1277
|
+
assert.equal(receipts.length, 0, 'NO receipt is minted for a failed review');
|
|
1278
|
+
assert.match(r.stderr, /### Verdict/, 'the missing section is named');
|
|
1279
|
+
assert.match(r.stderr, /re-run/i, 'documented as a failed review — re-run, never fatal');
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
it('EMPTY review output is the same failed run (non-zero, no receipt)', () => {
|
|
1283
|
+
const sb = makeSandbox();
|
|
1284
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: '' } });
|
|
1285
|
+
const receipts = readReceipts(sb.repo);
|
|
1286
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1287
|
+
assert.notEqual(r.status, 0);
|
|
1288
|
+
assert.equal(receipts.length, 0);
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
it('the closed vocabulary still parses (SHIP WITH NITS before SHIP; REWORK) and a recognized run exits 0', () => {
|
|
1292
|
+
for (const [out, want] of [[VERDICT_OUTPUT, 'SHIP WITH NITS'], ['### Verdict\nREWORK — reasons.\n', 'REWORK']]) {
|
|
1293
|
+
const sb = makeSandbox();
|
|
1294
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: out } });
|
|
1295
|
+
const receipts = readReceipts(sb.repo);
|
|
1296
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1297
|
+
assert.equal(r.status, 0, r.stderr);
|
|
1298
|
+
assert.equal(receipts[0].verdict, want);
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
1304
|
+
it('ONE banner line carries the ACTUAL model and the receipt carries the SAME posture (agy has no tier)', () => {
|
|
1305
|
+
const sb = makeSandbox();
|
|
1306
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
1307
|
+
const receipts = readReceipts(sb.repo);
|
|
1308
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1309
|
+
assert.equal(r.status, 0, r.stderr);
|
|
1310
|
+
assert.match(r.stderr, /review posture: model=Gemini 3\.1 Pro \(High\)/, 'the banner states the actual run posture');
|
|
1311
|
+
assert.deepEqual(receipts[0].posture, { model: 'Gemini 3.1 Pro (High)' }, 'banner ↔ receipt parity');
|
|
1312
|
+
assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
it('an ATTESTING review with AGY_MODEL explicitly emptied REFUSES pre-spend naming the fix', () => {
|
|
1316
|
+
const sb = makeSandbox();
|
|
1317
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '' } });
|
|
1318
|
+
const receipts = readReceipts(sb.repo);
|
|
1319
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1320
|
+
assert.notEqual(r.status, 0);
|
|
1321
|
+
assert.equal(r.invoked, false, 'refused BEFORE any spend');
|
|
1322
|
+
assert.equal(receipts.length, 0);
|
|
1323
|
+
assert.match(r.stderr, /AGY_MODEL/, 'the fix is named');
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
it('AGY_PROBE=1 with AGY_MODEL emptied still runs (probe exempt; posture model null on the probe receipt)', () => {
|
|
1327
|
+
const sb = makeSandbox();
|
|
1328
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '', AGY_PROBE: '1' } });
|
|
1329
|
+
const receipts = readReceipts(sb.repo);
|
|
1330
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1331
|
+
assert.equal(r.status, 0, r.stderr);
|
|
1332
|
+
assert.equal(receipts[0].probe, true);
|
|
1333
|
+
assert.deepEqual(receipts[0].posture, { model: null }, 'an unknowable model is recorded null, never guessed');
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded', () => {
|
|
1337
|
+
const hostile = 'we"ird \\ mo"del';
|
|
1338
|
+
const sb = makeSandbox();
|
|
1339
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: hostile } });
|
|
1340
|
+
const receipts = readReceipts(sb.repo); // JSON.parse throwing here IS the encoding failure
|
|
1341
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1342
|
+
assert.equal(r.status, 0, r.stderr);
|
|
1343
|
+
assert.equal(receipts[0].posture.model, hostile, 'the exact bytes round-trip through strict encoding');
|
|
1344
|
+
assert.match(r.stderr, /review posture: /, 'the banner still renders');
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
it('a model string carrying CONTROL BYTES refuses pre-spend (never a broken banner or receipt)', () => {
|
|
1348
|
+
const sb = makeSandbox();
|
|
1349
|
+
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: `bad${String.fromCharCode(1)}model` } });
|
|
1350
|
+
const receipts = readReceipts(sb.repo);
|
|
1351
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1352
|
+
assert.notEqual(r.status, 0);
|
|
1353
|
+
assert.equal(r.invoked, false);
|
|
1354
|
+
assert.equal(receipts.length, 0);
|
|
1355
|
+
assert.match(r.stderr, /control/i);
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "antigravity-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "4.0.0",
|
|
7
7
|
"provides": ["review", "probe"],
|
|
8
|
+
"posture": { "model": "Gemini 3.1 Pro (High)" },
|
|
8
9
|
"roles": {
|
|
9
10
|
"review": {
|
|
10
11
|
"cmd": "agy-review",
|
|
@@ -14,13 +15,14 @@
|
|
|
14
15
|
"output": "advisory",
|
|
15
16
|
"contract": {
|
|
16
17
|
"invocations": [
|
|
17
|
-
"agy-review code [--facts @f] [--decided @f] [--focus \"…\"] [extra focus…]",
|
|
18
|
+
"agy-review code [--facts @f] [--ungrounded] [--decided @f] [--focus \"…\"] [extra focus…]",
|
|
18
19
|
"agy-review plan <plan-file> [--facts @f] [--decided @f] [--focus \"…\"]",
|
|
19
20
|
"agy-review diff <diff-file> [--facts @f] [--decided @f] [--focus \"…\"]"
|
|
20
21
|
],
|
|
21
|
-
"grounding": "grounded review — agy reads NOTHING by default, an ungrounded review GUESSES: --facts @f = the verified facts to review AGAINST; --decided @f = decisions already made, do NOT re-raise (anti-circling)",
|
|
22
|
+
"grounding": "grounded review — agy reads NOTHING by default, an ungrounded review GUESSES: --facts @f = the verified facts to review AGAINST; --decided @f = decisions already made, do NOT re-raise (anti-circling). code mode REQUIRES a non-empty --facts payload and refuses BEFORE spending a run (escapes: --ungrounded, AGY_PROBE=1); plan/diff proceed with a loud warning",
|
|
22
23
|
"flags": [
|
|
23
|
-
"--facts @f — verified facts the review runs AGAINST (
|
|
24
|
+
"--facts @f — verified facts the review runs AGAINST (code mode REQUIRES a non-empty payload; plan/diff warn loudly when omitted)",
|
|
25
|
+
"--ungrounded — deliberately ungrounded CODE review, a throwaway opinion (code mode only, contradicts --facts; the receipt records grounded:false and never attests)",
|
|
24
26
|
"--decided @f — already-decided / already-addressed list; do NOT re-raise (anti-circling; the round-2 payload)",
|
|
25
27
|
"--focus \"…\" — extra focus (repeatable; code mode also takes trailing focus words)"
|
|
26
28
|
],
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
"agy-review --continue [--decided @f] [--focus \"…\"]",
|
|
29
31
|
"agy-review --conversation <id> [--decided @f] [--focus \"…\"]"
|
|
30
32
|
],
|
|
31
|
-
"receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): 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/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_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); a write failure warns, never fails the review",
|
|
33
|
+
"receipt": "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): 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/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_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} (agy has no 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, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); a write failure warns, never fails the review",
|
|
32
34
|
"notes": [
|
|
33
35
|
"pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt"
|
|
34
36
|
]
|
|
@@ -50,7 +52,8 @@
|
|
|
50
52
|
"whenNotTo": ["a clean tree — the wrapper exits before spending a run"],
|
|
51
53
|
"invocationRefs": [{ "contractField": "invocations", "index": 0 }],
|
|
52
54
|
"operands": [
|
|
53
|
-
{ "slot": "[--facts @f]", "required": false, "description": "the verified facts to review AGAINST;
|
|
55
|
+
{ "slot": "[--facts @f]", "required": false, "description": "the verified facts to review AGAINST; code mode refuses pre-spend without a non-empty payload (escapes: --ungrounded, AGY_PROBE=1)" },
|
|
56
|
+
{ "slot": "[--ungrounded]", "required": false, "description": "deliberately ungrounded code review — a throwaway opinion; the receipt records grounded:false and never attests" },
|
|
54
57
|
{ "slot": "[--decided @f]", "required": false, "description": "decisions already made; the reviewer must not re-raise them (anti-circling)" },
|
|
55
58
|
{ "slot": "[--focus \"…\"]", "required": false, "description": "what this review must look at (repeatable)" },
|
|
56
59
|
{ "slot": "[extra focus…]", "required": false, "description": "extra focus words appended to the review directive" }
|
|
@@ -59,6 +62,7 @@
|
|
|
59
62
|
{ "value": "read-only posture — the prompt forbids edits, commands and git writes", "enforcement": "advisory", "source": "bin/agy-review.sh" },
|
|
60
63
|
{ "value": "receives no ambient repo/file context by default — the wrapper passes no --add-dir", "enforcement": "enforced", "source": "bin/agy-review.sh" },
|
|
61
64
|
{ "value": "an ungrounded review guesses — stale-model and partial-diff false positives", "enforcement": "advisory", "source": "bin/agy-review.sh" },
|
|
65
|
+
{ "value": "without a non-empty --facts payload the run refuses BEFORE the spend (exit 2) — the only escapes are --ungrounded and AGY_PROBE=1", "enforcement": "enforced", "source": "bin/agy-review.sh" },
|
|
62
66
|
{ "value": "an ungrounded run records grounded:false and the review-state gate rejects it", "enforcement": "enforced", "source": "capability.json roles.review.contract.receipt" },
|
|
63
67
|
{ "value": "an oversized prompt refuses rather than truncate", "enforcement": "enforced", "condition": "unless AGY_REVIEW_ALLOW_ADDDIR=1 offloads it to a private --add-dir staging dir", "source": "capability.json settings.AGY_REVIEW_ALLOW_ADDDIR" }
|
|
64
68
|
],
|
|
@@ -185,9 +189,9 @@
|
|
|
185
189
|
"deliberately reviewing with a cheaper non-frontier model"
|
|
186
190
|
],
|
|
187
191
|
"whenNotTo": ["any run whose output informs what ships — a probe never attests"],
|
|
188
|
-
"descriptor": "AGY_PROBE=1 agy-review code --facts @<facts-file>",
|
|
192
|
+
"descriptor": "AGY_PROBE=1 agy-review code [--facts @<facts-file>]",
|
|
189
193
|
"operands": [
|
|
190
|
-
{ "slot": "@<facts-file>", "required":
|
|
194
|
+
{ "slot": "[--facts @<facts-file>]", "required": false, "description": "the verified facts the review runs AGAINST — a probe may run ungrounded (its receipt never attests either way)" }
|
|
191
195
|
],
|
|
192
196
|
"guardrails": [
|
|
193
197
|
{ "value": "a probe review mints a probe-marked receipt the review-state gate rejects", "enforcement": "enforced", "source": "agent-workflow-kit tools/review-state.mjs" }
|