@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
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
#
|
|
4
4
|
# bash .tfcore/utils/tf-assets.sh --base http://localhost:5099 \
|
|
5
5
|
# --paths "/,/login,/export" --cookie 'AuthCookie=<value>'
|
|
6
|
+
# An app whose sign-in lives in the page and sets no cookie (AppManager TF-011):
|
|
7
|
+
# --login-path /login --user <u> --password <p> (or --storage-state <file>)
|
|
8
|
+
# opens each path in a browser that signed in through the form and grades the
|
|
9
|
+
# document it drew; `pages[].reached` says how a 401 page was reached.
|
|
10
|
+
# A desktop head (Lekhak TF-006): --cdp http://<host>:9223 in place of --base attaches to the app,
|
|
11
|
+
# opens each path by pushState and fetches every declared asset from inside the page, because
|
|
12
|
+
# nothing outside the embedded browser can reach the files it serves.
|
|
6
13
|
#
|
|
7
14
|
# WHAT IT ASKS, and it is the only question no other gate asks:
|
|
8
15
|
#
|
|
@@ -50,8 +57,10 @@
|
|
|
50
57
|
|
|
51
58
|
set -uo pipefail
|
|
52
59
|
|
|
60
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
53
61
|
BASE=""; PATHS="/"; TIMEOUT=20; LABEL=""; OUT=""; EXTERNAL=0; MINBYTES=1
|
|
54
62
|
HDRS="" # newline-separated "K: V" pairs (--header, repeatable; --cookie is sugar)
|
|
63
|
+
COOKIE=""; LOGIN_PATH=""; USER=""; PASS=""; STORAGE=""; CDP=""
|
|
55
64
|
while [[ $# -gt 0 ]]; do
|
|
56
65
|
case "$1" in
|
|
57
66
|
--base) BASE="${2:-}"; shift 2 ;;
|
|
@@ -62,18 +71,44 @@ while [[ $# -gt 0 ]]; do
|
|
|
62
71
|
--min-bytes) MINBYTES="${2:-}"; shift 2 ;;
|
|
63
72
|
--include-external) EXTERNAL=1; shift ;;
|
|
64
73
|
--header) HDRS="${HDRS}${2:-}"$'\n'; shift 2 ;;
|
|
65
|
-
--cookie) HDRS="${HDRS}Cookie: ${2:-}"$'\n'; shift 2 ;;
|
|
66
|
-
-
|
|
74
|
+
--cookie) HDRS="${HDRS}Cookie: ${2:-}"$'\n'; COOKIE="${2:-}"; shift 2 ;;
|
|
75
|
+
# a sign-in the page keeps for itself sets no cookie (AppManager TF-011): the pages are then
|
|
76
|
+
# opened in a browser that signed in through the form, the same options tf-verify-screens takes
|
|
77
|
+
--login-path) LOGIN_PATH="${2:-}"; shift 2 ;;
|
|
78
|
+
--user) USER="${2:-}"; shift 2 ;;
|
|
79
|
+
--password) PASS="${2:-}"; shift 2 ;;
|
|
80
|
+
--storage-state) STORAGE="${2:-}"; shift 2 ;;
|
|
81
|
+
--cdp) CDP="${2:-}"; shift 2 ;; # a desktop head over its DevTools port (Lekhak TF-006)
|
|
82
|
+
-h|--help) sed -n '2,56p' "$0"; exit 0 ;;
|
|
67
83
|
*) echo "unknown argument: $1" >&2; exit 3 ;;
|
|
68
84
|
esac
|
|
69
85
|
done
|
|
70
86
|
|
|
71
|
-
[[ -n "$BASE" ]] || { printf '%s\n' \
|
|
72
|
-
"usage: tf-assets.sh --base URL [--paths /,/login] [--cookie 'k=v'] [--include-external]" \
|
|
87
|
+
[[ -n "$BASE" || -n "$CDP" ]] || { printf '%s\n' \
|
|
88
|
+
"usage: tf-assets.sh --base URL|--cdp URL [--paths /,/login] [--cookie 'k=v'] [--include-external]" \
|
|
89
|
+
" [--login-path /login --user U --password P] [--storage-state file]" \
|
|
73
90
|
" [--json-out tests/.artifacts/assets/REQ-NFR-015.json]" >&2; exit 3; }
|
|
74
91
|
command -v python3 >/dev/null 2>&1 || { echo '{"status":"no-python3"}'; exit 3; }
|
|
75
92
|
|
|
76
|
-
|
|
93
|
+
# ---- the documents, through a signed-in browser when a sign-in is given (TF-011) --------------
|
|
94
|
+
DOCS=""
|
|
95
|
+
if [[ -n "$CDP" ]]; then
|
|
96
|
+
if ! command -v node >/dev/null 2>&1 || ! node -e "import('playwright').then(()=>process.exit(0)).catch(()=>process.exit(1))" 2>/dev/null; then
|
|
97
|
+
echo '{"status":"no-playwright","note":"--cdp needs node and playwright (verify-phase §1 installs the verify environment)"}'; exit 3
|
|
98
|
+
fi
|
|
99
|
+
DOCS="$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX")" && mv "$DOCS" "$DOCS.json" && DOCS="$DOCS.json"
|
|
100
|
+
node "$HERE/tf-assets-browser.mjs" --cdp "$CDP" --paths "$PATHS" --json-out "$DOCS" >&2 || { echo '{"status":"browser-failed"}'; rm -f "$DOCS"; exit 3; }
|
|
101
|
+
elif [[ -n "$LOGIN_PATH" || -n "$STORAGE" ]]; then
|
|
102
|
+
if ! command -v node >/dev/null 2>&1 || ! node -e "import('playwright').then(()=>process.exit(0)).catch(()=>process.exit(1))" 2>/dev/null; then
|
|
103
|
+
echo '{"status":"no-playwright","note":"--login-path needs node and playwright (verify-phase §1 installs the verify environment)"}'; exit 3
|
|
104
|
+
fi
|
|
105
|
+
DOCS="$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX")" && mv "$DOCS" "$DOCS.json" && DOCS="$DOCS.json" # only GNU mktemp takes a suffix option
|
|
106
|
+
node "$HERE/tf-assets-browser.mjs" --base "$BASE" --paths "$PATHS" --json-out "$DOCS" \
|
|
107
|
+
${LOGIN_PATH:+--login-path "$LOGIN_PATH"} ${USER:+--user "$USER"} ${PASS:+--password "$PASS"} \
|
|
108
|
+
${STORAGE:+--storage-state "$STORAGE"} ${COOKIE:+--cookie "$COOKIE"} >&2 || { echo '{"status":"browser-failed"}'; rm -f "$DOCS"; exit 3; }
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
TF_CDP="$CDP" TF_BASE="$BASE" TF_PATHS="$PATHS" TF_TIMEOUT="$TIMEOUT" TF_LABEL="$LABEL" TF_OUT="$OUT" TF_DOCS="$DOCS" \
|
|
77
112
|
TF_HEADERS="$HDRS" TF_EXTERNAL="$EXTERNAL" TF_MINBYTES="$MINBYTES" python3 - <<'PY'
|
|
78
113
|
import html.parser, json, os, ssl, sys, urllib.error, urllib.request
|
|
79
114
|
from urllib.parse import urljoin, urlsplit
|
|
@@ -85,6 +120,34 @@ LABEL = os.environ.get("TF_LABEL") or None
|
|
|
85
120
|
OUT = os.environ.get("TF_OUT") or None
|
|
86
121
|
EXTERNAL = os.environ.get("TF_EXTERNAL") == "1"
|
|
87
122
|
MINBYTES = int(os.environ.get("TF_MINBYTES") or 1)
|
|
123
|
+
# the documents a signed-in browser fetched, by path (TF-011); empty when none was asked for
|
|
124
|
+
DOCS = {}
|
|
125
|
+
LOGIN = None
|
|
126
|
+
if os.environ.get("TF_DOCS"):
|
|
127
|
+
try:
|
|
128
|
+
_d = json.load(open(os.environ["TF_DOCS"], encoding="utf-8"))
|
|
129
|
+
DOCS, LOGIN = _d.get("pages", {}), _d.get("login")
|
|
130
|
+
except Exception:
|
|
131
|
+
DOCS = {}
|
|
132
|
+
try:
|
|
133
|
+
os.remove(os.environ["TF_DOCS"])
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
# an attached desktop head (TF-006): the assets were fetched inside the page, by URL
|
|
137
|
+
CDP = os.environ.get("TF_CDP") or ""
|
|
138
|
+
FETCHED = {}
|
|
139
|
+
if CDP:
|
|
140
|
+
try:
|
|
141
|
+
_d
|
|
142
|
+
except NameError:
|
|
143
|
+
_d = {}
|
|
144
|
+
if _d.get("unreachable") or not DOCS:
|
|
145
|
+
print(json.dumps({"status": "unreachable", "base": CDP, "label": LABEL,
|
|
146
|
+
"note": "the desktop head's DevTools port did not answer — a `build` gate problem, not an assets result"}, indent=2))
|
|
147
|
+
raise SystemExit(2)
|
|
148
|
+
BASE = (_d.get("origin") or "").rstrip("/")
|
|
149
|
+
for _pg in DOCS.values():
|
|
150
|
+
FETCHED.update(_pg.get("fetched") or {})
|
|
88
151
|
|
|
89
152
|
HEADERS = []
|
|
90
153
|
for line in (os.environ.get("TF_HEADERS") or "").splitlines():
|
|
@@ -145,6 +208,11 @@ class AssetParser(html.parser.HTMLParser):
|
|
|
145
208
|
def fetch(url, want_body=True):
|
|
146
209
|
"""Return (status, nbytes, error). Never raises — a transport failure is a
|
|
147
210
|
finding, not a crash, and the caller needs every path's result."""
|
|
211
|
+
if CDP:
|
|
212
|
+
f = FETCHED.get(url)
|
|
213
|
+
if f is None:
|
|
214
|
+
return None, 0, "not fetched inside the attached app", b""
|
|
215
|
+
return f.get("status"), f.get("bytes", 0), f.get("error"), b""
|
|
148
216
|
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
|
149
217
|
for k, v in HEADERS:
|
|
150
218
|
req.add_header(k, v)
|
|
@@ -193,7 +261,7 @@ def fetch_page(url):
|
|
|
193
261
|
|
|
194
262
|
|
|
195
263
|
# --- reachability first: an unreachable base is a `build` problem, not an assets one
|
|
196
|
-
probe_status, _, probe_err = fetch_page(BASE + "/")
|
|
264
|
+
probe_status, _, probe_err = (200, b"", None) if CDP else fetch_page(BASE + "/")
|
|
197
265
|
if probe_status is None:
|
|
198
266
|
print(json.dumps({"status": "unreachable", "base": BASE, "error": probe_err,
|
|
199
267
|
"label": LABEL, "note": "app not reachable — this is a `build` "
|
|
@@ -207,7 +275,23 @@ graded_assets = 0
|
|
|
207
275
|
|
|
208
276
|
for p in PATHS:
|
|
209
277
|
page_url = BASE + (p if p.startswith("/") else "/" + p)
|
|
210
|
-
|
|
278
|
+
extra = {}
|
|
279
|
+
doc = DOCS.get(p if p.startswith("/") else "/" + p)
|
|
280
|
+
if doc is not None:
|
|
281
|
+
# the document as the signed-in browser drew it (TF-011). A page that stayed signed out is
|
|
282
|
+
# an auth wall: nothing of it is graded, like a redirect to the sign-in page.
|
|
283
|
+
if doc.get("signed_out"):
|
|
284
|
+
redirected += 1
|
|
285
|
+
pages.append({"path": p, "status": doc.get("document_status"), "assets": [], "declared": 0,
|
|
286
|
+
"graded": 0, "failed": 0, "redirected": True, "auth_wall": True, "url": doc.get("url")})
|
|
287
|
+
continue
|
|
288
|
+
status, body, err = (doc.get("status") or None), (doc.get("html") or "").encode("utf-8"), (doc.get("error") or None)
|
|
289
|
+
if status and status < 400 and body:
|
|
290
|
+
status = 200
|
|
291
|
+
if doc.get("reached"):
|
|
292
|
+
extra = {"document_status": doc.get("document_status"), "reached": doc["reached"]}
|
|
293
|
+
else:
|
|
294
|
+
status, body, err = fetch_page(page_url)
|
|
211
295
|
if status is None:
|
|
212
296
|
pages.append({"path": p, "status": None, "error": err, "assets": [],
|
|
213
297
|
"declared": 0, "graded": 0, "failed": 0})
|
|
@@ -269,16 +353,16 @@ for p in PATHS:
|
|
|
269
353
|
|
|
270
354
|
pages.append({"path": p, "status": status, "declared": len(rows),
|
|
271
355
|
"graded": sum(1 for r in rows if r.get("graded")),
|
|
272
|
-
"failed": failed, "assets": rows})
|
|
356
|
+
"failed": failed, "assets": rows, **extra})
|
|
273
357
|
|
|
274
358
|
# Every path turned away = nothing was graded. Reporting "0 findings" there would be
|
|
275
359
|
# a PASS on a screen the harness never saw — the same false green tf-perf.sh's exit 4
|
|
276
360
|
# exists to prevent, and the same shape as TF-011's ungradeable-looks-clean failure.
|
|
277
361
|
if PATHS and redirected == len(PATHS):
|
|
278
|
-
out = {"status": "redirected", "base": BASE, "label": LABEL, "pages": pages,
|
|
279
|
-
"findings": [], "note": "every path answered 3xx — nothing was graded. "
|
|
280
|
-
"Present a session with --cookie / --header and re-run.
|
|
281
|
-
"ASSETS-UNMEASURED (auth wall); never record this as a pass."}
|
|
362
|
+
out = {"status": "redirected", "base": BASE, "label": LABEL, "pages": pages, "login": LOGIN,
|
|
363
|
+
"findings": [], "note": "every path answered 3xx or stayed signed out — nothing was graded. "
|
|
364
|
+
"Present a session with --cookie / --header, or --login-path/--user/--password, and re-run. "
|
|
365
|
+
"Grade the REQ ASSETS-UNMEASURED (auth wall); never record this as a pass."}
|
|
282
366
|
print(json.dumps(out, indent=2))
|
|
283
367
|
if OUT:
|
|
284
368
|
os.makedirs(os.path.dirname(os.path.abspath(OUT)), exist_ok=True)
|
|
@@ -294,6 +378,7 @@ out = {
|
|
|
294
378
|
"findings_n": len(findings),
|
|
295
379
|
"findings": findings,
|
|
296
380
|
"pages": pages,
|
|
381
|
+
"login": LOGIN,
|
|
297
382
|
"include_external": EXTERNAL,
|
|
298
383
|
# An assets run that graded nothing is UNGRADEABLE, not clean. Said out loud in
|
|
299
384
|
# the payload so a consumer cannot read `findings_n: 0` as coverage.
|
|
@@ -16,8 +16,9 @@ docs/<App>-BRD.md (Screen | Requirements | Verified | Open | Status) with a
|
|
|
16
16
|
re-renders docs/<App>-BRD.html through tf-render-html.sh unless --no-render.
|
|
17
17
|
|
|
18
18
|
Status per screen: Done (every row terminal), Planned (no row started),
|
|
19
|
-
Partial (some terminal, some open), In progress (started, none terminal)
|
|
20
|
-
Terminal = Verified, Done (pre-existing)
|
|
19
|
+
Partial (some terminal, some open), In progress (started, none terminal), Not applicable
|
|
20
|
+
(every row N/A). Terminal = Verified, Done (pre-existing). An N/A row is left out of the counts,
|
|
21
|
+
never counted as verified (AppManager TF-018). Blocked is counted as open.
|
|
21
22
|
|
|
22
23
|
Python 3 standard library only. Exit 0 written or nothing to do, 2 could not run.
|
|
23
24
|
"""
|
|
@@ -27,7 +28,9 @@ import re
|
|
|
27
28
|
import subprocess
|
|
28
29
|
import sys
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
# norm() drops a bracketed tail, so "Done (pre-existing)" arrives as "done" and was counted open (AppManager TF-003)
|
|
32
|
+
TERMINAL = {"verified", "done", "done (pre-existing)"}
|
|
33
|
+
NA = {"n/a"}
|
|
31
34
|
NOT_STARTED = {"not started"}
|
|
32
35
|
|
|
33
36
|
|
|
@@ -80,6 +83,8 @@ def table(rows, screen_of):
|
|
|
80
83
|
if s not in per:
|
|
81
84
|
per[s] = {"n": 0, "v": 0, "ns": 0}
|
|
82
85
|
order.append(s)
|
|
86
|
+
if status in NA:
|
|
87
|
+
continue
|
|
83
88
|
per[s]["n"] += 1
|
|
84
89
|
per[s]["v"] += 1 if status in TERMINAL else 0
|
|
85
90
|
per[s]["ns"] += 1 if status in NOT_STARTED else 0
|
|
@@ -87,7 +92,9 @@ def table(rows, screen_of):
|
|
|
87
92
|
for s in order:
|
|
88
93
|
d = per[s]
|
|
89
94
|
open_n = d["n"] - d["v"]
|
|
90
|
-
if
|
|
95
|
+
if d["n"] == 0:
|
|
96
|
+
st = "Not applicable"
|
|
97
|
+
elif open_n == 0:
|
|
91
98
|
st = "Done"
|
|
92
99
|
elif d["ns"] == d["n"]:
|
|
93
100
|
st = "Planned"
|
|
@@ -153,7 +160,9 @@ def main(argv):
|
|
|
153
160
|
with open(brd, "w", encoding="utf-8") as f:
|
|
154
161
|
f.write(new_text)
|
|
155
162
|
verified = sum(1 for _, s in rows if s in TERMINAL)
|
|
156
|
-
|
|
163
|
+
na = sum(1 for _, s in rows if s in NA)
|
|
164
|
+
print(f"tf-brd-status: {brd_rel} Development status {how}: {screens} screens, {verified} of {len(rows) - na} "
|
|
165
|
+
f"requirements verified" + (f", {na} not applicable" if na else ""))
|
|
157
166
|
if render:
|
|
158
167
|
script = os.path.join(root, ".tfcore", "utils", "tf-render-html.sh")
|
|
159
168
|
if os.path.isfile(script):
|
|
@@ -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,11 +207,30 @@ 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}")
|
|
214
|
+
# A defect on a screen goes to one builder. Split by builder, the UI and the backend clusters of one
|
|
215
|
+
# page both carried its mockup defect, both edited the same files at once, and one left a component
|
|
216
|
+
# half-written that broke every other builder's boot for an hour (Chatur TF-004). Such a page is
|
|
217
|
+
# one cluster, built by the UI builder when one of its UI rows carries the defect.
|
|
218
|
+
cls_of = {r["id"]: re.match(r"REQ-(UI|FN|RAG|NFR)-", r["id"]).group(1) for r in work}
|
|
219
|
+
sec_of = {r["id"]: ent.get(r["id"].upper(), ("Other", "", "", ""))[0] for r in work}
|
|
220
|
+
fixed = {}
|
|
221
|
+
for r in work:
|
|
222
|
+
s, cls = sec_of[r["id"]], cls_of[r["id"]]
|
|
223
|
+
if r["defects"] and cls in ("UI", "FN") and (cls == "UI" or s not in fixed):
|
|
224
|
+
fixed[s] = builder[cls]
|
|
154
225
|
for r in work:
|
|
155
226
|
sec, acc, mock, brd = ent.get(r["id"].upper(), ("Other", "", "", ""))
|
|
156
227
|
cls = re.match(r"REQ-(UI|FN|RAG|NFR)-", r["id"]).group(1)
|
|
157
|
-
|
|
228
|
+
who = fixed[sec] if cls in ("UI", "FN") and sec in fixed else builder[cls]
|
|
229
|
+
key = ("Non-functional" if cls == "NFR" else "RAG" if cls == "RAG" else sec, who)
|
|
158
230
|
clusters.setdefault(key, []).append((r, acc, mock, brd))
|
|
231
|
+
for s in sorted(fixed):
|
|
232
|
+
if builder["UI"] != builder["FN"] and {"UI", "FN"} <= {cls_of[r["id"]] for r in work if sec_of[r["id"]] == s}:
|
|
233
|
+
print(f"One builder for {s}: it carries a defect, so its UI and backend rows go to {fixed[s]} together")
|
|
159
234
|
print()
|
|
160
235
|
print("## Clusters (one sub-agent each; spawn them all in one turn)")
|
|
161
236
|
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
@@ -174,6 +249,8 @@ def main(argv):
|
|
|
174
249
|
print(f"- {r['id']} [{label}/{builder}] {r['title']} — {r['status']}{todo}")
|
|
175
250
|
if acc:
|
|
176
251
|
print(f" Acceptance: {acc}")
|
|
252
|
+
for x in r["defects"]:
|
|
253
|
+
print(f" Defect: {x}")
|
|
177
254
|
if mock:
|
|
178
255
|
print(f" Mockup: {mock}")
|
|
179
256
|
if not prompts:
|
|
@@ -184,18 +261,24 @@ def main(argv):
|
|
|
184
261
|
die(f"template {TPL} does not exist at its literal path")
|
|
185
262
|
tpl = read(TPL)
|
|
186
263
|
tpl = re.sub(r"<!--.*?-->\s*", "", tpl, flags=re.S)
|
|
264
|
+
guide = usage_guide(app)
|
|
187
265
|
for label, sec, builder, items in named:
|
|
188
266
|
lines = []
|
|
189
267
|
for r, acc, mock, brd in items:
|
|
190
268
|
lines.append(f"- {r['id']} — {r['title']}" + (f" ({brd})" if brd else ""))
|
|
191
269
|
lines.append(f" Acceptance: {acc or 'MISSING — refuse the row and report it'}")
|
|
270
|
+
lines.extend(f" Defect: {x}" for x in r["defects"])
|
|
192
271
|
if mock:
|
|
193
272
|
lines.append(f" Mockup: {mock}")
|
|
273
|
+
if any(r["defects"] for r, *_ in items):
|
|
274
|
+
lines.append("\nA row with a Defect line is on this list for that defect. Fix every one at the file and "
|
|
275
|
+
"line it names, keep the acceptance line true, and replace the row's Remarks: a row that "
|
|
276
|
+
"already passes its acceptance line is not done while its defect stands.")
|
|
194
277
|
mocks = sorted({m for _r, _a, m, _b in items if m})
|
|
195
278
|
body = (tpl.replace("{App}", app).replace("{Cluster}", label).replace("{Builder}", builder)
|
|
196
279
|
.replace("{Section}", sec).replace("{Rows}", "\n".join(lines))
|
|
197
280
|
.replace("{Mockups}", ", ".join(mocks) if mocks else "none (not a UI cluster)")
|
|
198
|
-
.replace("{Checklist}", cl))
|
|
281
|
+
.replace("{Checklist}", cl).replace("{UsageGuide}", guide))
|
|
199
282
|
print()
|
|
200
283
|
print(f"## Prompt for cluster {label} [{builder}]")
|
|
201
284
|
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
|