@techierathore/techieflow 1.0.2 → 1.0.4

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 (204) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +5 -13
  2. package/.claude/commands/TechieFlow/agents/architect.md +1 -16
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +42 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +24 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_owner-language.md +26 -0
  7. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  8. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  9. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  10. package/.claude/commands/TechieFlow/tasks/amend-docs.md +21 -143
  11. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  12. package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +4 -0
  13. package/.claude/commands/TechieFlow/tasks/create-doc.md +4 -0
  14. package/.claude/commands/TechieFlow/tasks/create-project-brief.md +22 -0
  15. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  16. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  17. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  18. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  19. package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +4 -0
  20. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  21. package/.claude/commands/TechieFlow/tasks/generate-html.md +5 -1
  22. package/.claude/commands/TechieFlow/tasks/handoff-phase.md +18 -16
  23. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  24. package/.claude/commands/TechieFlow/tasks/metrics-report.md +3 -1
  25. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  26. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  27. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  28. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  29. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  30. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  31. package/.opencode/plugin/techieflow.js +40 -13
  32. package/.tfcore/agents/analyst.md +5 -13
  33. package/.tfcore/agents/architect.md +1 -16
  34. package/.tfcore/agents/flow-master.md +42 -215
  35. package/.tfcore/agents/verifier.md +24 -64
  36. package/.tfcore/core-config.yaml +10 -0
  37. package/.tfcore/hooks/block-git.sh +4 -4
  38. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  39. package/.tfcore/hooks/guard-build.sh +76 -0
  40. package/.tfcore/hooks/guard-db.sh +90 -0
  41. package/.tfcore/hooks/guard-metrics.sh +68 -0
  42. package/.tfcore/hooks/guard-status-html.sh +116 -71
  43. package/.tfcore/hooks/guard-status.sh +43 -16
  44. package/.tfcore/hooks/guard-verify-deps.sh +124 -0
  45. package/.tfcore/hooks/guard-verify.sh +70 -48
  46. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  47. package/.tfcore/install-manifest.yaml +0 -18
  48. package/.tfcore/routing.yaml +86 -14
  49. package/.tfcore/standards/coding-standards-core.md +61 -0
  50. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  51. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  52. package/.tfcore/tasks/_owner-language.md +26 -0
  53. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  54. package/.tfcore/tasks/_status-update-gate.md +10 -74
  55. package/.tfcore/tasks/_yolo-mode.md +20 -58
  56. package/.tfcore/tasks/amend-docs.md +21 -143
  57. package/.tfcore/tasks/build-phase.md +14 -224
  58. package/.tfcore/tasks/create-deep-research-prompt.md +4 -0
  59. package/.tfcore/tasks/create-doc.md +4 -0
  60. package/.tfcore/tasks/create-project-brief.md +22 -0
  61. package/.tfcore/tasks/day1-brownfield.md +31 -505
  62. package/.tfcore/tasks/day1-greenfield.md +28 -213
  63. package/.tfcore/tasks/deploy-checklist.md +19 -0
  64. package/.tfcore/tasks/devguide.md +10 -219
  65. package/.tfcore/tasks/facilitate-brainstorming-session.md +4 -0
  66. package/.tfcore/tasks/fix-issues.md +10 -129
  67. package/.tfcore/tasks/generate-html.md +5 -1
  68. package/.tfcore/tasks/handoff-phase.md +18 -16
  69. package/.tfcore/tasks/log-miss.md +19 -158
  70. package/.tfcore/tasks/metrics-report.md +3 -1
  71. package/.tfcore/tasks/mockups.md +12 -117
  72. package/.tfcore/tasks/refresh-status.md +9 -130
  73. package/.tfcore/tasks/split-brd.md +10 -126
  74. package/.tfcore/tasks/triage-and-fix.md +14 -0
  75. package/.tfcore/tasks/triage-issues.md +11 -152
  76. package/.tfcore/tasks/verify-phase.md +36 -572
  77. package/.tfcore/telemetry/SCHEMA.md +100 -8
  78. package/.tfcore/telemetry/tf-metrics.sh +564 -32
  79. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  80. package/.tfcore/templates/stack-questions.md +77 -0
  81. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  82. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  83. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  84. package/.tfcore/templates/v4custom/app-brief-tmpl.md +65 -0
  85. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  86. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  87. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  88. package/.tfcore/templates/v4custom/app-decision-request-tmpl.md +85 -0
  89. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  90. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  91. package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +86 -31
  92. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  93. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  94. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  95. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  96. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  97. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  98. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  99. package/.tfcore/templates/v4custom/html-render-shell.md +1 -1
  100. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  101. package/.tfcore/user-guide.md +0 -23
  102. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  103. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  104. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  105. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  106. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  107. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  108. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  109. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  110. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  111. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  112. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  113. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  114. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  115. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  116. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  117. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  118. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  119. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  120. package/.tfcore/utils/tf-brd-status.py +176 -0
  121. package/.tfcore/utils/tf-brd-status.sh +19 -0
  122. package/.tfcore/utils/tf-build-list.py +211 -0
  123. package/.tfcore/utils/tf-build-list.sh +21 -0
  124. package/.tfcore/utils/tf-build.sh +143 -0
  125. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  126. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  127. package/.tfcore/utils/tf-day1-files.py +208 -0
  128. package/.tfcore/utils/tf-day1-files.sh +20 -0
  129. package/.tfcore/utils/tf-devguide-list.py +249 -0
  130. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  131. package/.tfcore/utils/tf-doc-check.py +1369 -0
  132. package/.tfcore/utils/tf-doc-check.sh +29 -0
  133. package/.tfcore/utils/tf-emit.sh +545 -18
  134. package/.tfcore/utils/tf-fix-close.sh +115 -0
  135. package/.tfcore/utils/tf-gitignore-audit.sh +50 -3
  136. package/.tfcore/utils/tf-goal.sh +284 -66
  137. package/.tfcore/utils/tf-harness.sh +4 -9
  138. package/.tfcore/utils/tf-log-miss.py +197 -0
  139. package/.tfcore/utils/tf-log-miss.sh +14 -0
  140. package/.tfcore/utils/tf-misses-md.py +208 -0
  141. package/.tfcore/utils/tf-misses-md.sh +14 -0
  142. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  143. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  144. package/.tfcore/utils/tf-model-pick.sh +475 -0
  145. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  146. package/.tfcore/utils/tf-perf-grade.py +97 -0
  147. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  148. package/.tfcore/utils/tf-phase.sh +92 -0
  149. package/.tfcore/utils/tf-render-html.py +20 -0
  150. package/.tfcore/utils/tf-routing-bind.sh +97 -6
  151. package/.tfcore/utils/tf-routing.sh +13 -7
  152. package/.tfcore/utils/tf-selfcheck.sh +323 -0
  153. package/.tfcore/utils/tf-split-brd.py +309 -0
  154. package/.tfcore/utils/tf-split-brd.sh +22 -0
  155. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  156. package/.tfcore/utils/tf-status-facts.py +358 -0
  157. package/.tfcore/utils/tf-status-facts.sh +20 -0
  158. package/.tfcore/utils/tf-triage.py +243 -0
  159. package/.tfcore/utils/tf-triage.sh +16 -0
  160. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  161. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  162. package/.tfcore/utils/tf-verify-env.sh +88 -0
  163. package/.tfcore/utils/tf-verify-list.py +270 -0
  164. package/.tfcore/utils/tf-verify-list.sh +13 -0
  165. package/.tfcore/utils/tf-verify-screens.mjs +355 -0
  166. package/.tfcore/utils/tf-verify-screens.sh +31 -0
  167. package/.tfcore/utils/tf-verify-tests.sh +166 -0
  168. package/.tfcore/utils/tf-verify-verdict.py +292 -0
  169. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  170. package/.tfcore/utils/tf-yolo.sh +59 -0
  171. package/README.md +120 -1145
  172. package/docs/TechieFlow-Installation.md +1 -5
  173. package/opencode.jsonc +37 -33
  174. package/package.json +1 -5
  175. package/scripts/install.mjs +56 -46
  176. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  177. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  178. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  179. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  180. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  181. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  182. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  183. package/.codex/config.toml +0 -46
  184. package/.codex/hooks.json +0 -63
  185. package/.codex/rules/techieflow.rules +0 -8
  186. package/.tfcore/hooks/codex-adapter.py +0 -248
  187. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  188. package/.tfcore/tasks/author-brd.md +0 -79
  189. package/.tfcore/tasks/document-project.md +0 -343
  190. package/.tfcore/tasks/execute-checklist.md +0 -86
  191. package/.tfcore/tasks/index-docs.md +0 -173
  192. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  193. package/.tfcore/tasks/shard-doc.md +0 -185
  194. package/.tfcore/templates/architecture-tmpl.yaml +0 -650
  195. package/.tfcore/templates/brownfield-architecture-tmpl.yaml +0 -476
  196. package/.tfcore/templates/competitor-analysis-tmpl.yaml +0 -306
  197. package/.tfcore/templates/front-end-architecture-tmpl.yaml +0 -218
  198. package/.tfcore/templates/fullstack-architecture-tmpl.yaml +0 -823
  199. package/.tfcore/templates/market-research-tmpl.yaml +0 -252
  200. package/.tfcore/templates/project-brief-tmpl.yaml +0 -221
  201. package/.tfcore/utils/techieflow-doc-template.md +0 -325
  202. package/.tfcore/utils/tf-codex-bind.py +0 -196
  203. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  204. package/WORKFLOW.html +0 -1986
