@windyroad/itil 0.35.7-preview.365 → 0.35.7-preview.387

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/README.md CHANGED
@@ -56,7 +56,7 @@ Supports creating new problems, updating root cause analysis, transitioning stat
56
56
 
57
57
  Supports declaring new incidents, recording evidence-first observations and hypotheses, logging mitigation attempts, transitioning lifecycle (Investigating → Mitigating → Restored → Closed), and automatically handing off to `manage-problem` when service is restored.
58
58
 
59
- See [ADR-011](../../docs/decisions/011-manage-incident-skill.proposed.md) for the incident-vs-problem split and [JTBD-201](../../docs/jtbd/tech-lead/JTBD-201-restore-service-fast.proposed.md) for the job this serves.
59
+ See [ADR-011](../../docs/decisions/011-manage-incident-skill.proposed.md) for the incident-vs-problem split.
60
60
 
61
61
  ## How It Works
62
62
 
@@ -86,7 +86,7 @@ See [ADR-011](../../docs/decisions/011-manage-incident-skill.proposed.md) for th
86
86
  | `/wr-itil:review-problems` | Re-rate every open and known-error ticket and refresh the WSJF ranking | Experimental |
87
87
  | `/wr-itil:reconcile-readme` | Detect and correct drift between `docs/problems/README.md` and on-disk ticket inventory | Experimental |
88
88
  | `/wr-itil:report-upstream` | Report a local problem as a structured issue against an upstream repository (ADR-024) | Experimental |
89
- | `/wr-itil:check-upstream-responses` | Poll upstream issues we filed via `/wr-itil:report-upstream` and surface new comments / state changes / label changes since last check (P249 Phase 1; outbound symmetric counterpart to ADR-062 inbound discovery; serves [JTBD-004](../../docs/jtbd/solo-developer/JTBD-004-connect-agents.proposed.md)) | Experimental |
89
+ | `/wr-itil:check-upstream-responses` | Poll upstream issues we filed via `/wr-itil:report-upstream` and surface new comments / state changes / label changes since last check (P249 Phase 1; outbound symmetric counterpart to ADR-062 inbound discovery) | Experimental |
90
90
  | `/wr-itil:capture-rfc` | Lightweight RFC-capture skill — mandatory problem-trace per ADR-060 I1 invariant; opens a coordinated multi-commit change traceable to ≥ 1 driving problem (Phase 1 of the Problem-RFC-Story framework, P170 / ADR-060) | Experimental |
91
91
  | `/wr-itil:manage-rfc` | Heavyweight RFC intake + lifecycle management — proposed → accepted → in-progress → verifying → closed; sibling to `manage-problem` at the RFC tier (ADR-060) | Experimental |
92
92
  | `/wr-itil:capture-story` | Lightweight story-capture skill — mandatory problem-trace AND JTBD-trace per ADR-060 I6 + I9 invariants; optional `--rfc` / `--story-map` flags (I7 + I8 enforce at `accepted` transition); drafts an INVEST-shaped sub-workstream entity under a parent RFC (Phase 2 of the Problem-RFC-Story framework, P170 / ADR-060) | Experimental |
@@ -102,27 +102,6 @@ See [ADR-011](../../docs/decisions/011-manage-incident-skill.proposed.md) for th
102
102
  | `/wr-itil:mitigate-incident` / `/wr-itil:restore-incident` / `/wr-itil:close-incident` / `/wr-itil:link-incident` | Incident lifecycle transitions (ADR-011) | Experimental |
103
103
  | `/wr-itil:scaffold-intake` | Scaffold OSS intake surfaces (`.github/ISSUE_TEMPLATE/`, `SECURITY.md`, `SUPPORT.md`, `CONTRIBUTING.md`) for downstream adopters (ADR-036) | Experimental |
104
104
 
