@windyroad/itil 0.35.7-preview.387 → 0.35.8-preview.389

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.
@@ -484,5 +484,5 @@
484
484
  }
485
485
  },
486
486
  "name": "wr-itil",
487
- "version": "0.35.7"
487
+ "version": "0.35.8"
488
488
  }
@@ -92,8 +92,10 @@ command_invokes_git_commit "$COMMAND" || exit 0
92
92
 
93
93
  # Run detection. Helper echoes offending ticket path on stdout when
94
94
  # detected; returns 1 in that case. Returns 0 (allow) on no-trap,
95
- # bypass env, or fail-open (non-git tree, parse error).
96
- TRAPPED_TICKET=$(detect_readme_refresh_required 2>/dev/null) && exit 0
95
+ # bypass env, a registered RISK_BYPASS commit-message trailer (P265
96
+ # `$COMMAND` is threaded in so the helper can inspect the trailer), or
97
+ # fail-open (non-git tree, parse error).
98
+ TRAPPED_TICKET=$(detect_readme_refresh_required "$COMMAND" 2>/dev/null) && exit 0
97
99
 
98
100
  # Extract the leading ticket-ID digits from the basename so the deny
99
101
  # names the ticket as `P<NNN>` rather than the full descriptive path
@@ -42,6 +42,16 @@
42
42
  # `.claude/settings.json` env field or shell `export` before
43
43
  # launching `claude` — inline-prefix syntax (`VAR=1 git commit ...`)
44
44
  # does NOT propagate from a Bash subshell to PreToolUse hooks (P173).
45
+ # - Registered `RISK_BYPASS: <token>` commit-message trailer (P265) →
46
+ # return 0 (allow). Narrow allow-list (currently only
47
+ # `adr-031-migration`, the standalone ADR-031 layout-migration
48
+ # commit, which is a rename-only change that legitimately stages no
49
+ # README refresh). The trailer is read from the live `git commit`
50
+ # command string at PreToolUse time (the commit message is not yet
51
+ # written), matching the sibling `risk-score-commit-gate.sh`
52
+ # recognition (P170 T11) so one logical migration commit clears both
53
+ # gates. Registry of record: ADR-014 commit-message bypass-token
54
+ # table.
45
55
  #
46
56
  # Narrative-only short-circuit (P230):
47
57
  # - When all staged ticket edits are purely narrative — no
@@ -101,21 +111,74 @@
101
111
  # shape — per-invocation deterministic, no markers).
102
112
  # P141 — sibling changeset-discipline helper (same shape).
103
113
  # P165 — this helper.
114
+ # P265 — RISK_BYPASS trailer allow-list bypass (this addition).
115
+
116
+ # Allow-list of registered RISK_BYPASS commit-message trailer tokens
117
+ # (P265). A policy-authorised commit may carry `RISK_BYPASS: <token>` in
118
+ # its message body; when <token> is registered here, the README-refresh
119
+ # gate allows the commit even though no README refresh is staged. The
120
+ # allow-list keeps the bypass narrow and auditable — a generic
121
+ # `RISK_BYPASS:` match would let any commit self-exempt.
122
+ #
123
+ # Registered tokens:
124
+ # adr-031-migration — the standalone per-state-subdir layout-migration
125
+ # commit written by lib/migrate-problems-layout.sh. It is a pure
126
+ # rename (no README content change — the table references tickets by
127
+ # ID, not path), so requiring a README refresh would deadlock the
128
+ # migration (P265). The same token clears the sibling
129
+ # risk-score-commit-gate.sh (P170 T11); both gates recognise it via
130
+ # the identical grep below so one logical migration commit clears
131
+ # both. Registry of record: ADR-014 commit-message bypass-token table.
132
+ _README_REFRESH_BYPASS_TRAILERS=("adr-031-migration")
133
+
134
+ # Returns 0 if the given `git commit` command string carries a
135
+ # registered RISK_BYPASS trailer from the allow-list above. The grep
136
+ # pattern is kept byte-identical to risk-score-commit-gate.sh so both
137
+ # commit gates recognise the token the same way (P265 architect verdict).
138
+ _readme_refresh_command_has_bypass_trailer() {
139
+ local command="${1:-}"
140
+ [ -n "$command" ] || return 1
141
+ local token
142
+ for token in "${_README_REFRESH_BYPASS_TRAILERS[@]}"; do
143
+ if printf '%s' "$command" \
144
+ | grep -qE "RISK_BYPASS:[[:space:]]*${token}([^A-Za-z0-9_-]|\$)"; then
145
+ return 0
146
+ fi
147
+ done
148
+ return 1
149
+ }
104
150
 
105
151
  # Detect whether the current staged set requires a README refresh that
106
152
  # is not staged.
107
153
  #
154
+ # $1 (optional) — the `git commit` command string. Inspected for a
155
+ # registered RISK_BYPASS trailer (P265). Empty/absent → no trailer
156
+ # bypass (fail-safe; preserves pre-P265 behaviour for any caller that
157
+ # does not thread the command through).
158
+ #
108
159
  # Echoes the offending ticket path on stdout when detected.
109
160
  #
110
161
  # Returns:
