@techierathore/techieflow 1.0.2 → 1.0.4
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/analyst.md +5 -13
- package/.claude/commands/TechieFlow/agents/architect.md +1 -16
- package/.claude/commands/TechieFlow/agents/flow-master.md +42 -215
- package/.claude/commands/TechieFlow/agents/verifier.md +24 -64
- package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
- package/.claude/commands/TechieFlow/tasks/_owner-language.md +26 -0
- package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
- package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +21 -143
- package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
- package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +4 -0
- package/.claude/commands/TechieFlow/tasks/create-doc.md +4 -0
- package/.claude/commands/TechieFlow/tasks/create-project-brief.md +22 -0
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
- package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
- package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
- package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +4 -0
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
- package/.claude/commands/TechieFlow/tasks/generate-html.md +5 -1
- package/.claude/commands/TechieFlow/tasks/handoff-phase.md +18 -16
- package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
- package/.claude/commands/TechieFlow/tasks/metrics-report.md +3 -1
- package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
- package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
- package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
- package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
- package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
- package/.opencode/plugin/techieflow.js +40 -13
- package/.tfcore/agents/analyst.md +5 -13
- package/.tfcore/agents/architect.md +1 -16
- package/.tfcore/agents/flow-master.md +42 -215
- package/.tfcore/agents/verifier.md +24 -64
- package/.tfcore/core-config.yaml +10 -0
- package/.tfcore/hooks/block-git.sh +4 -4
- package/.tfcore/hooks/guard-artifacts.sh +2 -2
- package/.tfcore/hooks/guard-build.sh +76 -0
- package/.tfcore/hooks/guard-db.sh +90 -0
- package/.tfcore/hooks/guard-metrics.sh +68 -0
- package/.tfcore/hooks/guard-status-html.sh +116 -71
- package/.tfcore/hooks/guard-status.sh +43 -16
- package/.tfcore/hooks/guard-verify-deps.sh +124 -0
- package/.tfcore/hooks/guard-verify.sh +70 -48
- package/.tfcore/hooks/sweep-artifacts.sh +2 -2
- package/.tfcore/install-manifest.yaml +0 -18
- package/.tfcore/routing.yaml +86 -14
- package/.tfcore/standards/coding-standards-core.md +61 -0
- package/.tfcore/standards/coding-standards-dotnet.md +93 -0
- package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
- package/.tfcore/tasks/_owner-language.md +26 -0
- package/.tfcore/tasks/_smoke-test-policy.md +20 -84
- package/.tfcore/tasks/_status-update-gate.md +10 -74
- package/.tfcore/tasks/_yolo-mode.md +20 -58
- package/.tfcore/tasks/amend-docs.md +21 -143
- package/.tfcore/tasks/build-phase.md +14 -224
- package/.tfcore/tasks/create-deep-research-prompt.md +4 -0
- package/.tfcore/tasks/create-doc.md +4 -0
- package/.tfcore/tasks/create-project-brief.md +22 -0
- package/.tfcore/tasks/day1-brownfield.md +31 -505
- package/.tfcore/tasks/day1-greenfield.md +28 -213
- package/.tfcore/tasks/deploy-checklist.md +19 -0
- package/.tfcore/tasks/devguide.md +10 -219
- package/.tfcore/tasks/facilitate-brainstorming-session.md +4 -0
- package/.tfcore/tasks/fix-issues.md +10 -129
- package/.tfcore/tasks/generate-html.md +5 -1
- package/.tfcore/tasks/handoff-phase.md +18 -16
- package/.tfcore/tasks/log-miss.md +19 -158
- package/.tfcore/tasks/metrics-report.md +3 -1
- package/.tfcore/tasks/mockups.md +12 -117
- package/.tfcore/tasks/refresh-status.md +9 -130
- package/.tfcore/tasks/split-brd.md +10 -126
- package/.tfcore/tasks/triage-and-fix.md +14 -0
- package/.tfcore/tasks/triage-issues.md +11 -152
- package/.tfcore/tasks/verify-phase.md +36 -572
- package/.tfcore/telemetry/SCHEMA.md +100 -8
- package/.tfcore/telemetry/tf-metrics.sh +564 -32
- package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
- package/.tfcore/templates/stack-questions.md +77 -0
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
- package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
- package/.tfcore/templates/v4custom/app-brief-tmpl.md +65 -0
- package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
- package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
- package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
- package/.tfcore/templates/v4custom/app-decision-request-tmpl.md +85 -0
- package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
- package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
- package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +86 -31
- package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
- package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
- package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
- package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
- package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
- package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
- package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
- package/.tfcore/templates/v4custom/html-render-shell.md +1 -1
- package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
- package/.tfcore/user-guide.md +0 -23
- package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-checklist-edit.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-devguide-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-doc-check.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-perf-grade.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-brd-status.py +176 -0
- package/.tfcore/utils/tf-brd-status.sh +19 -0
- package/.tfcore/utils/tf-build-list.py +211 -0
- package/.tfcore/utils/tf-build-list.sh +21 -0
- package/.tfcore/utils/tf-build.sh +143 -0
- package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
- package/.tfcore/utils/tf-checklist-edit.py +133 -0
- package/.tfcore/utils/tf-day1-files.py +208 -0
- package/.tfcore/utils/tf-day1-files.sh +20 -0
- package/.tfcore/utils/tf-devguide-list.py +249 -0
- package/.tfcore/utils/tf-devguide-list.sh +18 -0
- package/.tfcore/utils/tf-doc-check.py +1369 -0
- package/.tfcore/utils/tf-doc-check.sh +29 -0
- package/.tfcore/utils/tf-emit.sh +545 -18
- package/.tfcore/utils/tf-fix-close.sh +115 -0
- package/.tfcore/utils/tf-gitignore-audit.sh +50 -3
- package/.tfcore/utils/tf-goal.sh +284 -66
- package/.tfcore/utils/tf-harness.sh +4 -9
- package/.tfcore/utils/tf-log-miss.py +197 -0
- package/.tfcore/utils/tf-log-miss.sh +14 -0
- package/.tfcore/utils/tf-misses-md.py +208 -0
- package/.tfcore/utils/tf-misses-md.sh +14 -0
- package/.tfcore/utils/tf-mockups-locate.py +66 -0
- package/.tfcore/utils/tf-mockups-locate.sh +18 -0
- package/.tfcore/utils/tf-model-pick.sh +475 -0
- package/.tfcore/utils/tf-opencode-audit.py +23 -7
- package/.tfcore/utils/tf-perf-grade.py +97 -0
- package/.tfcore/utils/tf-perf-grade.sh +12 -0
- package/.tfcore/utils/tf-phase.sh +92 -0
- package/.tfcore/utils/tf-render-html.py +20 -0
- package/.tfcore/utils/tf-routing-bind.sh +97 -6
- package/.tfcore/utils/tf-routing.sh +13 -7
- package/.tfcore/utils/tf-selfcheck.sh +323 -0
- package/.tfcore/utils/tf-split-brd.py +309 -0
- package/.tfcore/utils/tf-split-brd.sh +22 -0
- package/.tfcore/utils/tf-status-evidence.sh +68 -0
- package/.tfcore/utils/tf-status-facts.py +358 -0
- package/.tfcore/utils/tf-status-facts.sh +20 -0
- package/.tfcore/utils/tf-triage.py +243 -0
- package/.tfcore/utils/tf-triage.sh +16 -0
- package/.tfcore/utils/tf-verify-boot.sh +250 -0
- package/.tfcore/utils/tf-verify-emit.sh +110 -0
- package/.tfcore/utils/tf-verify-env.sh +88 -0
- package/.tfcore/utils/tf-verify-list.py +270 -0
- package/.tfcore/utils/tf-verify-list.sh +13 -0
- package/.tfcore/utils/tf-verify-screens.mjs +355 -0
- package/.tfcore/utils/tf-verify-screens.sh +31 -0
- package/.tfcore/utils/tf-verify-tests.sh +166 -0
- package/.tfcore/utils/tf-verify-verdict.py +292 -0
- package/.tfcore/utils/tf-verify-verdict.sh +15 -0
- package/.tfcore/utils/tf-yolo.sh +59 -0
- package/README.md +120 -1145
- package/docs/TechieFlow-Installation.md +1 -5
- package/opencode.jsonc +37 -33
- package/package.json +1 -5
- package/scripts/install.mjs +56 -46
- package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
- package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
- package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
- package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
- package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
- package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
- package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
- package/.codex/config.toml +0 -46
- package/.codex/hooks.json +0 -63
- package/.codex/rules/techieflow.rules +0 -8
- package/.tfcore/hooks/codex-adapter.py +0 -248
- package/.tfcore/tasks/advanced-elicitation.md +0 -117
- package/.tfcore/tasks/author-brd.md +0 -79
- package/.tfcore/tasks/document-project.md +0 -343
- package/.tfcore/tasks/execute-checklist.md +0 -86
- package/.tfcore/tasks/index-docs.md +0 -173
- package/.tfcore/tasks/kb-mode-interaction.md +0 -75
- package/.tfcore/tasks/shard-doc.md +0 -185
- package/.tfcore/templates/architecture-tmpl.yaml +0 -650
- package/.tfcore/templates/brownfield-architecture-tmpl.yaml +0 -476
- package/.tfcore/templates/competitor-analysis-tmpl.yaml +0 -306
- package/.tfcore/templates/front-end-architecture-tmpl.yaml +0 -218
- package/.tfcore/templates/fullstack-architecture-tmpl.yaml +0 -823
- package/.tfcore/templates/market-research-tmpl.yaml +0 -252
- package/.tfcore/templates/project-brief-tmpl.yaml +0 -221
- package/.tfcore/utils/techieflow-doc-template.md +0 -325
- package/.tfcore/utils/tf-codex-bind.py +0 -196
- package/.tfcore/utils/tf-codex-telemetry.py +0 -60
- package/WORKFLOW.html +0 -1986
|
@@ -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
|