@windyroad/architect 0.20.3 → 0.20.4-preview.1058

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/package.json +6 -2
  4. package/agents/test/architect-codex-eval-contract.bats +0 -33
  5. package/agents/test/architect-needs-direction-verdict.bats +0 -64
  6. package/agents/test/architect-output-formatting.bats +0 -26
  7. package/agents/test/architect-performance-review.bats +0 -76
  8. package/agents/test/architect-pre-edit-review-mode.bats +0 -50
  9. package/agents/test/architect-unratified-dependency-verdict.bats +0 -55
  10. package/hooks/test/architect-compendium-update-entry.bats +0 -299
  11. package/hooks/test/architect-detect-once-per-session.bats +0 -106
  12. package/hooks/test/architect-detect-scope.bats +0 -56
  13. package/hooks/test/architect-enforce-scope.bats +0 -124
  14. package/hooks/test/architect-gate.bats +0 -102
  15. package/hooks/test/architect-mark-reviewed-verdict-grep.bats +0 -113
  16. package/hooks/test/architect-mark-reviewed.bats +0 -26
  17. package/hooks/test/architect-marker-only-exempt.bats +0 -183
  18. package/hooks/test/architect-no-stop-hook.bats +0 -17
  19. package/hooks/test/architect-oversight-marker-discipline.bats +0 -245
  20. package/hooks/test/architect-oversight-nudge.bats +0 -75
  21. package/hooks/test/architect-project-root.bats +0 -81
  22. package/hooks/test/architect-readme-pairing-check.bats +0 -126
  23. package/hooks/test/architect-slide-marker.bats +0 -92
  24. package/hooks/test/codex-agent-completion.bats +0 -64
  25. package/hooks/test/slide-marker-on-subprocess-return.bats +0 -118
  26. package/hooks/test/substance-aware-drift.bats +0 -243
  27. package/scripts/test/check-amends-backreference.bats +0 -68
  28. package/scripts/test/codex-pack-and-agent.bats +0 -86
  29. package/scripts/test/detect-unoversighted.bats +0 -139
  30. package/scripts/test/generate-decisions-compendium.bats +0 -210
  31. package/scripts/test/is-decision-unconfirmed.bats +0 -143
  32. package/skills/capture-adr/test/capture-adr.bats +0 -362
  33. package/skills/create-adr/test/create-adr-adr-044-contract.bats +0 -185
  34. package/skills/create-adr/test/create-adr-codex-eval-contract.bats +0 -34
  35. package/skills/create-adr/test/create-adr-decision-boundary.bats +0 -49
  36. package/skills/create-adr/test/create-adr-id-collision-guard.bats +0 -35
  37. package/skills/create-adr/test/create-adr-next-id-origin-lookup.bats +0 -45
  38. package/skills/create-adr/test/create-adr-rename-restage-reminder.bats +0 -38
  39. package/skills/create-adr/test/create-adr-substance-confirm-pattern.bats +0 -169
  40. package/skills/review-design/test/review-design-codex-contract.bats +0 -22
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Behavioural tests for architect-readme-pairing-check.sh (RFC-014 Story B,
4
- # ADR-078 Phase 1 / Option 9). Exercises the hook against a real staged git
5
- # index; asserts on its PreToolUse allow/deny decision (exit code + deny JSON).
6
- # Behavioural — no grep on hook source (feedback_behavioural_tests).
7
-
8
- setup() {
9
- HOOK="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/architect-readme-pairing-check.sh"
10
- REPO="$(mktemp -d)"
11
- cd "$REPO"
12
- git init -q
13
- git config user.email t@e.x
14
- git config user.name t
15
- mkdir -p docs/decisions
16
- echo "# compendium" > docs/decisions/README.md
17
- echo "# adr 049" > docs/decisions/049-x.proposed.md
18
- git add -A && git commit -q -m init
19
- }
20
-
21
- teardown() {
22
- cd /
23
- rm -rf "$REPO"
24
- }
25
-
26
- # Run the hook with a synthetic `git commit` Bash PreToolUse payload.
27
- run_commit_hook() {
28
- local cmd="${1:-git commit -m wip}"
29
- echo "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"$cmd\"}}" | bash "$HOOK"
30
- }
31
-
32
- @test "denies commit when an ADR body is staged without README (criterion 1)" {
33
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
34
- git add docs/decisions/049-x.proposed.md
35
- run run_commit_hook
36
- [ "$status" -eq 2 ]
37
- [[ "$output" == *"deny"* ]]
38
- [[ "$output" == *"049-x.proposed.md"* ]]
39
- }
40
-
41
- @test "permits commit when ADR body AND README are both staged (criterion 2)" {
42
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
43
- echo "# compendium refreshed" > docs/decisions/README.md
44
- git add docs/decisions/049-x.proposed.md docs/decisions/README.md
45
- run run_commit_hook
46
- [ "$status" -eq 0 ]
47
- }
48
-
49
- @test "permits commit when only README is staged (compendium-only edit) (criterion 3)" {
50
- echo "# compendium refreshed" > docs/decisions/README.md
51
- git add docs/decisions/README.md
52
- run run_commit_hook
53
- [ "$status" -eq 0 ]
54
- }
55
-
56
- @test "permits commit when no ADR-touching change is staged (criterion 4)" {
57
- echo "x" > unrelated.txt
58
- git add unrelated.txt
59
- run run_commit_hook
60
- [ "$status" -eq 0 ]
61
- }
62
-
63
- @test "deny message names the unpaired ADR file + recovery directive (criterion 5)" {
64
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
65
- git add docs/decisions/049-x.proposed.md
66
- run run_commit_hook
67
- [ "$status" -eq 2 ]
68
- [[ "$output" == *"049-x.proposed.md"* ]]
69
- [[ "$output" == *"wr-architect-generate-decisions-compendium"* ]]
70
- }
71
-
72
- @test "allows non-commit Bash commands silently" {
73
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
74
- git add docs/decisions/049-x.proposed.md
75
- run run_commit_hook "git status"
76
- [ "$status" -eq 0 ]
77
- }
78
-
79
- @test "RISK_BYPASS token permits an intentional split" {
80
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
81
- git add docs/decisions/049-x.proposed.md
82
- run run_commit_hook "git commit -m 'wip RISK_BYPASS: architect-compendium-deferred'"
83
- [ "$status" -eq 0 ]
84
- }
85
-
86
- # --- P366 leading-token detection regression guards ---
87
- # These exercise the shared command_invokes_git_commit helper that the hook
88
- # now sources (replacing inline awk). Permit-path-only coverage is what let
89
- # the original BSD-awk `\b` bug hide; these are the deny-path / mention-path
90
- # guards the ticket asks for.
91
-
92
- @test "denies 'cd <repo> && git commit' with an unpaired ADR (P366 cd-prefix)" {
93
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
94
- git add docs/decisions/049-x.proposed.md
95
- run run_commit_hook "cd $REPO && git commit -m wip"
96
- [ "$status" -eq 2 ]
97
- [[ "$output" == *"deny"* ]]
98
- }
99
-
100
- @test "denies 'VAR=1 git commit' with an unpaired ADR (P366 env-prefix)" {
101
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
102
- git add docs/decisions/049-x.proposed.md
103
- run run_commit_hook "GIT_AUTHOR_NAME=x git commit -m wip"
104
- [ "$status" -eq 2 ]
105
- [[ "$output" == *"deny"* ]]
106
- }
107
-
108
- @test "permits a command that merely MENTIONS 'git commit' as a substring (P366 mention-path)" {
109
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
110
- git add docs/decisions/049-x.proposed.md
111
- run run_commit_hook "grep -r 'git commit' docs/"
112
- [ "$status" -eq 0 ]
113
- }
114
-
115
- @test "permits 'git commit-tree' plumbing (P366 token-boundary)" {
116
- echo "# adr 049 edited" > docs/decisions/049-x.proposed.md
117
- git add docs/decisions/049-x.proposed.md
118
- run run_commit_hook "git commit-tree HEAD"
119
- [ "$status" -eq 0 ]
120
- }
121
-
122
- @test "registered in hooks.json as PreToolUse Bash (criterion 6)" {
123
- HOOKS_JSON="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/hooks.json"
124
- run jq -e '.hooks.PreToolUse[] | select(.matcher=="Bash") | .hooks[] | select(.command | test("architect-readme-pairing-check"))' "$HOOKS_JSON"
125
- [ "$status" -eq 0 ]
126
- }
@@ -1,92 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Hook-level integration tests for architect-slide-marker.sh (P111).
4
- # Verifies that the PostToolUse:Agent|Bash hook correctly wires session_id
5
- # extraction + slide_marker_on_subprocess_return for the architect markers.
6
-
7
- setup() {
8
- HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
9
- HOOK="$HOOKS_DIR/architect-slide-marker.sh"
10
- TEST_SESSION="bats-arch-slide-$$-${BATS_TEST_NUMBER}"
11
- REVIEW_MARKER="/tmp/architect-reviewed-${TEST_SESSION}"
12
- PLAN_MARKER="/tmp/architect-plan-reviewed-${TEST_SESSION}"
13
- rm -f "$REVIEW_MARKER" "$PLAN_MARKER"
14
- }
15
-
16
- teardown() {
17
- rm -f "$REVIEW_MARKER" "$PLAN_MARKER"
18
- }
19
-
20
- _backdate() {
21
- local file="$1" seconds="$2"
22
- local stamp
23
- stamp=$(date -v-${seconds}S +%Y%m%d%H%M.%S 2>/dev/null \
24
- || date -d "${seconds} seconds ago" +%Y%m%d%H%M.%S 2>/dev/null)
25
- touch -t "$stamp" "$file"
26
- }
27
-
28
- @test "hook: slides architect-reviewed marker on subprocess return" {
29
- touch "$REVIEW_MARKER"
30
- _backdate "$REVIEW_MARKER" 60
31
- BEFORE=$(stat -c%Y "$REVIEW_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$REVIEW_MARKER")
32
- echo '{"session_id":"'"$TEST_SESSION"'","tool_name":"Agent","tool_response":{"content":[]}}' | "$HOOK"
33
- AFTER=$(stat -c%Y "$REVIEW_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$REVIEW_MARKER")
34
- [ "$AFTER" -gt "$BEFORE" ]
35
- }
36
-
37
- @test "hook: slides architect-plan-reviewed marker too" {
38
- touch "$PLAN_MARKER"
39
- _backdate "$PLAN_MARKER" 60
40
- BEFORE=$(stat -c%Y "$PLAN_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$PLAN_MARKER")
41
- echo '{"session_id":"'"$TEST_SESSION"'","tool_name":"Agent","tool_response":{"content":[]}}' | "$HOOK"
42
- AFTER=$(stat -c%Y "$PLAN_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$PLAN_MARKER")
43
- [ "$AFTER" -gt "$BEFORE" ]
44
- }
45
-
46
- @test "hook: skips slide when tool_response.is_error=true" {
47
- touch "$REVIEW_MARKER"
48
- _backdate "$REVIEW_MARKER" 60
49
- BEFORE=$(stat -c%Y "$REVIEW_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$REVIEW_MARKER")
50
- echo '{"session_id":"'"$TEST_SESSION"'","tool_name":"Bash","tool_response":{"is_error":true,"content":[]}}' | "$HOOK"
51
- AFTER=$(stat -c%Y "$REVIEW_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$REVIEW_MARKER")
52
- [ "$BEFORE" = "$AFTER" ]
53
- }
54
-
55
- @test "hook: no-op when no marker exists (never creates)" {
56
- [ ! -f "$REVIEW_MARKER" ]
57
- echo '{"session_id":"'"$TEST_SESSION"'","tool_name":"Agent","tool_response":{"content":[]}}' | "$HOOK"
58
- [ ! -f "$REVIEW_MARKER" ]
59
- }
60
-
61
- @test "hook: exits 0 when session_id is missing" {
62
- run bash -c 'echo "{}" | '"$HOOK"
63
- [ "$status" -eq 0 ]
64
- }
65
-
66
- @test "hook: P111 reproduction — long subprocess does not cause parent marker expiry on return" {
67
- touch "$REVIEW_MARKER"
68
- # Marker is 50 minutes old, well within default 60-min TTL but close.
69
- # Without the slide on subprocess return, a 15-min subprocess would push
70
- # the next PreToolUse check past TTL. With the slide, the next check sees
71
- # a fresh marker.
72
- _backdate "$REVIEW_MARKER" 3000
73
- echo '{"session_id":"'"$TEST_SESSION"'","tool_name":"Bash","tool_input":{"command":"claude -p ..."},"tool_response":{"content":[{"type":"text","text":"OK"}]}}' | "$HOOK"
74
- NOW=$(date +%s)
75
- AFTER=$(stat -c%Y "$REVIEW_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$REVIEW_MARKER")
76
- AGE=$((NOW - AFTER))
77
- [ "$AGE" -lt 5 ]
78
- }
79
-
80
- @test "hook: P213 — slides on Skill tool_name (Agent|Bash|Skill matcher expansion)" {
81
- # Confirms hooks.json matcher expansion (Agent|Bash → Agent|Bash|Skill)
82
- # composes correctly with the existing hook script. The hook script does
83
- # not filter on tool_name; the matcher in hooks.json gates which tool
84
- # completions fire the script. This test asserts the script handles the
85
- # Skill-shaped tool_response identically to Agent|Bash.
86
- touch "$REVIEW_MARKER"
87
- _backdate "$REVIEW_MARKER" 60
88
- BEFORE=$(stat -c%Y "$REVIEW_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$REVIEW_MARKER")
89
- echo '{"session_id":"'"$TEST_SESSION"'","tool_name":"Skill","tool_input":{"skill":"wr-risk-scorer:assess-release"},"tool_response":{"content":[{"type":"text","text":"OK"}]}}' | "$HOOK"
90
- AFTER=$(stat -c%Y "$REVIEW_MARKER" 2>/dev/null || /usr/bin/stat -f%m "$REVIEW_MARKER")
91
- [ "$AFTER" -gt "$BEFORE" ]
92
- }
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- setup() {
4
- HOOK_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
5
- TMP="$(mktemp -d)"
6
- export TMPDIR="$TMP/runtime"
7
- export CLAUDE_PROJECT_DIR="$TMP/project"
8
- mkdir -p "$TMPDIR" "$CLAUDE_PROJECT_DIR/docs/decisions"
9
- SESSION="architect-codex-$$"
10
- TARGET="agent-target"
11
- MARKER="/tmp/architect-reviewed-$SESSION"
12
- HASH="$MARKER.hash"
13
- PLAN="/tmp/architect-plan-reviewed-$SESSION"
14
- rm -f "$MARKER" "$HASH" "$PLAN"
15
- }
16
-
17
- teardown() {
18
- rm -f "$MARKER" "$HASH" "$PLAN"
19
- rm -rf "$TMP"
20
- }
21
-
22
- dispatch() {
23
- printf '%s' "$1" | node "$HOOK_DIR/codex-agent-completion.mjs"
24
- }
25
-
26
- spawn() {
27
- printf '{"session_id":"%s","cwd":"%s","tool_name":"multi_agent_v1__spawn_agent","tool_input":{"agent_type":"%s"},"tool_response":{"agent_id":"%s"}}' "$SESSION" "$CLAUDE_PROJECT_DIR" "$1" "$TARGET"
28
- }
29
-
30
- close() {
31
- printf '{"session_id":"%s","cwd":"%s","tool_name":"multi_agent_v1__close_agent","tool_input":{"target":"%s"},"tool_response":{"previous_status":{"completed":"%s"}}}' "$SESSION" "$CLAUDE_PROJECT_DIR" "$TARGET" "$1"
32
- }
33
-
34
- @test "matched Codex architect PASS creates review markers" {
35
- dispatch "$(spawn wr-architect:agent)"
36
- dispatch "$(close '**Architecture Review: PASS**')"
37
- [ -f "$MARKER" ]
38
- [ -f "$HASH" ]
39
- [ -f "$PLAN" ]
40
- }
41
-
42
- @test "issues and malformed output fail closed" {
43
- dispatch "$(spawn wr-architect:agent)"
44
- dispatch "$(close '**Architecture Review: ISSUES FOUND**')"
45
- [ ! -e "$MARKER" ]
46
- dispatch "$(spawn wr-architect:agent)"
47
- dispatch "$(close 'review complete')"
48
- [ ! -e "$MARKER" ]
49
- }
50
-
51
- @test "unmatched close and non-architect target do not unlock" {
52
- dispatch "$(close '**Architecture Review: PASS**')"
53
- [ ! -e "$MARKER" ]
54
- dispatch "$(spawn default)"
55
- dispatch "$(close '**Architecture Review: PASS**')"
56
- [ ! -e "$MARKER" ]
57
- }
58
-
59
- @test "reused target clears stale architect identity" {
60
- dispatch "$(spawn wr-architect:agent)"
61
- dispatch "$(spawn default)"
62
- dispatch "$(close '**Architecture Review: PASS**')"
63
- [ ! -e "$MARKER" ]
64
- }
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Tests for slide_marker_on_subprocess_return helper (P111).
4
- #
5
- # Behavioural contract:
6
- # - Slides an existing marker forward (touch) on PostToolUse:Agent|Bash
7
- # completion, treating subprocess wall-clock as continuous parent-session
8
- # work for TTL purposes.
9
- # - Never CREATES a marker (creating requires a real gate review).
10
- # - Skips slide on subprocess error (tool_response.is_error=true) so a failed
11
- # subprocess does NOT extend the parent's trust window (ADR-009 amendment).
12
- # - No-op when no marker exists or session_id is empty (fail-safe).
13
-
14
- setup() {
15
- HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
16
- source "$HOOKS_DIR/lib/gate-helpers.sh"
17
-
18
- TEST_SESSION="bats-slide-$$-${BATS_TEST_NUMBER}"
19
- MARKER="/tmp/architect-reviewed-${TEST_SESSION}"
20
- rm -f "$MARKER"
21
- }
22
-
23
- teardown() {
24
- rm -f "$MARKER"
25
- }
26
-
27
- # Helper: backdate file mtime by N seconds (portable between macOS and Linux)
28
- _backdate() {
29
- local file="$1" seconds="$2"
30
- local stamp
31
- stamp=$(date -v-${seconds}S +%Y%m%d%H%M.%S 2>/dev/null \
32
- || date -d "${seconds} seconds ago" +%Y%m%d%H%M.%S 2>/dev/null)
33
- touch -t "$stamp" "$file"
34
- }
35
-
36
- @test "slide: existing marker is touched on success response" {
37
- touch "$MARKER"
38
- _backdate "$MARKER" 60
39
- BEFORE=$(_mtime "$MARKER")
40
- _HOOK_INPUT='{"tool_response":{"content":[]}}'
41
- slide_marker_on_subprocess_return "$MARKER"
42
- AFTER=$(_mtime "$MARKER")
43
- [ "$AFTER" -gt "$BEFORE" ]
44
- }
45
-
46
- @test "slide: long-running subprocess does NOT cause parent marker expiry on return (P111 reproduction)" {
47
- # Simulate the P111 failure mode: parent's marker is set, then a long
48
- # subprocess runs (we backdate the marker to simulate elapsed wall-clock),
49
- # then PostToolUse fires with a successful tool_response. The marker mtime
50
- # must be refreshed so the parent's NEXT PreToolUse gate check (which
51
- # compares NOW - mtime against TTL) sees a fresh marker.
52
- touch "$MARKER"
53
- # Marker is 50 minutes old — under default 60-min TTL but close to expiry.
54
- # Without the slide on subprocess return, a subsequent 15-min subprocess
55
- # would push the mtime past TTL and the next PreToolUse would deny.
56
- _backdate "$MARKER" 3000
57
- BEFORE=$(_mtime "$MARKER")
58
- _HOOK_INPUT='{"tool_response":{"content":[{"type":"text","text":"OK"}]}}'
59
- slide_marker_on_subprocess_return "$MARKER"
60
- AFTER=$(_mtime "$MARKER")
61
- NOW=$(date +%s)
62
- [ "$AFTER" -gt "$BEFORE" ]
63
- # And the new mtime is approximately NOW (within 5 seconds of slide call)
64
- AGE=$((NOW - AFTER))
65
- [ "$AGE" -lt 5 ]
66
- }
67
-
68
- @test "slide: does NOT touch marker when tool_response.is_error=true" {
69
- touch "$MARKER"
70
- _backdate "$MARKER" 60
71
- BEFORE=$(_mtime "$MARKER")
72
- _HOOK_INPUT='{"tool_response":{"is_error":true,"content":[]}}'
73
- slide_marker_on_subprocess_return "$MARKER"
74
- AFTER=$(_mtime "$MARKER")
75
- [ "$BEFORE" = "$AFTER" ]
76
- }
77
-
78
- @test "slide: no-op when marker does not exist (never creates)" {
79
- [ ! -f "$MARKER" ]
80
- _HOOK_INPUT='{"tool_response":{"content":[]}}'
81
- slide_marker_on_subprocess_return "$MARKER"
82
- [ ! -f "$MARKER" ]
83
- }
84
-
85
- @test "slide: no-op when marker path argument is empty" {
86
- _HOOK_INPUT='{"tool_response":{"content":[]}}'
87
- run slide_marker_on_subprocess_return ""
88
- [ "$status" -eq 0 ]
89
- }
90
-
91
- @test "slide: malformed hook input is fail-safe (no slide)" {
92
- touch "$MARKER"
93
- _backdate "$MARKER" 60
94
- BEFORE=$(_mtime "$MARKER")
95
- _HOOK_INPUT='not valid json'
96
- slide_marker_on_subprocess_return "$MARKER"
97
- AFTER=$(_mtime "$MARKER")
98
- # Fail-safe: when the hook input cannot be parsed, treat as error and skip
99
- [ "$BEFORE" = "$AFTER" ]
100
- }
101
-
102
- @test "slide: triggers correctly on Skill tool_response shape (P213, ADR-009 2026-06-08 amendment)" {
103
- # hooks.json matcher expansion Agent|Bash → Agent|Bash|Skill (P213 Option D)
104
- # widens slide-marker coverage to PostToolUse:Skill completions (e.g. the
105
- # /wr-risk-scorer:assess-* sibling assessor SKILLs run as long subprocesses
106
- # by the AFK orchestrator). The Skill tool_response shape is identical to
107
- # Agent|Bash (Claude Code's uniform PostToolUse contract), so the matcher-
108
- # agnostic helper composes without code changes. This test documents that
109
- # contract explicitly so a future hook_input shape divergence regression
110
- # surfaces here rather than at the gate-denial site.
111
- touch "$MARKER"
112
- _backdate "$MARKER" 60
113
- BEFORE=$(_mtime "$MARKER")
114
- _HOOK_INPUT='{"tool_name":"Skill","tool_response":{"content":[{"type":"text","text":"OK"}]}}'
115
- slide_marker_on_subprocess_return "$MARKER"
116
- AFTER=$(_mtime "$MARKER")
117
- [ "$AFTER" -gt "$BEFORE" ]
118
- }
@@ -1,243 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Behavioural tests for ADR-009 amendment 2026-06-06: substance-aware drift +
4
- # atomic verdict-write. Closes P353 (hash-marker brittleness umbrella) +
5
- # P303 (architect-gate multi-ADR deadlock drift-relock facet).
6
- #
7
- # Cases ratified by the user 2026-06-06:
8
- # (a) Trivial edit (whitespace, CRLF, trailing newline) does NOT re-trigger.
9
- # (b) Substantive edit (new content, changed line) DOES re-trigger.
10
- # (c) Atomic write — marker + hash file land together on PASS, with the
11
- # marker's mtime current and the hash file's content equal to the
12
- # substance hash of the policy content.
13
- # (d) Conservative fallback — semantic edits beyond the documented
14
- # whitespace normalisation (e.g. single-numeral change) ARE treated as
15
- # substantive (re-review fires).
16
-
17
- setup() {
18
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
19
- source "$SCRIPT_DIR/lib/gate-helpers.sh"
20
- source "$SCRIPT_DIR/lib/architect-gate.sh"
21
-
22
- TEST_SESSION="bats-arch-substance-$$-${BATS_TEST_NUMBER}"
23
- TEST_DIR=$(mktemp -d -t substance-aware-drift.XXXXXX)
24
- mkdir -p "$TEST_DIR/docs/decisions"
25
- CLAUDE_PROJECT_DIR="$TEST_DIR"
26
- export CLAUDE_PROJECT_DIR
27
-
28
- MARKER="/tmp/architect-reviewed-${TEST_SESSION}"
29
- HASH_FILE="${MARKER}.hash"
30
- rm -f "$MARKER" "$HASH_FILE"
31
- }
32
-
33
- teardown() {
34
- rm -f "$MARKER" "$HASH_FILE"
35
- rm -rf "$TEST_DIR"
36
- }
37
-
38
- # Helper: install ADR content, then store the substance hash + marker as the
39
- # architect-mark-reviewed.sh PASS path would.
40
- _install_and_mark() {
41
- local body="$1"
42
- printf '%s' "$body" > "$TEST_DIR/docs/decisions/123-test.proposed.md"
43
- local hash
44
- hash=$(_substance_hash_path "$TEST_DIR/docs/decisions")
45
- _atomic_mark_with_hash "$MARKER" "$hash"
46
- }
47
-
48
- # ---------------------------------------------------------------------------
49
- # Case (a) — trivial whitespace / line-ending edits do NOT re-trigger
50
- # ---------------------------------------------------------------------------
51
-
52
- @test "substance-aware: trailing-whitespace edit does NOT re-trigger drift" {
53
- _install_and_mark "# ADR-123
54
-
55
- A line of policy content.
56
- "
57
- # Add trailing spaces to the same line.
58
- printf '%s' "# ADR-123
59
-
60
- A line of policy content.
61
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
62
-
63
- run check_architect_gate "$TEST_SESSION"
64
- [ "$status" -eq 0 ]
65
- [ -f "$MARKER" ]
66
- [ -f "$HASH_FILE" ]
67
- }
68
-
69
- @test "substance-aware: CRLF→LF edit does NOT re-trigger drift" {
70
- _install_and_mark "# ADR-123
71
-
72
- A line of policy content.
73
- "
74
- # Convert the same content to CRLF line endings.
75
- printf '# ADR-123\r\n\r\nA line of policy content.\r\n' \
76
- > "$TEST_DIR/docs/decisions/123-test.proposed.md"
77
-
78
- run check_architect_gate "$TEST_SESSION"
79
- [ "$status" -eq 0 ]
80
- [ -f "$MARKER" ]
81
- }
82
-
83
- @test "substance-aware: extra trailing newlines do NOT re-trigger drift" {
84
- _install_and_mark "# ADR-123
85
-
86
- A line of policy content.
87
- "
88
- # Add multiple trailing newlines.
89
- printf '%s' "# ADR-123
90
-
91
- A line of policy content.
92
-
93
-
94
-
95
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
96
-
97
- run check_architect_gate "$TEST_SESSION"
98
- [ "$status" -eq 0 ]
99
- [ -f "$MARKER" ]
100
- }
101
-
102
- # ---------------------------------------------------------------------------
103
- # Case (b) — substantive content edits DO re-trigger
104
- # ---------------------------------------------------------------------------
105
-
106
- @test "substance-aware: new-word edit DOES re-trigger drift" {
107
- _install_and_mark "# ADR-123
108
-
109
- A line of policy content.
110
- "
111
- # Real content change.
112
- printf '%s' "# ADR-123
113
-
114
- A different line of policy content.
115
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
116
-
117
- run check_architect_gate "$TEST_SESSION"
118
- [ "$status" -ne 0 ]
119
- [ ! -f "$MARKER" ]
120
- [ ! -f "$HASH_FILE" ]
121
- }
122
-
123
- @test "substance-aware: added-paragraph edit DOES re-trigger drift" {
124
- _install_and_mark "# ADR-123
125
-
126
- A line of policy content.
127
- "
128
- printf '%s' "# ADR-123
129
-
130
- A line of policy content.
131
-
132
- A whole new paragraph of substantive change.
133
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
134
-
135
- run check_architect_gate "$TEST_SESSION"
136
- [ "$status" -ne 0 ]
137
- [ ! -f "$MARKER" ]
138
- }
139
-
140
- @test "substance-aware: new file in docs/decisions DOES re-trigger drift" {
141
- _install_and_mark "# ADR-123
142
-
143
- Content.
144
- "
145
- # Adding a NEW ADR file is a substantive change.
146
- printf '%s' "# ADR-124
147
-
148
- New ADR content.
149
- " > "$TEST_DIR/docs/decisions/124-new.proposed.md"
150
-
151
- run check_architect_gate "$TEST_SESSION"
152
- [ "$status" -ne 0 ]
153
- [ ! -f "$MARKER" ]
154
- }
155
-
156
- # ---------------------------------------------------------------------------
157
- # Case (c) — atomic write persists the PASS marker reliably
158
- # ---------------------------------------------------------------------------
159
-
160
- @test "atomic-write: PASS write lands marker + hash together" {
161
- printf '%s' "# ADR-123
162
-
163
- Policy content.
164
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
165
- local hash
166
- hash=$(_substance_hash_path "$TEST_DIR/docs/decisions")
167
-
168
- run _atomic_mark_with_hash "$MARKER" "$hash"
169
- [ "$status" -eq 0 ]
170
- [ -f "$MARKER" ]
171
- [ -f "$HASH_FILE" ]
172
- # The stored hash equals the substance hash of the policy content.
173
- [ "$(cat "$HASH_FILE")" = "$hash" ]
174
- }
175
-
176
- @test "atomic-write: failed hash-write leaves neither file (no half-state)" {
177
- # Point the marker into a non-existent directory to force mv failure.
178
- local bad_marker="/tmp/does-not-exist-bats-$$/architect-reviewed-${TEST_SESSION}"
179
- run _atomic_mark_with_hash "$bad_marker" "deadbeef"
180
- [ "$status" -ne 0 ]
181
- [ ! -f "$bad_marker" ]
182
- [ ! -f "${bad_marker}.hash" ]
183
- }
184
-
185
- @test "atomic-write: PASS write enables a subsequent gate check to allow" {
186
- printf '%s' "# ADR-123
187
-
188
- Policy content.
189
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
190
- local hash
191
- hash=$(_substance_hash_path "$TEST_DIR/docs/decisions")
192
- _atomic_mark_with_hash "$MARKER" "$hash"
193
-
194
- run check_architect_gate "$TEST_SESSION"
195
- [ "$status" -eq 0 ]
196
- }
197
-
198
- # ---------------------------------------------------------------------------
199
- # Case (d) — conservative boundary: semantic edits beyond whitespace
200
- # normalisation ARE treated as substantive
201
- # ---------------------------------------------------------------------------
202
-
203
- @test "conservative: single-numeral change DOES re-trigger drift" {
204
- _install_and_mark "# ADR-123
205
-
206
- Threshold is 5 minutes.
207
- "
208
- # A single-numeral change is semantic — conservative boundary re-reviews.
209
- printf '%s' "# ADR-123
210
-
211
- Threshold is 6 minutes.
212
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
213
-
214
- run check_architect_gate "$TEST_SESSION"
215
- [ "$status" -ne 0 ]
216
- [ ! -f "$MARKER" ]
217
- }
218
-
219
- @test "conservative: frontmatter-key change DOES re-trigger drift" {
220
- _install_and_mark "---
221
- status: proposed
222
- date: 2026-06-06
223
- ---
224
-
225
- # ADR-123
226
-
227
- Body.
228
- "
229
- # Frontmatter date bump — conservative boundary re-reviews.
230
- printf '%s' "---
231
- status: proposed
232
- date: 2026-06-07
233
- ---
234
-
235
- # ADR-123
236
-
237
- Body.
238
- " > "$TEST_DIR/docs/decisions/123-test.proposed.md"
239
-
240
- run check_architect_gate "$TEST_SESSION"
241
- [ "$status" -ne 0 ]
242
- [ ! -f "$MARKER" ]
243
- }