105
- ## Jobs to be Done
106
-
107
- This plugin serves the [Jobs to be Done](../../docs/jtbd/) below. Per [ADR-051](../../docs/decisions/051-jtbd-anchored-readme-with-drift-advisory.proposed.md), the persona-grouped JTBD anchor is the canonical source of truth for the README's value framing.
108
-
109
- ### Plugin user
110
-
111
- - **[JTBD-301 Report a Problem Without Pre-Classifying It](../../docs/jtbd/plugin-user/JTBD-301-report-problem-without-pre-classifying.proposed.md)** — adopters who hit a problem with an installed `@windyroad/*` plugin describe what they observed; `/wr-itil:scaffold-intake` provisions the intake template downstream so triage decides the category, not the reporter.
112
-
113
- ### Tech lead / consultant
114
-
115
- - **[JTBD-201 Restore Service Fast with an Audit Trail](../../docs/jtbd/tech-lead/JTBD-201-restore-service-fast.proposed.md)** — the manage-incident skill carries an evidence-first lifecycle (investigating → mitigating → restored → closed), with handoff to manage-problem for the root-cause work.
116
-
117
- ### Solo developer
118
-
119
- - **[JTBD-006 Progress the Backlog While I'm Away](../../docs/jtbd/solo-developer/JTBD-006-work-backlog-afk.proposed.md)** — `/wr-itil:work-problems` is the AFK orchestrator that loops through the WSJF-ranked backlog, working tickets without interactive input until quota or a stop condition fires.
120
- - **[JTBD-008 Decompose a Fix Into Coordinated Changes](../../docs/jtbd/solo-developer/JTBD-008-decompose-fix-into-coordinated-changes.proposed.md)** — `/wr-itil:capture-rfc` + `/wr-itil:manage-rfc` are the capture-time decomposition surface for multi-commit coordinated changes traced to a driving problem (Phase 1); `/wr-itil:capture-story` is the INVEST-shaped sub-workstream surface for individual slices under those coordinated changes (Phase 2 — story tier). The I1 trace-to-problem invariant is gate-enforced at capture-rfc time; I6 + I9 problem-and-JTBD-trace invariants are gate-enforced at capture-story time (P170 / ADR-060).
121
-
122
- ### Plugin user (currency anchor)
123
-
124
- - **[JTBD-302 Trust That the README Describes the Plugin I Just Installed](../../docs/jtbd/plugin-user/JTBD-302-trust-readme-describes-installed-behaviour.proposed.md)** — this README is anchored on current JTBD job IDs; drift between prose and shipped behaviour is detectable at retro time per ADR-051.
125
-
126
105
  ## Updating and Uninstalling
127
106
 
128
107
  ```bash
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical effort-tally script.
3
+ exec "$(dirname "$0")/../scripts/effort-tally.sh" "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.35.7-preview.365",
3
+ "version": "0.35.7-preview.387",
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"
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ # wr-itil — per-ticket effort tally from AFK iteration cost metadata (ADR-067, P248)
3
+ #
4
+ # @jtbd JTBD-006 (Progress the Backlog While I'm Away — AFK actuals feed WSJF calibration)
5
+ # @jtbd JTBD-202 (Run Pre-Flight Governance Checks — structured, auditable effort tally)
6
+ #
7
+ # Attributes `.afk-run-state/iter*.json` actuals back to their source ticket
8
+ # (the `pNNN` token in the filename) and emits one tally line per ticket in the
9
+ # `## Effort Tally` schema. The reusable core shared by:
10
+ # - the backfill (seed historical tickets — ADR-067 Decision Outcome item 4)
11
+ # - go-forward per-iter append (work-problems — ADR-067 item 2)
12
+ #
13
+ # Authority hierarchy (P089 Gap 2 — load-bearing): `total_cost_usd` is the
14
+ # AUTHORITATIVE actual (session-cumulative by CLI contract; reliable token-spend
15
+ # proxy). `duration_ms` is reliable wall-clock. Raw `usage.*` token counts are
16
+ # BEST-EFFORT (undercount when a subprocess exits on a background-ack turn) and
17
+ # are emitted with a `~` best-effort marker.
18
+ #
19
+ # Usage:
20
+ # effort-tally.sh [AFK_DIR]
21
+ # AFK_DIR defaults to .afk-run-state
22
+ #
23
+ # Output (stdout): one line per ticket, sorted by descending cost:
24
+ # P<NNN> | iters=<N> | cost_usd=<authoritative> | minutes=<reliable> | tokens=~<best-effort>M
25
+ # Always exits 0.
26
+
27
+ set -euo pipefail
28
+
29
+ AFK_DIR="${1:-.afk-run-state}"
30
+ [ -d "$AFK_DIR" ] || exit 0
31
+
32
+ python3 - "$AFK_DIR" <<'PY'
33
+ import json, glob, os, re, sys
34
+ from collections import defaultdict
35
+
36
+ afk_dir = sys.argv[1]
37
+
38
+ def cost_obj(d):
39
+ """Return the dict carrying total_cost_usd, whether d is a dict or an event list."""
40
+ if isinstance(d, dict):
41
+ return d if d.get("total_cost_usd") is not None else None
42
+ if isinstance(d, list):
43
+ for item in reversed(d):
44
+ if isinstance(item, dict) and item.get("total_cost_usd") is not None:
45
+ return item
46
+ return None
47
+
48
+ agg = defaultdict(lambda: {"cost": 0.0, "dur_ms": 0, "tokens": 0, "iters": 0})
49
+ for path in glob.glob(os.path.join(afk_dir, "*.json")):
50
+ m = re.search(r'p(\d{3})', os.path.basename(path))
51
+ if not m:
52
+ continue
53
+ tid = "P" + m.group(1)
54
+ try:
55
+ with open(path) as fh:
56
+ d = json.load(fh)
57
+ except Exception:
58
+ continue
59
+ o = cost_obj(d)
60
+ if not o:
61
+ continue
62
+ a = agg[tid]
63
+ a["cost"] += float(o.get("total_cost_usd") or 0)
64
+ a["dur_ms"] += int(o.get("duration_ms") or 0)
65
+ u = o.get("usage") or {}
66
+ a["tokens"] += sum(int(u.get(k) or 0) for k in
67
+ ("input_tokens", "output_tokens",
68
+ "cache_creation_input_tokens", "cache_read_input_tokens"))
69
+ a["iters"] += 1
70
+
71
+ for tid, a in sorted(agg.items(), key=lambda kv: kv[1]["cost"], reverse=True):
72
+ print(f"{tid} | iters={a['iters']} | cost_usd={a['cost']:.2f} | "
73
+ f"minutes={a['dur_ms']/60000:.1f} | tokens=~{a['tokens']/1e6:.1f}M")
74
+ PY
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # ADR-067 / P248: effort-tally.sh attributes .afk-run-state/iter*.json actuals
4
+ # back to their source ticket (pNNN filename token) and emits the per-ticket
5
+ # tally. Behavioural — exercises the script against fixture iter-JSON trees.
6
+
7
+ setup() {
8
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
9
+ SCRIPT="$REPO_ROOT/packages/itil/scripts/effort-tally.sh"
10
+ DIR="$(mktemp -d)"
11
+ mkdir -p "$DIR/.afk-run-state"
12
+ }
13
+
14
+ teardown() { rm -rf "$DIR"; }
15
+
16
+ mk_iter() { # mk_iter <filename> <cost> <duration_ms> <input_tokens>
17
+ cat > "$DIR/.afk-run-state/$1" <<EOF
18
+ {"total_cost_usd": $2, "duration_ms": $3, "usage": {"input_tokens": $4, "output_tokens": 0, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}
19
+ EOF
20
+ }
21
+
22
+ @test "attributes a single iter to its ticket via the pNNN filename token" {
23
+ mk_iter "iter1-p087.json" 12.50 600000 1000000
24
+ run bash "$SCRIPT" "$DIR/.afk-run-state"
25
+ [ "$status" -eq 0 ]
26
+ [[ "$output" == *"P087"* ]]
27
+ [[ "$output" == *"cost_usd=12.50"* ]]
28
+ [[ "$output" == *"minutes=10.0"* ]]
29
+ }
30
+
31
+ @test "sums multiple iters for the same ticket" {
32
+ mk_iter "iter1-p087.json" 10.00 300000 500000
33
+ mk_iter "iter2-p087.json" 5.00 300000 500000
34
+ run bash "$SCRIPT" "$DIR/.afk-run-state"
35
+ [[ "$output" == *"P087 | iters=2 | cost_usd=15.00"* ]]
36
+ }
37
+
38
+ @test "authoritative cost comes from total_cost_usd; tokens flagged best-effort with ~" {
39
+ mk_iter "iter1-p100.json" 7.00 60000 2000000
40
+ run bash "$SCRIPT" "$DIR/.afk-run-state"
41
+ [[ "$output" == *"cost_usd=7.00"* ]]
42
+ [[ "$output" == *"tokens=~2.0M"* ]]
43
+ }
44
+
45
+ @test "tickets are sorted by descending cost" {
46
+ mk_iter "iter1-p010.json" 3.00 60000 100000
47
+ mk_iter "iter1-p020.json" 30.00 60000 100000
48
+ run bash "$SCRIPT" "$DIR/.afk-run-state"
49
+ # P020 (30.00) must appear before P010 (3.00)
50
+ [[ "$(echo "$output" | grep -n P020 | cut -d: -f1)" -lt "$(echo "$output" | grep -n P010 | cut -d: -f1)" ]]
51
+ }
52
+
53
+ @test "handles JSON-array (event-stream) shape, not just a single object" {
54
+ cat > "$DIR/.afk-run-state/iter1-p050.json" <<'EOF'
55
+ [{"type":"system"},{"type":"result","total_cost_usd":9.00,"duration_ms":120000,"usage":{"input_tokens":3000000,"output_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}]
56
+ EOF
57
+ run bash "$SCRIPT" "$DIR/.afk-run-state"
58
+ [[ "$output" == *"P050"* ]]
59
+ [[ "$output" == *"cost_usd=9.00"* ]]
60
+ }
61
+
62
+ @test "files without a pNNN token are ignored" {
63
+ mk_iter "drain-push.json" 99.00 60000 100000
64
+ cp "$DIR/.afk-run-state/drain-push.json" "$DIR/.afk-run-state/work-problems-session-totals.json"
65
+ run bash "$SCRIPT" "$DIR/.afk-run-state"
66
+ [ -z "$output" ]
67
+ }
68
+
69
+ @test "files without total_cost_usd are skipped" {
70
+ echo '{"pid": 1234, "start": 999}' > "$DIR/.afk-run-state/iter1-p077.json"
71
+ run bash "$SCRIPT" "$DIR/.afk-run-state"
72
+ [ -z "$output" ]
73
+ }
74
+
75
+ @test "missing afk dir exits 0 with no output" {
76
+ run bash "$SCRIPT" "$DIR/nonexistent"
77
+ [ "$status" -eq 0 ]
78
+ [ -z "$output" ]
79
+ }
@@ -317,6 +317,14 @@ IDLE_TIMEOUT_S="${WORK_PROBLEMS_IDLE_TIMEOUT_S:-3600}"
317
317
  # into iter subprocesses' first turn.
318
318
  export WR_SUPPRESS_PENDING_QUESTIONS=1
319
319
 
320
+ # AFK-iter oversight-nudge suppression (ADR-066): the architect plugin's
321
+ # SessionStart oversight nudge ("N decisions lack human oversight — run
322
+ # /wr-architect:review-decisions") is an interactive batch-confirm prompt. It
323
+ # must NOT fire into an absent-user iter subprocess. architect-oversight-nudge.sh
324
+ # self-suppresses when this env var is set — same discipline as the
325
+ # pending-questions guard above (JTBD-006 friction guard).
326
+ export WR_SUPPRESS_OVERSIGHT_NUDGE=1
327
+
320
328
  claude -p \
321
329
  --permission-mode bypassPermissions \
322
330
  --output-format json \