@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.
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,183 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P301: architect-enforce-edit.sh exempts marker-only frontmatter diffs to
4
- # docs/decisions/*.md ADRs from the full architect review gate. The
5
- # architect-oversight-marker-discipline.sh hook remains the safety net for
6
- # `human-oversight: confirmed` introductions; this exemption only short-
7
- # circuits the enforce-edit drift/TTL gate when the diff adds/modifies
8
- # nothing but the narrow oversight-marker grammar.
9
- #
10
- # Behavioural — exercises the full hook with constructed PreToolUse stdin
11
- # JSON payloads under a sandbox project dir; asserts on stdout+exit.
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 architect gate — only runs when docs/decisions/ exists.
20
- mkdir -p docs/decisions
21
- echo "# stub" > docs/decisions/001-stub.proposed.md
22
- SID="marker-only-exempt-$$-$BATS_TEST_NUMBER"
23
- }
24
-
25
- teardown() {
26
- cd "$ORIG_DIR"
27
- rm -rf "$TEST_DIR"
28
- rm -f "/tmp/architect-reviewed-${SID}" "/tmp/architect-reviewed-${SID}.hash"
29
- }
30
-
31
- # Helper: run the hook with constructed JSON. Echo to a file then pipe to
32
- # avoid heredoc / quote-escaping headaches with multi-line content.
33
- run_hook_json() {
34
- local json_file="$1"
35
- bash "$HOOK" < "$json_file"
36
- }
37
-
38
- # ── Marker-only ADD: should exempt ───────────────────────────────────────
39
-
40
- @test "P301: Edit adding only human-oversight + oversight-date is exempt (no architect marker required)" {
41
- adr="$PWD/docs/decisions/100-some-adr.proposed.md"
42
- cat > "$adr" <<'EOF'
43
- ---
44
- status: "proposed"
45
- date: 2026-06-08
46
- ---
47
-
48
- # 100 some adr
49
-
50
- Body content unchanged.
51
- EOF
52
- old=$'---\nstatus: "proposed"\ndate: 2026-06-08\n---'
53
- new=$'---\nstatus: "proposed"\ndate: 2026-06-08\nhuman-oversight: unconfirmed\noversight-date: 2026-06-08\n---'
54
- json_file=$(mktemp)
55
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
56
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
57
- run run_hook_json "$json_file"
58
- rm -f "$json_file"
59
- [ "$status" -eq 0 ]
60
- [[ "$output" != *"BLOCKED"* ]]
61
- }
62
-
63
- @test "P301: Edit updating human-oversight value (unconfirmed → confirmed) is exempt" {
64
- adr="$PWD/docs/decisions/101-promote.proposed.md"
65
- cat > "$adr" <<'EOF'
66
- ---
67
- status: "proposed"
68
- human-oversight: unconfirmed
69
- ---
70
-
71
- # 101 promote
72
- EOF
73
- old=$'human-oversight: unconfirmed'
74
- new=$'human-oversight: confirmed\noversight-date: 2026-06-08'
75
- json_file=$(mktemp)
76
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
77
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
78
- run run_hook_json "$json_file"
79
- rm -f "$json_file"
80
- [ "$status" -eq 0 ]
81
- [[ "$output" != *"BLOCKED"* ]]
82
- }
83
-
84
- @test "P301: Edit adding rejected-pending-supersede + supersede-ticket is exempt" {
85
- adr="$PWD/docs/decisions/102-rejected.proposed.md"
86
- cat > "$adr" <<'EOF'
87
- ---
88
- status: "proposed"
89
- ---
90
-
91
- # 102 rejected
92
- EOF
93
- old=$'status: "proposed"'
94
- new=$'status: "proposed"\nhuman-oversight: rejected-pending-supersede\nsupersede-ticket: P999'
95
- json_file=$(mktemp)
96
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
97
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
98
- run run_hook_json "$json_file"
99
- rm -f "$json_file"
100
- [ "$status" -eq 0 ]
101
- [[ "$output" != *"BLOCKED"* ]]
102
- }
103
-
104
- # ── Mixed marker + body: must NOT exempt ─────────────────────────────────
105
-
106
- @test "P301: Edit changing body content along with markers still gates" {
107
- adr="$PWD/docs/decisions/110-mixed.proposed.md"
108
- cat > "$adr" <<'EOF'
109
- ---
110
- status: "proposed"
111
- ---
112
-
113
- # 110 mixed
114
-
115
- Original body.
116
- EOF
117
- old=$'---\nstatus: "proposed"\n---\n\n# 110 mixed\n\nOriginal body.'
118
- new=$'---\nstatus: "proposed"\nhuman-oversight: confirmed\noversight-date: 2026-06-08\n---\n\n# 110 mixed\n\nRewritten body with new policy claim.'
119
- json_file=$(mktemp)
120
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
121
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
122
- run run_hook_json "$json_file"
123
- rm -f "$json_file"
124
- [[ "$output" == *"BLOCKED"* ]]
125
- }
126
-
127
- @test "P301: Edit changing status field (not a marker line) still gates" {
128
- adr="$PWD/docs/decisions/111-status-change.proposed.md"
129
- cat > "$adr" <<'EOF'
130
- ---
131
- status: "proposed"
132
- ---
133
-
134
- # 111
135
- EOF
136
- old=$'status: "proposed"'
137
- new=$'status: "accepted"\nhuman-oversight: confirmed\noversight-date: 2026-06-08'
138
- json_file=$(mktemp)
139
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
140
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
141
- run run_hook_json "$json_file"
142
- rm -f "$json_file"
143
- [[ "$output" == *"BLOCKED"* ]]
144
- }
145
-
146
- # ── Pure body change: must still gate (no marker involvement) ────────────
147
-
148
- @test "P301: Edit changing only body content with no marker lines still gates" {
149
- adr="$PWD/docs/decisions/120-body.proposed.md"
150
- cat > "$adr" <<'EOF'
151
- ---
152
- status: "proposed"
153
- ---
154
-
155
- # 120 body
156
-
157
- Some text.
158
- EOF
159
- old='Some text.'
160
- new='Some text. And new text.'
161
- json_file=$(mktemp)
162
- jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
163
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
164
- run run_hook_json "$json_file"
165
- rm -f "$json_file"
166
- [[ "$output" == *"BLOCKED"* ]]
167
- }
168
-
169
- # ── Scope: exemption is narrow to docs/decisions/*.md ────────────────────
170
-
171
- @test "P301: marker-only diff to a NON docs/decisions/ path still gates (no path exemption)" {
172
- mkdir -p "$PWD/src"
173
- echo "// stub" > "$PWD/src/x.ts"
174
- src="$PWD/src/x.ts"
175
- old='// stub'
176
- new='// stub'$'\n''human-oversight: confirmed'
177
- json_file=$(mktemp)
178
- jq -nc --arg p "$src" --arg s "$SID" --arg o "$old" --arg n "$new" \
179
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}' > "$json_file"
180
- run run_hook_json "$json_file"
181
- rm -f "$json_file"
182
- [[ "$output" == *"BLOCKED"* ]]
183
- }
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P001 / ADR-009: Stop-hook marker reset removed. Marker lifecycle is
4
- # governed by TTL + drift detection. This test asserts the Stop hook
5
- # entry is absent from the plugin's hooks.json.
6
-
7
- setup() {
8
- PLUGIN_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)"
9
- }
10
-
11
- @test "architect: hooks.json has no Stop hook entry (ADR-009)" {
12
- ! grep -q '"Stop"' "$PLUGIN_DIR/hooks/hooks.json"
13
- }
14
-
15
- @test "architect: architect-reset-marker.sh has been removed" {
16
- [ ! -f "$PLUGIN_DIR/hooks/architect-reset-marker.sh" ]
17
- }
@@ -1,245 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # P348 / ADR-066 amendment 2026-06-02: architect-oversight-marker-discipline.sh
4
- # is a PreToolUse:Edit|Write hook that denies any Edit/Write that introduces
5
- # `human-oversight: confirmed` into a docs/decisions/ ADR's frontmatter unless
6
- # a session-scoped evidence marker `/tmp/oversight-confirmed-<sha>-<sid>`
7
- # exists for THAT specific ADR under THIS session.
8
- #
9
- # Behavioural — exercises the hook with constructed PreToolUse stdin JSON
10
- # payloads under SESSION_MARKER_DIR sandboxing, asserts on stdout+exit.
11
-
12
- setup() {
13
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
14
- HOOK="$REPO_ROOT/packages/architect/hooks/architect-oversight-marker-discipline.sh"
15
- MARK_SCRIPT="$REPO_ROOT/packages/architect/scripts/mark-oversight-confirmed.sh"
16
-
17
- DIR="$(mktemp -d)"
18
- mkdir -p "$DIR/docs/decisions"
19
- MARK_DIR="$(mktemp -d)"
20
- export SESSION_MARKER_DIR="$MARK_DIR"
21
-
22
- ORIG_DIR="$PWD"
23
- cd "$DIR"
24
-
25
- SID="discipline-test-$$"
26
- }
27
-
28
- teardown() {
29
- cd "$ORIG_DIR"
30
- rm -rf "$DIR" "$MARK_DIR"
31
- unset SESSION_MARKER_DIR
32
- }
33
-
34
- # Helper: compute the marker path the hook expects for an ADR file.
35
- expected_marker() {
36
- local f="$1"
37
- local abs_dir abs path_hash
38
- abs_dir="$(cd "$(dirname "$f")" && pwd)"
39
- abs="$abs_dir/$(basename "$f")"
40
- if command -v sha256sum >/dev/null 2>&1; then
41
- path_hash=$(printf '%s' "$abs" | sha256sum | cut -d' ' -f1 | cut -c1-16)
42
- else
43
- path_hash=$(printf '%s' "$abs" | shasum -a 256 | cut -d' ' -f1 | cut -c1-16)
44
- fi
45
- printf '%s/oversight-confirmed-%s-%s\n' "$MARK_DIR" "$path_hash" "$SID"
46
- }
47
-
48
- mk_existing_adr() {
49
- local name="$1"; shift
50
- {
51
- echo "---"
52
- echo "status: \"proposed\""
53
- echo "date: 2026-06-02"
54
- for line in "$@"; do echo "$line"; done
55
- echo "---"
56
- echo "# $name"
57
- } > "$DIR/docs/decisions/$name"
58
- }
59
-
60
- # ── Positive paths (marker present → allow) ──────────────────────────────
61
-
62
- @test "Write introducing 'human-oversight: confirmed' with marker present is allowed" {
63
- mk_existing_adr "200-unconfirmed.proposed.md"
64
- adr="$DIR/docs/decisions/200-unconfirmed.proposed.md"
65
- : > "$(expected_marker "$adr")"
66
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# 200-unconfirmed\n'
67
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg c "$new_content" \
68
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
69
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
70
- [ "$status" -eq 0 ]
71
- [[ "$output" != *"BLOCKED"* ]]
72
- }
73
-
74
- @test "Edit introducing 'human-oversight: confirmed' with marker present is allowed" {
75
- mk_existing_adr "201-pending.proposed.md"
76
- adr="$DIR/docs/decisions/201-pending.proposed.md"
77
- : > "$(expected_marker "$adr")"
78
- old='date: 2026-06-02'
79
- new=$'date: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02'
80
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
81
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}')
82
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
83
- [ "$status" -eq 0 ]
84
- [[ "$output" != *"BLOCKED"* ]]
85
- }
86
-
87
- # ── Negative paths (no marker → deny) ───────────────────────────────────
88
-
89
- @test "Write introducing 'human-oversight: confirmed' WITHOUT marker is denied" {
90
- mk_existing_adr "210-orphan.proposed.md"
91
- adr="$DIR/docs/decisions/210-orphan.proposed.md"
92
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# 210-orphan\n'
93
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg c "$new_content" \
94
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
95
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
96
- [ "$status" -eq 0 ]
97
- [[ "$output" == *'"permissionDecision": "deny"'* ]]
98
- [[ "$output" == *"BLOCKED"* ]]
99
- [[ "$output" == *"oversight-confirmed-"* ]]
100
- [[ "$output" == *"wr-architect-mark-oversight-confirmed"* ]]
101
- }
102
-
103
- @test "Edit introducing 'human-oversight: confirmed' WITHOUT marker is denied" {
104
- mk_existing_adr "211-no-evidence.proposed.md"
105
- adr="$DIR/docs/decisions/211-no-evidence.proposed.md"
106
- old='date: 2026-06-02'
107
- new=$'date: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02'
108
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg o "$old" --arg n "$new" \
109
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}')
110
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
111
- [[ "$output" == *'"permissionDecision": "deny"'* ]]
112
- }
113
-
114
- # ── AFK-unconfirmed-write path (no marker required) ──────────────────────
115
-
116
- @test "Write introducing 'human-oversight: unconfirmed' is allowed without marker (AFK path)" {
117
- mk_existing_adr "220-afk.proposed.md"
118
- adr="$DIR/docs/decisions/220-afk.proposed.md"
119
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: unconfirmed\n---\n\n# 220-afk\n'
120
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg c "$new_content" \
121
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
122
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
123
- [ "$status" -eq 0 ]
124
- [[ "$output" != *"BLOCKED"* ]]
125
- }
126
-
127
- @test "Write introducing rejected-pending-supersede is allowed without marker" {
128
- mk_existing_adr "221-rejected.proposed.md"
129
- adr="$DIR/docs/decisions/221-rejected.proposed.md"
130
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: rejected-pending-supersede\nsupersede-ticket: P999\n---\n\n# 221-rejected\n'
131
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg c "$new_content" \
132
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
133
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
134
- [ "$status" -eq 0 ]
135
- [[ "$output" != *"BLOCKED"* ]]
136
- }
137
-
138
- # ── Scope / non-fire paths ────────────────────────────────────────────────
139
-
140
- @test "Edit to a non-ADR path (src/index.ts) exits 0 silently" {
141
- mkdir -p "$DIR/src"
142
- echo "// stub" > "$DIR/src/index.ts"
143
- json=$(jq -nc --arg p "$DIR/src/index.ts" --arg s "$SID" \
144
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"// stub",new_string:"// human-oversight: confirmed"}}')
145
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
146
- [ "$status" -eq 0 ]
147
- [ -z "$output" ]
148
- }
149
-
150
- @test "Edit to docs/decisions/README.md exits 0 silently" {
151
- echo "# index" > "$DIR/docs/decisions/README.md"
152
- adr="$DIR/docs/decisions/README.md"
153
- new=$'# index\nhuman-oversight: confirmed'
154
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg n "$new" \
155
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"# index",new_string:$n}}')
156
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
157
- [ "$status" -eq 0 ]
158
- [ -z "$output" ]
159
- }
160
-
161
- @test "Edit whose new content lacks the marker exits 0 silently" {
162
- mk_existing_adr "230-unrelated.proposed.md"
163
- adr="$DIR/docs/decisions/230-unrelated.proposed.md"
164
- json=$(jq -nc --arg p "$adr" --arg s "$SID" \
165
- '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"# 230-unrelated",new_string:"# 230 renamed"}}')
166
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
167
- [ "$status" -eq 0 ]
168
- [ -z "$output" ]
169
- }
170
-
171
- @test "Write whose OLD content already had the marker (re-stamp / no-op) is allowed without re-evidencing" {
172
- # Pre-existing ADR already carries the marker; new content keeps it.
173
- mk_existing_adr "240-already.proposed.md" "human-oversight: confirmed" "oversight-date: 2026-05-30"
174
- adr="$DIR/docs/decisions/240-already.proposed.md"
175
- new_content=$'---\nstatus: "accepted"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-05-30\n---\n\n# 240-already\n'
176
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg c "$new_content" \
177
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
178
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
179
- [ "$status" -eq 0 ]
180
- [[ "$output" != *"BLOCKED"* ]]
181
- }
182
-
183
- # ── End-to-end: mark-oversight-confirmed.sh + the hook ──────────────────
184
-
185
- @test "mark-oversight-confirmed.sh writes a marker that satisfies the hook" {
186
- mk_existing_adr "250-e2e.proposed.md"
187
- adr="$DIR/docs/decisions/250-e2e.proposed.md"
188
- # Seed an announce marker so candidate enumeration finds the SID.
189
- : > "$MARK_DIR/architect-announced-$SID"
190
- bash "$MARK_SCRIPT" "$adr"
191
- # The mark script writes under every candidate; the hook's expected marker
192
- # filename for our SID must now exist.
193
- [ -f "$(expected_marker "$adr")" ]
194
- new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# 250-e2e\n'
195
- json=$(jq -nc --arg p "$adr" --arg s "$SID" --arg c "$new_content" \
196
- '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
197
- run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
198
- [ "$status" -eq 0 ]
199
- [[ "$output" != *"BLOCKED"* ]]
200
- }
201
-
202
- # P380: on macOS SESSION_MARKER_DIR defaults to /tmp, a symlink to /private/tmp.
203
- # `find <symlink> -maxdepth 1` in default (-P) mode refuses to descend the
204
- # start-point symlink, so candidate enumeration returns empty and the script
205
- # writes ZERO markers (silent cold-path exit 0). The `-L` flag follows it. This
206
- # test points SESSION_MARKER_DIR at a SYMLINK to the marker dir (reproducing the
207
- # macOS /tmp shape on any platform); RED without `-L`, GREEN with it.
208
- @test "mark-oversight-confirmed.sh enumerates candidates when SESSION_MARKER_DIR is a symlink (P380)" {
209
- mk_existing_adr "251-symlink.proposed.md"
210
- adr="$DIR/docs/decisions/251-symlink.proposed.md"
211
- : > "$MARK_DIR/architect-announced-$SID"
212
- link_dir="${MARK_DIR}.link"
213
- ln -s "$MARK_DIR" "$link_dir"
214
- SESSION_MARKER_DIR="$link_dir" bash "$MARK_SCRIPT" "$adr"
215
- rm -f "$link_dir"
216
- # Marker written under the discovered SID despite the symlinked marker dir.
217
- [ -f "$(expected_marker "$adr")" ]
218
- }
219
-
220
- # P368: genuine cold path — no CLAUDE_SESSION_ID and no announce markers at all.
221
- # The shim correctly writes zero markers (nothing to key on) but must NOT do so
222
- # SILENTLY: a loud stderr diagnostic explains why the subsequent discipline-hook
223
- # deny happens, breaking the confusing "you didn't run the shim" loop the shim's
224
- # prior silent exit 0 produced. Exit stays 0 (documented contract: do not crash
225
- # SKILL flows before any hook has fired this session).
226
- @test "mark-oversight-confirmed.sh emits a loud stderr diagnostic on the no-candidate cold path (P368)" {
227
- mk_existing_adr "252-coldpath.proposed.md"
228
- adr="$DIR/docs/decisions/252-coldpath.proposed.md"
229
- # MARK_DIR is empty (no *-announced-* markers); unset the env SID fast-path too.
230
- run env -u CLAUDE_SESSION_ID bash "$MARK_SCRIPT" "$adr"
231
- [ "$status" -eq 0 ] # contract: cold path still exits 0
232
- [ ! -f "$(expected_marker "$adr")" ] # no marker written
233
- [[ "$output" == *"no candidate session id"* ]] # loud, not silent
234
- [[ "$output" == *"P368"* ]]
235
- }
236
-
237
- # ── Non-Edit/Write tool calls always exit 0 silently ────────────────────
238
-
239
- @test "tool_name=Bash exits 0 silently regardless of file path" {
240
- json=$(jq -nc --arg s "$SID" \
241
- '{tool_name:"Bash",session_id:$s,tool_input:{command:"echo human-oversight: confirmed"}}')
242
- run bash -c "echo '$json' | bash '$HOOK'"
243
- [ "$status" -eq 0 ]
244
- [ -z "$output" ]
245
- }
@@ -1,75 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # ADR-066: architect-oversight-nudge.sh (SessionStart) emits a one-line nudge
4
- # when ADRs lack the human-oversight marker, is silent when none do, and
5
- # self-suppresses under the AFK guard (WR_SUPPRESS_OVERSIGHT_NUDGE=1) so the
6
- # interactive batch-confirm never fires into an absent-user iteration (JTBD-006).
7
- # Behavioural — exercises the hook against fixture trees and asserts on stdout.
8
-
9
- setup() {
10
- REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
11
- HOOK="$REPO_ROOT/packages/architect/hooks/architect-oversight-nudge.sh"
12
- PLUGIN_ROOT="$REPO_ROOT/packages/architect"
13
- DIR="$(mktemp -d)"
14
- mkdir -p "$DIR/docs/decisions"
15
- # Hermeticity (P391): the AFK work-problems orchestrator exports
16
- # WR_SUPPRESS_OVERSIGHT_NUDGE=1, which the hook self-suppresses on. Unset it so
17
- # count-emitting tests assert real behaviour; guard-specific tests set it
18
- # per-invocation via `run env WR_SUPPRESS_OVERSIGHT_NUDGE=...`.
19
- unset WR_SUPPRESS_OVERSIGHT_NUDGE
20
- }
21
-
22
- teardown() {
23
- rm -rf "$DIR"
24
- }
25
-
26
- mk_unmarked() {
27
- { echo "---"; echo "status: \"proposed\""; echo "date: 2026-05-25"; echo "---"; echo "# $1"; } \
28
- > "$DIR/docs/decisions/$1"
29
- }
30
- mk_marked() {
31
- { echo "---"; echo "status: \"proposed\""; echo "date: 2026-05-25"; echo "human-oversight: confirmed"; echo "---"; echo "# $1"; } \
32
- > "$DIR/docs/decisions/$1"
33
- }
34
-
35
- @test "emits a count line when there are unoversighted ADRs" {
36
- mk_unmarked "010-a.proposed.md"
37
- mk_unmarked "011-b.proposed.md"
38
- run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
39
- [ "$status" -eq 0 ]
40
- [[ "$output" == *"2 recorded decisions lack human oversight"* ]]
41
- [[ "$output" == *"/wr-architect:review-decisions"* ]]
42
- }
43
-
44
- @test "uses singular wording for exactly one unoversighted ADR" {
45
- mk_unmarked "010-a.proposed.md"
46
- run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
47
- [[ "$output" == *"1 recorded decision lacks human oversight"* ]]
48
- }
49
-
50
- @test "silent when every ADR is confirmed" {
51
- mk_marked "010-a.proposed.md"
52
- run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
53
- [ "$status" -eq 0 ]
54
- [ -z "$output" ]
55
- }
56
-
57
- @test "AFK guard suppresses the nudge entirely" {
58
- mk_unmarked "010-a.proposed.md"
59
- run env WR_SUPPRESS_OVERSIGHT_NUDGE=1 CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
60
- [ "$status" -eq 0 ]
61
- [ -z "$output" ]
62
- }
63
-
64
- @test "guard value other than 1 does not suppress" {
65
- mk_unmarked "010-a.proposed.md"
66
- mk_unmarked "011-b.proposed.md"
67
- run env WR_SUPPRESS_OVERSIGHT_NUDGE=0 CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
68
- [[ "$output" == *"lack human oversight"* ]]
69
- }
70
-
71
- @test "silent when project has no docs/decisions dir" {
72
- run env CLAUDE_PROJECT_DIR="$DIR/empty" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
73
- [ "$status" -eq 0 ]
74
- [ -z "$output" ]
75
- }
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Tests for architect-enforce-edit.sh project-root check (P004).
4
- # Verifies that absolute paths outside $PWD are exempted.
5
-
6
- setup() {
7
- SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
8
- HOOK="$SCRIPT_DIR/architect-enforce-edit.sh"
9
- ORIG_DIR="$PWD"
10
- }
11
-
12
- teardown() {
13
- cd "$ORIG_DIR"
14
- }
15
-
16
- run_hook_with_file() {
17
- local file_path="$1"
18
- local json="{\"tool_input\":{\"file_path\":\"${file_path}\"},\"session_id\":\"test-session-$$\"}"
19
- echo "$json" | bash "$HOOK"
20
- }
21
-
22
- @test "project-root: absolute path outside project exits 0" {
23
- run run_hook_with_file "/Users/other/somewhere/file.json"
24
- [ "$status" -eq 0 ]
25
- [[ "$output" != *"BLOCKED"* ]]
26
- }
27
-
28
- @test "project-root: home-dir config path exits 0" {
29
- run run_hook_with_file "/Users/somebody/.claude/channels/discord/access.json"
30
- [ "$status" -eq 0 ]
31
- [[ "$output" != *"BLOCKED"* ]]
32
- }
33
-
34
- @test "project-root: absolute path inside \$PWD is still gated" {
35
- # Use a temp dir as PWD with docs/decisions to trigger the gate
36
- TEST_DIR=$(mktemp -d)
37
- mkdir -p "$TEST_DIR/docs/decisions"
38
- echo "# ADR" > "$TEST_DIR/docs/decisions/001-test.proposed.md"
39
- cd "$TEST_DIR"
40
- run run_hook_with_file "$TEST_DIR/src/index.ts"
41
- [ "$status" -eq 0 ]
42
- [[ "$output" == *"BLOCKED"* ]]
43
- rm -rf "$TEST_DIR"
44
- }
45
-
46
- # P191 Phase 2: the architect gate must resolve docs/decisions from the
47
- # project root (CLAUDE_PROJECT_DIR), NOT the hook's actual runtime CWD. Claude
48
- # Code can launch the hook with a working directory that differs from the
49
- # session/project dir; a relative `[ ! -d "docs/decisions" ]` then
50
- # false-negatives and the gate FAILS OPEN (exit 0) — silently DEACTIVATING the
51
- # architect gate so edits bypass review. This is a governance hole, strictly
52
- # more severe than the JTBD gate's fail-closed nuisance (P191 Phase 1).
53
- @test "project-root: gate stays ACTIVE via CLAUDE_PROJECT_DIR when hook CWD differs (P191 Phase 2)" {
54
- local proj other json
55
- proj="$(mktemp -d)"
56
- other="$(mktemp -d)" # a CWD that does NOT contain docs/decisions
57
- mkdir -p "$proj/docs/decisions"
58
- echo "# ADR" > "$proj/docs/decisions/001-test.proposed.md"
59
- json="{\"tool_input\":{\"file_path\":\"${proj}/src/index.ts\"},\"session_id\":\"test-session-$$\"}"
60
- # Fire from `other` (wrong CWD) with CLAUDE_PROJECT_DIR set to the real
61
- # project. Pre-fix this silently exited 0 (gate inactive, edit allowed);
62
- # post-fix the gate is ACTIVE and denies for the missing review marker.
63
- run env CLAUDE_PROJECT_DIR="$proj" bash -c "cd '$other' && printf '%s' '$json' | bash '$HOOK'"
64
- rm -rf "$proj" "$other"
65
- [[ "$output" == *"BLOCKED"* ]]
66
- [[ "$output" == *"without architecture review"* ]]
67
- }
68
-
69
- # P191 Phase 2 regression guard: when docs/decisions genuinely does not exist
70
- # under the project root, the gate correctly stays INACTIVE (fail-open exit 0).
71
- # The fix narrows the false-negative; it must not start gating projects that
72
- # have no architecture decisions.
73
- @test "project-root: genuinely-absent docs/decisions stays inactive (fail-open preserved, P191 Phase 2)" {
74
- local proj json
75
- proj="$(mktemp -d)" # no docs/decisions created
76
- json="{\"tool_input\":{\"file_path\":\"${proj}/src/index.ts\"},\"session_id\":\"test-session-$$\"}"
77
- run env CLAUDE_PROJECT_DIR="$proj" bash -c "printf '%s' '$json' | bash '$HOOK'"
78
- rm -rf "$proj"
79
- [ "$status" -eq 0 ]
80
- [[ "$output" != *"BLOCKED"* ]]
81
- }