@windyroad/architect 0.20.3 → 0.20.4
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/package.json +6 -2
- package/agents/test/architect-codex-eval-contract.bats +0 -33
- package/agents/test/architect-needs-direction-verdict.bats +0 -64
- package/agents/test/architect-output-formatting.bats +0 -26
- package/agents/test/architect-performance-review.bats +0 -76
- package/agents/test/architect-pre-edit-review-mode.bats +0 -50
- package/agents/test/architect-unratified-dependency-verdict.bats +0 -55
- package/hooks/test/architect-compendium-update-entry.bats +0 -299
- package/hooks/test/architect-detect-once-per-session.bats +0 -106
- package/hooks/test/architect-detect-scope.bats +0 -56
- package/hooks/test/architect-enforce-scope.bats +0 -124
- package/hooks/test/architect-gate.bats +0 -102
- package/hooks/test/architect-mark-reviewed-verdict-grep.bats +0 -113
- package/hooks/test/architect-mark-reviewed.bats +0 -26
- package/hooks/test/architect-marker-only-exempt.bats +0 -183
- package/hooks/test/architect-no-stop-hook.bats +0 -17
- package/hooks/test/architect-oversight-marker-discipline.bats +0 -245
- package/hooks/test/architect-oversight-nudge.bats +0 -75
- package/hooks/test/architect-project-root.bats +0 -81
- package/hooks/test/architect-readme-pairing-check.bats +0 -126
- package/hooks/test/architect-slide-marker.bats +0 -92
- package/hooks/test/codex-agent-completion.bats +0 -64
- package/hooks/test/slide-marker-on-subprocess-return.bats +0 -118
- package/hooks/test/substance-aware-drift.bats +0 -243
- package/scripts/test/check-amends-backreference.bats +0 -68
- package/scripts/test/codex-pack-and-agent.bats +0 -86
- package/scripts/test/detect-unoversighted.bats +0 -139
- package/scripts/test/generate-decisions-compendium.bats +0 -210
- package/scripts/test/is-decision-unconfirmed.bats +0 -143
- package/skills/capture-adr/test/capture-adr.bats +0 -362
- package/skills/create-adr/test/create-adr-adr-044-contract.bats +0 -185
- package/skills/create-adr/test/create-adr-codex-eval-contract.bats +0 -34
- package/skills/create-adr/test/create-adr-decision-boundary.bats +0 -49
- package/skills/create-adr/test/create-adr-id-collision-guard.bats +0 -35
- package/skills/create-adr/test/create-adr-next-id-origin-lookup.bats +0 -45
- package/skills/create-adr/test/create-adr-rename-restage-reminder.bats +0 -38
- package/skills/create-adr/test/create-adr-substance-confirm-pattern.bats +0 -169
- package/skills/review-design/test/review-design-codex-contract.bats +0 -22
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P095 / ADR-038: architect-detect.sh UserPromptSubmit hook must emit
|
|
4
|
-
# the full MANDATORY instruction block only on the first prompt of a
|
|
5
|
-
# session (identified by session_id on stdin). Subsequent prompts in
|
|
6
|
-
# the same session emit only a terse reminder. New sessions or empty
|
|
7
|
-
# session_ids fall back to the full block.
|
|
8
|
-
|
|
9
|
-
setup() {
|
|
10
|
-
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
11
|
-
HOOK="$REPO_ROOT/packages/architect/hooks/architect-detect.sh"
|
|
12
|
-
|
|
13
|
-
# Stub project working dir so the hook's docs/decisions/ detection fires.
|
|
14
|
-
WORKDIR="$(mktemp -d)"
|
|
15
|
-
mkdir -p "$WORKDIR/docs/decisions"
|
|
16
|
-
: > "$WORKDIR/docs/decisions/.keep"
|
|
17
|
-
|
|
18
|
-
SID="arch-detect-test-$$-$RANDOM"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
teardown() {
|
|
22
|
-
rm -f "/tmp/architect-announced-${SID}"
|
|
23
|
-
rm -f "/tmp/architect-announced-${SID}-alt"
|
|
24
|
-
rm -rf "$WORKDIR"
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
# Helper: invoke the hook with a given session_id from stdin, while cd'd
|
|
28
|
-
# into WORKDIR so the `docs/decisions` detection picks up the stub.
|
|
29
|
-
run_hook() {
|
|
30
|
-
local sid="$1"
|
|
31
|
-
(cd "$WORKDIR" && echo "{\"session_id\":\"$sid\"}" | bash "$HOOK")
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@test "architect-detect: first invocation emits the full MANDATORY block" {
|
|
35
|
-
run run_hook "$SID"
|
|
36
|
-
[ "$status" -eq 0 ]
|
|
37
|
-
[ "${#output}" -gt 500 ]
|
|
38
|
-
[[ "$output" == *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
39
|
-
[[ "$output" == *"wr-architect:agent"* ]]
|
|
40
|
-
[[ "$output" == *"SCOPE:"* ]]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@test "architect-detect: first invocation writes the announcement marker" {
|
|
44
|
-
run run_hook "$SID"
|
|
45
|
-
[ "$status" -eq 0 ]
|
|
46
|
-
[ -f "/tmp/architect-announced-${SID}" ]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@test "architect-detect: second invocation emits only a terse reminder" {
|
|
50
|
-
run_hook "$SID" >/dev/null
|
|
51
|
-
run run_hook "$SID"
|
|
52
|
-
[ "$status" -eq 0 ]
|
|
53
|
-
# Terse reminder is < 250 bytes; full block is > 500 bytes.
|
|
54
|
-
[ "${#output}" -lt 250 ]
|
|
55
|
-
# Terse reminder names the gate AND the delegation affordance.
|
|
56
|
-
[[ "$output" == *"architecture"* ]] || [[ "$output" == *"ARCHITECTURE"* ]]
|
|
57
|
-
[[ "$output" == *"wr-architect:agent"* ]]
|
|
58
|
-
# Full SCOPE block is NOT re-emitted.
|
|
59
|
-
[[ "$output" != *"Does NOT apply to"* ]]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@test "architect-detect: terse reminder preserves the MANDATORY signal word" {
|
|
63
|
-
run_hook "$SID" >/dev/null
|
|
64
|
-
run run_hook "$SID"
|
|
65
|
-
[ "$status" -eq 0 ]
|
|
66
|
-
# Per JTBD-lead condition: terse reminder must keep an imperative
|
|
67
|
-
# marker (MANDATORY / REQUIRED / NON-OPTIONAL) so the enforcement
|
|
68
|
-
# signal does not soften after turn 1.
|
|
69
|
-
[[ "$output" == *"MANDATORY"* ]] || [[ "$output" == *"REQUIRED"* ]] || [[ "$output" == *"NON-OPTIONAL"* ]]
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@test "architect-detect: terse reminder names the trigger artifact" {
|
|
73
|
-
run_hook "$SID" >/dev/null
|
|
74
|
-
run run_hook "$SID"
|
|
75
|
-
[ "$status" -eq 0 ]
|
|
76
|
-
# Per JTBD-lead condition: reason for the gate must stay visible
|
|
77
|
-
# on subsequent turns.
|
|
78
|
-
[[ "$output" == *"docs/decisions"* ]]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@test "architect-detect: different session_id re-emits the full block" {
|
|
82
|
-
run_hook "$SID" >/dev/null
|
|
83
|
-
local SID2="${SID}-alt"
|
|
84
|
-
run run_hook "$SID2"
|
|
85
|
-
[ "$status" -eq 0 ]
|
|
86
|
-
[ "${#output}" -gt 500 ]
|
|
87
|
-
[[ "$output" == *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
88
|
-
rm -f "/tmp/architect-announced-${SID2}"
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@test "architect-detect: empty session_id emits the full block and writes no marker" {
|
|
92
|
-
run run_hook ""
|
|
93
|
-
[ "$status" -eq 0 ]
|
|
94
|
-
[ "${#output}" -gt 500 ]
|
|
95
|
-
[[ "$output" == *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
96
|
-
[ ! -f "/tmp/architect-announced-" ]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@test "architect-detect: absent docs/decisions/ emits the no-decisions NOTE (unchanged by this ADR)" {
|
|
100
|
-
local NO_DECISIONS="$(mktemp -d)"
|
|
101
|
-
run bash -c "cd '$NO_DECISIONS' && echo '{\"session_id\":\"$SID\"}' | bash '$HOOK'"
|
|
102
|
-
[ "$status" -eq 0 ]
|
|
103
|
-
[[ "$output" == *"no docs/decisions/ directory"* ]]
|
|
104
|
-
[[ "$output" != *"MANDATORY ARCHITECTURE CHECK"* ]]
|
|
105
|
-
rm -rf "$NO_DECISIONS"
|
|
106
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# Tests for architect-detect.sh (UserPromptSubmit) — verifies the injected
|
|
4
|
-
# scope exclusion text lists governance docs that the PreToolUse gate already
|
|
5
|
-
# exempts (P029). Without this, the LLM wastes time delegating to the
|
|
6
|
-
# architect for files the edit gate would allow anyway.
|
|
7
|
-
|
|
8
|
-
setup() {
|
|
9
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
10
|
-
HOOK="$SCRIPT_DIR/architect-detect.sh"
|
|
11
|
-
ORIG_DIR="$PWD"
|
|
12
|
-
TEST_DIR=$(mktemp -d)
|
|
13
|
-
cd "$TEST_DIR"
|
|
14
|
-
mkdir -p docs/decisions
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
teardown() {
|
|
18
|
-
cd "$ORIG_DIR"
|
|
19
|
-
rm -rf "$TEST_DIR"
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
@test "detect: scope text mentions problem files exemption (P029)" {
|
|
23
|
-
run bash "$HOOK" </dev/null
|
|
24
|
-
[ "$status" -eq 0 ]
|
|
25
|
-
[[ "$output" == *"docs/problems/"* ]] || [[ "$output" == *"problem tickets"* ]]
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@test "detect: scope text mentions BRIEFING.md exemption (P029)" {
|
|
29
|
-
run bash "$HOOK" </dev/null
|
|
30
|
-
[ "$status" -eq 0 ]
|
|
31
|
-
[[ "$output" == *"BRIEFING"* ]]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@test "detect: scope text mentions docs/briefing/ exemption (P100)" {
|
|
35
|
-
run bash "$HOOK" </dev/null
|
|
36
|
-
[ "$status" -eq 0 ]
|
|
37
|
-
[[ "$output" == *"docs/briefing/"* ]]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@test "detect: scope text mentions RISK-POLICY exemption (P029)" {
|
|
41
|
-
run bash "$HOOK" </dev/null
|
|
42
|
-
[ "$status" -eq 0 ]
|
|
43
|
-
[[ "$output" == *"RISK-POLICY"* ]]
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@test "detect: scope text mentions changeset exemption (P029)" {
|
|
47
|
-
run bash "$HOOK" </dev/null
|
|
48
|
-
[ "$status" -eq 0 ]
|
|
49
|
-
[[ "$output" == *"changeset"* ]]
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
@test "detect: scope text mentions memory files exemption (P029)" {
|
|
53
|
-
run bash "$HOOK" </dev/null
|
|
54
|
-
[ "$status" -eq 0 ]
|
|
55
|
-
[[ "$output" == *"memory"* ]] || [[ "$output" == *"MEMORY"* ]]
|
|
56
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# Tests for architect-enforce-edit.sh — verifies peer-plugin policy files are
|
|
4
|
-
# exempt from the architect gate (P009). Each plugin governs its own policy
|
|
5
|
-
# docs via its own enforce hook; the architect should not re-gate them.
|
|
6
|
-
#
|
|
7
|
-
# All tests are functional (P011): they execute the hook with mock JSON
|
|
8
|
-
# input and assert on exit status + BLOCKED output. Source-grep assertions
|
|
9
|
-
# were removed because they over-specified the implementation and gave
|
|
10
|
-
# false confidence (a literal string can appear in source without the
|
|
11
|
-
# matching case branch actually short-circuiting).
|
|
12
|
-
|
|
13
|
-
setup() {
|
|
14
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
15
|
-
HOOK="$SCRIPT_DIR/architect-enforce-edit.sh"
|
|
16
|
-
ORIG_DIR="$PWD"
|
|
17
|
-
TEST_DIR=$(mktemp -d)
|
|
18
|
-
cd "$TEST_DIR"
|
|
19
|
-
# Engage the gate: architect-enforce only runs when docs/decisions/ exists.
|
|
20
|
-
mkdir -p docs/decisions
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
teardown() {
|
|
24
|
-
cd "$ORIG_DIR"
|
|
25
|
-
rm -rf "$TEST_DIR"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
# Helper: run the hook with a mock JSON input for a given file path.
|
|
29
|
-
# Claude Code passes absolute file_path values, so tests use $PWD-prefixed
|
|
30
|
-
# paths to match real shape (after the P004 root check).
|
|
31
|
-
run_hook_with_file() {
|
|
32
|
-
local file_path="$1"
|
|
33
|
-
local json="{\"tool_input\":{\"file_path\":\"${file_path}\"},\"session_id\":\"test-session-$$\"}"
|
|
34
|
-
echo "$json" | bash "$HOOK"
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
assert_path_allowed() {
|
|
38
|
-
local file_path="$1"
|
|
39
|
-
run run_hook_with_file "$file_path"
|
|
40
|
-
[ "$status" -eq 0 ]
|
|
41
|
-
[[ "$output" != *"BLOCKED"* ]]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
@test "architect: does NOT exempt legacy docs/JOBS_TO_BE_DONE.md (ADR-008 Option 3, P019)" {
|
|
45
|
-
# Legacy single-file path is no longer a recognised peer-plugin policy
|
|
46
|
-
# artefact — ADR-008 Option 3 removes it from runtime consideration.
|
|
47
|
-
# The architect gate treats it as ordinary content and applies its
|
|
48
|
-
# normal review rules.
|
|
49
|
-
run run_hook_with_file "$PWD/docs/JOBS_TO_BE_DONE.md"
|
|
50
|
-
[ "$status" -eq 0 ]
|
|
51
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@test "architect: exempts VCS-internal .git/ plumbing files (P458)" {
|
|
55
|
-
# A commit-message scratch file under .git/ is not a project artefact. Without
|
|
56
|
-
# the exclusion a .txt under the repo root is gated (no extension exemption).
|
|
57
|
-
assert_path_allowed "$PWD/.git/cruise-brake-commit-msg.txt"
|
|
58
|
-
assert_path_allowed "$PWD/.git/COMMIT_EDITMSG"
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@test "architect: exempts JTBD directory file (P009)" {
|
|
62
|
-
assert_path_allowed "$PWD/docs/jtbd/solo-developer/persona.md"
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@test "architect: exempts PRODUCT_DISCOVERY.md (P009)" {
|
|
66
|
-
assert_path_allowed "$PWD/docs/PRODUCT_DISCOVERY.md"
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@test "architect: exempts voice-tone policy file (P009)" {
|
|
70
|
-
assert_path_allowed "$PWD/docs/VOICE-AND-TONE.md"
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@test "architect: exempts style-guide policy file (P009)" {
|
|
74
|
-
assert_path_allowed "$PWD/docs/STYLE-GUIDE.md"
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# --- Story maps + stories exemptions (P170 Phase 2 Slice 2.5 / ADR-060 amendment 2026-05-12) ---
|
|
78
|
-
# ADR-060 § Phase 2 encoding amendment lines 481-496 mandates exempting
|
|
79
|
-
# `docs/story-maps/**/*.html` and `docs/stories/**/*.md` from the 5
|
|
80
|
-
# enforce-edit gates. Mirrors the docs/problems and docs/jtbd exemptions —
|
|
81
|
-
# these are governance-managed surfaces with their own capture/manage skills.
|
|
82
|
-
|
|
83
|
-
@test "architect: exempts docs/story-maps/ HTML in per-state subdir (P170 / ADR-060)" {
|
|
84
|
-
assert_path_allowed "$PWD/docs/story-maps/draft/STORY-MAP-001-rfc-framework.html"
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@test "architect: exempts docs/story-maps/ HTML in completed subdir (P170 / ADR-060)" {
|
|
88
|
-
assert_path_allowed "$PWD/docs/story-maps/completed/STORY-MAP-002-foo.html"
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@test "architect: exempts docs/story-maps/README.md (P170 / ADR-060)" {
|
|
92
|
-
assert_path_allowed "$PWD/docs/story-maps/README.md"
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
@test "architect: exempts docs/stories/ markdown in per-state subdir (P170 / ADR-060)" {
|
|
96
|
-
assert_path_allowed "$PWD/docs/stories/draft/STORY-001-some-slice.md"
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@test "architect: exempts docs/stories/ markdown in done subdir (P170 / ADR-060)" {
|
|
100
|
-
assert_path_allowed "$PWD/docs/stories/done/STORY-002-other.md"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@test "architect: exempts docs/stories/README.md (P170 / ADR-060)" {
|
|
104
|
-
assert_path_allowed "$PWD/docs/stories/README.md"
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
# --- Retros exemption (P203) ---
|
|
108
|
-
# `docs/retros/*` is the ask-hygiene + run-retro narrative trail written
|
|
109
|
-
# routinely by `/wr-retrospective:run-retro` Step 2d + Step 5. Routine
|
|
110
|
-
# appends should not fire the architect gate — they are not load-bearing
|
|
111
|
-
# architecture content. Mirrors the docs/problems and docs/jtbd peer-
|
|
112
|
-
# plugin-policy exemptions.
|
|
113
|
-
|
|
114
|
-
@test "architect: exempts docs/retros/ ask-hygiene file (P203)" {
|
|
115
|
-
assert_path_allowed "$PWD/docs/retros/2026-06-06-some-iter-ask-hygiene.md"
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
@test "architect: exempts docs/retros/ analyze-context file (P203)" {
|
|
119
|
-
assert_path_allowed "$PWD/docs/retros/2026-06-06-context-analysis.md"
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@test "architect: exempts docs/retros/ retro narrative file (P203)" {
|
|
123
|
-
assert_path_allowed "$PWD/docs/retros/2026-06-06-session-wrap.md"
|
|
124
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# Tests for architect-gate.sh (TTL, drift, marker)
|
|
4
|
-
|
|
5
|
-
setup() {
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
7
|
-
source "$SCRIPT_DIR/lib/architect-gate.sh"
|
|
8
|
-
TEST_SESSION="test-$$-$BATS_TEST_NUMBER"
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
teardown() {
|
|
12
|
-
rm -f "/tmp/architect-reviewed-${TEST_SESSION}"
|
|
13
|
-
rm -f "/tmp/architect-reviewed-${TEST_SESSION}.hash"
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
@test "gate denies when no marker exists" {
|
|
17
|
-
run check_architect_gate "$TEST_SESSION"
|
|
18
|
-
[ "$status" -ne 0 ]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
@test "gate allows when marker exists and is fresh" {
|
|
22
|
-
touch "/tmp/architect-reviewed-${TEST_SESSION}"
|
|
23
|
-
run check_architect_gate "$TEST_SESSION"
|
|
24
|
-
[ "$status" -eq 0 ]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
@test "gate denies when marker is expired" {
|
|
28
|
-
touch "/tmp/architect-reviewed-${TEST_SESSION}"
|
|
29
|
-
# Set TTL to 0 to force expiry
|
|
30
|
-
ARCHITECT_TTL=0 run check_architect_gate "$TEST_SESSION"
|
|
31
|
-
[ "$status" -ne 0 ]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# P215 / RFC-021 — ARCHITECT_GATE_REASON behaviour. The gate must expose a
|
|
35
|
-
# differentiated reason per failure mode (no marker / TTL expired / drift
|
|
36
|
-
# detected) so the downstream deny message carries a clear recovery directive.
|
|
37
|
-
# Mirrors sibling REVIEW_GATE_REASON pattern in jtbd/voice-tone/style-guide
|
|
38
|
-
# review-gate.sh.
|
|
39
|
-
|
|
40
|
-
@test "ARCHITECT_GATE_REASON names re-delegate directive when no marker" {
|
|
41
|
-
ARCHITECT_GATE_REASON=""
|
|
42
|
-
check_architect_gate "$TEST_SESSION" || true
|
|
43
|
-
[[ "$ARCHITECT_GATE_REASON" == *"wr-architect:agent"* ]]
|
|
44
|
-
[[ "$ARCHITECT_GATE_REASON" == *"Agent tool"* ]]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
# P400 — the no-marker reason must also document the SendMessage-resume
|
|
48
|
-
# recovery path: a verdict upgrade after ISSUES FOUND is a FRESH Agent spawn
|
|
49
|
-
# (a SendMessage resume does NOT fire the marker hook), or the manual marker
|
|
50
|
-
# assertion. Without this directive the deny message is a dead-end for the
|
|
51
|
-
# most common recovery flow.
|
|
52
|
-
@test "ARCHITECT_GATE_REASON documents SendMessage-resume recovery when no marker" {
|
|
53
|
-
ARCHITECT_GATE_REASON=""
|
|
54
|
-
check_architect_gate "$TEST_SESSION" || true
|
|
55
|
-
[[ "$ARCHITECT_GATE_REASON" == *"SendMessage"* ]]
|
|
56
|
-
[[ "$ARCHITECT_GATE_REASON" == *"FRESH Agent spawn"* ]]
|
|
57
|
-
[[ "$ARCHITECT_GATE_REASON" == *"touch /tmp/architect-reviewed-"* ]]
|
|
58
|
-
[[ "$ARCHITECT_GATE_REASON" == *"rm -f /tmp/architect-reviewed-"* ]]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@test "ARCHITECT_GATE_REASON names re-delegate directive when TTL expired" {
|
|
62
|
-
touch "/tmp/architect-reviewed-${TEST_SESSION}"
|
|
63
|
-
ARCHITECT_GATE_REASON=""
|
|
64
|
-
ARCHITECT_TTL=0 check_architect_gate "$TEST_SESSION" || true
|
|
65
|
-
[[ "$ARCHITECT_GATE_REASON" == *"expired"* ]]
|
|
66
|
-
[[ "$ARCHITECT_GATE_REASON" == *"wr-architect:agent"* ]]
|
|
67
|
-
[[ "$ARCHITECT_GATE_REASON" == *"refresh the marker"* ]]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@test "ARCHITECT_GATE_REASON names drift directive when stored hash differs" {
|
|
71
|
-
# Set up project root with a docs/decisions directory and a stored hash that
|
|
72
|
-
# differs from the current substance hash to force the drift branch.
|
|
73
|
-
TEST_PROJECT_DIR=$(mktemp -d)
|
|
74
|
-
mkdir -p "$TEST_PROJECT_DIR/docs/decisions"
|
|
75
|
-
echo "# ADR-001 current content" > "$TEST_PROJECT_DIR/docs/decisions/001-x.md"
|
|
76
|
-
touch "/tmp/architect-reviewed-${TEST_SESSION}"
|
|
77
|
-
echo "stale-hash-that-will-not-match" > "/tmp/architect-reviewed-${TEST_SESSION}.hash"
|
|
78
|
-
ARCHITECT_GATE_REASON=""
|
|
79
|
-
CLAUDE_PROJECT_DIR="$TEST_PROJECT_DIR" check_architect_gate "$TEST_SESSION" || true
|
|
80
|
-
rm -rf "$TEST_PROJECT_DIR"
|
|
81
|
-
[[ "$ARCHITECT_GATE_REASON" == *"drift"* ]] || [[ "$ARCHITECT_GATE_REASON" == *"changed"* ]]
|
|
82
|
-
[[ "$ARCHITECT_GATE_REASON" == *"wr-architect:agent"* ]]
|
|
83
|
-
[[ "$ARCHITECT_GATE_REASON" == *"refresh the marker"* ]]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@test "architect-enforce-edit deny output includes ARCHITECT_GATE_REASON directive" {
|
|
87
|
-
HOOK="$SCRIPT_DIR/architect-enforce-edit.sh"
|
|
88
|
-
ORIG_DIR="$PWD"
|
|
89
|
-
TEST_DIR=$(mktemp -d)
|
|
90
|
-
cd "$TEST_DIR"
|
|
91
|
-
mkdir -p docs/decisions
|
|
92
|
-
echo "# adr stub" > docs/decisions/001-x.md
|
|
93
|
-
json="{\"tool_input\":{\"file_path\":\"$PWD/src/x.ts\"},\"session_id\":\"deny-test-$$\"}"
|
|
94
|
-
run bash -c "echo '$json' | bash '$HOOK'"
|
|
95
|
-
cd "$ORIG_DIR"
|
|
96
|
-
rm -rf "$TEST_DIR"
|
|
97
|
-
[[ "$output" == *"BLOCKED"* ]]
|
|
98
|
-
[[ "$output" == *"wr-architect:agent"* ]]
|
|
99
|
-
# No marker exists for this fresh session — deny reason must explicitly
|
|
100
|
-
# name the re-delegate directive (not vague "review required").
|
|
101
|
-
[[ "$output" == *"No architect review marker"* ]] || [[ "$output" == *"marker"* ]]
|
|
102
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# Behavioural tests for architect-mark-reviewed.sh verdict parsing (P181).
|
|
4
|
-
# Drives the hook with realistic agent-output payloads and asserts marker
|
|
5
|
-
# creation matches the heading-shape contract in
|
|
6
|
-
# packages/architect/agents/agent.md "How to Report".
|
|
7
|
-
#
|
|
8
|
-
# P181 root cause: literal-substring grep `grep -q "ISSUES FOUND"` matches
|
|
9
|
-
# anywhere in the response — including prose narrative that mentions the
|
|
10
|
-
# verdict string without it being the canonical heading. Anchored heading
|
|
11
|
-
# match fixes the false-positive FAIL → silent marker-drop → edit block.
|
|
12
|
-
|
|
13
|
-
setup() {
|
|
14
|
-
HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
15
|
-
HOOK="$HOOKS_DIR/architect-mark-reviewed.sh"
|
|
16
|
-
TEST_SESSION="bats-arch-verdict-$$-${BATS_TEST_NUMBER}"
|
|
17
|
-
REVIEW_MARKER="/tmp/architect-reviewed-${TEST_SESSION}"
|
|
18
|
-
HASH_MARKER="/tmp/architect-reviewed-${TEST_SESSION}.hash"
|
|
19
|
-
PLAN_MARKER="/tmp/architect-plan-reviewed-${TEST_SESSION}"
|
|
20
|
-
rm -f "$REVIEW_MARKER" "$HASH_MARKER" "$PLAN_MARKER"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
teardown() {
|
|
24
|
-
rm -f "$REVIEW_MARKER" "$HASH_MARKER" "$PLAN_MARKER"
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
# Build a PostToolUse:Agent input JSON with the given agent-output text.
|
|
28
|
-
# Uses python3 (already a hard dep of gate-helpers.sh) for safe escaping.
|
|
29
|
-
_make_input() {
|
|
30
|
-
local text="$1"
|
|
31
|
-
python3 -c "
|
|
32
|
-
import json, sys
|
|
33
|
-
text = sys.argv[1]
|
|
34
|
-
print(json.dumps({
|
|
35
|
-
'session_id': '$TEST_SESSION',
|
|
36
|
-
'tool_name': 'Agent',
|
|
37
|
-
'tool_input': {'subagent_type': 'wr-architect:agent'},
|
|
38
|
-
'tool_response': {'content': [{'type': 'text', 'text': text}]}
|
|
39
|
-
}))
|
|
40
|
-
" "$text"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# ---------------------------------------------------------------------------
|
|
44
|
-
# Sanity: canonical headings classify correctly
|
|
45
|
-
# ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
@test "verdict-grep: marker drops on canonical PASS heading" {
|
|
48
|
-
INPUT=$(_make_input "**Architecture Review: PASS**
|
|
49
|
-
|
|
50
|
-
No conflicts with existing decisions.")
|
|
51
|
-
echo "$INPUT" | "$HOOK"
|
|
52
|
-
[ -f "$REVIEW_MARKER" ]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@test "verdict-grep: marker NOT created on canonical ISSUES FOUND heading" {
|
|
56
|
-
INPUT=$(_make_input "**Architecture Review: ISSUES FOUND**
|
|
57
|
-
|
|
58
|
-
1. [Decision Conflict] — ADR-009 violation.")
|
|
59
|
-
echo "$INPUT" | "$HOOK"
|
|
60
|
-
[ ! -f "$REVIEW_MARKER" ]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
@test "verdict-grep: marker drops on PASS heading with blockquote prefix" {
|
|
64
|
-
INPUT=$(_make_input "> **Architecture Review: PASS**
|
|
65
|
-
> No conflicts.")
|
|
66
|
-
echo "$INPUT" | "$HOOK"
|
|
67
|
-
[ -f "$REVIEW_MARKER" ]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@test "verdict-grep: marker NOT created on ISSUES FOUND heading with blockquote prefix" {
|
|
71
|
-
INPUT=$(_make_input "> **Architecture Review: ISSUES FOUND**
|
|
72
|
-
> 1. [Conflict] ...")
|
|
73
|
-
echo "$INPUT" | "$HOOK"
|
|
74
|
-
[ ! -f "$REVIEW_MARKER" ]
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# ---------------------------------------------------------------------------
|
|
78
|
-
# P181 bug-fix cases: substring-anywhere false-positive FAIL
|
|
79
|
-
# ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
@test "verdict-grep: no marker without a canonical heading" {
|
|
82
|
-
INPUT=$(_make_input "I reviewed the change. The previous review surfaced ISSUES FOUND that have since been addressed; the current proposed change is fine.")
|
|
83
|
-
echo "$INPUT" | "$HOOK"
|
|
84
|
-
[ ! -f "$REVIEW_MARKER" ]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@test "verdict-grep: NEEDS DIRECTION does not unlock edits" {
|
|
88
|
-
INPUT=$(_make_input "**Architecture Review: NEEDS DIRECTION**
|
|
89
|
-
|
|
90
|
-
A decision must be recorded. This differs from an ISSUES FOUND verdict because the option is not pinned.
|
|
91
|
-
|
|
92
|
-
- Option A — ...
|
|
93
|
-
- Option B — ...")
|
|
94
|
-
echo "$INPUT" | "$HOOK"
|
|
95
|
-
[ ! -f "$REVIEW_MARKER" ]
|
|
96
|
-
[ ! -f "$PLAN_MARKER" ]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@test "verdict-grep: marker drops when PASS heading present and body also says 'ISSUES FOUND' inline" {
|
|
100
|
-
# PASS check runs first and must win even with substring noise downstream.
|
|
101
|
-
# This works in both old and new code; sanity-anchors the precedence rule.
|
|
102
|
-
INPUT=$(_make_input "**Architecture Review: PASS**
|
|
103
|
-
|
|
104
|
-
No conflicts. Note: earlier sessions reported ISSUES FOUND on adjacent files but those are out of scope here.")
|
|
105
|
-
echo "$INPUT" | "$HOOK"
|
|
106
|
-
[ -f "$REVIEW_MARKER" ]
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
@test "verdict-grep: prose-only approval does not unlock edits" {
|
|
110
|
-
INPUT=$(_make_input "Review complete — no issues found in the diff.")
|
|
111
|
-
echo "$INPUT" | "$HOOK"
|
|
112
|
-
[ ! -f "$REVIEW_MARKER" ]
|
|
113
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# Tests for architect verdict parsing from output text
|
|
4
|
-
|
|
5
|
-
@test "grep matches 'Architecture Review: PASS'" {
|
|
6
|
-
output="Some preamble text\n\n**Architecture Review: PASS**\n\nNo conflicts."
|
|
7
|
-
echo -e "$output" | grep -q "Architecture Review: PASS"
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
@test "grep matches 'ISSUES FOUND'" {
|
|
11
|
-
output="**Architecture Review: ISSUES FOUND**\n\n1. [Decision Conflict]"
|
|
12
|
-
echo -e "$output" | grep -q "ISSUES FOUND"
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
@test "grep does NOT match unrelated text" {
|
|
16
|
-
output="The review is complete. Everything looks good."
|
|
17
|
-
! echo -e "$output" | grep -q "Architecture Review: PASS"
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
@test "subagent pattern matches wr-architect:agent" {
|
|
21
|
-
SUBAGENT="wr-architect:agent"
|
|
22
|
-
case "$SUBAGENT" in
|
|
23
|
-
*architect*) true ;;
|
|
24
|
-
*) false ;;
|
|
25
|
-
esac
|
|
26
|
-
}
|