@techierathore/techieflow 1.1.0 → 1.2.0
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.
- package/.claude/commands/TechieFlow/agents/flow-master.md +2 -0
- package/.claude/commands/TechieFlow/agents/verifier.md +2 -2
- package/.claude/commands/TechieFlow/tasks/_develop-mode.md +34 -0
- package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +7 -5
- package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +2 -2
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +1 -1
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +2 -2
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +4 -4
- package/.claude/commands/TechieFlow/tasks/develop-end-to-end.md +30 -0
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +1 -0
- package/.claude/commands/TechieFlow/tasks/mockups.md +1 -1
- package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +2 -2
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +12 -11
- package/.opencode/plugin/techieflow.js +15 -7
- package/.tfcore/agents/flow-master.md +2 -0
- package/.tfcore/agents/verifier.md +2 -2
- package/.tfcore/core-config.yaml +2 -1
- package/.tfcore/hooks/block-git.sh +25 -2
- package/.tfcore/hooks/guard-db.sh +106 -9
- package/.tfcore/hooks/guard-status-html.sh +5 -1
- package/.tfcore/hooks/guard-verify-deps.sh +16 -1
- package/.tfcore/hooks/yolo-allow.sh +27 -0
- package/.tfcore/standards/blazor-zoom.cs.txt +191 -0
- package/.tfcore/standards/coding-standards-dotnet.md +31 -3
- package/.tfcore/tasks/_develop-mode.md +34 -0
- package/.tfcore/tasks/_smoke-test-policy.md +7 -5
- package/.tfcore/tasks/_yolo-mode.md +2 -2
- package/.tfcore/tasks/amend-docs.md +1 -1
- package/.tfcore/tasks/day1-brownfield.md +2 -2
- package/.tfcore/tasks/day1-greenfield.md +4 -4
- package/.tfcore/tasks/develop-end-to-end.md +30 -0
- package/.tfcore/tasks/fix-issues.md +1 -0
- package/.tfcore/tasks/mockups.md +1 -1
- package/.tfcore/tasks/triage-and-fix.md +2 -2
- package/.tfcore/tasks/verify-phase.md +12 -11
- package/.tfcore/telemetry/SCHEMA.md +6 -2
- package/.tfcore/telemetry/tf-metrics.sh +19 -0
- package/.tfcore/templates/stack-defaults/dotnet.md +5 -1
- package/.tfcore/templates/v4custom/app-architecture-tmpl.md +5 -0
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +2 -1
- package/.tfcore/templates/v4custom/app-checklist-tmpl.md +11 -0
- package/.tfcore/templates/v4custom/app-phases-tmpl.md +2 -1
- package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +4 -1
- package/.tfcore/templates/v4custom/html-render-shell.md +2 -0
- package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/tf-appium.mjs +179 -0
- package/.tfcore/utils/tf-brd-status.py +10 -1
- package/.tfcore/utils/tf-build-list.py +16 -1
- package/.tfcore/utils/tf-build.sh +75 -0
- package/.tfcore/utils/tf-checklist-edit.py +28 -3
- package/.tfcore/utils/tf-day1-files.py +55 -28
- package/.tfcore/utils/tf-develop.sh +264 -0
- package/.tfcore/utils/tf-devguide-list.py +6 -2
- package/.tfcore/utils/tf-doc-check.py +124 -21
- package/.tfcore/utils/tf-doc-tests.sh +40 -3
- package/.tfcore/utils/tf-emit.sh +40 -10
- package/.tfcore/utils/tf-fix-close.sh +13 -7
- package/.tfcore/utils/tf-maccatalyst-check.sh +92 -0
- package/.tfcore/utils/tf-menus.mjs +401 -0
- package/.tfcore/utils/tf-mockup-parity.mjs +361 -28
- package/.tfcore/utils/tf-mockup-parity.sh +14 -3
- package/.tfcore/utils/tf-owner-text.py +27 -5
- package/.tfcore/utils/tf-phase.sh +24 -4
- package/.tfcore/utils/tf-render-html.py +19 -3
- package/.tfcore/utils/tf-req-ids.py +36 -0
- package/.tfcore/utils/tf-stack-check.sh +3 -0
- package/.tfcore/utils/tf-status-facts.py +90 -15
- package/.tfcore/utils/tf-theme.mjs +189 -0
- package/.tfcore/utils/tf-triage.py +30 -4
- package/.tfcore/utils/tf-verify-boot.sh +296 -21
- package/.tfcore/utils/tf-verify-emit.sh +37 -17
- package/.tfcore/utils/tf-verify-list.py +219 -20
- package/.tfcore/utils/tf-verify-list.sh +3 -1
- package/.tfcore/utils/tf-verify-native.mjs +307 -0
- package/.tfcore/utils/tf-verify-screens.mjs +184 -20
- package/.tfcore/utils/tf-verify-screens.sh +17 -4
- package/.tfcore/utils/tf-verify-tests.sh +107 -17
- package/.tfcore/utils/tf-verify-verdict.py +118 -15
- package/.tfcore/utils/tf-verify-zoom.mjs +193 -0
- package/.tfcore/utils/tf-verify-zoom.sh +29 -0
- package/.tfcore/utils/tf-yolo-alias.sh +84 -0
- package/.tfcore/utils/tf-yolo-perms.py +135 -0
- package/.tfcore/utils/tf-yolo-start.sh +44 -0
- package/.tfcore/utils/tf-yolo.sh +31 -3
- package/.tfcore/utils/tf_feedback.py +86 -18
- package/.tfcore/utils/tf_stack_check.py +93 -0
- package/README.md +2 -2
- package/opencode.jsonc +6 -2
- package/package.json +1 -1
- package/scripts/install.mjs +14 -3
- package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
|
@@ -39,9 +39,34 @@ EVERYTHING = re.compile(r"(?i)\beverything else is (?:now )?fixed\b|\ball (?:the
|
|
|
39
39
|
CLOSED = re.compile(r"(?i)(✅|\bclosed (?:on )?\d{4}-\d{2}-\d{2}|is \*\*closed\*\*|\bwill not fix\b|\bwont-fix\b)")
|
|
40
40
|
MERGED = re.compile(r"(?i)^\s*merged into\b")
|
|
41
41
|
FIXED_IN_BODY = re.compile(r"(?i)\bfixed upstream\b")
|
|
42
|
+
STILL_OPEN = re.compile(r"(?i)\bopen\b|\bnot (?:yet )?(?:fixed|resolved)\b|\bunfixed\b")
|
|
42
43
|
BLOCKS = re.compile(r"(?im)^\s*[-*]\s*\*\*Blocks:?\*\*:?\s*(yes|no)\b")
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
def ids_in(t):
|
|
47
|
+
"""Every id the text names, shorthand included: "TR-RAG-028/029/030" and "TR-RAG-031..034"
|
|
48
|
+
(or "031–034") name each id they cover, not only the first."""
|
|
49
|
+
out = []
|
|
50
|
+
for m in re.finditer(r"(?<![\w-])([A-Z][A-Z0-9]*(?:-[A-Z]+)*)-(\d{2,})(?!\d)((?:/\d{2,})+|(?:\.\.|…|–|—)\d{2,})?", t):
|
|
51
|
+
pre, n, tail = m.group(1), m.group(2), m.group(3) or ""
|
|
52
|
+
out.append(f"{pre}-{n}")
|
|
53
|
+
w = len(n)
|
|
54
|
+
if tail.startswith("/"):
|
|
55
|
+
out += [f"{pre}-{x.zfill(w)}" for x in tail.strip("/").split("/")]
|
|
56
|
+
elif tail:
|
|
57
|
+
hi = int(re.sub(r"^\D+", "", tail))
|
|
58
|
+
if int(n) < hi <= int(n) + 50:
|
|
59
|
+
out += [f"{pre}-{str(x).zfill(w)}" for x in range(int(n) + 1, hi + 1)]
|
|
60
|
+
return out
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def unquoted(t):
|
|
64
|
+
"""The text without fenced code, inline code and double-quoted phrases."""
|
|
65
|
+
t = re.sub(r"(?ms)^[ \t]*```.*?^[ \t]*```", "", t)
|
|
66
|
+
t = re.sub(r"`[^`\n]*`", "", t)
|
|
67
|
+
return re.sub(r"[\"“][^\"”\n]*[\"”]", "", t)
|
|
68
|
+
|
|
69
|
+
|
|
45
70
|
def entries(path):
|
|
46
71
|
"""-> [{id, title, state, since, blocks, start, end}] in file order."""
|
|
47
72
|
text = open(path, encoding="utf-8", errors="replace").read()
|
|
@@ -51,9 +76,12 @@ def entries(path):
|
|
|
51
76
|
end = heads[i + 1].start() if i + 1 < len(heads) else len(text)
|
|
52
77
|
nxt = re.search(r"(?m)^#{1,%d}\s" % len(m.group(1)), text[m.end():end])
|
|
53
78
|
body = text[m.end(): m.end() + nxt.start()] if nxt else text[m.end():end]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
# quoted or code text is somebody's words, not a mark: `fixed upstream` in a repro, a test name
|
|
80
|
+
head = unquoted(body[:2500])
|
|
81
|
+
title = unquoted(m.group(3))
|
|
82
|
+
# the mark is read in the heading as well (TechieRag TF-001: "— **fixed upstream 2026-10-03**")
|
|
83
|
+
state = ("closed" if CLOSED.search(head) or CLOSED.search(title) or MERGED.search(m.group(3))
|
|
84
|
+
else "fixed" if FIXED_IN_BODY.search(head) or FIXED_IN_BODY.search(title) else "open")
|
|
57
85
|
b = BLOCKS.search(body)
|
|
58
86
|
out.setdefault(m.group(2), {"id": m.group(2), "title": m.group(3).strip(" `"), "state": state,
|
|
59
87
|
"since": "", "blocks": b.group(1).lower() if b else "",
|
|
@@ -73,18 +101,33 @@ def reply_blocks(text):
|
|
|
73
101
|
"## Resolution status (TechieFlow team, 2026-09-09)", "## Replies from …", or a quoted
|
|
74
102
|
"> ## ✅ RESOLVED LIBRARY-SIDE 2026-08-31" — and runs to the next heading of its level,
|
|
75
103
|
or, when quoted, to the end of the quote."""
|
|
76
|
-
out = []
|
|
104
|
+
out, seen = [], set()
|
|
105
|
+
date_of = lambda h: (re.search(r"\d{4}-\d{2}-\d{2}", h) or [""])[0]
|
|
77
106
|
for m in REPLY_HEAD.finditer(text):
|
|
107
|
+
if m.start() in seen:
|
|
108
|
+
continue
|
|
78
109
|
rest = text[m.end():]
|
|
79
110
|
if m.group(1): # quoted: the block is the run of '>' lines after the heading's own line
|
|
80
111
|
rest = rest[1:] if rest.startswith("\n") else rest
|
|
81
112
|
q = re.match(r"(?:[ \t]*>[^\n]*\n?)*", rest)
|
|
82
|
-
|
|
113
|
+
out.append((date_of(m.group(0)), re.sub(r"(?m)^[ \t]*>[ \t]?", "", q.group(0))))
|
|
114
|
+
continue
|
|
115
|
+
level = len(re.match(r"[ \t]*(#+)", m.group(0)).group(1))
|
|
116
|
+
nxt = re.search(r"(?m)^#{1,%d}\s" % level, rest)
|
|
117
|
+
section = rest[: nxt.start()] if nxt else rest
|
|
118
|
+
if level == 2:
|
|
119
|
+
# A "## Replies from …" section holds dated "### " blocks, written months apart. Each is its
|
|
120
|
+
# own reply: an "all others fixed" kept in an old one must not reach entries a newer block
|
|
121
|
+
# merely names (TechieRag TF-001: Sevak's TR-RAG-047 read fixed when TR-RAG-048 was replied to).
|
|
122
|
+
subs = list(re.finditer(r"(?m)^###[ \t]+[^\n]*", section))
|
|
123
|
+
out.append((date_of(m.group(0)), section[: subs[0].start()] if subs else section))
|
|
124
|
+
for j, sm in enumerate(subs):
|
|
125
|
+
end = subs[j + 1].start() if j + 1 < len(subs) else len(section)
|
|
126
|
+
seen.add(m.end() + sm.start())
|
|
127
|
+
# the heading line stays in: "### TF-025 — fixed 2026-09-19" names the entry it answers
|
|
128
|
+
out.append((date_of(sm.group(0)) or date_of(m.group(0)), section[sm.start(): end]))
|
|
83
129
|
else:
|
|
84
|
-
|
|
85
|
-
body = rest[: nxt.start()] if nxt else rest
|
|
86
|
-
d = re.search(r"\d{4}-\d{2}-\d{2}", m.group(0))
|
|
87
|
-
out.append((d.group(0) if d else "", body))
|
|
130
|
+
out.append((date_of(m.group(0)), section))
|
|
88
131
|
return out
|
|
89
132
|
|
|
90
133
|
|
|
@@ -98,24 +141,44 @@ def replied_fixed(text, known):
|
|
|
98
141
|
items = re.split(r"\n\s*\n|\n(?=\s*[-*]\s)|\n(?=\s*\|)", body)
|
|
99
142
|
mentioned = []
|
|
100
143
|
for it in items:
|
|
101
|
-
ids =
|
|
144
|
+
ids = ids_in(it)
|
|
102
145
|
mentioned += ids
|
|
103
146
|
if not ids:
|
|
104
147
|
continue
|
|
105
148
|
if it.lstrip().startswith("|"):
|
|
106
|
-
first =
|
|
149
|
+
first = ids_in(it.split("|")[1] if it.count("|") > 1 else "")
|
|
107
150
|
out += [(i, date) for i in first]
|
|
108
|
-
elif FIXED_WORDS.search(it):
|
|
109
|
-
|
|
151
|
+
elif FIXED_WORDS.search(unquoted(it)) and not EVERYTHING.search(unquoted(it)):
|
|
152
|
+
# "Open: TR-RAG-001, TR-RAG-002. All others fixed": the ids are the exceptions.
|
|
153
|
+
# Read clause by clause: "1 OPEN: TR-RAG-001 …; 2 FIXED: TR-RAG-005 …" fixes 005 only,
|
|
154
|
+
# and "TR-RAG-035 (minor, OPEN)" beside "TR-RAG-034 (minor, FIXED)" stays open.
|
|
155
|
+
# A "fixed" clause naming no id answers the clause before it: "TR-RAG-010: every
|
|
156
|
+
# assembly carries its version. Fixed in 1.1.2." And "TR-RAG-017/019 → BRD-126 (fixed);
|
|
157
|
+
# TR-RAG-020…022 → BRD-127" fixes 017 and 019 only.
|
|
158
|
+
prev = []
|
|
159
|
+
for cl in re.split(r"(?<=[.;])\s+|\s+·\s+", it):
|
|
160
|
+
own_ids = ids_in(cl)
|
|
161
|
+
bare = unquoted(cl)
|
|
162
|
+
if FIXED_WORDS.search(bare) and not STILL_OPEN.search(bare):
|
|
163
|
+
out += [(i, date) for i in (own_ids or prev)]
|
|
164
|
+
if own_ids:
|
|
165
|
+
prev = [] if STILL_OPEN.search(bare) else own_ids
|
|
110
166
|
for it in items:
|
|
111
|
-
|
|
167
|
+
# a quoted or code "all others fixed" is somebody's words being cited, not this reply's
|
|
168
|
+
# claim: "…only because of the July "All others fixed app-side" line" fixed 13 entries the
|
|
169
|
+
# same block listed as still open (TechieRag TF-002)
|
|
170
|
+
if EVERYTHING.search(unquoted(it)) and mentioned:
|
|
171
|
+
# bounded by the ids of its own item, which it never covers ("all others"); an item
|
|
172
|
+
# naming none takes the block's. A running log in one block once let a July "all
|
|
173
|
+
# others fixed" reach every entry its later lines named (TechieRag TF-001).
|
|
174
|
+
own = ids_in(it)
|
|
112
175
|
top = {}
|
|
113
|
-
for i in mentioned:
|
|
176
|
+
for i in own or mentioned:
|
|
114
177
|
p, n = i.rsplit("-", 1)
|
|
115
178
|
top[p] = max(top.get(p, 0), int(n))
|
|
116
179
|
for k in known:
|
|
117
180
|
p, n = k.rsplit("-", 1)
|
|
118
|
-
if p in top and int(n) <= top[p]:
|
|
181
|
+
if p in top and int(n) <= top[p] and k not in own:
|
|
119
182
|
out.append((k, date))
|
|
120
183
|
return out
|
|
121
184
|
|
|
@@ -132,12 +195,17 @@ def upstream_of(path, app=None):
|
|
|
132
195
|
|
|
133
196
|
|
|
134
197
|
def state_map(root):
|
|
135
|
-
"""Every entry id across the project's feedback files -> (state, "<file>, <date>")
|
|
198
|
+
"""Every entry id across the project's feedback files -> [(state, "<file>, <date>", names)], one
|
|
199
|
+
item per file holding it. Every consumer numbers its own entries from 1, so a library with nine
|
|
200
|
+
consumer files can hold nine TR-002s (TrBlazeUI TF-006); `names` are the two project names in the
|
|
201
|
+
file's name (Lekhak and TrBlazeUI in Lekhak-TrBlazeUI-Feedback.md), which a line names to say whose."""
|
|
136
202
|
out = {}
|
|
137
203
|
for f in files(root):
|
|
138
204
|
rel = os.path.relpath(f, root).replace(os.sep, "/")
|
|
205
|
+
names = tuple(os.path.basename(f)[: -len("-Feedback.md")].split("-"))
|
|
139
206
|
for e in entries(f):
|
|
140
|
-
out
|
|
207
|
+
out.setdefault(e["id"], []).append(
|
|
208
|
+
(e["state"], f"{rel}" + (f", Resolution status {e['since']}" if e["since"] else ""), names))
|
|
141
209
|
return out
|
|
142
210
|
|
|
143
211
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""tf_stack_check.py — does the code use the packages the Architecture requires? (2026-10-08)
|
|
3
|
+
|
|
4
|
+
bash .tfcore/utils/tf-stack-check.sh <App> [--json-out <file>]
|
|
5
|
+
|
|
6
|
+
Reads the `Required packages:` line of docs/<App>-Architecture.md (Stack decisions), which copies the
|
|
7
|
+
answer set's own line: each package in backticks, or "none". Then looks for each package in the
|
|
8
|
+
project's dependency files, whatever the stack: .csproj/.fsproj/.vbproj, Directory.*.props,
|
|
9
|
+
packages.config, package.json, requirements*.txt, pyproject.toml, Pipfile, go.mod, Cargo.toml, Gemfile,
|
|
10
|
+
pom.xml, build.gradle(.kts), composer.json, pubspec.yaml. Build output, dependencies, tests/.artifacts
|
|
11
|
+
and the framework itself are not searched.
|
|
12
|
+
|
|
13
|
+
Why: in the first *develop-end-to-end proof on OpenCode the model searched for a package by the wrong
|
|
14
|
+
name, decided the mandated UI library was not published, built the screen with plain components and
|
|
15
|
+
had every row Verified. Nothing checked that the code used what the Stack decisions named.
|
|
16
|
+
|
|
17
|
+
Prints PASS (every package referenced), FAIL (with the packages missing), or NONE (no line, or
|
|
18
|
+
"none"). tf-verify-verdict.py runs it itself and fails every row's build check on FAIL.
|
|
19
|
+
Exit 0 PASS or NONE · 1 FAIL · 2 could not run. Python 3 standard library only.
|
|
20
|
+
"""
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
PRUNE = {"bin", "obj", "node_modules", ".git", ".artifacts", ".tfcore", ".claude", ".opencode", "dist",
|
|
27
|
+
"packages", "TestResults", ".venv", "venv", "target", "vendor", "OldDocs"}
|
|
28
|
+
NAMES = re.compile(r"(?i)^(?:.*\.(?:cs|fs|vb)proj|directory\.(?:packages|build)\.props|packages\.config|package\.json|"
|
|
29
|
+
r"requirements[\w.-]*\.txt|pyproject\.toml|pipfile|go\.mod|cargo\.toml|gemfile|pom\.xml|"
|
|
30
|
+
r"build\.gradle(?:\.kts)?|composer\.json|pubspec\.yaml)$")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def required(root, app):
|
|
34
|
+
"""-> (list of packages, the Architecture path) ; [] when there is no line or it says none."""
|
|
35
|
+
p = os.path.join(root, "docs", f"{app}-Architecture.md")
|
|
36
|
+
if not os.path.isfile(p):
|
|
37
|
+
return [], p
|
|
38
|
+
for line in open(p, encoding="utf-8", errors="replace"):
|
|
39
|
+
m = re.match(r"\s*(?:[-*]\s*)?\**Required packages:?\**:?\s*(.*)$", line, re.I)
|
|
40
|
+
if m:
|
|
41
|
+
return re.findall(r"`([^`\s]+)`", m.group(1)), p
|
|
42
|
+
return [], p
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def manifests(root):
|
|
46
|
+
for d, dirs, files in os.walk(root):
|
|
47
|
+
dirs[:] = [x for x in dirs if x not in PRUNE and not x.startswith(".")]
|
|
48
|
+
for f in files:
|
|
49
|
+
if NAMES.match(f):
|
|
50
|
+
yield os.path.join(d, f)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check(root, app):
|
|
54
|
+
pkgs, arch = required(root, app)
|
|
55
|
+
if not pkgs:
|
|
56
|
+
return {"verdict": "NONE", "architecture": os.path.relpath(arch, root), "required": [], "missing": [], "found": {}}
|
|
57
|
+
found = {}
|
|
58
|
+
files = list(manifests(root))
|
|
59
|
+
for f in files:
|
|
60
|
+
try:
|
|
61
|
+
text = open(f, encoding="utf-8", errors="replace").read()
|
|
62
|
+
except OSError:
|
|
63
|
+
continue
|
|
64
|
+
for pk in pkgs:
|
|
65
|
+
if re.search(r"(?<![A-Za-z0-9._-])%s(?![A-Za-z0-9._-])" % re.escape(pk), text, re.I):
|
|
66
|
+
found.setdefault(pk, os.path.relpath(f, root))
|
|
67
|
+
missing = [pk for pk in pkgs if pk not in found]
|
|
68
|
+
return {"verdict": "FAIL" if missing else "PASS", "architecture": os.path.relpath(arch, root),
|
|
69
|
+
"required": pkgs, "missing": missing, "found": found, "files_searched": len(files)}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main(argv):
|
|
73
|
+
if len(argv) < 2 or argv[1] in ("-h", "--help"):
|
|
74
|
+
print(__doc__)
|
|
75
|
+
return 0 if len(argv) >= 2 else 2
|
|
76
|
+
app = argv[1]
|
|
77
|
+
r = check(os.getcwd(), app)
|
|
78
|
+
if "--json-out" in argv:
|
|
79
|
+
out = argv[argv.index("--json-out") + 1]
|
|
80
|
+
os.makedirs(os.path.dirname(out) or ".", exist_ok=True)
|
|
81
|
+
json.dump(r, open(out, "w"), indent=1)
|
|
82
|
+
if r["verdict"] == "NONE":
|
|
83
|
+
print(f"NONE {app}: {r['architecture']} lists no required packages")
|
|
84
|
+
elif r["verdict"] == "PASS":
|
|
85
|
+
print(f"PASS {app}: " + ", ".join(f"{k} in {v}" for k, v in r["found"].items()))
|
|
86
|
+
else:
|
|
87
|
+
print(f"FAIL {app}: the Architecture requires {', '.join(r['missing'])}; no project file references "
|
|
88
|
+
+ ("it" if len(r["missing"]) == 1 else "them") + f" ({r['files_searched']} searched). Add the package; never replace it with a workaround")
|
|
89
|
+
return 1 if r["verdict"] == "FAIL" else 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
sys.exit(main(sys.argv))
|
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ flowchart TD
|
|
|
86
86
|
|
|
87
87
|
**An existing application** is the same after day-1: `*day1-brownfield` reads the code, writes the same documents plus the DevGuide, and migrates any existing plan into the one checklist.
|
|
88
88
|
|
|
89
|
-
**Which command next?** Pick by the weakest open row: anything unbuilt means `*build-phase`; all built but not verified means `*verify all`; all verified means `*handoff-phase`. When in doubt, build. If a session died mid-phase, the status file is stale — run `*refresh-status {App}` first, which rebuilds it from the checklist, the working tree and a fresh build.
|
|
89
|
+
**Which command next?** Pick by the weakest open row: anything unbuilt means `*build-phase`; all built but not verified means `*verify all`; all verified means `*handoff-phase`. When in doubt, build. A project in phases verifies one phase at a time: `*verify all {App} --phase 1` grades phase 1's checklist, and `--phase all` grades every phase in turn. Without `--phase` it grades the phase in progress. PROJECT-STATUS prints the exact line to paste. If a session died mid-phase, the status file is stale — run `*refresh-status {App}` first, which rebuilds it from the checklist, the working tree and a fresh build.
|
|
90
90
|
|
|
91
91
|
**Running unattended.** Do not start a long run as a bare harness command. The supervisor survives a usage limit, a crash and an agent that stops early:
|
|
92
92
|
|
|
@@ -110,7 +110,7 @@ Replace `{App}` with your application name. Claude Code takes the long persona p
|
|
|
110
110
|
| A change to an existing project | `… analyst *amend-docs {App} "<what changed>"` | `/flow-analyst *amend-docs {App} "…"` |
|
|
111
111
|
| Brainstorm, brief, research | `… analyst *brainstorm {topic}` · `*create-project-brief` · `*research-prompt {topic}` | same, on `/flow-analyst` |
|
|
112
112
|
| Build everything open | `/TechieFlow:agents:flow-master *build-phase {App}` | `/flow-master *build-phase {App}` |
|
|
113
|
-
| Verify | `/TechieFlow:agents:verifier *verify ui\|functional\|all` | `/flow-verifier *verify all` |
|
|
113
|
+
| Verify | `/TechieFlow:agents:verifier *verify ui\|functional\|all {App} [--phase N\|all]` | `/flow-verifier *verify all {App} [--phase N\|all]` |
|
|
114
114
|
| Bugs: log and analyse only | `… flow-master *triage-issues {App} {evidence} [verify]` | `/flow-master *triage-issues …` |
|
|
115
115
|
| Bugs: fix them | `… flow-master *fix-issues {App} {folder}` | `/flow-master *fix-issues …` |
|
|
116
116
|
| Bugs: the whole sequence unattended | `… flow-master *triage-and-fix {App} {evidence}` | `/flow-master *triage-and-fix …` |
|
package/opencode.jsonc
CHANGED
|
@@ -589,7 +589,7 @@
|
|
|
589
589
|
"sudo *": "ask"
|
|
590
590
|
}
|
|
591
591
|
},
|
|
592
|
-
"description": "Vidur — autonomous verification (*verify ui|functional|all); writes verdicts into the checklist Requirements Status tables"
|
|
592
|
+
"description": "Vidur — autonomous verification (*verify ui|functional|all [--phase N|all]); writes verdicts into the checklist Requirements Status tables"
|
|
593
593
|
},
|
|
594
594
|
"trblazeui": {
|
|
595
595
|
"prompt": "You are the TrBlazeUI sub-agent. Read `.opencode/command/trblazeui.md` (fall back, in order, to `.claude/commands/trblazeui.md`, `.claude/trblazeui.md`, `.trblazeui/TrBlazeUI-AI-Reference.md`) and fully adopt that persona and its operating guidelines — it is the authoritative persona. NuGet credentials are user-level (Windows native `%AppData%\\NuGet\\NuGet.Config`, macOS/Linux `$HOME/.nuget/NuGet/NuGet.Config`, Docker `/root/.nuget/NuGet/NuGet.Config` from `%USERPROFILE%\\.opencode-docker\\nuget\\NuGet.Config`); never put a PAT in the repository. In Docker, never assume a Windows config is decryptable and never install `maui-tizen`; verify the mounted config and use the Docker build ladder. If neither file exists (TrBlazeUI NuGet not yet deployed), report the exact source/auth failure instead of blindly running `dotnet build`. Otherwise act as an expert .NET/Blazor developer specializing in the TrBlazeUI component library: build UI screens to match the approved mockups (`docs/{AppName}-UIDesign.md` + `docs/mockups/*.html`), follow `docs/{AppName}-Coding-Standards.md`, tag work with `[REQ-UI-N]` in the checklist Remarks, log TrBlazeUI gaps to `docs/{AppName}-TrBlazeUI-Feedback.md`. Never run git or gh.",
|
|
@@ -833,6 +833,10 @@
|
|
|
833
833
|
"template": "{file:./.tfcore/tasks/split-brd.md}",
|
|
834
834
|
"description": "Split the BRD into the one docs/{App}-Checklist.md (REQ-UI/FN/RAG/NFR-* in a single Requirements Status table)."
|
|
835
835
|
},
|
|
836
|
+
"techieflow:tasks:develop-end-to-end": {
|
|
837
|
+
"template": "{file:./.tfcore/tasks/develop-end-to-end.md}",
|
|
838
|
+
"description": "A new app from a brief to UAT, unattended: Day 1 both stages, build with inline verify and fixes, handoff; tf-develop.sh commits and pushes after each phase and writes the Build Report with time and tokens per phase. Driven by /flow-master."
|
|
839
|
+
},
|
|
836
840
|
"techieflow:tasks:build-phase": {
|
|
837
841
|
"template": "{file:./.tfcore/tasks/build-phase.md}",
|
|
838
842
|
"description": "The single unified build: implement every open REQ (UI/FN/RAG/NFR) from the one Checklist by clustering and calling /trblazeui (from mockups) + /techierag as sub-agents; self-smoke (data + visual); chain the verifier. Driven by /flow-master."
|
|
@@ -855,7 +859,7 @@
|
|
|
855
859
|
},
|
|
856
860
|
"techieflow:tasks:verify-phase": {
|
|
857
861
|
"template": "{file:./.tfcore/tasks/verify-phase.md}",
|
|
858
|
-
"description": "Verifies every checklist row in a scope (ui | functional | all | REQ list) against the running app: boots it, runs the seven checks in order through the tf-verify-* scripts, writes the verdicts into the checklist and the telemetry. YOLO by default. Driven by /verifier."
|
|
862
|
+
"description": "Verifies every checklist row in a scope (ui | functional | all | REQ list) of one phase's checklist, or each phase in turn with --phase all, against the running app: boots it, runs the seven checks in order through the tf-verify-* scripts, writes the verdicts into the checklist and the telemetry. YOLO by default. Driven by /verifier."
|
|
859
863
|
},
|
|
860
864
|
"techieflow:tasks:handoff-phase": {
|
|
861
865
|
"template": "{file:./.tfcore/tasks/handoff-phase.md}",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techierathore/techieflow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "TechieFlow: a spec-driven development framework for AI coding agents, for Claude Code and OpenCode. One command installs it into any project.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
package/scripts/install.mjs
CHANGED
|
@@ -313,7 +313,7 @@ const canonicalSettings = String.raw`{
|
|
|
313
313
|
],
|
|
314
314
|
"ask": [],
|
|
315
315
|
"deny": [
|
|
316
|
-
"Bash(rm -rf /)", "Bash(rm -rf
|
|
316
|
+
"Bash(rm -rf /)", "Bash(rm -rf ~)",
|
|
317
317
|
"Bash(git commit*)", "Bash(git push*)", "Bash(git add*)", "Bash(git rm*)",
|
|
318
318
|
"Bash(git mv*)", "Bash(git reset*)", "Bash(git checkout*)", "Bash(git switch*)",
|
|
319
319
|
"Bash(git restore*)", "Bash(git merge*)", "Bash(git rebase*)", "Bash(git cherry-pick*)",
|
|
@@ -392,6 +392,15 @@ const canonicalSettings = String.raw`{
|
|
|
392
392
|
"command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/guard-verify.sh\""
|
|
393
393
|
}
|
|
394
394
|
]
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"matcher": "*",
|
|
398
|
+
"hooks": [
|
|
399
|
+
{
|
|
400
|
+
"type": "command",
|
|
401
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.tfcore/hooks/yolo-allow.sh\""
|
|
402
|
+
}
|
|
403
|
+
]
|
|
395
404
|
}
|
|
396
405
|
],
|
|
397
406
|
"SessionStart": [
|
|
@@ -723,7 +732,9 @@ function migrateLegacyLayout() {
|
|
|
723
732
|
const rootConfig = join(target, "opencode.jsonc");
|
|
724
733
|
if (existsSync(rootConfig) && legacyConfigPattern.test(read(rootConfig))) {
|
|
725
734
|
copyFileSync(rootConfig, `${rootConfig}.bak`);
|
|
726
|
-
|
|
735
|
+
// the content only, as the shell's `cp` onto an existing file does: copyFileSync also copies the
|
|
736
|
+
// template's mode, so an executable checkout of opencode.jsonc gave a different file (CI, 2026-10-04)
|
|
737
|
+
writeFileSync(rootConfig, readFileSync(join(sourceRoot, "opencode.jsonc")));
|
|
727
738
|
say(" opencode.jsonc — legacy version backed up to opencode.jsonc.bak, replaced with current template");
|
|
728
739
|
}
|
|
729
740
|
say("");
|
|
@@ -742,7 +753,7 @@ function refreshRootOpenCodeConfig() {
|
|
|
742
753
|
if (dryRun) say(" opencode.jsonc — WOULD refresh from template (no project-only content; old file → .bak)");
|
|
743
754
|
else {
|
|
744
755
|
copyFileSync(rootConfig, `${rootConfig}.bak`);
|
|
745
|
-
|
|
756
|
+
writeFileSync(rootConfig, readFileSync(join(sourceRoot, "opencode.jsonc"))); // keeps its mode, as `cp` does
|
|
746
757
|
say(" opencode.jsonc — refreshed from template (no project-only content; old file → opencode.jsonc.bak)");
|
|
747
758
|
replaced = true;
|
|
748
759
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|