@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,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
|
-
}
|