111
- # 0 — no change required, or BYPASS env set, or fail-open (allow)
162
+ # 0 — no change required, BYPASS env set, a registered RISK_BYPASS
163
+ # trailer is present, or fail-open (allow)
112
164
  # 1 — ticket change staged + README not staged (caller should deny)
113
165
  detect_readme_refresh_required() {
166
+ local command="${1:-}"
167
+
114
168
  # Bypass via env var — single most-common legitimate escape.
115
169
  if [ "${BYPASS_README_REFRESH_GATE:-}" = "1" ]; then
116
170
  return 0
117
171
  fi
118
172
 
173
+ # Bypass via registered RISK_BYPASS commit-message trailer (P265).
174
+ # The ADR-031 layout-migration commit is a rename-only change that
175
+ # legitimately stages no README refresh; its `RISK_BYPASS:
176
+ # adr-031-migration` trailer carries the policy authorisation
177
+ # (ADR-031 § Backward Compatibility + ADR-013 Rule 6).
178
+ if _readme_refresh_command_has_bypass_trailer "$command"; then
179
+ return 0
180
+ fi
181
+
119
182
  # Fail-open if not inside a git working tree.
120
183
  git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
121
184
 
@@ -526,3 +526,58 @@ EOF
526
526
  [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
527
527
  [ "${#output}" -eq 0 ]
528
528
  }
529
+
530
+ # --- P265: RISK_BYPASS commit-message trailer allow-list bypass ---
531
+ #
532
+ # The ADR-031 layout-migration commit (lib/migrate-problems-layout.sh)
533
+ # is a pure rename (flat docs/problems/NNN-*.<state>.md → per-state
534
+ # subdir docs/problems/<state>/NNN-*.md) that legitimately stages NO
535
+ # README refresh — the rename does not change README content (the table
536
+ # references tickets by ID, not path). Its `RISK_BYPASS: adr-031-migration`
537
+ # trailer (written via sequential `-m` paragraphs, so the literal token
538
+ # appears in the `git commit` command argv) carries the policy
539
+ # authorisation (ADR-031 § Backward Compatibility + ADR-013 Rule 6).
540
+ # The hook must allow such commits silently. The bypass is an allow-list:
541
+ # only the registered token bypasses; an unregistered RISK_BYPASS token
542
+ # still denies. The recognition grep is kept identical to the sibling
543
+ # risk-score-commit-gate.sh (P170 T11 precedent) so both commit gates
544
+ # recognise the token the same way.
545
+
546
+ @test "P265 allow: migration rename + RISK_BYPASS: adr-031-migration trailer → allow silently" {
547
+ echo "# Problem 999 flat" > docs/problems/999-mig.open.md
548
+ git add docs/problems/999-mig.open.md
549
+ git -c commit.gpgsign=false commit --quiet -m "seed flat ticket"
550
+ git mv docs/problems/999-mig.open.md docs/problems/open/999-mig.md
551
+ run run_bash_hook "git commit -m 'docs(problems): auto-migrate to per-state subdirectory layout (ADR-031)' -m 'RISK_BYPASS: adr-031-migration'"
552
+ [ "$status" -eq 0 ]
553
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
554
+ # Bypass is an allow path — silent per ADR-045 Pattern 1.
555
+ [ "${#output}" -eq 0 ]
556
+ }
557
+
558
+ @test "P265 deny: same migration rename WITHOUT the trailer still denies (negative control)" {
559
+ echo "# Problem 999 flat" > docs/problems/999-mig.open.md
560
+ git add docs/problems/999-mig.open.md
561
+ git -c commit.gpgsign=false commit --quiet -m "seed flat ticket"
562
+ git mv docs/problems/999-mig.open.md docs/problems/open/999-mig.md
563
+ run run_bash_hook "git commit -m 'docs(problems): auto-migrate'"
564
+ [ "$status" -eq 0 ]
565
+ [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
566
+ }
567
+
568
+ @test "P265 deny: unregistered RISK_BYPASS token does NOT bypass (allow-list scope)" {
569
+ echo "# Problem 999" > docs/problems/open/999-x.md
570
+ git add docs/problems/open/999-x.md
571
+ run run_bash_hook "git commit -m 'feat' -m 'RISK_BYPASS: some-other-thing'"
572
+ [ "$status" -eq 0 ]
573
+ [[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
574
+ }
575
+
576
+ @test "P265 allow: registered trailer bypasses a newly-staged ticket too (bypass is staged-shape-agnostic)" {
577
+ echo "# Problem 999" > docs/problems/open/999-x.md
578
+ git add docs/problems/open/999-x.md
579
+ run run_bash_hook "git commit -m 'docs(problems): auto-migrate' -m 'RISK_BYPASS: adr-031-migration'"
580
+ [ "$status" -eq 0 ]
581
+ [[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
582
+ [ "${#output}" -eq 0 ]
583
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.35.7-preview.387",
3
+ "version": "0.35.8-preview.389",
4
4
  "description": "ITIL-aligned IT service management for Claude Code (problem, and future incident/change skills)",
5
5
  "bin": {
6
6
  "windyroad-itil": "./bin/install.mjs"