repo-harness 0.7.5 → 0.8.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/README.es.md CHANGED
@@ -85,7 +85,7 @@ artifacts.
85
85
  ## Novedades
86
86
 
87
87
  Las notas de versión viven en [`docs/CHANGELOG.md`](docs/CHANGELOG.md). La línea
88
- actual es `0.7.5`.
88
+ actual es `0.8.0`.
89
89
 
90
90
  ## Cómo funciona
91
91
 
@@ -419,8 +419,8 @@ Guards habituales:
419
419
 
420
420
  ## Release actual
421
421
 
422
- - npm package: `repo-harness@0.7.5`
423
- - Generated workflow stamp: `repo-harness@0.7.5+template@0.7.5`
422
+ - npm package: `repo-harness@0.8.0`
423
+ - Generated workflow stamp: `repo-harness@0.8.0+template@0.8.0`
424
424
  - GitHub repository: `Ancienttwo/repo-harness`
425
425
  - Release history: [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
426
426
 
@@ -584,7 +584,7 @@ bun run benchmark:skills --eval repair-agents-task-sync
584
584
  - `scripts/create-project-dirs.sh`
585
585
  - Legacy-doc migrator: `scripts/migrate-workflow-docs.ts`
586
586
 
587
- ## Generated vs Self-Hosted Hook Parity
587
+ ## Generated vs Self-Hosted Hook Projection
588
588
 
589
589
  - El comportamiento downstream de hooks lo define la salida generada desde `assets/hooks/` y `assets/reference-configs/`.
590
590
  - Este repo dogfoodea el mismo contract, pero el comportamiento self-host no se sincroniza mágicamente con los generated repos; cada cambio debe actualizar explícitamente ambas superficies cuando aplique.
package/README.fr.md CHANGED
@@ -85,7 +85,7 @@ l'emportent.
85
85
  ## Nouveautés
86
86
 
87
87
  Les notes de version vivent dans [`docs/CHANGELOG.md`](docs/CHANGELOG.md). La
88
- ligne actuelle est `0.7.5`.
88
+ ligne actuelle est `0.8.0`.
89
89
 
90
90
  ## Comment ça marche
91
91
 
@@ -424,8 +424,8 @@ Guards courants :
424
424
 
425
425
  ## Release actuelle
426
426
 
427
- - npm package : `repo-harness@0.7.5`
428
- - Generated workflow stamp : `repo-harness@0.7.5+template@0.7.5`
427
+ - npm package : `repo-harness@0.8.0`
428
+ - Generated workflow stamp : `repo-harness@0.8.0+template@0.8.0`
429
429
  - GitHub repository : `Ancienttwo/repo-harness`
430
430
  - Release history : [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
431
431
 
@@ -590,7 +590,7 @@ bun run benchmark:skills --eval repair-agents-task-sync
590
590
  - `scripts/create-project-dirs.sh`
591
591
  - Legacy-doc migrator : `scripts/migrate-workflow-docs.ts`
592
592
 
593
- ## Generated vs Self-Hosted Hook Parity
593
+ ## Generated vs Self-Hosted Hook Projection
594
594
 
595
595
  - Le comportement downstream des hooks est défini par la sortie générée depuis `assets/hooks/` et `assets/reference-configs/`.
596
596
  - Ce repo dogfoode le même contract, mais le comportement self-host ne se synchronise pas magiquement avec les generated repos ; un changement doit mettre à jour explicitement les deux surfaces lorsque nécessaire.
package/README.ja.md CHANGED
@@ -75,7 +75,7 @@ review、checks、handoff と食い違う場合は、source artifacts を優先
75
75
  ## What's New
76
76
 
77
77
  リリースノートは [`docs/CHANGELOG.md`](docs/CHANGELOG.md) にあります。現在の
78
- ラインは `0.7.5` です。
78
+ ラインは `0.8.0` です。
79
79
 
80
80
  ## 仕組み
81
81
 
@@ -398,8 +398,8 @@ hook がブロックしたときは、まず terminal の構造化された出
398
398
 
399
399
  ## 現在の Release
400
400
 
401
- - npm package:`repo-harness@0.7.5`
402
- - Generated workflow stamp:`repo-harness@0.7.5+template@0.7.5`
401
+ - npm package:`repo-harness@0.8.0`
402
+ - Generated workflow stamp:`repo-harness@0.8.0+template@0.8.0`
403
403
  - GitHub repository:`Ancienttwo/repo-harness`
404
404
  - Release history:[`docs/CHANGELOG.md`](docs/CHANGELOG.md)
405
405
 
@@ -558,7 +558,7 @@ bun run benchmark:skills --eval repair-agents-task-sync
558
558
  - `scripts/create-project-dirs.sh`
559
559
  - Legacy-doc migrator:`scripts/migrate-workflow-docs.ts`
560
560
 
561
- ## Generated vs Self-Hosted Hook Parity
561
+ ## Generated vs Self-Hosted Hook Projection
562
562
 
563
563
  - Downstream hook behavior は `assets/hooks/` と `assets/reference-configs/` から生成される出力で定義されます。
564
564
  - この repo は同じ contract を dogfood しますが、self-host behavior は generated repos と自動同期されません。変更は両方の surface を明示的に更新する必要があります。
package/README.md CHANGED
@@ -78,7 +78,7 @@ active plan, contract, review, checks, or handoff, the source artifacts win.
78
78
  ## What's New
79
79
 
80
80
  Release notes live in [`docs/CHANGELOG.md`](docs/CHANGELOG.md). The current line
81
- is `0.7.5`.
81
+ is `0.8.0`.
82
82
 
83
83
  ## How It Works
84
84
 
@@ -439,13 +439,13 @@ and not arbitrary shell.
439
439
 
440
440
  ## Hook Authority Map
441
441
 
442
- - `.ai/hooks/` is the only shared hook implementation you should edit first.
442
+ - `assets/hooks/` is the only human-authored shared hook implementation. This self-host repo keeps `.ai/hooks/` as a checked-in generated projection for `"hook_source": "repo"` dogfood.
443
443
  - `~/.claude/settings.json` is the user-level Claude adapter that dispatches into opted-in repos.
444
444
  - `~/.codex/hooks.json` is the user-level Codex adapter that dispatches into the same runner.
445
445
  - Repo-local `.claude/settings.json` and `.codex/hooks.json` hook adapters are legacy project-level config and should be retired during migration.
446
446
  - Codex must mark `~/.codex/hooks.json` as trusted in Codex Settings before those hooks run.
447
- - Debug in this order: user-level adapter config -> `repo-harness-hook` (or fallback `repo-harness hook`) -> route registry -> `.ai/hooks/*`.
448
- - If `repo-harness-hook` reports `.ai/hooks` drift, refresh the repo-local copy with `repo-harness adopt --repo <root>`.
447
+ - Debug in this order: user-level adapter config -> `repo-harness-hook` (or fallback `repo-harness hook`) -> route registry -> active hook source.
448
+ - For hook product changes, edit `assets/hooks/<path>`, run `bun run sync:hooks`, then verify with `bun run check:hooks`. Package-only templates are classified in `assets/hooks/projection.json` and are not projected into `.ai/hooks/`.
449
449
 
450
450
  The installed adapter owns eight shared managed hook routes. Codex also installs
451
451
  three Codex-only bounded-delegation routes. The route tuple
@@ -535,8 +535,8 @@ Most common guards:
535
535
 
536
536
  ## Current Release
537
537
 
538
- - npm package: `repo-harness@0.7.5`
539
- - Generated workflow stamp: `repo-harness@0.7.5+template@0.7.5`
538
+ - npm package: `repo-harness@0.8.0`
539
+ - Generated workflow stamp: `repo-harness@0.8.0+template@0.8.0`
540
540
  - GitHub repository: `Ancienttwo/repo-harness`
541
541
  - Release history: [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
542
542
 
@@ -705,13 +705,11 @@ bun run benchmark:skills --eval repair-agents-task-sync
705
705
  - `scripts/init-project.sh`
706
706
  - `scripts/create-project-dirs.sh`
707
707
 
708
- ## Generated vs Self-Hosted Hook Parity
708
+ ## Generated vs Self-Hosted Hook Projection
709
709
 
710
- - Downstream hook behavior is defined by generated output from `assets/hooks/` plus
711
- `assets/reference-configs/`.
712
- - This repo dogfoods the same contract, but self-host behavior is not magically in
713
- sync with generated repos unless a change explicitly updates both surfaces.
714
- - Every hook change should say whether it affects `self-host`, `generated`, or `both`.
710
+ - Downstream hook behavior is defined by `assets/hooks/` plus `assets/reference-configs/`.
711
+ - This repo dogfoods the same hook runtime through `.ai/hooks/`, but that tree is generated from `assets/hooks/projection.json`.
712
+ - Every hook change should update canonical `assets/hooks/` once, run `bun run sync:hooks`, and include `bun run check:hooks` in verification.
715
713
 
716
714
  ## Package Manager Defaults
717
715
 
package/README.zh-CN.md CHANGED
@@ -69,7 +69,7 @@ review、checks 或 handoff 冲突,以 source artifacts 为准。
69
69
 
70
70
  ## What's New
71
71
 
72
- Release notes 见 [`docs/CHANGELOG.md`](docs/CHANGELOG.md),当前版本线是 `0.7.5`。
72
+ Release notes 见 [`docs/CHANGELOG.md`](docs/CHANGELOG.md),当前版本线是 `0.8.0`。
73
73
 
74
74
  ## 工作原理
75
75
 
@@ -360,12 +360,13 @@ repo-harness mcp serve --repo . --transport http --profile orchestrator --enable
360
360
 
361
361
  ## Hook Authority Map
362
362
 
363
- - `.ai/hooks/` 是唯一应该优先编辑的 shared hook implementation。
363
+ - `assets/hooks/` 是唯一人工维护的 shared hook implementation。本仓库的 `.ai/hooks/` 是给 `"hook_source": "repo"` dogfood 使用的 checked-in generated projection。
364
364
  - `~/.claude/settings.json` 是 user-level Claude adapter,负责 dispatch 到 opted-in repos。
365
365
  - `~/.codex/hooks.json` 是 user-level Codex adapter,dispatch 到同一个 runner。
366
366
  - Repo-local `.claude/settings.json` 和 `.codex/hooks.json` hook adapters 是 legacy project-level config,迁移时应退休。
367
367
  - Codex 必须在 Settings 里信任 `~/.codex/hooks.json`,hooks 才会执行。
368
- - 调试顺序:user-level adapter config -> `repo-harness-hook` 或 fallback `repo-harness hook` -> route registry -> `.ai/hooks/*`。
368
+ - 调试顺序:user-level adapter config -> `repo-harness-hook` 或 fallback `repo-harness hook` -> route registry -> active hook source。
369
+ - Hook 产品变更只改 `assets/hooks/<path>`,然后运行 `bun run sync:hooks` 和 `bun run check:hooks`。Package-only templates 在 `assets/hooks/projection.json` 分类,不投影到 `.ai/hooks/`。
369
370
 
370
371
 
371
372
  The installed adapter owns eight managed hook routes. The route tuple
@@ -444,8 +445,8 @@ hook block 工作时,先看 terminal 里的结构化输出。核心字段是
444
445
 
445
446
  ## 当前 Release
446
447
 
447
- - npm package:`repo-harness@0.7.5`
448
- - Generated workflow stamp:`repo-harness@0.7.5+template@0.7.5`
448
+ - npm package:`repo-harness@0.8.0`
449
+ - Generated workflow stamp:`repo-harness@0.8.0+template@0.8.0`
449
450
  - GitHub repository:`Ancienttwo/repo-harness`
450
451
  - Release history:[`docs/CHANGELOG.md`](docs/CHANGELOG.md)
451
452
 
@@ -618,11 +619,11 @@ bun run benchmark:skills --eval repair-agents-task-sync
618
619
  - `scripts/create-project-dirs.sh`
619
620
  - Legacy-doc migrator:`scripts/migrate-workflow-docs.ts`
620
621
 
621
- ## Generated vs Self-Hosted Hook Parity
622
+ ## Generated vs Self-Hosted Hook Projection
622
623
 
623
- - 下游 hook 行为由 `assets/hooks/` 和 `assets/reference-configs/` 的生成输出定义。
624
- - 本仓库 dogfood 同一套 contract,但 self-host 行为不会自动与 generated repos 同步;变更必须显式更新两侧 surface。
625
- - 每个 hook 变更都要说明影响 `self-host`、`generated` 还是 `both`。
624
+ - 下游 hook 行为由 `assets/hooks/` 和 `assets/reference-configs/` 定义。
625
+ - 本仓库通过 `.ai/hooks/` dogfood 同一套 hook runtime,但这棵树由 `assets/hooks/projection.json` 生成。
626
+ - 每个 hook 变更只应更新 canonical `assets/hooks/`,运行 `bun run sync:hooks`,并在验证里包含 `bun run check:hooks`。
626
627
 
627
628
  ## Package Manager Defaults
628
629
 
@@ -11,6 +11,8 @@ Keep this file focused on the local contract for this primary functional block.
11
11
  - Treat `.ai/context/context-map.json` as the index of discoverable context files.
12
12
  - Do not keep pushing context files deeper by default; add lower-level files only for a separately owned functional block with its own commands and invariants.
13
13
  - Prefer repo-local workflow artifacts over tool-specific chat memory.
14
+ - `assets/hooks/` is the only human-authored hook source. Treat `.ai/hooks/` as the generated self-host projection; after editing hook files, run `bun run sync:hooks`, then `bun run check:hooks`.
15
+ - Do not hand-edit generated `.ai/hooks/` drift. Classify package-only or repo-only exceptions in `assets/hooks/projection.json`.
14
16
 
15
17
  <!-- BEGIN CAPABILITY CONTEXT -->
16
18
  ## Capability Context
@@ -11,6 +11,8 @@ Keep this file focused on the local contract for this primary functional block.
11
11
  - Treat `.ai/context/context-map.json` as the index of discoverable context files.
12
12
  - Do not keep pushing context files deeper by default; add lower-level files only for a separately owned functional block with its own commands and invariants.
13
13
  - Prefer repo-local workflow artifacts over tool-specific chat memory.
14
+ - `assets/hooks/` is the only human-authored hook source. Treat `.ai/hooks/` as the generated self-host projection; after editing hook files, run `bun run sync:hooks`, then `bun run check:hooks`.
15
+ - Do not hand-edit generated `.ai/hooks/` drift. Classify package-only or repo-only exceptions in `assets/hooks/projection.json`.
14
16
 
15
17
  <!-- BEGIN CAPABILITY CONTEXT -->
16
18
  ## Capability Context
@@ -21,32 +21,57 @@ minimal_change_post_edit_enabled() {
21
21
  [[ "$compact" == *'"post_edit_observer":true'* ]]
22
22
  }
23
23
 
24
- minimal_change_hook_entry() {
25
- local lib_dir hooks_dir source_hook_cli
24
+ repo_harness_hook_cli() {
25
+ local lib_dir hooks_dir repo_root source_cli source_hook_cli
26
26
 
27
27
  lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
28
  hooks_dir="$(cd "$lib_dir/.." && pwd)"
29
- source_hook_cli="$(cd "$hooks_dir/../.." 2>/dev/null && pwd)/src/cli/hook-entry.ts"
29
+ repo_root="$(cd "$hooks_dir/../.." 2>/dev/null && pwd)"
30
+ source_cli="$repo_root/src/cli/index.ts"
31
+ source_hook_cli="$repo_root/src/cli/hook-entry.ts"
30
32
 
31
33
  if [[ -n "${REPO_HARNESS_HOOK_CLI:-}" && -f "${REPO_HARNESS_HOOK_CLI:-}" ]] && command -v bun >/dev/null 2>&1; then
32
- bun "$REPO_HARNESS_HOOK_CLI" minimal-change "$@"
34
+ bun "$REPO_HARNESS_HOOK_CLI" "$@"
33
35
  return $?
34
36
  fi
35
37
 
36
38
  if [[ -f "$source_hook_cli" ]] && command -v bun >/dev/null 2>&1; then
37
- bun "$source_hook_cli" minimal-change "$@"
39
+ bun "$source_hook_cli" "$@"
38
40
  return $?
39
41
  fi
40
42
 
41
43
  if [[ -n "${HOOK_REPO_ROOT:-}" && -f "$HOOK_REPO_ROOT/src/cli/hook-entry.ts" ]] && command -v bun >/dev/null 2>&1; then
42
- bun "$HOOK_REPO_ROOT/src/cli/hook-entry.ts" minimal-change "$@"
44
+ bun "$HOOK_REPO_ROOT/src/cli/hook-entry.ts" "$@"
45
+ return $?
46
+ fi
47
+
48
+ if [[ -n "${REPO_HARNESS_CLI:-}" && -f "${REPO_HARNESS_CLI:-}" ]] && command -v bun >/dev/null 2>&1; then
49
+ bun "$REPO_HARNESS_CLI" "$@"
50
+ return $?
51
+ fi
52
+
53
+ if [[ -f "$source_cli" ]] && command -v bun >/dev/null 2>&1; then
54
+ bun "$source_cli" "$@"
43
55
  return $?
44
56
  fi
45
57
 
46
58
  if command -v repo-harness-hook >/dev/null 2>&1; then
47
- repo-harness-hook minimal-change "$@"
59
+ repo-harness-hook "$@"
60
+ return $?
61
+ fi
62
+
63
+ if command -v repo-harness >/dev/null 2>&1; then
64
+ repo-harness "$@"
48
65
  return $?
49
66
  fi
50
67
 
51
68
  return 127
52
69
  }
70
+
71
+ minimal_change_hook_entry() {
72
+ repo_harness_hook_cli minimal-change "$@"
73
+ }
74
+
75
+ review_rubric_prompt() {
76
+ repo_harness_hook_cli review-rubric --format prompt 2>/dev/null || true
77
+ }
@@ -1288,6 +1288,166 @@ workflow_review_recommends_pass() {
1288
1288
  grep -Eq '^> \*\*Recommendation\*\*:[[:space:]]*pass[[:space:]]*$' "$review_file"
1289
1289
  }
1290
1290
 
1291
+ workflow_review_metadata_field() {
1292
+ local review_file="${1:-}"
1293
+ local field="${2:-}"
1294
+ [[ -n "$review_file" && -f "$review_file" && -n "$field" ]] || return 1
1295
+ awk -v field="$field" '
1296
+ # Top-of-file metadata only: stop at the first section heading so a
1297
+ # section-level "> **<field>**:" line (e.g. the External Acceptance section
1298
+ # carries its own Reviewed Diff Fingerprint) can never be read as a top-level
1299
+ # value when the real header omits it.
1300
+ /^## / { exit }
1301
+ index($0, "> **" field "**:") == 1 {
1302
+ sub("^> \\*\\*" field "\\*\\*:[[:space:]]*", "");
1303
+ gsub(/\r/, "");
1304
+ print;
1305
+ exit;
1306
+ }
1307
+ ' "$review_file"
1308
+ }
1309
+
1310
+ workflow_review_fingerprint() {
1311
+ workflow_review_metadata_field "${1:-}" "Reviewed Diff Fingerprint"
1312
+ }
1313
+
1314
+ workflow_review_rubric_version() {
1315
+ workflow_review_metadata_field "${1:-}" "Review Rubric Version"
1316
+ }
1317
+
1318
+ # Classify the top-of-file Review Rubric Version. Echoes one of:
1319
+ # absent - no rubric line at all (a genuine pre-rubric legacy artifact)
1320
+ # 1 - the supported modern rubric version
1321
+ # malformed - a rubric line is present but is not a supported version
1322
+ # (non-numeric, 0, an unsupported number, or quote/space garbage)
1323
+ # A present-but-unsupported rubric means the artifact claims a schema this gate
1324
+ # cannot evaluate, so callers must fail closed rather than fall through to the
1325
+ # lenient legacy path. Only a genuinely absent rubric stays lenient.
1326
+ workflow_review_rubric_class() {
1327
+ local raw trimmed
1328
+ raw="$(workflow_review_rubric_version "${1:-}")"
1329
+ # Trim surrounding whitespace WITHOUT xargs: an unbalanced quote in the value
1330
+ # makes xargs fail and emit nothing, which would silently downgrade a malformed
1331
+ # rubric to "absent" (legacy lenient) — exactly the fail-open this prevents.
1332
+ trimmed="${raw#"${raw%%[![:space:]]*}"}"
1333
+ trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
1334
+ if [[ -z "$trimmed" ]]; then
1335
+ printf 'absent'
1336
+ elif [[ "$trimmed" == "1" ]]; then
1337
+ printf '1'
1338
+ else
1339
+ printf 'malformed'
1340
+ fi
1341
+ }
1342
+
1343
+ workflow_hook_cli_json() {
1344
+ if [[ -n "${REPO_HARNESS_HOOK_CLI:-}" && -f "${REPO_HARNESS_HOOK_CLI:-}" ]] && command -v bun >/dev/null 2>&1; then
1345
+ bun "$REPO_HARNESS_HOOK_CLI" "$@"
1346
+ return $?
1347
+ fi
1348
+ if [[ -n "${HOOK_REPO_ROOT:-}" && -f "$HOOK_REPO_ROOT/src/cli/hook-entry.ts" ]] && command -v bun >/dev/null 2>&1; then
1349
+ bun "$HOOK_REPO_ROOT/src/cli/hook-entry.ts" "$@"
1350
+ return $?
1351
+ fi
1352
+ if [[ -n "${REPO_HARNESS_CLI:-}" && -f "${REPO_HARNESS_CLI:-}" ]] && command -v bun >/dev/null 2>&1; then
1353
+ bun "$REPO_HARNESS_CLI" "$@"
1354
+ return $?
1355
+ fi
1356
+ if command -v repo-harness-hook >/dev/null 2>&1; then
1357
+ repo-harness-hook "$@"
1358
+ return $?
1359
+ fi
1360
+ if command -v repo-harness >/dev/null 2>&1; then
1361
+ repo-harness "$@"
1362
+ return $?
1363
+ fi
1364
+ return 127
1365
+ }
1366
+
1367
+ workflow_json_field() {
1368
+ local json="${1:-}"
1369
+ local field="${2:-}"
1370
+ [[ -n "$json" && -n "$field" ]] || return 1
1371
+ if command -v jq >/dev/null 2>&1; then
1372
+ printf '%s' "$json" | jq -r ".$field // empty" 2>/dev/null || true
1373
+ return
1374
+ fi
1375
+ if command -v bun >/dev/null 2>&1; then
1376
+ JSON_INPUT="$json" JSON_FIELD="$field" bun -e '
1377
+ try {
1378
+ const parsed = JSON.parse(process.env.JSON_INPUT ?? "");
1379
+ const value = parsed?.[process.env.JSON_FIELD ?? ""];
1380
+ if (value != null) process.stdout.write(String(value));
1381
+ } catch {}
1382
+ ' 2>/dev/null || true
1383
+ fi
1384
+ }
1385
+
1386
+ workflow_current_review_fingerprint_json() {
1387
+ # Bind the fingerprint to the resolved target branch so base_rev tracks the
1388
+ # target tip; without --base the CLI falls back to HEAD, making the branch diff
1389
+ # HEAD...HEAD (empty) and blinding the gate to target movement.
1390
+ local target
1391
+ target="$(workflow_target_branch)"
1392
+ workflow_hook_cli_json review-fingerprint --base "$target" --format json 2>/dev/null || true
1393
+ }
1394
+
1395
+ workflow_current_review_fingerprint_value() {
1396
+ local json status fingerprint
1397
+ json="$(workflow_current_review_fingerprint_json)"
1398
+ status="$(workflow_json_field "$json" "status")"
1399
+ [[ "$status" == "ok" ]] || return 1
1400
+ fingerprint="$(workflow_json_field "$json" "fingerprint")"
1401
+ [[ -n "$fingerprint" ]] || return 1
1402
+ printf '%s' "$fingerprint"
1403
+ }
1404
+
1405
+ workflow_review_freshness_status() {
1406
+ local review_file="${1:-}"
1407
+ local reviewed rubric_class current_json current_status current_fingerprint current_scope
1408
+
1409
+ reviewed="$(workflow_review_fingerprint "$review_file" | xargs || true)"
1410
+ rubric_class="$(workflow_review_rubric_class "$review_file")"
1411
+ if [[ "$rubric_class" == "malformed" ]]; then
1412
+ # A malformed/unsupported rubric claims a schema this gate cannot evaluate;
1413
+ # fail closed at the freshness stage instead of treating it as legacy.
1414
+ printf 'malformed_schema\t-\tReview Rubric Version is malformed or unsupported; rerun /check to record the review under a supported rubric, or record a Manual Override.\n'
1415
+ return 0
1416
+ fi
1417
+ if [[ -z "$reviewed" || "$reviewed" == "pending" || "$reviewed" == "unknown" ]]; then
1418
+ # A supported rubric (v1+) with no concrete fingerprint was never bound to the
1419
+ # diff: fail closed (`missing`). An `absent` rubric stays on the advisory legacy
1420
+ # path here — the external-acceptance gate is the authority that requires a
1421
+ # supported rubric (a rubric-less review fails external acceptance), so absent
1422
+ # is still blocked at every Done/finish/verify gate that enforces external.
1423
+ if [[ "$rubric_class" == "1" ]]; then
1424
+ printf 'missing\t-\tReview fingerprint is missing for rubric v%s; rerun /check and peer acceptance to record the current Reviewed Diff Fingerprint.\n' "$rubric_class"
1425
+ return 0
1426
+ fi
1427
+ printf 'legacy_missing\t-\tReview fingerprint is missing; rerun /check to refresh the review metadata.\n'
1428
+ return 0
1429
+ fi
1430
+ if ! [[ "$reviewed" =~ ^sha256:[0-9a-f]{64}$ ]]; then
1431
+ printf 'malformed\t%s\tReview fingerprint is malformed; rerun /check and peer acceptance.\n' "$reviewed"
1432
+ return 0
1433
+ fi
1434
+
1435
+ current_json="$(workflow_current_review_fingerprint_json)"
1436
+ current_status="$(workflow_json_field "$current_json" "status")"
1437
+ current_fingerprint="$(workflow_json_field "$current_json" "fingerprint")"
1438
+ current_scope="$(workflow_json_field "$current_json" "scope")"
1439
+ if [[ "$current_status" != "ok" || -z "$current_fingerprint" ]]; then
1440
+ printf 'unknown\t-\tCurrent implementation diff fingerprint is unknown; rerun /check after git state is readable.\n'
1441
+ return 0
1442
+ fi
1443
+ if [[ "$reviewed" != "$current_fingerprint" ]]; then
1444
+ printf 'stale\t%s\tReview is stale for current implementation diff fingerprint %s; rerun /check and peer acceptance.\n' "$reviewed" "$current_fingerprint"
1445
+ return 0
1446
+ fi
1447
+
1448
+ printf 'pass\t%s\tReview fingerprint is fresh for %s.\n' "$reviewed" "${current_scope:-branch+staged+unstaged+untracked}"
1449
+ }
1450
+
1291
1451
  workflow_external_acceptance_expected_reviewer() {
1292
1452
  local host="${HOOK_HOST:-}"
1293
1453
 
@@ -1409,6 +1569,51 @@ workflow_external_acceptance_status() {
1409
1569
  return 0
1410
1570
  fi
1411
1571
 
1572
+ # Bind the peer's acceptance to the exact diff they reviewed. A supported rubric
1573
+ # (v1) requires the External Acceptance section to carry its own current Reviewed
1574
+ # Diff Fingerprint and scope; otherwise a stale F1 acceptance keeps satisfying the
1575
+ # gate after the implementation moves to F2, because the top-of-file fingerprint
1576
+ # is agent-editable. An absent or malformed rubric fails closed here — external
1577
+ # acceptance is the authority that requires a supported rubric; Manual Override
1578
+ # above is the only escape for a genuine pre-rubric legacy artifact.
1579
+ local rubric_class section_fp section_scope current_fp
1580
+ rubric_class="$(workflow_review_rubric_class "$review_file")"
1581
+ case "$rubric_class" in
1582
+ absent)
1583
+ # An absent rubric cannot be proven to predate the rubric (it may have been
1584
+ # stripped to skip binding), so fail closed. Manual Override above is the
1585
+ # escape hatch for a genuine pre-rubric legacy artifact.
1586
+ printf 'fail\t%s\t%s\tReview Rubric Version is missing; rerun peer acceptance under a supported rubric or record a Manual Override.\n' "${reviewer:--}" "${source:--}"
1587
+ return 0
1588
+ ;;
1589
+ malformed)
1590
+ # An unsupported rubric must not silently disable the binding check.
1591
+ printf 'fail\t%s\t%s\tReview Rubric Version is malformed or unsupported; rerun peer acceptance under a supported rubric or record a Manual Override.\n' "${reviewer:--}" "${source:--}"
1592
+ return 0
1593
+ ;;
1594
+ *)
1595
+ section_fp="$(workflow_external_acceptance_field "$section" "Reviewed Diff Fingerprint" | xargs || true)"
1596
+ section_scope="$(workflow_external_acceptance_field "$section" "Reviewed Scope" | xargs || true)"
1597
+ current_fp="$(workflow_current_review_fingerprint_value || true)"
1598
+ if [[ -z "$current_fp" ]]; then
1599
+ printf 'fail\t%s\t%s\tCurrent implementation diff fingerprint is unknown; rerun peer acceptance after git state is readable.\n' "${reviewer:--}" "${source:--}"
1600
+ return 0
1601
+ fi
1602
+ if ! [[ "$section_fp" =~ ^sha256:[0-9a-f]{64}$ ]]; then
1603
+ printf 'fail\t%s\t%s\tExternal acceptance is missing a valid Reviewed Diff Fingerprint for rubric v%s; rerun peer acceptance for the current diff.\n' "${reviewer:--}" "${source:--}" "$rubric_class"
1604
+ return 0
1605
+ fi
1606
+ if [[ "$section_fp" != "$current_fp" ]]; then
1607
+ printf 'fail\t%s\t%s\tExternal acceptance fingerprint %s is stale for current implementation diff %s; rerun peer acceptance.\n' "${reviewer:--}" "${source:--}" "$section_fp" "$current_fp"
1608
+ return 0
1609
+ fi
1610
+ if [[ "$section_scope" != "branch+staged+unstaged+untracked" ]]; then
1611
+ printf 'fail\t%s\t%s\tExternal acceptance scope is %s; expected branch+staged+unstaged+untracked.\n' "${reviewer:--}" "${source:--}" "${section_scope:-missing}"
1612
+ return 0
1613
+ fi
1614
+ ;;
1615
+ esac
1616
+
1412
1617
  printf 'pass\t%s\t%s\tExternal acceptance passed.\n' "$reviewer" "$source"
1413
1618
  }
1414
1619
 
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "canonical_root": "assets/hooks",
4
+ "projection_target": ".ai/hooks",
5
+ "package_only": [
6
+ "projection.json",
7
+ "codex.hooks.template.json",
8
+ "settings.template.json"
9
+ ],
10
+ "repo_only": []
11
+ }
@@ -770,11 +770,31 @@ emit_waza_route_hint() {
770
770
 
771
771
  if pg_fact REVIEW_RELEASE; then
772
772
  echo "[WazaRoute] Review/release intent detected. Default route: Waza /check."
773
+ emit_review_rubric_prompt
774
+ emit_review_fingerprint_prompt
773
775
  emit_external_acceptance_prompt review
774
776
  emit_cross_review_hint merge
775
777
  fi
776
778
  }
777
779
 
780
+ emit_review_rubric_prompt() {
781
+ local rubric
782
+ rubric="$(review_rubric_prompt || true)"
783
+ [[ -n "$rubric" ]] || return 0
784
+ printf '%s\n' "$rubric"
785
+ }
786
+
787
+ emit_review_fingerprint_prompt() {
788
+ local fingerprint review_file
789
+ fingerprint="$(workflow_current_review_fingerprint_value || true)"
790
+ review_file="$(workflow_active_review || true)"
791
+ echo "[ReviewFreshness] Current implementation diff fingerprint: ${fingerprint:-unknown}"
792
+ echo "[ReviewFreshness] Record these review metadata lines in ${review_file:-tasks/reviews/<slug>.review.md}:"
793
+ echo "> **Review Rubric Version**: 1"
794
+ echo "> **Reviewed Diff Fingerprint**: ${fingerprint:-unknown}"
795
+ echo "> **Reviewed Scope**: branch+staged+unstaged+untracked"
796
+ }
797
+
778
798
  # Cross-review advisory: nudge the agent to consider an independent second
779
799
  # opinion from a different-vendor model. Advisory only (echo, exit 0); the agent
780
800
  # decides whether to act. Host-aware: in Codex suggest claude-review, otherwise
@@ -800,7 +820,7 @@ emit_cross_review_hint() {
800
820
 
801
821
  emit_external_acceptance_prompt() {
802
822
  local mode="${1:-review}"
803
- local expected_reviewer expected_source command active_plan_local contract_file_local review_file checks_file
823
+ local expected_reviewer expected_source command active_plan_local contract_file_local review_file checks_file rubric fingerprint
804
824
 
805
825
  expected_reviewer="$(workflow_external_acceptance_expected_reviewer)"
806
826
  expected_source="$(workflow_external_acceptance_expected_source "$expected_reviewer")"
@@ -814,6 +834,8 @@ emit_external_acceptance_prompt() {
814
834
  contract_file_local="$(workflow_active_contract || true)"
815
835
  review_file="$(workflow_active_review || true)"
816
836
  checks_file="$(workflow_checks_file)"
837
+ rubric="$(review_rubric_prompt || true)"
838
+ fingerprint="$(workflow_current_review_fingerprint_value || true)"
817
839
 
818
840
  echo "[ExternalAcceptance] Review/release intent detected. Start peer acceptance in parallel with local /check."
819
841
  echo "[ExternalAcceptance] Mode: $mode"
@@ -821,11 +843,14 @@ emit_external_acceptance_prompt() {
821
843
  echo "[ExternalAcceptance] Current contract: ${contract_file_local:-"(none)"}"
822
844
  echo "[ExternalAcceptance] Current review: ${review_file:-tasks/reviews/<slug>.review.md}"
823
845
  echo "[ExternalAcceptance] Current checks: $checks_file"
846
+ echo "[ExternalAcceptance] Current diff fingerprint: ${fingerprint:-unknown}"
824
847
  echo "[ExternalAcceptance] Peer reviewer: $expected_reviewer via $command"
825
848
  echo "[ExternalAcceptance] Diff scope for peer: branch diff against target, staged diff, unstaged diff, and untracked files."
826
849
  cat <<EOF_EXTERNAL_ACCEPTANCE
827
850
  [ExternalAcceptance] Prompt to send with $command:
828
- Review the current sprint for acceptance only. Do not run /check. Do not edit files. Do not write files. Inspect the diff scope, contract, review evidence, and checks evidence, then return only a Markdown block that can be pasted into ${review_file:-tasks/reviews/<slug>.review.md}.
851
+ Review the current sprint for acceptance only. Do not run /check. Do not edit files. Do not write files. Inspect the diff scope, contract, review evidence, checks evidence, and Review Rubric v1, then return only a Markdown block that can be pasted into ${review_file:-tasks/reviews/<slug>.review.md}.
852
+
853
+ ${rubric:-[ReviewRubric] Deep Diff Review Rubric v1 unavailable; use severity order P0/P1/P2/P3 and report no style-only nits.}
829
854
 
830
855
  ## External Acceptance Advice
831
856
  > **External Acceptance**: pass
@@ -833,6 +858,9 @@ Review the current sprint for acceptance only. Do not run /check. Do not edit fi
833
858
  > **External Source**: $expected_source
834
859
  > **External Started**: YYYY-MM-DDTHH:MM:SS+0800
835
860
  > **External Completed**: YYYY-MM-DDTHH:MM:SS+0800
861
+ > **Review Rubric Version**: 1
862
+ > **Reviewed Diff Fingerprint**: ${fingerprint:-unknown}
863
+ > **Reviewed Scope**: branch+staged+unstaged+untracked
836
864
 
837
865
  - P1 blockers: none
838
866
  - P2 advisories:
@@ -1119,6 +1147,27 @@ if [ "$done_intent" -eq 1 ]; then
1119
1147
  exit 2
1120
1148
  fi
1121
1149
 
1150
+ review_freshness="$(workflow_review_freshness_status "$review_file")"
1151
+ IFS=$'\t' read -r review_freshness_state review_fingerprint review_freshness_message <<< "$review_freshness"
1152
+ case "$review_freshness_state" in
1153
+ pass)
1154
+ ;;
1155
+ legacy_missing)
1156
+ # Advisory only: an absent rubric is blocked downstream by the external
1157
+ # acceptance gate, which requires a supported rubric.
1158
+ echo "[ReviewFreshness] WARN: $review_freshness_message"
1159
+ ;;
1160
+ *)
1161
+ echo "[ReviewFreshnessGuard] $review_freshness_message"
1162
+ hook_structured_error \
1163
+ "ReviewFreshnessGuard" \
1164
+ "$review_freshness_message" \
1165
+ "Rerun Waza /check and peer acceptance so $review_file records the current Reviewed Diff Fingerprint." \
1166
+ "quality_gate"
1167
+ exit 2
1168
+ ;;
1169
+ esac
1170
+
1122
1171
  external_status="$(workflow_external_acceptance_status "$review_file")"
1123
1172
  IFS=$'\t' read -r external_state external_reviewer external_source external_message <<< "$external_status"
1124
1173
  if [ "$external_state" != "pass" ] && [ "$external_state" != "manual_override" ]; then
@@ -385,6 +385,17 @@ refresh_handoff
385
385
  minimal_change_refresh_review
386
386
  minimal_change_append_handoff
387
387
 
388
+ review_file="$(workflow_active_review || true)"
389
+ if [[ -n "$review_file" && -f "$review_file" ]]; then
390
+ review_freshness="$(workflow_review_freshness_status "$review_file")"
391
+ IFS=$'\t' read -r review_freshness_state _review_fingerprint review_freshness_message <<< "$review_freshness"
392
+ case "$review_freshness_state" in
393
+ stale|malformed|malformed_schema|unknown|missing)
394
+ echo "[ReviewFreshness] $review_freshness_message" >&2
395
+ ;;
396
+ esac
397
+ fi
398
+
388
399
  if should_run_plan_completeness_gate "$stop_hook_active" "$last_assistant_message"; then
389
400
  signature="$(plan_completeness_signature)"
390
401
  if [[ "$(plan_completeness_last_signature 2>/dev/null || true)" != "$signature" ]]; then