@techierathore/techieflow 1.0.4 → 1.1.1
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/_owner-language.md +1 -0
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +4 -3
- package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +2 -2
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +2 -2
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +3 -3
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +5 -5
- package/.claude/commands/TechieFlow/tasks/develop-end-to-end.md +30 -0
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +3 -2
- package/.claude/commands/TechieFlow/tasks/generate-html.md +2 -2
- package/.claude/commands/TechieFlow/tasks/handoff-phase.md +2 -2
- package/.claude/commands/TechieFlow/tasks/mockups.md +2 -2
- package/.claude/commands/TechieFlow/tasks/productguide.md +2 -2
- package/.claude/commands/TechieFlow/tasks/render-workflow-docs.md +1 -1
- package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +2 -2
- package/.claude/commands/TechieFlow/tasks/triage-issues.md +2 -2
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +12 -11
- package/.opencode/plugin/techieflow.js +194 -14
- 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 +4 -2
- package/.tfcore/hooks/guard-build.sh +5 -1
- package/.tfcore/hooks/guard-db.sh +112 -13
- package/.tfcore/hooks/guard-status-html.sh +86 -2
- package/.tfcore/hooks/guard-verify-deps.sh +16 -1
- package/.tfcore/hooks/guard-verify.sh +8 -3
- package/.tfcore/hooks/sweep-artifacts.sh +46 -0
- 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/standards/owner-words.txt +23 -0
- package/.tfcore/tasks/_develop-mode.md +34 -0
- package/.tfcore/tasks/_owner-language.md +1 -0
- package/.tfcore/tasks/_status-update-gate.md +4 -3
- package/.tfcore/tasks/_yolo-mode.md +2 -2
- package/.tfcore/tasks/amend-docs.md +2 -2
- package/.tfcore/tasks/day1-brownfield.md +3 -3
- package/.tfcore/tasks/day1-greenfield.md +5 -5
- package/.tfcore/tasks/develop-end-to-end.md +30 -0
- package/.tfcore/tasks/fix-issues.md +3 -2
- package/.tfcore/tasks/generate-html.md +2 -2
- package/.tfcore/tasks/handoff-phase.md +2 -2
- package/.tfcore/tasks/mockups.md +2 -2
- package/.tfcore/tasks/productguide.md +2 -2
- package/.tfcore/tasks/render-workflow-docs.md +1 -1
- package/.tfcore/tasks/triage-and-fix.md +2 -2
- package/.tfcore/tasks/triage-issues.md +2 -2
- package/.tfcore/tasks/verify-phase.md +12 -11
- package/.tfcore/telemetry/SCHEMA.md +10 -2
- package/.tfcore/telemetry/install-metrics.sh +3 -2
- package/.tfcore/telemetry/tf-metrics.sh +38 -2
- package/.tfcore/templates/stack-defaults/dotnet.md +3 -1
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +6 -0
- package/.tfcore/templates/v4custom/app-architecture-tmpl.md +5 -0
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +7 -4
- package/.tfcore/templates/v4custom/app-checklist-tmpl.md +11 -0
- package/.tfcore/templates/v4custom/app-phase-brd-tmpl.md +94 -0
- package/.tfcore/templates/v4custom/app-phase-uidesign-tmpl.md +60 -0
- package/.tfcore/templates/v4custom/app-phases-tmpl.md +2 -1
- package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +4 -2
- package/.tfcore/templates/v4custom/build-subagent-prompt.md +2 -2
- package/.tfcore/templates/v4custom/html-render-shell.md +6 -2
- 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-assets-browser.mjs +96 -0
- package/.tfcore/utils/tf-assets.sh +97 -12
- package/.tfcore/utils/tf-brd-status.py +14 -5
- package/.tfcore/utils/tf-build-list.py +95 -12
- package/.tfcore/utils/tf-build-list.sh +2 -1
- package/.tfcore/utils/tf-build.sh +163 -15
- package/.tfcore/utils/tf-checklist-edit.py +21 -3
- package/.tfcore/utils/tf-ci-repro.py +685 -0
- package/.tfcore/utils/tf-ci-repro.sh +24 -0
- package/.tfcore/utils/tf-day1-files.py +55 -28
- package/.tfcore/utils/tf-develop.sh +264 -0
- package/.tfcore/utils/tf-devguide-list.py +53 -24
- package/.tfcore/utils/tf-doc-check.py +218 -26
- package/.tfcore/utils/tf-doc-tests.sh +116 -0
- package/.tfcore/utils/tf-emit.sh +132 -45
- package/.tfcore/utils/tf-feedback.sh +13 -0
- package/.tfcore/utils/tf-fix-close.sh +87 -31
- package/.tfcore/utils/tf-goal.sh +12 -12
- package/.tfcore/utils/tf-harness.sh +9 -4
- package/.tfcore/utils/tf-lock.sh +29 -0
- package/.tfcore/utils/tf-log-miss.py +34 -8
- package/.tfcore/utils/tf-login.mjs +167 -0
- package/.tfcore/utils/tf-maccatalyst-check.sh +92 -0
- package/.tfcore/utils/tf-menus.mjs +401 -0
- package/.tfcore/utils/tf-misses-md.py +10 -4
- package/.tfcore/utils/tf-mockup-parity.mjs +763 -71
- package/.tfcore/utils/tf-mockup-parity.sh +25 -2
- package/.tfcore/utils/tf-owner-text.py +233 -0
- package/.tfcore/utils/tf-owner-text.sh +14 -0
- package/.tfcore/utils/tf-phase.sh +38 -4
- package/.tfcore/utils/tf-portable.sh +230 -0
- package/.tfcore/utils/tf-render-html.py +79 -5
- package/.tfcore/utils/tf-req-ids.py +36 -0
- package/.tfcore/utils/tf-selfcheck.sh +39 -6
- package/.tfcore/utils/tf-split-brd.py +11 -1
- package/.tfcore/utils/tf-stack-check.sh +3 -0
- package/.tfcore/utils/tf-status-evidence.sh +11 -9
- package/.tfcore/utils/tf-status-facts.py +160 -45
- package/.tfcore/utils/tf-theme.mjs +189 -0
- package/.tfcore/utils/tf-triage.py +74 -17
- package/.tfcore/utils/tf-triage.sh +2 -0
- package/.tfcore/utils/tf-verify-boot.sh +529 -54
- package/.tfcore/utils/tf-verify-emit.sh +57 -17
- package/.tfcore/utils/tf-verify-env.sh +33 -4
- package/.tfcore/utils/tf-verify-list.py +290 -24
- 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 +341 -44
- package/.tfcore/utils/tf-verify-screens.sh +23 -5
- package/.tfcore/utils/tf-verify-tests.sh +310 -19
- package/.tfcore/utils/tf-verify-verdict.py +163 -21
- 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_defect.py +27 -0
- package/.tfcore/utils/tf_feedback.py +272 -0
- package/.tfcore/utils/tf_roadmap.py +35 -0
- package/.tfcore/utils/tf_stack_check.py +93 -0
- package/README.md +6 -2
- package/docs/TechieFlow-Installation.md +5 -3
- package/opencode.jsonc +6 -2
- package/package.json +1 -1
- package/scripts/install.mjs +13 -2
- 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
|
@@ -12,7 +12,7 @@ Usage:
|
|
|
12
12
|
Writes a sibling <file>.html for each input. Exit codes:
|
|
13
13
|
0 all rendered
|
|
14
14
|
1 a render failed
|
|
15
|
-
2 refused (checklist, or missing spec)
|
|
15
|
+
2 refused (checklist, miss log, feedback file, or missing spec)
|
|
16
16
|
|
|
17
17
|
Dependency-free by design: Python 3 standard library only. The reference machine
|
|
18
18
|
has no pandoc / python-markdown / node markdown library installed.
|
|
@@ -101,6 +101,20 @@ class Slugger(object):
|
|
|
101
101
|
self.seen[s] = c
|
|
102
102
|
return s if c == 1 else "%s-%d" % (s, c)
|
|
103
103
|
|
|
104
|
+
def github(self, text):
|
|
105
|
+
"""The id GitHub gives the same heading, which a markdown table of contents links to: digits
|
|
106
|
+
kept, punctuation dropped, spaces to hyphens, a repeat suffixed -1, -2 (AppManager TF-029:
|
|
107
|
+
`### 3.7 Group Service (GroupSvc)` is #37-group-service-groupsvc there, #group-service-groupsvc
|
|
108
|
+
here, so every numbered TOC link in the rendered guide went nowhere)."""
|
|
109
|
+
if not hasattr(self, "gh_seen"):
|
|
110
|
+
self.gh_seen = {}
|
|
111
|
+
s = re.sub(r"<[^>]+>", "", text)
|
|
112
|
+
s = re.sub(r"[*`~]", "", s).strip().lower()
|
|
113
|
+
s = re.sub(r"[^\w\- ]", "", s, flags=re.UNICODE).replace(" ", "-")
|
|
114
|
+
c = self.gh_seen.get(s, 0)
|
|
115
|
+
self.gh_seen[s] = c + 1
|
|
116
|
+
return s if c == 0 else "%s-%d" % (s, c)
|
|
117
|
+
|
|
104
118
|
|
|
105
119
|
# ----------------------------------------------------------------------------
|
|
106
120
|
# Inline conversion
|
|
@@ -111,6 +125,27 @@ RAW_TAG = re.compile(
|
|
|
111
125
|
re.I)
|
|
112
126
|
|
|
113
127
|
|
|
128
|
+
# Where the page being rendered will live, and every page this run will write (Lekhak TF-015). A
|
|
129
|
+
# relative link to a sibling .md is written as .html when that .html is there already or is written in
|
|
130
|
+
# the same run, so an index of a split guide opens the rendered pages, not raw markdown. A link to a
|
|
131
|
+
# markdown file that has no rendered twin (an agent document, a checklist) stays .md.
|
|
132
|
+
LINK_CTX = {"page_dir": None, "batch": set()}
|
|
133
|
+
MD_LINK = re.compile(r"^([^#?]+?)\.md((?:[?#].*)?)$", re.I)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def md_link_to_html(href):
|
|
137
|
+
if not LINK_CTX["page_dir"] or re.match(r"^[a-z][a-z0-9+.-]*:|^/|^#", href, re.I):
|
|
138
|
+
return href # a web link, an absolute path, an anchor
|
|
139
|
+
m = MD_LINK.match(href)
|
|
140
|
+
if not m:
|
|
141
|
+
return href
|
|
142
|
+
from urllib.parse import unquote
|
|
143
|
+
target = os.path.normpath(os.path.join(LINK_CTX["page_dir"], unquote(m.group(1)) + ".html"))
|
|
144
|
+
if os.path.isfile(target) or target in LINK_CTX["batch"]:
|
|
145
|
+
return m.group(1) + ".html" + m.group(2)
|
|
146
|
+
return href
|
|
147
|
+
|
|
148
|
+
|
|
114
149
|
def inline(text):
|
|
115
150
|
"""Markdown inline -> HTML. Code spans and deliberate raw inline HTML are
|
|
116
151
|
protected before escaping, so `<a id="d-req-ui-001"></a>` survives verbatim
|
|
@@ -136,7 +171,7 @@ def inline(text):
|
|
|
136
171
|
lambda m: '<img src="%s" alt="%s" style="max-width:100%%">'
|
|
137
172
|
% (m.group(2), m.group(1)), text)
|
|
138
173
|
text = re.sub(r"\[([^\]]+)\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)",
|
|
139
|
-
lambda m: '<a href="%s">%s</a>' % (m.group(2), m.group(1)), text)
|
|
174
|
+
lambda m: '<a href="%s">%s</a>' % (md_link_to_html(m.group(2)), m.group(1)), text)
|
|
140
175
|
text = re.sub(r"<(https?://[^>\s]+)>", r'<a href="\1">\1</a>', text)
|
|
141
176
|
|
|
142
177
|
text = re.sub(r"\*\*\*(.+?)\*\*\*", r"<strong><em>\1</em></strong>", text, flags=re.S)
|
|
@@ -184,11 +219,13 @@ class Renderer(object):
|
|
|
184
219
|
|
|
185
220
|
def heading(self, level, text):
|
|
186
221
|
slug = self.sl.slug(text)
|
|
222
|
+
gh = self.sl.github(text) # every level, so its repeat count matches GitHub's
|
|
187
223
|
if level in (2, 3, 4):
|
|
188
|
-
|
|
189
|
-
|
|
224
|
+
alias = '<a id="%s"></a>' % gh if gh and gh != slug else ""
|
|
225
|
+
self.emit('<h%d id="%s">%s%s<a class="anchor-link" href="#%s">#</a></h%d>'
|
|
226
|
+
% (level, slug, alias, inline(text), slug, level))
|
|
190
227
|
else:
|
|
191
|
-
self.emit("<h%d>%s</h%d>" % (level, inline(text), level))
|
|
228
|
+
self.emit("<h%d%s>%s</h%d>" % (level, ' id="%s"' % gh if gh else "", inline(text), level))
|
|
192
229
|
if level == 2:
|
|
193
230
|
self.h2 += 1
|
|
194
231
|
if level in (2, 3):
|
|
@@ -495,6 +532,26 @@ def is_miss_list(base, raw):
|
|
|
495
532
|
return "Rewritten by `tf-misses-md.sh`" in raw[:2000]
|
|
496
533
|
|
|
497
534
|
|
|
535
|
+
FEEDBACK_ENTRY = re.compile(r"^#{2,3}\s+[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-\d+\b", re.M)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def is_feedback_file(base, raw):
|
|
539
|
+
"""Is this an upstream feedback file, or an ordinary document about feedback?
|
|
540
|
+
|
|
541
|
+
`docs/{App}-{Upstream}-Feedback.md` is read by the upstream's agent and by the
|
|
542
|
+
maintainer who fixes it, in markdown. Nobody opens an HTML copy; the status gate's
|
|
543
|
+
"every human document this command wrote" kept producing one anyway, and an owner
|
|
544
|
+
asked more than once for it to be deleted (Chatur TF-001).
|
|
545
|
+
|
|
546
|
+
Identify the document, as the two rules above do: the name must match AND the
|
|
547
|
+
content must carry the template's `| Upstream |` header row or an entry heading
|
|
548
|
+
that opens with its id (TF-001, TR-RAG-002). A customer-feedback summary has neither."""
|
|
549
|
+
if not re.search(r"-Feedback\.md$", base, re.I):
|
|
550
|
+
return False
|
|
551
|
+
head = raw[:8000]
|
|
552
|
+
return bool(re.search(r"^\|\s*Upstream\s*\|", head, re.M) or FEEDBACK_ENTRY.search(raw))
|
|
553
|
+
|
|
554
|
+
|
|
498
555
|
NEXT_CMD_H2 = re.compile(r"^##\s+Next command to run\s*$", re.I)
|
|
499
556
|
|
|
500
557
|
|
|
@@ -561,8 +618,13 @@ def render(md_path, css, head_js, body_js, out_dir=None):
|
|
|
561
618
|
raise Refused("%s is the derived miss log — it is read in markdown by agents "
|
|
562
619
|
"and by the owner, and is NEVER rendered to HTML "
|
|
563
620
|
"(html-render-shell §0)." % base)
|
|
621
|
+
if is_feedback_file(base, raw_probe):
|
|
622
|
+
raise Refused("%s is an upstream feedback file — it is read in markdown by the "
|
|
623
|
+
"upstream's agent, and is NEVER rendered to HTML "
|
|
624
|
+
"(html-render-shell §0)." % base)
|
|
564
625
|
|
|
565
626
|
raw = raw_probe
|
|
627
|
+
LINK_CTX["page_dir"] = os.path.abspath(out_dir or os.path.dirname(os.path.abspath(md_path)))
|
|
566
628
|
lines = raw.split("\n")
|
|
567
629
|
fm, lines = frontmatter(lines)
|
|
568
630
|
|
|
@@ -702,6 +764,18 @@ def main(argv):
|
|
|
702
764
|
|
|
703
765
|
css, head_js, body_js = load_shell(spec)
|
|
704
766
|
|
|
767
|
+
# the pages this run will write, for md_link_to_html (a file the §0 ban refuses writes none)
|
|
768
|
+
LINK_CTX["batch"] = set()
|
|
769
|
+
for md in args:
|
|
770
|
+
try:
|
|
771
|
+
b = os.path.basename(md)
|
|
772
|
+
probe = open(md, encoding="utf-8").read().replace("\r\n", "\n")
|
|
773
|
+
if not (is_requirements_checklist(b, probe) or is_miss_list(b, probe) or is_feedback_file(b, probe)):
|
|
774
|
+
LINK_CTX["batch"].add(os.path.normpath(os.path.join(
|
|
775
|
+
os.path.abspath(out_dir or os.path.dirname(os.path.abspath(md))), re.sub(r"\.md$", "", b) + ".html")))
|
|
776
|
+
except OSError:
|
|
777
|
+
pass
|
|
778
|
+
|
|
705
779
|
rc = 0
|
|
706
780
|
for md in args:
|
|
707
781
|
if not os.path.isfile(md):
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""tf-req-ids.py — which rows a test's name claims (Chatur TF-012). Imported by tf-verify-tests.sh.
|
|
2
|
+
|
|
3
|
+
A test proves the ids its name STARTS with, nothing else. An xUnit theory reported as
|
|
4
|
+
`REQ-UI-048 requirement ids are found in test names(aName: "REQ-FN-053 …")` passed REQ-FN-053 and
|
|
5
|
+
REQ-FN-055, which no test had ever touched. The ids are read from the leading run of REQ tokens
|
|
6
|
+
(separated by spaces, commas, slashes, "and" …, after at most one short capital tag such as FIX or
|
|
7
|
+
UAT), before any argument list. An id named later in the name is the test's subject matter, not its
|
|
8
|
+
claim. A theory whose argument is NAMED for the row it proves (`row: "REQ-RAG-125"`, also req, reqId,
|
|
9
|
+
requirement) claims that row: TechieRag's per-row cases.
|
|
10
|
+
"""
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
ID = re.compile(r"(REQ-(?:UI|FN|NFR|RAG)-\d{3})", re.I)
|
|
14
|
+
LEAD_SEP = re.compile(r"(?:[\s,;/&+|·:\[\]–—-]|\band\b)*", re.I)
|
|
15
|
+
TAG = re.compile(r"\s*\[?[A-Z][A-Z0-9]{1,5}\]?:?\s+(?=REQ-)")
|
|
16
|
+
ROW_ARG = re.compile(r"\b(?:row|req|reqid|req_id|requirement)\s*[:=]\s*[\"']?(REQ-(?:UI|FN|NFR|RAG)-\d{3})\b", re.I)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def ids_of(name):
|
|
20
|
+
"""the ids one name claims, in order"""
|
|
21
|
+
name = str(name or "")
|
|
22
|
+
head, out = name.split("(", 1)[0], [x.upper() for x in ROW_ARG.findall(name)]
|
|
23
|
+
t = TAG.match(head)
|
|
24
|
+
p = t.end() if t else 0
|
|
25
|
+
while True:
|
|
26
|
+
p = LEAD_SEP.match(head, p).end()
|
|
27
|
+
m = ID.match(head, p)
|
|
28
|
+
if not m:
|
|
29
|
+
return out
|
|
30
|
+
out.append(m.group(1).upper())
|
|
31
|
+
p = m.end()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def mentioned(name):
|
|
35
|
+
"""every id the name carries anywhere"""
|
|
36
|
+
return {x.upper() for x in ID.findall(str(name or ""))}
|
|
@@ -117,7 +117,7 @@ else:
|
|
|
117
117
|
# TF-019: a row can sit at `Needs re-verify` forever, which pinned the mode and dropped
|
|
118
118
|
# every not-started row out of the list AND out of the counts, so a build pass could report
|
|
119
119
|
# a phase finished with rows at 0%. The arithmetic is the check: every row lands in exactly
|
|
120
|
-
# one of open / terminal / Blocked.
|
|
120
|
+
# one of open / terminal / Blocked / roadmap.
|
|
121
121
|
apps = app_names()
|
|
122
122
|
if not apps:
|
|
123
123
|
skip("tf-build-list", "no checklist in docs/ yet")
|
|
@@ -128,7 +128,7 @@ for app, phase, path in apps:
|
|
|
128
128
|
if rc not in (0, 2):
|
|
129
129
|
broken("tf-build-list", "%s: exited %d" % (label, rc), out.strip().splitlines()[0][:120] if out.strip() else "")
|
|
130
130
|
continue
|
|
131
|
-
m = re.search(r"Mode: (\w+) — (\d+) row\(s\) to build; (\d+) terminal, (\d+) Blocked, (\d+) total", out)
|
|
131
|
+
m = re.search(r"Mode: (\w+) — (\d+) row\(s\) to build; (\d+) terminal, (\d+) Blocked, (?:(\d+) roadmap, )?(\d+) total", out)
|
|
132
132
|
if not m:
|
|
133
133
|
if "has no REQ rows" in out:
|
|
134
134
|
skip("tf-build-list", "%s: checklist has no requirement rows yet" % label)
|
|
@@ -136,11 +136,11 @@ for app, phase, path in apps:
|
|
|
136
136
|
broken("tf-build-list", "%s: no counts line to check" % label,
|
|
137
137
|
"the output shape changed; the arithmetic can no longer be verified")
|
|
138
138
|
continue
|
|
139
|
-
mode, work, term, blocked, total = m.group(1), *(int(x) for x in m.groups()[1:])
|
|
140
|
-
if work + term + blocked != total:
|
|
139
|
+
mode, work, term, blocked, roadmap, total = m.group(1), *(int(x or 0) for x in m.groups()[1:])
|
|
140
|
+
if work + term + blocked + roadmap != total:
|
|
141
141
|
broken("tf-build-list",
|
|
142
|
-
"%s: %d rows are in no category (%d open + %d terminal + %d blocked ≠ %d total)"
|
|
143
|
-
% (label, total - work - term - blocked, work, term, blocked, total),
|
|
142
|
+
"%s: %d rows are in no category (%d open + %d terminal + %d blocked + %d roadmap ≠ %d total)"
|
|
143
|
+
% (label, total - work - term - blocked - roadmap, work, term, blocked, roadmap, total),
|
|
144
144
|
"a build pass trusting this list would leave those rows at 0%")
|
|
145
145
|
else:
|
|
146
146
|
ok("tf-build-list", "%s: %d rows, all accounted for (%s)" % (label, total, mode))
|
|
@@ -288,12 +288,45 @@ for name, cmd in (("tf-doc-check", ["bash", os.path.join(UTILS, "tf-doc-check.sh
|
|
|
288
288
|
first = next((l for l in out.splitlines() if l.strip()), "")
|
|
289
289
|
ok(name, "runs (exit %d)" % rc)
|
|
290
290
|
|
|
291
|
+
# ---- 8. the upstream feedback files read as they are ------------------------------------
|
|
292
|
+
# 2026-09-11: PROJECT-STATUS counted every ### heading as an entry and nothing as fixed, so
|
|
293
|
+
# TfLens read "62 open of 62" and its agent told the owner two problems fixed two days earlier
|
|
294
|
+
# were open (MISS-TechieFlow-20260911-04). The one reader is run here, on the real files, and
|
|
295
|
+
# whatever an upstream team has fixed and this project has not re-checked is named below.
|
|
296
|
+
waiting = []
|
|
297
|
+
sys.path.insert(0, UTILS)
|
|
298
|
+
try:
|
|
299
|
+
import tf_feedback
|
|
300
|
+
fb = tf_feedback.files(REPO)
|
|
301
|
+
if not fb:
|
|
302
|
+
skip("tf_feedback", "no upstream feedback files in this project")
|
|
303
|
+
else:
|
|
304
|
+
n = 0
|
|
305
|
+
for f in fb:
|
|
306
|
+
for e in tf_feedback.entries(f):
|
|
307
|
+
n += 1
|
|
308
|
+
if e["state"] == "fixed":
|
|
309
|
+
waiting.append((e["id"], e["since"], os.path.basename(f)))
|
|
310
|
+
ok("tf_feedback", "%d feedback file(s), %d entries read; %d fixed upstream, not yet re-checked"
|
|
311
|
+
% (len(fb), n, len(waiting)))
|
|
312
|
+
except Exception as e:
|
|
313
|
+
broken("tf_feedback", "crashed on this project's feedback files", str(e)[:120])
|
|
314
|
+
|
|
291
315
|
# ---- output -----------------------------------------------------------------------------
|
|
292
316
|
proj = os.path.basename(REPO.rstrip(os.sep))
|
|
293
317
|
print("# tf-selfcheck — %s — the framework's own scripts against this project's real files" % proj)
|
|
294
318
|
for l in lines:
|
|
295
319
|
print(l)
|
|
296
320
|
print()
|
|
321
|
+
if waiting:
|
|
322
|
+
# Not a defect and not a failure: work this project owes. An upstream fix counts only once it
|
|
323
|
+
# has been re-checked here; until then it must never be reported to the owner as open.
|
|
324
|
+
print("Fixed upstream, not yet re-checked here — never report these as open problems:")
|
|
325
|
+
for rid, since, f in waiting:
|
|
326
|
+
print(" %-12s fixed %-10s %s" % (rid, since or "", f))
|
|
327
|
+
print("Re-check each with its entry's \"Verify from here\" step, then close it:")
|
|
328
|
+
print(' bash .tfcore/utils/tf-feedback.sh <App> --close <ID> "<what you ran and what it showed>"')
|
|
329
|
+
print()
|
|
297
330
|
if not findings:
|
|
298
331
|
print("Every framework script works on this project. Nothing to file.")
|
|
299
332
|
raise SystemExit(0)
|
|
@@ -223,7 +223,17 @@ def run(app, phase, force, add_missing, in_order=False):
|
|
|
223
223
|
existing = read(cl_p) if os.path.isfile(cl_p) else ""
|
|
224
224
|
if existing and not force and not add_missing:
|
|
225
225
|
die(f"{cl_p} already exists; use --add-missing to append rows for new BRD items, or --force to rewrite", 1)
|
|
226
|
-
|
|
226
|
+
# A BRD item already has a row when any row names it: on its `*BRD:*` detail line, or anywhere on
|
|
227
|
+
# its status-table line. The table form used to be read only as a bracket holding exactly one id,
|
|
228
|
+
# so "(BRD-76, Phase 3)" and "(BRD-118, BRD-120, Phase 3)" were invisible and --add-missing
|
|
229
|
+
# appended 44 rows to TfLens phase 3 for 13 new items, 31 of them copies of Verified rows (TF-025).
|
|
230
|
+
mapped = {b for line in re.findall(r"(?m)^.*\*BRD:\*.*$", existing) for b in re.findall(r"BRD-\d+", line)}
|
|
231
|
+
mapped |= {b for row in re.findall(r"(?m)^\|\s*REQ-.*$", existing) for b in re.findall(r"BRD-\d+", row)}
|
|
232
|
+
# ...and on the requirement's own line in its detail entry, with the lines indented under it. A
|
|
233
|
+
# checklist migrated from an older plan names its items there, as "(BRD-1, BRD-2)" or
|
|
234
|
+
# "*(BRD-84, BRD-85)*", with no *BRD:* line: AppManager's run appended a row for all 88 items (TF-001).
|
|
235
|
+
for block in re.findall(r"(?m)^\s*[-*]\s*(?:<a id=[^>]*>\s*</a>\s*)?\*\*REQ-[A-Z]+-\d{3}\*\*.*(?:\n[ \t]+\S.*)*", existing):
|
|
236
|
+
mapped |= set(re.findall(r"BRD-\d+", block))
|
|
227
237
|
counters = other_phase_counters(app, cl_p, below=phase if in_order else None)
|
|
228
238
|
if add_missing:
|
|
229
239
|
for m in re.finditer(r"REQ-(UI|FN|RAG|NFR)-(\d{3})", existing):
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# 4. the build verdict from tf-build.sh (skipped with --no-build)
|
|
13
13
|
# Then the agent decides the interruption signature and runs tf-status-facts.sh for the gate.
|
|
14
14
|
set -uo pipefail
|
|
15
|
+
source "$(dirname "${BASH_SOURCE[0]}")/tf-portable.sh" # GNU-only commands, for a stock Mac
|
|
15
16
|
APP="${1:-}"; [[ -z "$APP" ]] && { sed -n '2,12p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 2; }
|
|
16
17
|
shift
|
|
17
18
|
BUILD=1; PHASE=""
|
|
@@ -22,7 +23,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
22
23
|
*) shift ;;
|
|
23
24
|
esac
|
|
24
25
|
done
|
|
25
|
-
[[ -z "$PHASE" ]] && PHASE="$(grep -oE '^appPhase
|
|
26
|
+
[[ -z "$PHASE" ]] && PHASE="$(grep -oE '^appPhase:[[:space:]]*[0-9]+' .tfcore/core-config.yaml 2>/dev/null | grep -oE '[0-9]+' || echo 1)"
|
|
26
27
|
[[ "$PHASE" -le 1 ]] && CL="docs/${APP}-Checklist.md" || CL="docs/${APP}-P${PHASE}-Checklist.md"
|
|
27
28
|
ST="PROJECT-STATUS.md"
|
|
28
29
|
|
|
@@ -31,19 +32,20 @@ echo
|
|
|
31
32
|
echo "## 1. What the stale status file claims (a hypothesis, not a fact)"
|
|
32
33
|
if [[ -f "$ST" ]]; then
|
|
33
34
|
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 '
|
|
35
|
-
echo "- written: $(
|
|
35
|
+
awk '/^## Next command to run/{p=1;next} /^## /{p=0} p' "$ST" | grep -E '^[[:space:]]*/' | sed 's/^[[:space:]]*/- next: /' | head -2
|
|
36
|
+
echo "- written: $(tf_date_from -u "@$(tf_stat_mtime "$ST")" +%Y-%m-%dT%H:%M:%SZ)"
|
|
36
37
|
else
|
|
37
38
|
echo "- no PROJECT-STATUS.md; treat as never written"
|
|
38
39
|
fi
|
|
39
40
|
echo
|
|
40
41
|
echo "## 2. Files changed after the status file was written (src/ and tests/, newest first)"
|
|
41
42
|
if [[ -f "$ST" ]]; then
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
# xargs runs ls only when find listed something (GNU xargs -r; BSD xargs has no -r)
|
|
44
|
+
NEWER=(); _found="$(find src tests -type f -newer "$ST" \
|
|
45
|
+
-not -path '*/bin/*' -not -path '*/obj/*' -not -path '*/node_modules/*' -not -path '*/.artifacts/*' 2>/dev/null)"
|
|
46
|
+
[[ -n "$_found" ]] && tf_read_lines NEWER < <(printf '%s\n' "$_found" | xargs ls -t 2>/dev/null)
|
|
45
47
|
echo "- ${#NEWER[@]} file(s)"
|
|
46
|
-
printf -- '- %s\n' "${NEWER[@]:0:30}"
|
|
48
|
+
printf -- '- %s\n' ${NEWER[@]+"${NEWER[@]:0:30}"}
|
|
47
49
|
[[ ${#NEWER[@]} -gt 30 ]] && echo "- (${#NEWER[@]} in total; the rest with: find src tests -type f -newer PROJECT-STATUS.md)"
|
|
48
50
|
else
|
|
49
51
|
echo "- no status file to compare against"
|
|
@@ -51,9 +53,9 @@ fi
|
|
|
51
53
|
echo
|
|
52
54
|
echo "## 3. Checklist rows that look interrupted ($CL)"
|
|
53
55
|
if [[ -f "$CL" ]]; then
|
|
54
|
-
grep -E '
|
|
56
|
+
grep -E '^[[:space:]]*\|[[:space:]]*`?REQ-' "$CL" | awk -F'|' '{id=$2; st=$4; gsub(/^[ `*]+|[ `*]+$/,"",id); gsub(/^[ ]+|[ ]+$/,"",st); print id" — "st}' \
|
|
55
57
|
| grep -iE ' — (Implemented|In Progress|Needs re-verify|PARTIAL|FAIL)' | sed 's/^/- /' | head -40
|
|
56
|
-
n=$(grep -E '
|
|
58
|
+
n=$(grep -E '^[[:space:]]*\|[[:space:]]*`?REQ-' "$CL" | awk -F'|' '{print $4}' | grep -ciE 'Implemented|In Progress|Needs re-verify|PARTIAL|FAIL' || true)
|
|
57
59
|
[[ "$n" == "0" ]] && echo "- none: every row is terminal, Blocked or not started"
|
|
58
60
|
else
|
|
59
61
|
echo "- $CL does not exist"
|