@@ -39,7 +39,7 @@ Questions 1–3 are answered by `gates.jsonl`, which remains **the primary strea
39
39
  | `project_type_inferred` | bool | Present **only when `true`** — `metrics.project_type` was absent from `core-config.yaml` and `app` was assumed. Reports must label these records **unclassified**, never silently pool them. |
40
40
  | `backfilled` | bool | Present **only when `true`** — the record was reconstructed after the fact, not written at the moment of the event. Written exclusively by `tf-metrics.sh --backfill-*`. |
41
41
  | `inferred` | string[] | Present only on backfilled records. Names the fields that were **guessed rather than read**. |
42
- | `harness` | string \| null | `claude-code` \| `opencode` \| `codex` \| `null`. **Detected by `tf-emit.sh`, never declared by a task** — see below. |
42
+ | `harness` | string \| null | `claude-code` \| `opencode` \| `null`. **Detected by `tf-emit.sh`, never declared by a task** — see below. `codex` is retired (2026-09-07): nothing writes it, and records that carry it stay valid and readable. |
43
43
 
44
44
  ### `project_type` — what it is and why it exists
45
45
 
@@ -70,11 +70,11 @@ So `install-metrics.sh` now re-examines **`docs` alone**, on a later refresh, an
70
70
 
71
71
  ### `harness` — detected, never declared
