@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.
Files changed (148) hide show
  1. package/.claude/commands/TechieFlow/agents/flow-master.md +2 -0
  2. package/.claude/commands/TechieFlow/agents/verifier.md +2 -2
  3. package/.claude/commands/TechieFlow/tasks/_develop-mode.md +34 -0
  4. package/.claude/commands/TechieFlow/tasks/_owner-language.md +1 -0
  5. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +4 -3
  6. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +2 -2
  7. package/.claude/commands/TechieFlow/tasks/amend-docs.md +2 -2
  8. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +3 -3
  9. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +5 -5
  10. package/.claude/commands/TechieFlow/tasks/develop-end-to-end.md +30 -0
  11. package/.claude/commands/TechieFlow/tasks/fix-issues.md +3 -2
  12. package/.claude/commands/TechieFlow/tasks/generate-html.md +2 -2
  13. package/.claude/commands/TechieFlow/tasks/handoff-phase.md +2 -2
  14. package/.claude/commands/TechieFlow/tasks/mockups.md +2 -2
  15. package/.claude/commands/TechieFlow/tasks/productguide.md +2 -2
  16. package/.claude/commands/TechieFlow/tasks/render-workflow-docs.md +1 -1
  17. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +2 -2
  18. package/.claude/commands/TechieFlow/tasks/triage-issues.md +2 -2
  19. package/.claude/commands/TechieFlow/tasks/verify-phase.md +12 -11
  20. package/.opencode/plugin/techieflow.js +194 -14
  21. package/.tfcore/agents/flow-master.md +2 -0
  22. package/.tfcore/agents/verifier.md +2 -2
  23. package/.tfcore/core-config.yaml +2 -1
  24. package/.tfcore/hooks/block-git.sh +4 -2
  25. package/.tfcore/hooks/guard-build.sh +5 -1
  26. package/.tfcore/hooks/guard-db.sh +112 -13
  27. package/.tfcore/hooks/guard-status-html.sh +86 -2
  28. package/.tfcore/hooks/guard-verify-deps.sh +16 -1
  29. package/.tfcore/hooks/guard-verify.sh +8 -3
  30. package/.tfcore/hooks/sweep-artifacts.sh +46 -0
  31. package/.tfcore/hooks/yolo-allow.sh +27 -0
  32. package/.tfcore/standards/blazor-zoom.cs.txt +191 -0
  33. package/.tfcore/standards/coding-standards-dotnet.md +31 -3
  34. package/.tfcore/standards/owner-words.txt +23 -0
  35. package/.tfcore/tasks/_develop-mode.md +34 -0
  36. package/.tfcore/tasks/_owner-language.md +1 -0
  37. package/.tfcore/tasks/_status-update-gate.md +4 -3
  38. package/.tfcore/tasks/_yolo-mode.md +2 -2
  39. package/.tfcore/tasks/amend-docs.md +2 -2
  40. package/.tfcore/tasks/day1-brownfield.md +3 -3
  41. package/.tfcore/tasks/day1-greenfield.md +5 -5
  42. package/.tfcore/tasks/develop-end-to-end.md +30 -0
  43. package/.tfcore/tasks/fix-issues.md +3 -2
  44. package/.tfcore/tasks/generate-html.md +2 -2
  45. package/.tfcore/tasks/handoff-phase.md +2 -2
  46. package/.tfcore/tasks/mockups.md +2 -2
  47. package/.tfcore/tasks/productguide.md +2 -2
  48. package/.tfcore/tasks/render-workflow-docs.md +1 -1
  49. package/.tfcore/tasks/triage-and-fix.md +2 -2
  50. package/.tfcore/tasks/triage-issues.md +2 -2
  51. package/.tfcore/tasks/verify-phase.md +12 -11
  52. package/.tfcore/telemetry/SCHEMA.md +10 -2
  53. package/.tfcore/telemetry/install-metrics.sh +3 -2
  54. package/.tfcore/telemetry/tf-metrics.sh +38 -2
  55. package/.tfcore/templates/stack-defaults/dotnet.md +3 -1
  56. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +6 -0
  57. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +5 -0
  58. package/.tfcore/templates/v4custom/app-brd-tmpl.md +7 -4
  59. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +11 -0
  60. package/.tfcore/templates/v4custom/app-phase-brd-tmpl.md +94 -0
  61. package/.tfcore/templates/v4custom/app-phase-uidesign-tmpl.md +60 -0
  62. package/.tfcore/templates/v4custom/app-phases-tmpl.md +2 -1
  63. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +4 -2
  64. package/.tfcore/templates/v4custom/build-subagent-prompt.md +2 -2
  65. package/.tfcore/templates/v4custom/html-render-shell.md +6 -2
  66. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  67. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  68. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  69. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  70. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  71. package/.tfcore/utils/tf-appium.mjs +179 -0
  72. package/.tfcore/utils/tf-assets-browser.mjs +96 -0
  73. package/.tfcore/utils/tf-assets.sh +97 -12
  74. package/.tfcore/utils/tf-brd-status.py +14 -5
  75. package/.tfcore/utils/tf-build-list.py +95 -12
  76. package/.tfcore/utils/tf-build-list.sh +2 -1
  77. package/.tfcore/utils/tf-build.sh +163 -15
  78. package/.tfcore/utils/tf-checklist-edit.py +21 -3
  79. package/.tfcore/utils/tf-ci-repro.py +685 -0
  80. package/.tfcore/utils/tf-ci-repro.sh +24 -0
  81. package/.tfcore/utils/tf-day1-files.py +55 -28
  82. package/.tfcore/utils/tf-develop.sh +264 -0
  83. package/.tfcore/utils/tf-devguide-list.py +53 -24
  84. package/.tfcore/utils/tf-doc-check.py +218 -26
  85. package/.tfcore/utils/tf-doc-tests.sh +116 -0
  86. package/.tfcore/utils/tf-emit.sh +132 -45
  87. package/.tfcore/utils/tf-feedback.sh +13 -0
  88. package/.tfcore/utils/tf-fix-close.sh +87 -31
  89. package/.tfcore/utils/tf-goal.sh +12 -12
  90. package/.tfcore/utils/tf-harness.sh +9 -4
  91. package/.tfcore/utils/tf-lock.sh +29 -0
  92. package/.tfcore/utils/tf-log-miss.py +34 -8
  93. package/.tfcore/utils/tf-login.mjs +167 -0
  94. package/.tfcore/utils/tf-maccatalyst-check.sh +92 -0
  95. package/.tfcore/utils/tf-menus.mjs +401 -0
  96. package/.tfcore/utils/tf-misses-md.py +10 -4
  97. package/.tfcore/utils/tf-mockup-parity.mjs +763 -71
  98. package/.tfcore/utils/tf-mockup-parity.sh +25 -2
  99. package/.tfcore/utils/tf-owner-text.py +233 -0
  100. package/.tfcore/utils/tf-owner-text.sh +14 -0
  101. package/.tfcore/utils/tf-phase.sh +38 -4
  102. package/.tfcore/utils/tf-portable.sh +230 -0
  103. package/.tfcore/utils/tf-render-html.py +79 -5
  104. package/.tfcore/utils/tf-req-ids.py +36 -0
  105. package/.tfcore/utils/tf-selfcheck.sh +39 -6
  106. package/.tfcore/utils/tf-split-brd.py +11 -1
  107. package/.tfcore/utils/tf-stack-check.sh +3 -0
  108. package/.tfcore/utils/tf-status-evidence.sh +11 -9
  109. package/.tfcore/utils/tf-status-facts.py +160 -45
  110. package/.tfcore/utils/tf-theme.mjs +189 -0
  111. package/.tfcore/utils/tf-triage.py +74 -17
  112. package/.tfcore/utils/tf-triage.sh +2 -0
  113. package/.tfcore/utils/tf-verify-boot.sh +529 -54
  114. package/.tfcore/utils/tf-verify-emit.sh +57 -17
  115. package/.tfcore/utils/tf-verify-env.sh +33 -4
  116. package/.tfcore/utils/tf-verify-list.py +290 -24
  117. package/.tfcore/utils/tf-verify-list.sh +3 -1
  118. package/.tfcore/utils/tf-verify-native.mjs +307 -0
  119. package/.tfcore/utils/tf-verify-screens.mjs +341 -44
  120. package/.tfcore/utils/tf-verify-screens.sh +23 -5
  121. package/.tfcore/utils/tf-verify-tests.sh +310 -19
  122. package/.tfcore/utils/tf-verify-verdict.py +163 -21
  123. package/.tfcore/utils/tf-verify-zoom.mjs +193 -0
  124. package/.tfcore/utils/tf-verify-zoom.sh +29 -0
  125. package/.tfcore/utils/tf-yolo-alias.sh +84 -0
  126. package/.tfcore/utils/tf-yolo-perms.py +135 -0
  127. package/.tfcore/utils/tf-yolo-start.sh +44 -0
  128. package/.tfcore/utils/tf-yolo.sh +31 -3
  129. package/.tfcore/utils/tf_defect.py +27 -0
  130. package/.tfcore/utils/tf_feedback.py +272 -0
  131. package/.tfcore/utils/tf_roadmap.py +35 -0
  132. package/.tfcore/utils/tf_stack_check.py +93 -0
  133. package/README.md +6 -2
  134. package/docs/TechieFlow-Installation.md +5 -3
  135. package/opencode.jsonc +6 -2
  136. package/package.json +1 -1
  137. package/scripts/install.mjs +13 -2
  138. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  139. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  140. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  141. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  142. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  143. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  144. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  145. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  146. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  147. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  148. 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
