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 +4 -4
- package/README.fr.md +4 -4
- package/README.ja.md +4 -4
- package/README.md +10 -12
- package/README.zh-CN.md +10 -9
- package/assets/hooks/AGENTS.md +2 -0
- package/assets/hooks/CLAUDE.md +2 -0
- package/assets/hooks/lib/minimal-change.sh +32 -7
- package/assets/hooks/lib/workflow-state.sh +205 -0
- package/assets/hooks/projection.json +11 -0
- package/assets/hooks/prompt-guard.sh +51 -2
- package/assets/hooks/stop-orchestrator.sh +11 -0
- package/assets/reference-configs/hook-operations.md +18 -18
- package/assets/skill-version.json +6 -2
- package/assets/templates/helpers/ensure-task-workflow.sh +3 -0
- package/assets/templates/helpers/plan-to-todo.sh +3 -0
- package/assets/templates/review.template.md +3 -0
- package/package.json +3 -1
- package/scripts/check-agent-tooling.sh +6 -4
- package/scripts/check-ci.sh +3 -0
- package/scripts/check-npm-release.sh +1 -0
- package/scripts/check-tarball-install-smoke.sh +86 -0
- package/scripts/ensure-task-workflow.sh +3 -0
- package/scripts/lib/project-init-lib.sh +10 -3
- package/scripts/plan-to-todo.sh +3 -0
- package/scripts/repo-harness.sh +20 -13
- package/scripts/sync-hook-sources.ts +294 -0
- package/src/cli/hook/diff-fingerprint.ts +530 -0
- package/src/cli/hook/review-rubric.ts +77 -0
- package/src/cli/hook-entry.ts +16 -0
- package/src/cli/index.ts +39 -0
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.
|
|
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.
|
|
423
|
-
- Generated workflow stamp: `repo-harness@0.
|
|
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
|
|
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.
|
|
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.
|
|
428
|
-
- Generated workflow stamp : `repo-harness@0.
|
|
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
|
|
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.
|
|
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.
|
|
402
|
-
- Generated workflow stamp:`repo-harness@0.
|
|
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
|
|
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.
|
|
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
|
-
-
|
|
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 ->
|
|
448
|
-
-
|
|
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.
|
|
539
|
-
- Generated workflow stamp: `repo-harness@0.
|
|
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
|
|
708
|
+
## Generated vs Self-Hosted Hook Projection
|
|
709
709
|
|
|
710
|
-
- Downstream hook behavior is defined by
|
|
711
|
-
|
|
712
|
-
-
|
|
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.
|
|
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
|
-
-
|
|
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 ->
|
|
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.
|
|
448
|
-
- Generated workflow stamp:`repo-harness@0.
|
|
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
|
|
622
|
+
## Generated vs Self-Hosted Hook Projection
|
|
622
623
|
|
|
623
|
-
- 下游 hook 行为由 `assets/hooks/` 和 `assets/reference-configs/`
|
|
624
|
-
-
|
|
625
|
-
- 每个 hook
|
|
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
|
|
package/assets/hooks/AGENTS.md
CHANGED
|
@@ -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
|
package/assets/hooks/CLAUDE.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
|
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
|
|
|
@@ -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,
|
|
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
|