@windyroad/itil 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -0
- package/bin/wr-itil-reconcile-rfcs +2 -0
- package/hooks/hooks.json +6 -0
- package/hooks/itil-rfc-trailer-advisory.sh +198 -0
- package/hooks/lib/create-gate.sh +30 -0
- package/hooks/manage-problem-enforce-create.sh +89 -44
- package/hooks/test/itil-rfc-trailer-advisory.bats +273 -0
- package/hooks/test/manage-problem-enforce-create.bats +105 -1
- package/package.json +1 -1
- package/scripts/reconcile-rfcs.sh +329 -0
- package/scripts/test/reconcile-rfcs.bats +433 -0
- package/scripts/test/update-problem-rfcs-section.bats +242 -0
- package/scripts/update-problem-rfcs-section.sh +160 -0
- package/skills/capture-rfc/SKILL.md +276 -0
- package/skills/manage-rfc/SKILL.md +260 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# @problem P170 — Slice 3 second half (B5.T9): PostToolUse:Bash hook
|
|
4
|
+
# detects `git commit` invocations whose HEAD commit message carries
|
|
5
|
+
# a `Refs: RFC-<NNN>` trailer, and emits a stderr advisory when the
|
|
6
|
+
# corresponding driving-problem ticket's `## RFCs` table is stale.
|
|
7
|
+
#
|
|
8
|
+
# Architect Q1 verdict: skill-side refresh primary; hook-side advisory
|
|
9
|
+
# for arbitrary commits (e.g. feat/fix/chore commits with `Refs:` trailer
|
|
10
|
+
# authored outside the RFC skills).
|
|
11
|
+
# Architect Q2 verdict: PostToolUse:Bash; advisory-only; silent-on-pass
|
|
12
|
+
# per ADR-045 Pattern 1; fail-open per ADR-013 Rule 6.
|
|
13
|
+
# Architect Q4 verdict: parse via `git interpret-trailers`; multi-`Refs:`
|
|
14
|
+
# trailers emit malformed-per-finding-8 advisory.
|
|
15
|
+
#
|
|
16
|
+
# Behavioural per ADR-052 + P081: assert on emitted stderr / exit code
|
|
17
|
+
# in response to simulated PostToolUse:Bash payload — no structural
|
|
18
|
+
# greps on hook source.
|
|
19
|
+
#
|
|
20
|
+
# @adr ADR-014 (single-commit grain — hook never auto-fixes via follow-up commit)
|
|
21
|
+
# @adr ADR-013 Rule 6 (fail-open)
|
|
22
|
+
# @adr ADR-045 (silent-on-pass; advisory band ≤300 bytes)
|
|
23
|
+
# @adr ADR-051 (load-bearing-from-the-start)
|
|
24
|
+
# @adr ADR-060 (Phase 1 item 12 + Confirmation criterion 3)
|
|
25
|
+
# @jtbd JTBD-006 (advisory does not block AFK loop — exit 0; stderr only)
|
|
26
|
+
# @jtbd JTBD-008 (reverse-trace surface JTBD-008 names — drift detection)
|
|
27
|
+
|
|
28
|
+
setup() {
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
30
|
+
HOOK="$SCRIPT_DIR/itil-rfc-trailer-advisory.sh"
|
|
31
|
+
ORIG_DIR="$PWD"
|
|
32
|
+
TEST_DIR=$(mktemp -d)
|
|
33
|
+
cd "$TEST_DIR"
|
|
34
|
+
git init --quiet -b main
|
|
35
|
+
git config user.email "test@example.com"
|
|
36
|
+
git config user.name "Test"
|
|
37
|
+
mkdir -p docs/rfcs docs/problems
|
|
38
|
+
echo "seed" > seed.txt
|
|
39
|
+
git add seed.txt
|
|
40
|
+
git -c commit.gpgsign=false commit --quiet -m "initial"
|
|
41
|
+
unset BYPASS_RFC_TRAILER_ADVISORY
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
teardown() {
|
|
45
|
+
cd "$ORIG_DIR"
|
|
46
|
+
rm -rf "$TEST_DIR"
|
|
47
|
+
unset BYPASS_RFC_TRAILER_ADVISORY
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
write_rfc() {
|
|
51
|
+
local id="$1" slug="$2" status="$3"
|
|
52
|
+
local problems="${4:-[P168]}"
|
|
53
|
+
cat > "docs/rfcs/RFC-${id}-${slug}.${status}.md" <<EOF
|
|
54
|
+
---
|
|
55
|
+
status: ${status}
|
|
56
|
+
rfc-id: ${slug}
|
|
57
|
+
reported: 2026-05-05
|
|
58
|
+
decision-makers: [test]
|
|
59
|
+
problems: ${problems}
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
# RFC-${id}: ${slug}
|
|
63
|
+
|
|
64
|
+
stub
|
|
65
|
+
EOF
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
write_problem_with_rfcs_section() {
|
|
69
|
+
local num="$1" rows="$2"
|
|
70
|
+
local file="docs/problems/${num}-stub.open.md"
|
|
71
|
+
cat > "$file" <<EOF
|
|
72
|
+
# Problem ${num}: stub
|
|
73
|
+
|
|
74
|
+
**Status**: Open
|
|
75
|
+
|
|
76
|
+
## Description
|
|
77
|
+
|
|
78
|
+
stub
|
|
79
|
+
|
|
80
|
+
## Related
|
|
81
|
+
|
|
82
|
+
stub
|
|
83
|
+
|
|
84
|
+
## RFCs
|
|
85
|
+
|
|
86
|
+
| RFC | Status | Title |
|
|
87
|
+
|-----|--------|-------|
|
|
88
|
+
${rows}
|
|
89
|
+
EOF
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
write_problem_without_rfcs_section() {
|
|
93
|
+
local num="$1"
|
|
94
|
+
cat > "docs/problems/${num}-stub.open.md" <<EOF
|
|
95
|
+
# Problem ${num}: stub
|
|
96
|
+
|
|
97
|
+
**Status**: Open
|
|
98
|
+
|
|
99
|
+
## Description
|
|
100
|
+
|
|
101
|
+
stub
|
|
102
|
+
|
|
103
|
+
## Related
|
|
104
|
+
|
|
105
|
+
stub
|
|
106
|
+
EOF
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
run_post_bash_hook() {
|
|
110
|
+
local cmd="$1"
|
|
111
|
+
local json
|
|
112
|
+
json=$(printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$cmd")
|
|
113
|
+
echo "$json" | bash "$HOOK"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# ── Existence + executable ──────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
@test "hook exists" {
|
|
119
|
+
[ -f "$HOOK" ]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@test "hook is executable" {
|
|
123
|
+
[ -x "$HOOK" ]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ── Silent paths ────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
@test "non-Bash tool → silent (exit 0; no output)" {
|
|
129
|
+
json='{"tool_name":"Write","tool_input":{"file_path":"foo.txt","content":"x"}}'
|
|
130
|
+
run bash -c "echo '$json' | bash '$HOOK'"
|
|
131
|
+
[ "$status" -eq 0 ]
|
|
132
|
+
[ -z "$output" ]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@test "non-commit Bash command → silent" {
|
|
136
|
+
run run_post_bash_hook "git status"
|
|
137
|
+
[ "$status" -eq 0 ]
|
|
138
|
+
[ -z "$output" ]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@test "BYPASS_RFC_TRAILER_ADVISORY=1 → silent regardless of drift" {
|
|
142
|
+
write_rfc "001" "foo" "accepted"
|
|
143
|
+
write_problem_without_rfcs_section "168"
|
|
144
|
+
echo "x" > stub.txt
|
|
145
|
+
git add stub.txt
|
|
146
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub" -m "" -m "Refs: RFC-001"
|
|
147
|
+
BYPASS_RFC_TRAILER_ADVISORY=1 run run_post_bash_hook "git commit -m foo"
|
|
148
|
+
[ "$status" -eq 0 ]
|
|
149
|
+
[ -z "$output" ]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@test "outside git work tree → silent" {
|
|
153
|
+
cd "$ORIG_DIR"
|
|
154
|
+
TMP_NONGIT=$(mktemp -d)
|
|
155
|
+
cd "$TMP_NONGIT"
|
|
156
|
+
run run_post_bash_hook "git commit -m foo"
|
|
157
|
+
[ "$status" -eq 0 ]
|
|
158
|
+
[ -z "$output" ]
|
|
159
|
+
cd "$ORIG_DIR"
|
|
160
|
+
rm -rf "$TMP_NONGIT"
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@test "no docs/rfcs/ → silent (project has not adopted RFC framework)" {
|
|
164
|
+
rm -rf docs/rfcs
|
|
165
|
+
echo "x" > stub.txt
|
|
166
|
+
git add stub.txt
|
|
167
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub"
|
|
168
|
+
run run_post_bash_hook "git commit -m foo"
|
|
169
|
+
[ "$status" -eq 0 ]
|
|
170
|
+
[ -z "$output" ]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@test "no docs/problems/ → silent (project has not adopted problem framework)" {
|
|
174
|
+
rm -rf docs/problems
|
|
175
|
+
echo "x" > stub.txt
|
|
176
|
+
git add stub.txt
|
|
177
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub"
|
|
178
|
+
run run_post_bash_hook "git commit -m foo"
|
|
179
|
+
[ "$status" -eq 0 ]
|
|
180
|
+
[ -z "$output" ]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@test "commit without Refs: RFC trailer → silent" {
|
|
184
|
+
write_rfc "001" "foo" "accepted"
|
|
185
|
+
write_problem_without_rfcs_section "168"
|
|
186
|
+
echo "x" > stub.txt
|
|
187
|
+
git add stub.txt
|
|
188
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: no trailer"
|
|
189
|
+
run run_post_bash_hook "git commit -m foo"
|
|
190
|
+
[ "$status" -eq 0 ]
|
|
191
|
+
[ -z "$output" ]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# ── Advisory paths ──────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
@test "Refs: RFC-NNN trailer + stale problem (no ## RFCs section) → stderr advisory" {
|
|
197
|
+
write_rfc "001" "foo" "accepted"
|
|
198
|
+
write_problem_without_rfcs_section "168"
|
|
199
|
+
echo "x" > stub.txt
|
|
200
|
+
git add stub.txt
|
|
201
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub" -m "" -m "Refs: RFC-001"
|
|
202
|
+
run run_post_bash_hook "git commit -m foo"
|
|
203
|
+
[ "$status" -eq 0 ]
|
|
204
|
+
[[ "$output" == *"RFC-001"* ]]
|
|
205
|
+
[[ "$output" == *"P168"* ]]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@test "Refs: RFC-NNN trailer + stale problem (## RFCs section missing this RFC) → stderr advisory" {
|
|
209
|
+
write_rfc "001" "foo" "accepted"
|
|
210
|
+
write_problem_with_rfcs_section "168" "| RFC-002 | proposed | other |"
|
|
211
|
+
echo "x" > stub.txt
|
|
212
|
+
git add stub.txt
|
|
213
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub" -m "" -m "Refs: RFC-001"
|
|
214
|
+
run run_post_bash_hook "git commit -m foo"
|
|
215
|
+
[ "$status" -eq 0 ]
|
|
216
|
+
[[ "$output" == *"RFC-001"* ]]
|
|
217
|
+
[[ "$output" == *"P168"* ]]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@test "Refs: RFC-NNN trailer + current problem ## RFCs (RFC listed) → silent" {
|
|
221
|
+
write_rfc "001" "foo" "accepted"
|
|
222
|
+
write_problem_with_rfcs_section "168" "| RFC-001 | accepted | foo |"
|
|
223
|
+
echo "x" > stub.txt
|
|
224
|
+
git add stub.txt
|
|
225
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub" -m "" -m "Refs: RFC-001"
|
|
226
|
+
run run_post_bash_hook "git commit -m foo"
|
|
227
|
+
[ "$status" -eq 0 ]
|
|
228
|
+
[ -z "$output" ]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# ── Multi-RFC malformed-per-finding-8 ──────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
@test "multiple Refs: RFC trailers → malformed advisory (architect Q4 / finding 8)" {
|
|
234
|
+
write_rfc "001" "foo" "accepted"
|
|
235
|
+
write_rfc "002" "bar" "accepted"
|
|
236
|
+
write_problem_with_rfcs_section "168" "| RFC-001 | accepted | foo |"
|
|
237
|
+
echo "x" > stub.txt
|
|
238
|
+
git add stub.txt
|
|
239
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub" -m "" -m "Refs: RFC-001
|
|
240
|
+
Refs: RFC-002"
|
|
241
|
+
run run_post_bash_hook "git commit -m foo"
|
|
242
|
+
[ "$status" -eq 0 ]
|
|
243
|
+
# The advisory names finding-8 / split / mis-scoped vocabulary.
|
|
244
|
+
[[ "$output" == *"finding-8"* || "$output" == *"split"* || "$output" == *"mis-scoped"* ]]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# ── Trailer to non-existent RFC ──────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
@test "Refs: RFC trailer with no matching file → silent (RFC may be in flight elsewhere)" {
|
|
250
|
+
write_problem_without_rfcs_section "168"
|
|
251
|
+
echo "x" > stub.txt
|
|
252
|
+
git add stub.txt
|
|
253
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub" -m "" -m "Refs: RFC-999"
|
|
254
|
+
run run_post_bash_hook "git commit -m foo"
|
|
255
|
+
[ "$status" -eq 0 ]
|
|
256
|
+
# Fail-open: missing RFC files don't promote to advisory (could be capture-rfc invocation in flight).
|
|
257
|
+
[ -z "$output" ]
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
# ── Advisory budget ─────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
@test "advisory message stays within ADR-045 advisory band (≤300 bytes)" {
|
|
263
|
+
write_rfc "001" "byte-budget-test-with-an-extra-long-slug-to-stress-row-width" "accepted"
|
|
264
|
+
write_problem_without_rfcs_section "168"
|
|
265
|
+
echo "x" > stub.txt
|
|
266
|
+
git add stub.txt
|
|
267
|
+
git -c commit.gpgsign=false commit --quiet -m "feat: stub" -m "" -m "Refs: RFC-001"
|
|
268
|
+
run run_post_bash_hook "git commit -m foo"
|
|
269
|
+
[ "$status" -eq 0 ]
|
|
270
|
+
[ -n "$output" ]
|
|
271
|
+
# Permit per-line overhead; the advisory should remain readable.
|
|
272
|
+
[ "${#output}" -le 600 ]
|
|
273
|
+
}
|
|
@@ -22,7 +22,7 @@ setup() {
|
|
|
22
22
|
ORIG_DIR="$PWD"
|
|
23
23
|
TEST_DIR=$(mktemp -d)
|
|
24
24
|
cd "$TEST_DIR"
|
|
25
|
-
mkdir -p docs/problems
|
|
25
|
+
mkdir -p docs/problems docs/rfcs
|
|
26
26
|
SID="mp-create-test-$$-$RANDOM"
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -30,6 +30,11 @@ teardown() {
|
|
|
30
30
|
cd "$ORIG_DIR"
|
|
31
31
|
rm -rf "$TEST_DIR"
|
|
32
32
|
rm -f "/tmp/manage-problem-grep-${SID}"
|
|
33
|
+
rm -f "/tmp/wr-itil-rfc-capture-grep-${SID}"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set_rfc_marker() {
|
|
37
|
+
: > "/tmp/wr-itil-rfc-capture-grep-${SID}"
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
# Helper: run the hook with mock JSON for a Write tool call to file_path
|
|
@@ -241,3 +246,102 @@ teardown_other_sid_marker() {
|
|
|
241
246
|
[[ "$output" != *"SID mismatch"* ]]
|
|
242
247
|
[[ "$output" != *"Step 2 substep 7"* ]]
|
|
243
248
|
}
|
|
249
|
+
|
|
250
|
+
# --- P170 / ADR-060: RFC tier extension ---
|
|
251
|
+
#
|
|
252
|
+
# The hook gate covers both docs/problems/ and docs/rfcs/. Each tier has its
|
|
253
|
+
# own marker (problems: /tmp/manage-problem-grep-${SID};
|
|
254
|
+
# rfcs: /tmp/wr-itil-rfc-capture-grep-${SID}) and its own deny message
|
|
255
|
+
# pointing to the right skill (manage-problem vs capture-rfc).
|
|
256
|
+
|
|
257
|
+
@test "rfcs deny: Write to new docs/rfcs/RFC-001-foo.proposed.md without RFC marker" {
|
|
258
|
+
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
259
|
+
[ "$status" -eq 0 ]
|
|
260
|
+
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
261
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@test "rfcs deny message names capture-rfc skill (not manage-problem)" {
|
|
265
|
+
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
266
|
+
[ "$status" -eq 0 ]
|
|
267
|
+
[[ "$output" == *"/wr-itil:capture-rfc"* ]]
|
|
268
|
+
[[ "$output" != *"/wr-itil:manage-problem"* ]]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@test "rfcs deny message names the I1 trace-to-problem invariant + ADR-060" {
|
|
272
|
+
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
273
|
+
[ "$status" -eq 0 ]
|
|
274
|
+
[[ "$output" == *"problem-trace"* ]]
|
|
275
|
+
[[ "$output" == *"ADR-060"* ]]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@test "rfcs allow: Write to new docs/rfcs/RFC-001-foo.proposed.md WITH RFC marker" {
|
|
279
|
+
set_rfc_marker
|
|
280
|
+
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
281
|
+
[ "$status" -eq 0 ]
|
|
282
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
283
|
+
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@test "rfcs allow: Write to docs/rfcs/README.md regardless of marker (chicken-and-egg)" {
|
|
287
|
+
run run_write_hook "$PWD/docs/rfcs/README.md" "$SID"
|
|
288
|
+
[ "$status" -eq 0 ]
|
|
289
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@test "rfcs allow: Write to docs/rfcs/ non-RFC basename (e.g. NOTES.md) regardless of marker" {
|
|
293
|
+
run run_write_hook "$PWD/docs/rfcs/NOTES.md" "$SID"
|
|
294
|
+
[ "$status" -eq 0 ]
|
|
295
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@test "rfcs deny: Write across all RFC lifecycle suffixes without marker (proposed/accepted/in-progress/verifying/closed)" {
|
|
299
|
+
for suffix in proposed accepted in-progress verifying closed; do
|
|
300
|
+
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.${suffix}.md" "$SID"
|
|
301
|
+
[ "$status" -eq 0 ]
|
|
302
|
+
[[ "$output" == *"BLOCKED"* ]] || { echo "expected deny for suffix=$suffix"; return 1; }
|
|
303
|
+
done
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@test "rfcs marker independence: problem marker does NOT unlock RFC writes" {
|
|
307
|
+
# The two markers are siblings, not interchangeable. Setting the
|
|
308
|
+
# problem-tier marker should NOT bypass the RFC-tier gate.
|
|
309
|
+
set_marker
|
|
310
|
+
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
311
|
+
[ "$status" -eq 0 ]
|
|
312
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
313
|
+
[[ "$output" == *"capture-rfc"* ]]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@test "problems marker independence: RFC marker does NOT unlock problem writes" {
|
|
317
|
+
# Inverse direction — preserves audit-trail per-surface granularity.
|
|
318
|
+
set_rfc_marker
|
|
319
|
+
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
320
|
+
[ "$status" -eq 0 ]
|
|
321
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
322
|
+
[[ "$output" == *"manage-problem"* ]]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@test "rfcs allow: existing RFC file (overwrite) regardless of marker" {
|
|
326
|
+
echo "stub" > docs/rfcs/RFC-001-foo.proposed.md
|
|
327
|
+
run run_write_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
328
|
+
[ "$status" -eq 0 ]
|
|
329
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@test "rfcs allow: Edit (not Write) regardless of marker" {
|
|
333
|
+
echo "stub" > docs/rfcs/RFC-001-foo.proposed.md
|
|
334
|
+
run run_edit_hook "$PWD/docs/rfcs/RFC-001-foo.proposed.md" "$SID"
|
|
335
|
+
[ "$status" -eq 0 ]
|
|
336
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
@test "rfcs deny: bare numeric basename (no RFC- prefix) does NOT match the rfcs gate" {
|
|
340
|
+
# Defensive: a docs/rfcs/123-foo.proposed.md (no RFC- prefix) is not
|
|
341
|
+
# an RFC by naming convention; it should NOT trigger the gate.
|
|
342
|
+
# (If the user accidentally creates such a file, it's project
|
|
343
|
+
# housekeeping, not a ticket.)
|
|
344
|
+
run run_write_hook "$PWD/docs/rfcs/123-no-prefix.proposed.md" "$SID"
|
|
345
|
+
[ "$status" -eq 0 ]
|
|
346
|
+
[[ "$output" != *"BLOCKED"* ]]
|
|
347
|
+
}
|
package/package.json
CHANGED