- self.emit('<h%d id="%s">%s<a class="anchor-link" href="#%s">#</a></h%d>'
189
- % (level, slug, inline(text), slug, level))
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
- mapped = set(re.findall(r"\*BRD:\*\s*(BRD-\d+)", existing)) | set(re.findall(r"\((BRD-\d+)\)", existing))
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):
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # tf-stack-check.sh — does the code use the packages the Architecture requires? See tf_stack_check.py.
3
+ exec python3 "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/tf_stack_check.py" "$@"
@@ -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:\s*[0-9]+' .tfcore/core-config.yaml 2>/dev/null | grep -oE '[0-9]+' || echo 1)"
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 '^\s*/' | sed 's/^\s*/- next: /' | head -2
35
- echo "- written: $(date -u -r "$ST" +%Y-%m-%dT%H:%M:%SZ)"
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
- mapfile -t NEWER < <(find src tests -type f -newer "$ST" \
43
- -not -path '*/bin/*' -not -path '*/obj/*' -not -path '*/node_modules/*' -not -path '*/.artifacts/*' 2>/dev/null \
44
- | xargs -r ls -t 2>/dev/null)
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 '^\s*\|\s*`?REQ-' "$CL" | awk -F'|' '{id=$2; st=$4; gsub(/^[ `*]+|[ `*]+$/,"",id); gsub(/^[ ]+|[ ]+$/,"",st); print id" — "st}' \
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 '^\s*\|\s*`?REQ-' "$CL" | awk -F'|' '{print $4}' | grep -ciE 'Implemented|In Progress|Needs re-verify|PARTIAL|FAIL' || true)
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"