@techierathore/techieflow 1.0.4 → 1.1.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/tasks/_owner-language.md +1 -0
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +4 -3
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +1 -1
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +1 -1
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +1 -1
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +2 -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 +1 -1
- 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-issues.md +2 -2
- package/.opencode/plugin/techieflow.js +179 -7
- package/.tfcore/hooks/block-git.sh +4 -2
- package/.tfcore/hooks/guard-build.sh +5 -1
- package/.tfcore/hooks/guard-db.sh +6 -4
- package/.tfcore/hooks/guard-status-html.sh +86 -2
- package/.tfcore/hooks/guard-verify.sh +8 -3
- package/.tfcore/hooks/sweep-artifacts.sh +46 -0
- package/.tfcore/standards/owner-words.txt +23 -0
- package/.tfcore/tasks/_owner-language.md +1 -0
- package/.tfcore/tasks/_status-update-gate.md +4 -3
- package/.tfcore/tasks/amend-docs.md +1 -1
- package/.tfcore/tasks/day1-brownfield.md +1 -1
- package/.tfcore/tasks/day1-greenfield.md +1 -1
- package/.tfcore/tasks/fix-issues.md +2 -2
- package/.tfcore/tasks/generate-html.md +2 -2
- package/.tfcore/tasks/handoff-phase.md +2 -2
- package/.tfcore/tasks/mockups.md +1 -1
- package/.tfcore/tasks/productguide.md +2 -2
- package/.tfcore/tasks/render-workflow-docs.md +1 -1
- package/.tfcore/tasks/triage-issues.md +2 -2
- package/.tfcore/telemetry/SCHEMA.md +4 -0
- package/.tfcore/telemetry/install-metrics.sh +3 -2
- package/.tfcore/telemetry/tf-metrics.sh +19 -2
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +6 -0
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +5 -3
- 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-uidesign-tmpl.md +4 -2
- package/.tfcore/templates/v4custom/build-subagent-prompt.md +2 -2
- package/.tfcore/templates/v4custom/html-render-shell.md +4 -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-misses-md.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-render-html.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-triage.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -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 +80 -12
- package/.tfcore/utils/tf-build-list.sh +2 -1
- package/.tfcore/utils/tf-build.sh +88 -15
- package/.tfcore/utils/tf-ci-repro.py +685 -0
- package/.tfcore/utils/tf-ci-repro.sh +24 -0
- package/.tfcore/utils/tf-devguide-list.py +47 -22
- package/.tfcore/utils/tf-doc-check.py +136 -6
- package/.tfcore/utils/tf-doc-tests.sh +79 -0
- package/.tfcore/utils/tf-emit.sh +92 -35
- package/.tfcore/utils/tf-feedback.sh +13 -0
- package/.tfcore/utils/tf-fix-close.sh +79 -29
- 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-misses-md.py +10 -4
- package/.tfcore/utils/tf-mockup-parity.mjs +427 -63
- package/.tfcore/utils/tf-mockup-parity.sh +14 -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 +15 -1
- package/.tfcore/utils/tf-portable.sh +230 -0
- package/.tfcore/utils/tf-render-html.py +60 -2
- package/.tfcore/utils/tf-selfcheck.sh +39 -6
- package/.tfcore/utils/tf-split-brd.py +11 -1
- package/.tfcore/utils/tf-status-evidence.sh +11 -9
- package/.tfcore/utils/tf-status-facts.py +72 -32
- package/.tfcore/utils/tf-triage.py +62 -16
- package/.tfcore/utils/tf-triage.sh +2 -0
- package/.tfcore/utils/tf-verify-boot.sh +263 -43
- package/.tfcore/utils/tf-verify-emit.sh +20 -0
- package/.tfcore/utils/tf-verify-env.sh +33 -4
- package/.tfcore/utils/tf-verify-list.py +75 -8
- package/.tfcore/utils/tf-verify-screens.mjs +171 -32
- package/.tfcore/utils/tf-verify-screens.sh +8 -3
- package/.tfcore/utils/tf-verify-tests.sh +215 -14
- package/.tfcore/utils/tf-verify-verdict.py +48 -9
- package/.tfcore/utils/tf_defect.py +27 -0
- package/.tfcore/utils/tf_feedback.py +209 -0
- package/.tfcore/utils/tf_roadmap.py +35 -0
- package/README.md +4 -0
- package/docs/TechieFlow-Installation.md +5 -3
- package/package.json +1 -1
|
@@ -7,10 +7,11 @@ Reads the phase's checklist (appPhase in core-config.yaml, or --phase) and print
|
|
|
7
7
|
- the mode: FIX (rows FAIL / PARTIAL / In Progress / Needs re-verify exist, and they
|
|
8
8
|
come FIRST in the list), FRESH (nothing failing), or NOTHING (every row terminal or
|
|
9
9
|
Blocked). The working list is every open row in both FIX and FRESH: the mode says
|
|
10
|
-
what to build first, never what to leave out.
|
|
10
|
+
what to build first, never what to leave out. A roadmap row (tf_roadmap.py) is not
|
|
11
|
+
open: it is counted and named apart, never built.
|
|
11
12
|
- the working list, grouped into clusters by the checklist's page section, each
|
|
12
|
-
naming its builder from the row prefix: REQ-UI -> trblazeui
|
|
13
|
-
REQ-FN / REQ-NFR -> builder
|
|
13
|
+
naming its builder from the row prefix: REQ-UI -> trblazeui when the project uses
|
|
14
|
+
TrBlazeUI (else builder), REQ-RAG -> techierag, REQ-FN / REQ-NFR -> builder
|
|
14
15
|
- with --prompts, one ready sub-agent prompt per cluster, filled from
|
|
15
16
|
.tfcore/templates/v4custom/build-subagent-prompt.md with the rows, their acceptance
|
|
16
17
|
lines, their mockups and the two standing rules.
|
|
@@ -20,7 +21,9 @@ import os
|
|
|
20
21
|
import re
|
|
21
22
|
import sys
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
# norm() drops a bracketed tail ("Verified (2026-09-01)"), so "Done (pre-existing)" arrives as "done":
|
|
25
|
+
# only the bracketed spelling was listed, and AppManager's 18 finished rows went on the working list (TF-003)
|
|
26
|
+
TERMINAL = {"verified", "done", "done (pre-existing)", "n/a"}
|
|
24
27
|
FIX = {"fail", "partial", "in progress", "needs re-verify"}
|
|
25
28
|
BLOCKED = {"blocked"}
|
|
26
29
|
BUILDER = {"UI": "trblazeui", "RAG": "techierag", "FN": "builder", "NFR": "builder"}
|
|
@@ -51,6 +54,44 @@ def norm(s):
|
|
|
51
54
|
return re.sub(r"\s*\(.*$", "", s).strip()
|
|
52
55
|
|
|
53
56
|
|
|
57
|
+
def ui_builder(app):
|
|
58
|
+
"""-> (builder, why) for the UI clusters: trblazeui only when the project uses TrBlazeUI.
|
|
59
|
+
|
|
60
|
+
The framework gives every project `.trblazeui/`, whether it uses the library or not, so that
|
|
61
|
+
folder says nothing: AppManager, which does not use it, had its UI rows sent to the trblazeui
|
|
62
|
+
sub-agent (TF-008). The project's own files decide: a TrBlazeUI package or project reference in
|
|
63
|
+
a .csproj or .props. Before any such file exists (a first build), the Architecture document
|
|
64
|
+
naming it decides. tests/regression/run.sh am_008."""
|
|
65
|
+
skip = {"bin", "obj", "node_modules", ".git", ".tfcore", ".claude", ".opencode", ".trblazeui",
|
|
66
|
+
".techierag", ".artifacts"}
|
|
67
|
+
seen = 0
|
|
68
|
+
for root, dirs, files in os.walk("."):
|
|
69
|
+
dirs[:] = sorted(d for d in dirs if d not in skip)
|
|
70
|
+
for fn in sorted(files):
|
|
71
|
+
if not fn.endswith((".csproj", ".props")):
|
|
72
|
+
continue
|
|
73
|
+
seen += 1
|
|
74
|
+
p = os.path.join(root, fn)
|
|
75
|
+
if re.search(r'Include="[^"]*\bTrBlazeUI[\w.]*(?:\.csproj)?"', read(p), re.I):
|
|
76
|
+
return "trblazeui", f"{os.path.relpath(p)} references TrBlazeUI"
|
|
77
|
+
if seen:
|
|
78
|
+
return "builder", f"none of the {seen} .csproj/.props file(s) references TrBlazeUI"
|
|
79
|
+
arch = os.path.join("docs", f"{app}-Architecture.md")
|
|
80
|
+
if os.path.isfile(arch) and re.search(r"\bTrBlazeUI\b", read(arch)):
|
|
81
|
+
return "trblazeui", f"no project file yet; {arch} names TrBlazeUI"
|
|
82
|
+
return "builder", "no project file yet, and the Architecture document does not name TrBlazeUI"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def usage_guide(app):
|
|
86
|
+
"""The guide the project really has, UsageGuide or Usage-Guide: tf-verify-list.py's reader. The prompt
|
|
87
|
+
named the first spelling only, a file AppManager does not have (TF-022). tests/regression/run.sh am_022."""
|
|
88
|
+
import importlib.util
|
|
89
|
+
spec = importlib.util.spec_from_file_location("tf_verify_list", os.path.join(os.path.dirname(os.path.abspath(__file__)), "tf-verify-list.py"))
|
|
90
|
+
vl = importlib.util.module_from_spec(spec)
|
|
91
|
+
spec.loader.exec_module(vl)
|
|
92
|
+
return vl.usage_guide(app)
|
|
93
|
+
|
|
94
|
+
|
|
54
95
|
def rows_of(text):
|
|
55
96
|
rows = []
|
|
56
97
|
for line in text.splitlines():
|
|
@@ -112,9 +153,14 @@ def main(argv):
|
|
|
112
153
|
if not rows:
|
|
113
154
|
die(f"{cl} has no REQ rows")
|
|
114
155
|
ent = entries_of(text)
|
|
156
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
157
|
+
import tf_roadmap
|
|
158
|
+
import tf_defect
|
|
159
|
+
marked = tf_roadmap.ids(text)
|
|
160
|
+
roadmap = [r for r in rows if r["id"].upper() in marked and r["status"] not in TERMINAL | BLOCKED]
|
|
115
161
|
|
|
116
|
-
fix_rows = [r for r in rows if r["status"] in FIX]
|
|
117
|
-
open_rows = [r for r in rows if r["status"] not in TERMINAL | BLOCKED]
|
|
162
|
+
fix_rows = [r for r in rows if r["status"] in FIX and r not in roadmap]
|
|
163
|
+
open_rows = [r for r in rows if r["status"] not in TERMINAL | BLOCKED and r not in roadmap]
|
|
118
164
|
# Repair before new work, but never INSTEAD of it. A row can sit at `Needs re-verify`
|
|
119
165
|
# permanently -- TfLens REQ-FN-067 and -070 need a repository that emits events.ndjson
|
|
120
166
|
# and none exists, so every honest verify run re-confirms that gate rather than clearing
|
|
@@ -130,17 +176,27 @@ def main(argv):
|
|
|
130
176
|
mode, work = "FRESH", open_rows
|
|
131
177
|
else:
|
|
132
178
|
mode, work = "NOTHING", []
|
|
179
|
+
# A row in FIX mode is on the list for a defect its acceptance line rarely names; a builder given
|
|
180
|
+
# only that line finds the row working and fixes nothing (AppManager TF-020). The defect rides with
|
|
181
|
+
# the row: its ⚠ clauses, else a FAIL/PARTIAL row's whole Remarks. tests/regression/run.sh am_020.
|
|
182
|
+
for r in rows:
|
|
183
|
+
r["defects"] = (tf_defect.clauses(r["remarks"]) or ([r["remarks"]] if r["status"] in {"fail", "partial"}
|
|
184
|
+
and r["remarks"] else [])) if r in fix_rows else []
|
|
133
185
|
terminal = sum(1 for r in rows if r["status"] in TERMINAL)
|
|
134
186
|
blocked = [r for r in rows if r["status"] in BLOCKED]
|
|
135
187
|
print(f"# tf-build-list — {app} — {cl} — phase {phase}")
|
|
136
|
-
print(f"Mode: {mode} — {len(work)} row(s) to build; {terminal} terminal, {len(blocked)} Blocked,
|
|
188
|
+
print(f"Mode: {mode} — {len(work)} row(s) to build; {terminal} terminal, {len(blocked)} Blocked, "
|
|
189
|
+
f"{len(roadmap)} roadmap, {len(rows)} total"
|
|
137
190
|
+ (f" — {', '.join(r['id'] for r in work[:8])}{' …' if len(work) > 8 else ''}" if work else ""))
|
|
138
|
-
# Every row is in exactly one of the
|
|
191
|
+
# Every row is in exactly one of the five counts, and the arithmetic is printed so a
|
|
139
192
|
# reader can check it rather than trust it. A row missing from all of them is the
|
|
140
193
|
# failure this line exists to make impossible.
|
|
141
|
-
if len(work) + terminal + len(blocked) != len(rows):
|
|
142
|
-
print(f"⚠ {len(rows) - len(work) - terminal - len(blocked)} row(s) are in no category — "
|
|
194
|
+
if len(work) + terminal + len(blocked) + len(roadmap) != len(rows):
|
|
195
|
+
print(f"⚠ {len(rows) - len(work) - terminal - len(blocked) - len(roadmap)} row(s) are in no category — "
|
|
143
196
|
"the checklist carries a status this script does not know; fix the row or the script")
|
|
197
|
+
if roadmap:
|
|
198
|
+
print("Roadmap, not in this phase's scope (not built; the owner moves them with *amend-docs): "
|
|
199
|
+
+ ", ".join(r["id"] for r in roadmap))
|
|
144
200
|
if fix_rows and len(work) > len(fix_rows):
|
|
145
201
|
print(f"Order: {len(fix_rows)} failing row(s) first ({', '.join(r['id'] for r in fix_rows)}), "
|
|
146
202
|
f"then {len(work) - len(fix_rows)} not yet started")
|
|
@@ -151,10 +207,14 @@ def main(argv):
|
|
|
151
207
|
return 0
|
|
152
208
|
|
|
153
209
|
clusters = {}
|
|
210
|
+
builder = dict(BUILDER)
|
|
211
|
+
if any(r["id"].upper().startswith("REQ-UI-") for r in work):
|
|
212
|
+
builder["UI"], why = ui_builder(app)
|
|
213
|
+
print(f"UI rows go to: {builder['UI']} — {why}")
|
|
154
214
|
for r in work:
|
|
155
215
|
sec, acc, mock, brd = ent.get(r["id"].upper(), ("Other", "", "", ""))
|
|
156
216
|
cls = re.match(r"REQ-(UI|FN|RAG|NFR)-", r["id"]).group(1)
|
|
157
|
-
key = ("Non-functional" if cls == "NFR" else "RAG" if cls == "RAG" else sec,
|
|
217
|
+
key = ("Non-functional" if cls == "NFR" else "RAG" if cls == "RAG" else sec, builder[cls])
|
|
158
218
|
clusters.setdefault(key, []).append((r, acc, mock, brd))
|
|
159
219
|
print()
|
|
160
220
|
print("## Clusters (one sub-agent each; spawn them all in one turn)")
|
|
@@ -174,6 +234,8 @@ def main(argv):
|
|
|
174
234
|
print(f"- {r['id']} [{label}/{builder}] {r['title']} — {r['status']}{todo}")
|
|
175
235
|
if acc:
|
|
176
236
|
print(f" Acceptance: {acc}")
|
|
237
|
+
for x in r["defects"]:
|
|
238
|
+
print(f" Defect: {x}")
|
|
177
239
|
if mock:
|
|
178
240
|
print(f" Mockup: {mock}")
|
|
179
241
|
if not prompts:
|
|
@@ -184,18 +246,24 @@ def main(argv):
|
|
|
184
246
|
die(f"template {TPL} does not exist at its literal path")
|
|
185
247
|
tpl = read(TPL)
|
|
186
248
|
tpl = re.sub(r"<!--.*?-->\s*", "", tpl, flags=re.S)
|
|
249
|
+
guide = usage_guide(app)
|
|
187
250
|
for label, sec, builder, items in named:
|
|
188
251
|
lines = []
|
|
189
252
|
for r, acc, mock, brd in items:
|
|
190
253
|
lines.append(f"- {r['id']} — {r['title']}" + (f" ({brd})" if brd else ""))
|
|
191
254
|
lines.append(f" Acceptance: {acc or 'MISSING — refuse the row and report it'}")
|
|
255
|
+
lines.extend(f" Defect: {x}" for x in r["defects"])
|
|
192
256
|
if mock:
|
|
193
257
|
lines.append(f" Mockup: {mock}")
|
|
258
|
+
if any(r["defects"] for r, *_ in items):
|
|
259
|
+
lines.append("\nA row with a Defect line is on this list for that defect. Fix every one at the file and "
|
|
260
|
+
"line it names, keep the acceptance line true, and replace the row's Remarks: a row that "
|
|
261
|
+
"already passes its acceptance line is not done while its defect stands.")
|
|
194
262
|
mocks = sorted({m for _r, _a, m, _b in items if m})
|
|
195
263
|
body = (tpl.replace("{App}", app).replace("{Cluster}", label).replace("{Builder}", builder)
|
|
196
264
|
.replace("{Section}", sec).replace("{Rows}", "\n".join(lines))
|
|
197
265
|
.replace("{Mockups}", ", ".join(mocks) if mocks else "none (not a UI cluster)")
|
|
198
|
-
.replace("{Checklist}", cl))
|
|
266
|
+
.replace("{Checklist}", cl).replace("{UsageGuide}", guide))
|
|
199
267
|
print()
|
|
200
268
|
print(f"## Prompt for cluster {label} [{builder}]")
|
|
201
269
|
print(body.strip())
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
# PARTIAL / In Progress / Needs re-verify make it FIX mode, which puts those rows FIRST and
|
|
10
10
|
# then lists every other open row; FRESH is the same list when nothing is failing; Blocked rows
|
|
11
11
|
# pass through. Clusters follow the checklist's page sections,
|
|
12
|
-
# each with its builder from the prefix (UI -> trblazeui
|
|
12
|
+
# each with its builder from the prefix (UI -> trblazeui when the project references TrBlazeUI,
|
|
13
|
+
# else builder; RAG -> techierag; FN/NFR -> builder).
|
|
13
14
|
# Prompts are filled from .tfcore/templates/v4custom/build-subagent-prompt.md.
|
|
14
15
|
# Writes nothing. Thin wrapper over tf-build-list.py; Python 3 stdlib only.
|
|
15
16
|
set -uo pipefail
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
# bash .tfcore/utils/tf-build.sh [build] [<target>] [-- <extra dotnet args>]
|
|
7
7
|
# bash .tfcore/utils/tf-build.sh test [<target>] [-- <extra dotnet args>]
|
|
8
8
|
# bash .tfcore/utils/tf-build.sh run <project> [-- <extra dotnet args>]
|
|
9
|
+
# bash .tfcore/utils/tf-build.sh publish <project> -- -o <dir> [-c <config>] # tf-verify-boot.sh's
|
|
9
10
|
# bash .tfcore/utils/tf-build.sh probe # print the platform and the rungs, run nothing
|
|
11
|
+
# bash .tfcore/utils/tf-build.sh --print [build|test] # print the command this repository uses, run nothing
|
|
10
12
|
#
|
|
11
13
|
# <target> is a .sln, .slnx or .csproj relative to the current folder (default: the one
|
|
12
14
|
# solution or project found here). Output goes to tests/.artifacts/build/<UTC time>.log.
|
|
@@ -27,14 +29,28 @@
|
|
|
27
29
|
# A workload / SDK-missing error (NETSDK1178, Microsoft.iOS/Android/MacCatalyst.Sdk, Workload ID,
|
|
28
30
|
# WindowsAppSDK, command not found) means the wrong rung: the next one is tried, nothing is logged
|
|
29
31
|
# as a blocker. A CS#### error means the code is wrong: the rung stays and the script exits 1.
|
|
32
|
+
# A locked output file (MSB3021, MSB3027, "being used by another process") is neither: the same
|
|
33
|
+
# rung waits and tries again, then says NOT-RUN naming the lock; it never falls to the next rung
|
|
34
|
+
# (TF-035). On WSL a build that changes side -- a WSL rung, then a Windows one, over the same obj/,
|
|
35
|
+
# in this run or since the last -- first clears obj/**/scopedcss: a Blazor build names each scoped
|
|
36
|
+
# stylesheet by a hash of the path, the two sides hash differently, and the incremental target
|
|
37
|
+
# kept the old names, so every page's own styles stopped applying (TfLens, 2026-09-11).
|
|
38
|
+
# One build, test or publish at a time per repository: a second one waits for the first
|
|
39
|
+
# (tests/.artifacts/build/.lock names who holds it). Builders side by side each rebuilt the same
|
|
40
|
+
# obj/ under the others: a running app lost its stylesheet or its process, and a publish carried a
|
|
41
|
+
# dll older than its own source (TF-043). A lock whose process has gone is taken over.
|
|
30
42
|
set -u
|
|
43
|
+
# mapfile and the other bash-4 / GNU-only spellings go through the shim (a stock Mac has bash 3.2).
|
|
44
|
+
source "$(dirname "${BASH_SOURCE[0]}")/tf-portable.sh"
|
|
31
45
|
|
|
32
|
-
MODE="build"; TARGET=""; EXTRA=()
|
|
46
|
+
MODE="build"; TARGET=""; EXTRA=(); PRINT=0
|
|
47
|
+
[[ "${1:-}" == "--print" ]] && { PRINT=1; shift; } # print the resolved command, run nothing (TfLens TF-049)
|
|
33
48
|
case "${1:-}" in
|
|
34
|
-
build|test|run|probe) MODE="$1"; shift ;;
|
|
49
|
+
build|test|run|publish|probe) MODE="$1"; shift ;;
|
|
35
50
|
esac
|
|
36
51
|
while [[ $# -gt 0 ]]; do
|
|
37
52
|
if [[ "$1" == "--" ]]; then shift; EXTRA=("$@"); break; fi
|
|
53
|
+
if [[ "$1" == "--print" ]]; then PRINT=1; shift; continue; fi
|
|
38
54
|
TARGET="$1"; shift
|
|
39
55
|
done
|
|
40
56
|
|
|
@@ -48,10 +64,11 @@ elif [[ "$U" == Linux* ]]; then
|
|
|
48
64
|
fi
|
|
49
65
|
elif [[ "$U" == windows || "$U" == MINGW* || "$U" == MSYS* || "$U" == CYGWIN* ]]; then PLATFORM="windows"
|
|
50
66
|
fi
|
|
67
|
+
PLATFORM="${TF_BUILD_PLATFORM:-$PLATFORM}" # the self-tests only
|
|
51
68
|
|
|
52
69
|
# ---- target ---------------------------------------------------------------------------
|
|
53
70
|
if [[ -z "$TARGET" && "$MODE" != "probe" ]]; then
|
|
54
|
-
|
|
71
|
+
tf_read_lines CANDS < <(ls -1 *.slnx *.sln 2>/dev/null; ls -1 *.csproj 2>/dev/null)
|
|
55
72
|
if [[ ${#CANDS[@]} -eq 1 ]]; then TARGET="${CANDS[0]}"
|
|
56
73
|
elif [[ ${#CANDS[@]} -gt 1 ]]; then echo "NOT-RUN more than one solution or project here; name one: ${CANDS[*]}"; exit 2
|
|
57
74
|
fi
|
|
@@ -63,14 +80,14 @@ projects=()
|
|
|
63
80
|
if [[ -n "$TARGET" ]]; then
|
|
64
81
|
case "$TARGET" in
|
|
65
82
|
*.csproj) projects=("$TARGET") ;;
|
|
66
|
-
*.sln)
|
|
67
|
-
*.slnx)
|
|
83
|
+
*.sln) tf_read_lines projects < <(grep -oE '"[^"]+\.csproj"' "$TARGET" 2>/dev/null | tr -d '"' | tr '\\' '/') ;;
|
|
84
|
+
*.slnx) tf_read_lines projects < <(grep -oE 'Path="[^"]+\.csproj"' "$TARGET" 2>/dev/null | sed 's/Path="//; s/"$//' | tr '\\' '/') ;;
|
|
68
85
|
esac
|
|
69
86
|
base="$(dirname "$TARGET")"
|
|
70
|
-
for p in "${projects[@]}"; do
|
|
87
|
+
for p in ${projects[@]+"${projects[@]}"}; do # the +form: an empty array under set -u (bash 3.2)
|
|
71
88
|
f="$p"; [[ -f "$f" ]] || f="$base/$p"
|
|
72
89
|
[[ -f "$f" ]] || continue
|
|
73
|
-
if grep -qiE '<UseMaui
|
|
90
|
+
if grep -qiE '<UseMaui>[[:space:]]*true|Microsoft\.NET\.Sdk\.Maui|net[0-9.]+-(android|ios|maccatalyst|windows10)' "$f"; then maui=1; fi
|
|
74
91
|
done
|
|
75
92
|
fi
|
|
76
93
|
|
|
@@ -96,10 +113,53 @@ verb="$MODE"
|
|
|
96
113
|
args=("$verb")
|
|
97
114
|
[[ -n "$TARGET" ]] && args+=("$TARGET")
|
|
98
115
|
[[ "$MODE" == "run" ]] && args=("run" "--project" "$TARGET")
|
|
99
|
-
args+=("${EXTRA[@]}")
|
|
116
|
+
args+=(${EXTRA[@]+"${EXTRA[@]}"})
|
|
117
|
+
[[ $PRINT -eq 1 ]] && { echo "dotnet ${args[*]}"; exit 0; }
|
|
100
118
|
|
|
101
119
|
mkdir -p tests/.artifacts/build
|
|
102
|
-
LOG="tests/.artifacts/build/$(date -u +%Y%m%dT%H%M%SZ)-$MODE
|
|
120
|
+
LOG="tests/.artifacts/build/$(date -u +%Y%m%dT%H%M%SZ)-$MODE-$$.log" # two builds in one second never share a log
|
|
121
|
+
|
|
122
|
+
# ---- one build or browser check at a time (TF-043, TF-048): tf-lock.sh. `run` does not take it.
|
|
123
|
+
source "$(dirname "${BASH_SOURCE[0]}")/tf-lock.sh"
|
|
124
|
+
LOCK="$TF_LOCK"
|
|
125
|
+
[[ "$MODE" != "run" ]] && tf_take_lock "$MODE"
|
|
126
|
+
LOCKED='error MSB302[17]|being used by another process|Access to the path .* is denied'
|
|
127
|
+
# which side of a WSL machine a rung builds on, and the project folders whose obj/ it writes
|
|
128
|
+
side_of() { case "$1" in winrun|cmd.exe|powershell.exe) echo windows ;; *) echo wsl ;; esac; }
|
|
129
|
+
# the projects the target builds, and every project they reference: a web head's static assets come
|
|
130
|
+
# from the library it references, whose obj/ the other side may have written (AppManager TF-004)
|
|
131
|
+
tf_read_lines pdirs < <(python3 - "${base:-.}" ${projects[@]+"${projects[@]}"} <<'PY'
|
|
132
|
+
import os, re, sys
|
|
133
|
+
base, todo, seen = sys.argv[1], [], []
|
|
134
|
+
for p in sys.argv[2:]:
|
|
135
|
+
todo.append(p if os.path.isfile(p) else os.path.join(base, p))
|
|
136
|
+
while todo:
|
|
137
|
+
f = os.path.normpath(todo.pop())
|
|
138
|
+
if not os.path.isfile(f) or os.path.dirname(f) in seen:
|
|
139
|
+
continue
|
|
140
|
+
seen.append(os.path.dirname(f) or ".")
|
|
141
|
+
for ref in re.findall(r'<ProjectReference\s+Include="([^"]+)"', open(f, encoding="utf-8", errors="replace").read()):
|
|
142
|
+
todo.append(os.path.join(os.path.dirname(f), ref.replace("\\", "/")))
|
|
143
|
+
print("\n".join(seen))
|
|
144
|
+
PY
|
|
145
|
+
)
|
|
146
|
+
cross_side() { # side: clear what another side built with its own paths in it, then mark this side
|
|
147
|
+
local d cleared=0
|
|
148
|
+
for d in ${pdirs[@]+"${pdirs[@]}"}; do
|
|
149
|
+
[[ -d "$d/obj" ]] || continue
|
|
150
|
+
if [[ "$(cat "$d/obj/.tf-build-side" 2>/dev/null)" != "$1" ]]; then
|
|
151
|
+
# the scoped stylesheets (TF-035) and the static web asset lists: each side writes its own absolute
|
|
152
|
+
# paths into those, and a Windows-written one (461 C:\ paths in AppManagerUI's) stopped the WSL
|
|
153
|
+
# host at startup with "The path must be absolute (Parameter 'root')" (AppManager TF-004)
|
|
154
|
+
if [[ -n "$(find "$d/obj" -type d -name scopedcss -print -quit 2>/dev/null)" || -n "$(find "$d/obj" "$d/bin" -name '*staticwebassets*.json' -print -quit 2>/dev/null)" ]]; then
|
|
155
|
+
find "$d/obj" -type d -name scopedcss -prune -exec rm -rf {} + 2>/dev/null
|
|
156
|
+
find "$d/obj" "$d/bin" -name '*staticwebassets*.json' -delete 2>/dev/null; cleared=1
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
echo "$1" > "$d/obj/.tf-build-side"
|
|
160
|
+
done
|
|
161
|
+
[[ $cleared -eq 1 ]] && echo "note the scoped stylesheets and static asset lists were built on the other side of this machine; cleared so the $1 build writes them itself"
|
|
162
|
+
}
|
|
103
163
|
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
164
|
|
|
105
165
|
tried=()
|
|
@@ -114,9 +174,15 @@ for r in "${rungs[@]}"; do
|
|
|
114
174
|
powershell.exe) cmd=(powershell.exe -Command "dotnet ${args[*]}"); label="powershell.exe dotnet" ;;
|
|
115
175
|
esac
|
|
116
176
|
tried+=("$label")
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
177
|
+
[[ "$PLATFORM" == "wsl" ]] && cross_side "$(side_of "$r")"
|
|
178
|
+
for attempt in $(seq 0 "${TF_BUILD_LOCK_RETRIES:-2}"); do
|
|
179
|
+
from=1; [[ -f "$LOG" ]] && from=$(( $(wc -l < "$LOG") + 1 )) # the redirection itself fails on a log not yet written (TF-037)
|
|
180
|
+
{ echo "### rung $n: ${cmd[*]}"; "${cmd[@]}" 2>&1; echo "### exit $?"; } >> "$LOG" 2>&1
|
|
181
|
+
rc="$(tail -1 "$LOG" | sed 's/### exit //')"
|
|
182
|
+
seg="$(tail -n +"$from" "$LOG")" # this attempt's lines only
|
|
183
|
+
[[ "$rc" != "0" ]] && grep -qE "$LOCKED" <<<"$seg" || break
|
|
184
|
+
[[ $attempt -lt ${TF_BUILD_LOCK_RETRIES:-2} ]] && sleep "${TF_BUILD_LOCK_WAIT:-15}"
|
|
185
|
+
done
|
|
120
186
|
if [[ "$rc" == "0" ]]; then
|
|
121
187
|
warns="$(grep -cE 'warning [A-Z]+[0-9]+' <<<"$seg" || true)"
|
|
122
188
|
echo "PASS $MODE on $PLATFORM via $label (rung $n) — $warns warning line(s); log $LOG"
|
|
@@ -127,16 +193,23 @@ for r in "${rungs[@]}"; do
|
|
|
127
193
|
# taken for a wrong rung and reported NOT-RUN "host issue" (MISS-TechieFlow-20260906-03).
|
|
128
194
|
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
195
|
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
|
|
196
|
+
first="$(grep -E 'error (CS|RZ|BL|XC|XLS|MSB|NU)[0-9]+' <<<"$seg" | head -1 | sed 's/^[[:space:]]*//' | cut -c1-160)"
|
|
131
197
|
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
|
|
198
|
+
grep -E 'error (CS|RZ|BL|XC|XLS|MSB|NU)[0-9]+' <<<"$seg" | sed 's/^[[:space:]]*//' | sort -u | head -10
|
|
133
199
|
exit 1
|
|
134
200
|
fi
|
|
135
|
-
if [[ "$MODE" == "test" ]] && grep -qE 'Failed!|Failed
|
|
201
|
+
if [[ "$MODE" == "test" ]] && grep -qE 'Failed!|Failed:[[:space:]]+[1-9]|Tests? failed' <<<"$seg"; then
|
|
136
202
|
echo "FAIL tests failed on $PLATFORM via $label (rung $n); log $LOG"
|
|
137
203
|
grep -E 'Failed |Failed!|Error Message|Total tests|Passed!' <<<"$seg" | head -12
|
|
138
204
|
exit 1
|
|
139
205
|
fi
|
|
206
|
+
# a locked output file: the rung is right and the code is fine; another rung over the same obj/
|
|
207
|
+
# is exactly what broke the stylesheets (TF-035), so stop here and name the lock
|
|
208
|
+
if grep -qE "$LOCKED" <<<"$seg"; then
|
|
209
|
+
first="$(grep -E "$LOCKED" <<<"$seg" | head -1 | sed 's/^[[:space:]]*//' | cut -c1-200)"
|
|
210
|
+
echo "NOT-RUN the build output is held by a running process (still after $((${TF_BUILD_LOCK_RETRIES:-2} + 1)) tries on $label): $first. Stop that app -- bash .tfcore/utils/tf-verify-boot.sh stop --port <its port> -- and build again. Not a code error; log $LOG"
|
|
211
|
+
exit 2
|
|
212
|
+
fi
|
|
140
213
|
# anything else (workload, wrong rung, tool absent): try the next rung
|
|
141
214
|
done
|
|
142
215
|
echo "NOT-RUN no rung could $MODE on $PLATFORM; tried: ${tried[*]}. This is a host issue, never a project blocker; log $LOG"
|