@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,309 @@
1
+ #!/usr/bin/env python3
2
+ """tf-split-brd.py — draft docs/<App>-Checklist.md from the BRD ledger (see tf-split-brd.sh).
3
+
4
+ bash .tfcore/utils/tf-split-brd.sh <App> # write the checklist (refuses if one exists)
5
+ bash .tfcore/utils/tf-split-brd.sh <App> --force # overwrite an existing checklist
6
+ bash .tfcore/utils/tf-split-brd.sh <App> --add-missing # append rows only for BRD items with no row yet
7
+ bash .tfcore/utils/tf-split-brd.sh <App> --phase 2 # a later phase of a Large project
8
+ bash .tfcore/utils/tf-split-brd.sh <App> --all-phases # every phase in docs/<App>-Phases.md
9
+
10
+ Large projects (docs/TechieFlow-Document-Schemas.md §2): phase 1 is docs/<App>-BRD.md and
11
+ docs/<App>-Checklist.md; phase n is docs/<App>-Pn-BRD.md and docs/<App>-Pn-Checklist.md. Without
12
+ --phase the phase comes from appPhase in .tfcore/core-config.yaml (1 when absent). REQ numbers run
13
+ on across phases: a new checklist starts each prefix after the highest number in every other
14
+ phase's checklist, so an id is never reused.
15
+
16
+ Reads docs/<App>-BRD.md: the header (App, Size), the Screens and flow table (screen, route),
17
+ every `**BRD-N**` ledger item with its title, screen, mockup link and acceptance line, and
18
+ the Non-functional table. Writes the checklist in the template shape: header, Goal, the one
19
+ Requirements Status table, then one section per screen holding the detail entries, plus
20
+ "RAG / AI requirements" and "Non-functional". Acceptance and perf-budget lines are copied
21
+ verbatim; an item without one gets a TODO line the checker will refuse, never an invented one.
22
+
23
+ Class is guessed, and the agent corrects it: Non-functional table -> REQ-NFR; text about
24
+ embeddings, vectors, prompts, chat or a model -> REQ-RAG; text about what a screen shows
25
+ (page, screen, list, form, dialog, button, menu, layout, theme, displays, shows) -> REQ-UI;
26
+ everything else -> REQ-FN. Exit 0 written, 1 refused, 2 could not run.
27
+ """
28
+ import os
29
+ import re
30
+ import sys
31
+
32
+ RAG_WORDS = re.compile(r"\b(embedding|embeddings|vector|rag\b|prompt template|llm|language model|retriev|semantic search|chatbot|chat completion)", re.I)
33
+ UI_WORDS = re.compile(r"\b(page|screen|list|lists|form|dialog|button|menu|layout|theme|display|displays|shows|shown|grid|table|card|panel|tab|tabs|badge|icon|responsive|mobile)\b", re.I)
34
+ STATUS_LINE = ("**Status values:** `Not Started` · `In Progress` · `Implemented` · `Verified` · `Done (pre-existing)` · "
35
+ "`Needs re-verify` · `PARTIAL` · `FAIL` · `Blocked` · `Owner-UAT` · `N/A`.")
36
+
37
+
38
+ def die(msg, code=2):
39
+ print(f"tf-split-brd: {msg}", file=sys.stderr)
40
+ sys.exit(code)
41
+
42
+
43
+ def read(p):
44
+ with open(p, encoding="utf-8", errors="replace") as f:
45
+ return f.read()
46
+
47
+
48
+ def header_field(text, name):
49
+ m = re.search(rf"(?im)^\|\s*{re.escape(name)}\s*\|\s*([^|]+?)\s*\|", text)
50
+ return m.group(1).strip() if m else ""
51
+
52
+
53
+ def section(text, name):
54
+ m = re.search(rf"(?ms)^##\s+(?:\d+[.)]\s*)?{name}[^\n]*\n(.*?)(?=^## |\Z)", text)
55
+ return m.group(1) if m else ""
56
+
57
+
58
+ def screens(text):
59
+ """[(name, route)] from the Screens and flow table; dialog rows are skipped."""
60
+ out = []
61
+ for line in section(text, "Screens and flow").splitlines():
62
+ cells = [c.strip() for c in line.strip().strip("|").split("|")]
63
+ if len(cells) < 2 or cells[0].lower() in ("screen", "") or set(cells[0]) <= set("-: "):
64
+ continue
65
+ route = cells[1].strip("` ")
66
+ if route.lower().startswith("on "):
67
+ continue
68
+ out.append((cells[0].strip("`* "), route))
69
+ return out
70
+
71
+
72
+ def ledger(text):
73
+ """Every **BRD-N** item: id, title, screen, mockup, acceptance, perf, section name."""
74
+ items = []
75
+ nfr_ids = set()
76
+ for line in section(text, "Non-functional requirements").splitlines():
77
+ m = re.match(r"^\s*\|\s*`?\**(BRD-\d+)", line)
78
+ if m:
79
+ nfr_ids.add(m.group(1))
80
+ cells = [c.strip() for c in line.strip().strip("|").split("|")]
81
+ title = cells[2] if len(cells) > 2 else cells[-1]
82
+ measure = cells[3] if len(cells) > 3 else ""
83
+ items.append({"id": m.group(1), "title": title.strip("`* "), "screen": "", "mockup": "",
84
+ "acceptance": "", "perf": measure if "perf-budget" in measure.lower() else "", "nfr": True})
85
+ lines = text.splitlines()
86
+ i = 0
87
+ while i < len(lines):
88
+ # The anchor is not decoration: a BRD's §9 screen inventory links to each item as
89
+ # [BRD-21](#brd-21), markdown generates no id for bold text inside a list item, and
90
+ # tf-doc-check refuses a broken link -- so a real BRD carries `<a id="brd-N"></a>`
91
+ # here and this regex matched NONE of TfLens's 179 items. tests/regression tf_017.
92
+ m = re.match(r"^\s*[-*]\s*(?:<a id=[\"\']?[^\"\'>]*[\"\']?\s*>\s*</a>\s*)?"
93
+ r"\*\*(BRD-\d+)\*\*\s*[—:-]+\s*(.*)$", lines[i])
94
+ if m and m.group(1) not in nfr_ids:
95
+ rid, rest = m.group(1), m.group(2)
96
+ title = re.split(r"\s*(?:\.\s+\*|\s\*Screen|\s·\s\*|\s\*Mockup)", rest, 1)[0].strip().rstrip(".")
97
+ sm = re.search(r"\*Screen:\*\s*([^·*\n]+)", rest)
98
+ mm = re.search(r"\(((?:\./|docs/)?mockups/[^)\s]+)\)", rest) or re.search(r"((?:docs/)?mockups/[\w./-]+\.html)", rest)
99
+ acc = perf = ""
100
+ j = i + 1
101
+ while j < len(lines) and (lines[j].startswith(" ") or lines[j].strip() == ""):
102
+ if re.search(r"\*Acceptance:?\*|^\s*[-*]\s*Acceptance:", lines[j], re.I):
103
+ acc = re.sub(r"^\s*[-*]\s*\*?Acceptance:?\*?:?\s*", "", lines[j]).strip()
104
+ pm = re.search(r"perf-budget:[^\n|]+", lines[j], re.I)
105
+ if pm:
106
+ perf = pm.group(0).strip()
107
+ if lines[j].strip() == "" and j + 1 < len(lines) and not lines[j + 1].startswith(" "):
108
+ break
109
+ j += 1
110
+ items.append({"id": rid, "title": title, "screen": sm.group(1).strip() if sm else "",
111
+ "mockup": mm.group(1) if mm else "", "acceptance": acc, "perf": perf, "nfr": False})
112
+ i += 1
113
+ return [it for it in items if not it["nfr"]] + [it for it in items if it["nfr"]]
114
+
115
+
116
+ def classify(it):
117
+ if it["nfr"]:
118
+ return "NFR"
119
+ blob = it["title"] + " " + it["acceptance"]
120
+ if RAG_WORDS.search(blob):
121
+ return "RAG"
122
+ if UI_WORDS.search(blob):
123
+ return "UI"
124
+ return "FN"
125
+
126
+
127
+ def slug(s):
128
+ return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-")
129
+
130
+
131
+ def entry(req, it):
132
+ mock = f" · *Mockup:* {it['mockup'].replace('docs/', '')}" if it["mockup"] else ""
133
+ acc = it["acceptance"] or "TODO — write: When <actor> <does what> on <screen>, then <a result a browser robot can observe>"
134
+ if it["perf"] and it["perf"].lower() not in acc.lower():
135
+ acc = acc.rstrip(".") + "; " + it["perf"]
136
+ return (f'<a id="d-{req.lower()}"></a>\n- **{req}** — {it["title"]}. *BRD:* {it["id"]}{mock}\n'
137
+ f" - *Acceptance:* {acc}\n")
138
+
139
+
140
+ def config_phase():
141
+ """appPhase from .tfcore/core-config.yaml; 1 when absent or null."""
142
+ p = os.path.join(".tfcore", "core-config.yaml")
143
+ if os.path.isfile(p):
144
+ m = re.search(r"(?m)^appPhase:\s*(\d+)", read(p))
145
+ if m:
146
+ return int(m.group(1))
147
+ return 1
148
+
149
+
150
+ def phase_names(app, phase):
151
+ """(BRD path, checklist path) for a phase: plain names for phase 1, App-Pn- otherwise."""
152
+ pfx = f"{app}-" if phase <= 1 else f"{app}-P{phase}-"
153
+ return os.path.join("docs", f"{pfx}BRD.md"), os.path.join("docs", f"{pfx}Checklist.md")
154
+
155
+
156
+ def phases_listed(app):
157
+ """Phase numbers from the Phases table of docs/<App>-Phases.md, in order."""
158
+ p = os.path.join("docs", f"{app}-Phases.md")
159
+ if not os.path.isfile(p):
160
+ die(f"{p} does not exist; --all-phases needs the Phases document")
161
+ nums = []
162
+ for line in section(read(p), "Phases").splitlines():
163
+ cells = [c.strip() for c in line.strip().strip("|").split("|")]
164
+ if cells and cells[0].strip("`* ").isdigit():
165
+ nums.append(int(cells[0].strip("`* ")))
166
+ if not nums:
167
+ die(f"{p} has no numbered rows in its Phases table")
168
+ return nums
169
+
170
+
171
+ def other_phase_counters(app, own_cl, below=None):
172
+ """Highest REQ number per prefix across the other phases' checklists, so ids run on.
173
+ below=n counts only phases under n (an --all-phases run, which rewrites them in order);
174
+ otherwise every other phase counts, so a single-phase run can never reuse an id."""
175
+ counters = {"UI": 0, "FN": 0, "RAG": 0, "NFR": 0}
176
+ if not os.path.isdir("docs"):
177
+ return counters
178
+ for f in os.listdir("docs"):
179
+ m = re.fullmatch(rf"{re.escape(app)}(?:-P(\d+))?-Checklist\.md", f)
180
+ if not m or os.path.join("docs", f) == own_cl:
181
+ continue
182
+ if below is not None and int(m.group(1) or 1) >= below:
183
+ continue
184
+ for r in re.finditer(r"REQ-(UI|FN|RAG|NFR)-(\d{3})", read(os.path.join("docs", f))):
185
+ counters[r.group(1)] = max(counters[r.group(1)], int(r.group(2)))
186
+ return counters
187
+
188
+
189
+ def main(argv):
190
+ if len(argv) < 2 or argv[1] in ("-h", "--help"):
191
+ print(__doc__)
192
+ return 0
193
+ app = argv[1]
194
+ force = "--force" in argv
195
+ add_missing = "--add-missing" in argv
196
+ phase = None
197
+ if "--phase" in argv:
198
+ i = argv.index("--phase")
199
+ if i + 1 >= len(argv) or not argv[i + 1].isdigit() or int(argv[i + 1]) < 1:
200
+ die("--phase needs a number from 1 upwards")
201
+ phase = int(argv[i + 1])
202
+ if "--all-phases" in argv:
203
+ rc = 0
204
+ for n in phases_listed(app):
205
+ rc = max(rc, run(app, n, force, add_missing, in_order=True))
206
+ return rc
207
+ return run(app, phase if phase else config_phase(), force, add_missing)
208
+
209
+
210
+ def run(app, phase, force, add_missing, in_order=False):
211
+ brd_p, cl_p = phase_names(app, phase)
212
+ if not os.path.isfile(brd_p):
213
+ die(f"{brd_p} does not exist" + (f" (phase {phase}; phase 1 is docs/{app}-BRD.md)" if phase > 1 else ""))
214
+ brd = read(brd_p)
215
+ items = ledger(brd)
216
+ if not items:
217
+ die(f"{brd_p} has no **BRD-N** items in its Requirements section")
218
+ scr = screens(brd)
219
+ routes = {s.lower(): r for s, r in scr}
220
+ size = header_field(brd, "Size") or "Small"
221
+ phase_label = header_field(brd, "Phase")
222
+
223
+ existing = read(cl_p) if os.path.isfile(cl_p) else ""
224
+ if existing and not force and not add_missing:
225
+ die(f"{cl_p} already exists; use --add-missing to append rows for new BRD items, or --force to rewrite", 1)
226
+ mapped = set(re.findall(r"\*BRD:\*\s*(BRD-\d+)", existing)) | set(re.findall(r"\((BRD-\d+)\)", existing))
227
+ counters = other_phase_counters(app, cl_p, below=phase if in_order else None)
228
+ if add_missing:
229
+ for m in re.finditer(r"REQ-(UI|FN|RAG|NFR)-(\d{3})", existing):
230
+ counters[m.group(1)] = max(counters[m.group(1)], int(m.group(2)))
231
+ new_counts = {"UI": 0, "FN": 0, "RAG": 0, "NFR": 0}
232
+
233
+ rows, entries = [], {}
234
+ for it in items:
235
+ if add_missing and it["id"] in mapped:
236
+ continue
237
+ cls = classify(it)
238
+ counters[cls] += 1
239
+ new_counts[cls] += 1
240
+ req = f"REQ-{cls}-{counters[cls]:03d}"
241
+ rows.append(f"| {req} | {it['title']} | Not Started | 0% | — | [view](#d-{req.lower()}) |")
242
+ key = ("Non-functional" if cls == "NFR" else "RAG / AI requirements" if cls == "RAG"
243
+ else (it["screen"] or "Other"))
244
+ entries.setdefault(key, []).append(entry(req, it))
245
+
246
+ if add_missing and existing:
247
+ if not rows:
248
+ print(f"tf-split-brd: every BRD item already has a row in {cl_p}; nothing to add")
249
+ return 0
250
+ text = existing
251
+ m = re.search(r"(?ms)^## Requirements Status\s*\n.*?(?=^## |\Z)", text)
252
+ if not m:
253
+ die(f"{cl_p} has no Requirements Status section")
254
+ tbl_end = m.end()
255
+ # insert rows after the last table row of the status table
256
+ seg = text[m.start():tbl_end]
257
+ last_row = list(re.finditer(r"(?m)^\|\s*REQ-[^\n]*$", seg))
258
+ if last_row:
259
+ pos = m.start() + last_row[-1].end()
260
+ text = text[:pos] + "\n" + "\n".join(rows) + text[pos:]
261
+ for key, ents in entries.items():
262
+ hm = re.search(rf"(?ms)^##\s+(?:Page:\s*)?{re.escape(key)}\b[^\n]*\n.*?(?=^## |\Z)", text)
263
+ block = "\n".join(ents)
264
+ if hm:
265
+ text = text[:hm.end()].rstrip("\n") + "\n\n" + block + "\n" + text[hm.end():]
266
+ else:
267
+ heading = key if key in ("Non-functional", "RAG / AI requirements") else f"Page: {key}" + (f" (`{routes[key.lower()]}`)" if key.lower() in routes else "")
268
+ text = text.rstrip("\n") + f"\n\n## {heading}\n\n{block}\n"
269
+ with open(cl_p, "w", encoding="utf-8", newline="\n") as f:
270
+ f.write(text)
271
+ print(f"tf-split-brd: appended {len(rows)} row(s) to {cl_p}: " + ", ".join(r.split("|")[1].strip() for r in rows))
272
+ return 0
273
+
274
+ goal = re.sub(r"\s+", " ", section(brd, "Summary").strip().split("\n\n")[0]).strip() or f"Build {app} as the BRD describes."
275
+ title = f"# {app} — Checklist" + (f" (phase {phase})" if phase_label else "")
276
+ out = [title, "", "| | |", "|---|---|", f"| App | {app} |", f"| Size | {size} |"]
277
+ if phase_label:
278
+ out.append(f"| Phase | {phase_label} |")
279
+ out += ["", "## Goal", "", goal + (" This checklist is the whole work list of this phase." if phase_label else " This single checklist is the whole work list."), "",
280
+ "## Requirements Status", "",
281
+ "| ID | Requirement | Status | % | Remarks | Details |", "|----|-------------|--------|---|---------|---------|"]
282
+ out += rows
283
+ out += ["", STATUS_LINE, ""]
284
+ ordered = [s for s, _ in scr if s in entries] + [k for k in entries if k not in dict(scr) and k not in ("RAG / AI requirements", "Non-functional")]
285
+ for key in ordered:
286
+ route = routes.get(key.lower(), "")
287
+ out.append(f"## Page: {key}" + (f" (`{route}`)" if route else ""))
288
+ out.append("")
289
+ out.append("\n".join(entries[key]))
290
+ for key in ("RAG / AI requirements", "Non-functional"):
291
+ if key in entries:
292
+ out += [f"## {key}", "", "\n".join(entries[key])]
293
+ with open(cl_p, "w", encoding="utf-8", newline="\n") as f:
294
+ f.write("\n".join(out).rstrip("\n") + "\n")
295
+ n = {k: v for k, v in new_counts.items() if v}
296
+ todo = sum(1 for it in items if not it["acceptance"] and not it["nfr"])
297
+ print(f"tf-split-brd: wrote {cl_p}: {len(rows)} rows (" + ", ".join(f"{k} {v}" for k, v in n.items()) + f"), {len(ordered)} screen section(s)"
298
+ + (f"; {todo} item(s) had no acceptance line and carry a TODO the checker will refuse" if todo else ""))
299
+ print("tf-split-brd: correct any class the guess got wrong, then run: bash .tfcore/utils/tf-doc-check.sh --app " + app)
300
+ return 0
301
+
302
+
303
+ if __name__ == "__main__":
304
+ try:
305
+ sys.exit(main(sys.argv))
306
+ except SystemExit:
307
+ raise
308
+ except Exception as e:
309
+ die(str(e))
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # tf-split-brd.sh — draft docs/<App>-Checklist.md from the BRD ledger.
3
+ #
4
+ # bash .tfcore/utils/tf-split-brd.sh MyApp # write it (refuses if one exists)
5
+ # bash .tfcore/utils/tf-split-brd.sh MyApp --force # rewrite
6
+ # bash .tfcore/utils/tf-split-brd.sh MyApp --add-missing # append rows for BRD items with no row (amend-docs)
7
+ # bash .tfcore/utils/tf-split-brd.sh MyApp --phase 2 # a later phase of a Large project (docs/MyApp-P2-BRD.md)
8
+ # bash .tfcore/utils/tf-split-brd.sh MyApp --all-phases # every phase listed in docs/MyApp-Phases.md
9
+ #
10
+ # Without --phase the phase is appPhase in core-config.yaml (1 when absent).
11
+ # REQ numbers run on across phases; an id is never reused.
12
+ #
13
+ # One row per BRD item in the template shape, acceptance lines copied verbatim,
14
+ # class guessed (UI / FN / RAG / NFR) for the agent to correct. Then run
15
+ # tf-doc-check.sh --app MyApp. Thin wrapper over tf-split-brd.py; Python 3 stdlib only.
16
+ set -uo pipefail
17
+ SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ if ! command -v python3 >/dev/null 2>&1; then
19
+ echo "tf-split-brd: python3 is required (standard library only)." >&2
20
+ exit 2
21
+ fi
22
+ exec python3 "$SELF_DIR/tf-split-brd.py" "$@"
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ # tf-status-evidence.sh — the evidence *refresh-status rebuilds PROJECT-STATUS from, after a
3
+ # session died before its status gate (Sitting 4b, 2026-09-06). Reads only; never git.
4
+ #
5
+ # bash .tfcore/utils/tf-status-evidence.sh <App> [--no-build] [--phase N]
6
+ #
7
+ # Prints, in this order:
8
+ # 1. what the stale PROJECT-STATUS.md claims (last_updated, current_phase, the next command)
9
+ # 2. files under src/ and tests/ changed after that file was last written (work the gate never saw)
10
+ # 3. checklist rows left at Implemented / In Progress / Needs re-verify / PARTIAL / FAIL
11
+ # (the fingerprint of a smoke that ran and a verify that did not)
12
+ # 4. the build verdict from tf-build.sh (skipped with --no-build)
13
+ # Then the agent decides the interruption signature and runs tf-status-facts.sh for the gate.
14
+ set -uo pipefail
15
+ APP="${1:-}"; [[ -z "$APP" ]] && { sed -n '2,12p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 2; }
16
+ shift
17
+ BUILD=1; PHASE=""
18
+ while [[ $# -gt 0 ]]; do
19
+ case "$1" in
20
+ --no-build) BUILD=0; shift ;;
21
+ --phase) PHASE="$2"; shift 2 ;;
22
+ *) shift ;;
23
+ esac
24
+ done
25
+ [[ -z "$PHASE" ]] && PHASE="$(grep -oE '^appPhase:\s*[0-9]+' .tfcore/core-config.yaml 2>/dev/null | grep -oE '[0-9]+' || echo 1)"
26
+ [[ "$PHASE" -le 1 ]] && CL="docs/${APP}-Checklist.md" || CL="docs/${APP}-P${PHASE}-Checklist.md"
27
+ ST="PROJECT-STATUS.md"
28
+
29
+ echo "# tf-status-evidence — $APP — $(date -u +%Y-%m-%dT%H:%M:%SZ) — phase $PHASE"
30
+ echo
31
+ echo "## 1. What the stale status file claims (a hypothesis, not a fact)"
32
+ if [[ -f "$ST" ]]; then
33
+ grep -E '^(last_updated|current_phase|last_verified_build|last_verified_date):' "$ST" | sed 's/^/- /'
34
+ awk '/^## Next command to run/{p=1;next} /^## /{p=0} p' "$ST" | grep -E '^\s*/' | sed 's/^\s*/- next: /' | head -2
35
+ echo "- written: $(date -u -r "$ST" +%Y-%m-%dT%H:%M:%SZ)"
36
+ else
37
+ echo "- no PROJECT-STATUS.md; treat as never written"
38
+ fi
39
+ echo
40
+ echo "## 2. Files changed after the status file was written (src/ and tests/, newest first)"
41
+ if [[ -f "$ST" ]]; then
42
+ mapfile -t NEWER < <(find src tests -type f -newer "$ST" \
43
+ -not -path '*/bin/*' -not -path '*/obj/*' -not -path '*/node_modules/*' -not -path '*/.artifacts/*' 2>/dev/null \
44
+ | xargs -r ls -t 2>/dev/null)
45
+ echo "- ${#NEWER[@]} file(s)"
46
+ printf -- '- %s\n' "${NEWER[@]:0:30}"
47
+ [[ ${#NEWER[@]} -gt 30 ]] && echo "- (${#NEWER[@]} in total; the rest with: find src tests -type f -newer PROJECT-STATUS.md)"
48
+ else
49
+ echo "- no status file to compare against"
50
+ fi
51
+ echo
52
+ echo "## 3. Checklist rows that look interrupted ($CL)"
53
+ if [[ -f "$CL" ]]; then
54
+ grep -E '^\s*\|\s*`?REQ-' "$CL" | awk -F'|' '{id=$2; st=$4; gsub(/^[ `*]+|[ `*]+$/,"",id); gsub(/^[ ]+|[ ]+$/,"",st); print id" — "st}' \
55
+ | grep -iE ' — (Implemented|In Progress|Needs re-verify|PARTIAL|FAIL)' | sed 's/^/- /' | head -40
56
+ n=$(grep -E '^\s*\|\s*`?REQ-' "$CL" | awk -F'|' '{print $4}' | grep -ciE 'Implemented|In Progress|Needs re-verify|PARTIAL|FAIL' || true)
57
+ [[ "$n" == "0" ]] && echo "- none: every row is terminal, Blocked or not started"
58
+ else
59
+ echo "- $CL does not exist"
60
+ fi
61
+ echo
62
+ echo "## 4. Build"
63
+ if [[ $BUILD -eq 1 ]]; then
64
+ bash "$(dirname "${BASH_SOURCE[0]}")/tf-build.sh" build 2>&1 | tail -3 | sed 's/^/- /'
65
+ else
66
+ echo "- skipped (--no-build)"
67
+ fi
68
+ exit 0