@techierathore/techieflow 1.0.3 → 1.1.0
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/commands/TechieFlow/agents/analyst.md +5 -8
- package/.claude/commands/TechieFlow/agents/architect.md +1 -10
- package/.claude/commands/TechieFlow/agents/flow-master.md +1 -0
- package/.claude/commands/TechieFlow/agents/verifier.md +1 -0
- package/.claude/commands/TechieFlow/tasks/_owner-language.md +27 -0
- package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +1 -1
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +4 -3
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +6 -5
- package/.claude/commands/TechieFlow/tasks/build-phase.md +1 -1
- package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +4 -0
- package/.claude/commands/TechieFlow/tasks/create-doc.md +4 -0
- package/.claude/commands/TechieFlow/tasks/create-project-brief.md +22 -0
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +3 -3
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +3 -3
- package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +4 -0
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +2 -2
- package/.claude/commands/TechieFlow/tasks/generate-html.md +7 -3
- package/.claude/commands/TechieFlow/tasks/handoff-phase.md +20 -18
- package/.claude/commands/TechieFlow/tasks/metrics-report.md +2 -0
- package/.claude/commands/TechieFlow/tasks/mockups.md +1 -1
- package/.claude/commands/TechieFlow/tasks/productguide.md +2 -2
- package/.claude/commands/TechieFlow/tasks/render-workflow-docs.md +1 -1
- package/.claude/commands/TechieFlow/tasks/triage-issues.md +2 -2
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +1 -1
- package/.opencode/plugin/techieflow.js +181 -9
- package/.tfcore/agents/analyst.md +5 -8
- package/.tfcore/agents/architect.md +1 -10
- package/.tfcore/agents/flow-master.md +1 -0
- package/.tfcore/agents/verifier.md +1 -0
- package/.tfcore/hooks/block-git.sh +4 -2
- package/.tfcore/hooks/guard-build.sh +5 -1
- package/.tfcore/hooks/guard-db.sh +6 -4
- package/.tfcore/hooks/guard-metrics.sh +13 -1
- package/.tfcore/hooks/guard-status-html.sh +86 -2
- package/.tfcore/hooks/guard-verify-deps.sh +124 -0
- package/.tfcore/hooks/guard-verify.sh +8 -3
- package/.tfcore/hooks/sweep-artifacts.sh +46 -0
- package/.tfcore/routing.yaml +61 -0
- package/.tfcore/standards/owner-words.txt +23 -0
- package/.tfcore/tasks/_owner-language.md +27 -0
- package/.tfcore/tasks/_smoke-test-policy.md +1 -1
- package/.tfcore/tasks/_status-update-gate.md +4 -3
- package/.tfcore/tasks/amend-docs.md +6 -5
- package/.tfcore/tasks/build-phase.md +1 -1
- package/.tfcore/tasks/create-deep-research-prompt.md +4 -0
- package/.tfcore/tasks/create-doc.md +4 -0
- package/.tfcore/tasks/create-project-brief.md +22 -0
- package/.tfcore/tasks/day1-brownfield.md +3 -3
- package/.tfcore/tasks/day1-greenfield.md +3 -3
- package/.tfcore/tasks/facilitate-brainstorming-session.md +4 -0
- package/.tfcore/tasks/fix-issues.md +2 -2
- package/.tfcore/tasks/generate-html.md +7 -3
- package/.tfcore/tasks/handoff-phase.md +20 -18
- package/.tfcore/tasks/metrics-report.md +2 -0
- package/.tfcore/tasks/mockups.md +1 -1
- package/.tfcore/tasks/productguide.md +2 -2
- package/.tfcore/tasks/render-workflow-docs.md +1 -1
- package/.tfcore/tasks/triage-issues.md +2 -2
- package/.tfcore/tasks/verify-phase.md +1 -1
- package/.tfcore/telemetry/SCHEMA.md +61 -1
- package/.tfcore/telemetry/install-metrics.sh +3 -2
- package/.tfcore/telemetry/tf-metrics.sh +435 -21
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +6 -0
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +5 -3
- package/.tfcore/templates/v4custom/app-brief-tmpl.md +65 -0
- package/.tfcore/templates/v4custom/app-decision-request-tmpl.md +85 -0
- package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +86 -31
- package/.tfcore/templates/v4custom/app-phase-brd-tmpl.md +94 -0
- package/.tfcore/templates/v4custom/app-phase-uidesign-tmpl.md +60 -0
- package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +4 -2
- package/.tfcore/templates/v4custom/build-subagent-prompt.md +2 -2
- package/.tfcore/templates/v4custom/html-render-shell.md +4 -2
- package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
- package/.tfcore/utils/tf-assets-browser.mjs +96 -0
- package/.tfcore/utils/tf-assets.sh +97 -12
- package/.tfcore/utils/tf-brd-status.py +14 -5
- package/.tfcore/utils/tf-build-list.py +99 -11
- package/.tfcore/utils/tf-build-list.sh +5 -3
- package/.tfcore/utils/tf-build.sh +88 -15
- package/.tfcore/utils/tf-ci-repro.py +685 -0
- package/.tfcore/utils/tf-ci-repro.sh +24 -0
- package/.tfcore/utils/tf-devguide-list.py +47 -22
- package/.tfcore/utils/tf-doc-check.py +242 -10
- package/.tfcore/utils/tf-doc-tests.sh +79 -0
- package/.tfcore/utils/tf-emit.sh +413 -19
- package/.tfcore/utils/tf-feedback.sh +13 -0
- package/.tfcore/utils/tf-fix-close.sh +113 -31
- package/.tfcore/utils/tf-gitignore-audit.sh +50 -3
- package/.tfcore/utils/tf-goal.sh +105 -18
- package/.tfcore/utils/tf-harness.sh +9 -4
- package/.tfcore/utils/tf-lock.sh +29 -0
- package/.tfcore/utils/tf-log-miss.py +34 -8
- package/.tfcore/utils/tf-login.mjs +167 -0
- package/.tfcore/utils/tf-misses-md.py +24 -21
- package/.tfcore/utils/tf-misses-md.sh +5 -4
- package/.tfcore/utils/tf-mockup-parity.mjs +427 -63
- package/.tfcore/utils/tf-mockup-parity.sh +14 -2
- package/.tfcore/utils/tf-model-pick.sh +475 -0
- package/.tfcore/utils/tf-owner-text.py +233 -0
- package/.tfcore/utils/tf-owner-text.sh +14 -0
- package/.tfcore/utils/tf-phase.sh +33 -1
- package/.tfcore/utils/tf-portable.sh +230 -0
- package/.tfcore/utils/tf-render-html.py +80 -2
- package/.tfcore/utils/tf-routing-bind.sh +87 -1
- package/.tfcore/utils/tf-routing.sh +7 -0
- package/.tfcore/utils/tf-selfcheck.sh +356 -0
- package/.tfcore/utils/tf-split-brd.py +17 -2
- package/.tfcore/utils/tf-status-evidence.sh +11 -9
- package/.tfcore/utils/tf-status-facts.py +133 -44
- package/.tfcore/utils/tf-triage.py +62 -16
- package/.tfcore/utils/tf-triage.sh +2 -0
- package/.tfcore/utils/tf-verify-boot.sh +263 -43
- package/.tfcore/utils/tf-verify-emit.sh +20 -0
- package/.tfcore/utils/tf-verify-env.sh +33 -4
- package/.tfcore/utils/tf-verify-list.py +75 -8
- package/.tfcore/utils/tf-verify-screens.mjs +254 -43
- package/.tfcore/utils/tf-verify-screens.sh +18 -4
- package/.tfcore/utils/tf-verify-tests.sh +272 -28
- package/.tfcore/utils/tf-verify-verdict.py +59 -11
- package/.tfcore/utils/tf_defect.py +27 -0
- package/.tfcore/utils/tf_feedback.py +209 -0
- package/.tfcore/utils/tf_roadmap.py +35 -0
- package/README.md +19 -5
- package/docs/TechieFlow-Installation.md +5 -3
- package/opencode.jsonc +24 -0
- package/package.json +1 -1
- package/scripts/install.mjs +4 -0
- package/.tfcore/templates/architecture-tmpl.yaml +0 -650
- package/.tfcore/templates/brownfield-architecture-tmpl.yaml +0 -476
- package/.tfcore/templates/competitor-analysis-tmpl.yaml +0 -306
- package/.tfcore/templates/front-end-architecture-tmpl.yaml +0 -218
- package/.tfcore/templates/fullstack-architecture-tmpl.yaml +0 -823
- package/.tfcore/templates/market-research-tmpl.yaml +0 -252
- package/.tfcore/templates/project-brief-tmpl.yaml +0 -221
- package/.tfcore/utils/techieflow-doc-template.md +0 -325
|
@@ -26,7 +26,7 @@ set -uo pipefail
|
|
|
26
26
|
|
|
27
27
|
command -v python3 >/dev/null 2>&1 || { echo "tf-metrics: python3 is required." >&2; exit 1; }
|
|
28
28
|
|
|
29
|
-
exec python3 - "$@" <<'PYEOF'
|
|
29
|
+
TF_METRICS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" exec python3 - "$@" <<'PYEOF'
|
|
30
30
|
import glob
|
|
31
31
|
import json
|
|
32
32
|
import os
|
|
@@ -53,12 +53,77 @@ LATE_GATES = {"perf": "2026-08-10", "assets": "2026-08-31", "mockup-parity": "20
|
|
|
53
53
|
# fill, so counting it as "not assessed" understates the practice being measured.
|
|
54
54
|
# It leaves that field's denominator and is reported separately. SCHEMA.md §5.5.6 /
|
|
55
55
|
# §3.5. Keep this table in sync when an optional field is added to any stream.
|
|
56
|
-
FIELD_SINCE = {"why_missed": "2026-08-28", "sort": "2026-09-07"}
|
|
56
|
+
FIELD_SINCE = {"why_missed": "2026-08-28", "sort": "2026-09-07", "what": "2026-09-07"}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _span(r):
|
|
60
|
+
"""Seconds between a run's own `started` and `ended`, or None if they cannot be read."""
|
|
61
|
+
a, b = r.get("started"), r.get("ended")
|
|
62
|
+
if not isinstance(a, str) or not isinstance(b, str):
|
|
63
|
+
return None
|
|
64
|
+
try:
|
|
65
|
+
fmt = "%Y-%m-%dT%H:%M:%SZ"
|
|
66
|
+
return int((datetime.strptime(b, fmt) - datetime.strptime(a, fmt)).total_seconds())
|
|
67
|
+
except Exception:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _dur(r):
|
|
72
|
+
"""A run's duration in seconds, or None when the record does not carry a real one.
|
|
73
|
+
|
|
74
|
+
THE TIMESTAMPS WIN. A record stores a start, an end and a duration, and on a stream
|
|
75
|
+
written before the emitter checked them the third can contradict the first two:
|
|
76
|
+
TechieBlog holds 14 such records, one storing -166 and thirteen storing a plausible
|
|
77
|
+
round number (3600, 2700, 1800, 900, 600) that bears no relation to their own clocks
|
|
78
|
+
-- `refresh-status` started 20:05:00, ended 17:59:39, stored 600. Only the one that
|
|
79
|
+
stored the negative was ever detectable by reading the number alone.
|
|
80
|
+
|
|
81
|
+
So the duration is taken from the timestamps whenever they can be read, and a record
|
|
82
|
+
whose timestamps are themselves impossible carries NO duration rather than a
|
|
83
|
+
plausible one. The count of both is published (`duration_excluded_n`,
|
|
84
|
+
`duration_recomputed_n`) and printed, because "32 records, correct" is only honest
|
|
85
|
+
when the reader is also told that 14 were dropped. Every consumer goes through this
|
|
86
|
+
one function, so the two readers of a stream cannot disagree about the same record.
|
|
87
|
+
tests/regression/run.sh tf_015."""
|
|
88
|
+
span = _span(r)
|
|
89
|
+
if span is not None:
|
|
90
|
+
return span if span > 0 else None
|
|
91
|
+
d = r.get("duration_s")
|
|
92
|
+
return d if isinstance(d, (int, float)) and d > 0 else None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _dur_quality(records):
|
|
96
|
+
"""(impossible, absent, recomputed) -- what _dur changed, split so the report can be
|
|
97
|
+
accurate rather than merely alarming.
|
|
98
|
+
|
|
99
|
+
"Its start is after its end" and "it never carried a duration" are different facts and
|
|
100
|
+
were briefly reported as one, which made this repo look like it held 30 corrupt records
|
|
101
|
+
when it holds 30 records that simply record no elapsed time (`started` == `ended`, no
|
|
102
|
+
`duration_s`). Neither reached a figure before or after -- but a reader told the wrong
|
|
103
|
+
reason chases the wrong thing."""
|
|
104
|
+
impossible = absent = recomputed = 0
|
|
105
|
+
for r in records:
|
|
106
|
+
span, stored = _span(r), r.get("duration_s")
|
|
107
|
+
has_stored = isinstance(stored, (int, float)) and stored > 0
|
|
108
|
+
if span is not None and span < 0:
|
|
109
|
+
impossible += 1
|
|
110
|
+
elif span is not None and span == 0:
|
|
111
|
+
absent += 1
|
|
112
|
+
elif span is None and not has_stored:
|
|
113
|
+
absent += 1
|
|
114
|
+
elif span is not None and has_stored and abs(span - stored) > 1:
|
|
115
|
+
recomputed += 1
|
|
116
|
+
return impossible, absent, recomputed
|
|
57
117
|
|
|
58
118
|
# Fields a `miss-amend` record may complete, and their closed vocabularies.
|
|
59
119
|
# Kept identical to tf-emit.sh's `_AMENDABLE`; the emitter enforces it on write and
|
|
60
120
|
# this enforces it again on read, because a merged stream can carry records this
|
|
61
121
|
# machine's emitter never saw. SCHEMA.md §5.5.7 states the rule for extending it.
|
|
122
|
+
# `what` is the miss's own sentence: free text, not a vocabulary. Kept amendable
|
|
123
|
+
# because the never-overwrite rule -- not the vocabulary -- is what stops an amend
|
|
124
|
+
# altering a fact. MISS-TechieFlow-20260909-02; tests/regression/run.sh tf_018.
|
|
125
|
+
AMENDABLE_TEXT_FIELDS = ("what",)
|
|
126
|
+
|
|
62
127
|
AMENDABLE_FIELDS = {
|
|
63
128
|
"why_missed": ("missing-checklist-item", "insufficient-verify-method",
|
|
64
129
|
"code-audit-limitation", "ambiguous-acceptance",
|
|
@@ -71,6 +136,72 @@ SORT_WORDS = {"spec": "the app's spec did not say it", "unsaid": "the framework
|
|
|
71
136
|
MIN_N = 3 # fewer supporting records than this -> "insufficient data", never a number
|
|
72
137
|
|
|
73
138
|
|
|
139
|
+
# --------------------------------------------------- list price (report time)
|
|
140
|
+
# WHERE PRICING BELONGS (owner, 2026-09-10). The framework's job is to record what it MEASURED:
|
|
141
|
+
# tokens, per model, and whatever cost the provider itself reported. Turning tokens into money is
|
|
142
|
+
# a reporting job — this file's, and TfLens's — so it happens HERE, at report time, from the
|
|
143
|
+
# fields already on the stream. Nothing priced is ever written to a record. That is SCHEMA.md §8's
|
|
144
|
+
# existing rule ("derived metrics are computed, never stored") applied to money, and it is why a
|
|
145
|
+
# rate card can change, or a reader disagree with this one, without a single record being wrong.
|
|
146
|
+
#
|
|
147
|
+
# The rate itself comes from `tf-model-pick.sh rate`, so the rate card is defined once and the
|
|
148
|
+
# project's own `.tfcore/rate-card.json` wins where it exists.
|
|
149
|
+
_RATE_CACHE = {}
|
|
150
|
+
REPO_OF = {} # id(record) -> the repo it was read from; never written into a record
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def rate_for(repo, model):
|
|
154
|
+
key = (repo, model)
|
|
155
|
+
if key not in _RATE_CACHE:
|
|
156
|
+
got = None
|
|
157
|
+
for base in (os.path.join(repo, ".tfcore", "utils"),
|
|
158
|
+
os.path.join(os.environ.get("TF_METRICS_DIR", ""), "..", "utils")):
|
|
159
|
+
script = os.path.join(base, "tf-model-pick.sh")
|
|
160
|
+
if not os.path.isfile(script):
|
|
161
|
+
continue
|
|
162
|
+
try:
|
|
163
|
+
out = subprocess.run(["bash", script, "rate", model],
|
|
164
|
+
capture_output=True, text=True, timeout=20,
|
|
165
|
+
env=dict(os.environ, TF_PROJECT_DIR=repo))
|
|
166
|
+
if out.returncode == 0:
|
|
167
|
+
parts = (out.stdout or "").strip().split("\t")
|
|
168
|
+
if len(parts) >= 4:
|
|
169
|
+
got = tuple(float(x) for x in parts[:4])
|
|
170
|
+
break
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
_RATE_CACHE[key] = got
|
|
174
|
+
return _RATE_CACHE[key]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def list_price(repo, rec):
|
|
178
|
+
"""What one run's measured tokens cost at list price, or None when nothing prices its model.
|
|
179
|
+
|
|
180
|
+
Output tokens are exact per model (`model_tokens_out`). Input and cache tokens are counted for
|
|
181
|
+
the window as a whole, so where a window ran several models they are shared out by each
|
|
182
|
+
model's output share — arithmetic, and the caller is told."""
|
|
183
|
+
split = rec.get("model_tokens_out")
|
|
184
|
+
if not isinstance(split, dict) or not split:
|
|
185
|
+
m = rec.get("model")
|
|
186
|
+
if not m or rec.get("tokens_out") is None:
|
|
187
|
+
return None
|
|
188
|
+
split = {m: rec.get("tokens_out") or 0}
|
|
189
|
+
total_out = sum(v or 0 for v in split.values())
|
|
190
|
+
ti = rec.get("tokens_in") or 0
|
|
191
|
+
tcr = rec.get("tokens_cache_read") or 0
|
|
192
|
+
tcw = rec.get("tokens_cache_write") or 0
|
|
193
|
+
usd, priced = 0.0, 0
|
|
194
|
+
for m, out in split.items():
|
|
195
|
+
r = rate_for(repo, m)
|
|
196
|
+
if not r:
|
|
197
|
+
continue
|
|
198
|
+
share = (float(out or 0) / total_out) if total_out else 0.0
|
|
199
|
+
usd += (ti * share * r[0] + (out or 0) * r[1]
|
|
200
|
+
+ tcr * share * r[2] + tcw * share * r[3]) / 1_000_000.0
|
|
201
|
+
priced += 1
|
|
202
|
+
return round(usd, 6) if priced else None
|
|
203
|
+
|
|
204
|
+
|
|
74
205
|
# ---------------------------------------------------------------- utilities
|
|
75
206
|
def die(msg):
|
|
76
207
|
sys.stderr.write("tf-metrics: %s\n" % msg)
|
|
@@ -99,6 +230,42 @@ def read_stream(repo, stream):
|
|
|
99
230
|
return out
|
|
100
231
|
|
|
101
232
|
|
|
233
|
+
def apply_voids(runs, app=None):
|
|
234
|
+
"""Drop the run records a `run-void` names, and hand back what was dropped.
|
|
235
|
+
|
|
236
|
+
A stream is append-only, so a run record written with a wrong figure can be
|
|
237
|
+
neither edited nor deleted — and nothing inside the record says it is wrong, so
|
|
238
|
+
every figure over it is quietly polluted. The correction is therefore another
|
|
239
|
+
record: `run-void` names a run by the pair every run carries (`cmd` + `started`)
|
|
240
|
+
and says why (SCHEMA.md §2.7, written by `tf-emit.sh --void-run`).
|
|
241
|
+
|
|
242
|
+
Two rules, both the same one this file applies everywhere else. The voided record
|
|
243
|
+
leaves every figure — it is not clamped, guessed at or half-counted. And the count
|
|
244
|
+
of what left travels with the figures, because a total offered without its
|
|
245
|
+
exclusions is just a different wrong number. A void naming no run on this stream
|
|
246
|
+
is an orphan: it is counted and reported, never silently dropped, exactly as an
|
|
247
|
+
orphaned miss-amend is. tests/regression/run.sh tf_void."""
|
|
248
|
+
key = lambda r: (r.get("cmd"), r.get("started"))
|
|
249
|
+
voids = {}
|
|
250
|
+
for v in runs:
|
|
251
|
+
if v.get("kind") == "run-void" and v.get("started"):
|
|
252
|
+
voids[key(v)] = v
|
|
253
|
+
kept, dropped, reasons = [], 0, []
|
|
254
|
+
matched = set()
|
|
255
|
+
for r in runs:
|
|
256
|
+
if r.get("kind") == "run-void":
|
|
257
|
+
continue
|
|
258
|
+
if key(r) in voids:
|
|
259
|
+
matched.add(key(r))
|
|
260
|
+
dropped += 1
|
|
261
|
+
v = voids[key(r)]
|
|
262
|
+
reasons.append("%s %s %s — %s" % (app or r.get("app") or "?", r.get("cmd"),
|
|
263
|
+
r.get("started"), v.get("reason") or "no reason recorded"))
|
|
264
|
+
continue
|
|
265
|
+
kept.append(r)
|
|
266
|
+
return kept, dropped, reasons, len(set(voids) - matched)
|
|
267
|
+
|
|
268
|
+
|
|
102
269
|
def emit(repo, stream, records, dry_run, quiet=False):
|
|
103
270
|
"""Append via tf-emit.sh — the single append primitive. Never write JSONL directly.
|
|
104
271
|
|
|
@@ -208,6 +375,11 @@ def analyse_misses(misses):
|
|
|
208
375
|
|
|
209
376
|
Raw counts and the miss-class distribution ARE poolable: a miss counts as a miss
|
|
210
377
|
whoever missed it; only its attribution is confidence-bounded."""
|
|
378
|
+
# a miss withdrawn by a miss-void leaves every figure, with its fixes and amends (§5.5.11)
|
|
379
|
+
voids = {m.get("miss_id"): m for m in misses if m.get("kind") == "miss-void"}
|
|
380
|
+
ids = {m.get("miss_id") for m in misses if m.get("kind") == "miss"}
|
|
381
|
+
voided = ["%s — %s" % (k, v.get("reason") or "no reason recorded") for k, v in voids.items() if k in ids]
|
|
382
|
+
misses = [m for m in misses if m.get("miss_id") not in voids or m.get("kind") == "review"]
|
|
211
383
|
opened = [m for m in misses if m.get("kind") == "miss" and not m.get("backfilled")]
|
|
212
384
|
fixes = [m for m in misses if m.get("kind") == "miss-fix" and not m.get("backfilled")]
|
|
213
385
|
amends = [m for m in misses if m.get("kind") == "miss-amend"]
|
|
@@ -227,11 +399,14 @@ def analyse_misses(misses):
|
|
|
227
399
|
for a in sorted(amends, key=lambda r: r.get("ts") or ""):
|
|
228
400
|
parent = opened_by_id.get(a.get("miss_id"))
|
|
229
401
|
fld = a.get("field")
|
|
230
|
-
if parent is None or fld not in AMENDABLE_FIELDS:
|
|
402
|
+
if parent is None or (fld not in AMENDABLE_FIELDS and fld not in AMENDABLE_TEXT_FIELDS):
|
|
231
403
|
orphan_amends += 1
|
|
232
404
|
continue
|
|
233
|
-
|
|
234
|
-
|
|
405
|
+
val = a.get("value")
|
|
406
|
+
ok = (isinstance(val, str) and val.strip()) if fld in AMENDABLE_TEXT_FIELDS \
|
|
407
|
+
else (val in AMENDABLE_FIELDS.get(fld, ()))
|
|
408
|
+
if parent.get(fld) is None and ok:
|
|
409
|
+
parent[fld] = val
|
|
235
410
|
amended += 1
|
|
236
411
|
|
|
237
412
|
# ---- eligibility floor for optional fields added mid-stream (SCHEMA.md §3.5,
|
|
@@ -360,7 +535,21 @@ def analyse_misses(misses):
|
|
|
360
535
|
# Dollars exist ONLY where a harness measured them. Claude Code carries
|
|
361
536
|
# cost_usd:null permanently (SCHEMA.md §4) and are never priced from a rate card
|
|
362
537
|
# here — a pooled sum over mixed harnesses would silently under-report.
|
|
363
|
-
|
|
538
|
+
#
|
|
539
|
+
# A null was never the only way to have no money figure, though. An OpenCode run on a
|
|
540
|
+
# subscription records cost_usd 0.0 — a real, measured zero, because the plan was already
|
|
541
|
+
# paid for — and averaging that in as a FREE repair is the TF-005 defect again, in the
|
|
542
|
+
# column right beside the one TF-005 was found in. Only a `metered` record is money
|
|
543
|
+
# (SCHEMA.md §2.5b). A record written before billing_mode existed is admitted on its old
|
|
544
|
+
# terms, and counted, so the change does not silently rewrite the past.
|
|
545
|
+
paid = [f for f in sole if f.get("cost_usd") is not None
|
|
546
|
+
and (f.get("billing_mode") in (None, "metered"))]
|
|
547
|
+
unpriced_subscription_n = len([f for f in sole if f.get("cost_usd") is not None
|
|
548
|
+
and f.get("billing_mode") not in (None, "metered")])
|
|
549
|
+
# And the figure that works on every harness: what the repair's tokens cost at list price,
|
|
550
|
+
# worked out here from the tokens the record carries. It is the only money-shaped number a
|
|
551
|
+
# Claude Code repair can have, because a Max subscription bills nothing per token.
|
|
552
|
+
listed = [p for p in (list_price(REPO_OF.get(id(f), ""), f) for f in sole) if p is not None]
|
|
364
553
|
escaped = [m for m in opened if m.get("found_by") in ("owner", "production")]
|
|
365
554
|
design = [m for m in opened if m.get("miss_class") == "unspecified-gap"]
|
|
366
555
|
|
|
@@ -382,6 +571,9 @@ def analyse_misses(misses):
|
|
|
382
571
|
"resolved_misses": len(resolved),
|
|
383
572
|
"amendments_applied": amended,
|
|
384
573
|
"orphan_amends": orphan_amends,
|
|
574
|
+
"misses_voided_n": len(voided),
|
|
575
|
+
"misses_voided": voided,
|
|
576
|
+
"miss_voids_orphaned_n": len(voids) - len(voided),
|
|
385
577
|
# Denominator is records that CARRY the field — why_missed is optional (§5.5.6)
|
|
386
578
|
# and a missing value means "not assessed", never a zero for some category —
|
|
387
579
|
# AND records that COULD have carried it: one written before 2026-08-28 had no
|
|
@@ -462,6 +654,12 @@ def analyse_misses(misses):
|
|
|
462
654
|
"cost_usd_per_miss_measured": round(
|
|
463
655
|
sum(f["cost_usd"] for f in paid) / len(paid), 4) if len(paid) >= MIN_N else None,
|
|
464
656
|
"cost_usd_records": len(paid),
|
|
657
|
+
# Repairs whose dollars were measured and are NOT money: a subscription's zero, a local
|
|
658
|
+
# model's absent bill. Printed beside the figure, never inside it.
|
|
659
|
+
"cost_usd_excluded_not_money_n": unpriced_subscription_n,
|
|
660
|
+
"cost_list_usd_per_miss": round(
|
|
661
|
+
sum(listed) / len(listed), 4) if len(listed) >= MIN_N else None,
|
|
662
|
+
"cost_list_usd_records": len(listed),
|
|
465
663
|
}
|
|
466
664
|
|
|
467
665
|
|
|
@@ -512,9 +710,16 @@ def analyse_phases(runs):
|
|
|
512
710
|
except Exception:
|
|
513
711
|
return None
|
|
514
712
|
|
|
713
|
+
# A stored duration of zero or less is not a measurement: it is an impossible record
|
|
714
|
+
# (`ended` before `started`) that reached the stream before the emitter refused those.
|
|
715
|
+
# Treat it as absent so it is re-derived here, and -- when the timestamps are just as
|
|
716
|
+
# impossible -- excluded from every figure below rather than admitted by a truthiness
|
|
717
|
+
# test. tests/regression/run.sh tf_015.
|
|
515
718
|
derived_n = 0
|
|
719
|
+
negative_durations = sum(1 for r in live
|
|
720
|
+
if isinstance(r.get("duration_s"), (int, float)) and r["duration_s"] < 0)
|
|
516
721
|
for r in live:
|
|
517
|
-
if r
|
|
722
|
+
if _dur(r) is not None or not r.get("started"):
|
|
518
723
|
continue
|
|
519
724
|
end = r.get("ended") or r.get("ts")
|
|
520
725
|
if not end:
|
|
@@ -542,16 +747,28 @@ def analyse_phases(runs):
|
|
|
542
747
|
return scope_of(r) == "tree" and r.get("subagent_runs") is not None
|
|
543
748
|
|
|
544
749
|
grand_out = sum(r.get("tokens_out") or 0 for r in live if has_tokens(r))
|
|
545
|
-
grand_dur = sum(r
|
|
750
|
+
grand_dur = sum(_dur(r) or 0 for r in live if _dur(r) is not None)
|
|
546
751
|
|
|
547
752
|
phases = OrderedDict()
|
|
548
753
|
for cmd in sorted({r.get("cmd") or "?" for r in live}):
|
|
549
754
|
rs = [r for r in live if (r.get("cmd") or "?") == cmd]
|
|
550
755
|
priced = [r for r in rs if has_tokens(r)]
|
|
551
|
-
durs = [r
|
|
756
|
+
durs = [_dur(r) for r in rs if _dur(r) is not None]
|
|
552
757
|
fan = [r for r in rs if sees_subagents(r)]
|
|
553
758
|
|
|
554
759
|
models = defaultdict(lambda: {"runs": 0, "tokens_out": 0})
|
|
760
|
+
# List price, COMPUTED HERE from the tokens the record carries (see list_price above —
|
|
761
|
+
# pricing is a reporting job, and nothing priced is ever stored). Per model, attributed
|
|
762
|
+
# only where the window ran ONE model, the same rule the measured dollars follow below.
|
|
763
|
+
list_of = {}
|
|
764
|
+
for r in priced:
|
|
765
|
+
lp = list_price(REPO_OF.get(id(r), ""), r)
|
|
766
|
+
if lp is None:
|
|
767
|
+
continue
|
|
768
|
+
list_of[id(r)] = lp
|
|
769
|
+
ms = r.get("models") or ([r["model"]] if r.get("model") else [])
|
|
770
|
+
if len(ms) == 1:
|
|
771
|
+
models[ms[0]]["list_usd"] = round(models[ms[0]].get("list_usd", 0.0) + lp, 6)
|
|
555
772
|
for r in priced:
|
|
556
773
|
split = r.get("model_tokens_out")
|
|
557
774
|
if isinstance(split, dict) and split:
|
|
@@ -578,12 +795,42 @@ def analyse_phases(runs):
|
|
|
578
795
|
sub_out = sum(r.get("tokens_out_subagents") or 0 for r in fan)
|
|
579
796
|
fan_out_total = sum(r.get("tokens_out") or 0 for r in fan if has_tokens(r))
|
|
580
797
|
|
|
798
|
+
# DOLLARS, and what each one means. A harness split was never enough: one OpenCode
|
|
799
|
+
# machine mixes a flat-fee subscription (whose marginal cost is a true zero), a metered
|
|
800
|
+
# API key (whose dollars are real money) and a local model (which has no bill at all),
|
|
801
|
+
# and pooling those three produces a money figure that is neither the money spent nor
|
|
802
|
+
# the effort used. `billing_mode` is the separation; only `metered` is money.
|
|
581
803
|
cost_by_harness = defaultdict(float)
|
|
582
804
|
cost_n = Counter()
|
|
805
|
+
cost_by_mode = defaultdict(float)
|
|
806
|
+
mode_n = Counter()
|
|
807
|
+
model_usd = defaultdict(float)
|
|
808
|
+
model_usd_n = Counter()
|
|
809
|
+
plan_by_model = defaultdict(float)
|
|
810
|
+
usd_unattributed, usd_unattributed_n = 0.0, 0
|
|
583
811
|
for r in priced:
|
|
812
|
+
mode = r.get("billing_mode") or "unrecorded"
|
|
813
|
+
mode_n[mode] += 1
|
|
584
814
|
if r.get("cost_usd") is not None:
|
|
585
815
|
cost_by_harness[r.get("harness") or "?"] += r["cost_usd"]
|
|
586
816
|
cost_n[r.get("harness") or "?"] += 1
|
|
817
|
+
cost_by_mode[mode] += r["cost_usd"]
|
|
818
|
+
if mode == "plan":
|
|
819
|
+
ms = r.get("models") or ([r["model"]] if r.get("model") else [])
|
|
820
|
+
if len(ms) == 1:
|
|
821
|
+
plan_by_model[ms[0]] += r["cost_usd"]
|
|
822
|
+
if mode == "metered":
|
|
823
|
+
# Per-model spend is attributed ONLY where the window ran one model.
|
|
824
|
+
# Splitting one window's dollars across several models by their token
|
|
825
|
+
# share is arithmetic, not measurement — the same line §5.5.3 draws
|
|
826
|
+
# between `sole` and `shared`, one stream over.
|
|
827
|
+
ms = r.get("models") or ([r["model"]] if r.get("model") else [])
|
|
828
|
+
if len(ms) == 1:
|
|
829
|
+
model_usd[ms[0]] += r["cost_usd"]
|
|
830
|
+
model_usd_n[ms[0]] += 1
|
|
831
|
+
else:
|
|
832
|
+
usd_unattributed += r["cost_usd"]
|
|
833
|
+
usd_unattributed_n += 1
|
|
587
834
|
|
|
588
835
|
tok = lambda k: sum(r.get(k) or 0 for r in priced)
|
|
589
836
|
phases[cmd] = {
|
|
@@ -621,7 +868,7 @@ def analyse_phases(runs):
|
|
|
621
868
|
"by_mode": OrderedDict(
|
|
622
869
|
(mode, {
|
|
623
870
|
"runs": len([r for r in rs if (r.get("mode") or "—") == mode]),
|
|
624
|
-
"duration_s": sum(r
|
|
871
|
+
"duration_s": sum(_dur(r) or 0
|
|
625
872
|
for r in rs if (r.get("mode") or "—") == mode),
|
|
626
873
|
"tokens_out": sum(r.get("tokens_out") or 0
|
|
627
874
|
for r in rs if (r.get("mode") or "—") == mode and has_tokens(r)),
|
|
@@ -666,6 +913,36 @@ def analyse_phases(runs):
|
|
|
666
913
|
"cost_usd_by_harness": OrderedDict(
|
|
667
914
|
(h, {"usd": round(v, 6), "records": cost_n[h]})
|
|
668
915
|
for h, v in sorted(cost_by_harness.items())),
|
|
916
|
+
# …and per billing mode, which is the separation that decides whether a number
|
|
917
|
+
# is money at all (SCHEMA.md §2.5b).
|
|
918
|
+
"billing_modes": OrderedDict(sorted(mode_n.items())),
|
|
919
|
+
"cost_usd_by_billing_mode": OrderedDict(
|
|
920
|
+
(mode, round(v, 6)) for mode, v in sorted(cost_by_mode.items())),
|
|
921
|
+
"money_usd": round(cost_by_mode.get("metered", 0.0), 6),
|
|
922
|
+
"money_records": mode_n.get("metered", 0),
|
|
923
|
+
# A window that mixed a metered model with a subscription one really was billed, but
|
|
924
|
+
# the bill cannot be separated from the part that was already paid for. Real money,
|
|
925
|
+
# reported on its own line rather than folded into the headline or dropped.
|
|
926
|
+
"money_mixed_usd": round(cost_by_mode.get("mixed", 0.0), 6),
|
|
927
|
+
"money_mixed_records": mode_n.get("mixed", 0),
|
|
928
|
+
"spend_by_model": OrderedDict(
|
|
929
|
+
(m, {"usd": round(v, 6), "records": model_usd_n[m]})
|
|
930
|
+
for m, v in sorted(model_usd.items(), key=lambda kv: (-kv[1], kv[0]))),
|
|
931
|
+
"spend_unattributed": {"usd": round(usd_unattributed, 6), "records": usd_unattributed_n},
|
|
932
|
+
# LIST PRICE — what these tokens would cost bought at the published rate. It is the
|
|
933
|
+
# only figure that compares a subscription phase with a metered one, and it is a
|
|
934
|
+
# price list applied to a measurement, never a bill. Reported apart from `money_usd`
|
|
935
|
+
# for exactly that reason, with the unpriced count beside it.
|
|
936
|
+
"list_usd": round(sum(list_of.values()), 6),
|
|
937
|
+
"list_usd_records": len(list_of),
|
|
938
|
+
"list_usd_unpriced_n": len(priced) - len(list_of),
|
|
939
|
+
# A monthly plan reports dollars too, but they are ALLOWANCE CONSUMED against a
|
|
940
|
+
# per-model limit, not an invoice — and they are the numbers that produce the limit
|
|
941
|
+
# the fallback chain exists for, so they are worth their own line.
|
|
942
|
+
"plan_allowance_usd": round(cost_by_mode.get("plan", 0.0), 6),
|
|
943
|
+
"plan_allowance_records": mode_n.get("plan", 0),
|
|
944
|
+
"plan_allowance_by_model": OrderedDict(
|
|
945
|
+
(m, round(v, 6)) for m, v in sorted(plan_by_model.items(), key=lambda kv: -kv[1])),
|
|
669
946
|
}
|
|
670
947
|
|
|
671
948
|
return {
|
|
@@ -673,6 +950,12 @@ def analyse_phases(runs):
|
|
|
673
950
|
"scope_coverage": OrderedDict(sorted(scope_cov.items())),
|
|
674
951
|
"tokens_out_total": grand_out,
|
|
675
952
|
"duration_s_total": grand_dur,
|
|
953
|
+
# What the duration figures are actually built on. A total is only honest when the
|
|
954
|
+
# reader is told what it left out: TechieBlog's 46 run records yield 32 usable ones.
|
|
955
|
+
"duration_measured_n": len([r for r in live if _dur(r) is not None]),
|
|
956
|
+
"duration_impossible_n": _dur_quality(live)[0],
|
|
957
|
+
"duration_absent_n": _dur_quality(live)[1],
|
|
958
|
+
"duration_recomputed_n": _dur_quality(live)[2],
|
|
676
959
|
"phases": phases,
|
|
677
960
|
"note": ("Per-phase effort over LIVE run records, aggregated by `cmd`. Token "
|
|
678
961
|
"figures exclude runs whose window could not be computed; fan-out "
|
|
@@ -965,10 +1248,15 @@ def analyse(repos):
|
|
|
965
1248
|
per_repo = []
|
|
966
1249
|
commit_dupes = 0
|
|
967
1250
|
session_dupes = 0
|
|
1251
|
+
voided_runs = 0
|
|
1252
|
+
void_reasons = []
|
|
1253
|
+
void_orphans = 0
|
|
968
1254
|
for repo in repos:
|
|
969
1255
|
g = read_stream(repo, "gates")
|
|
970
|
-
r = read_stream(repo, "runs")
|
|
971
|
-
|
|
1256
|
+
r, vn, vr, vo = apply_voids(read_stream(repo, "runs"), app_name(repo))
|
|
1257
|
+
voided_runs += vn; void_reasons += vr; void_orphans += vo
|
|
1258
|
+
repo_misses = read_stream(repo, "misses")
|
|
1259
|
+
misses += repo_misses
|
|
972
1260
|
# Per repo, not across them (SCHEMA.md §4): the OpenCode plugin
|
|
973
1261
|
# appends a cumulative snapshot at every root-session idle.
|
|
974
1262
|
s, sd = dedupe_sessions(read_stream(repo, "sessions"))
|
|
@@ -976,13 +1264,18 @@ def analyse(repos):
|
|
|
976
1264
|
# Per repo, not across them: two repos may legitimately share a short sha.
|
|
977
1265
|
c, d = dedupe_commits(read_stream(repo, "commits"))
|
|
978
1266
|
commit_dupes += d
|
|
1267
|
+
# Which repo each record came from, so the report can price it with THAT project's rate
|
|
1268
|
+
# card and routing. Kept beside the records rather than written into them: nothing here
|
|
1269
|
+
# may add a field a record did not have.
|
|
1270
|
+
for _x in r + repo_misses:
|
|
1271
|
+
REPO_OF[id(_x)] = repo
|
|
979
1272
|
gates += g; runs += r; sessions += s; commits += c
|
|
980
1273
|
per_repo.append({"repo": repo, "app": app_name(repo),
|
|
981
1274
|
"project_type": project_type(repo)[0],
|
|
982
1275
|
"gates": len(g), "gates_backfilled": sum(1 for x in g if x.get("backfilled")),
|
|
983
1276
|
"runs": len(r), "sessions": len(s), "commits": len(c),
|
|
984
|
-
"misses": sum(1 for x in
|
|
985
|
-
if
|
|
1277
|
+
"misses": sum(1 for x in repo_misses if x.get("kind") == "miss" and x.get("miss_id") not in
|
|
1278
|
+
{v.get("miss_id") for v in repo_misses if v.get("kind") == "miss-void"}),
|
|
986
1279
|
# A repo reclassified after records were written (most often a
|
|
987
1280
|
# greenfield project born `docs` and upgraded once it grew a head)
|
|
988
1281
|
# keeps the OLD value on every existing record — append-only means
|
|
@@ -996,9 +1289,19 @@ def analyse(repos):
|
|
|
996
1289
|
"commit_hook": has_commit_hook(repo)})
|
|
997
1290
|
|
|
998
1291
|
def seg(records):
|
|
1292
|
+
# An `FR` verdict grades a framework requirement line, not an application's
|
|
1293
|
+
# screen: it is written by a runner over a document the framework controls, so
|
|
1294
|
+
# it passes far more often than a REQ verified against a running app. Pooled
|
|
1295
|
+
# into an application segment it raises that segment's first-pass rate and
|
|
1296
|
+
# dilutes its escape rate, with nothing on the output to show it happened.
|
|
1297
|
+
# SCHEMA.md §3 states the rule -- "it never pools with the others" -- and this
|
|
1298
|
+
# is where it is applied. Tested by tests/regression/run.sh tf_020.
|
|
999
1299
|
d = defaultdict(list)
|
|
1000
1300
|
for r in records:
|
|
1001
|
-
|
|
1301
|
+
if r.get("req_class") == "FR":
|
|
1302
|
+
key = "framework-requirement"
|
|
1303
|
+
else:
|
|
1304
|
+
key = "unclassified" if r.get("project_type_inferred") else r.get("project_type", "app")
|
|
1002
1305
|
d[key].append(r)
|
|
1003
1306
|
return d
|
|
1004
1307
|
|
|
@@ -1031,6 +1334,10 @@ def analyse(repos):
|
|
|
1031
1334
|
|
|
1032
1335
|
out = {"per_repo": per_repo, "tainted_reqs": sorted("%s:%s" % x for x in tainted if x[1]),
|
|
1033
1336
|
"live": {}, "backfilled": {}, "pooled": {},
|
|
1337
|
+
# what a `run-void` took out of every figure above, and why (SCHEMA.md §2.7)
|
|
1338
|
+
"runs_voided_n": voided_runs,
|
|
1339
|
+
"runs_voided": void_reasons,
|
|
1340
|
+
"run_voids_orphaned_n": void_orphans,
|
|
1034
1341
|
"misses": analyse_misses(misses),
|
|
1035
1342
|
"phases": analyse_phases(runs)}
|
|
1036
1343
|
|
|
@@ -1069,10 +1376,12 @@ def analyse(repos):
|
|
|
1069
1376
|
# ---- poolable metrics: runs, cadence, tokens. Exempt from both separations.
|
|
1070
1377
|
build_runs = [r for r in runs if r.get("cmd") == "build-phase"]
|
|
1071
1378
|
fix_runs = [r for r in runs if r.get("mode") == "fix"]
|
|
1072
|
-
throughput = [float(r["reqs_count"]) / r
|
|
1073
|
-
for r in runs if r
|
|
1379
|
+
throughput = [float(r["reqs_count"]) / _dur(r)
|
|
1380
|
+
for r in runs if _dur(r) is not None and r.get("reqs_count")]
|
|
1074
1381
|
batch = [r["reqs_count"] for r in build_runs if r.get("reqs_count") is not None]
|
|
1075
1382
|
verified_transitions = sum(1 for g in gates if g.get("verdict") == "Verified")
|
|
1383
|
+
pooled_list = [p for p in (list_price(REPO_OF.get(id(r), ""), r) for r in runs)
|
|
1384
|
+
if p is not None]
|
|
1076
1385
|
tok = sum((s.get("input_tokens") or 0) + (s.get("output_tokens") or 0) for s in sessions)
|
|
1077
1386
|
days = {c.get("ts", "")[:10] for c in commits if c.get("ts")}
|
|
1078
1387
|
out["pooled"] = {
|
|
@@ -1086,7 +1395,16 @@ def analyse(repos):
|
|
|
1086
1395
|
"tokens_total": tok,
|
|
1087
1396
|
"tokens_per_verified_req": round(float(tok) / verified_transitions, 1)
|
|
1088
1397
|
if verified_transitions >= MIN_N and tok else None,
|
|
1089
|
-
"cost_usd": None, # never estimated — see SCHEMA.md §4
|
|
1398
|
+
"cost_usd": None, # what was BILLED is never estimated — see SCHEMA.md §4
|
|
1399
|
+
# What the same work costs at list price. A price, not a bill (SCHEMA.md §2.5b), and the
|
|
1400
|
+
# only money-shaped headline that works on a subscription — which is most of this
|
|
1401
|
+
# framework's runs. Summed over the runs that carry it, with that count published, so a
|
|
1402
|
+
# figure built on half the runs cannot be read as if it covered all of them.
|
|
1403
|
+
"list_usd_total": round(sum(pooled_list), 4),
|
|
1404
|
+
"list_usd_records": len(pooled_list),
|
|
1405
|
+
"list_usd_per_verified_req": (
|
|
1406
|
+
round(sum(pooled_list) / verified_transitions, 4)
|
|
1407
|
+
if verified_transitions >= MIN_N and len(pooled_list) >= MIN_N else None),
|
|
1090
1408
|
"commits": len(commits),
|
|
1091
1409
|
"commit_duplicates_collapsed": commit_dupes,
|
|
1092
1410
|
"session_duplicates_collapsed": session_dupes,
|
|
@@ -1173,6 +1491,20 @@ def print_report(a, repos):
|
|
|
1173
1491
|
print(" across project_type or across live/backfilled would produce a")
|
|
1174
1492
|
print(" number that cannot be defended. See SCHEMA.md §6.")
|
|
1175
1493
|
print("")
|
|
1494
|
+
if a.get("runs_voided_n"):
|
|
1495
|
+
print(" %d run record(s) VOIDED — named by a run-void record as wrong, so they are"
|
|
1496
|
+
% a["runs_voided_n"])
|
|
1497
|
+
print(" in no figure above. Both records stay on the stream; nothing was deleted:")
|
|
1498
|
+
for line in a.get("runs_voided", [])[:6]:
|
|
1499
|
+
print(" " + line)
|
|
1500
|
+
if len(a.get("runs_voided", [])) > 6:
|
|
1501
|
+
print(" ... and %d more" % (len(a["runs_voided"]) - 6))
|
|
1502
|
+
print("")
|
|
1503
|
+
if a.get("run_voids_orphaned_n"):
|
|
1504
|
+
print(" %d run-void record(s) name a run that is not on this stream — the void was"
|
|
1505
|
+
% a["run_voids_orphaned_n"])
|
|
1506
|
+
print(" written against a record that never arrived. Nothing was excluded for them.")
|
|
1507
|
+
print("")
|
|
1176
1508
|
if a["tainted_reqs"]:
|
|
1177
1509
|
print(" %d REQ(s) excluded from the LIVE first-pass rate because they carry"
|
|
1178
1510
|
% len(a["tainted_reqs"]))
|
|
@@ -1193,8 +1525,14 @@ def print_report(a, repos):
|
|
|
1193
1525
|
print(" REQ throughput : %s REQs/hour (median across runs)" %
|
|
1194
1526
|
(p["throughput_median_reqs_per_hour"] if p["throughput_median_reqs_per_hour"] is not None else "insufficient data"))
|
|
1195
1527
|
print(" sessions / tokens : %d sessions, %s total tokens" % (p["sessions"], "{:,}".format(p["tokens_total"])))
|
|
1196
|
-
print(" tokens per Verified : %s (
|
|
1528
|
+
print(" tokens per Verified : %s (what you were BILLED is never estimated — SCHEMA.md §4)" %
|
|
1197
1529
|
(p["tokens_per_verified_req"] if p["tokens_per_verified_req"] is not None else "insufficient data"))
|
|
1530
|
+
if p.get("list_usd_records"):
|
|
1531
|
+
print(" list price : $%s over %d priced run(s) — the published rate applied to"
|
|
1532
|
+
% (p["list_usd_total"], p["list_usd_records"]))
|
|
1533
|
+
print(" the tokens actually measured. A price, not a bill.")
|
|
1534
|
+
if p.get("list_usd_per_verified_req") is not None:
|
|
1535
|
+
print(" list $ per Verified : $%s" % p["list_usd_per_verified_req"])
|
|
1198
1536
|
print(" commit cadence : %s commits/active day (%d commits over %d days)" %
|
|
1199
1537
|
(p["commits_per_active_day"] if p["commits_per_active_day"] is not None else "—",
|
|
1200
1538
|
p["commits"], p["active_days"]))
|
|
@@ -1243,6 +1581,15 @@ def print_phases(p, W):
|
|
|
1243
1581
|
print("-" * W)
|
|
1244
1582
|
print(" %d live run record(s). Token-window coverage: %s"
|
|
1245
1583
|
% (p["runs_live"], " ".join("%s=%d" % kv for kv in p["scope_coverage"].items())))
|
|
1584
|
+
if any(p.get(k) for k in ("duration_impossible_n", "duration_absent_n", "duration_recomputed_n")):
|
|
1585
|
+
print(" duration: %d of %d record(s) usable"
|
|
1586
|
+
% (p.get("duration_measured_n", 0), p["runs_live"])
|
|
1587
|
+
+ (", %d impossible (start after end, discarded)" % p["duration_impossible_n"]
|
|
1588
|
+
if p.get("duration_impossible_n") else "")
|
|
1589
|
+
+ (", %d record no elapsed time" % p["duration_absent_n"]
|
|
1590
|
+
if p.get("duration_absent_n") else "")
|
|
1591
|
+
+ (", %d taken from the timestamps over a stored figure that disagreed"
|
|
1592
|
+
% p["duration_recomputed_n"] if p.get("duration_recomputed_n") else ""))
|
|
1246
1593
|
print(" A window is only as good as its scope: 'tree' saw the subagents, 'main'")
|
|
1247
1594
|
print(" did not look, 'none'/absent measured nothing at all and is excluded from")
|
|
1248
1595
|
print(" every token figure below rather than averaged in as a zero.")
|
|
@@ -1323,8 +1670,57 @@ def print_phases(p, W):
|
|
|
1323
1670
|
for h, c in m["cost_usd_by_harness"].items():
|
|
1324
1671
|
print(" cost (%s) : $%s over %d record(s) — MEASURED, never pooled "
|
|
1325
1672
|
"across harness" % (h, c["usd"], c["records"]))
|
|
1326
|
-
|
|
1327
|
-
|
|
1673
|
+
bmodes = m.get("billing_modes") or {}
|
|
1674
|
+
if bmodes:
|
|
1675
|
+
print(" paid for by : " +
|
|
1676
|
+
" · ".join("%s %d" % (k, v) for k, v in bmodes.items()))
|
|
1677
|
+
if m.get("list_usd_records"):
|
|
1678
|
+
print(" list price : $%s over %d run(s) — the published rate applied to"
|
|
1679
|
+
% (m["list_usd"], m["list_usd_records"]))
|
|
1680
|
+
print(" the tokens on the record, worked out HERE and never")
|
|
1681
|
+
print(" stored. A price, not a bill: it is what makes a")
|
|
1682
|
+
print(" subscription run comparable with a metered one.")
|
|
1683
|
+
if m.get("list_usd_unpriced_n"):
|
|
1684
|
+
print(" %d run(s) had no rate for their model and are"
|
|
1685
|
+
% m["list_usd_unpriced_n"])
|
|
1686
|
+
print(" outside it — never priced at zero.")
|
|
1687
|
+
if m.get("money_records"):
|
|
1688
|
+
print(" money billed : $%s over %d metered run(s). Only a metered run is"
|
|
1689
|
+
% (m["money_usd"], m["money_records"]))
|
|
1690
|
+
print(" money billed; a subscription's zero is a flat fee")
|
|
1691
|
+
print(" already paid, a local model has no bill (§2.5b).")
|
|
1692
|
+
if m.get("plan_allowance_records"):
|
|
1693
|
+
print(" plan allowance : $%s used over %d run(s) on a monthly plan. This is"
|
|
1694
|
+
% (m["plan_allowance_usd"], m["plan_allowance_records"]))
|
|
1695
|
+
print(" the provider's own meter against a PER-MODEL")
|
|
1696
|
+
print(" allowance, not an invoice — and it is what runs out")
|
|
1697
|
+
print(" and sends a tier to its fallback.")
|
|
1698
|
+
for mod, v in (m.get("plan_allowance_by_model") or {}).items():
|
|
1699
|
+
print(" %-30s $%s of allowance" % (mod, v))
|
|
1700
|
+
if m.get("money_mixed_usd"):
|
|
1701
|
+
print(" also billed : $%s on %d run(s) whose window mixed billing modes —"
|
|
1702
|
+
% (m["money_mixed_usd"], m["money_mixed_records"]))
|
|
1703
|
+
print(" real money, but not separable from the part a")
|
|
1704
|
+
print(" subscription had already paid for.")
|
|
1705
|
+
for mod, c in (m.get("models") or {}).items():
|
|
1706
|
+
bits = []
|
|
1707
|
+
if c.get("list_usd"):
|
|
1708
|
+
bits.append("list $%s" % c["list_usd"])
|
|
1709
|
+
sp = (m.get("spend_by_model") or {}).get(mod)
|
|
1710
|
+
if sp:
|
|
1711
|
+
bits.append("billed $%s" % sp["usd"])
|
|
1712
|
+
if bits:
|
|
1713
|
+
print(" %-30s %s" % (mod, " · ".join(bits)))
|
|
1714
|
+
un = m.get("spend_unattributed") or {}
|
|
1715
|
+
if un.get("records"):
|
|
1716
|
+
print(" %-30s billed $%-8s over %d run(s) — the window ran several"
|
|
1717
|
+
% ("(not attributed)", un["usd"], un["records"]))
|
|
1718
|
+
print(" models, and one window's dollars are not split by token share.")
|
|
1719
|
+
# `bm` used to be assigned inside `if m["reqs_touched_total"] or m["files_written_total"]:`
|
|
1720
|
+
# and read outside it, so --phases crashed with UnboundLocalError on any phase that
|
|
1721
|
+
# touched no REQ and wrote no file — which is every metrics-report and every render.
|
|
1722
|
+
# MISS-TechieFlow-20260910-03.
|
|
1723
|
+
bm = m.get("by_mode") or {}
|
|
1328
1724
|
if len(bm) > 1:
|
|
1329
1725
|
print(" by mode : (in the order they ran)")
|
|
1330
1726
|
for mode, v in bm.items():
|
|
@@ -1407,6 +1803,15 @@ def print_misses(m, W):
|
|
|
1407
1803
|
print(" tokens to apply the corrections : %s (n=%d)"
|
|
1408
1804
|
% (m["review_tokens_correct_mean"] if m["review_tokens_correct_mean"] is not None
|
|
1409
1805
|
else "insufficient data", m["review_tokens_correct_n"]))
|
|
1806
|
+
if m.get("misses_voided_n"):
|
|
1807
|
+
print(" withdrawn : %d miss(es) named by a miss-void, in no figure above:" % m["misses_voided_n"])
|
|
1808
|
+
for line in m["misses_voided"][:6]:
|
|
1809
|
+
print(" " + line)
|
|
1810
|
+
if m["misses_voided_n"] > 6:
|
|
1811
|
+
print(" ... and %d more" % (m["misses_voided_n"] - 6))
|
|
1812
|
+
if m.get("miss_voids_orphaned_n"):
|
|
1813
|
+
print(" ⚠ %d miss-void record(s) name no miss on this stream — nothing was excluded for them"
|
|
1814
|
+
% m["miss_voids_orphaned_n"])
|
|
1410
1815
|
if m["orphan_amends"]:
|
|
1411
1816
|
print(" ⚠ %d miss-amend record(s) name no known miss, or a field outside the"
|
|
1412
1817
|
% m["orphan_amends"])
|
|
@@ -1453,6 +1858,15 @@ def print_misses(m, W):
|
|
|
1453
1858
|
print(" Claude Code carries cost_usd:null permanently and is NEVER")
|
|
1454
1859
|
print(" priced from a rate card here (SCHEMA.md §4). Real dollars come from")
|
|
1455
1860
|
print(" OpenCode runs; token counts are the honest figure everywhere else.")
|
|
1861
|
+
if m.get("cost_list_usd_per_miss") is not None:
|
|
1862
|
+
print(" list price per miss : $%s (%d record(s)) — the repair's tokens at the"
|
|
1863
|
+
% (m["cost_list_usd_per_miss"], m["cost_list_usd_records"]))
|
|
1864
|
+
print(" published rate. Works on every harness, and is a price, not a bill.")
|
|
1865
|
+
if m.get("cost_usd_excluded_not_money_n"):
|
|
1866
|
+
print(" %d fix record(s) measured a cost that is NOT money — a subscription's"
|
|
1867
|
+
% m["cost_usd_excluded_not_money_n"])
|
|
1868
|
+
print(" zero or a local model's absent bill — and are outside that figure")
|
|
1869
|
+
print(" rather than averaged in as free repairs (SCHEMA.md §2.5b).")
|
|
1456
1870
|
print(" shared (apportioned): %d fix records — equal division, NOT a measurement"
|
|
1457
1871
|
% m["cost_shared_n"])
|
|
1458
1872
|
print(" tokens out per miss : %s (n=%d priced)" %
|