@techierathore/techieflow 1.0.1 → 1.0.3

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 (178) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +0 -5
  2. package/.claude/commands/TechieFlow/agents/architect.md +0 -6
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +41 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +23 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  7. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  8. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  9. package/.claude/commands/TechieFlow/tasks/amend-docs.md +20 -143
  10. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  11. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  12. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  13. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  14. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  15. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  16. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  17. package/.claude/commands/TechieFlow/tasks/metrics-report.md +1 -1
  18. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  19. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  20. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  21. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  22. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  23. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  24. package/.opencode/plugin/techieflow.js +39 -12
  25. package/.tfcore/agents/analyst.md +0 -5
  26. package/.tfcore/agents/architect.md +0 -6
  27. package/.tfcore/agents/flow-master.md +41 -215
  28. package/.tfcore/agents/verifier.md +23 -64
  29. package/.tfcore/core-config.yaml +10 -0
  30. package/.tfcore/hooks/block-git.sh +4 -4
  31. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  32. package/.tfcore/hooks/guard-build.sh +76 -0
  33. package/.tfcore/hooks/guard-db.sh +90 -0
  34. package/.tfcore/hooks/guard-metrics.sh +56 -0
  35. package/.tfcore/hooks/guard-status-html.sh +116 -71
  36. package/.tfcore/hooks/guard-status.sh +43 -16
  37. package/.tfcore/hooks/guard-verify.sh +70 -48
  38. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  39. package/.tfcore/install-manifest.yaml +0 -18
  40. package/.tfcore/routing.yaml +25 -14
  41. package/.tfcore/standards/coding-standards-core.md +61 -0
  42. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  43. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  44. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  45. package/.tfcore/tasks/_status-update-gate.md +10 -74
  46. package/.tfcore/tasks/_yolo-mode.md +20 -58
  47. package/.tfcore/tasks/amend-docs.md +20 -143
  48. package/.tfcore/tasks/build-phase.md +14 -224
  49. package/.tfcore/tasks/day1-brownfield.md +31 -505
  50. package/.tfcore/tasks/day1-greenfield.md +28 -213
  51. package/.tfcore/tasks/deploy-checklist.md +19 -0
  52. package/.tfcore/tasks/devguide.md +10 -219
  53. package/.tfcore/tasks/fix-issues.md +10 -129
  54. package/.tfcore/tasks/log-miss.md +19 -158
  55. package/.tfcore/tasks/metrics-report.md +1 -1
  56. package/.tfcore/tasks/mockups.md +12 -117
  57. package/.tfcore/tasks/refresh-status.md +9 -130
  58. package/.tfcore/tasks/split-brd.md +10 -126
  59. package/.tfcore/tasks/triage-and-fix.md +14 -0
  60. package/.tfcore/tasks/triage-issues.md +11 -152
  61. package/.tfcore/tasks/verify-phase.md +36 -572
  62. package/.tfcore/telemetry/SCHEMA.md +43 -7
  63. package/.tfcore/telemetry/tf-metrics.sh +152 -17
  64. package/.tfcore/templates/project-brief-tmpl.yaml +1 -1
  65. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  66. package/.tfcore/templates/stack-questions.md +77 -0
  67. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  68. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  69. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  70. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  71. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  72. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  73. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  74. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  75. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  76. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  77. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  78. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  79. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  80. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  81. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  82. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  83. package/.tfcore/user-guide.md +0 -23
  84. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  85. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  86. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  87. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  88. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  89. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  90. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  91. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  92. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  93. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  94. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  95. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  96. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  97. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  98. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  99. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  100. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  101. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  102. package/.tfcore/utils/tf-brd-status.py +176 -0
  103. package/.tfcore/utils/tf-brd-status.sh +19 -0
  104. package/.tfcore/utils/tf-build-list.py +191 -0
  105. package/.tfcore/utils/tf-build-list.sh +20 -0
  106. package/.tfcore/utils/tf-build.sh +143 -0
  107. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  108. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  109. package/.tfcore/utils/tf-day1-files.py +208 -0
  110. package/.tfcore/utils/tf-day1-files.sh +20 -0
  111. package/.tfcore/utils/tf-devguide-list.py +249 -0
  112. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  113. package/.tfcore/utils/tf-doc-check.py +1267 -0
  114. package/.tfcore/utils/tf-doc-check.sh +29 -0
  115. package/.tfcore/utils/tf-emit.sh +203 -13
  116. package/.tfcore/utils/tf-fix-close.sh +83 -0
  117. package/.tfcore/utils/tf-goal.sh +193 -62
  118. package/.tfcore/utils/tf-harness.sh +4 -9
  119. package/.tfcore/utils/tf-log-miss.py +197 -0
  120. package/.tfcore/utils/tf-log-miss.sh +14 -0
  121. package/.tfcore/utils/tf-misses-md.py +211 -0
  122. package/.tfcore/utils/tf-misses-md.sh +13 -0
  123. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  124. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  125. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  126. package/.tfcore/utils/tf-perf-grade.py +97 -0
  127. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  128. package/.tfcore/utils/tf-phase.sh +74 -0
  129. package/.tfcore/utils/tf-routing-bind.sh +10 -5
  130. package/.tfcore/utils/tf-routing.sh +6 -7
  131. package/.tfcore/utils/tf-split-brd.py +304 -0
  132. package/.tfcore/utils/tf-split-brd.sh +22 -0
  133. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  134. package/.tfcore/utils/tf-status-facts.py +309 -0
  135. package/.tfcore/utils/tf-status-facts.sh +20 -0
  136. package/.tfcore/utils/tf-triage.py +243 -0
  137. package/.tfcore/utils/tf-triage.sh +16 -0
  138. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  139. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  140. package/.tfcore/utils/tf-verify-env.sh +88 -0
  141. package/.tfcore/utils/tf-verify-list.py +270 -0
  142. package/.tfcore/utils/tf-verify-list.sh +13 -0
  143. package/.tfcore/utils/tf-verify-screens.mjs +283 -0
  144. package/.tfcore/utils/tf-verify-screens.sh +22 -0
  145. package/.tfcore/utils/tf-verify-tests.sh +123 -0
  146. package/.tfcore/utils/tf-verify-verdict.py +283 -0
  147. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  148. package/.tfcore/utils/tf-yolo.sh +59 -0
  149. package/README.md +114 -1149
  150. package/docs/TechieFlow-Installation.md +27 -7
  151. package/opencode.jsonc +13 -33
  152. package/package.json +4 -7
  153. package/scripts/install.mjs +73 -46
  154. package/scripts/npm-cleanup.mjs +137 -0
  155. package/scripts/npm-postinstall.mjs +49 -0
  156. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  157. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  158. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  159. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  160. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  161. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  162. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  163. package/.codex/config.toml +0 -46
  164. package/.codex/hooks.json +0 -63
  165. package/.codex/rules/techieflow.rules +0 -8
  166. package/.tfcore/hooks/codex-adapter.py +0 -248
  167. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  168. package/.tfcore/tasks/author-brd.md +0 -79
  169. package/.tfcore/tasks/document-project.md +0 -343
  170. package/.tfcore/tasks/execute-checklist.md +0 -86
  171. package/.tfcore/tasks/index-docs.md +0 -173
  172. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  173. package/.tfcore/tasks/shard-doc.md +0 -185
  174. package/.tfcore/utils/tf-codex-bind.py +0 -196
  175. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  176. package/WORKFLOW.html +0 -1986
  177. package/scripts/test-install.mjs +0 -505
  178. package/scripts/validate.mjs +0 -163
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env bash
2
+ # tf-verify-boot.sh — start the application for a verify, reach it, and stop it (Sitting 4c, 2026-09-06).
3
+ #
4
+ # bash .tfcore/utils/tf-verify-boot.sh start [--head web|windows|static] [--project <csproj>]
5
+ # [--port N] [--config Release] [--static <dir>]
6
+ # bash .tfcore/utils/tf-verify-boot.sh stop
7
+ # bash .tfcore/utils/tf-verify-boot.sh status
8
+ #
9
+ # Heads:
10
+ # web a project on Microsoft.NET.Sdk.Web: started with `dotnet run --urls`, first through
11
+ # the rungs tf-build.sh knows (dotnet, ~/.dotnet/dotnet, then Windows-side cmd.exe on
12
+ # WSL), polled until it answers. Prints BOOTED mode=base url=http://localhost:PORT.
13
+ # windows a MAUI Blazor Hybrid head (UseMaui + a net*-windows target): started Windows-side with
14
+ # WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9222, the DevTools port
15
+ # relayed to every interface by tf-cdp-relay.ps1, and reached from WSL over CDP. Prints
16
+ # BOOTED mode=cdp url=http://<host>:9223. Proven on MyDiary, 2026-09-06.
17
+ # static a folder of HTML served by python3 (the framework self-test, a mockup set).
18
+ # android, ios, maccatalyst: no driver ships in this framework version. NONE with that reason;
19
+ # their rows are recorded as not verified, never as static-only passes.
20
+ # Without --head the script picks web when a web project exists, else windows when a MAUI project
21
+ # with a windows target exists, else NONE. --project names the project when there are several.
22
+ # The state goes to tests/.artifacts/verify/boot.json; the app log to tests/.artifacts/verify/app.log.
23
+ # `stop` kills what `start` started and nothing else. Never asks anyone to start anything.
24
+ # Exit 0 booted / stopped · 2 NONE (reason printed; kind=build-error, host or no-driver) · 3 usage.
25
+ set -u
26
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ DIR="tests/.artifacts/verify"; STATE="$DIR/boot.json"; LOG="$DIR/app.log"
28
+ mkdir -p "$DIR"
29
+
30
+ PLATFORM="linux"
31
+ U="$(uname -s 2>/dev/null || echo windows)"
32
+ if [[ "$U" == Darwin* ]]; then PLATFORM="macos"
33
+ elif [[ "$U" == Linux* ]]; then grep -qiE "microsoft|wsl" /proc/version 2>/dev/null && PLATFORM="wsl"
34
+ else PLATFORM="windows"; fi
35
+
36
+ winarg() { # a Windows path as a cmd.exe argument: bare when it has no space (a quote passed
37
+ # through the WSL bridge reaches dotnet as part of the path: "does not exist: \"C:\...\""; miss 23)
38
+ if [[ "$1" == *" "* ]]; then printf '"%s"' "$1"; else printf '%s' "$1"; fi; }
39
+ json_escape() { python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1"; }
40
+
41
+ write_state() { # head mode url pids rung project reason kind
42
+ python3 - "$@" <<'PY'
43
+ import json, sys, datetime
44
+ head, mode, url, pids, rung, project, reason, kind = sys.argv[1:9]
45
+ json.dump({"head": head, "mode": mode, "url": url, "pids": [int(p) for p in pids.split() if p.isdigit()],
46
+ "rung": rung, "project": project, "reason": reason, "reason_kind": kind, "stopped": False,
47
+ "platform": sys.argv[9] if len(sys.argv) > 9 else "", "log": "tests/.artifacts/verify/app.log",
48
+ "started": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")},
49
+ open("tests/.artifacts/verify/boot.json", "w"), indent=1)
50
+ PY
51
+ }
52
+
53
+ find_projects() { # prints csproj paths under src/, source/, the root and one level down; never tests
54
+ find . -maxdepth 4 -name '*.csproj' -not -path '*/bin/*' -not -path '*/obj/*' -not -path '*/node_modules/*' \
55
+ -not -path './tests/*' -not -path '*/test/*' -not -path '*Tests/*' -not -path '*.Tests/*' 2>/dev/null | sed 's#^\./##' | sort
56
+ }
57
+ is_web() { grep -qE 'Microsoft\.NET\.Sdk\.Web|Microsoft\.NET\.Sdk\.BlazorWebAssembly' "$1"; }
58
+ is_maui_win() { grep -qiE '<UseMaui>\s*true|Microsoft\.NET\.Sdk\.Maui' "$1" && grep -qE 'net[0-9.]+-windows' "$1"; }
59
+
60
+ poll_http() { # url seconds pid-to-watch(optional) -> 0 when it answers
61
+ local url="$1" secs="$2" pid="${3:-}" i=0 code
62
+ while [[ $i -lt $secs ]]; do
63
+ code="$(curl -s -o /dev/null -m 3 -w '%{http_code}' "$url" 2>/dev/null || true)"
64
+ [[ "$code" =~ ^[23] ]] && return 0
65
+ if [[ -n "$pid" ]] && ! kill -0 "$pid" 2>/dev/null; then return 1; fi
66
+ sleep 2; i=$((i+2))
67
+ done
68
+ return 1
69
+ }
70
+ WRONG_RUNG='NETSDK1178|Microsoft\.(iOS|Android|MacCatalyst|tvOS)\.Sdk|Workload ID|WindowsAppSDK|command not found|No such file or directory|workload.*not installed'
71
+ CODE_ERR='error (CS|RZ|BL|XC|XLS)[0-9]{3,5}|error MSB[0-9]{4}: .*(does not exist|could not be found)|error NU1'
72
+
73
+ cmd="${1:-}"; shift || true
74
+ case "$cmd" in
75
+ status) if [[ -f "$STATE" ]]; then cat "$STATE"; else echo "NONE no boot state"; fi; exit 0 ;;
76
+ stop)
77
+ [[ -f "$STATE" ]] || { echo "STOPPED nothing was started"; exit 0; }
78
+ python3 - <<'PY'
79
+ import json, os, signal, subprocess, time
80
+ s = json.load(open("tests/.artifacts/verify/boot.json"))
81
+ for pid in s.get("pids", []):
82
+ for sig in (signal.SIGTERM, signal.SIGKILL):
83
+ try:
84
+ subprocess.run(["pkill", "-" + sig.name.replace("SIG", ""), "-P", str(pid)], capture_output=True)
85
+ os.kill(pid, sig)
86
+ except ProcessLookupError:
87
+ break
88
+ except Exception:
89
+ pass
90
+ time.sleep(1)
91
+ image = s.get("win_image") or ""
92
+ if image:
93
+ subprocess.run(["taskkill.exe", "/F", "/T", "/IM", image], capture_output=True)
94
+ if s.get("win_port"):
95
+ ps = f"Get-NetTCPConnection -LocalPort {s['win_port']} -State Listen -ErrorAction SilentlyContinue | ForEach-Object {{ Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }}"
96
+ subprocess.run(["powershell.exe", "-NoProfile", "-Command", ps], capture_output=True)
97
+ s["stopped"] = True
98
+ json.dump(s, open("tests/.artifacts/verify/boot.json", "w"), indent=1)
99
+ print(f"STOPPED head={s.get('head')} mode={s.get('mode')} pids={s.get('pids')}")
100
+ PY
101
+ exit 0 ;;
102
+ start) ;;
103
+ *) sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 3 ;;
104
+ esac
105
+
106
+ HEAD=""; PROJECT=""; PORT=""; CONFIG=""; STATIC=""
107
+ while [[ $# -gt 0 ]]; do
108
+ case "$1" in
109
+ --head) HEAD="${2:-}"; shift 2 ;;
110
+ --project) PROJECT="${2:-}"; shift 2 ;;
111
+ --port) PORT="${2:-}"; shift 2 ;;
112
+ --config) CONFIG="${2:-}"; shift 2 ;;
113
+ --static) STATIC="${2:-}"; HEAD="static"; shift 2 ;;
114
+ *) echo "tf-verify-boot: unknown argument $1" >&2; exit 3 ;;
115
+ esac
116
+ done
117
+ : > "$LOG"
118
+
119
+ # ---- static ---------------------------------------------------------------------------
120
+ if [[ "$HEAD" == "static" ]]; then
121
+ [[ -d "$STATIC" ]] || { echo "NONE head=static reason=folder $STATIC does not exist"; write_state static none "" "" "" "$STATIC" "folder missing" host; exit 2; }
122
+ PORT="${PORT:-5099}"
123
+ if curl -s -o /dev/null -m 2 "http://localhost:$PORT/" 2>/dev/null; then
124
+ echo "NONE head=static reason=port $PORT is already in use; stop that process or pass --port"; exit 2
125
+ fi
126
+ nohup python3 -m http.server "$PORT" --directory "$STATIC" --bind 127.0.0.1 > "$LOG" 2>&1 &
127
+ PID=$!
128
+ if poll_http "http://localhost:$PORT/" 20 "$PID"; then
129
+ write_state static base "http://localhost:$PORT" "$PID" "python3 http.server" "$STATIC" "" "" "$PLATFORM"
130
+ echo "BOOTED head=static mode=base url=http://localhost:$PORT pid=$PID log=$LOG"; exit 0
131
+ fi
132
+ kill "$PID" 2>/dev/null; write_state static none "" "" "" "$STATIC" "http.server did not answer on $PORT" host
133
+ echo "NONE head=static reason=http.server did not answer on $PORT (log $LOG)"; exit 2
134
+ fi
135
+
136
+ # ---- pick the project and the head ------------------------------------------------------
137
+ mapfile -t ALL < <(find_projects)
138
+ WEB=(); WIN=()
139
+ for p in "${ALL[@]}"; do is_web "$p" && WEB+=("$p"); is_maui_win "$p" && WIN+=("$p"); done
140
+ if [[ -n "$PROJECT" ]]; then
141
+ [[ -f "$PROJECT" ]] || { echo "NONE reason=--project $PROJECT does not exist"; exit 3; }
142
+ if [[ -z "$HEAD" ]]; then is_web "$PROJECT" && HEAD=web; [[ -z "$HEAD" ]] && is_maui_win "$PROJECT" && HEAD=windows; fi
143
+ elif [[ -z "$HEAD" ]]; then
144
+ if [[ ${#WEB[@]} -gt 0 ]]; then HEAD=web; PROJECT="${WEB[0]}"
145
+ elif [[ ${#WIN[@]} -gt 0 ]]; then HEAD=windows; PROJECT="${WIN[0]}"; fi
146
+ else
147
+ case "$HEAD" in web) PROJECT="${WEB[0]:-}" ;; windows) PROJECT="${WIN[0]:-}" ;; esac
148
+ fi
149
+ case "$HEAD" in
150
+ android|ios|maccatalyst)
151
+ write_state "$HEAD" none "" "" "" "${PROJECT:-}" "no driver for the $HEAD head ships in this framework version" no-driver "$PLATFORM"
152
+ echo "NONE head=$HEAD reason=no driver for the $HEAD head ships in this framework version; its rows are recorded as not verified"; exit 2 ;;
153
+ web|windows) ;;
154
+ "") write_state none none "" "" "" "" "no web project and no MAUI project with a windows target found under src/, source/ or the root" host "$PLATFORM"
155
+ echo "NONE head=none reason=no web project (Microsoft.NET.Sdk.Web) and no MAUI project with a windows target found; name one with --project"; exit 2 ;;
156
+ *) echo "NONE reason=unknown head $HEAD"; exit 3 ;;
157
+ esac
158
+ [[ -n "$PROJECT" ]] || { write_state "$HEAD" none "" "" "" "" "no project for head $HEAD" host "$PLATFORM"; echo "NONE head=$HEAD reason=no project found for that head; name one with --project"; exit 2; }
159
+ PDIR="$(dirname "$PROJECT")"; PNAME="$(basename "$PROJECT" .csproj)"
160
+ ASM="$(grep -oE '<AssemblyName>[^<]+' "$PROJECT" 2>/dev/null | head -1 | sed 's/<AssemblyName>//')"; ASM="${ASM:-$PNAME}"
161
+ CFGARGS=(); [[ -n "$CONFIG" ]] && CFGARGS=(-c "$CONFIG")
162
+
163
+ # ---- web ------------------------------------------------------------------------------
164
+ if [[ "$HEAD" == "web" ]]; then
165
+ if [[ -z "$PORT" ]]; then
166
+ LS="$PDIR/Properties/launchSettings.json"
167
+ [[ -f "$LS" ]] && PORT="$(grep -oE 'http://localhost:[0-9]+' "$LS" | head -1 | sed 's#.*:##')"
168
+ PORT="${PORT:-5099}"
169
+ fi
170
+ if curl -s -o /dev/null -m 2 "http://localhost:$PORT/" 2>/dev/null; then
171
+ echo "NONE head=web reason=port $PORT is already in use; stop that process or pass --port"; exit 2
172
+ fi
173
+ URL="http://localhost:$PORT"
174
+ rungs=()
175
+ case "$PLATFORM" in
176
+ wsl) command -v dotnet >/dev/null 2>&1 && rungs+=("dotnet"); [[ -x "$HOME/.dotnet/dotnet" ]] && rungs+=("$HOME/.dotnet/dotnet"); command -v cmd.exe >/dev/null 2>&1 && rungs+=("cmd.exe") ;;
177
+ *) rungs+=("dotnet") ;;
178
+ esac
179
+ [[ ${#rungs[@]} -gt 0 ]] || { write_state web none "" "" "" "$PROJECT" "no dotnet on this host" host "$PLATFORM"; echo "NONE head=web reason=no dotnet found on $PLATFORM"; exit 2; }
180
+ tried=()
181
+ for r in "${rungs[@]}"; do
182
+ echo "### rung: $r" >> "$LOG"
183
+ case "$r" in
184
+ cmd.exe)
185
+ WP="$(winarg "$(wslpath -w "$PROJECT")")"
186
+ nohup cmd.exe /c "dotnet run --project $WP ${CFGARGS[*]} --urls $URL" >> "$LOG" 2>&1 < /dev/null &
187
+ PID=$!; label="cmd.exe /c dotnet" ;;
188
+ *)
189
+ ASPNETCORE_ENVIRONMENT="${ASPNETCORE_ENVIRONMENT:-Development}" nohup "$r" run --project "$PROJECT" "${CFGARGS[@]}" --urls "$URL" >> "$LOG" 2>&1 < /dev/null &
190
+ PID=$!; label="$r" ;;
191
+ esac
192
+ tried+=("$label")
193
+ if poll_http "$URL/" 120 "$PID"; then
194
+ WP_PORT=""; [[ "$r" == "cmd.exe" ]] && WP_PORT="$PORT"
195
+ write_state web base "$URL" "$PID" "$label" "$PROJECT" "" "" "$PLATFORM"
196
+ [[ -n "$WP_PORT" ]] && python3 -c "import json;p='$STATE';s=json.load(open(p));s['win_port']=$WP_PORT;json.dump(s,open(p,'w'),indent=1)"
197
+ echo "BOOTED head=web mode=base url=$URL rung=$label project=$PROJECT pid=$PID log=$LOG"; exit 0
198
+ fi
199
+ pkill -P "$PID" 2>/dev/null; kill "$PID" 2>/dev/null
200
+ seg="$(awk '/^### rung: /{p=0} index($0,"### rung: '"$r"'")==1{p=1} p' "$LOG")"
201
+ if grep -qE "$CODE_ERR" <<<"$seg" && ! grep -qE "$WRONG_RUNG" <<<"$seg"; then
202
+ first="$(grep -E 'error (CS|RZ|BL|XC|XLS|MSB|NU)[0-9]+' <<<"$seg" | head -1 | sed 's/^\s*//' | cut -c1-160)"
203
+ write_state web none "" "" "$label" "$PROJECT" "build error: $first" build-error "$PLATFORM"
204
+ echo "NONE head=web kind=build-error reason=the code does not build via $label: $first (log $LOG)"; exit 2
205
+ fi
206
+ done
207
+ write_state web none "" "" "" "$PROJECT" "no rung answered on $URL; tried ${tried[*]}" host "$PLATFORM"
208
+ echo "NONE head=web kind=host reason=no rung brought $PROJECT up on $URL within 120 s; tried: ${tried[*]} (log $LOG)"; exit 2
209
+ fi
210
+
211
+ # ---- windows (MAUI Blazor Hybrid over CDP) --------------------------------------------------
212
+ [[ "$PLATFORM" == "wsl" || "$PLATFORM" == "windows" ]] || { write_state windows none "" "" "" "$PROJECT" "a Windows head runs only from WSL or Windows" host "$PLATFORM"; echo "NONE head=windows reason=a Windows head runs only from WSL or Windows, this is $PLATFORM"; exit 2; }
213
+ command -v cmd.exe >/dev/null 2>&1 || { write_state windows none "" "" "" "$PROJECT" "cmd.exe not reachable" host "$PLATFORM"; echo "NONE head=windows reason=cmd.exe is not reachable from this shell"; exit 2; }
214
+ TFM="$(grep -oE 'net[0-9.]+-windows[0-9.]*' "$PROJECT" | head -1)"
215
+ CDP_PORT=9222; RELAY_PORT="${PORT:-9223}"
216
+ WP="$(winarg "$(wslpath -w "$PROJECT")")"; RELAY="$(wslpath -w "$HERE/tf-cdp-relay.ps1")"
217
+ echo "### windows head: $PROJECT -f $TFM" >> "$LOG"
218
+ nohup cmd.exe /c "set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=$CDP_PORT&& dotnet run --project $WP -f $TFM ${CFGARGS[*]}" >> "$LOG" 2>&1 < /dev/null &
219
+ APP_PID=$!
220
+ nohup powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$RELAY" -ListenPort "$RELAY_PORT" -TargetPort "$CDP_PORT" >> "$DIR/relay.log" 2>&1 < /dev/null &
221
+ RELAY_PID=$!
222
+ GW="$(ip route 2>/dev/null | awk '/default/{print $3; exit}')"
223
+ HOSTS=("localhost"); [[ -n "$GW" ]] && HOSTS+=("$GW")
224
+ i=0; URL=""
225
+ while [[ $i -lt 300 && -z "$URL" ]]; do
226
+ for h in "${HOSTS[@]}"; do
227
+ if curl -s -m 2 "http://$h:$RELAY_PORT/json/version" 2>/dev/null | grep -q '"webSocketDebuggerUrl"'; then URL="http://$h:$RELAY_PORT"; break; fi
228
+ done
229
+ [[ -n "$URL" ]] && break
230
+ if ! kill -0 "$APP_PID" 2>/dev/null; then
231
+ seg="$(cat "$LOG")"
232
+ if grep -qE "$CODE_ERR" <<<"$seg"; then
233
+ first="$(grep -E 'error (CS|RZ|BL|XC|XLS|MSB|NU)[0-9]+' <<<"$seg" | head -1 | sed 's/^\s*//' | cut -c1-160)"
234
+ kill "$RELAY_PID" 2>/dev/null
235
+ write_state windows none "" "" "cmd.exe /c dotnet run -f $TFM" "$PROJECT" "build error: $first" build-error "$PLATFORM"
236
+ echo "NONE head=windows kind=build-error reason=the code does not build: $first (log $LOG)"; exit 2
237
+ fi
238
+ fi
239
+ sleep 3; i=$((i+3))
240
+ done
241
+ if [[ -z "$URL" ]]; then
242
+ pkill -P "$APP_PID" 2>/dev/null; kill "$APP_PID" "$RELAY_PID" 2>/dev/null
243
+ taskkill.exe /F /T /IM "$ASM.exe" >/dev/null 2>&1
244
+ write_state windows none "" "" "cmd.exe /c dotnet run -f $TFM" "$PROJECT" "the DevTools port $RELAY_PORT never answered within 300 s" host "$PLATFORM"
245
+ echo "NONE head=windows kind=host reason=the app's DevTools port never answered on ${HOSTS[*]}:$RELAY_PORT within 300 s (log $LOG, relay $DIR/relay.log)"; exit 2
246
+ fi
247
+ write_state windows cdp "$URL" "$APP_PID $RELAY_PID" "cmd.exe /c dotnet run -f $TFM" "$PROJECT" "" "" "$PLATFORM"
248
+ python3 -c "import json;p='$STATE';s=json.load(open(p));s['win_image']='$ASM.exe';json.dump(s,open(p,'w'),indent=1)"
249
+ echo "BOOTED head=windows mode=cdp url=$URL project=$PROJECT tfm=$TFM pids=$APP_PID,$RELAY_PID image=$ASM.exe log=$LOG"
250
+ exit 0
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ # tf-verify-emit.sh — the telemetry of a verify run, from its verdict file (Sitting 4c, 2026-09-06).
3
+ #
4
+ # bash .tfcore/utils/tf-verify-emit.sh <App> [--dir tests/.artifacts/verify] [--mode <mode>] [--started <ISO>]
5
+ #
6
+ # Reads <dir>/verdicts.json (written by tf-verify-verdict.sh) and appends, through tf-emit.sh only:
7
+ # gates.jsonl one record per row that passed or failed a check: the first failing check (or null),
8
+ # the checks that ran, the failure class, the prior status, the attempt number
9
+ # misses.jsonl one `miss` per failing row that has no open miss of the same class (the duplicate
10
+ # check is tf-emit.sh --open-miss); origin = the last build or fix run that touched
11
+ # the row, looked up in runs.jsonl, or left out when there is none
12
+ # runs.jsonl one `run` record for the verify pass: cmd verify-phase, the rows touched, the build
13
+ # result, whether YOLO was on
14
+ # Rows the run could not grade (not tested, not observable, not driven) get no record. Telemetry
15
+ # never blocks: a refused record is printed and the script still exits 0.
16
+ set -u
17
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ [[ $# -ge 1 && "$1" != "-h" && "$1" != "--help" ]] || { sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'; exit 3; }
19
+ APP="$1"; shift
20
+ DIR="tests/.artifacts/verify"; MODE=""; STARTED=""
21
+ while [[ $# -gt 0 ]]; do
22
+ case "$1" in
23
+ --dir) DIR="${2:-}"; shift 2 ;;
24
+ --mode) MODE="${2:-}"; shift 2 ;;
25
+ --started) STARTED="${2:-}"; shift 2 ;;
26
+ *) echo "tf-verify-emit: unknown argument $1" >&2; exit 3 ;;
27
+ esac
28
+ done
29
+ [[ -f "$DIR/verdicts.json" ]] || { echo "tf-verify-emit: $DIR/verdicts.json missing; run tf-verify-verdict.sh first" >&2; exit 2; }
30
+ YOLO=false; bash "$HERE/tf-yolo.sh" is-on >/dev/null 2>&1 && YOLO=true
31
+ [[ -z "$STARTED" ]] && STARTED="$(bash "$HERE/tf-phase.sh" show 2>/dev/null | sed -n 's/.*"started":"\([^"]*\)".*/\1/p')"
32
+
33
+ TF_APP="$APP" TF_DIR="$DIR" TF_MODE="$MODE" TF_STARTED="$STARTED" TF_YOLO="$YOLO" TF_EMIT="$HERE/tf-emit.sh" python3 - <<'PY'
34
+ import datetime, json, os, subprocess
35
+
36
+ EMIT = os.environ["TF_EMIT"]; app = os.environ["TF_APP"]; d = os.environ["TF_DIR"]
37
+ mode = os.environ["TF_MODE"] or None; started = os.environ["TF_STARTED"]; yolo = os.environ["TF_YOLO"] == "true"
38
+ v = json.load(open(os.path.join(d, "verdicts.json"), encoding="utf-8"))
39
+ run_id = started or v.get("started") or ""
40
+ now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
41
+
42
+ def emit(stream, rec):
43
+ p = subprocess.run(["bash", EMIT, stream], input=json.dumps(rec), capture_output=True, text=True)
44
+ msg = (p.stdout + p.stderr).strip()
45
+ if p.returncode != 0 or "refus" in msg.lower() or "error" in msg.lower():
46
+ print(f" {stream}: not written — {msg.splitlines()[0][:160] if msg else 'no reason printed'}")
47
+ return False
48
+ return True
49
+
50
+ def q(*args):
51
+ p = subprocess.run(["bash", EMIT, *args], capture_output=True, text=True)
52
+ return p.stdout.strip()
53
+
54
+ # origin lookup: the last build-phase or fix-issues run that touched the row
55
+ runs_path = os.path.join(q("--where") or "docs/metrics", "runs.jsonl")
56
+ origin = {}
57
+ if os.path.isfile(runs_path):
58
+ for line in open(runs_path, encoding="utf-8", errors="replace"):
59
+ try:
60
+ r = json.loads(line)
61
+ except Exception:
62
+ continue
63
+ if r.get("kind") != "run" or r.get("backfilled") or r.get("cmd") not in ("build-phase", "fix-issues"):
64
+ continue
65
+ for rid in r.get("reqs_touched") or []:
66
+ if r.get("started", "") >= origin.get(rid, ("", ""))[0]:
67
+ origin[rid] = (r.get("started", ""), r.get("cmd"), (r.get("subagents") or ["flow-master"])[0] if r.get("subagents") else "flow-master")
68
+
69
+ gates = misses = 0
70
+ for row in v["rows"]:
71
+ if not row.get("emit_gate"):
72
+ continue
73
+ rid = row["id"]
74
+ att = q("--next-attempt", rid) or "1"
75
+ rec = {"kind": "gate", "app": app, "run_id": run_id, "req_id": rid, "req_class": row["class"],
76
+ "attempt": int(att) if att.isdigit() else 1, "verdict": row["status"], "gate": row["gate"],
77
+ "gates_run": row["gates_run"], "failure_class": row["failure_class"], "prior_verdict": row["prior_verdict"]}
78
+ if emit("gates", rec):
79
+ gates += 1
80
+ if not row["gate"]:
81
+ continue
82
+ cls = ("regression" if (row["prior_verdict"] or "").lower().startswith("verified")
83
+ else "standards-violation" if row["gate"] == "standards"
84
+ else "partial-implementation")
85
+ opened = q("--open-miss", rid)
86
+ if opened and opened.split()[-1] == cls:
87
+ continue
88
+ mid = q("--next-miss-id")
89
+ if not mid:
90
+ print(f" misses: no id for {rid}; skipped")
91
+ continue
92
+ m = {"kind": "miss", "miss_id": mid, "req_id": rid, "req_class": row["class"], "miss_class": cls,
93
+ "artifact": "src", "severity": "major", "origin_phase": "build-phase", "origin_agent": "flow-master",
94
+ "found_by": "gate", "found_phase": "verify-phase", "found_gate": row["gate"], "found_run_id": run_id,
95
+ "failure_class": row["failure_class"] or "other"}
96
+ if rid in origin:
97
+ m["origin_run_id"], m["origin_phase"], m["origin_agent"] = origin[rid]
98
+ if emit("misses", m):
99
+ misses += 1
100
+
101
+ touched = [r["id"] for r in v["rows"] if r.get("emit_gate")]
102
+ rec = {"kind": "run", "app": app, "cmd": "verify-phase", "mode": mode, "yolo": yolo, "ended": now,
103
+ "reqs_touched": touched, "reqs_count": len(touched), "subagents": [], "files_written": 2,
104
+ "build_result": v.get("build_result", "not-run")}
105
+ if run_id:
106
+ rec["started"] = run_id
107
+ ok = emit("runs", rec)
108
+ print(f"telemetry: {gates} gate record(s), {misses} new miss(es), run record {'written' if ok else 'not written'} (cmd verify-phase, yolo {str(yolo).lower()})")
109
+ PY
110
+ exit 0
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # tf-verify-env.sh — make sure the browser tooling for a verify is present (Sitting 4c, 2026-09-06).
3
+ #
4
+ # bash .tfcore/utils/tf-verify-env.sh [--check]
5
+ #
6
+ # Ensures, in the current project: package.json; the playwright and @playwright/test packages;
7
+ # the Chromium browser; playwright.config.ts with its output pinned under tests/.artifacts/;
8
+ # the .gitignore lines for everything this tooling generates. --check reports and changes nothing.
9
+ # Prints one line per item and READY at the end, or NOT-READY with the one command the owner
10
+ # must run once (only when a browser install needs sudo and sudo is unavailable).
11
+ # Exit 0 ready · 1 not ready · 2 node or npm missing.
12
+ set -u
13
+ CHECK=0; [[ "${1:-}" == "--check" ]] && CHECK=1
14
+ command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 || { echo "NOT-READY node and npm are required"; exit 2; }
15
+ ok=1
16
+
17
+ say() { echo " $*"; }
18
+
19
+ # package.json
20
+ if [[ ! -f package.json ]]; then
21
+ if [[ $CHECK -eq 1 ]]; then say "package.json: missing"; ok=0; else npm init -y >/dev/null 2>&1 && say "package.json: created"; fi
22
+ else say "package.json: present"; fi
23
+
24
+ # packages
25
+ need=()
26
+ node -e "require.resolve('playwright')" >/dev/null 2>&1 || need+=("playwright")
27
+ node -e "require.resolve('@playwright/test')" >/dev/null 2>&1 || need+=("@playwright/test")
28
+ if [[ ${#need[@]} -gt 0 ]]; then
29
+ if [[ $CHECK -eq 1 ]]; then say "packages: missing ${need[*]}"; ok=0
30
+ else say "packages: installing ${need[*]}"; npm install -D "${need[@]}" >/dev/null 2>&1 || { say "packages: npm install failed"; ok=0; }; fi
31
+ else say "packages: playwright and @playwright/test present"; fi
32
+
33
+ # browser
34
+ if node -e "const {chromium}=require('playwright'); const p=chromium.executablePath(); require('fs').existsSync(p)?process.exit(0):process.exit(1)" >/dev/null 2>&1; then
35
+ say "chromium: present"
36
+ else
37
+ if [[ $CHECK -eq 1 ]]; then say "chromium: missing"; ok=0
38
+ else
39
+ say "chromium: installing"
40
+ if ! npx playwright install chromium >/dev/null 2>&1; then
41
+ if ! npx playwright install --with-deps chromium >/dev/null 2>&1; then
42
+ if command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
43
+ sudo npx playwright install --with-deps chromium >/dev/null 2>&1 || { say "chromium: install failed even with sudo"; ok=0; }
44
+ else
45
+ say "chromium: needs system libraries; run once: sudo npx playwright install --with-deps chromium"; ok=0
46
+ fi
47
+ fi
48
+ fi
49
+ fi
50
+ fi
51
+
52
+ # playwright.config.ts
53
+ CFG='import { defineConfig } from '"'"'@playwright/test'"'"';
54
+ export default defineConfig({
55
+ testDir: '"'"'./tests/verify'"'"',
56
+ outputDir: '"'"'./tests/.artifacts/test-results'"'"',
57
+ reporter: '"'"'line'"'"',
58
+ use: { headless: true, screenshot: '"'"'only-on-failure'"'"', trace: '"'"'retain-on-failure'"'"' },
59
+ });'
60
+ if [[ ! -f playwright.config.ts ]]; then
61
+ if [[ $CHECK -eq 1 ]]; then say "playwright.config.ts: missing"; ok=0; else printf '%s\n' "$CFG" > playwright.config.ts; say "playwright.config.ts: written"; fi
62
+ elif ! grep -q "tests/.artifacts" playwright.config.ts; then
63
+ if [[ $CHECK -eq 1 ]]; then say "playwright.config.ts: outputDir not under tests/.artifacts"; ok=0
64
+ else
65
+ if grep -q "outputDir" playwright.config.ts; then
66
+ sed -i "s#outputDir:[^,]*,#outputDir: './tests/.artifacts/test-results',#" playwright.config.ts
67
+ else
68
+ sed -i "s#defineConfig({#defineConfig({\n outputDir: './tests/.artifacts/test-results',#" playwright.config.ts
69
+ fi
70
+ say "playwright.config.ts: outputDir pinned under tests/.artifacts"
71
+ fi
72
+ else say "playwright.config.ts: present, output pinned"; fi
73
+
74
+ # .gitignore
75
+ missing=()
76
+ for line in "node_modules/" "tests/.artifacts/" "test-results/" "playwright-report/" "/docs/.last-verify.json" ".verify/"; do
77
+ grep -qsF -- "$line" .gitignore || missing+=("$line")
78
+ done
79
+ if [[ ${#missing[@]} -gt 0 ]]; then
80
+ if [[ $CHECK -eq 1 ]]; then say ".gitignore: missing ${missing[*]}"; ok=0
81
+ else
82
+ { [[ -f .gitignore ]] && [[ -n "$(tail -c1 .gitignore)" ]] && echo; echo "# TechieFlow agent artifacts — machine-generated test harness & logs, never commit"; printf '%s\n' "${missing[@]}"; } >> .gitignore
83
+ say ".gitignore: added ${missing[*]}"
84
+ fi
85
+ else say ".gitignore: complete"; fi
86
+
87
+ mkdir -p tests/.artifacts/verify tests/verify 2>/dev/null
88
+ if [[ $ok -eq 1 ]]; then echo "READY"; exit 0; else echo "NOT-READY"; exit 1; fi