workflow-toolkit 1.0.1

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.
Files changed (142) hide show
  1. package/.agents/skills/ponytail/SKILL.md +111 -0
  2. package/.agents/skills/ponytail-audit/SKILL.md +37 -0
  3. package/.agents/skills/ponytail-debt/SKILL.md +41 -0
  4. package/.agents/skills/ponytail-gain/SKILL.md +47 -0
  5. package/.agents/skills/ponytail-help/SKILL.md +70 -0
  6. package/.agents/skills/ponytail-review/SKILL.md +52 -0
  7. package/.agents/skills/prompt-review/SKILL.md +60 -0
  8. package/.agents/skills/wtk/SKILL.md +64 -0
  9. package/.agents/skills/wtk/references/artifacts.md +82 -0
  10. package/.agents/skills/wtk/references/evidence.md +68 -0
  11. package/.agents/skills/wtk/references/git.md +75 -0
  12. package/.agents/skills/wtk/references/test-contract.md +76 -0
  13. package/.agents/skills/wtk/references/validation.md +127 -0
  14. package/.agents/skills/wtk-config/SKILL.md +102 -0
  15. package/.agents/skills/wtk-config/assets/agents/claude/deep-reviewer.md +40 -0
  16. package/.agents/skills/wtk-config/assets/agents/claude/designer.md +57 -0
  17. package/.agents/skills/wtk-config/assets/agents/claude/explorer.md +41 -0
  18. package/.agents/skills/wtk-config/assets/agents/claude/implementer.md +57 -0
  19. package/.agents/skills/wtk-config/assets/agents/claude/planner.md +68 -0
  20. package/.agents/skills/wtk-config/assets/agents/claude/verifier.md +80 -0
  21. package/.agents/skills/wtk-config/assets/agents/codex/deep-reviewer.toml +41 -0
  22. package/.agents/skills/wtk-config/assets/agents/codex/designer.toml +55 -0
  23. package/.agents/skills/wtk-config/assets/agents/codex/explorer.toml +35 -0
  24. package/.agents/skills/wtk-config/assets/agents/codex/implementer.toml +52 -0
  25. package/.agents/skills/wtk-config/assets/agents/codex/planner.toml +66 -0
  26. package/.agents/skills/wtk-config/assets/agents/codex/verifier.toml +78 -0
  27. package/.agents/skills/wtk-config/assets/agents/cursor/deep-reviewer.md +38 -0
  28. package/.agents/skills/wtk-config/assets/agents/cursor/designer.md +55 -0
  29. package/.agents/skills/wtk-config/assets/agents/cursor/explorer.md +39 -0
  30. package/.agents/skills/wtk-config/assets/agents/cursor/implementer.md +55 -0
  31. package/.agents/skills/wtk-config/assets/agents/cursor/planner.md +66 -0
  32. package/.agents/skills/wtk-config/assets/agents/cursor/verifier.md +79 -0
  33. package/.agents/skills/wtk-config/scripts/ad-index.py +123 -0
  34. package/.agents/skills/wtk-config/scripts/repository_intelligence.py +671 -0
  35. package/.agents/skills/wtk-config/scripts/workflow_config.py +972 -0
  36. package/.agents/skills/wtk-deep-review/SKILL.md +164 -0
  37. package/.agents/skills/wtk-deep-review/assets/PROMPT.md +56 -0
  38. package/.agents/skills/wtk-deep-review/assets/REVIEW_UI.html +1340 -0
  39. package/.agents/skills/wtk-deep-review/assets/findings.schema.json +113 -0
  40. package/.agents/skills/wtk-deep-review/references/context-pack.md +88 -0
  41. package/.agents/skills/wtk-deep-review/references/orchestration.md +164 -0
  42. package/.agents/skills/wtk-deep-review/references/output-contracts.md +149 -0
  43. package/.agents/skills/wtk-deep-review/references/publish-github.md +83 -0
  44. package/.agents/skills/wtk-deep-review/references/state-and-learnings.md +64 -0
  45. package/.agents/skills/wtk-deep-review/references/subagent-runtimes.md +33 -0
  46. package/.agents/skills/wtk-deep-review/references/taxonomy.md +70 -0
  47. package/.agents/skills/wtk-deep-review/scripts/_common.py +371 -0
  48. package/.agents/skills/wtk-deep-review/scripts/build_jobs.py +561 -0
  49. package/.agents/skills/wtk-deep-review/scripts/build_knowledge.py +349 -0
  50. package/.agents/skills/wtk-deep-review/scripts/build_manifest.py +479 -0
  51. package/.agents/skills/wtk-deep-review/scripts/graft_context.py +160 -0
  52. package/.agents/skills/wtk-deep-review/scripts/graphify_context.py +75 -0
  53. package/.agents/skills/wtk-deep-review/scripts/merge_findings.py +313 -0
  54. package/.agents/skills/wtk-deep-review/scripts/render_html.py +240 -0
  55. package/.agents/skills/wtk-deep-review/scripts/render_review.py +282 -0
  56. package/.agents/skills/wtk-deep-review/scripts/run_jobs.py +436 -0
  57. package/.agents/skills/wtk-deep-review/scripts/token_metrics.py +369 -0
  58. package/.agents/skills/wtk-discover/SKILL.md +270 -0
  59. package/.agents/skills/wtk-discover/references/document-format.md +149 -0
  60. package/.agents/skills/wtk-implement/SKILL.md +95 -0
  61. package/.agents/skills/wtk-implement/references/checklist-format.md +116 -0
  62. package/.agents/skills/wtk-implement/references/screens.md +129 -0
  63. package/.agents/skills/wtk-implement/references/test-policy.md +152 -0
  64. package/.agents/skills/wtk-implement/references/verify.md +297 -0
  65. package/.agents/skills/wtk-knowledge-check/SKILL.md +16 -0
  66. package/.agents/skills/wtk-knowledge-check/scripts/check.ts +502 -0
  67. package/.agents/skills/wtk-knowledge-check/scripts/cli.ts +32 -0
  68. package/.agents/skills/wtk-knowledge-check/scripts/frontmatter.ts +44 -0
  69. package/.agents/skills/wtk-lean/.skill-meta.json +6 -0
  70. package/.agents/skills/wtk-lean/NOTICE.md +18 -0
  71. package/.agents/skills/wtk-lean/SKILL.md +137 -0
  72. package/.agents/skills/wtk-lean/references/build.md +168 -0
  73. package/.agents/skills/wtk-lean/references/checks.md +253 -0
  74. package/.agents/skills/wtk-lean/references/memory.md +156 -0
  75. package/.agents/skills/wtk-lean/references/plan.md +514 -0
  76. package/.agents/skills/wtk-lean/references/verify.md +336 -0
  77. package/.agents/skills/wtk-lean/scripts/check_commit.py +121 -0
  78. package/.agents/skills/wtk-lean/scripts/fixtures/checks.md +98 -0
  79. package/.agents/skills/wtk-lean/scripts/fixtures/plan.md +119 -0
  80. package/.agents/skills/wtk-lean/scripts/fixtures/verification.md +41 -0
  81. package/.agents/skills/wtk-lean/scripts/lessons.py +412 -0
  82. package/.agents/skills/wtk-lean/scripts/selftest.py +401 -0
  83. package/.agents/skills/wtk-lean/scripts/validate_checks.py +417 -0
  84. package/.agents/skills/wtk-lean/scripts/validate_plan.py +580 -0
  85. package/.agents/skills/wtk-lean/scripts/validate_verification.py +357 -0
  86. package/.agents/skills/wtk-plan/SKILL.md +102 -0
  87. package/.agents/skills/wtk-plan/references/document-format.md +222 -0
  88. package/.agents/skills/wtk-qa/SKILL.md +14 -0
  89. package/.agents/skills/wtk-qa-execute/SKILL.md +115 -0
  90. package/.agents/skills/wtk-qa-execute/references/fix-loop.md +23 -0
  91. package/.agents/skills/wtk-qa-execute/references/session-protocol.md +25 -0
  92. package/.agents/skills/wtk-qa-plan/SKILL.md +111 -0
  93. package/.agents/skills/wtk-qa-plan/references/profile.md +35 -0
  94. package/.agents/skills/wtk-ship/SKILL.md +49 -0
  95. package/.agents/skills/wtk-ship/remediation.py +170 -0
  96. package/.agents/skills/wtk-ship/scripts/close_feature.py +83 -0
  97. package/.agents/skills/wtk-ship/scripts/review_convergence.py +373 -0
  98. package/.wtk.toml.example +93 -0
  99. package/AGENTS.md +129 -0
  100. package/NOTICE.md +12 -0
  101. package/README.md +440 -0
  102. package/bin/wtk.js +25 -0
  103. package/docs/toolkit/README.md +56 -0
  104. package/docs/toolkit/decisions.md +61 -0
  105. package/docs/toolkit/guidelines/CONTEXT-BUDGET.md +64 -0
  106. package/docs/toolkit/guidelines/DX.md +67 -0
  107. package/docs/toolkit/guidelines/FRONTEND.md +73 -0
  108. package/docs/toolkit/guidelines/KNOWLEDGE-WIKI.md +128 -0
  109. package/docs/toolkit/guidelines/MODELING.md +134 -0
  110. package/docs/toolkit/guidelines/QA-EXECUTION.md +46 -0
  111. package/docs/toolkit/guidelines/QA-SCENARIOS.md +165 -0
  112. package/docs/toolkit/guidelines/REVIEW-ROUNDS.md +160 -0
  113. package/docs/toolkit/guidelines/SECURITY.md +160 -0
  114. package/docs/toolkit/guidelines/UI-UX.md +117 -0
  115. package/docs/toolkit/guidelines/WORKFLOW-MEMORY.md +73 -0
  116. package/docs/toolkit/guidelines.md +50 -0
  117. package/docs/toolkit/loop.md +77 -0
  118. package/docs/toolkit/purpose.md +51 -0
  119. package/docs/toolkit/repository-intelligence.md +53 -0
  120. package/docs/toolkit/reviews.md +77 -0
  121. package/knowledge/AGENTS.md +316 -0
  122. package/knowledge/raw/README.md +17 -0
  123. package/package.json +74 -0
  124. package/scripts/install_security_skills.py +816 -0
  125. package/scripts/installer/engine.js +254 -0
  126. package/scripts/installer/knowledge.js +28 -0
  127. package/scripts/installer/packets.js +77 -0
  128. package/scripts/installer/terminal.js +98 -0
  129. package/scripts/installer/transaction.js +75 -0
  130. package/skills-lock.json +92 -0
  131. package/templates/adoption/agents/core.md +23 -0
  132. package/templates/adoption/agents/quality.md +3 -0
  133. package/templates/adoption/knowledge/wiki/architecture/index.md +3 -0
  134. package/templates/adoption/knowledge/wiki/decisions/index.md +3 -0
  135. package/templates/adoption/knowledge/wiki/design/index.md +3 -0
  136. package/templates/adoption/knowledge/wiki/domain/index.md +3 -0
  137. package/templates/adoption/knowledge/wiki/index.md +3 -0
  138. package/templates/adoption/knowledge/wiki/log.md +3 -0
  139. package/templates/adoption/knowledge/wiki/open-questions/index.md +3 -0
  140. package/templates/adoption/knowledge/wiki/product/index.md +3 -0
  141. package/templates/adoption/knowledge/wiki/research/index.md +3 -0
  142. package/templates/adoption/product/AGENT-CONTEXT.md +25 -0
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env python3
2
+ """Content-safe observational token metrics for compatible providers."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ import secrets
9
+ import sqlite3
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+
13
+ USAGE_FIELDS = (
14
+ "input_tokens",
15
+ "cached_input_tokens",
16
+ "output_tokens",
17
+ "reasoning_output_tokens",
18
+ )
19
+
20
+
21
+ class TokenMetricsError(Exception):
22
+ """Safe error used internally when a measurement cannot be trusted."""
23
+
24
+
25
+ def _empty_usage() -> dict[str, int | None]:
26
+ return {"total_tokens": 0, **{field: None for field in USAGE_FIELDS}}
27
+
28
+
29
+ def _safe_count(value: object) -> int | None:
30
+ return value if isinstance(value, int) and not isinstance(value, bool) and value >= 0 else None
31
+
32
+
33
+ def _usage(value: object) -> bool:
34
+ expected = {"total_tokens", *USAGE_FIELDS}
35
+ return (
36
+ isinstance(value, dict)
37
+ and set(value) == expected
38
+ and _safe_count(value["total_tokens"]) is not None
39
+ and all(value[field] is None or _safe_count(value[field]) is not None for field in USAGE_FIELDS)
40
+ )
41
+
42
+
43
+ def _timestamp(value: object) -> bool:
44
+ if not isinstance(value, str) or not value:
45
+ return False
46
+ try:
47
+ datetime.fromisoformat(value.replace("Z", "+00:00"))
48
+ return True
49
+ except ValueError:
50
+ return False
51
+
52
+
53
+ def _now() -> str:
54
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
55
+
56
+
57
+ def _rollout_usage(path: object) -> dict[str, int | None]:
58
+ if not isinstance(path, str) or not path or not Path(path).is_file():
59
+ return {field: None for field in USAGE_FIELDS}
60
+ latest: object = None
61
+ try:
62
+ with Path(path).open(encoding="utf-8", errors="replace") as stream:
63
+ for line in stream:
64
+ try:
65
+ event = json.loads(line)
66
+ except json.JSONDecodeError:
67
+ continue
68
+ payload = event.get("payload") if isinstance(event, dict) else None
69
+ if not isinstance(payload, dict) or payload.get("type") != "token_count":
70
+ continue
71
+ info = payload.get("info")
72
+ if not isinstance(info, dict):
73
+ latest = None
74
+ continue
75
+ candidate = info.get("total_token_usage", info.get("last_token_usage"))
76
+ latest = candidate if isinstance(candidate, dict) else None
77
+ except OSError:
78
+ return {field: None for field in USAGE_FIELDS}
79
+ if not isinstance(latest, dict):
80
+ return {field: None for field in USAGE_FIELDS}
81
+ return {field: _safe_count(latest.get(field)) for field in USAGE_FIELDS}
82
+
83
+
84
+ def read_telemetry(db_path: str | Path, reviewer_prefix: str) -> dict[str, dict[str, int | None]]:
85
+ """Read only allowlisted counters for the explicitly configured path."""
86
+ path = Path(db_path).expanduser()
87
+ prefix = reviewer_prefix.rstrip("/") if isinstance(reviewer_prefix, str) else ""
88
+ if not prefix or not path.is_file():
89
+ raise TokenMetricsError("telemetry")
90
+ db: sqlite3.Connection | None = None
91
+ try:
92
+ db = sqlite3.connect(path.as_posix())
93
+ db.execute("PRAGMA query_only = ON")
94
+ columns = {row[1] for row in db.execute("PRAGMA table_info(threads)")}
95
+ if not {"id", "rollout_path", "tokens_used", "agent_path"}.issubset(columns):
96
+ raise TokenMetricsError("telemetry")
97
+ result: dict[str, dict[str, int | None]] = {}
98
+ for thread_id, rollout_path, tokens_used, agent_path in db.execute(
99
+ "SELECT id, rollout_path, tokens_used, agent_path FROM threads"
100
+ ):
101
+ if not isinstance(thread_id, str):
102
+ raise TokenMetricsError("telemetry")
103
+ if not isinstance(agent_path, str) or not (agent_path == prefix or agent_path.startswith(prefix + "/")):
104
+ continue
105
+ total = _safe_count(tokens_used)
106
+ if total is None:
107
+ raise TokenMetricsError("telemetry")
108
+ result[thread_id] = {"total_tokens": total, **_rollout_usage(rollout_path)}
109
+ return result
110
+ except TokenMetricsError:
111
+ raise
112
+ except (OSError, sqlite3.Error):
113
+ raise TokenMetricsError("telemetry") from None
114
+ finally:
115
+ if db is not None:
116
+ db.close()
117
+
118
+
119
+ def delta_usage(baseline: dict[str, dict[str, int | None]], snapshot: dict[str, dict[str, int | None]]) -> dict[str, int | None]:
120
+ if not set(baseline).issubset(snapshot):
121
+ raise TokenMetricsError("telemetry")
122
+ rows = []
123
+ for thread_id, current in snapshot.items():
124
+ previous = baseline.get(thread_id, _empty_usage())
125
+ total = int(current["total_tokens"]) - int(previous["total_tokens"])
126
+ if total < 0:
127
+ raise TokenMetricsError("telemetry")
128
+ detail: dict[str, int | None] = {"total_tokens": total}
129
+ for field in USAGE_FIELDS:
130
+ current_value, baseline_value = current[field], previous[field]
131
+ if current_value is None:
132
+ detail[field] = None
133
+ elif baseline_value is None:
134
+ detail[field] = current_value
135
+ else:
136
+ detail[field] = current_value - baseline_value
137
+ if detail[field] < 0:
138
+ raise TokenMetricsError("telemetry")
139
+ rows.append(detail)
140
+ result: dict[str, int | None] = {"total_tokens": sum(int(row["total_tokens"]) for row in rows)}
141
+ for field in USAGE_FIELDS:
142
+ result[field] = None if any(row[field] is None for row in rows) else sum(int(row[field]) for row in rows)
143
+ return result
144
+
145
+
146
+ def _total_usage(snapshot: dict[str, dict[str, int | None]]) -> dict[str, int | None]:
147
+ """Aggregate provider totals without retaining any provider content."""
148
+ rows = list(snapshot.values())
149
+ result: dict[str, int | None] = {
150
+ "total_tokens": sum(int(row["total_tokens"]) for row in rows),
151
+ }
152
+ for field in USAGE_FIELDS:
153
+ result[field] = None if any(row[field] is None for row in rows) else sum(int(row[field]) for row in rows)
154
+ return result
155
+
156
+
157
+ def _write_json(path: Path, payload: dict) -> None:
158
+ path.parent.mkdir(parents=True, exist_ok=True)
159
+ temporary = path.with_name(f".{path.name}.{os.getpid()}.{secrets.token_hex(4)}.tmp")
160
+ try:
161
+ temporary.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
162
+ os.chmod(temporary, 0o600)
163
+ os.replace(temporary, path)
164
+ finally:
165
+ temporary.unlink(missing_ok=True)
166
+
167
+
168
+ def _scope(options: dict) -> dict:
169
+ return {
170
+ "repository": str(options.get("repository", "unknown")),
171
+ "round": int(options.get("round", 0)),
172
+ "base": str(options.get("base", "unknown")),
173
+ "head": str(options.get("head", "unknown")),
174
+ "selected_files": int(options.get("selected_files", 0)),
175
+ "carried_files": int(options.get("carried_files", 0)),
176
+ "jobs": int(options.get("jobs", 0)),
177
+ "model": str(options.get("model", "unknown")),
178
+ "reasoning_effort": str(options.get("reasoning", "unknown")),
179
+ "reviewer_prefix": str(options.get("reviewer_prefix", "")),
180
+ }
181
+
182
+
183
+ def _unavailable(path: Path, reason: str, scope: dict | None = None, db_path: str | Path | None = None) -> dict:
184
+ payload = {
185
+ "schema_version": 1,
186
+ "kind": "review_token_metrics",
187
+ "started_at": _now(),
188
+ "finalized_at": _now(),
189
+ "runtime_db": str(Path(db_path).expanduser().resolve()) if db_path else "",
190
+ "scope": scope or _scope({}),
191
+ "status": "unavailable",
192
+ "reason": reason,
193
+ }
194
+ _write_json(path, payload)
195
+ return payload
196
+
197
+
198
+ def _valid_scope(scope: object) -> bool:
199
+ required = {"repository", "round", "base", "head", "selected_files", "carried_files", "jobs", "model", "reasoning_effort", "reviewer_prefix"}
200
+ if not isinstance(scope, dict) or set(scope) != required or not isinstance(scope["reviewer_prefix"], str):
201
+ return False
202
+ if not all(isinstance(scope[key], str) and bool(scope[key]) for key in ("repository", "base", "head", "model", "reasoning_effort")):
203
+ return False
204
+ return all(isinstance(scope[key], int) and scope[key] >= 0 for key in ("round", "selected_files", "carried_files", "jobs"))
205
+
206
+
207
+ def _valid_metrics(value: object) -> bool:
208
+ if not isinstance(value, dict):
209
+ return False
210
+ if value.get("status") == "unavailable":
211
+ return (
212
+ set(value) == {"schema_version", "kind", "started_at", "finalized_at", "runtime_db", "scope", "status", "reason"}
213
+ and value["schema_version"] == 1
214
+ and value["kind"] == "review_token_metrics"
215
+ and _timestamp(value["started_at"])
216
+ and _timestamp(value["finalized_at"])
217
+ and isinstance(value["runtime_db"], str)
218
+ and _valid_scope(value["scope"])
219
+ and isinstance(value["reason"], str)
220
+ )
221
+ expected = {"schema_version", "kind", "started_at", "finalized_at", "runtime_db", "scope", "baseline_by_thread", "reviewer_thread_count", "checkpoints", "usage", "final_snapshot_by_thread", "final_usage", "status"}
222
+ if set(value) != expected or value["schema_version"] != 1 or value["kind"] != "review_token_metrics" or not _timestamp(value["started_at"]):
223
+ return False
224
+ if value["finalized_at"] is not None and not _timestamp(value["finalized_at"]):
225
+ return False
226
+ scope = value["scope"]
227
+ if not _valid_scope(scope):
228
+ return False
229
+ if value["status"] == "running":
230
+ final_valid = value["final_snapshot_by_thread"] is None and value["final_usage"] is None
231
+ else:
232
+ final_snapshot = value["final_snapshot_by_thread"]
233
+ final_valid = (
234
+ isinstance(final_snapshot, dict)
235
+ and all(_usage(row) for row in final_snapshot.values())
236
+ and _usage(value["final_usage"])
237
+ )
238
+ if final_valid:
239
+ try:
240
+ final_valid = (
241
+ delta_usage(value["baseline_by_thread"], final_snapshot) == value["usage"]
242
+ and _total_usage(final_snapshot) == value["final_usage"]
243
+ )
244
+ except (KeyError, TypeError, TokenMetricsError):
245
+ final_valid = False
246
+ return (
247
+ isinstance(value["runtime_db"], str)
248
+ and isinstance(value["baseline_by_thread"], dict)
249
+ and all(_usage(row) for row in value["baseline_by_thread"].values())
250
+ and isinstance(value["reviewer_thread_count"], int)
251
+ and isinstance(value["checkpoints"], list)
252
+ and all(isinstance(row, dict) and set(row) == {"recorded_at", "completed_jobs", "usage"} and _timestamp(row["recorded_at"]) and isinstance(row["completed_jobs"], int) and row["completed_jobs"] >= 0 and _usage(row["usage"]) for row in value["checkpoints"])
253
+ and _usage(value["usage"])
254
+ and value["status"] in {"running", "complete"}
255
+ and (value["status"] == "running" or value["finalized_at"] is not None)
256
+ and final_valid
257
+ )
258
+
259
+
260
+ def read_metrics(path: str | Path) -> dict:
261
+ try:
262
+ value = json.loads(Path(path).read_text(encoding="utf-8"))
263
+ except (OSError, json.JSONDecodeError):
264
+ raise TokenMetricsError("ledger") from None
265
+ if not _valid_metrics(value):
266
+ raise TokenMetricsError("ledger")
267
+ return value
268
+
269
+
270
+ def start_metrics(path: str | Path, db_path: str | Path | None, reviewer_prefix: str | None, **options) -> dict:
271
+ state_path = Path(path).expanduser()
272
+ scope = _scope({**options, "reviewer_prefix": reviewer_prefix or ""})
273
+ if state_path.exists():
274
+ try:
275
+ metrics = read_metrics(state_path)
276
+ except TokenMetricsError:
277
+ return _unavailable(state_path, "persisted metrics unavailable", scope, db_path)
278
+ if metrics["status"] == "unavailable":
279
+ return metrics
280
+ if metrics["scope"] != scope or metrics["runtime_db"] != str(Path(db_path).expanduser().resolve() if db_path else ""):
281
+ return _unavailable(state_path, "metrics scope unavailable", scope, db_path)
282
+ if metrics["status"] == "complete":
283
+ return metrics
284
+ return validate_metrics(state_path)
285
+ if not db_path or not reviewer_prefix:
286
+ return _unavailable(state_path, "compatible telemetry unavailable", scope, db_path)
287
+ try:
288
+ snapshot = read_telemetry(db_path, reviewer_prefix)
289
+ except TokenMetricsError:
290
+ return _unavailable(state_path, "compatible telemetry unavailable", scope, db_path)
291
+ metrics = {
292
+ "schema_version": 1,
293
+ "kind": "review_token_metrics",
294
+ "started_at": _now(),
295
+ "finalized_at": None,
296
+ "runtime_db": str(Path(db_path).expanduser().resolve()),
297
+ "scope": scope,
298
+ "baseline_by_thread": snapshot,
299
+ "reviewer_thread_count": len(snapshot),
300
+ "checkpoints": [],
301
+ "usage": _empty_usage(),
302
+ "final_snapshot_by_thread": None,
303
+ "final_usage": None,
304
+ "status": "running",
305
+ }
306
+ _write_json(state_path, metrics)
307
+ return metrics
308
+
309
+
310
+ def _measure(path: Path, metrics: dict) -> dict:
311
+ if metrics["status"] != "running":
312
+ return metrics
313
+ try:
314
+ snapshot = read_telemetry(metrics["runtime_db"], metrics["scope"]["reviewer_prefix"])
315
+ usage = delta_usage(metrics["baseline_by_thread"], snapshot)
316
+ if usage["total_tokens"] < metrics["usage"]["total_tokens"]:
317
+ raise TokenMetricsError("telemetry")
318
+ except TokenMetricsError:
319
+ return _unavailable(path, "runtime telemetry unavailable", metrics["scope"], metrics["runtime_db"])
320
+ return {**metrics, "reviewer_thread_count": len(snapshot), "usage": usage}
321
+
322
+
323
+ def validate_metrics(path: str | Path) -> dict:
324
+ state_path = Path(path)
325
+ metrics = read_metrics(state_path)
326
+ measured = _measure(state_path, metrics)
327
+ if measured is not metrics:
328
+ _write_json(state_path, measured)
329
+ return measured
330
+
331
+
332
+ def checkpoint_metrics(path: str | Path, completed_jobs: int) -> dict:
333
+ state_path = Path(path)
334
+ metrics = read_metrics(state_path)
335
+ measured = _measure(state_path, metrics)
336
+ if measured["status"] == "unavailable":
337
+ return measured
338
+ checkpoint = {"recorded_at": _now(), "completed_jobs": max(0, int(completed_jobs)), "usage": measured["usage"]}
339
+ next_metrics = {**measured, "checkpoints": [*measured["checkpoints"], checkpoint]}
340
+ _write_json(state_path, next_metrics)
341
+ return next_metrics
342
+
343
+
344
+ def finalize_metrics(path: str | Path) -> dict:
345
+ state_path = Path(path)
346
+ metrics = read_metrics(state_path)
347
+ if metrics["status"] in {"complete", "unavailable"}:
348
+ return metrics
349
+ measured = _measure(state_path, metrics)
350
+ if measured["status"] == "unavailable":
351
+ return measured
352
+ try:
353
+ final_snapshot = read_telemetry(measured["runtime_db"], measured["scope"]["reviewer_prefix"])
354
+ final_usage = _total_usage(final_snapshot)
355
+ except TokenMetricsError:
356
+ return _unavailable(state_path, "runtime telemetry unavailable", measured["scope"], measured["runtime_db"])
357
+ finalized = {
358
+ **measured,
359
+ "finalized_at": _now(),
360
+ "final_snapshot_by_thread": final_snapshot,
361
+ "final_usage": final_usage,
362
+ "status": "complete",
363
+ }
364
+ _write_json(state_path, finalized)
365
+ return finalized
366
+
367
+
368
+ def write_unavailable_metrics(path: str | Path, reason: str = "compatible telemetry unavailable") -> None:
369
+ _unavailable(Path(path), reason)