@windyroad/itil 0.47.10-preview.583 → 0.47.11-preview.587
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windyroad/itil",
|
|
3
|
-
"version": "0.47.
|
|
3
|
+
"version": "0.47.11-preview.587",
|
|
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"
|
|
@@ -401,10 +401,11 @@ Open the issue:
|
|
|
401
401
|
gh issue create \
|
|
402
402
|
--repo "${UPSTREAM_OWNER_REPO}" \
|
|
403
403
|
--title "${TITLE_PREFIXED_BY_TEMPLATE}" \
|
|
404
|
-
--body "${FILLED_BODY}"
|
|
405
|
-
--label "${MATCHED_TEMPLATE_LABEL_IF_ANY}"
|
|
404
|
+
--body "${FILLED_BODY}"
|
|
406
405
|
```
|
|
407
406
|
|
|
407
|
+
Do **not** pass `--label` on this call (P207). Labels are supplied by the matched template's YAML `labels:` frontmatter and applied by GitHub when the issue form is submitted; passing `--label <name>` for a label that has not been pre-created on the upstream repo causes `gh issue create` to hard-fail with `could not add label: '<name>' not found`. The flag is redundant when the matched template carries `labels:` and a hard-fail surface when it does not. If the upstream has no matched template at all (structured-default body path, Step 3 preference order item 7), omit labels entirely — leave triage to the upstream maintainer's existing routing.
|
|
408
|
+
|
|
408
409
|
Capture the returned issue URL. The voice-tone gate per ADR-028 may delegate-and-retry; treat this as expected (see "Voice-tone gate interaction" above). Proceed to Step 7 once the issue is created.
|
|
409
410
|
|
|
410
411
|
### 5c. Comment path (P070)
|
|
@@ -511,7 +511,7 @@ rm -f "$ITER_JSON"
|
|
|
511
511
|
|
|
512
512
|
1. **Context**: this is one iteration of the AFK work-problems loop. The user is AFK. The orchestrator selected `P<NNN> (<title>)` as the highest-WSJF actionable ticket.
|
|
513
513
|
2. **Task**: apply the `/wr-itil:manage-problem` workflow for `work highest WSJF problem that can be progressed non-interactively as the user is AFK`. Follow manage-problem SKILL.md verbatim, including architect / jtbd / style-guide / voice-tone gate reviews and the commit gate (manage-problem Step 11). Because this subprocess has the Agent tool in its own surface, the normal review-via-subagent paths work — no inline-verdict fallback needed.
|
|
514
|
-
3. **Constraints**: commit the completed work per ADR-014. Do NOT push, do NOT run `push:watch`, do NOT run `release:watch` — the orchestrator's Step 6.5 owns release cadence. Do NOT invoke `capture-*` background skills mid-iter (AFK carve-out — ADR-032), **EXCEPT for retro-surfaced observations of recurring class-of-behaviour** — those route to `/wr-itil:capture-problem` per the **P342 mechanical-stage carve-out** (see retro-on-exit constraint #4 below; same trust-boundary as `/wr-retrospective:run-retro` Step 4a verification close-on-evidence — P342). Do NOT use `ScheduleWakeup` under any circumstance (P083 — iteration workers must not self-reschedule). **NEVER call `AskUserQuestion` mid-loop in AFK** (P135 / ADR-044): direction / deviation-approval / one-time-override / silent-framework observations queue at `ITERATION_SUMMARY.outstanding_questions` for loop-end batched presentation. **This includes the manage-problem substance-confirm-before-build guard (ADR-074 (Confirm a decision's substance before building dependent work)):** when the propose-fix step detects that the fix builds on a born-`proposed` decision whose substance is unconfirmed (via `wr-architect-is-decision-unconfirmed`), the iter does NOT implement on it and does NOT ask mid-loop — it queues a `category: "direction"` entry naming the unconfirmed ADR + its Decision Outcome for loop-end confirmation, and routes the ticket to `action: skipped`, `skip_reason_category: user-answerable`. Building on the unconfirmed substance instead (or guessing the choice) is the P315 failure this guard exists to prevent. The queued substance-confirm is a legitimate cat-1 direction ask — it is NOT counted as lazy in the Step 2d Ask Hygiene Pass (ADR-074 lazy-count exclusion). Per-iter `AskUserQuestion` calls are sub-contracting framework-resolved decisions back to the user (lazy deferral per Step 2d Ask Hygiene Pass classification). Non-interactive defaults apply per ADR-013 Rule 6 + ADR-044's framework-resolution boundary. **Treat the user as transient** (P130): even when observably present at orchestrator dispatch time, the user may answer one question and disappear for hours; presence is not a reliable signal and is not the goal. The iter's job is to progress the ticket and accumulate questions for batched surfacing — not to ask "is it OK to proceed?" at a mechanical-stage boundary. **Do NOT poll `bats` output with a bats-console-summary regex against TAP-format output** (P146 — bash until-loop-deadlock antipattern). The bats-console-summary line `<N> tests, <M> failures` is emitted ONLY by bats's *default* (non-TAP) formatter; `bats --tap` does not emit a console summary, so a polling loop of shape `until [ -f $OUT ] && grep -qE '^[0-9]+ tests?,' $OUT; do sleep 5; done` spins forever after bats completes (silent deadlock — no error, no exit; recovery requires manual SIGTERM with metadata loss per the P146/P147 stuck-before-emit subclass). When you need to wait on a backgrounded bats run, prefer `wait $bg_pid` (Unix idiom — completion signaled by process exit, no regex required) or, for the Bash tool, `run_in_background=true` + `BashOutput` polling on the tool's exit-state field rather than regex-poll on stdout. If you genuinely must regex-poll TAP output, anchor on the TAP plan line `^[0-9]+\.\.[0-9]+` (e.g. `1..1455`) — TAP's plan line is emitted on completion and is format-stable across bats versions; the bats-console-summary line is not. The console-summary vs TAP-format divergence is the load-bearing detail: `bats` and `bats --tap` produce structurally different stdout, and the antipattern assumes the former when iter dispatch typically uses the latter. **Do NOT poll subprocess completion with `pgrep -f '<pattern>'` inside an `until` / `while` loop** (P232 — self-referential pgrep deadlock; sibling variant of P146). `pgrep -f` matches against the FULL command line of every running process, so the polling loop's own `zsh -c` argument (which contains the literal `pgrep -f '<pattern>'` text) matches itself; with multiple concurrent polling loops, each loop matches the others and spins forever. Worked example of the antipattern: `until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done` — the 2026-05-16 P232 deadlock witness; 4 concurrent polling loops each matched the others' command lines while no actual bats process ran; 45 min wall-clock + $20-30 wasted before manual SIGTERM. The same self-reference shape applies to `while pgrep -f ...; do sleep; done` and to `until ! pkill -0 -f '<pattern>'` / `while pkill -0 -f '<pattern>'` (signal-0 polling). The structural fix is the same as P146: prefer `wait $bg_pid` (Unix idiom — shell-native completion signal, no regex / no pgrep) or Bash-tool `run_in_background=true` + `BashOutput` polling (harness-tracked completion state). The hook `packages/itil/hooks/itil-bash-polling-antipattern-detect.sh` denies these shapes at PreToolUse:Bash, but the prompt rule belongs here too — structural enforcement + prompt discipline together close the class.
|
|
514
|
+
3. **Constraints**: commit the completed work per ADR-014. Do NOT push, do NOT run `push:watch`, do NOT run `release:watch` — the orchestrator's Step 6.5 owns release cadence. Do NOT invoke `capture-*` background skills mid-iter (AFK carve-out — ADR-032), **EXCEPT for retro-surfaced observations of recurring class-of-behaviour** — those route to `/wr-itil:capture-problem` per the **P342 mechanical-stage carve-out** (see retro-on-exit constraint #4 below; same trust-boundary as `/wr-retrospective:run-retro` Step 4a verification close-on-evidence — P342). Do NOT use `ScheduleWakeup` under any circumstance (P083 — iteration workers must not self-reschedule). **NEVER call `AskUserQuestion` mid-loop in AFK** (P135 / ADR-044): direction / deviation-approval / one-time-override / silent-framework observations queue at `ITERATION_SUMMARY.outstanding_questions` for loop-end batched presentation. **This includes the manage-problem substance-confirm-before-build guard (ADR-074 (Confirm a decision's substance before building dependent work)):** when the propose-fix step detects that the fix builds on a born-`proposed` decision whose substance is unconfirmed (via `wr-architect-is-decision-unconfirmed`), the iter does NOT implement on it and does NOT ask mid-loop — it queues a `category: "direction"` entry naming the unconfirmed ADR + its Decision Outcome for loop-end confirmation, and routes the ticket to `action: skipped`, `skip_reason_category: user-answerable`. Building on the unconfirmed substance instead (or guessing the choice) is the P315 failure this guard exists to prevent. The queued substance-confirm is a legitimate cat-1 direction ask — it is NOT counted as lazy in the Step 2d Ask Hygiene Pass (ADR-074 lazy-count exclusion). Per-iter `AskUserQuestion` calls are sub-contracting framework-resolved decisions back to the user (lazy deferral per Step 2d Ask Hygiene Pass classification). Non-interactive defaults apply per ADR-013 Rule 6 + ADR-044's framework-resolution boundary. **Treat the user as transient** (P130): even when observably present at orchestrator dispatch time, the user may answer one question and disappear for hours; presence is not a reliable signal and is not the goal. The iter's job is to progress the ticket and accumulate questions for batched surfacing — not to ask "is it OK to proceed?" at a mechanical-stage boundary. **Do NOT poll `bats` output with a bats-console-summary regex against TAP-format output** (P146 — bash until-loop-deadlock antipattern). The bats-console-summary line `<N> tests, <M> failures` is emitted ONLY by bats's *default* (non-TAP) formatter; `bats --tap` does not emit a console summary, so a polling loop of shape `until [ -f $OUT ] && grep -qE '^[0-9]+ tests?,' $OUT; do sleep 5; done` spins forever after bats completes (silent deadlock — no error, no exit; recovery requires manual SIGTERM with metadata loss per the P146/P147 stuck-before-emit subclass). When you need to wait on a backgrounded bats run, prefer `wait $bg_pid` (Unix idiom — completion signaled by process exit, no regex required) or, for the Bash tool, `run_in_background=true` + `BashOutput` polling on the tool's exit-state field rather than regex-poll on stdout. If you genuinely must regex-poll TAP output, anchor on the TAP plan line `^[0-9]+\.\.[0-9]+` (e.g. `1..1455`) — TAP's plan line is emitted on completion and is format-stable across bats versions; the bats-console-summary line is not. The console-summary vs TAP-format divergence is the load-bearing detail: `bats` and `bats --tap` produce structurally different stdout, and the antipattern assumes the former when iter dispatch typically uses the latter. **Do NOT poll subprocess completion with `pgrep -f '<pattern>'` inside an `until` / `while` loop** (P232 — self-referential pgrep deadlock; sibling variant of P146). `pgrep -f` matches against the FULL command line of every running process, so the polling loop's own `zsh -c` argument (which contains the literal `pgrep -f '<pattern>'` text) matches itself; with multiple concurrent polling loops, each loop matches the others and spins forever. Worked example of the antipattern: `until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done` — the 2026-05-16 P232 deadlock witness; 4 concurrent polling loops each matched the others' command lines while no actual bats process ran; 45 min wall-clock + $20-30 wasted before manual SIGTERM. The same self-reference shape applies to `while pgrep -f ...; do sleep; done` and to `until ! pkill -0 -f '<pattern>'` / `while pkill -0 -f '<pattern>'` (signal-0 polling). The structural fix is the same as P146: prefer `wait $bg_pid` (Unix idiom — shell-native completion signal, no regex / no pgrep) or Bash-tool `run_in_background=true` + `BashOutput` polling (harness-tracked completion state). The hook `packages/itil/hooks/itil-bash-polling-antipattern-detect.sh` denies these shapes at PreToolUse:Bash, but the prompt rule belongs here too — structural enforcement + prompt discipline together close the class. **If the fix changes shippable code or package behaviour** (any path under `packages/<plugin>/{src,bin,hooks,skills,scripts,lib,agents}` excluding test paths — `test/`, `hooks/test/`, `scripts/test/` — and excluding `README.md` + `docs/*.md`), **the iter MUST author a `.changeset/*.md` entry in the same single ADR-014-grain commit as the fix** (the changeset names the bumping plugin via the YAML frontmatter `"@windyroad/<plugin>": <patch|minor|major>` per the changesets-action contract). **Doc-only changes** (under `docs/`, `*.md`) **and test-only changes** (under any `test/` path) **that ship no behaviour MAY omit the changeset**. The orchestrator's Step 6.5 release-cadence drain runs `release:watch` only when `.changeset/` is non-empty after push — without an iter-authored changeset, code-shape fixes accumulate without ever shipping to npm (violating JTBD-006's audit-trail expectation + JTBD-007's "Keep Plugins Current" closure dependency). Hook `packages/itil/hooks/itil-changeset-discipline.sh` (P141) provides hook-level enforcement at `git commit` time as defence-in-depth — but plugin hook execution depends on the marketplace cache carrying the current hook version, so the prompt-time constraint here MUST land independently (composes-with the hook; does NOT rely on the hook being installed). Inbound-reported from downstream consumer bbstats as their P195 — see [Related](#related) for `**Origin**: inbound-reported (bbstats#195)` per ADR-076. **`@jtbd JTBD-006`** (load-bearing) **`@jtbd JTBD-007`** (closure-dependent).
|
|
515
515
|
4. **Retro-on-exit (P086) + retro-surfaced observation classification (P342)**: before emitting `ITERATION_SUMMARY`, invoke `/wr-retrospective:run-retro`. Retro runs INSIDE this subprocess so its Step 2b pipeline-instability scan has access to the iteration's rich tool-call history (hook misbehaviour, repeat-workaround patterns, subagent-delegation friction, release-path instability). Retro may create tickets or update `docs/BRIEFING.md` — run-retro commits its own work per ADR-014; any tickets it creates ride into either the iteration's own commit (if retro runs before the main commit) or a retro-owned follow-up commit, and the orchestrator picks them up on the next Step 1 scan. Proceed to `ITERATION_SUMMARY` emission regardless of retro findings — retro is non-blocking at the iter-subprocess layer (do not block on retro): if retro fails or surfaces findings, the iteration still returns a summary so the AFK loop does not silently halt on a flaky retro run. (Session-level retro at the orchestrator-main-turn layer per Step 2.4 gate (b) IS load-bearing — distinct surface; see Step 2.4 prose for the orchestrator-layer halt semantics.)
|
|
516
516
|
|
|
517
517
|
**P342 classification taxonomy — retro-surfaced observations.** When the iter-retro's Step 4b Stage 1 surfaces a ticketable observation, the routing depends on classification:
|
|
@@ -1023,4 +1023,6 @@ When every skipped ticket is in the `upstream-blocked` category (stop-condition
|
|
|
1023
1023
|
- **ADR-022** (`docs/decisions/022-problem-verification-pending.proposed.md`) — iteration outcomes map into the return-summary's `outcome` field (`verifying` for a released fix, `known-error` for a root-cause-confirmed ticket awaiting release, etc.).
|
|
1024
1024
|
- **ADR-032** (`docs/decisions/032-governance-skill-invocation-patterns.proposed.md`) — pattern taxonomy parent; Step 5 implements the AFK iteration-isolation wrapper — subprocess-boundary variant per the P084 amendment (2026-04-21), refining the P077 Agent-tool amendment. The P077 amendment remains in the ADR as the historical Agent-tool variant; the subprocess variant is the lead for new adopters.
|
|
1025
1025
|
- **ADR-037** (`docs/decisions/037-skill-testing-strategy.proposed.md`) — doc-lint bats contract-assertion pattern used by `test/work-problems-step-5-delegation.bats`.
|
|
1026
|
-
- **
|
|
1026
|
+
- **P206** (`docs/problems/known-error/206-work-problems-iter-workers-dont-add-changesets-fix-commits-accumulate-without-release.md`) — driver for Step 5 iter-prompt-body's explicit "if the fix changes shippable code, author a `.changeset/*.md` in the same commit" constraint (composes defence-in-depth with hook P141's `git commit`-time enforcement). Inbound-reported by downstream consumer **bbstats** as their P195 (`**Origin**: inbound-reported (bbstats#195)` per ADR-076 sort tier). Behavioural second-source: `test/work-problems-step-5-iter-changeset-required.bats` (structural-permitted per ADR-052; tdd-review comment in fixture).
|
|
1027
|
+
- **P141** (`docs/problems/verifying/141-iter-prompt-time-reminder-misses-40-percent-of-publishable-iters-hook-level-enforcement.md`) — sibling hook (`packages/itil/hooks/itil-changeset-discipline.sh`) that enforces the changeset-discipline rule at `git commit` time. The Step 5 iter-prompt-body constraint composes-with this hook; the prompt-time rule is load-bearing because plugin-hook execution depends on the marketplace cache carrying the current hook version (a fresh-cache adopter without P141 still gets the constraint via the prompt).
|
|
1028
|
+
- **JTBD-001**, **JTBD-006**, **JTBD-007**, **JTBD-101**, **JTBD-201** — personas whose reliability expectations the iteration-isolation wrapper restores. JTBD-006 (Progress the Backlog While I'm Away) + JTBD-007 (Keep Plugins Current Across Projects) are the load-bearing pair for the P206 changeset-discipline constraint — JTBD-006 requires the audit trail to stay accurate at release boundary; JTBD-007's closure depends on fixes actually shipping to npm.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# P206 — work-problems Step 5 iteration-prompt-body must EXPLICITLY require
|
|
3
|
+
# AFK iter subprocesses to author a `.changeset/*.md` alongside any fix
|
|
4
|
+
# commit that ships shippable code. Hook P141 (itil-changeset-discipline.sh)
|
|
5
|
+
# enforces this at git-commit time but depends on the plugin marketplace
|
|
6
|
+
# cache being current. The prompt-time constraint composes defence-in-depth
|
|
7
|
+
# with the hook so iter subprocesses self-author the changeset rather than
|
|
8
|
+
# discovering the gap at commit time (or worse — slipping through when the
|
|
9
|
+
# hook is missing from the install).
|
|
10
|
+
#
|
|
11
|
+
# Reported as inbound from downstream consumer bbstats (their P195) on
|
|
12
|
+
# 2026-05-15; covered by ADR-076 Origin field tier.
|
|
13
|
+
#
|
|
14
|
+
# tdd-review: structural-permitted (justification: SKILL.md is the named
|
|
15
|
+
# contract document under ADR-052; behavioural alternative would require a
|
|
16
|
+
# synthetic `claude -p` iter dispatch harness that completes a fix commit
|
|
17
|
+
# and asserts `.changeset/*.md` co-presence — that harness sits outside the
|
|
18
|
+
# skill layer and depends on the Anthropic CLI binary. Same Permitted
|
|
19
|
+
# Exception precedent as `work-problems-step-5-delegation.bats:99-105` and
|
|
20
|
+
# the P083 / P086 / P089 ScheduleWakeup / retro / stdin-redirect fixtures
|
|
21
|
+
# in the same directory).
|
|
22
|
+
#
|
|
23
|
+
# @problem P206
|
|
24
|
+
# @problem P141
|
|
25
|
+
# @jtbd JTBD-006
|
|
26
|
+
# @jtbd JTBD-007
|
|
27
|
+
# @jtbd JTBD-001
|
|
28
|
+
#
|
|
29
|
+
# Cross-reference:
|
|
30
|
+
# P206 — this ticket (work-problems iter workers don't add changesets)
|
|
31
|
+
# P141 — sibling changeset-discipline hook (hook-level enforcement;
|
|
32
|
+
# prompt-time constraint composes defence-in-depth)
|
|
33
|
+
# bbstats#195 — inbound report from downstream consumer
|
|
34
|
+
# ADR-014 (governance skills commit their own work)
|
|
35
|
+
# ADR-018 (inter-iteration release cadence — changesets are the load-
|
|
36
|
+
# bearing input to ADR-020's auto-release path)
|
|
37
|
+
# ADR-052 (behavioural tests default; structural-permitted with comment)
|
|
38
|
+
# ADR-076 (inbound-reported problems rank ahead via sort tier — Origin
|
|
39
|
+
# field stamping)
|
|
40
|
+
# JTBD-006 (Progress the Backlog While I'm Away) — load-bearing
|
|
41
|
+
# JTBD-007 (Keep Plugins Current Across Projects) — closure depends on
|
|
42
|
+
# release actually shipping
|
|
43
|
+
|
|
44
|
+
setup() {
|
|
45
|
+
SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
46
|
+
SKILL_FILE="${SKILL_DIR}/SKILL.md"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@test "SKILL.md Step 5 iteration-prompt-body names changeset as required when fix changes shippable code (P206)" {
|
|
50
|
+
# The iteration-prompt-body must EXPLICITLY require iter subprocesses to
|
|
51
|
+
# author a `.changeset/*.md` alongside any fix commit that ships shippable
|
|
52
|
+
# code. Without the explicit constraint, iter subprocesses can complete
|
|
53
|
+
# fix commits that the orchestrator's Step 6.5 release-cadence drain
|
|
54
|
+
# then has nothing to release — fixes accumulate without an npm publish.
|
|
55
|
+
# The constraint must name `changeset` AND a "shippable code" qualifier
|
|
56
|
+
# so doc-only and test-only commits are correctly exempted.
|
|
57
|
+
run grep -niE "changeset.{0,200}(shippable|publishable|packages/<plugin>)" "$SKILL_FILE"
|
|
58
|
+
[ "$status" -eq 0 ]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@test "SKILL.md Step 5 iteration-prompt-body exempts doc-only and test-only changes from the changeset requirement (P206)" {
|
|
62
|
+
# Doc-only changes (e.g. JTBD edits, ADR edits, retro additions) and
|
|
63
|
+
# test-only changes (under `test/`) ship no behaviour — requiring a
|
|
64
|
+
# changeset for them would author noise CHANGELOG bullets per release.
|
|
65
|
+
# The constraint must name the exemption explicitly so iter subprocesses
|
|
66
|
+
# do not over-apply the rule.
|
|
67
|
+
run grep -niE "(doc.?only|docs.?only|test.?only).{0,80}(omit|may omit|no changeset|not required)|changeset.{0,80}(omit|exempt)" "$SKILL_FILE"
|
|
68
|
+
[ "$status" -eq 0 ]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@test "SKILL.md Step 5 iteration-prompt-body cites P141 as the defence-in-depth hook (P206)" {
|
|
72
|
+
# The prompt-time constraint composes with hook P141 (changeset-discipline).
|
|
73
|
+
# Naming P141 inline in the prompt-body makes the defence-in-depth
|
|
74
|
+
# composition self-documenting — a future contributor reading the prompt
|
|
75
|
+
# understands the hook is the second layer, not the only layer.
|
|
76
|
+
run grep -nE "P141" "$SKILL_FILE"
|
|
77
|
+
[ "$status" -eq 0 ]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@test "SKILL.md Related section cites P206 (this fix)" {
|
|
81
|
+
# ADR-037 + ADR-052 self-documenting contract: the contract document
|
|
82
|
+
# carries a Related section listing the problem tickets it satisfies.
|
|
83
|
+
# P206 closure depends on this citation landing.
|
|
84
|
+
run grep -nE "P206" "$SKILL_FILE"
|
|
85
|
+
[ "$status" -eq 0 ]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@test "SKILL.md Step 5 iteration-prompt-body cites bbstats#195 as the inbound source (P206 + ADR-076 Origin)" {
|
|
89
|
+
# ADR-076 reported-first tier requires the Origin field to be visible at
|
|
90
|
+
# the contract surface so future contributors understand the constraint
|
|
91
|
+
# was driven by external evidence, not internal speculation. The bbstats
|
|
92
|
+
# back-link is the audit anchor.
|
|
93
|
+
run grep -niE "bbstats.{0,20}195|bbstats/.{0,30}195|bbstats#195" "$SKILL_FILE"
|
|
94
|
+
[ "$status" -eq 0 ]
|
|
95
|
+
}
|