@techierathore/techieflow 1.0.1 → 1.0.3

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 (178) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +0 -5
  2. package/.claude/commands/TechieFlow/agents/architect.md +0 -6
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +41 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +23 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  7. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  8. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  9. package/.claude/commands/TechieFlow/tasks/amend-docs.md +20 -143
  10. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  11. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  12. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  13. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  14. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  15. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  16. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  17. package/.claude/commands/TechieFlow/tasks/metrics-report.md +1 -1
  18. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  19. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  20. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  21. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  22. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  23. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  24. package/.opencode/plugin/techieflow.js +39 -12
  25. package/.tfcore/agents/analyst.md +0 -5
  26. package/.tfcore/agents/architect.md +0 -6
  27. package/.tfcore/agents/flow-master.md +41 -215
  28. package/.tfcore/agents/verifier.md +23 -64
  29. package/.tfcore/core-config.yaml +10 -0
  30. package/.tfcore/hooks/block-git.sh +4 -4
  31. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  32. package/.tfcore/hooks/guard-build.sh +76 -0
  33. package/.tfcore/hooks/guard-db.sh +90 -0
  34. package/.tfcore/hooks/guard-metrics.sh +56 -0
  35. package/.tfcore/hooks/guard-status-html.sh +116 -71
  36. package/.tfcore/hooks/guard-status.sh +43 -16
  37. package/.tfcore/hooks/guard-verify.sh +70 -48
  38. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  39. package/.tfcore/install-manifest.yaml +0 -18
  40. package/.tfcore/routing.yaml +25 -14
  41. package/.tfcore/standards/coding-standards-core.md +61 -0
  42. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  43. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  44. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  45. package/.tfcore/tasks/_status-update-gate.md +10 -74
  46. package/.tfcore/tasks/_yolo-mode.md +20 -58
  47. package/.tfcore/tasks/amend-docs.md +20 -143
  48. package/.tfcore/tasks/build-phase.md +14 -224
  49. package/.tfcore/tasks/day1-brownfield.md +31 -505
  50. package/.tfcore/tasks/day1-greenfield.md +28 -213
  51. package/.tfcore/tasks/deploy-checklist.md +19 -0
  52. package/.tfcore/tasks/devguide.md +10 -219
  53. package/.tfcore/tasks/fix-issues.md +10 -129
  54. package/.tfcore/tasks/log-miss.md +19 -158
  55. package/.tfcore/tasks/metrics-report.md +1 -1
  56. package/.tfcore/tasks/mockups.md +12 -117
  57. package/.tfcore/tasks/refresh-status.md +9 -130
  58. package/.tfcore/tasks/split-brd.md +10 -126
  59. package/.tfcore/tasks/triage-and-fix.md +14 -0
  60. package/.tfcore/tasks/triage-issues.md +11 -152
  61. package/.tfcore/tasks/verify-phase.md +36 -572
  62. package/.tfcore/telemetry/SCHEMA.md +43 -7
  63. package/.tfcore/telemetry/tf-metrics.sh +152 -17
  64. package/.tfcore/templates/project-brief-tmpl.yaml +1 -1
  65. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  66. package/.tfcore/templates/stack-questions.md +77 -0
  67. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  68. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  69. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  70. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  71. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  72. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  73. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  74. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  75. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  76. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  77. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  78. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  79. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  80. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  81. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  82. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  83. package/.tfcore/user-guide.md +0 -23
  84. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  85. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  86. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  87. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  88. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  89. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  90. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  91. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  92. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  93. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  94. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  95. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  96. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  97. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  98. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  99. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  100. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  101. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  102. package/.tfcore/utils/tf-brd-status.py +176 -0
  103. package/.tfcore/utils/tf-brd-status.sh +19 -0
  104. package/.tfcore/utils/tf-build-list.py +191 -0
  105. package/.tfcore/utils/tf-build-list.sh +20 -0
  106. package/.tfcore/utils/tf-build.sh +143 -0
  107. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  108. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  109. package/.tfcore/utils/tf-day1-files.py +208 -0
  110. package/.tfcore/utils/tf-day1-files.sh +20 -0
  111. package/.tfcore/utils/tf-devguide-list.py +249 -0
  112. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  113. package/.tfcore/utils/tf-doc-check.py +1267 -0
  114. package/.tfcore/utils/tf-doc-check.sh +29 -0
  115. package/.tfcore/utils/tf-emit.sh +203 -13
  116. package/.tfcore/utils/tf-fix-close.sh +83 -0
  117. package/.tfcore/utils/tf-goal.sh +193 -62
  118. package/.tfcore/utils/tf-harness.sh +4 -9
  119. package/.tfcore/utils/tf-log-miss.py +197 -0
  120. package/.tfcore/utils/tf-log-miss.sh +14 -0
  121. package/.tfcore/utils/tf-misses-md.py +211 -0
  122. package/.tfcore/utils/tf-misses-md.sh +13 -0
  123. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  124. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  125. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  126. package/.tfcore/utils/tf-perf-grade.py +97 -0
  127. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  128. package/.tfcore/utils/tf-phase.sh +74 -0
  129. package/.tfcore/utils/tf-routing-bind.sh +10 -5
  130. package/.tfcore/utils/tf-routing.sh +6 -7
  131. package/.tfcore/utils/tf-split-brd.py +304 -0
  132. package/.tfcore/utils/tf-split-brd.sh +22 -0
  133. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  134. package/.tfcore/utils/tf-status-facts.py +309 -0
  135. package/.tfcore/utils/tf-status-facts.sh +20 -0
  136. package/.tfcore/utils/tf-triage.py +243 -0
  137. package/.tfcore/utils/tf-triage.sh +16 -0
  138. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  139. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  140. package/.tfcore/utils/tf-verify-env.sh +88 -0
  141. package/.tfcore/utils/tf-verify-list.py +270 -0
  142. package/.tfcore/utils/tf-verify-list.sh +13 -0
  143. package/.tfcore/utils/tf-verify-screens.mjs +283 -0
  144. package/.tfcore/utils/tf-verify-screens.sh +22 -0
  145. package/.tfcore/utils/tf-verify-tests.sh +123 -0
  146. package/.tfcore/utils/tf-verify-verdict.py +283 -0
  147. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  148. package/.tfcore/utils/tf-yolo.sh +59 -0
  149. package/README.md +114 -1149
  150. package/docs/TechieFlow-Installation.md +27 -7
  151. package/opencode.jsonc +13 -33
  152. package/package.json +4 -7
  153. package/scripts/install.mjs +73 -46
  154. package/scripts/npm-cleanup.mjs +137 -0
  155. package/scripts/npm-postinstall.mjs +49 -0
  156. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  157. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  158. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  159. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  160. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  161. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  162. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  163. package/.codex/config.toml +0 -46
  164. package/.codex/hooks.json +0 -63
  165. package/.codex/rules/techieflow.rules +0 -8
  166. package/.tfcore/hooks/codex-adapter.py +0 -248
  167. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  168. package/.tfcore/tasks/author-brd.md +0 -79
  169. package/.tfcore/tasks/document-project.md +0 -343
  170. package/.tfcore/tasks/execute-checklist.md +0 -86
  171. package/.tfcore/tasks/index-docs.md +0 -173
  172. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  173. package/.tfcore/tasks/shard-doc.md +0 -185
  174. package/.tfcore/utils/tf-codex-bind.py +0 -196
  175. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  176. package/WORKFLOW.html +0 -1986
  177. package/scripts/test-install.mjs +0 -505
  178. package/scripts/validate.mjs +0 -163
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env python3
2
+ """tf-build-list.py — the mechanical start of a build pass (see tf-build-list.sh).
3
+
4
+ bash .tfcore/utils/tf-build-list.sh <App> [--phase N] [--prompts]
5
+
6
+ Reads the phase's checklist (appPhase in core-config.yaml, or --phase) and prints:
7
+ - the mode: FIX (rows FAIL / PARTIAL / In Progress / Needs re-verify exist), FRESH
8
+ (every open row), or NOTHING (every row terminal or Blocked)
9
+ - the working list, grouped into clusters by the checklist's page section, each
10
+ naming its builder from the row prefix: REQ-UI -> trblazeui, REQ-RAG -> techierag,
11
+ REQ-FN / REQ-NFR -> builder
12
+ - with --prompts, one ready sub-agent prompt per cluster, filled from
13
+ .tfcore/templates/v4custom/build-subagent-prompt.md with the rows, their acceptance
14
+ lines, their mockups and the two standing rules.
15
+ Writes nothing. Python 3 standard library only. Exit 0 printed, 2 could not run.
16
+ """
17
+ import os
18
+ import re
19
+ import sys
20
+
21
+ TERMINAL = {"verified", "done (pre-existing)", "n/a"}
22
+ FIX = {"fail", "partial", "in progress", "needs re-verify"}
23
+ BLOCKED = {"blocked"}
24
+ BUILDER = {"UI": "trblazeui", "RAG": "techierag", "FN": "builder", "NFR": "builder"}
25
+ TPL = os.path.join(".tfcore", "templates", "v4custom", "build-subagent-prompt.md")
26
+
27
+
28
+ def die(msg):
29
+ print(f"tf-build-list: {msg}", file=sys.stderr)
30
+ sys.exit(2)
31
+
32
+
33
+ def read(p):
34
+ with open(p, encoding="utf-8", errors="replace") as f:
35
+ return f.read()
36
+
37
+
38
+ def config_phase():
39
+ p = os.path.join(".tfcore", "core-config.yaml")
40
+ if os.path.isfile(p):
41
+ m = re.search(r"(?m)^appPhase:\s*(\d+)", read(p))
42
+ if m:
43
+ return int(m.group(1))
44
+ return 1
45
+
46
+
47
+ def norm(s):
48
+ s = s.strip().strip("`* ").lower()
49
+ return re.sub(r"\s*\(.*$", "", s).strip()
50
+
51
+
52
+ def rows_of(text):
53
+ rows = []
54
+ for line in text.splitlines():
55
+ if re.match(r"^\s*\|\s*`?REQ-", line):
56
+ c = [x.strip() for x in line.strip().strip("|").split("|")]
57
+ if len(c) >= 3:
58
+ rows.append({"id": c[0].strip("`* "), "title": c[1].strip("`* "), "status": norm(c[2]),
59
+ "remarks": c[4] if len(c) > 4 else ""})
60
+ return rows
61
+
62
+
63
+ def entries_of(text):
64
+ """id -> (section heading, acceptance line, mockup, brd)"""
65
+ out, current = {}, "Other"
66
+ lines = text.splitlines()
67
+ for i, line in enumerate(lines):
68
+ m = re.match(r"^##\s+(.+)$", line)
69
+ if m:
70
+ current = re.sub(r"^(?:page|screen)\s*:\s*", "", m.group(1).strip(), flags=re.I)
71
+ continue
72
+ a = re.search(r"<a id=['\"]d-(req-[a-z]+-\d+)['\"]", line, re.I)
73
+ if a:
74
+ rid = a.group(1).upper()
75
+ acc = mock = brd = ""
76
+ for j in range(i, min(i + 12, len(lines))):
77
+ l = lines[j]
78
+ if re.search(r"\*Acceptance:?\*|^\s*[-*]\s*Acceptance:", l, re.I) and not acc:
79
+ acc = re.sub(r"^\s*[-*]\s*\*?Acceptance:?\*?:?\s*", "", l).strip()
80
+ mm = re.search(r"((?:docs/)?mockups/[\w./-]+\.html)", l)
81
+ if mm and not mock:
82
+ mock = mm.group(1)
83
+ bm = re.search(r"BRD-\d+", l)
84
+ if bm and not brd:
85
+ brd = bm.group(0)
86
+ if j > i and re.match(r"^\s*(?:[-*]\s*)?<a id=|^## ", l):
87
+ break # the next entry starts (it begins with "- <a id="): nothing below is this row's
88
+ out[rid] = (current, acc, mock, brd)
89
+ return out
90
+
91
+
92
+ def main(argv):
93
+ if len(argv) < 2 or argv[1] in ("-h", "--help"):
94
+ print(__doc__)
95
+ return 0
96
+ app = argv[1]
97
+ prompts = "--prompts" in argv
98
+ phase = config_phase()
99
+ if "--phase" in argv:
100
+ i = argv.index("--phase")
101
+ if i + 1 >= len(argv) or not argv[i + 1].isdigit():
102
+ die("--phase needs a number")
103
+ phase = int(argv[i + 1])
104
+ pfx = f"{app}-" if phase <= 1 else f"{app}-P{phase}-"
105
+ cl = os.path.join("docs", f"{pfx}Checklist.md")
106
+ if not os.path.isfile(cl):
107
+ die(f"{cl} does not exist; day-1 stage 2 writes it")
108
+ text = read(cl)
109
+ rows = rows_of(text)
110
+ if not rows:
111
+ die(f"{cl} has no REQ rows")
112
+ ent = entries_of(text)
113
+
114
+ fix_rows = [r for r in rows if r["status"] in FIX]
115
+ open_rows = [r for r in rows if r["status"] not in TERMINAL | BLOCKED]
116
+ if fix_rows:
117
+ mode, work = "FIX", fix_rows
118
+ elif open_rows:
119
+ mode, work = "FRESH", open_rows
120
+ else:
121
+ mode, work = "NOTHING", []
122
+ terminal = sum(1 for r in rows if r["status"] in TERMINAL)
123
+ blocked = [r for r in rows if r["status"] in BLOCKED]
124
+ print(f"# tf-build-list — {app} — {cl} — phase {phase}")
125
+ print(f"Mode: {mode} — {len(work)} row(s) to build; {terminal} terminal, {len(blocked)} Blocked, {len(rows)} total"
126
+ + (f" — {', '.join(r['id'] for r in work[:8])}{' …' if len(work) > 8 else ''}" if work else ""))
127
+ if blocked:
128
+ print("Blocked (library gaps, pass through unless the feedback entry is resolved): " + ", ".join(r["id"] for r in blocked))
129
+ if not work:
130
+ print("Nothing to build. Run the status gate; the next command is the verifier's or the owner's.")
131
+ return 0
132
+
133
+ clusters = {}
134
+ for r in work:
135
+ sec, acc, mock, brd = ent.get(r["id"].upper(), ("Other", "", "", ""))
136
+ cls = re.match(r"REQ-(UI|FN|RAG|NFR)-", r["id"]).group(1)
137
+ key = ("Non-functional" if cls == "NFR" else "RAG" if cls == "RAG" else sec, BUILDER[cls])
138
+ clusters.setdefault(key, []).append((r, acc, mock, brd))
139
+ print()
140
+ print("## Clusters (one sub-agent each; spawn them all in one turn)")
141
+ letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
142
+ named = []
143
+ for i, ((sec, builder), items) in enumerate(clusters.items()):
144
+ label = letters[i % 26] + ("" if i < 26 else str(i // 26))
145
+ named.append((label, sec, builder, items))
146
+ mocks = sorted({m for _r, _a, m, _b in items if m})
147
+ print(f"Cluster {label} [{builder}]: " + ", ".join(r["id"] for r, *_ in items)
148
+ + f" ({sec}" + (f" — mockup: {', '.join(mocks)}" if mocks else "") + ")")
149
+ print()
150
+ print("## Working list")
151
+ for label, sec, builder, items in named:
152
+ for r, acc, mock, brd in items:
153
+ todo = "" if acc else " ⚠ no acceptance line: fix the checklist first"
154
+ print(f"- {r['id']} [{label}/{builder}] {r['title']} — {r['status']}{todo}")
155
+ if acc:
156
+ print(f" Acceptance: {acc}")
157
+ if mock:
158
+ print(f" Mockup: {mock}")
159
+ if not prompts:
160
+ print()
161
+ print("Add --prompts for one ready sub-agent prompt per cluster.")
162
+ return 0
163
+ if not os.path.isfile(TPL):
164
+ die(f"template {TPL} does not exist at its literal path")
165
+ tpl = read(TPL)
166
+ tpl = re.sub(r"<!--.*?-->\s*", "", tpl, flags=re.S)
167
+ for label, sec, builder, items in named:
168
+ lines = []
169
+ for r, acc, mock, brd in items:
170
+ lines.append(f"- {r['id']} — {r['title']}" + (f" ({brd})" if brd else ""))
171
+ lines.append(f" Acceptance: {acc or 'MISSING — refuse the row and report it'}")
172
+ if mock:
173
+ lines.append(f" Mockup: {mock}")
174
+ mocks = sorted({m for _r, _a, m, _b in items if m})
175
+ body = (tpl.replace("{App}", app).replace("{Cluster}", label).replace("{Builder}", builder)
176
+ .replace("{Section}", sec).replace("{Rows}", "\n".join(lines))
177
+ .replace("{Mockups}", ", ".join(mocks) if mocks else "none (not a UI cluster)")
178
+ .replace("{Checklist}", cl))
179
+ print()
180
+ print(f"## Prompt for cluster {label} [{builder}]")
181
+ print(body.strip())
182
+ return 0
183
+
184
+
185
+ if __name__ == "__main__":
186
+ try:
187
+ sys.exit(main(sys.argv))
188
+ except SystemExit:
189
+ raise
190
+ except Exception as e:
191
+ die(str(e))
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # tf-build-list.sh — the mechanical start of a build pass.
3
+ #
4
+ # bash .tfcore/utils/tf-build-list.sh MyApp # mode (FIX / FRESH / NOTHING), clusters, working list
5
+ # bash .tfcore/utils/tf-build-list.sh MyApp --prompts # plus one ready sub-agent prompt per cluster
6
+ # bash .tfcore/utils/tf-build-list.sh MyApp --phase 2 # a later phase of a Large project
7
+ #
8
+ # Reads the phase's checklist (appPhase in core-config.yaml unless --phase). Rows FAIL /
9
+ # PARTIAL / In Progress / Needs re-verify make it FIX mode over exactly those rows; else every
10
+ # open row is FRESH; Blocked rows pass through. Clusters follow the checklist's page sections,
11
+ # each with its builder from the prefix (UI -> trblazeui, RAG -> techierag, FN/NFR -> builder).
12
+ # Prompts are filled from .tfcore/templates/v4custom/build-subagent-prompt.md.
13
+ # Writes nothing. Thin wrapper over tf-build-list.py; Python 3 stdlib only.
14
+ set -uo pipefail
15
+ SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ if ! command -v python3 >/dev/null 2>&1; then
17
+ echo "tf-build-list: python3 is required (standard library only)." >&2
18
+ exit 2
19
+ fi
20
+ exec python3 "$SELF_DIR/tf-build-list.py" "$@"
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env bash
2
+ # tf-build.sh — build, test or run a .NET target on whatever host this is, trying the
3
+ # invocation rungs in order and stopping at the first that works (Sitting 4b, 2026-09-05;
4
+ # replaces the prose ladder build-invocation-ladder.md §0 to §C).
5
+ #
6
+ # bash .tfcore/utils/tf-build.sh [build] [<target>] [-- <extra dotnet args>]
7
+ # bash .tfcore/utils/tf-build.sh test [<target>] [-- <extra dotnet args>]
8
+ # bash .tfcore/utils/tf-build.sh run <project> [-- <extra dotnet args>]
9
+ # bash .tfcore/utils/tf-build.sh probe # print the platform and the rungs, run nothing
10
+ #
11
+ # <target> is a .sln, .slnx or .csproj relative to the current folder (default: the one
12
+ # solution or project found here). Output goes to tests/.artifacts/build/<UTC time>.log.
13
+ #
14
+ # Prints ONE verdict line the agent copies into the status log row:
15
+ # PASS built on wsl via cmd.exe /c dotnet (rung 4) — 0 warnings
16
+ # FAIL real compile errors on wsl via ~/.dotnet/dotnet (rung 2): 3 CS errors, first: …
17
+ # NOT-RUN no rung could run dotnet on wsl; tried: dotnet, ~/.dotnet/dotnet, cmd.exe, winrun, powershell.exe
18
+ # Exit 0 PASS · 1 FAIL (the code is wrong: fix it) · 2 NOT-RUN (the host is wrong: never a
19
+ # project blocker; say which rungs were tried).
20
+ #
21
+ # Rungs by host (docs/TechieFlow-How-It-Works.md; build-invocation-ladder.md §D keeps the
22
+ # runtime-driver table, which this script does not replace):
23
+ # macOS / native Linux / native Windows: dotnet
24
+ # WSL, no MAUI head in the target: dotnet · ~/.dotnet/dotnet · winrun · cmd.exe /c dotnet · powershell.exe dotnet
25
+ # WSL, a MAUI head in the target: cmd.exe /c dotnet · winrun · powershell.exe dotnet (workloads live Windows-side)
26
+ # OpenCode Docker (TF_OPENCODE_DOCKER=1 or winrun + TF_WINDOWS_APP_PATH): MAUI → winrun; else dotnet
27
+ # A workload / SDK-missing error (NETSDK1178, Microsoft.iOS/Android/MacCatalyst.Sdk, Workload ID,
28
+ # WindowsAppSDK, command not found) means the wrong rung: the next one is tried, nothing is logged
29
+ # as a blocker. A CS#### error means the code is wrong: the rung stays and the script exits 1.
30
+ set -u
31
+
32
+ MODE="build"; TARGET=""; EXTRA=()
33
+ case "${1:-}" in
34
+ build|test|run|probe) MODE="$1"; shift ;;
35
+ esac
36
+ while [[ $# -gt 0 ]]; do
37
+ if [[ "$1" == "--" ]]; then shift; EXTRA=("$@"); break; fi
38
+ TARGET="$1"; shift
39
+ done
40
+
41
+ # ---- platform -------------------------------------------------------------------------
42
+ PLATFORM="linux"
43
+ U="$(uname -s 2>/dev/null || echo windows)"
44
+ if [[ "$U" == Darwin* ]]; then PLATFORM="macos"
45
+ elif [[ "$U" == Linux* ]]; then
46
+ if grep -qiE "microsoft|wsl" /proc/version 2>/dev/null; then PLATFORM="wsl"
47
+ elif [[ "${TF_OPENCODE_DOCKER:-}" == "1" ]] || { [[ -x /usr/local/bin/winrun ]] && [[ -n "${TF_WINDOWS_APP_PATH:-}" ]]; }; then PLATFORM="docker"
48
+ fi
49
+ elif [[ "$U" == windows || "$U" == MINGW* || "$U" == MSYS* || "$U" == CYGWIN* ]]; then PLATFORM="windows"
50
+ fi
51
+
52
+ # ---- target ---------------------------------------------------------------------------
53
+ if [[ -z "$TARGET" && "$MODE" != "probe" ]]; then
54
+ mapfile -t CANDS < <(ls -1 *.slnx *.sln 2>/dev/null; ls -1 *.csproj 2>/dev/null)
55
+ if [[ ${#CANDS[@]} -eq 1 ]]; then TARGET="${CANDS[0]}"
56
+ elif [[ ${#CANDS[@]} -gt 1 ]]; then echo "NOT-RUN more than one solution or project here; name one: ${CANDS[*]}"; exit 2
57
+ fi
58
+ fi
59
+
60
+ # ---- does the target include a MAUI head? ----------------------------------------------
61
+ maui=0
62
+ projects=()
63
+ if [[ -n "$TARGET" ]]; then
64
+ case "$TARGET" in
65
+ *.csproj) projects=("$TARGET") ;;
66
+ *.sln) mapfile -t projects < <(grep -oE '"[^"]+\.csproj"' "$TARGET" 2>/dev/null | tr -d '"' | tr '\\' '/') ;;
67
+ *.slnx) mapfile -t projects < <(grep -oE 'Path="[^"]+\.csproj"' "$TARGET" 2>/dev/null | sed 's/Path="//; s/"$//' | tr '\\' '/') ;;
68
+ esac
69
+ base="$(dirname "$TARGET")"
70
+ for p in "${projects[@]}"; do
71
+ f="$p"; [[ -f "$f" ]] || f="$base/$p"
72
+ [[ -f "$f" ]] || continue
73
+ if grep -qiE '<UseMaui>\s*true|Microsoft\.NET\.Sdk\.Maui|net[0-9.]+-(android|ios|maccatalyst|windows10)' "$f"; then maui=1; fi
74
+ done
75
+ fi
76
+
77
+ # ---- rungs -----------------------------------------------------------------------------
78
+ rungs=()
79
+ case "$PLATFORM" in
80
+ macos|linux|windows) rungs=("dotnet") ;;
81
+ docker) if [[ $maui -eq 1 ]]; then rungs=("winrun"); else rungs=("dotnet" "winrun"); fi ;;
82
+ wsl)
83
+ if [[ $maui -eq 1 ]]; then rungs=("cmd.exe" "winrun" "powershell.exe")
84
+ else rungs=("dotnet" "$HOME/.dotnet/dotnet" "winrun" "cmd.exe" "powershell.exe"); fi ;;
85
+ esac
86
+
87
+ if [[ "$MODE" == "probe" ]]; then
88
+ echo "platform: $PLATFORM · target: ${TARGET:-none} · maui head: $([[ $maui -eq 1 ]] && echo yes || echo no)"
89
+ echo "rungs, in order: ${rungs[*]}"
90
+ exit 0
91
+ fi
92
+ [[ -z "$TARGET" && "$MODE" != "run" ]] && { echo "NOT-RUN no .sln, .slnx or .csproj here; name the target"; exit 2; }
93
+
94
+ # ---- the dotnet verb -------------------------------------------------------------------
95
+ verb="$MODE"
96
+ args=("$verb")
97
+ [[ -n "$TARGET" ]] && args+=("$TARGET")
98
+ [[ "$MODE" == "run" ]] && args=("run" "--project" "$TARGET")
99
+ args+=("${EXTRA[@]}")
100
+
101
+ mkdir -p tests/.artifacts/build
102
+ LOG="tests/.artifacts/build/$(date -u +%Y%m%dT%H%M%SZ)-$MODE.log"
103
+ WRONG_RUNG='NETSDK1178|Microsoft\.(iOS|Android|MacCatalyst|tvOS)\.Sdk|Workload ID|not recognized|WindowsAppSDK|command not found|No such file or directory|is not recognized as an internal or external command|The term .* is not recognized|workload.*not installed|Inadequate permissions'
104
+
105
+ tried=()
106
+ n=0
107
+ for r in "${rungs[@]}"; do
108
+ n=$((n+1))
109
+ case "$r" in
110
+ dotnet) cmd=(dotnet "${args[@]}"); label="dotnet" ;;
111
+ */.dotnet/dotnet) cmd=("$r" "${args[@]}"); label="~/.dotnet/dotnet" ;;
112
+ winrun) cmd=(winrun "dotnet ${args[*]}"); label="winrun dotnet" ;;
113
+ cmd.exe) cmd=(cmd.exe /c "dotnet ${args[*]}"); label="cmd.exe /c dotnet" ;;
114
+ powershell.exe) cmd=(powershell.exe -Command "dotnet ${args[*]}"); label="powershell.exe dotnet" ;;
115
+ esac
116
+ tried+=("$label")
117
+ { echo "### rung $n: ${cmd[*]}"; "${cmd[@]}" 2>&1; echo "### exit $?"; } >> "$LOG" 2>&1
118
+ rc="$(tail -1 "$LOG" | sed 's/### exit //')"
119
+ seg="$(awk -v n="### rung $n:" 'index($0,n)==1{p=1} p' "$LOG")"
120
+ if [[ "$rc" == "0" ]]; then
121
+ warns="$(grep -cE 'warning [A-Z]+[0-9]+' <<<"$seg" || true)"
122
+ echo "PASS $MODE on $PLATFORM via $label (rung $n) — $warns warning line(s); log $LOG"
123
+ exit 0
124
+ fi
125
+ # Code errors: C# (CS), Razor (RZ), Blazor (BL), XAML (XC, XLS), missing reference (MSB/NU).
126
+ # Until 2026-09-06 only CS counted, so a build failing on sixteen Razor RZ9991 errors was
127
+ # taken for a wrong rung and reported NOT-RUN "host issue" (MISS-TechieFlow-20260906-03).
128
+ if grep -qE 'error (CS|RZ|BL|XC|XLS)[0-9]{3,5}|error MSB[0-9]{4}: .*(does not exist|could not be found)|error NU1' <<<"$seg" && ! grep -qE "$WRONG_RUNG" <<<"$seg"; then
129
+ errs="$(grep -cE 'error (CS|RZ|BL|XC|XLS|MSB|NU)[0-9]+' <<<"$seg" || true)"
130
+ first="$(grep -E 'error (CS|RZ|BL|XC|XLS|MSB|NU)[0-9]+' <<<"$seg" | head -1 | sed 's/^\s*//' | cut -c1-160)"
131
+ echo "FAIL real errors on $PLATFORM via $label (rung $n): $errs error line(s), first: $first; log $LOG"
132
+ grep -E 'error (CS|RZ|BL|XC|XLS|MSB|NU)[0-9]+' <<<"$seg" | sed 's/^\s*//' | sort -u | head -10
133
+ exit 1
134
+ fi
135
+ if [[ "$MODE" == "test" ]] && grep -qE 'Failed!|Failed:\s+[1-9]|Tests? failed' <<<"$seg"; then
136
+ echo "FAIL tests failed on $PLATFORM via $label (rung $n); log $LOG"
137
+ grep -E 'Failed |Failed!|Error Message|Total tests|Passed!' <<<"$seg" | head -12
138
+ exit 1
139
+ fi
140
+ # anything else (workload, wrong rung, tool absent): try the next rung
141
+ done
142
+ echo "NOT-RUN no rung could $MODE on $PLATFORM; tried: ${tried[*]}. This is a host issue, never a project blocker; log $LOG"
143
+ exit 2
@@ -0,0 +1,64 @@
1
+ # tf-cdp-relay.ps1 — relay a Windows loopback port to every interface (Sitting 4c, 2026-09-06).
2
+ #
3
+ # powershell.exe -File tf-cdp-relay.ps1 -ListenPort 9223 -TargetPort 9222
4
+ #
5
+ # A MAUI Blazor Hybrid app's WebView2 opens its DevTools port on 127.0.0.1 only, so WSL cannot
6
+ # reach it. This relay listens on every interface and forwards to the loopback port, so Playwright
7
+ # in WSL can attach with connectOverCDP('http://<windows host ip>:9223'). Started and stopped by
8
+ # tf-verify-boot.sh; proven on MyDiary's Windows head, 2026-09-06 (the DevGuide run).
9
+ param(
10
+ [int]$ListenPort = 9223,
11
+ [string]$TargetHost = '127.0.0.1',
12
+ [int]$TargetPort = 9222
13
+ )
14
+
15
+ Add-Type -TypeDefinition @"
16
+ using System;
17
+ using System.Net;
18
+ using System.Net.Sockets;
19
+ using System.Threading;
20
+
21
+ public static class TcpRelay {
22
+ public static void Start(int listenPort, string targetHost, int targetPort) {
23
+ var listener = new TcpListener(IPAddress.Any, listenPort);
24
+ listener.Start();
25
+ while (true) {
26
+ var client = listener.AcceptTcpClient();
27
+ var t = new Thread(() => Handle(client, targetHost, targetPort));
28
+ t.IsBackground = true;
29
+ t.Start();
30
+ }
31
+ }
32
+
33
+ private static void Handle(TcpClient client, string targetHost, int targetPort) {
34
+ try {
35
+ using (client)
36
+ using (var target = new TcpClient(targetHost, targetPort)) {
37
+ using (var cs = client.GetStream())
38
+ using (var ts = target.GetStream()) {
39
+ var t1 = new Thread(() => Pump(cs, ts));
40
+ t1.IsBackground = true;
41
+ t1.Start();
42
+ Pump(ts, cs);
43
+ t1.Join();
44
+ }
45
+ }
46
+ } catch {}
47
+ }
48
+
49
+ private static void Pump(System.IO.Stream src, System.IO.Stream dst) {
50
+ var buf = new byte[65536];
51
+ try {
52
+ int n;
53
+ while ((n = src.Read(buf, 0, buf.Length)) > 0) {
54
+ dst.Write(buf, 0, n);
55
+ dst.Flush();
56
+ }
57
+ } catch {}
58
+ try { dst.Close(); } catch {}
59
+ }
60
+ }
61
+ "@
62
+
63
+ Write-Host "relay listening on 0.0.0.0:$ListenPort -> $TargetHost`:$TargetPort"
64
+ [TcpRelay]::Start($ListenPort, $TargetHost, $TargetPort)
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env python3
2
+ """tf-checklist-edit.py — the checklist edits the bug scripts share (Sitting 4c, 2026-09-06).
3
+
4
+ Imported by tf-triage.py and tf-log-miss.py; not run on its own. Every function takes the
5
+ checklist path, edits the file in place and returns what it did. The shapes follow
6
+ docs/TechieFlow-Document-Schemas.md §3.4: the six-cell row, the `<a id="d-req-…">` entry, one
7
+ acceptance line, a Remarks cell of at most 60 words.
8
+ """
9
+ import datetime
10
+ import os
11
+ import re
12
+
13
+ ROW = re.compile(r"^(\s*\|\s*`?)(REQ-[A-Z]+-\d+)(`?\s*\|)")
14
+ TODAY = datetime.date.today().isoformat()
15
+
16
+
17
+ def read(p):
18
+ with open(p, encoding="utf-8") as f:
19
+ return f.read()
20
+
21
+
22
+ def write(p, s):
23
+ with open(p, "w", encoding="utf-8") as f:
24
+ f.write(s)
25
+
26
+
27
+ def words(s, n=60):
28
+ w = s.split()
29
+ return s if len(w) <= n else " ".join(w[:n]) + "…" # the ellipsis rides on the last word: the checker counts words, and a bare "…" was a 61st (miss 29)
30
+
31
+
32
+ def rows(text):
33
+ """id -> {status, pct, remarks, title, line_no}"""
34
+ out = {}
35
+ for i, line in enumerate(text.splitlines()):
36
+ m = ROW.match(line)
37
+ if m:
38
+ c = [x.strip() for x in line.strip().strip("|").split("|")]
39
+ if len(c) >= 6:
40
+ pct = re.sub(r"[^0-9]", "", c[3])
41
+ out[m.group(2).upper()] = {"status": c[2].strip("`* "), "pct": int(pct) if pct.isdigit() else None,
42
+ "remarks": c[4], "title": c[1].strip("`* "), "line_no": i}
43
+ return out
44
+
45
+
46
+ def set_cells(path, rid, status=None, pct=None, remark=None, append_remark=None):
47
+ """rewrite the Status, % and Remarks cells of one row; returns the prior cells or None"""
48
+ text = read(path)
49
+ lines = text.splitlines(keepends=True)
50
+ prior = None
51
+ for i, line in enumerate(lines):
52
+ m = ROW.match(line)
53
+ if not m or m.group(2).upper() != rid.upper():
54
+ continue
55
+ nl = "\r\n" if line.endswith("\r\n") else "\n"
56
+ c = line.rstrip("\r\n").strip().strip("|").split("|")
57
+ if len(c) < 6:
58
+ break
59
+ prior = {"status": c[2].strip().strip("`* "), "pct": c[3].strip(), "remarks": c[4].strip()}
60
+ if status is not None:
61
+ c[2] = f" {status} "
62
+ if pct is not None:
63
+ c[3] = f" {pct}% " if "%" in c[3] or c[3].strip() == "" else f" {pct} "
64
+ if remark is not None:
65
+ c[4] = f" {words(remark.replace('|', '/'))} "
66
+ elif append_remark:
67
+ c[4] = f" {words((prior['remarks'] + ' ' + append_remark.replace('|', '/')).strip())} "
68
+ lines[i] = "|" + "|".join(c) + "|" + nl
69
+ break
70
+ if prior is not None:
71
+ write(path, "".join(lines))
72
+ return prior
73
+
74
+
75
+ def next_id(text, prefix):
76
+ ids = [int(m.group(1)) for m in re.finditer(rf"REQ-{prefix}-(\d{{3}})", text)]
77
+ return f"REQ-{prefix}-{(max(ids) + 1 if ids else 1):03d}"
78
+
79
+
80
+ def add_row(path, prefix, title, acceptance, section=None, remark=None, status="Not Started", mockup=None, brd="BRD-pending"):
81
+ """append a row to the Requirements Status table and a detail entry under `section`
82
+ (a new section at the end when it does not exist); returns the new id"""
83
+ text = read(path)
84
+ rid = next_id(text, prefix)
85
+ nl = "\r\n" if "\r\n" in text else "\n"
86
+ lines = text.split(nl)
87
+ # the table: after the last REQ row
88
+ last = max((i for i, l in enumerate(lines) if ROW.match(l)), default=None)
89
+ if last is None:
90
+ raise SystemExit("no Requirements Status table with REQ rows in " + path)
91
+ remark = words((remark or f"logged from UAT {TODAY}").replace("|", "/"))
92
+ anchor = f"d-{rid.lower()}"
93
+ lines.insert(last + 1, f"| {rid} | {title.replace('|', '/')} | {status} | 0% | {remark} | [d](#{anchor}) |")
94
+ # the entry
95
+ entry = [f"- <a id=\"{anchor}\"></a> **{rid}** ({brd}) {title}" + (f" Mockup: {mockup}" if mockup else ""),
96
+ f" - *Acceptance:* {acceptance}", ""]
97
+ sec_idx = None
98
+ if section:
99
+ for i, l in enumerate(lines):
100
+ if re.match(rf"^##\s+(?:page|screen)?\s*:?\s*{re.escape(section)}\s*$", l, re.I):
101
+ sec_idx = i
102
+ break
103
+ if sec_idx is not None:
104
+ # after the last entry of that section: the line before the next "## " heading
105
+ j = sec_idx + 1
106
+ while j < len(lines) and not lines[j].startswith("## "):
107
+ j += 1
108
+ while j > sec_idx + 1 and lines[j - 1].strip() == "":
109
+ j -= 1
110
+ lines[j:j] = [""] + entry[:-1]
111
+ else:
112
+ heading = f"## {section}" if section else "## Logged from UAT"
113
+ if not any(l.strip() == heading for l in lines):
114
+ lines += ["", heading, ""]
115
+ lines += entry[:-1]
116
+ write(path, nl.join(lines).rstrip(nl) + nl)
117
+ return rid
118
+
119
+
120
+ def demote(path, rid, symptom, kind=None, evidence=None, source="owner", prefix="⚠ UAT bug"):
121
+ """Needs re-verify, % capped at 75, a dated Remark; returns the prior cells"""
122
+ r = rows(read(path)).get(rid.upper())
123
+ if not r:
124
+ return None
125
+ pct = min(r["pct"] if r["pct"] is not None else 75, 75)
126
+ ev = f"evidence: {evidence}" if evidence else f"evidence: {source} report"
127
+ remark = f"{prefix} {TODAY}: {symptom} ({ev}" + (f"; kind: {kind}" if kind else "") + ")"
128
+ return set_cells(path, rid, status="Needs re-verify", pct=pct, remark=remark)
129
+
130
+
131
+ def note(path, rid, text):
132
+ """a dated remark, no status change"""
133
+ return set_cells(path, rid, append_remark=f"{TODAY} triage: {text}")