@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
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ """tf-log-miss.py — one miss record from one sentence (see tf-log-miss.sh).
3
+
4
+ bash .tfcore/utils/tf-log-miss.sh <App> --what "<the owner's sentence>" --sort spec|unsaid|weak-check|ignored
5
+ [--req REQ-UI-014 | --new "<title>" --acceptance "<When … on <screen>, then …>" [--prefix FN] [--section "<screen>"]]
6
+ [--class <miss_class>] [--why <why_missed>] [--artifact <artifact>] [--severity blocker|major|minor]
7
+ [--found-by owner|production] [--evidence <path>] [--fixed [--fix-run <ISO>]] [--started <ISO>]
8
+
9
+ First the sort (FR-32): --sort answers the four questions, in order, and is required.
10
+ spec the app's spec did not say it -> fix the checklist line; the framework is untouched
11
+ unsaid the framework never said it -> one requirement line plus a check; no prose in a task
12
+ weak-check a check existed and did not catch it -> fix the check (a review becomes a script)
13
+ ignored it was written and not followed -> a hook or script, or delete the rule
14
+ Then, in order: the duplicate check (an open miss of the same class on the row is reported, not
15
+ re-logged; a null why_missed or sort on it is completed with --amend); the origin lookup
16
+ (tf-emit.sh --origin-of); the miss record with the sentence as `what` and the sort; the checklist line
17
+ (the row demoted to Needs re-verify with "⚠ miss <date>: <sentence>", or a new Not Started row with
18
+ BRD-pending when nothing owns it; nothing when the repo has no checklist); with --fixed, a miss-fix
19
+ record instead of a demotion; then the run record (cmd log-miss). The emitter rewrites the readable
20
+ docs/<App>-Misses.md. Prints the report block. Exit 0 · 2 could not run.
21
+ """
22
+ import datetime
23
+ import importlib.util
24
+ import json
25
+ import os
26
+ import re
27
+ import subprocess
28
+ import sys
29
+
30
+ HERE = os.path.dirname(os.path.abspath(__file__))
31
+ EMIT = os.path.join(HERE, "tf-emit.sh")
32
+ CLASSES = {"missed-requirement", "partial-implementation", "unspecified-gap", "regression", "wrong-behaviour", "standards-violation", "spec-contradiction", "other"}
33
+ DEFAULT_ARTIFACT = {"unspecified-gap": "brd", "spec-contradiction": "brd", "standards-violation": "src"}
34
+ SORTS = ("spec", "unsaid", "weak-check", "ignored")
35
+ SORT_QUESTIONS = """tf-log-miss: --sort is missing. Answer the four questions in order and pass the first that fits:
36
+ 1. Did the app's spec say it clearly? no -> --sort spec (fix the checklist line)
37
+ 2. Did the framework say it anywhere? no -> --sort unsaid (one requirement line plus a check)
38
+ 3. Was there a check, and did it fail to catch it? yes -> --sort weak-check (fix the check)
39
+ 4. Was it written and ignored anyway? yes -> --sort ignored (a hook, or delete the rule)"""
40
+
41
+
42
+ def die(m):
43
+ print(f"tf-log-miss: {m}", file=sys.stderr)
44
+ sys.exit(2)
45
+
46
+
47
+ def opt(argv, name, default=None):
48
+ return argv[argv.index(name) + 1] if name in argv and argv.index(name) + 1 < len(argv) else default
49
+
50
+
51
+ def q(*args):
52
+ return subprocess.run(["bash", EMIT, *args], capture_output=True, text=True).stdout.strip()
53
+
54
+
55
+ def emit(stream, rec):
56
+ """Append one record. Returns (ok, reason) — the reason is what the emitter refused it for,
57
+ so the caller can say the miss was NOT recorded instead of claiming it was (miss 06 of
58
+ 2026-09-07: a refused artifact value printed 'Miss logged' and an id that existed nowhere)."""
59
+ p = subprocess.run(["bash", EMIT, stream], input=json.dumps(rec), capture_output=True, text=True)
60
+ msg = (p.stdout + p.stderr).strip()
61
+ ok = p.returncode == 0 and "refus" not in msg.lower() and "error" not in msg.lower()
62
+ reason = ""
63
+ if not ok:
64
+ reason = msg.splitlines()[0][:200] if msg else "no reason printed"
65
+ print(f" {stream}: not written — {reason}")
66
+ return ok, reason
67
+
68
+
69
+ def main(argv):
70
+ if len(argv) < 2 or argv[1] in ("-h", "--help") or "--what" not in argv:
71
+ print(__doc__)
72
+ return 0 if "-h" in argv or "--help" in argv else 2
73
+ app = argv[1]
74
+ what = opt(argv, "--what", "").strip()
75
+ if not what:
76
+ die("--what needs the sentence")
77
+ sort = opt(argv, "--sort")
78
+ if not sort:
79
+ print(SORT_QUESTIONS, file=sys.stderr)
80
+ return 2
81
+ if sort not in SORTS:
82
+ die(f"--sort must be one of {', '.join(SORTS)}")
83
+ req = (opt(argv, "--req") or "").upper() or None
84
+ new_title = opt(argv, "--new")
85
+ cls = opt(argv, "--class") or ("unspecified-gap" if new_title else "wrong-behaviour")
86
+ if cls not in CLASSES:
87
+ die(f"--class must be one of {', '.join(sorted(CLASSES))}")
88
+ why = opt(argv, "--why")
89
+ artifact = opt(argv, "--artifact") or DEFAULT_ARTIFACT.get(cls, "src")
90
+ severity = opt(argv, "--severity", "major")
91
+ found_by = opt(argv, "--found-by", "owner")
92
+ fixed = "--fixed" in argv
93
+ started = opt(argv, "--started") or ""
94
+ if not started:
95
+ mk = subprocess.run(["bash", os.path.join(HERE, "tf-phase.sh"), "show"], capture_output=True, text=True).stdout
96
+ m = re.search(r'"started":"([^"]*)"', mk)
97
+ started = m.group(1) if m else datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
98
+ now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
99
+
100
+ spec = importlib.util.spec_from_file_location("tf_checklist_edit", os.path.join(HERE, "tf-checklist-edit.py"))
101
+ ce = importlib.util.module_from_spec(spec)
102
+ spec.loader.exec_module(ce)
103
+ phase = 1
104
+ cfg = os.path.join(".tfcore", "core-config.yaml")
105
+ if os.path.isfile(cfg):
106
+ m = re.search(r"(?m)^appPhase:\s*(\d+)", open(cfg, encoding="utf-8").read())
107
+ phase = int(m.group(1)) if m else 1
108
+ cl = os.path.join("docs", f"{app}-Checklist.md" if phase <= 1 else f"{app}-P{phase}-Checklist.md")
109
+ has_cl = os.path.isfile(cl)
110
+
111
+ # 1. duplicate check
112
+ if req:
113
+ opened = q("--open-miss", req)
114
+ if opened and opened.split()[-1] == cls:
115
+ mid = opened.split()[0]
116
+ line = f"already logged as {mid}, still open."
117
+ for fld, val in (("why_missed", why), ("sort", sort)):
118
+ if val:
119
+ r = q("--amend", mid, fld, val)
120
+ line += f" {fld} completed with {val}." if "refus" not in r.lower() else f" ({fld} not amended: {r.split('—')[-1].strip()[:90]})"
121
+ print(f"# Miss not re-logged — {app}\n{line}")
122
+ return 0
123
+
124
+ # 2. the checklist line
125
+ doc_line = "no checklist in this repo — record only"
126
+ if has_cl:
127
+ if req:
128
+ if fixed:
129
+ doc_line = f"{req} left as it is (--fixed)"
130
+ else:
131
+ prior = ce.demote(cl, req, what, evidence=opt(argv, "--evidence"), source=found_by,
132
+ prefix="⚠ prod bug" if found_by == "production" else "⚠ miss")
133
+ doc_line = f"{req} demoted to Needs re-verify (was {prior['status']})" if prior else f"{req} is not a row of {cl}; record only"
134
+ elif new_title:
135
+ acc = opt(argv, "--acceptance") or die("--new needs --acceptance")
136
+ req = ce.add_row(cl, opt(argv, "--prefix", "FN").upper(), new_title, acc, section=opt(argv, "--section"),
137
+ mockup=opt(argv, "--mockup"), remark=f"logged via *log-miss {ce.TODAY}")
138
+ doc_line = f"new Not Started row {req} (BRD-pending; *amend-docs gives it its BRD item)"
139
+ else:
140
+ doc_line = "no owning row and no --new: record only"
141
+
142
+ # 3. the miss
143
+ mid = q("--next-miss-id")
144
+ rec = {"kind": "miss", "miss_id": mid, "req_id": req, "req_class": req.split("-")[1] if req else None, "miss_class": cls,
145
+ "artifact": artifact, "severity": severity, "why_missed": why, "origin_phase": "day1-greenfield" if cls in ("unspecified-gap", "spec-contradiction") else "build-phase",
146
+ "origin_agent": "analyst" if cls in ("unspecified-gap", "spec-contradiction") else "flow-master",
147
+ "found_by": found_by, "found_phase": "log-miss", "found_gate": None, "found_run_id": started, "failure_class": "other", "what": what[:300],
148
+ "sort": sort}
149
+ attributed = "inferred, no run record found"
150
+ if req and cls not in ("unspecified-gap", "spec-contradiction"):
151
+ o = q("--origin-of", req).split()
152
+ if len(o) >= 3:
153
+ rec["origin_run_id"], rec["origin_phase"], rec["origin_agent"] = o[0], o[1], o[2]
154
+ attributed = f"linked to run {o[0]}"
155
+ ok, refusal = emit("misses", rec)
156
+
157
+ # 4. --fixed
158
+ fix_line = ""
159
+ if fixed and ok:
160
+ fa = q("--next-fix-attempt", mid) or "1"
161
+ fr = {"kind": "miss-fix", "miss_id": mid, "req_id": req, "fix_cmd": "log-miss", "fix_attempt": int(fa) if fa.isdigit() else 1,
162
+ "verdict_after": "Verified", "reopened": False}
163
+ if opt(argv, "--fix-run"):
164
+ fr["fix_run_id"] = opt(argv, "--fix-run")
165
+ fix_ok, _ = emit("misses", fr)
166
+ fix_line = ("closed in the same run" + (f" against run {fr['fix_run_id']}" if "fix_run_id" in fr else " (fix run unknown: costed none)")) if fix_ok else "miss-fix not written"
167
+
168
+ # 5. the run record
169
+ run = {"kind": "run", "app": app, "cmd": "log-miss", "mode": None, "started": started, "ended": now,
170
+ "reqs_touched": [req] if req else [], "reqs_count": 1 if req else 0, "subagents": [], "files_written": 1 if has_cl else 0, "build_result": "not-run"}
171
+ emit("runs", run)
172
+
173
+ if not ok:
174
+ print(f"# Miss NOT recorded — {app}")
175
+ print(f"Refused : {refusal}")
176
+ print(f"Nothing carries the id {mid}: the stream has no record and docs/{app}-Misses.md is unchanged.")
177
+ print(f"Docs : {doc_line}")
178
+ print("Next : fix the value the emitter named and run the same command again.")
179
+ return 2
180
+
181
+ print(f"# Miss logged — {app}")
182
+ print(f"{mid} {cls} / {artifact} / {severity}")
183
+ print(f"Why missed : {why or 'not assessed'}")
184
+ print(f"Whose gap : {sort} — " + {"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",
185
+ "weak-check": "the check was too weak; fix the check", "ignored": "said and ignored; a hook, or delete the rule"}[sort])
186
+ print(f"Owning REQ : {req or 'none'}")
187
+ print(f"Attributed : {rec['origin_phase']} ({rec['origin_agent']}) — {attributed}")
188
+ print(f"Found by : {found_by}")
189
+ print(f"Docs : {doc_line}; docs/{app}-Misses.md rewritten")
190
+ if fix_line:
191
+ print(f"Fix : {fix_line}")
192
+ print(f"Next : *fix-issues {app}" + (f" {req}" if req and not fixed else ""))
193
+ return 0
194
+
195
+
196
+ if __name__ == "__main__":
197
+ sys.exit(main(sys.argv))
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # tf-log-miss.sh — one miss record from one sentence (Sitting 4c, 2026-09-06).
3
+ #
4
+ # bash .tfcore/utils/tf-log-miss.sh <App> --what "<the owner's sentence>" [--req REQ-UI-014 | --new "<title>" --acceptance "<When … on <screen>, then …>"]
5
+ # [--class <miss_class>] [--why <why_missed>] [--artifact <artifact>] [--severity blocker|major|minor]
6
+ # [--found-by owner|production] [--evidence <path>] [--fixed [--fix-run <ISO>]] [--started <ISO>]
7
+ #
8
+ # Duplicate check, origin lookup, the miss record carrying the sentence, the checklist line (a demotion,
9
+ # or a new Not Started row with BRD-pending), a miss-fix instead of a demotion with --fixed, then the run
10
+ # record. Never boots, never fixes, never argues. Details: tf-log-miss.py.
11
+ set -u
12
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ command -v python3 >/dev/null 2>&1 || { echo "tf-log-miss: python3 is required" >&2; exit 2; }
14
+ exec python3 "$HERE/tf-log-miss.py" "$@"
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env python3
2
+ """tf-misses-md.py — the readable miss list, docs/<App>-Misses.md (Session 5, 2026-09-07; FR-31).
3
+
4
+ python3 .tfcore/utils/tf-misses-md.py [--root <repo>] [--app <App>] [--quiet]
5
+
6
+ Rebuilds docs/<App>-Misses.md from docs/metrics/misses.jsonl: one row per `miss` record, newest first,
7
+ in three tables (open, fixed, will not fix), each row holding the miss id, the owning row, when and
8
+ by whom it was found, whose gap it was (the `sort` field, folded from any miss-amend) and the `what`
9
+ sentence. Markdown only — there is no HTML sibling: the miss log is read by agents and by the owner
10
+ in markdown, and tf-render-html.py refuses it (owner, 2026-09-08; an HTML copy nobody opened was
11
+ being re-rendered on every miss in every project). tf-emit.sh calls this after every write to the
12
+ misses stream, so the file is never older than the record; run it by hand once on a project whose
13
+ stream predates it. The file is derived: never edit it, edit nothing, log a miss.
14
+ Exit 0 always (telemetry has no veto); the reason for a skipped write is printed unless --quiet.
15
+ """
16
+ import datetime
17
+ import glob
18
+ import json
19
+ import os
20
+ import re
21
+ import sys
22
+
23
+ HERE = os.path.dirname(os.path.abspath(__file__))
24
+
25
+ SORT_WORDS = {
26
+ "spec": "the app's spec",
27
+ "unsaid": "the framework never said it",
28
+ "weak-check": "the check was too weak",
29
+ "ignored": "said and ignored",
30
+ }
31
+ SORT_LEGEND = ("**Whose gap** answers the four questions of the miss protocol: "
32
+ "**the app's spec** did not say it, so the checklist line is fixed; "
33
+ "**the framework never said it**, so one requirement line and a check are added; "
34
+ "**the check was too weak** (a review, or a script that did not fire), so the check is fixed; "
35
+ "**said and ignored**, so the rule becomes a hook or is deleted. "
36
+ "**not sorted** means the record predates the sort or nobody has answered yet; "
37
+ "`bash .tfcore/utils/tf-emit.sh --amend <miss> sort <spec|unsaid|weak-check|ignored>` completes it.")
38
+
39
+
40
+ def find_root(explicit):
41
+ if explicit and os.path.isdir(explicit):
42
+ return os.path.abspath(explicit)
43
+ for k in ("TF_METRICS_ROOT", "CLAUDE_PROJECT_DIR"):
44
+ v = os.environ.get(k)
45
+ if v and os.path.isdir(v):
46
+ return os.path.abspath(v)
47
+ d = os.getcwd()
48
+ while d and d != os.path.dirname(d):
49
+ if os.path.isdir(os.path.join(d, ".tfcore")) or os.path.isdir(os.path.join(d, "docs")):
50
+ return d
51
+ d = os.path.dirname(d)
52
+ return os.getcwd()
53
+
54
+
55
+ def app_name(root, explicit):
56
+ if explicit:
57
+ return explicit
58
+ hits = [h for h in glob.glob(os.path.join(root, "docs", "*-Checklist.md"))
59
+ if not re.search(r"-(Deployment|P\d+)-Checklist\.md$", h)]
60
+ if len(hits) == 1:
61
+ return os.path.basename(hits[0])[: -len("-Checklist.md")]
62
+ return os.path.basename(os.path.abspath(root))
63
+
64
+
65
+ def load(path):
66
+ out = []
67
+ try:
68
+ with open(path, encoding="utf-8") as fh:
69
+ for line in fh:
70
+ line = line.strip()
71
+ if not line:
72
+ continue
73
+ try:
74
+ out.append(json.loads(line))
75
+ except Exception:
76
+ pass
77
+ except FileNotFoundError:
78
+ pass
79
+ return out
80
+
81
+
82
+ def cell(text):
83
+ text = (text or "").replace("\r", " ").replace("\n", " ").replace("|", "\\|").strip()
84
+ return text or "—"
85
+
86
+
87
+ def day(ts):
88
+ return (ts or "")[:10] or "—"
89
+
90
+
91
+ def build(root, app):
92
+ recs = load(os.path.join(root, "docs", "metrics", "misses.jsonl"))
93
+ misses = [r for r in recs if r.get("kind") == "miss"]
94
+ by_id = {}
95
+ for i, m in enumerate(misses):
96
+ if not m.get("miss_id"): # a record from before the emitter required an id: still a miss, still a row
97
+ m["miss_id"] = f"(no id, record {i + 1})"
98
+ by_id.setdefault(m["miss_id"], m)
99
+ for a in sorted((r for r in recs if r.get("kind") == "miss-amend"), key=lambda r: r.get("ts") or ""):
100
+ p = by_id.get(a.get("miss_id"))
101
+ if p is not None and a.get("field") and p.get(a["field"]) is None:
102
+ p[a["field"]] = a.get("value")
103
+ latest = {}
104
+ for f in recs:
105
+ if f.get("kind") != "miss-fix" or not f.get("miss_id"):
106
+ continue
107
+ prev = latest.get(f["miss_id"])
108
+ if prev is None or (f.get("ts") or "") >= (prev.get("ts") or ""):
109
+ latest[f["miss_id"]] = f
110
+
111
+ def state(m):
112
+ v = (latest.get(m["miss_id"]) or {}).get("verdict_after")
113
+ return "fixed" if v == "Verified" else "wont-fix" if v == "wont-fix" else "open"
114
+
115
+ groups = {"open": [], "fixed": [], "wont-fix": []}
116
+ for m in sorted(by_id.values(), key=lambda r: (r.get("miss_id") or "", r.get("ts") or ""), reverse=True):
117
+ groups[state(m)].append(m)
118
+
119
+ def what(m):
120
+ w = (m.get("what") or "").strip()
121
+ if w:
122
+ return cell(w)
123
+ bits = [m.get("miss_class") or "?", m.get("artifact") or "?"]
124
+ if m.get("why_missed"):
125
+ bits.append("why: " + m["why_missed"])
126
+ return "no sentence recorded (" + ", ".join(bits) + ")"
127
+
128
+ def found(m):
129
+ who = m.get("found_by") or "?"
130
+ return f"{day(m.get('ts'))} by {who}"
131
+
132
+ def gap(m):
133
+ return SORT_WORDS.get(m.get("sort") or "", "not sorted")
134
+
135
+ def ident(m):
136
+ return m["miss_id"] + (f" ({m['req_id']})" if m.get("req_id") else "")
137
+
138
+ today = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")
139
+ n = len(by_id)
140
+ lines = [f"# {app} — Misses", "", "| | |", "|---|---|", f"| App | {app} |",
141
+ f"| Count | {n} logged: {len(groups['open'])} open, {len(groups['fixed'])} fixed, {len(groups['wont-fix'])} will not fix |",
142
+ "| Source | `docs/metrics/misses.jsonl`, one row per miss record. Rewritten by `tf-misses-md.sh` on every new record. Never edit it: a wrong row is corrected by a new record. |",
143
+ f"| Updated | {today} |", "", SORT_LEGEND, ""]
144
+ if not n:
145
+ lines += ["No miss has been logged yet.", ""]
146
+ for key, title in (("open", "Open"), ("fixed", "Fixed"), ("wont-fix", "Will not fix")):
147
+ rows = groups[key]
148
+ if not rows:
149
+ continue
150
+ lines += [f"## {title} ({len(rows)})", ""]
151
+ if key == "open":
152
+ lines += ["| Miss | Found | Whose gap | What went wrong |", "|---|---|---|---|"]
153
+ for m in rows:
154
+ lines.append(f"| {ident(m)} | {found(m)} | {gap(m)} | {what(m)} |")
155
+ else:
156
+ lines += ["| Miss | Found | Closed | Whose gap | What went wrong |", "|---|---|---|---|---|"]
157
+ for m in rows:
158
+ f = latest.get(m["miss_id"]) or {}
159
+ closed = day(f.get("ts")) + (f" by {f['fix_cmd']}" if f.get("fix_cmd") else "")
160
+ lines.append(f"| {ident(m)} | {found(m)} | {closed} | {gap(m)} | {what(m)} |")
161
+ lines.append("")
162
+ return "\n".join(lines), n
163
+
164
+
165
+ def main(argv):
166
+ if "-h" in argv or "--help" in argv:
167
+ print(__doc__)
168
+ return 0
169
+ quiet = "--quiet" in argv
170
+
171
+ def opt(name):
172
+ return argv[argv.index(name) + 1] if name in argv and argv.index(name) + 1 < len(argv) else None
173
+
174
+ try:
175
+ root = find_root(opt("--root"))
176
+ app = app_name(root, opt("--app"))
177
+ text, n = build(root, app)
178
+ rel = os.path.join("docs", f"{app}-Misses.md")
179
+ out = os.path.join(root, rel)
180
+ os.makedirs(os.path.dirname(out), exist_ok=True)
181
+ old = None
182
+ try:
183
+ with open(out, encoding="utf-8") as fh:
184
+ old = fh.read()
185
+ except FileNotFoundError:
186
+ pass
187
+ stale = out[:-3] + ".html" # left by a pre-2026-09-08 run, before the miss
188
+ gone = "" # log was ruled an agent document (owner)
189
+ if os.path.isfile(stale):
190
+ os.remove(stale)
191
+ gone = "; removed the stale HTML copy"
192
+ same = lambda s: re.sub(r"\| Updated \| [0-9-]+ \|", "", s) # everything but the date must match
193
+ if old is not None and same(old) == same(text):
194
+ if not quiet:
195
+ print(f"tf-misses-md: {rel} unchanged ({n} misses){gone}")
196
+ return 0
197
+ with open(out, "w", encoding="utf-8", newline="\n") as fh:
198
+ fh.write(text)
199
+ if not quiet:
200
+ print(f"tf-misses-md: wrote {rel} ({n} misses){gone}")
201
+ except Exception as e:
202
+ if not quiet:
203
+ print(f"tf-misses-md: nothing written ({e})")
204
+ return 0
205
+
206
+
207
+ if __name__ == "__main__":
208
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # tf-misses-md.sh — rebuild the readable miss list docs/<App>-Misses.md from the misses
3
+ # stream (Session 5, 2026-09-07; FR-31). Markdown only: the miss log is never rendered to
4
+ # HTML. tf-emit.sh runs it after every write to that stream; run it by hand once on a
5
+ # project whose stream is older than this script.
6
+ #
7
+ # bash .tfcore/utils/tf-misses-md.sh [--root <repo>] [--app <App>] [--quiet]
8
+ #
9
+ # Exit 0 always. Details: tf-misses-md.py.
10
+ set -u
11
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ command -v python3 >/dev/null 2>&1 || { echo "tf-misses-md: python3 is required" >&2; exit 0; }
13
+ python3 "$HERE/tf-misses-md.py" "$@"
14
+ exit 0
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ """tf-mockups-locate.py — bring every mockup into docs/mockups/ (see tf-mockups-locate.sh).
3
+
4
+ bash .tfcore/utils/tf-mockups-locate.sh [--dry-run]
5
+
6
+ The framework keeps one mockup folder, docs/mockups/. A brownfield repository may hold
7
+ mockups elsewhere (a root mockups/ folder, a designs/ folder, an html file beside the
8
+ docs). Two folders confused a real project, so this script finds every folder whose
9
+ name says mockup (mockup, mockups, wireframes, designs, screens) outside docs/mockups/,
10
+ docs/OldDocs/, the framework folders, node_modules, bin and obj, and moves each .html,
11
+ .png, .jpg or .svg it holds into docs/mockups/. A name already taken there gets the
12
+ source folder name as a suffix. Prints one line per file moved, then the final list of
13
+ docs/mockups/ files. Exit 0 always; 2 if docs/ cannot be created.
14
+ """
15
+ import os
16
+ import shutil
17
+ import sys
18
+
19
+ NAMES = {"mockup", "mockups", "wireframe", "wireframes", "designs", "screens", "ui-mockups", "mock-ups"}
20
+ SKIP_DIRS = {".tfcore", ".claude", ".opencode", ".git", "node_modules", "bin", "obj",
21
+ "OldDocs", "tests", "wwwroot"}
22
+ EXT = (".html", ".htm", ".png", ".jpg", ".jpeg", ".svg")
23
+
24
+
25
+ def main(argv):
26
+ dry = "--dry-run" in argv
27
+ if "-h" in argv or "--help" in argv:
28
+ print(__doc__)
29
+ return 0
30
+ root = os.getcwd()
31
+ target = os.path.join(root, "docs", "mockups")
32
+ try:
33
+ os.makedirs(target, exist_ok=True)
34
+ except Exception as e:
35
+ print(f"tf-mockups-locate: cannot create docs/mockups ({e})", file=sys.stderr)
36
+ return 2
37
+ moved = 0
38
+ for dirpath, dirnames, filenames in os.walk(root):
39
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
40
+ rel = os.path.relpath(dirpath, root).replace(os.sep, "/")
41
+ if rel in (".", "docs/mockups") or rel.startswith("docs/mockups/"):
42
+ continue
43
+ if os.path.basename(dirpath).lower() not in NAMES:
44
+ continue
45
+ for fn in sorted(filenames):
46
+ if not fn.lower().endswith(EXT):
47
+ continue
48
+ src = os.path.join(dirpath, fn)
49
+ dest = os.path.join(target, fn)
50
+ if os.path.exists(dest):
51
+ stem, ext = os.path.splitext(fn)
52
+ dest = os.path.join(target, f"{stem}-{os.path.basename(dirpath)}{ext}")
53
+ print(f"tf-mockups-locate: {'would move' if dry else 'moved'} {rel}/{fn} -> docs/mockups/{os.path.basename(dest)}")
54
+ if not dry:
55
+ shutil.move(src, dest)
56
+ moved += 1
57
+ if not dry and not os.listdir(dirpath):
58
+ os.rmdir(dirpath)
59
+ print(f"tf-mockups-locate: removed the empty folder {rel}/")
60
+ files = sorted(f for f in os.listdir(target) if f.lower().endswith(EXT))
61
+ print(f"tf-mockups-locate: {moved} file(s) {'to move' if dry else 'moved'}; docs/mockups/ now holds {len(files)}: " + (", ".join(files) if files else "(none)"))
62
+ return 0
63
+
64
+
65
+ if __name__ == "__main__":
66
+ sys.exit(main(sys.argv))
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # tf-mockups-locate.sh — bring every mockup in the repository into docs/mockups/.
3
+ #
4
+ # bash .tfcore/utils/tf-mockups-locate.sh # move, then list docs/mockups/
5
+ # bash .tfcore/utils/tf-mockups-locate.sh --dry-run # only report
6
+ #
7
+ # Finds folders named mockup(s), wireframe(s), designs or screens outside
8
+ # docs/mockups/ and moves their .html/.png/.jpg/.svg files there, so the
9
+ # framework's one mockup folder is the only one. Run at brownfield day-1 and by
10
+ # the mockups task before it links or draws anything.
11
+ # Thin wrapper over tf-mockups-locate.py; Python 3 standard library only.
12
+ set -uo pipefail
13
+ SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ if ! command -v python3 >/dev/null 2>&1; then
15
+ echo "tf-mockups-locate: python3 is required (standard library only)." >&2
16
+ exit 2
17
+ fi
18
+ exec python3 "$SELF_DIR/tf-mockups-locate.py" "$@"