@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 +2 -23
- package/bin/wr-itil-effort-tally +3 -0
- package/package.json +1 -1
- package/scripts/effort-tally.sh +74 -0
- package/scripts/test/effort-tally.bats +79 -0
- package/skills/work-problems/SKILL.md +8 -0
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
|
|
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
|
|
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
|
package/package.json
CHANGED
|
@@ -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 \
|