@windyroad/itil 0.61.1 → 0.61.2
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/package.json +7 -2
- package/agents/test/fixtures/proceed-new-genuinely-new.md +0 -43
- package/agents/test/fixtures/proceed-new-subtle-sibling.md +0 -47
- package/agents/test/fixtures/regression-p347-vs-p346.md +0 -54
- package/agents/test/hang-off-check.bats +0 -225
- package/hooks/test/block-list.bats +0 -153
- package/hooks/test/command-detect.bats +0 -181
- package/hooks/test/itil-assistant-output-gate.bats +0 -114
- package/hooks/test/itil-assistant-output-review.bats +0 -224
- package/hooks/test/itil-bash-polling-antipattern-detect.bats +0 -154
- package/hooks/test/itil-changeset-discipline.bats +0 -603
- package/hooks/test/itil-claude-space-protection.bats +0 -294
- package/hooks/test/itil-commit-trailer-transition-advisory.bats +0 -80
- package/hooks/test/itil-correction-detect.bats +0 -189
- package/hooks/test/itil-deferral-cadence-gate.bats +0 -187
- package/hooks/test/itil-fictional-defer-detect.bats +0 -292
- package/hooks/test/itil-fix-title-lifecycle-advisory.bats +0 -129
- package/hooks/test/itil-mid-loop-ask-detect.bats +0 -220
- package/hooks/test/itil-no-implement-draft-gate.bats +0 -210
- package/hooks/test/itil-pending-questions-surface.bats +0 -307
- package/hooks/test/itil-readme-refresh-discipline.bats +0 -642
- package/hooks/test/itil-rfc-oversight-nudge.bats +0 -83
- package/hooks/test/itil-rfc-trailer-advisory.bats +0 -375
- package/hooks/test/itil-story-mirror-migration-nudge.bats +0 -122
- package/hooks/test/manage-problem-enforce-create.bats +0 -424
- package/hooks/test/p057-staging-trap-detect.bats +0 -231
- package/hooks/test/pre-publish-intake-gate.bats +0 -144
- package/hooks/test/runtime-sid-marker.bats +0 -90
- package/hooks/test/session-id.bats +0 -370
- package/scripts/migrate-problems-add-type.sh +0 -116
- package/scripts/test/catchup-scan.bats +0 -274
- package/scripts/test/check-afk-accept-eligible.bats +0 -310
- package/scripts/test/check-fail-soft-skip-discipline.bats +0 -172
- package/scripts/test/check-fix-rfc-trace.bats +0 -164
- package/scripts/test/check-locale-discipline.bats +0 -291
- package/scripts/test/check-problems-readme-budget.bats +0 -192
- package/scripts/test/check-rfc-has-stories.bats +0 -69
- package/scripts/test/check-rfc-rejected-alternatives.bats +0 -108
- package/scripts/test/check-rfc-stories-ratified.bats +0 -82
- package/scripts/test/check-upstream-responses.bats +0 -503
- package/scripts/test/classify-readme-drift.bats +0 -392
- package/scripts/test/derive-first-dispatch.bats +0 -274
- package/scripts/test/derive-release-vehicle.bats +0 -454
- package/scripts/test/detect-unratified-stories-maps.bats +0 -60
- package/scripts/test/dual-tolerant-glob-rfc-002-t2.bats +0 -309
- package/scripts/test/effort-tally.bats +0 -170
- package/scripts/test/evaluate-relevance.bats +0 -773
- package/scripts/test/kind-a-commands.bats +0 -46
- package/scripts/test/mark-create-gate.bats +0 -59
- package/scripts/test/mark-story-oversight-confirmed.bats +0 -53
- package/scripts/test/migrate-problems-add-type.bats +0 -280
- package/scripts/test/migrate-story-status-mirror.bats +0 -167
- package/scripts/test/no-type-regression-guard.bats +0 -74
- package/scripts/test/oversight-mirror-corpus-lint.bats +0 -150
- package/scripts/test/plugin-exercise-index.bats +0 -386
- package/scripts/test/plugin-maturity-doc-lint.bats +0 -470
- package/scripts/test/plugin-maturity-populate.bats +0 -631
- package/scripts/test/plugin-maturity-render.bats +0 -469
- package/scripts/test/plugin-validate-ci-gate.bats +0 -208
- package/scripts/test/reconcile-readme.bats +0 -1007
- package/scripts/test/reconcile-rfcs.bats +0 -496
- package/scripts/test/reconcile-stories.bats +0 -173
- package/scripts/test/reconcile-story-maps.bats +0 -74
- package/scripts/test/release-watch-poll-loop.bats +0 -180
- package/scripts/test/resolve-governance-plugin-dirs.bats +0 -78
- package/scripts/test/rfc-stories-extension.bats +0 -175
- package/scripts/test/skill-invocations.bats +0 -535
- package/scripts/test/skill-md-dual-tolerant-coverage-rfc-002-t3.bats +0 -374
- package/scripts/test/story-oversight-lib.bats +0 -190
- package/scripts/test/update-jtbd-references-related-problems.bats +0 -178
- package/scripts/test/update-problem-references-section.bats +0 -242
- package/scripts/test/update-problem-rfcs-section.bats +0 -242
- package/scripts/test/update-references-section-sibling-helpers.bats +0 -80
- package/scripts/test/update-rfc-commits-section.bats +0 -77
- package/scripts/test/verify-iter-summary.bats +0 -196
- package/scripts/test/working-the-problem-traversal.bats +0 -116
- package/skills/capture-problem/test/capture-problem-step-1-5b-jtbd-trace.bats +0 -398
- package/skills/capture-problem/test/capture-problem.bats +0 -389
- package/skills/capture-story/test/capture-story-behavioural.bats +0 -317
- package/skills/capture-story-map/test/capture-story-map-behavioural.bats +0 -98
- package/skills/close-incident/test/close-incident-contract.bats +0 -146
- package/skills/link-incident/test/link-incident-contract.bats +0 -125
- package/skills/list-incidents/test/list-incidents-contract.bats +0 -115
- package/skills/list-problems/test/list-problems-contract.bats +0 -100
- package/skills/list-stories/test/list-stories-contract.bats +0 -127
- package/skills/list-story-maps/test/list-story-maps-contract.bats +0 -46
- package/skills/manage-incident/test/manage-incident-adr-044-contract.bats +0 -206
- package/skills/manage-incident/test/manage-incident-close-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-link-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-list-forwarder.bats +0 -68
- package/skills/manage-incident/test/manage-incident-mitigate-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident-restore-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident.bats +0 -171
- package/skills/manage-problem/test/manage-problem-adr-044-step4-derive-first.bats +0 -151
- package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +0 -53
- package/skills/manage-problem/test/manage-problem-concern-boundary.bats +0 -64
- package/skills/manage-problem/test/manage-problem-effort-buckets.bats +0 -56
- package/skills/manage-problem/test/manage-problem-external-root-cause-detection.bats +0 -119
- package/skills/manage-problem/test/manage-problem-first-run-intake-prompt.bats +0 -43
- package/skills/manage-problem/test/manage-problem-git-mv-restage-reminder.bats +0 -48
- package/skills/manage-problem/test/manage-problem-id-collision-guard.bats +0 -39
- package/skills/manage-problem/test/manage-problem-list-forwarder.bats +0 -66
- package/skills/manage-problem/test/manage-problem-next-id-origin-lookup.bats +0 -50
- package/skills/manage-problem/test/manage-problem-no-prose-options.bats +0 -74
- package/skills/manage-problem/test/manage-problem-output-formatting.bats +0 -30
- package/skills/manage-problem/test/manage-problem-parked-and-cache.bats +0 -73
- package/skills/manage-problem/test/manage-problem-readme-refresh-on-transition.bats +0 -62
- package/skills/manage-problem/test/manage-problem-readme-tie-break-order.bats +0 -318
- package/skills/manage-problem/test/manage-problem-readme-vq-sort-order.bats +0 -206
- package/skills/manage-problem/test/manage-problem-release-vehicle-seed.bats +0 -83
- package/skills/manage-problem/test/manage-problem-review-forwarder.bats +0 -71
- package/skills/manage-problem/test/manage-problem-step-9d-recovery-path.bats +0 -97
- package/skills/manage-problem/test/manage-problem-transition-forwarder.bats +0 -96
- package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +0 -365
- package/skills/manage-problem/test/manage-problem-verification-detection.bats +0 -64
- package/skills/manage-problem/test/manage-problem-verification-pending.bats +0 -114
- package/skills/manage-problem/test/manage-problem-work-forwarder.bats +0 -88
- package/skills/manage-story/test/manage-story-contract.bats +0 -174
- package/skills/manage-story-map/test/manage-story-map-contract.bats +0 -63
- package/skills/mitigate-incident/test/mitigate-incident-contract.bats +0 -258
- package/skills/reconcile-readme/test/reconcile-readme-contract.bats +0 -201
- package/skills/report-upstream/test/report-upstream-contract.bats +0 -394
- package/skills/restore-incident/test/restore-incident-contract.bats +0 -170
- package/skills/review-problems/test/inbound-channels-cache-shape.bats +0 -127
- package/skills/review-problems/test/inbound-discovery-contract.bats +0 -383
- package/skills/review-problems/test/jtbd-301-verdict-shape-contract.bats +0 -225
- package/skills/review-problems/test/review-problems-contract.bats +0 -235
- package/skills/review-problems/test/review-problems-likely-verified-cell-shape.bats +0 -229
- package/skills/scaffold-intake/test/scaffold-intake-contract.bats +0 -126
- package/skills/scaffold-intake/test/scaffold-intake-fixture.bats +0 -164
- package/skills/scaffold-intake/test/scaffold-intake-secrets-absent.bats +0 -87
- package/skills/transition-problem/test/transition-problem-contract.bats +0 -285
- package/skills/transition-problems/test/transition-problems-contract.bats +0 -331
- package/skills/update-upstream/test/update-upstream-contract.bats +0 -375
- package/skills/work-problem/test/work-problem-contract.bats +0 -294
- package/skills/work-problems/test/work-problems-above-appetite-remediation.bats +0 -139
- package/skills/work-problems/test/work-problems-adr-013-rule-6-p352-amendment.bats +0 -166
- package/skills/work-problems/test/work-problems-auto-migrate-step.bats +0 -57
- package/skills/work-problems/test/work-problems-cost-logging.bats +0 -109
- package/skills/work-problems/test/work-problems-deviation-candidate-shape.bats +0 -121
- package/skills/work-problems/test/work-problems-first-run-intake-prompt.bats +0 -36
- package/skills/work-problems/test/work-problems-inter-iteration-verify.bats +0 -59
- package/skills/work-problems/test/work-problems-mid-loop-userpromptsubmit-handler.bats +0 -69
- package/skills/work-problems/test/work-problems-no-mid-loop-asking.bats +0 -192
- package/skills/work-problems/test/work-problems-p341-pre-all-done-gate.bats +0 -122
- package/skills/work-problems/test/work-problems-p342-r007-derive-then-pass-flags.bats +0 -170
- package/skills/work-problems/test/work-problems-p342-retro-auto-ticket-carveout.bats +0 -120
- package/skills/work-problems/test/work-problems-preflight-failure-handling.bats +0 -248
- package/skills/work-problems/test/work-problems-preflight-session-continuity.bats +0 -139
- package/skills/work-problems/test/work-problems-preflight.bats +0 -69
- package/skills/work-problems/test/work-problems-release-cadence.bats +0 -63
- package/skills/work-problems/test/work-problems-step-0-iter-error-staleness.bats +0 -93
- package/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats +0 -138
- package/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats +0 -238
- package/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats +0 -174
- package/skills/work-problems/test/work-problems-step-2-5-routing.bats +0 -80
- package/skills/work-problems/test/work-problems-step-2-5b-cross-halt-routing.bats +0 -149
- package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +0 -191
- package/skills/work-problems/test/work-problems-step-5-bats-polling-discipline.bats +0 -91
- package/skills/work-problems/test/work-problems-step-5-delegation.bats +0 -333
- package/skills/work-problems/test/work-problems-step-5-idle-timeout-sigterm.bats +0 -421
- package/skills/work-problems/test/work-problems-step-5-is-error-transient-halt.bats +0 -278
- package/skills/work-problems/test/work-problems-step-5-iter-changeset-required.bats +0 -95
- package/skills/work-problems/test/work-problems-step-5-prompt-body-re-grounding.bats +0 -128
- package/skills/work-problems/test/work-problems-step-5-stream-timeout-salvage.bats +0 -251
- package/skills/work-problems/test/work-problems-step-6-5-always-drain.bats +0 -203
- package/skills/work-problems/test/work-problems-step-6-5-cache-refresh-chain.bats +0 -174
- package/skills/work-problems/test/work-problems-step-6-5-fix-and-continue.bats +0 -254
- package/skills/work-problems/test/work-problems-step-6-5-postrelease-kv-callback.bats +0 -209
- package/skills/work-problems/test/work-problems-stop-condition-questions.bats +0 -85
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P287 regression guard — replaces the historical i2-no-type-branching.bats
|
|
4
|
-
# (which asserted no-control-flow-branch-on-type as an I2 invariant).
|
|
5
|
-
#
|
|
6
|
-
# With the type axis retired per twice-confirmed user direction
|
|
7
|
-
# (2026-05-25 + 2026-06-02), the I2 branch-protection invariant is
|
|
8
|
-
# vacuous (no `type` field to branch on). This regression guard
|
|
9
|
-
# preserves the audit-trail compliance evidence per architect-review
|
|
10
|
-
# verdict 2026-06-02 by asserting the POSITIVE state:
|
|
11
|
-
#
|
|
12
|
-
# - The `**Type**:` body field is GONE from the capture-problem
|
|
13
|
-
# skeleton template and from every committed problem ticket.
|
|
14
|
-
# - The `lexical_classify_two_sided` helper function is removed.
|
|
15
|
-
# - capture-problem SKILL.md no longer carries the Step 1.5 Type
|
|
16
|
-
# classification section or its dispatch flags.
|
|
17
|
-
#
|
|
18
|
-
# Drift here = silent reintroduction of the type axis = P287 regression.
|
|
19
|
-
#
|
|
20
|
-
# @problem P287 (type-classification retirement)
|
|
21
|
-
# @problem P176 (agent-side I2 coverage gap — historical; with the
|
|
22
|
-
# type axis retired the gap is vacuous on that axis)
|
|
23
|
-
# @adr ADR-052 (behavioural-by-default; this is a regression-guard
|
|
24
|
-
# structural assertion per Surface 2 escape-hatch contract — the
|
|
25
|
-
# contract surface IS the on-disk artefact state)
|
|
26
|
-
# @adr ADR-014 (single-purpose; one mechanical regression invariant)
|
|
27
|
-
# @jtbd JTBD-001 (enforce governance without slowing down — primary)
|
|
28
|
-
|
|
29
|
-
setup() {
|
|
30
|
-
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
31
|
-
PKG_ROOT="$REPO_ROOT/packages/itil"
|
|
32
|
-
SKILL_FILE="$PKG_ROOT/skills/capture-problem/SKILL.md"
|
|
33
|
-
HELPER="$REPO_ROOT/packages/shared/derive-first-dispatch.sh"
|
|
34
|
-
[ -f "$SKILL_FILE" ]
|
|
35
|
-
[ -f "$HELPER" ]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
@test "P287: no committed problem ticket carries a **Type**: body field" {
|
|
39
|
-
# Find any docs/problems/**/*.md ticket that still carries the
|
|
40
|
-
# retired type field on a body line. Any match = P287 regression.
|
|
41
|
-
result=$(grep -r --include='*.md' -l '^\*\*Type\*\*: ' "$REPO_ROOT/docs/problems/" 2>/dev/null || true)
|
|
42
|
-
[ -z "$result" ]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@test "P287: capture-problem skeleton template does not include a **Type**: line" {
|
|
46
|
-
! grep -F '**Type**: <type_value>' "$SKILL_FILE"
|
|
47
|
-
! grep -F '**Type**: technical' "$SKILL_FILE"
|
|
48
|
-
! grep -F '**Type**: user-business' "$SKILL_FILE"
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@test "P287: capture-problem SKILL.md does not carry a Step 1.5 Type classification header" {
|
|
52
|
-
# The retired Step 1.5 section was titled "### 1.5 Type classification".
|
|
53
|
-
! grep -E '^### 1\.5 Type classification' "$SKILL_FILE"
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@test "P287: shared dispatch helper does not export lexical_classify_two_sided" {
|
|
57
|
-
! grep -E '^lexical_classify_two_sided\(\)' "$HELPER"
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@test "P287: per-package lib/ copies are byte-identical to the canonical shared/ source" {
|
|
61
|
-
# ADR-017 sync compliance — the strip removed lexical_classify_two_sided
|
|
62
|
-
# from the canonical packages/shared/derive-first-dispatch.sh; the
|
|
63
|
-
# sync script must propagate the removal to per-package copies.
|
|
64
|
-
diff -q "$HELPER" "$REPO_ROOT/packages/itil/lib/derive-first-dispatch.sh"
|
|
65
|
-
diff -q "$HELPER" "$REPO_ROOT/packages/architect/lib/derive-first-dispatch.sh"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@test "P287: capture-problem SKILL.md does not carry --type= flag rows (--no-prompt REVIVED per ADR-060 Amendment 2026-06-02 as AFK marker)" {
|
|
69
|
-
# The retired Step 1.5 dispatch flags must not appear in the flag table.
|
|
70
|
-
# NB: --no-prompt was REVIVED per ADR-060 Amendment 2026-06-02 — see
|
|
71
|
-
# capture-problem-step-1-5b-jtbd-trace.bats line 327 (positive assertion).
|
|
72
|
-
! grep -E '^\| `--type=technical`' "$SKILL_FILE"
|
|
73
|
-
! grep -E '^\| `--type=user-business`' "$SKILL_FILE"
|
|
74
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
# P474 / STORY-055 — corpus lint: no artefact may mirror an excluded key.
|
|
3
|
-
#
|
|
4
|
-
# This is a LINT, not a behavioural test, and the distinction is deliberate rather
|
|
5
|
-
# than an ADR-052 exception. ADR-052's forbidden class is structural assertion on
|
|
6
|
-
# document content *as a proxy for behaviour*; all its documented failure modes
|
|
7
|
-
# are proxy failures. Here the content IS the deliverable — "no live artefact
|
|
8
|
-
# carries a mirror" is a property of data, and data has no behaviour to assert.
|
|
9
|
-
# So it homes under ADR-005's script/data-validation authority, which ADR-052
|
|
10
|
-
# explicitly leaves intact.
|
|
11
|
-
#
|
|
12
|
-
# WHY it exists: P474's mirror audit was a one-time inspection, and a one-time
|
|
13
|
-
# inspection has no cadence. The fingerprint excludes four frontmatter keys; if
|
|
14
|
-
# any of them acquires a body mirror, accepting a story silently drops its own
|
|
15
|
-
# ratification and the no-implement gate then denies its own implementing commit.
|
|
16
|
-
# That already happened once with `status`.
|
|
17
|
-
#
|
|
18
|
-
# The behavioural half lives in the last test: it covers a mirror of ANY excluded
|
|
19
|
-
# key in ANY shape, including an HTML meta mirror, which the lint's line-anchored
|
|
20
|
-
# bold-key pattern structurally cannot reach.
|
|
21
|
-
#
|
|
22
|
-
# Two things this lint does NOT catch, recorded rather than implied:
|
|
23
|
-
# 1. The story-map `href` leg (open P474 task) — a card links
|
|
24
|
-
# `../../stories/<state>/…`, so the lifecycle directory sits inside the map's
|
|
25
|
-
# hashed content while the card's `data-status` is normalised out. No
|
|
26
|
-
# bold-key pattern can see that.
|
|
27
|
-
# 2. A mirror written with leading whitespace or inside a list item — the cost
|
|
28
|
-
# of line-anchoring, taken so no exclusion set is needed.
|
|
29
|
-
#
|
|
30
|
-
# @problem P474 @adr ADR-090 ADR-005 ADR-052 @story STORY-055
|
|
31
|
-
|
|
32
|
-
setup() {
|
|
33
|
-
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
34
|
-
LIB="${REPO_ROOT}/packages/itil/lib/story-oversight.sh"
|
|
35
|
-
# shellcheck source=/dev/null
|
|
36
|
-
source "$LIB"
|
|
37
|
-
STORIES="$REPO_ROOT/docs/stories"
|
|
38
|
-
MAPS="$REPO_ROOT/docs/story-maps"
|
|
39
|
-
TMPD="$(mktemp -d)"
|
|
40
|
-
}
|
|
41
|
-
teardown() { rm -rf "$TMPD"; }
|
|
42
|
-
|
|
43
|
-
# Build the pattern from the lib's own accessor, so a key added to the exclusion
|
|
44
|
-
# set is covered without editing this file. Case-INSENSITIVE is load-bearing: the
|
|
45
|
-
# accessor holds lowercase keys (`status`) while every real mirror is Title-Case
|
|
46
|
-
# (`**Status**:`), so a case-sensitive pattern built from the accessor matches
|
|
47
|
-
# nothing on arrival AND stays green when a mirror is reintroduced.
|
|
48
|
-
mirror_pattern() {
|
|
49
|
-
local alt; alt="$(oversight_excluded_keys | paste -sd'|' -)"
|
|
50
|
-
printf '^\\*\\*(%s)\\*\\*[[:space:]]*:' "$alt"
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
# Line-anchored on purpose. Unanchored, this matches four lines of legitimate
|
|
54
|
-
# prose *about* the defect (the stories README, two lines in STORY-054, and
|
|
55
|
-
# STORY-055's own problem trace), which would redden on arrival and require an
|
|
56
|
-
# exclusion list growing with every future story that discusses a mirror.
|
|
57
|
-
scan() {
|
|
58
|
-
# -H is load-bearing, not decoration. Given exactly ONE file argument, GNU grep
|
|
59
|
-
# OMITS the filename prefix while this machine's grep emits it — so a fixture
|
|
60
|
-
# scan over a single file produced output containing the matched line but not
|
|
61
|
-
# the file it came from, and the assertion naming that file passed locally and
|
|
62
|
-
# failed on CI. Forcing the prefix makes output identical across grep
|
|
63
|
-
# implementations, and makes the two production diagnostics below name the
|
|
64
|
-
# offending artefact rather than only quoting the line.
|
|
65
|
-
grep -rHniE "$(mirror_pattern)" "$@" 2>/dev/null || true
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@test "lint: the excluded-key accessor is non-empty and the pattern is well-formed" {
|
|
69
|
-
# Guard against a silently-empty pattern — the whole lint would go vacuous.
|
|
70
|
-
local keys; keys="$(oversight_excluded_keys)"
|
|
71
|
-
[ -n "$keys" ]
|
|
72
|
-
[ "$(printf '%s\n' "$keys" | wc -l | tr -d ' ')" -ge 4 ]
|
|
73
|
-
[[ "$(mirror_pattern)" == *'status'* ]]
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
@test "lint: search roots exist and hold artefacts — fails loudly, never vacuously" {
|
|
77
|
-
# A renamed or adopter-relative path must fail here rather than let the scan
|
|
78
|
-
# below pass by matching nothing. This is the shape that reddened main once.
|
|
79
|
-
[ -d "$STORIES" ]
|
|
80
|
-
[ -d "$MAPS" ]
|
|
81
|
-
local n
|
|
82
|
-
n="$(find "$STORIES" -name 'STORY-*.md' -type f 2>/dev/null | wc -l | tr -d ' ')"
|
|
83
|
-
[ "$n" -ge 1 ] || { echo "no STORY-*.md under $STORIES — lint would be vacuous"; false; }
|
|
84
|
-
n="$(find "$MAPS" -name '*.html' -type f 2>/dev/null | wc -l | tr -d ' ')"
|
|
85
|
-
[ "$n" -ge 1 ] || { echo "no maps under $MAPS — lint would be vacuous"; false; }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@test "lint: no story artefact mirrors an excluded frontmatter key in its body" {
|
|
89
|
-
local hits
|
|
90
|
-
hits="$(scan "$STORIES"/*/STORY-*.md)"
|
|
91
|
-
[ -z "$hits" ] || {
|
|
92
|
-
printf 'body mirror of an excluded key found — accepting these stories will drop their ratification:\n%s\n' "$hits"
|
|
93
|
-
printf 'remove the line; run `wr-itil-migrate-story-status-mirror docs/stories` for an existing corpus.\n'
|
|
94
|
-
false
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@test "lint: no story map mirrors an excluded frontmatter key in its body" {
|
|
99
|
-
# The map's own `<meta name="status">` is the key's HOME in the HTML encoding,
|
|
100
|
-
# not a mirror of it, and the lib's filter already excludes it — so the
|
|
101
|
-
# line-anchored bold-key pattern correctly does not flag it.
|
|
102
|
-
local hits
|
|
103
|
-
hits="$(scan "$MAPS"/*/*.html)"
|
|
104
|
-
[ -z "$hits" ] || {
|
|
105
|
-
printf 'body mirror of an excluded key found in a map:\n%s\n' "$hits"
|
|
106
|
-
false
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
@test "lint: RED on an injected mirror — proves the pattern actually fires" {
|
|
111
|
-
mkdir -p "$TMPD/stories/draft"
|
|
112
|
-
printf -- '---\nstatus: accepted\n---\n\n# STORY-999: x\n\n**Status**: accepted\n**Reported**: 2026-07-30\n' \
|
|
113
|
-
> "$TMPD/stories/draft/STORY-999-x.md"
|
|
114
|
-
local hits; hits="$(scan "$TMPD/stories"/*/STORY-*.md)"
|
|
115
|
-
[ -n "$hits" ] || { echo "lint failed to detect an injected line-start **Status**: mirror"; false; }
|
|
116
|
-
[[ "$hits" == *"STORY-999"* ]]
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
@test "lint: prose ABOUT a mirror is not flagged — that is what line-anchoring buys" {
|
|
120
|
-
mkdir -p "$TMPD/prose/draft"
|
|
121
|
-
printf -- '---\nstatus: accepted\n---\n\n# STORY-998: y\n\nThe `**Status**:` line used to be hashed.\n<!-- No **Status**: body line, deliberately. -->\n' \
|
|
122
|
-
> "$TMPD/prose/draft/STORY-998-y.md"
|
|
123
|
-
[ -z "$(scan "$TMPD/prose"/*/STORY-*.md)" ]
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
# The behavioural half. Covers a mirror of ANY excluded key in ANY shape,
|
|
127
|
-
# including the HTML meta form the lint's pattern cannot reach.
|
|
128
|
-
@test "behavioural: a mirror of any excluded key drifts a ratified artefact" {
|
|
129
|
-
local k
|
|
130
|
-
for k in $(oversight_excluded_keys); do
|
|
131
|
-
# Skip the marker keys themselves — those are excluded BY the marker write,
|
|
132
|
-
# so a "mirror" of them is what the filter is for.
|
|
133
|
-
case "$k" in human-oversight|oversight-hash|oversight-basis) continue ;; esac
|
|
134
|
-
local f="$TMPD/mirror-$k.md"
|
|
135
|
-
printf -- '---\n%s: draft\nhuman-oversight: confirmed\noversight-hash: P\n---\n\n# STORY-997\n\nbody\n' "$k" > "$f"
|
|
136
|
-
local h; h="$(oversight_content_hash "$f")"
|
|
137
|
-
sed -i.bak "s/oversight-hash: P/oversight-hash: $h/" "$f" && rm -f "$f.bak"
|
|
138
|
-
run is_story_map_ratified "$f"
|
|
139
|
-
[ "$status" -eq 0 ]
|
|
140
|
-
# Add a body mirror of that key, then advance BOTH copies the way a
|
|
141
|
-
# transition would. The frontmatter change alone is excluded; the body
|
|
142
|
-
# mirror is not — so ratification must drop, which is P474 exactly.
|
|
143
|
-
printf -- '**%s**: accepted\n' "$k" >> "$f"
|
|
144
|
-
h="$(oversight_content_hash "$f")"
|
|
145
|
-
sed -i.bak "s/^oversight-hash: .*/oversight-hash: $h/" "$f" && rm -f "$f.bak"
|
|
146
|
-
sed -i.bak "s/^${k}: draft/${k}: accepted/; s/^\\*\\*${k}\\*\\*: accepted/**${k}**: DIFFERENT/" "$f" && rm -f "$f.bak"
|
|
147
|
-
run is_story_map_ratified "$f"
|
|
148
|
-
[ "$status" -ne 0 ] || { echo "a body mirror of '$k' did not drift the hash"; false; }
|
|
149
|
-
done
|
|
150
|
-
}
|
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# @problem P087 — Phase 2b git-axis script behavioural confirmation.
|
|
4
|
-
#
|
|
5
|
-
# Contract under test: `packages/itil/scripts/plugin-exercise-index.sh` runs
|
|
6
|
-
# `git log --since=<N>d --name-only --pretty=format:%H|%aI|%s` once at the
|
|
7
|
-
# project root, auto-discovers plugins by listing `packages/*/`, and emits
|
|
8
|
-
# one NDJSON record per plugin with the v1.0 schema fields per ADR-058
|
|
9
|
-
# §Script contracts (line 87-113). Exit code 0 always per ADR-013 Rule 6
|
|
10
|
-
# fail-safe (outside-git-repo, missing `packages/`, opt-out marker all hit
|
|
11
|
-
# the zero-records/stderr-comment path).
|
|
12
|
-
#
|
|
13
|
-
# Confirmation criteria 6-8 from ADR-058 §Confirmation are the load-bearing
|
|
14
|
-
# behavioural assertions in this file. Sibling to Phase 2a's
|
|
15
|
-
# `skill-invocations.bats` (criteria 1-5).
|
|
16
|
-
#
|
|
17
|
-
# @adr ADR-058 (Plugin maturity measurement mechanism)
|
|
18
|
-
# @adr ADR-049 (Shim grammar — `wr-itil-plugin-exercise-index` on $PATH)
|
|
19
|
-
# @adr ADR-035 (Privacy posture — opt-out marker, no network primitive,
|
|
20
|
-
# content sanitisation — commit subjects parsed only for `BREAKING|feat!|fix!`
|
|
21
|
-
# token presence, never echoed to stdout)
|
|
22
|
-
# @adr ADR-052 (Behavioural tests default — NDJSON-output-driven against
|
|
23
|
-
# fixture git repos, not source-greps on script body; the no-network
|
|
24
|
-
# negative-grep at Confirmation #3 is the documented carve-out)
|
|
25
|
-
# @adr ADR-013 Rule 6 (non-interactive fail-safe — exit 0 always)
|
|
26
|
-
# @jtbd JTBD-101 (Extend the Suite — hardening-prioritisation outcome,
|
|
27
|
-
# commits_window + closed_tickets_window + days_shipped serve the
|
|
28
|
-
# git-axis half of the 2026-05-04 outcome amendment)
|
|
29
|
-
# @jtbd JTBD-201 (Restore Service Fast — audit-trail composition)
|
|
30
|
-
|
|
31
|
-
setup() {
|
|
32
|
-
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
33
|
-
SCRIPT="$SCRIPTS_DIR/plugin-exercise-index.sh"
|
|
34
|
-
FIXTURE_DIR="$(mktemp -d)"
|
|
35
|
-
REPO_ROOT="$FIXTURE_DIR/repo"
|
|
36
|
-
mkdir -p "$REPO_ROOT"
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
teardown() {
|
|
40
|
-
rm -rf "$FIXTURE_DIR"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# Helper: initialise a temp git repo at REPO_ROOT with a stable author/email
|
|
44
|
-
# (otherwise `git commit` aborts on systems without user.name configured).
|
|
45
|
-
init_repo() {
|
|
46
|
-
(
|
|
47
|
-
cd "$REPO_ROOT"
|
|
48
|
-
git init -q -b main
|
|
49
|
-
git config user.email "bats@example.com"
|
|
50
|
-
git config user.name "bats"
|
|
51
|
-
git config commit.gpgsign false
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
# Helper: stage a file under packages/<plugin>/ and commit at a given date.
|
|
56
|
-
# Date is ISO 8601 (e.g. 2026-05-01T12:00:00) — fed to GIT_AUTHOR_DATE /
|
|
57
|
-
# GIT_COMMITTER_DATE so log --since works deterministically.
|
|
58
|
-
commit_under_plugin() {
|
|
59
|
-
local plugin="$1"; local relpath="$2"; local subject="$3"; local date="$4"
|
|
60
|
-
local full="$REPO_ROOT/packages/$plugin/$relpath"
|
|
61
|
-
mkdir -p "$(dirname "$full")"
|
|
62
|
-
echo "content-$RANDOM" >> "$full"
|
|
63
|
-
(
|
|
64
|
-
cd "$REPO_ROOT"
|
|
65
|
-
GIT_AUTHOR_DATE="$date" GIT_COMMITTER_DATE="$date" \
|
|
66
|
-
git -c user.email=bats@example.com -c user.name=bats \
|
|
67
|
-
add "packages/$plugin/$relpath" >/dev/null
|
|
68
|
-
GIT_AUTHOR_DATE="$date" GIT_COMMITTER_DATE="$date" \
|
|
69
|
-
git -c user.email=bats@example.com -c user.name=bats \
|
|
70
|
-
commit -q -m "$subject"
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
# Helper: ISO timestamp N days ago (UTC). Cross-platform — uses python3.
|
|
75
|
-
days_ago_iso() {
|
|
76
|
-
python3 -c "import sys, datetime; print((datetime.datetime.utcnow() - datetime.timedelta(days=int(sys.argv[1]))).strftime('%Y-%m-%dT%H:%M:%S'))" "$1"
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
# ── Existence / executable ──────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
@test "plugin-exercise-index: canonical script exists" {
|
|
82
|
-
[ -f "$SCRIPT" ]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@test "plugin-exercise-index: canonical script is executable" {
|
|
86
|
-
[ -x "$SCRIPT" ]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@test "plugin-exercise-index: shim file exists with ADR-049 grammar" {
|
|
90
|
-
local shim="$SCRIPTS_DIR/../bin/wr-itil-plugin-exercise-index"
|
|
91
|
-
[ -f "$shim" ]
|
|
92
|
-
[ -x "$shim" ]
|
|
93
|
-
grep -q 'exec.*scripts/plugin-exercise-index.sh' "$shim"
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# ── Confirmation #6: git-axis composite fixture ─────────────────────────────
|
|
97
|
-
# Seed a temp git repo with packages/dummy/ + three commits (one in window,
|
|
98
|
-
# two out of window). Assert NDJSON record for plugin="dummy" has
|
|
99
|
-
# commits_window=1 under the 60-day default window.
|
|
100
|
-
|
|
101
|
-
@test "Confirmation #6: three commits, one in window, commits_window=1" {
|
|
102
|
-
init_repo
|
|
103
|
-
local in_window=$(days_ago_iso 10)
|
|
104
|
-
local out_window_1=$(days_ago_iso 90)
|
|
105
|
-
local out_window_2=$(days_ago_iso 120)
|
|
106
|
-
commit_under_plugin "dummy" "src/a.txt" "feat: in-window change" "$in_window"
|
|
107
|
-
commit_under_plugin "dummy" "src/b.txt" "feat: out-of-window 1" "$out_window_1"
|
|
108
|
-
commit_under_plugin "dummy" "src/c.txt" "feat: out-of-window 2" "$out_window_2"
|
|
109
|
-
|
|
110
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
111
|
-
[ "$status" -eq 0 ]
|
|
112
|
-
# Exactly one NDJSON record for plugin="dummy"
|
|
113
|
-
local rec
|
|
114
|
-
rec="$(echo "$output" | grep '"plugin":"dummy"')"
|
|
115
|
-
[ -n "$rec" ]
|
|
116
|
-
echo "$rec" | python3 -c "
|
|
117
|
-
import json, sys
|
|
118
|
-
r = json.loads(sys.stdin.read().strip())
|
|
119
|
-
assert r['schema_version'] == '1.0', r
|
|
120
|
-
assert r['axis'] == 'plugin-exercise-index', r
|
|
121
|
-
assert r['plugin'] == 'dummy', r
|
|
122
|
-
assert r['commits_window'] == 1, r
|
|
123
|
-
assert r['window_days'] == 60, r
|
|
124
|
-
assert r['days_shipped'] >= 120, r
|
|
125
|
-
"
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
@test "Confirmation #6: emits one record per discovered plugin" {
|
|
129
|
-
init_repo
|
|
130
|
-
local ts=$(days_ago_iso 5)
|
|
131
|
-
commit_under_plugin "alpha" "src/a.txt" "feat: alpha change" "$ts"
|
|
132
|
-
commit_under_plugin "beta" "src/b.txt" "feat: beta change" "$ts"
|
|
133
|
-
|
|
134
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
135
|
-
[ "$status" -eq 0 ]
|
|
136
|
-
echo "$output" | grep -q '"plugin":"alpha"'
|
|
137
|
-
echo "$output" | grep -q '"plugin":"beta"'
|
|
138
|
-
# Line count = number of discovered plugins (alpha + beta)
|
|
139
|
-
local line_count
|
|
140
|
-
line_count="$(printf '%s' "$output" | grep -c .)"
|
|
141
|
-
[ "$line_count" -eq 2 ]
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@test "Confirmation #6: commit subject containing literal pipe parses correctly" {
|
|
145
|
-
# ADR-058 line 89 pins `--pretty=format:%H|%aI|%s` — the parser must
|
|
146
|
-
# split on first two `|` only (line.split('|', 2)) so subjects with
|
|
147
|
-
# literal `|` characters do not corrupt parsing. Architect advisory.
|
|
148
|
-
init_repo
|
|
149
|
-
local ts=$(days_ago_iso 5)
|
|
150
|
-
commit_under_plugin "pipey" "src/a.txt" "feat: subject with | pipe in it" "$ts"
|
|
151
|
-
|
|
152
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
153
|
-
[ "$status" -eq 0 ]
|
|
154
|
-
echo "$output" | grep -q '"plugin":"pipey"'
|
|
155
|
-
echo "$output" | grep -q '"commits_window":1'
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
# ── Confirmation #7: outside-git-repo fixture ───────────────────────────────
|
|
159
|
-
|
|
160
|
-
@test "Confirmation #7: outside-git-repo emits zero records, stderr comment, exit 0" {
|
|
161
|
-
# REPO_ROOT exists but no `git init` ran — it is not a git repo.
|
|
162
|
-
local stdout_file="$FIXTURE_DIR/nogit.out"
|
|
163
|
-
local stderr_file="$FIXTURE_DIR/nogit.err"
|
|
164
|
-
"$SCRIPT" --window-days=60 --project-root="$REPO_ROOT" >"$stdout_file" 2>"$stderr_file"
|
|
165
|
-
local rc=$?
|
|
166
|
-
[ "$rc" -eq 0 ]
|
|
167
|
-
[ ! -s "$stdout_file" ]
|
|
168
|
-
grep -q "not a git repository" "$stderr_file"
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@test "Confirmation #7: missing packages/ directory emits zero records, exit 0" {
|
|
172
|
-
init_repo
|
|
173
|
-
# Repo has no packages/ directory at all. Make a non-packages commit so
|
|
174
|
-
# `git log` returns something but no plugins are discovered.
|
|
175
|
-
(
|
|
176
|
-
cd "$REPO_ROOT"
|
|
177
|
-
echo readme > README.md
|
|
178
|
-
GIT_AUTHOR_DATE="$(days_ago_iso 5)" GIT_COMMITTER_DATE="$(days_ago_iso 5)" \
|
|
179
|
-
git -c user.email=bats@example.com -c user.name=bats add README.md >/dev/null
|
|
180
|
-
GIT_AUTHOR_DATE="$(days_ago_iso 5)" GIT_COMMITTER_DATE="$(days_ago_iso 5)" \
|
|
181
|
-
git -c user.email=bats@example.com -c user.name=bats commit -q -m "feat: readme"
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
local stdout_file="$FIXTURE_DIR/nopkg.out"
|
|
185
|
-
local stderr_file="$FIXTURE_DIR/nopkg.err"
|
|
186
|
-
"$SCRIPT" --window-days=60 --project-root="$REPO_ROOT" >"$stdout_file" 2>"$stderr_file"
|
|
187
|
-
local rc=$?
|
|
188
|
-
[ "$rc" -eq 0 ]
|
|
189
|
-
[ ! -s "$stdout_file" ]
|
|
190
|
-
grep -q "no packages/ directory" "$stderr_file"
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
# ── Confirmation #8: schema-version contract ────────────────────────────────
|
|
194
|
-
|
|
195
|
-
@test "Confirmation #8: every NDJSON record has schema_version=1.0" {
|
|
196
|
-
init_repo
|
|
197
|
-
local ts=$(days_ago_iso 5)
|
|
198
|
-
commit_under_plugin "p1" "src/a.txt" "feat: p1" "$ts"
|
|
199
|
-
commit_under_plugin "p2" "src/b.txt" "feat: p2" "$ts"
|
|
200
|
-
|
|
201
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
202
|
-
[ "$status" -eq 0 ]
|
|
203
|
-
# Validate every line carries schema_version="1.0"
|
|
204
|
-
echo "$output" | python3 -c "
|
|
205
|
-
import json, sys
|
|
206
|
-
for line in sys.stdin:
|
|
207
|
-
line = line.strip()
|
|
208
|
-
if not line: continue
|
|
209
|
-
r = json.loads(line)
|
|
210
|
-
assert r['schema_version'] == '1.0', r
|
|
211
|
-
assert r['axis'] == 'plugin-exercise-index', r
|
|
212
|
-
"
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
# ── Privacy: opt-out marker ─────────────────────────────────────────────────
|
|
216
|
-
|
|
217
|
-
@test "opt-out marker disables reads, stderr comment, exit 0" {
|
|
218
|
-
init_repo
|
|
219
|
-
local ts=$(days_ago_iso 5)
|
|
220
|
-
commit_under_plugin "x" "src/a.txt" "feat: would-be-recorded" "$ts"
|
|
221
|
-
mkdir -p "$REPO_ROOT/.claude"
|
|
222
|
-
touch "$REPO_ROOT/.claude/.skill-metrics-opt-out"
|
|
223
|
-
|
|
224
|
-
local stdout_file="$FIXTURE_DIR/opt.out"
|
|
225
|
-
local stderr_file="$FIXTURE_DIR/opt.err"
|
|
226
|
-
"$SCRIPT" --window-days=60 --project-root="$REPO_ROOT" >"$stdout_file" 2>"$stderr_file"
|
|
227
|
-
local rc=$?
|
|
228
|
-
[ "$rc" -eq 0 ]
|
|
229
|
-
[ ! -s "$stdout_file" ]
|
|
230
|
-
grep -q "opt-out marker present at" "$stderr_file"
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
# ── Privacy: no-network-primitive (ADR-052 negative-grep carve-out) ─────────
|
|
234
|
-
|
|
235
|
-
@test "canonical body contains no network primitives" {
|
|
236
|
-
run grep -nE '\bcurl\b|\bwget\b|\bnc[[:space:]]|\bfetch\b|http\.client|\burllib\b' "$SCRIPT"
|
|
237
|
-
[ "$status" -eq 1 ]
|
|
238
|
-
[ -z "$output" ]
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
# ── Privacy: commit-subject prose never leaks beyond breaking-marker test ───
|
|
242
|
-
|
|
243
|
-
@test "commit subjects never appear in NDJSON output beyond BREAKING/feat!/fix! parse" {
|
|
244
|
-
init_repo
|
|
245
|
-
local ts=$(days_ago_iso 5)
|
|
246
|
-
# Subject contains a recognisable token that must NOT echo to stdout.
|
|
247
|
-
commit_under_plugin "secrety" "src/a.txt" "feat: contains SECRETPROSEXYZ token" "$ts"
|
|
248
|
-
|
|
249
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
250
|
-
[ "$status" -eq 0 ]
|
|
251
|
-
echo "$output" | grep -q '"plugin":"secrety"'
|
|
252
|
-
! echo "$output" | grep -q "SECRETPROSEXYZ"
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
# ── composite_index calculation ─────────────────────────────────────────────
|
|
256
|
-
|
|
257
|
-
@test "composite_index = log10(commits+1) + log10(closed+1) + days_shipped_bonus" {
|
|
258
|
-
init_repo
|
|
259
|
-
# Set the OLDEST commit > 60 days ago to earn the days_shipped >= 60 bonus
|
|
260
|
-
# of +1.0. Then add 9 in-window commits so log10(9+1) = 1.0.
|
|
261
|
-
commit_under_plugin "calc" "src/old.txt" "feat: old" "$(days_ago_iso 90)"
|
|
262
|
-
for i in 1 2 3 4 5 6 7 8 9; do
|
|
263
|
-
commit_under_plugin "calc" "src/n$i.txt" "feat: n$i" "$(days_ago_iso 5)"
|
|
264
|
-
done
|
|
265
|
-
|
|
266
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
267
|
-
[ "$status" -eq 0 ]
|
|
268
|
-
# commits_window=9 (only the 9 recent ones), days_shipped >= 60 (90 days
|
|
269
|
-
# since oldest), closed_tickets_window=0 (no docs/problems/).
|
|
270
|
-
# composite_index = log10(10) + log10(1) + 1.0 = 1.0 + 0.0 + 1.0 = 2.0
|
|
271
|
-
echo "$output" | grep '"plugin":"calc"' | python3 -c "
|
|
272
|
-
import json, sys
|
|
273
|
-
r = json.loads(sys.stdin.read().strip())
|
|
274
|
-
assert r['commits_window'] == 9, r
|
|
275
|
-
assert r['days_shipped'] >= 60, r
|
|
276
|
-
assert r['closed_tickets_window'] == 0, r
|
|
277
|
-
assert abs(r['composite_index'] - 2.0) < 0.01, r
|
|
278
|
-
"
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
@test "composite_index respects days_shipped < 60 (no bonus)" {
|
|
282
|
-
init_repo
|
|
283
|
-
# All commits within last 30 days; days_shipped < 60.
|
|
284
|
-
for i in 1 2 3 4 5 6 7 8 9; do
|
|
285
|
-
commit_under_plugin "young" "src/n$i.txt" "feat: n$i" "$(days_ago_iso 5)"
|
|
286
|
-
done
|
|
287
|
-
|
|
288
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
289
|
-
[ "$status" -eq 0 ]
|
|
290
|
-
# commits_window=9, days_shipped=5, closed_tickets_window=0
|
|
291
|
-
# composite_index = log10(10) + log10(1) + 0.0 = 1.0
|
|
292
|
-
echo "$output" | grep '"plugin":"young"' | python3 -c "
|
|
293
|
-
import json, sys
|
|
294
|
-
r = json.loads(sys.stdin.read().strip())
|
|
295
|
-
assert r['commits_window'] == 9, r
|
|
296
|
-
assert r['days_shipped'] < 60, r
|
|
297
|
-
assert abs(r['composite_index'] - 1.0) < 0.01, r
|
|
298
|
-
"
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
# ── closed_tickets_window ──────────────────────────────────────────────────
|
|
302
|
-
|
|
303
|
-
@test "closed_tickets_window counts .closed/.verifying tickets citing the plugin" {
|
|
304
|
-
init_repo
|
|
305
|
-
local ts=$(days_ago_iso 5)
|
|
306
|
-
commit_under_plugin "cited" "src/a.txt" "feat: cited" "$ts"
|
|
307
|
-
# Seed two closed tickets in docs/problems/ — one cites packages/cited/,
|
|
308
|
-
# one does not. Both have recent mtime so the 90-day window includes them.
|
|
309
|
-
mkdir -p "$REPO_ROOT/docs/problems"
|
|
310
|
-
echo "## Related
|
|
311
|
-
- packages/cited/scripts/foo.sh" > "$REPO_ROOT/docs/problems/100-something.closed.md"
|
|
312
|
-
echo "## Related
|
|
313
|
-
- packages/other/scripts/bar.sh" > "$REPO_ROOT/docs/problems/101-other.closed.md"
|
|
314
|
-
|
|
315
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
316
|
-
[ "$status" -eq 0 ]
|
|
317
|
-
echo "$output" | grep '"plugin":"cited"' | python3 -c "
|
|
318
|
-
import json, sys
|
|
319
|
-
r = json.loads(sys.stdin.read().strip())
|
|
320
|
-
assert r['closed_tickets_window'] == 1, r
|
|
321
|
-
"
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
# ── breaking_change_age_days ───────────────────────────────────────────────
|
|
325
|
-
|
|
326
|
-
@test "breaking_change_age_days surfaces BREAKING/feat!/fix! marker presence" {
|
|
327
|
-
init_repo
|
|
328
|
-
local recent=$(days_ago_iso 3)
|
|
329
|
-
local older=$(days_ago_iso 20)
|
|
330
|
-
commit_under_plugin "brk" "src/a.txt" "feat: normal change" "$older"
|
|
331
|
-
commit_under_plugin "brk" "src/b.txt" "feat!: breaking change" "$recent"
|
|
332
|
-
|
|
333
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
334
|
-
[ "$status" -eq 0 ]
|
|
335
|
-
echo "$output" | grep '"plugin":"brk"' | python3 -c "
|
|
336
|
-
import json, sys
|
|
337
|
-
r = json.loads(sys.stdin.read().strip())
|
|
338
|
-
v = r['breaking_change_age_days']
|
|
339
|
-
assert v is not None, r
|
|
340
|
-
assert 0 <= v <= 7, r
|
|
341
|
-
"
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
@test "breaking_change_age_days is null when no breaking marker in window" {
|
|
345
|
-
init_repo
|
|
346
|
-
local ts=$(days_ago_iso 5)
|
|
347
|
-
commit_under_plugin "nobreak" "src/a.txt" "feat: ordinary" "$ts"
|
|
348
|
-
commit_under_plugin "nobreak" "src/b.txt" "fix: ordinary" "$ts"
|
|
349
|
-
|
|
350
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT"
|
|
351
|
-
[ "$status" -eq 0 ]
|
|
352
|
-
echo "$output" | grep '"plugin":"nobreak"' | python3 -c "
|
|
353
|
-
import json, sys
|
|
354
|
-
r = json.loads(sys.stdin.read().strip())
|
|
355
|
-
assert r['breaking_change_age_days'] is None, r
|
|
356
|
-
"
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
# ── Forward-extension flag: --category-overrides ────────────────────────────
|
|
360
|
-
|
|
361
|
-
@test "category-overrides flag is accepted without functional effect" {
|
|
362
|
-
init_repo
|
|
363
|
-
commit_under_plugin "cat" "src/a.txt" "feat: cat" "$(days_ago_iso 5)"
|
|
364
|
-
echo '{}' > "$FIXTURE_DIR/overrides.json"
|
|
365
|
-
|
|
366
|
-
run "$SCRIPT" --window-days=60 --project-root="$REPO_ROOT" --category-overrides="$FIXTURE_DIR/overrides.json"
|
|
367
|
-
[ "$status" -eq 0 ]
|
|
368
|
-
echo "$output" | grep -q '"plugin":"cat"'
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
# ── Window-days filter ──────────────────────────────────────────────────────
|
|
372
|
-
|
|
373
|
-
@test "window-days filter excludes commits older than the window" {
|
|
374
|
-
init_repo
|
|
375
|
-
commit_under_plugin "win" "src/old.txt" "feat: old" "$(days_ago_iso 30)"
|
|
376
|
-
commit_under_plugin "win" "src/new.txt" "feat: new" "$(days_ago_iso 1)"
|
|
377
|
-
|
|
378
|
-
run "$SCRIPT" --window-days=7 --project-root="$REPO_ROOT"
|
|
379
|
-
[ "$status" -eq 0 ]
|
|
380
|
-
echo "$output" | grep '"plugin":"win"' | python3 -c "
|
|
381
|
-
import json, sys
|
|
382
|
-
r = json.loads(sys.stdin.read().strip())
|
|
383
|
-
assert r['commits_window'] == 1, r
|
|
384
|
-
assert r['window_days'] == 7, r
|
|
385
|
-
"
|
|
386
|
-
}
|