72
72
 
73
- The framework runs under **three harnesses**: Claude Code (`.claude/commands/TechieFlow/`), OpenCode (agents/tasks loaded from `opencode.jsonc`), and Codex (`.agents/skills/` plus `.codex/agents/`). The task content is identical across harnesses. A task template therefore **cannot know which one is executing it** — an agent copying a literal from the markdown would stamp whichever harness the example happened to name, and every per-harness comparison would be quietly wrong.
73
+ The framework runs under **two harnesses**: Claude Code (`.claude/commands/TechieFlow/`) and OpenCode (agents/tasks loaded from `opencode.jsonc`). A third, Codex, was retired on 2026-09-07; records written before then may carry `harness: "codex"` and stay valid. The task content is identical across harnesses. A task template therefore **cannot know which one is executing it** — an agent copying a literal from the markdown would stamp whichever harness the example happened to name, and every per-harness comparison would be quietly wrong.
74
74
 
75
75
  So `tf-emit.sh` detects it and injects it. **Never write `harness` into an emit template.** Detection order:
76
76
 
77
- 1. the adapter-owned `TF_HARNESS`, then harness environment variables (`CLAUDECODE`, `CLAUDE_CODE_*` → `claude-code`; any `OPENCODE*` → `opencode`; Codex thread/session markers → `codex`);
77
+ 1. the adapter-owned `TF_HARNESS`, then harness environment variables (`CLAUDECODE`, `CLAUDE_CODE_*` → `claude-code`; any `OPENCODE*` → `opencode`);
78
78
  2. the parent process chain, bounded to 12 levels (OpenCode sets no `OPENCODE_*` variables, so the process name is the only honest signal);
79
79
  3. **`null`** if neither resolves.
80
80
 
@@ -93,7 +93,7 @@ So `tf-emit.sh` detects it and injects it. **Never write `harness` into an emit
93
93
 
94
94
  | Field | Type | Values / notes |
95
95
  |---|---|---|
