@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,535 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# @problem P087 — Phase 2a transcript-axis script behavioural confirmation.
|
|
4
|
-
#
|
|
5
|
-
# Contract under test: `packages/itil/scripts/skill-invocations.sh` reads
|
|
6
|
-
# `~/.claude/projects/**/*.jsonl` (recursive), tallies tool_use invocations
|
|
7
|
-
# by `Skill` / `Agent` / `Bash` (per ADR-058 §Script contracts), and emits
|
|
8
|
-
# one NDJSON record per surface to stdout. Exit code 0 always per ADR-013
|
|
9
|
-
# Rule 6 fail-safe (inaccessible root, opt-out marker, no data all hit the
|
|
10
|
-
# zero-records/stderr-comment path).
|
|
11
|
-
#
|
|
12
|
-
# Confirmation criteria 1-5 from ADR-058 §Confirmation are the load-bearing
|
|
13
|
-
# behavioural assertions in this file. Phase 2b's git-axis script ships
|
|
14
|
-
# criteria 6-8 in a sibling bats file.
|
|
15
|
-
#
|
|
16
|
-
# @adr ADR-058 (Plugin maturity measurement mechanism)
|
|
17
|
-
# @adr ADR-049 (Shim grammar — `wr-itil-skill-invocations` on $PATH)
|
|
18
|
-
# @adr ADR-035 (Privacy posture — opt-out marker, no network primitive,
|
|
19
|
-
# content sanitisation, path-hashing)
|
|
20
|
-
# @adr ADR-052 (Behavioural tests default — NDJSON-output-driven against
|
|
21
|
-
# fixture transcripts, not source-greps on script body; the no-network
|
|
22
|
-
# negative-grep at Confirmation #3 is the documented carve-out)
|
|
23
|
-
# @adr ADR-013 Rule 6 (non-interactive fail-safe — exit 0 always)
|
|
24
|
-
# @jtbd JTBD-101 (Extend the Suite — hardening-prioritisation outcome)
|
|
25
|
-
# @jtbd JTBD-201 (Restore Service Fast — audit-trail composition)
|
|
26
|
-
|
|
27
|
-
setup() {
|
|
28
|
-
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
29
|
-
SCRIPT="$SCRIPTS_DIR/skill-invocations.sh"
|
|
30
|
-
FIXTURE_DIR="$(mktemp -d)"
|
|
31
|
-
TRANSCRIPT_ROOT="$FIXTURE_DIR/transcripts"
|
|
32
|
-
PROJECT_ROOT="$FIXTURE_DIR/project"
|
|
33
|
-
mkdir -p "$TRANSCRIPT_ROOT" "$PROJECT_ROOT"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
teardown() {
|
|
37
|
-
rm -rf "$FIXTURE_DIR"
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
# Helper: write a synthetic JSONL transcript under the fixture root.
|
|
41
|
-
# Each call appends one assistant message carrying a single tool_use entry.
|
|
42
|
-
# Layout mirrors the live `~/.claude/projects/<encoded-path>/<UUID>.jsonl`
|
|
43
|
-
# shape (recursive rglob discovers it).
|
|
44
|
-
write_skill_invocation() {
|
|
45
|
-
local file="$1"; local skill_name="$2"; local ts="$3"
|
|
46
|
-
mkdir -p "$(dirname "$file")"
|
|
47
|
-
python3 - "$file" "$skill_name" "$ts" <<'PYEOF'
|
|
48
|
-
import json, sys
|
|
49
|
-
file, skill, ts = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
50
|
-
rec = {
|
|
51
|
-
"type": "assistant",
|
|
52
|
-
"timestamp": ts,
|
|
53
|
-
"message": {
|
|
54
|
-
"role": "assistant",
|
|
55
|
-
"content": [
|
|
56
|
-
{"type": "tool_use", "name": "Skill", "input": {"skill": skill}}
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
with open(file, "a") as fh:
|
|
61
|
-
fh.write(json.dumps(rec) + "\n")
|
|
62
|
-
PYEOF
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
write_agent_invocation() {
|
|
66
|
-
local file="$1"; local agent_kind="$2"; local ts="$3"
|
|
67
|
-
mkdir -p "$(dirname "$file")"
|
|
68
|
-
python3 - "$file" "$agent_kind" "$ts" <<'PYEOF'
|
|
69
|
-
import json, sys
|
|
70
|
-
file, kind, ts = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
71
|
-
rec = {
|
|
72
|
-
"type": "assistant",
|
|
73
|
-
"timestamp": ts,
|
|
74
|
-
"message": {
|
|
75
|
-
"role": "assistant",
|
|
76
|
-
"content": [
|
|
77
|
-
{"type": "tool_use", "name": "Agent", "input": {"subagent_type": kind}}
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
with open(file, "a") as fh:
|
|
82
|
-
fh.write(json.dumps(rec) + "\n")
|
|
83
|
-
PYEOF
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
write_bash_invocation() {
|
|
87
|
-
local file="$1"; local cmd="$2"; local ts="$3"
|
|
88
|
-
mkdir -p "$(dirname "$file")"
|
|
89
|
-
python3 - "$file" "$cmd" "$ts" <<'PYEOF'
|
|
90
|
-
import json, sys
|
|
91
|
-
file, cmd, ts = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
92
|
-
rec = {
|
|
93
|
-
"type": "assistant",
|
|
94
|
-
"timestamp": ts,
|
|
95
|
-
"message": {
|
|
96
|
-
"role": "assistant",
|
|
97
|
-
"content": [
|
|
98
|
-
{"type": "tool_use", "name": "Bash", "input": {"command": cmd}}
|
|
99
|
-
]
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
with open(file, "a") as fh:
|
|
103
|
-
fh.write(json.dumps(rec) + "\n")
|
|
104
|
-
PYEOF
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
# Helper: produce an ISO 8601 timestamp N hours before "now" (UTC).
|
|
108
|
-
recent_iso() {
|
|
109
|
-
python3 -c "import sys, datetime; print((datetime.datetime.utcnow() - datetime.timedelta(hours=int(sys.argv[1]))).strftime('%Y-%m-%dT%H:%M:%SZ'))" "$1"
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# ── Existence / executable ──────────────────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
@test "skill-invocations: canonical script exists" {
|
|
115
|
-
[ -f "$SCRIPT" ]
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
@test "skill-invocations: canonical script is executable" {
|
|
119
|
-
[ -x "$SCRIPT" ]
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@test "skill-invocations: shim file exists with ADR-049 grammar" {
|
|
123
|
-
local shim="$SCRIPTS_DIR/../bin/wr-itil-skill-invocations"
|
|
124
|
-
[ -f "$shim" ]
|
|
125
|
-
[ -x "$shim" ]
|
|
126
|
-
grep -q 'exec.*scripts/skill-invocations.sh' "$shim"
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
# ── Confirmation #1: NDJSON-shape fixture ───────────────────────────────────
|
|
130
|
-
# Seed three Skill invocations of `wr-itil:manage-problem` within the
|
|
131
|
-
# default 30-day window. Assert one NDJSON record on stdout with
|
|
132
|
-
# axis="skill-invocations", surface="wr-itil:manage-problem", kind="skill",
|
|
133
|
-
# plugin="itil", invocations=3. Each line is valid JSON with the expected
|
|
134
|
-
# schema keys.
|
|
135
|
-
|
|
136
|
-
@test "Confirmation #1: NDJSON record for 3 wr-itil:manage-problem invocations" {
|
|
137
|
-
local sess="$TRANSCRIPT_ROOT/proj/aaa.jsonl"
|
|
138
|
-
local ts=$(recent_iso 1)
|
|
139
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$ts"
|
|
140
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$ts"
|
|
141
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$ts"
|
|
142
|
-
|
|
143
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
144
|
-
[ "$status" -eq 0 ]
|
|
145
|
-
# Exactly one record line, no extras, no leading/trailing junk on stdout
|
|
146
|
-
local line_count
|
|
147
|
-
line_count="$(printf '%s' "$output" | grep -c .)"
|
|
148
|
-
[ "$line_count" -eq 1 ]
|
|
149
|
-
# Validate schema fields
|
|
150
|
-
echo "$output" | python3 -c "
|
|
151
|
-
import json, sys
|
|
152
|
-
rec = json.loads(sys.stdin.read().strip())
|
|
153
|
-
assert rec['schema_version'] == '1.0', rec
|
|
154
|
-
assert rec['axis'] == 'skill-invocations', rec
|
|
155
|
-
assert rec['surface'] == 'wr-itil:manage-problem', rec
|
|
156
|
-
assert rec['kind'] == 'skill', rec
|
|
157
|
-
assert rec['plugin'] == 'itil', rec
|
|
158
|
-
assert rec['window_days'] == 30, rec
|
|
159
|
-
assert rec['invocations'] == 3, rec
|
|
160
|
-
assert rec.get('first_invocation_iso') is not None, rec
|
|
161
|
-
assert rec.get('last_invocation_iso') is not None, rec
|
|
162
|
-
"
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
@test "Confirmation #1: aggregates Skill + Agent + Bash kinds into distinct records" {
|
|
166
|
-
local sess="$TRANSCRIPT_ROOT/proj/mix.jsonl"
|
|
167
|
-
local ts=$(recent_iso 1)
|
|
168
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$ts"
|
|
169
|
-
write_agent_invocation "$sess" "wr-architect:agent" "$ts"
|
|
170
|
-
write_bash_invocation "$sess" "wr-itil-reconcile-readme docs/problems" "$ts"
|
|
171
|
-
|
|
172
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
173
|
-
[ "$status" -eq 0 ]
|
|
174
|
-
# Three distinct NDJSON lines, one per kind
|
|
175
|
-
local line_count
|
|
176
|
-
line_count="$(printf '%s' "$output" | grep -c .)"
|
|
177
|
-
[ "$line_count" -eq 3 ]
|
|
178
|
-
echo "$output" | grep -q '"kind":"skill"'
|
|
179
|
-
echo "$output" | grep -q '"kind":"agent"'
|
|
180
|
-
echo "$output" | grep -q '"kind":"bash-attributed"'
|
|
181
|
-
echo "$output" | grep -q '"plugin":"itil"'
|
|
182
|
-
echo "$output" | grep -q '"plugin":"architect"'
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
@test "Confirmation #1: filters unknown short-form skill names from plugin attribution" {
|
|
186
|
-
local sess="$TRANSCRIPT_ROOT/proj/short.jsonl"
|
|
187
|
-
local ts=$(recent_iso 1)
|
|
188
|
-
# Bare names without `wr-<plugin>:` prefix should be excluded from
|
|
189
|
-
# per-plugin attribution per ADR-058 §Script contracts.
|
|
190
|
-
write_skill_invocation "$sess" "commit" "$ts"
|
|
191
|
-
write_skill_invocation "$sess" "loop" "$ts"
|
|
192
|
-
|
|
193
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
194
|
-
[ "$status" -eq 0 ]
|
|
195
|
-
# No records emitted — both invocations are short-form and unattributable
|
|
196
|
-
[ -z "$output" ]
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
# ── Confirmation #2: opt-out marker fixture ─────────────────────────────────
|
|
200
|
-
|
|
201
|
-
@test "Confirmation #2: opt-out marker disables reads, stderr comment, exit 0" {
|
|
202
|
-
# Seed a transcript that WOULD produce records if read.
|
|
203
|
-
local sess="$TRANSCRIPT_ROOT/proj/optout.jsonl"
|
|
204
|
-
local ts=$(recent_iso 1)
|
|
205
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$ts"
|
|
206
|
-
# Plant the opt-out marker in the project root.
|
|
207
|
-
mkdir -p "$PROJECT_ROOT/.claude"
|
|
208
|
-
touch "$PROJECT_ROOT/.claude/.skill-metrics-opt-out"
|
|
209
|
-
|
|
210
|
-
# Capture stdout and stderr separately so we can assert: zero records
|
|
211
|
-
# on stdout, opt-out comment on stderr, exit 0.
|
|
212
|
-
local stdout_file="$FIXTURE_DIR/optout.out"
|
|
213
|
-
local stderr_file="$FIXTURE_DIR/optout.err"
|
|
214
|
-
"$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT" >"$stdout_file" 2>"$stderr_file"
|
|
215
|
-
local rc=$?
|
|
216
|
-
[ "$rc" -eq 0 ]
|
|
217
|
-
[ ! -s "$stdout_file" ]
|
|
218
|
-
grep -q "opt-out marker present at" "$stderr_file"
|
|
219
|
-
grep -q "$PROJECT_ROOT/.claude/.skill-metrics-opt-out" "$stderr_file"
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
# ── Confirmation #3: no-network-primitive (negative grep carve-out) ─────────
|
|
223
|
-
# ADR-052 carves out this case from "behavioural unless declarative-only".
|
|
224
|
-
# Asserts the canonical script body invokes none of the standard exfiltration
|
|
225
|
-
# primitives. Negative-grep is the closest behavioural approximation of
|
|
226
|
-
# "this script is incapable of network egress".
|
|
227
|
-
|
|
228
|
-
@test "Confirmation #3: canonical body contains no network primitives" {
|
|
229
|
-
# Grep for curl, wget, raw `nc ` (space-bounded to avoid the `since`,
|
|
230
|
-
# `synchronise`, etc. false-positives), fetch, http.client, urllib.
|
|
231
|
-
run grep -nE '\bcurl\b|\bwget\b|\bnc[[:space:]]|\bfetch\b|http\.client|\burllib\b' "$SCRIPT"
|
|
232
|
-
# `run` captures exit; grep exits 1 on no matches — that is the pass.
|
|
233
|
-
[ "$status" -eq 1 ]
|
|
234
|
-
[ -z "$output" ]
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
# ── Confirmation #4: path-hashing / no-content-leak fixture ─────────────────
|
|
238
|
-
|
|
239
|
-
@test "Confirmation #4: literal secret-shaped token never appears in NDJSON" {
|
|
240
|
-
local sess="$TRANSCRIPT_ROOT/proj/secret.jsonl"
|
|
241
|
-
local ts=$(recent_iso 1)
|
|
242
|
-
# Bash command carries an absolute-path-shaped string containing a
|
|
243
|
-
# synthetic secret token. The plugin attribution extracts the wr-itil-*
|
|
244
|
-
# shim name only; the surrounding path and secret are NOT in the
|
|
245
|
-
# emitted NDJSON.
|
|
246
|
-
write_bash_invocation "$sess" "/private/users/foo/password-XXXX-secret/bin/wr-itil-reconcile-readme docs/problems" "$ts"
|
|
247
|
-
|
|
248
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
249
|
-
[ "$status" -eq 0 ]
|
|
250
|
-
# NDJSON contains the surface attribution
|
|
251
|
-
echo "$output" | grep -q '"plugin":"itil"'
|
|
252
|
-
echo "$output" | grep -q '"kind":"bash-attributed"'
|
|
253
|
-
# NDJSON does NOT contain the secret token or the raw absolute path
|
|
254
|
-
! echo "$output" | grep -q "password-XXXX-secret"
|
|
255
|
-
! echo "$output" | grep -q "/private/users/foo"
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
@test "Confirmation #4: any surface containing a project path is sha256-12hex hashed" {
|
|
259
|
-
# Structural assertion on the schema: when path-bearing fields are added
|
|
260
|
-
# in future schema versions, they MUST be 12-hex-char sha256 prefixes.
|
|
261
|
-
# The v1.0 schema does not include path-bearing fields; this fixture
|
|
262
|
-
# asserts the structural invariant by verifying no unhashed path shape
|
|
263
|
-
# surfaces. If a future schema bump introduces a `project_path_hash`
|
|
264
|
-
# field, the assertion regex below catches non-hex shapes.
|
|
265
|
-
local sess="$TRANSCRIPT_ROOT/proj/hash.jsonl"
|
|
266
|
-
local ts=$(recent_iso 1)
|
|
267
|
-
write_bash_invocation "$sess" "wr-itil-reconcile-readme docs/problems" "$ts"
|
|
268
|
-
|
|
269
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
270
|
-
[ "$status" -eq 0 ]
|
|
271
|
-
# Any value matching a `path_hash` field (current or future schema)
|
|
272
|
-
# MUST be exactly 12 lowercase hex chars. Negative regex: no field with
|
|
273
|
-
# value matching a raw path shape.
|
|
274
|
-
! echo "$output" | grep -qE '"[a-z_]+":"/[^"]*"'
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
# ── Confirmation #5: inaccessible-directory fixture ─────────────────────────
|
|
278
|
-
|
|
279
|
-
@test "Confirmation #5: inaccessible --root emits zero records, stderr comment, exit 0" {
|
|
280
|
-
local stdout_file="$FIXTURE_DIR/inacc.out"
|
|
281
|
-
local stderr_file="$FIXTURE_DIR/inacc.err"
|
|
282
|
-
"$SCRIPT" --window-days=30 --root="/nonexistent/path/that/cannot/exist" --project-root="$PROJECT_ROOT" >"$stdout_file" 2>"$stderr_file"
|
|
283
|
-
local rc=$?
|
|
284
|
-
[ "$rc" -eq 0 ]
|
|
285
|
-
[ ! -s "$stdout_file" ]
|
|
286
|
-
grep -q "transcript root inaccessible" "$stderr_file"
|
|
287
|
-
grep -q "/nonexistent/path/that/cannot/exist" "$stderr_file"
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
# ── Forward-extension flag: --category-overrides ────────────────────────────
|
|
291
|
-
# ADR-058 §"Per-category override hook" — ships unused in Phase 2; the flag
|
|
292
|
-
# is accepted-and-validated, no functional effect yet.
|
|
293
|
-
|
|
294
|
-
@test "category-overrides flag is accepted without functional effect" {
|
|
295
|
-
local sess="$TRANSCRIPT_ROOT/proj/cat.jsonl"
|
|
296
|
-
local ts=$(recent_iso 1)
|
|
297
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$ts"
|
|
298
|
-
# Empty JSON file is a valid (no-op) overrides file.
|
|
299
|
-
echo '{}' > "$FIXTURE_DIR/overrides.json"
|
|
300
|
-
|
|
301
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT" --category-overrides="$FIXTURE_DIR/overrides.json"
|
|
302
|
-
[ "$status" -eq 0 ]
|
|
303
|
-
echo "$output" | grep -q '"invocations":1'
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
# ── Window-days filtering ───────────────────────────────────────────────────
|
|
307
|
-
|
|
308
|
-
@test "window-days filter excludes invocations older than the window" {
|
|
309
|
-
local sess="$TRANSCRIPT_ROOT/proj/old.jsonl"
|
|
310
|
-
# Hours-back exceeds the 1-day window (24h).
|
|
311
|
-
local old_ts=$(recent_iso 48)
|
|
312
|
-
local recent_ts=$(recent_iso 1)
|
|
313
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$old_ts"
|
|
314
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$recent_ts"
|
|
315
|
-
|
|
316
|
-
run "$SCRIPT" --window-days=1 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
317
|
-
[ "$status" -eq 0 ]
|
|
318
|
-
# Only the in-window invocation counts; old one drops.
|
|
319
|
-
echo "$output" | grep -q '"invocations":1'
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
# ── Phase 2d: substring-prefilter false-positive fall-through ───────────────
|
|
323
|
-
# Iter 6 (2026-05-17) adds a cheap substring guard before json.loads() to skip
|
|
324
|
-
# lines that cannot possibly contribute counts. The filter checks for the
|
|
325
|
-
# literal substrings `"type":"assistant"` and `"tool_use"` in each line; lines
|
|
326
|
-
# missing either are skipped without parsing. Correctness invariant: any line
|
|
327
|
-
# whose body content (a `type=text` block, a tool_result, a user message
|
|
328
|
-
# rendered into the transcript verbatim) happens to contain those substrings
|
|
329
|
-
# MUST fall through to full JSON parse and the existing not-a-real-tool_use
|
|
330
|
-
# check MUST exclude it from counts. This fixture seeds exactly that scenario:
|
|
331
|
-
# an assistant message carrying a single `type=text` content block whose body
|
|
332
|
-
# literally contains both trigger substrings. The legitimate tool_use line in
|
|
333
|
-
# the same fixture establishes the expected count = 1. Without the existing
|
|
334
|
-
# `c.get("type") == "tool_use"` guard, the false-positive line would inflate
|
|
335
|
-
# counts; the assertion below catches any future regression on the
|
|
336
|
-
# fall-through path.
|
|
337
|
-
|
|
338
|
-
@test "Phase 2d: false-positive substring fall-through does not inflate counts" {
|
|
339
|
-
local sess="$TRANSCRIPT_ROOT/proj/falsepos.jsonl"
|
|
340
|
-
local ts=$(recent_iso 1)
|
|
341
|
-
# One legitimate Skill invocation (counts as 1).
|
|
342
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$ts"
|
|
343
|
-
# One adversarial assistant message: text body contains both trigger
|
|
344
|
-
# substrings but no real tool_use entry. Must NOT add to counts.
|
|
345
|
-
python3 - "$sess" "$ts" <<'PYEOF'
|
|
346
|
-
import json, sys
|
|
347
|
-
file, ts = sys.argv[1], sys.argv[2]
|
|
348
|
-
rec = {
|
|
349
|
-
"type": "assistant",
|
|
350
|
-
"timestamp": ts,
|
|
351
|
-
"message": {
|
|
352
|
-
"role": "assistant",
|
|
353
|
-
"content": [
|
|
354
|
-
{"type": "text", "text": 'discussing "type":"assistant" and "tool_use" tokens in prose'}
|
|
355
|
-
]
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
with open(file, "a") as fh:
|
|
359
|
-
fh.write(json.dumps(rec) + "\n")
|
|
360
|
-
PYEOF
|
|
361
|
-
|
|
362
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
363
|
-
[ "$status" -eq 0 ]
|
|
364
|
-
# Exactly one record (the legitimate Skill invocation); count = 1.
|
|
365
|
-
local line_count
|
|
366
|
-
line_count="$(printf '%s' "$output" | grep -c .)"
|
|
367
|
-
[ "$line_count" -eq 1 ]
|
|
368
|
-
echo "$output" | grep -q '"invocations":1'
|
|
369
|
-
echo "$output" | grep -q '"surface":"wr-itil:manage-problem"'
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
# ── Phase 2e: binary-search-to-first-in-window byte-seek ────────────────────
|
|
373
|
-
# Iter 7 (2026-05-17) adds a binary-search byte-seek before the line iterator
|
|
374
|
-
# for files above a size threshold. JSONL is append-only — older lines appear
|
|
375
|
-
# earlier in the file by author-timestamp monotonicity. The bisect locates the
|
|
376
|
-
# first byte offset whose line carries a timestamp >= cutoff, then scans
|
|
377
|
-
# forward. Files below the threshold linear-scan from byte 0 (bisect overhead
|
|
378
|
-
# is not worth it for small files). Correctness invariants pinned below.
|
|
379
|
-
|
|
380
|
-
# Helper: write a large jsonl that straddles the window cutoff. The first
|
|
381
|
-
# `old_count` lines carry timestamps `old_iso` (out-of-window); the next
|
|
382
|
-
# `new_count` lines carry timestamps `new_iso` (in-window). Pads each record
|
|
383
|
-
# with a `_pad` field so the file is comfortably above the bisect threshold
|
|
384
|
-
# even with modest line counts. Sets mtime to "fresh" so the file-level
|
|
385
|
-
# mtime filter does not skip the file before the bisect runs.
|
|
386
|
-
write_straddle_file() {
|
|
387
|
-
local file="$1"; local old_count="$2"; local new_count="$3"
|
|
388
|
-
local old_iso="$4"; local new_iso="$5"
|
|
389
|
-
mkdir -p "$(dirname "$file")"
|
|
390
|
-
python3 - "$file" "$old_count" "$new_count" "$old_iso" "$new_iso" <<'PYEOF'
|
|
391
|
-
import json, sys
|
|
392
|
-
file, old_count, new_count, old_iso, new_iso = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], sys.argv[5]
|
|
393
|
-
# Pad each record so the file is comfortably > 256 KB even at modest line counts.
|
|
394
|
-
pad = "x" * 2048
|
|
395
|
-
def rec(ts, skill):
|
|
396
|
-
return {
|
|
397
|
-
"type": "assistant",
|
|
398
|
-
"timestamp": ts,
|
|
399
|
-
"_pad": pad,
|
|
400
|
-
"message": {
|
|
401
|
-
"role": "assistant",
|
|
402
|
-
"content": [{"type": "tool_use", "name": "Skill", "input": {"skill": skill}}],
|
|
403
|
-
},
|
|
404
|
-
}
|
|
405
|
-
with open(file, "w") as fh:
|
|
406
|
-
for _ in range(old_count):
|
|
407
|
-
fh.write(json.dumps(rec(old_iso, "wr-itil:manage-problem")) + "\n")
|
|
408
|
-
for _ in range(new_count):
|
|
409
|
-
fh.write(json.dumps(rec(new_iso, "wr-itil:manage-problem")) + "\n")
|
|
410
|
-
PYEOF
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
@test "Phase 2e: byte-seek straddle file counts only in-window lines" {
|
|
414
|
-
local sess="$TRANSCRIPT_ROOT/proj/straddle.jsonl"
|
|
415
|
-
local old_iso=$(recent_iso 1440) # 60 days ago — out-of-window for 30d
|
|
416
|
-
local new_iso=$(recent_iso 1) # 1 hour ago — in-window
|
|
417
|
-
# 200 old + 50 new = 250 lines × ~2.2KB padded = ~550KB → bisect path.
|
|
418
|
-
write_straddle_file "$sess" 200 50 "$old_iso" "$new_iso"
|
|
419
|
-
# Ensure file size is above the 256KB bisect threshold (sanity check).
|
|
420
|
-
local size
|
|
421
|
-
size=$(python3 -c 'import os,sys; print(os.path.getsize(sys.argv[1]))' "$sess")
|
|
422
|
-
[ "$size" -gt 262144 ]
|
|
423
|
-
|
|
424
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
425
|
-
[ "$status" -eq 0 ]
|
|
426
|
-
# Only the 50 in-window invocations count; the 200 historical lines are
|
|
427
|
-
# excluded by the message-level timestamp filter (already correct under
|
|
428
|
-
# linear scan; the bisect must preserve this invariant).
|
|
429
|
-
echo "$output" | grep -q '"invocations":50'
|
|
430
|
-
echo "$output" | grep -q '"surface":"wr-itil:manage-problem"'
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
@test "Phase 2e: byte-seek all-in-window file counts every line (no fallthrough loss)" {
|
|
434
|
-
local sess="$TRANSCRIPT_ROOT/proj/allnew.jsonl"
|
|
435
|
-
local new_iso=$(recent_iso 1)
|
|
436
|
-
# 250 lines × ~2.2KB = ~550KB → bisect path. Bisect finds offset 0 (every
|
|
437
|
-
# line already in-window) and the linear scan from there counts all 250.
|
|
438
|
-
write_straddle_file "$sess" 0 250 "$new_iso" "$new_iso"
|
|
439
|
-
local size
|
|
440
|
-
size=$(python3 -c 'import os,sys; print(os.path.getsize(sys.argv[1]))' "$sess")
|
|
441
|
-
[ "$size" -gt 262144 ]
|
|
442
|
-
|
|
443
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
444
|
-
[ "$status" -eq 0 ]
|
|
445
|
-
echo "$output" | grep -q '"invocations":250'
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
@test "Phase 2e: small file under threshold takes linear-scan path correctly" {
|
|
449
|
-
local sess="$TRANSCRIPT_ROOT/proj/small.jsonl"
|
|
450
|
-
local old_iso=$(recent_iso 1440)
|
|
451
|
-
local new_iso=$(recent_iso 1)
|
|
452
|
-
# Three lines without padding — well under 256KB → linear-scan path.
|
|
453
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$old_iso"
|
|
454
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$new_iso"
|
|
455
|
-
write_skill_invocation "$sess" "wr-itil:manage-problem" "$new_iso"
|
|
456
|
-
local size
|
|
457
|
-
size=$(python3 -c 'import os,sys; print(os.path.getsize(sys.argv[1]))' "$sess")
|
|
458
|
-
[ "$size" -lt 262144 ]
|
|
459
|
-
|
|
460
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
461
|
-
[ "$status" -eq 0 ]
|
|
462
|
-
# 2 in-window (1 old, 2 new); message-timestamp filter excludes the old.
|
|
463
|
-
echo "$output" | grep -q '"invocations":2'
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
@test "Phase 2e: empty large file emits zero records and exits 0" {
|
|
467
|
-
local sess="$TRANSCRIPT_ROOT/proj/empty.jsonl"
|
|
468
|
-
mkdir -p "$(dirname "$sess")"
|
|
469
|
-
# Create empty file with fresh mtime.
|
|
470
|
-
: > "$sess"
|
|
471
|
-
|
|
472
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
473
|
-
[ "$status" -eq 0 ]
|
|
474
|
-
# No records.
|
|
475
|
-
[ -z "$output" ]
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
@test "Phase 2e: non-monotonic timestamps — graceful degradation, no crash, NDJSON well-formed" {
|
|
479
|
-
# Architect advisory (P087 iter-7 review 2026-05-17): pin behaviour under
|
|
480
|
-
# clock-skew / replay where in-window lines appear BEFORE out-of-window
|
|
481
|
-
# lines within the same file. Real Claude Code session jsonl files are
|
|
482
|
-
# append-only by a single process with a monotonic-ish wall clock; this
|
|
483
|
-
# fixture documents the contract under synthetic violation. Bisect MAY
|
|
484
|
-
# under-count under non-monotonic input (it locates the first in-window
|
|
485
|
-
# line by byte position, not by content scan); the contract is that the
|
|
486
|
-
# script exits 0 and emits structurally well-formed NDJSON. ADR-058
|
|
487
|
-
# §Performance amendment pins monotonicity as the input invariant.
|
|
488
|
-
local sess="$TRANSCRIPT_ROOT/proj/nonmono.jsonl"
|
|
489
|
-
local old_iso=$(recent_iso 1440)
|
|
490
|
-
local new_iso=$(recent_iso 1)
|
|
491
|
-
mkdir -p "$(dirname "$sess")"
|
|
492
|
-
# Interleave new / old / new / old ... pattern; padded so file is over
|
|
493
|
-
# threshold and bisect path activates.
|
|
494
|
-
python3 - "$sess" "$old_iso" "$new_iso" <<'PYEOF'
|
|
495
|
-
import json, sys
|
|
496
|
-
file, old_iso, new_iso = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
497
|
-
pad = "y" * 2048
|
|
498
|
-
def rec(ts):
|
|
499
|
-
return {
|
|
500
|
-
"type": "assistant",
|
|
501
|
-
"timestamp": ts,
|
|
502
|
-
"_pad": pad,
|
|
503
|
-
"message": {
|
|
504
|
-
"role": "assistant",
|
|
505
|
-
"content": [{"type": "tool_use", "name": "Skill", "input": {"skill": "wr-itil:manage-problem"}}],
|
|
506
|
-
},
|
|
507
|
-
}
|
|
508
|
-
with open(file, "w") as fh:
|
|
509
|
-
# 200 lines interleaved old/new — non-monotonic on purpose.
|
|
510
|
-
for i in range(200):
|
|
511
|
-
ts = new_iso if i % 2 == 0 else old_iso
|
|
512
|
-
fh.write(json.dumps(rec(ts)) + "\n")
|
|
513
|
-
PYEOF
|
|
514
|
-
local size
|
|
515
|
-
size=$(python3 -c 'import os,sys; print(os.path.getsize(sys.argv[1]))' "$sess")
|
|
516
|
-
[ "$size" -gt 262144 ]
|
|
517
|
-
|
|
518
|
-
run "$SCRIPT" --window-days=30 --root="$TRANSCRIPT_ROOT" --project-root="$PROJECT_ROOT"
|
|
519
|
-
[ "$status" -eq 0 ]
|
|
520
|
-
# Structurally well-formed: zero-or-one record, each line valid JSON, no
|
|
521
|
-
# crash. Exact count is NOT pinned — bisect under-count under non-monotonic
|
|
522
|
-
# input is documented graceful degradation per ADR-058 amendment.
|
|
523
|
-
if [ -n "$output" ]; then
|
|
524
|
-
echo "$output" | python3 -c "
|
|
525
|
-
import json, sys
|
|
526
|
-
for line in sys.stdin.read().splitlines():
|
|
527
|
-
if not line: continue
|
|
528
|
-
rec = json.loads(line)
|
|
529
|
-
assert rec['schema_version'] == '1.0', rec
|
|
530
|
-
assert rec['axis'] == 'skill-invocations', rec
|
|
531
|
-
assert isinstance(rec.get('invocations'), int), rec
|
|
532
|
-
assert rec['invocations'] >= 0, rec
|
|
533
|
-
"
|
|
534
|
-
fi
|
|
535
|
-
}
|