96
- | `cmd` | string | `day1-brownfield` \| `day1-greenfield` \| `split-brd` \| `mockups` \| `build-phase` \| `verify-phase` \| `fix-issues` \| `triage-issues` \| `log-miss` \| `devguide` \| `productguide` \| `handoff-phase` \| `refresh-status` \| `amend-docs` |
96
+ | `cmd` | string | `day1-brownfield` \| `day1-greenfield` \| `split-brd` \| `mockups` \| `build-phase` \| `verify-phase` \| `fix-issues` \| `triage-issues` \| `log-miss` \| `devguide` \| `productguide` \| `handoff-phase` \| `refresh-status` \| `amend-docs` \| `deploy-checklist` (added 2026-09-06) \| `metrics-report` \| `generate-html` \| `render-workflow-docs` \| `triage-and-fix` \| `framework-reset` (framework maintenance, D-19; all five added 2026-09-07, after FR-37's check was built and found 27 records already carrying a value the schema did not list). A value outside this list is a defect in the emitting task, not in the reader: the reader keeps it. |
97
97
  | `mode` | string \| null | `build` \| `fix`. `build-phase` already distinguishes these (FIX mode) — capture it; the ratio is the rework metric. `null` for commands with no mode. |
98
98
  | `started` | string | ISO-8601 UTC. When the task began — the timestamp you noted at step 0, not "now minus a guess". |
99
99
  | `ended` | string | ISO-8601 UTC. Normally equal to `ts`. |
@@ -104,6 +104,7 @@ So `tf-emit.sh` detects it and injects it. **Never write `harness` into an emit
104
104
  | `files_written` | int | A count the agent **already knows**. Do not shell out to compute it. |
105
105
  | `build_result` | string \| null | `pass` \| `fail` \| `not-run`. |
106
106
  | `harness` | string \| null | Injected by `tf-emit.sh` (§1). **Do not emit it yourself.** |
107
+ | `yolo` | bool | Whether YOLO / goal mode was on for this run (D-12, FR-35; added 2026-09-05). Written by the task from the flag file at the status gate; wired into every task in Session 4c. |
107
108
 
108
109
  ---
109
110
 
@@ -120,12 +121,40 @@ So `tf-emit.sh` detects it and injects it. **Never write `harness` into an emit
120
121
  | `routed` | bool | `model == tier_model`. Present only when both are known. Routing is observed, never enforced — `routed:false` is drift made visible, not an error. |
121
122
  | `tokens_in`, `tokens_out`, `tokens_cache_read`, `tokens_cache_write` | int | Σ over assistant messages whose timestamp ∈ [`started`, `ended`]. Requires the record to carry `started` + `ended` and the session pointer (`.tfcore/.session/<harness>.json` — written by the `session-pointer.sh` hook on Claude, by the plugin on OpenCode). |
122
123
  | `cost_usd` | number \| null | Σ real per-message cost from `opencode.db` on OpenCode; **always `null` on Claude Code** (the transcript has no cost and a rate-card estimate would be an estimate presented as a measurement). |
123
- | `tokens_scope` | string | `tree` = the full session tree — OpenCode: pointer session + descendant sessions; Claude: pointer transcript + the subagent transcripts beside it (`<transcript-dir>/<session-id>/subagents/agent-*.jsonl`, a deterministic path verified 2026-08-20 via a `SubagentStop` payload's `agent_transcript_path`). `main` = Claude main thread only (no subagents dir existed). `conversation` = an exact persisted Codex rollout counter bounded by documented conversation events; measured session data, not an estimate. `none` = window could not be computed (no pointer / unreadable store / empty window) — **tokens are never estimated**. |
124
+ | `tokens_scope` | string | `tree` = the full session tree — OpenCode: pointer session + descendant sessions; Claude: pointer transcript + the subagent transcripts beside it (`<transcript-dir>/<session-id>/subagents/agent-*.jsonl`, a deterministic path verified 2026-08-20 via a `SubagentStop` payload's `agent_transcript_path`). `main` = Claude main thread only (no subagents dir existed). `conversation` = an exact persisted measured session data, not an estimate. `none` = window could not be computed (no pointer / unreadable store / empty window) — **tokens are never estimated**. |
124
125
 
125
126
  | `attempt` | int | **`runs` only, added 2026-08-21.** `1 +` the number of prior non-backfilled `run` records with the same `cmd` whose `reqs_touched` intersects this record's. Stamped only when the record carries a non-empty `reqs_touched`; absent on backfilled records and on REQ-less runs (`metrics-report`, renders). Distinct from the gate-level `attempt` in §3.1 (per REQ per verify). This is the counter `routing.yaml` `escalation:` reads **at launch** — `bash .tfcore/utils/tf-emit.sh --next-run-attempt <cmd> <REQ-ID>...` prints the value the next record would get. Advisory: telemetry records, it never switches a model (DECISIONS.md 2026-08-21). |
126
127
 
127
128
  Provenance rule applied once more: **never pool `cost_usd` across harness** — Claude records are `null`, and a sum over mixed records silently under-reports. Tokens may be compared across harness; dollars may not.
128
129
 
130
+ ### 2.5b How the model was paid for (added 2026-09-10; injected by `tf-emit.sh`, never emitted by an agent)
131
+
132
+ `cost_usd` alone answers *how much* and never *of what*, and on one machine it is three different quantities at once. The owner found this on 2026-09-10 (MISS-TechieFlow-20260910-02): a `runs.jsonl` here holds OpenCode records on a metered API key where the dollars are real money, OpenCode records on an OAuth subscription where OpenCode records **0.0** because the plan was already paid for, OpenCode records on a model running on this machine where there is no bill at all, and Claude Code records that are permanently `null`. Summing them produces a number that is neither the money spent nor the effort used, and the two zeroes are indistinguishable from a free run.
133
+
134
+ | Field | Type | Meaning / source |
135
+ |---|---|---|
136
+ | `billing_mode` | string | `subscription` \| `metered` \| `local` \| `mixed` \| `unknown`. Resolved per observed model by `bash .tfcore/utils/tf-model-pick.sh billing <model> <harness>`: OpenCode's own `auth.json` says `oauth` (a plan) or `api` (a key), a provider that runs on this machine is `local`, a provider with no credential is `unknown` and is never assumed to be billable, and `routing.yaml` `billing:` overrides any of it by name. Claude Code is **declared** in `billing.claude` (default `subscription`), because nothing in a transcript says which plan paid for it. `mixed` when one window ran models of more than one mode — which is exactly what a fallback from a subscription to a metered key looks like. |
137
+ | `cost_source` | string | `opencode-db` where a per-message cost was read, `none` where there is no cost source at all. **Never a rate card.** |
138
+
139
+ `billing_mode` has five values, and the `plan` one is the reason the field is not a boolean:
140
+
141
+ | Value | What the provider's cost number is |
142
+ |---|---|
143
+ | `subscription` | Nothing, or a recorded `0.0`. A flat fee bought the quota before the run started. |
144
+ | `plan` | **Allowance consumed against a per-model limit, not an invoice.** OpenCode Go is $10/month with a $15–$60 monthly allowance *per model* and 5-hour / weekly / monthly thresholds (<https://opencode.ai/docs/go>), so its dollars are a meter — and they are precisely the numbers that produce the limit `routing.yaml` `fallbacks:` exists for. Detected from the provider name, because an API key alone cannot tell a plan from a per-token bill. |
145
+ | `metered` | Money actually billed, per token. |
146
+ | `local` | No bill at all. |
147
+ | `mixed` / `unknown` | Several modes in one window; or nothing here could tell. `unknown` is never quietly treated as billable. |
148
+
149
+ **Money is never computed into a record.** Tokens are what the framework measures and stores — per model, main thread and sub-agents apart (§2.5, §2.6) — and turning them into a price is a *reporting* job, done by `tf-metrics.sh` (and by any other reader, TfLens included) from the fields already on the stream. That is §8's existing rule, *derived metrics are computed, never stored*, applied to money; it is why a rate card can change, or a reader disagree with this one, without a single record becoming wrong, and why every run ever recorded is re-priced the moment the rate card is corrected. The shared rate lookup is `bash .tfcore/utils/tf-model-pick.sh rate <model>`, which reads the models.dev catalog and lets a project's own `.tfcore/rate-card.json` win.
150
+
151
+ Two simplifications in that price, stated rather than hidden: the **base** rate is used, so a provider's long-context surcharge is not applied (nothing records per-message context length, and guessing it would be worse than the small understatement); and a model the rate card does not carry produces no number rather than a zero.
152
+ | `fallback_from` | string | **`runs`/`gates` only.** The tier's own model, named here when the run started while that model was on cooldown for a usage limit. It is what separates *"routing drifted"* from *"the tier model was out of quota and the chain moved on"* — both of which set `routed:false`, and only one of which is a problem. |
153
+
154
+ **The reporting rule, enforced in `tf-metrics.sh` and not merely stated here:** the report carries **three** money lines and never merges them — *list price*, computed over every record that can be priced and labelled a price rather than a bill; *plan allowance used*, per model, over `billing_mode:"plan"` records, because that is the meter a per-model limit is read against; and *money billed*, over `billing_mode:"metered"` records only. Each prints what it excluded. A window whose models were paid for in different ways contributes real money that cannot be separated from the part a subscription had already covered, so it gets its own line rather than being folded into any of the three. Per-model spend is attributed only where the window ran a single model; splitting one window's dollars across several models by token share is arithmetic, not measurement — the same line §5.5.3 draws between `sole` and `shared`. A record written before 2026-09-10 carries no `billing_mode`; it is admitted on its old terms and counted separately, so this change does not silently rewrite the past.
155
+
156
+ Tokens remain the unit that works everywhere, and they are what every figure is built from. `cost_usd` is whatever the provider itself reported, and `billing_mode` is what stops a reader mistaking a meter, a flat fee and an invoice for one another.
157
+
129
158
  ### 2.6 Per-phase effort fields (added 2026-08-31; injected by `tf-emit.sh`, never emitted by an agent)
130
159
 
131
160
  Added so the question *"what did each phase cost — in time, in tokens, on which model, with how much fan-out?"* is answerable from the streams rather than reconstructed by hand. §2 and §2.5 already carried `cmd`, `started`/`ended`/`duration_s`, the four token counters, `model`, `harness`, `tier`/`tier_model`/`routed` and `attempt`. Three things were missing, and each was missing for the same reason: it was either **self-reported** or **not represented at all**.
@@ -146,6 +175,34 @@ Added so the question *"what did each phase cost — in time, in tokens, on whic
146
175
 
147
176
  **These do not create per-feature timing.** The unit stays **the run**; §0's non-goal is unchanged. `cmd` is the phase, and a phase figure is an aggregate over runs of that `cmd` — never a per-ticket cycle time.
148
177
 
178
+ ### 2.7 `kind: "run-void"` — taking a wrong run out of the figures without deleting it (added 2026-09-09)
179
+
180
+ The stream is append-only, so a run record that turns out to be **wrong** can be neither edited nor deleted — and nothing inside such a record says it is wrong, so every figure built over it is quietly polluted. §5.5.7 solved the same problem on the misses stream by adding a record kind rather than an edit. This is that answer for runs.
181
+
182
+ A `run-void` names one run record and says why it should not be counted:
183
+
184
+ | Field | Type | Meaning |
185
+ |---|---|---|
186
+ | `kind` | `"run-void"` | the record kind |
187
+ | `app` | string | filled by the emitter from the run it names |
188
+ | `cmd` | string | the named run's `cmd` |
189
+ | `started` | ISO-8601 | the named run's `started` — `cmd` + `started` is the pair the readers already key on |
190
+ | `reason` | string | free text, required: why the record is wrong, in a sentence a reader can check |
191
+
192
+ **Written only by `bash .tfcore/utils/tf-emit.sh --void-run <cmd> <started> "<reason>"`**, which refuses one that names no run on the stream (a void about nothing is a claim about nothing) and refuses a second void on the same run. Like an amend, it exits 0 either way: telemetry has no veto (§10).
193
+
194
+ **What the readers must do, and `tf-metrics.sh` does:** the named record leaves **every** figure — it is never clamped, halved or guessed at — and the count of what left travels with the figures, as `runs_voided_n` with the reasons in `runs_voided`. A total offered without its exclusions is just a different wrong number (the §5.5.8 rule, one stream over). A void naming a run that is not on this stream is an **orphan**: counted as `run_voids_orphaned_n` and reported, never silently dropped, exactly as an orphaned `miss-amend` is.
195
+
196
+ **What it is not.** It is not a delete and not an edit: both records stay, in order, and a reader can always see what was corrected and why. It cannot be used to remove a run whose figures are merely unflattering — the reason is on the record and a reader can check it, which is the whole protection. It is not a way to fix a number: there is no "corrected duration" field, because a figure nobody measured is not recoverable by asserting one.
197
+
198
+ ### 2.7b A run may not start before the last one ended (added 2026-09-10)
199
+
200
+ §2.7 gave a way to **correct** a start time nobody measured, and nothing refused one — so the same mistake was made twice more on the day the correction shipped, each record beginning before the record before it ended, each quietly adding its overlap to every total that crossed it (MISS-TechieFlow-20260910-04, sorted `ignored`: it was written down and not followed). A correction mechanism with no refusal in front of it is a fix that has to be remembered, which is the definition of a rule that rots.
201
+
202
+ `tf-emit.sh` now **refuses** a live `run` record for an app whose `started` precedes the latest `ended` of that app's live runs. The refusal names the record it collides with and the timestamp to use instead; nothing is appended. **Voided and backfilled records are excluded from the comparison**, which is what keeps the §2.7 repair path working: void the wrong record, then the corrected one is accepted.
203
+
204
+ The one real overlap is two machines appending to the same `merge=union` stream (§6). That is declared, with `bash .tfcore/utils/tf-emit.sh runs --allow-overlap`, and never assumed — the mistake this refuses is easy to make and impossible to see afterwards, so the door is a flag rather than a fallback. FR-72; `bash tests/regression/run.sh tf_overlap`.
205
+
149
206
  ## 3. `docs/metrics/gates.jsonl` — one record per REQ verdict per verify run
150
207
 
151
208
  **This is the primary stream.** If only one stream survives, it is this one.
@@ -172,7 +229,7 @@ Failure:
172
229
  |---|---|---|
173
230
  | `run_id` | string | The `started` timestamp of the owning run. Ties every REQ verdict in one verify pass together. |
174
231
  | `req_id` | string | e.g. `REQ-UI-004`. |
175
- | `req_class` | string | `UI` \| `FN` \| `RAG` \| `NFR` — the prefix segment of `req_id`. |
232
+ | `req_class` | string | `UI` \| `FN` \| `RAG` \| `NFR` — the prefix segment of `req_id`. **`FR` added 2026-09-07**: the framework's own requirement lines (`docs/TechieFlow-Requirements.md`), graded by `tests/requirements/run.sh`. It is the one `req_class` that does not come from an application's checklist, and it never pools with the others — a framework line and a screen requirement are not the same unit. |
176
233
  | `attempt` | int | See §3.1. Derive it; never guess it. |
177
234
  | `verdict` | string | Mirrors the checklist vocabulary **exactly**: `Verified` \| `Needs re-verify` \| `FAIL` \| `Blocked` \| `Implemented` \| `Done (pre-existing)`. |
178
235
  | `gate` | string \| null | **The FIRST gate that failed**, or `null` on a pass. See §3.2. |
@@ -297,7 +354,7 @@ Written by `.tfcore/hooks/metrics-session.sh`, wired to the **`SessionEnd`** hoo
297
354
  | `cache_creation_tokens` | int | Σ `usage.cache_creation_input_tokens`. |
298
355
  | `cost_usd` | number \| null | **Almost always `null`.** See the limitation below. |
299
356
 
300
- **Known limitation — cost is not in the transcript.** Claude Code `2.x` transcripts carry token counts but no per-message dollar cost, and this framework runs on a Claude Max subscription where marginal per-token cost is not the real unit anyway. `cost_usd` is therefore emitted as `null` and *never* computed from a rate card — that would be an estimate presented as a measurement. Consequently **"cost per verified REQ" is reported in tokens, not dollars**, unless a real cost source appears. (OpenCode's `opencode stats` *does* report real cost — that is the one thing it can measure which Claude Code cannot. Wiring it in is the obvious way this field stops being `null`.)
357
+ **Known limitation — cost is not in the transcript.** Claude Code `2.x` transcripts carry token counts but no per-message dollar cost, and this framework runs on a Claude Max subscription where marginal per-token cost is not the real unit anyway. `cost_usd` is therefore emitted as `null` and *never* computed from a rate card — that would be an estimate presented as a measurement. (Since 2026-09-10 the report does price a run from a rate card, at read time and never into a record; §2.5b is what keeps the two apart.) Consequently **"cost per verified REQ" is reported in tokens, not dollars**, unless a real cost source appears. (OpenCode's `opencode stats` *does* report real cost — that is the one thing it can measure which Claude Code cannot. Wiring it in is the obvious way this field stops being `null`.) **A caveat added 2026-09-10:** OpenCode's cost is real only where the provider is billed per token. On a provider signed in with OAuth it records `0.0`, which is true of the marginal cost and false of the money, so a session record's cost carries the same warning as a run record's — with the difference that `sessions.jsonl` has no `billing_mode` field, because it is not joinable to `runs.jsonl` on anything but time (§4, below) and would have no report to serve. Read session dollars per provider or not at all.
301
358
 
302
359
  **Known limitation — session ≠ run.** One session may span several commands, or one command several sessions. `sessions.jsonl` is **not** joinable to `runs.jsonl` on anything but time. Do not attribute a session's tokens to a single run.
303
360
 
@@ -392,6 +449,8 @@ This is the only stream that carries **three record kinds**: `miss` opens, `miss
392
449
  | `found_gate` | string \| null | When `found_by == "gate"`, which gate — same vocabulary as §3.2. `null` otherwise. |
393
450
  | `found_run_id` | string \| null | `started` of the finding run. |
394
451
  | `failure_class` | string \| null | The §3.3 closed vocabulary, reused verbatim. `null` where none applies. |
452
+ | `what` | string \| null | **The one free-text field** (added 2026-09-05, reset Sitting 4a; D-10). One sentence, in the owner's words, saying what was missed. Never requirement text, file content or a secret (§9). Written into the human-readable `docs/<App>-Misses.md` beside the record by the emitter (§5.5.10, Session 5). |
453
+ | `sort` | string \| null | **Whose gap it was** (added 2026-09-07, reset Session 5; FR-32). The four questions of the miss protocol, asked in order, the first that fits: `spec` (the app's spec did not say it; fix the checklist line) · `unsaid` (the framework never said it; one requirement line plus a check) · `weak-check` (a check existed and did not catch it; fix the check) · `ignored` (it was written and not followed; a hook, or delete the rule). Required by `tf-log-miss.sh`; defaulted by `tf-triage.sh` (`weak-check` for a demoted row, `spec` for a new one, `ignored` for a code edit during triage); amendable (§5.5.7). `null` on records written before the field existed; `FIELD_SINCE` keeps them out of the denominator. |
395
454
 
396
455
  **`origin_model`, `origin_harness` and `origin_confidence` are never written by an agent** — the same rule as `harness` (§1) and for the same reason: task markdown is shared byte-identically across three harnesses, so a copied literal would stamp the wrong model on every record and quietly corrupt every per-model comparison built on it. `tf-emit.sh` resolves all three from `origin_run_id`, and **forces `origin_model` / `origin_harness` to `null` whenever the lookup fails, overwriting anything the caller supplied.** A guess is worse than a gap here, because nothing downstream can see that it was one.
397
456
 
@@ -419,7 +478,7 @@ The agent's job on a `miss` record is therefore small and honest: name what was
419
478
  | `verdict_after` | string | `Verified` \| `Needs re-verify` \| `FAIL` \| `deferred` \| `wont-fix` |
420
479
  | `reopened` | bool | `true` when this miss had already closed `Verified` and a later escape re-opened it. |
421
480
  | `cost_attribution` | string | **Derived by `tf-emit.sh`** from the fix run's `reqs_touched`: `sole` \| `shared:<n>` \| `none` — §5.5.3. |
422
- | `tokens_in`, `tokens_out`, `tokens_cache_read`, `tokens_cache_write`, `cost_usd`, `tokens_scope`, `model` | — | **Injected by `tf-emit.sh`** from the `fix_run_id` window, by exactly the §2.5 mechanism. Never written by an agent. `cost_usd` stays `null` on Claude Code and Codex, per §4. |
481
+ | `tokens_in`, `tokens_out`, `tokens_cache_read`, `tokens_cache_write`, `cost_usd`, `tokens_scope`, `model` | — | **Injected by `tf-emit.sh`** from the `fix_run_id` window, by exactly the §2.5 mechanism. Never written by an agent. `cost_usd` stays `null` on Claude Code, per §4. |
423
482
 
424
483
  ### 5.5.3 `cost_attribution` — the field the money number stands on
425
484
 
@@ -507,6 +566,7 @@ That is what keeps it inside the append-only rule rather than an edit wearing a
507
566
  | Field | Vocabulary |
508
567
  |---|---|
509
568
  | `why_missed` | §5.5.6's seven values |
569
+ | `sort` | `spec` · `unsaid` · `weak-check` · `ignored` (§5.5.1; added 2026-09-07) |
510
570
 
511
571
  - **A judgement may be completed; an observation may not.** `why_missed` is a classification an analyst can still make honestly next week. `found_gate` is a fact about a run that is over — §3.5's *"never backfill the old records with a verdict they never had"* is this same rule seen from the other side.
512
572
  - **Nothing the emitter derives is ever amendable** — `origin_model`, `origin_harness`, `origin_confidence`, `cost_attribution` and every token/cost field are excluded outright. An amend that could set them would be a hole straight through §5.5.1's central rule.
@@ -526,6 +586,8 @@ It **prints its refusal on stdout** rather than failing silently — an agent th
526
586
 
527
587
  ### 5.5.7b Closed vocabularies are enforced ON WRITE (added 2026-08-31)
528
588
 
589
+ **Since 2026-09-05 (reset Sitting 4a) the emitter also refuses:** a record with no `kind`; a record carrying a field this document does not define for its kind (the allowlist is `_ALLOWED` in `tf-emit.sh`, kept in step with these tables); and a `miss` with no `miss_id`. It also overwrites, never merely fills, `harness` when the harness is detected, every derived cost and model field on a `runs`/`gates` record, and the cost fields on a `miss-fix`. A caller cannot self-report any of them. The `_metrics-emit-gate.md` prose that used to say all this was deleted in the same sitting; the script is the rule.
590
+
529
591
  `tf-emit.sh` validates `miss_class`, `artifact`, `severity`, `found_by` and `why_missed` on a `miss`, and `verdict_after` and `fix_cmd` on a `miss-fix`, against the vocabularies above. **A record carrying a value outside one is refused and never appended**, with the reason and the allowed values printed on stdout. It still exits 0 — telemetry has no veto (§10), and neither does a refused record.
530
592
 
531
593
  This closes an asymmetry that was real and was hit: the emitter checked the `miss-amend` allowlist meticulously (§5.5.7) and checked a `miss` record's own enums **not at all**. So a single typo in `miss_class` landed permanently on an append-only stream, invented a category in every distribution built on it, and **could not be corrected** — `miss_class` is not amendable (§5.5.7 admits only closed-vocabulary judgements the emitter does not derive), and constraint 5 forbids editing the file. The only legal move left was to report it.
@@ -567,6 +629,36 @@ resolved in this direction, so the two implementations agree by construction rat
567
629
 
568
630
  ---
569
631
 
632
+ ### 5.5.9 `kind: "review"` — an owner review, and what it cost (FR-36; built 2026-09-06, Sitting 4b)
633
+
634
+ Decided in Session 2 of the reset (D-17): every phase that ends in an owner review records the review's outcome, so the cost of deviation is measurable in every phase, not only in build and verify. UAT issues stay `miss` records; a review record is the owner reading a phase's output and giving corrections.
635
+
636
+ Written by the run that applies the corrections (`day1-greenfield --stage2`, `amend-docs`; build, verify and handoff reviews are wired in Sitting 4c), after that run's own `runs.jsonl` record exists:
637
+
638
+ ```json
639
+ {"kind":"review","phase":"day1-review","reviewed_run_id":"2026-09-05T16:19:38Z",
640
+ "correction_run_id":"2026-09-06T05:02:11Z","corrections":2,
641
+ "what":"the lock screen had no way out and the acceptance lines were too dense to read"}
642
+ ```
643
+
644
+ | Field | Type | Meaning |
645
+ |---|---|---|
646
+ | `phase` | string | `day1-review` · `build-review` · `verify-review` · `handoff-review` (closed vocabulary, enforced on write) |
647
+ | `reviewed_run_id` | string | the `started` of the run that produced the reviewed output |
648
+ | `correction_run_id` | string \| null | the `started` of the run that applied the corrections; null when the owner applied them by hand |
649
+ | `corrections` | integer | how many corrections the owner gave (required) |
650
+ | `what` | string | one sentence in the owner's words; the only free text, like `miss.what` |
651
+ | `tokens_produce`, `cost_produce_usd`, `model_produce` | derived | copied from the run `reviewed_run_id` names (`tokens_out`, `cost_usd`, `model`); null when no run record matches |
652
+ | `tokens_correct`, `cost_correct_usd`, `model_correct` | derived | the same from the run `correction_run_id` names |
653
+
654
+ The two costs are the review's two numbers: what it cost to produce something the owner had to correct, and what the corrections cost. A caller's cost figure is discarded, as on `miss-fix`. Readers that count misses ignore this kind; `tf-metrics.sh` reports reviews since Session 5 (2026-09-07): the count, the corrections given, by phase, and the two token means over the records that carry them (§5.5.8).
655
+
656
+ ### 5.5.10 The readable file — `docs/<App>-Misses.md` (FR-31; built 2026-09-07, Session 5)
657
+
658
+ The stream is for scripts. The owner reads `docs/<App>-Misses.md`: one row per `miss` record, newest first, in three tables (open, fixed, will not fix), each row holding the miss id and its row, when and by whom it was found, whose gap it was (`sort`, in words) and the `what` sentence. A record without a sentence shows its class and artifact instead; a record from before ids were required still gets a row.
659
+
660
+ `tf-emit.sh` rebuilds the file and its HTML through `tf-misses-md.py` after **every** write to the misses stream, whichever door wrote it (log-miss, triage, fix-close, a hand-written record, an amend), so the file is never older than the record. It is **derived**: nothing reads it, nobody edits it, and a wrong row is corrected by a new record. A project whose stream predates the script gets the file by running `bash .tfcore/utils/tf-misses-md.sh` once. The check for FR-31 is in `tests/bugs/run.sh`: the row count equals the record count.
661
+
570
662
  ## 6. Provenance — three separations, one rule applied three times
571
663
 
572
664
  **Data from different provenances never merges.**