@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.
Files changed (204) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +5 -13
  2. package/.claude/commands/TechieFlow/agents/architect.md +1 -16
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +42 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +24 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_owner-language.md +26 -0
  7. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  8. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  9. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  10. package/.claude/commands/TechieFlow/tasks/amend-docs.md +21 -143
  11. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  12. package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +4 -0
  13. package/.claude/commands/TechieFlow/tasks/create-doc.md +4 -0
  14. package/.claude/commands/TechieFlow/tasks/create-project-brief.md +22 -0
  15. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  16. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  17. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  18. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  19. package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +4 -0
  20. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  21. package/.claude/commands/TechieFlow/tasks/generate-html.md +5 -1
  22. package/.claude/commands/TechieFlow/tasks/handoff-phase.md +18 -16
  23. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  24. package/.claude/commands/TechieFlow/tasks/metrics-report.md +3 -1
  25. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  26. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  27. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  28. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  29. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  30. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  31. package/.opencode/plugin/techieflow.js +40 -13
  32. package/.tfcore/agents/analyst.md +5 -13
  33. package/.tfcore/agents/architect.md +1 -16
  34. package/.tfcore/agents/flow-master.md +42 -215
  35. package/.tfcore/agents/verifier.md +24 -64
  36. package/.tfcore/core-config.yaml +10 -0
  37. package/.tfcore/hooks/block-git.sh +4 -4
  38. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  39. package/.tfcore/hooks/guard-build.sh +76 -0
  40. package/.tfcore/hooks/guard-db.sh +90 -0
  41. package/.tfcore/hooks/guard-metrics.sh +68 -0
  42. package/.tfcore/hooks/guard-status-html.sh +116 -71
  43. package/.tfcore/hooks/guard-status.sh +43 -16
  44. package/.tfcore/hooks/guard-verify-deps.sh +124 -0
  45. package/.tfcore/hooks/guard-verify.sh +70 -48
  46. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  47. package/.tfcore/install-manifest.yaml +0 -18
  48. package/.tfcore/routing.yaml +86 -14
  49. package/.tfcore/standards/coding-standards-core.md +61 -0
  50. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  51. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  52. package/.tfcore/tasks/_owner-language.md +26 -0
  53. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  54. package/.tfcore/tasks/_status-update-gate.md +10 -74
  55. package/.tfcore/tasks/_yolo-mode.md +20 -58
  56. package/.tfcore/tasks/amend-docs.md +21 -143
  57. package/.tfcore/tasks/build-phase.md +14 -224
  58. package/.tfcore/tasks/create-deep-research-prompt.md +4 -0
  59. package/.tfcore/tasks/create-doc.md +4 -0
  60. package/.tfcore/tasks/create-project-brief.md +22 -0
  61. package/.tfcore/tasks/day1-brownfield.md +31 -505
  62. package/.tfcore/tasks/day1-greenfield.md +28 -213
  63. package/.tfcore/tasks/deploy-checklist.md +19 -0
  64. package/.tfcore/tasks/devguide.md +10 -219
  65. package/.tfcore/tasks/facilitate-brainstorming-session.md +4 -0
  66. package/.tfcore/tasks/fix-issues.md +10 -129
  67. package/.tfcore/tasks/generate-html.md +5 -1
  68. package/.tfcore/tasks/handoff-phase.md +18 -16
  69. package/.tfcore/tasks/log-miss.md +19 -158
  70. package/.tfcore/tasks/metrics-report.md +3 -1
  71. package/.tfcore/tasks/mockups.md +12 -117
  72. package/.tfcore/tasks/refresh-status.md +9 -130
  73. package/.tfcore/tasks/split-brd.md +10 -126
  74. package/.tfcore/tasks/triage-and-fix.md +14 -0
  75. package/.tfcore/tasks/triage-issues.md +11 -152
  76. package/.tfcore/tasks/verify-phase.md +36 -572
  77. package/.tfcore/telemetry/SCHEMA.md +100 -8
  78. package/.tfcore/telemetry/tf-metrics.sh +564 -32
  79. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  80. package/.tfcore/templates/stack-questions.md +77 -0
  81. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  82. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  83. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  84. package/.tfcore/templates/v4custom/app-brief-tmpl.md +65 -0
  85. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  86. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  87. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  88. package/.tfcore/templates/v4custom/app-decision-request-tmpl.md +85 -0
  89. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  90. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  91. package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +86 -31
  92. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  93. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  94. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  95. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  96. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  97. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  98. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  99. package/.tfcore/templates/v4custom/html-render-shell.md +1 -1
  100. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  101. package/.tfcore/user-guide.md +0 -23
  102. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  103. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  104. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  105. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  106. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  107. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  108. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  109. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  110. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  111. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  112. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  113. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  114. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  115. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  116. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  117. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  118. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  119. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  120. package/.tfcore/utils/tf-brd-status.py +176 -0
  121. package/.tfcore/utils/tf-brd-status.sh +19 -0
  122. package/.tfcore/utils/tf-build-list.py +211 -0
  123. package/.tfcore/utils/tf-build-list.sh +21 -0
  124. package/.tfcore/utils/tf-build.sh +143 -0
  125. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  126. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  127. package/.tfcore/utils/tf-day1-files.py +208 -0
  128. package/.tfcore/utils/tf-day1-files.sh +20 -0
  129. package/.tfcore/utils/tf-devguide-list.py +249 -0
  130. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  131. package/.tfcore/utils/tf-doc-check.py +1369 -0
  132. package/.tfcore/utils/tf-doc-check.sh +29 -0
  133. package/.tfcore/utils/tf-emit.sh +545 -18
  134. package/.tfcore/utils/tf-fix-close.sh +115 -0
  135. package/.tfcore/utils/tf-gitignore-audit.sh +50 -3
  136. package/.tfcore/utils/tf-goal.sh +284 -66
  137. package/.tfcore/utils/tf-harness.sh +4 -9
  138. package/.tfcore/utils/tf-log-miss.py +197 -0
  139. package/.tfcore/utils/tf-log-miss.sh +14 -0
  140. package/.tfcore/utils/tf-misses-md.py +208 -0
  141. package/.tfcore/utils/tf-misses-md.sh +14 -0
  142. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  143. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  144. package/.tfcore/utils/tf-model-pick.sh +475 -0
  145. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  146. package/.tfcore/utils/tf-perf-grade.py +97 -0
  147. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  148. package/.tfcore/utils/tf-phase.sh +92 -0
  149. package/.tfcore/utils/tf-render-html.py +20 -0
  150. package/.tfcore/utils/tf-routing-bind.sh +97 -6
  151. package/.tfcore/utils/tf-routing.sh +13 -7
  152. package/.tfcore/utils/tf-selfcheck.sh +323 -0
  153. package/.tfcore/utils/tf-split-brd.py +309 -0
  154. package/.tfcore/utils/tf-split-brd.sh +22 -0
  155. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  156. package/.tfcore/utils/tf-status-facts.py +358 -0
  157. package/.tfcore/utils/tf-status-facts.sh +20 -0
  158. package/.tfcore/utils/tf-triage.py +243 -0
  159. package/.tfcore/utils/tf-triage.sh +16 -0
  160. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  161. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  162. package/.tfcore/utils/tf-verify-env.sh +88 -0
  163. package/.tfcore/utils/tf-verify-list.py +270 -0
  164. package/.tfcore/utils/tf-verify-list.sh +13 -0
  165. package/.tfcore/utils/tf-verify-screens.mjs +355 -0
  166. package/.tfcore/utils/tf-verify-screens.sh +31 -0
  167. package/.tfcore/utils/tf-verify-tests.sh +166 -0
  168. package/.tfcore/utils/tf-verify-verdict.py +292 -0
  169. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  170. package/.tfcore/utils/tf-yolo.sh +59 -0
  171. package/README.md +120 -1145
  172. package/docs/TechieFlow-Installation.md +1 -5
  173. package/opencode.jsonc +37 -33
  174. package/package.json +1 -5
  175. package/scripts/install.mjs +56 -46
  176. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  177. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  178. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  179. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  180. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  181. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  182. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  183. package/.codex/config.toml +0 -46
  184. package/.codex/hooks.json +0 -63
  185. package/.codex/rules/techieflow.rules +0 -8
  186. package/.tfcore/hooks/codex-adapter.py +0 -248
  187. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  188. package/.tfcore/tasks/author-brd.md +0 -79
  189. package/.tfcore/tasks/document-project.md +0 -343
  190. package/.tfcore/tasks/execute-checklist.md +0 -86
  191. package/.tfcore/tasks/index-docs.md +0 -173
  192. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  193. package/.tfcore/tasks/shard-doc.md +0 -185
  194. package/.tfcore/templates/architecture-tmpl.yaml +0 -650
  195. package/.tfcore/templates/brownfield-architecture-tmpl.yaml +0 -476
  196. package/.tfcore/templates/competitor-analysis-tmpl.yaml +0 -306
  197. package/.tfcore/templates/front-end-architecture-tmpl.yaml +0 -218
  198. package/.tfcore/templates/fullstack-architecture-tmpl.yaml +0 -823
  199. package/.tfcore/templates/market-research-tmpl.yaml +0 -252
  200. package/.tfcore/templates/project-brief-tmpl.yaml +0 -221
  201. package/.tfcore/utils/techieflow-doc-template.md +0 -325
  202. package/.tfcore/utils/tf-codex-bind.py +0 -196
  203. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  204. package/WORKFLOW.html +0 -1986
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ # tf-fix-close.sh — the telemetry a fix owes when it lands (Sitting 4c, 2026-09-06; D-15).
3
+ #
4
+ # bash .tfcore/utils/tf-fix-close.sh <App> [--started <ISO>] [--reqs REQ-UI-009,REQ-FN-014]
5
+ # [--subagents trblazeui,builder] [--build pass|fail|not-run] [--files N]
6
+ # bash .tfcore/utils/tf-fix-close.sh <App> --misses MISS-App-20260901-06,MISS-App-20260830-03 \
7
+ # --fix-cmd amend-docs [--verdict Verified]
8
+ #
9
+ # The second form closes misses DIRECTLY, by id, with no checklist row and no verify ledger.
10
+ # It exists because a miss whose deficient artifact is a document is fixed by *amend-docs
11
+ # editing the BRD -- never by the verifier touching a row -- so the row-driven form above can
12
+ # never reach it, and the miss stayed open forever (TF-016). It writes NO run record: the
13
+ # amending command emits its own through the status gate. `fix_cmd: "amend-docs"` was already
14
+ # legal in SCHEMA §5.5.2; only this door was missing.
15
+ #
16
+ # Emits, in this order, through tf-emit.sh only:
17
+ # 1. the fix-issues run record (cmd fix-issues, mode fix, the rows touched) — first, because the
18
+ # miss-fix records below carry no numbers of their own: the emitter copies this run's token window
19
+ # 2. one miss-fix per row that has an open miss, with verdict_after taken from the verify ledger
20
+ # docs/.last-verify.json written by the verify the fix chained, mapped to a checklist status (PASS →
21
+ # Verified; FAIL → FAIL; any other failing verdict or an absent row → Needs re-verify). A row with no open miss is listed,
22
+ # not invented: a fix on a defect nobody logged is a triage gap, so open it first with tf-triage.sh
23
+ # demote or tf-log-miss.sh, then re-run this.
24
+ # --reqs defaults to every row in the ledger. Telemetry never blocks: refusals are printed, exit 0.
25
+ set -u
26
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ [[ $# -ge 1 && "$1" != "-h" && "$1" != "--help" ]] || { sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'; exit 3; }
28
+ APP="$1"; shift
29
+ STARTED=""; REQS=""; SUBS=""; BUILD="pass"; FILES=""; MISSES=""; FIXCMD="fix-issues"; VERDICT=""
30
+ while [[ $# -gt 0 ]]; do
31
+ case "$1" in
32
+ --started) STARTED="${2:-}"; shift 2 ;;
33
+ --reqs) REQS="${2:-}"; shift 2 ;;
34
+ --subagents) SUBS="${2:-}"; shift 2 ;;
35
+ --build) BUILD="${2:-}"; shift 2 ;;
36
+ --files) FILES="${2:-}"; shift 2 ;;
37
+ --misses) MISSES="${2:-}"; shift 2 ;;
38
+ --fix-cmd) FIXCMD="${2:-}"; shift 2 ;;
39
+ --verdict) VERDICT="${2:-}"; shift 2 ;;
40
+ *) echo "tf-fix-close: unknown argument $1" >&2; exit 3 ;;
41
+ esac
42
+ done
43
+ [[ -z "$STARTED" ]] && STARTED="$(bash "$HERE/tf-phase.sh" show 2>/dev/null | sed -n 's/.*"started":"\([^"]*\)".*/\1/p')"
44
+ TF_APP="$APP" TF_STARTED="$STARTED" TF_REQS="$REQS" TF_SUBS="$SUBS" TF_BUILD="$BUILD" TF_FILES="$FILES" \
45
+ TF_MISSES="$MISSES" TF_FIXCMD="$FIXCMD" TF_VERDICT="$VERDICT" TF_EMIT="$HERE/tf-emit.sh" python3 - <<'PY'
46
+ import datetime, json, os, subprocess
47
+ EMIT = os.environ["TF_EMIT"]; app = os.environ["TF_APP"]; started = os.environ["TF_STARTED"]
48
+ now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
49
+ def q(*a): return subprocess.run(["bash", EMIT, *a], capture_output=True, text=True).stdout.strip()
50
+ def emit(stream, rec):
51
+ p = subprocess.run(["bash", EMIT, stream], input=json.dumps(rec), capture_output=True, text=True)
52
+ msg = (p.stdout + p.stderr).strip(); ok = p.returncode == 0 and "refus" not in msg.lower() and "error" not in msg.lower()
53
+ if not ok: print(f" {stream}: not written — {msg.splitlines()[0][:160] if msg else 'no reason printed'}")
54
+ return ok
55
+ # --- close by miss id (the *amend-docs door) -------------------------------------------
56
+ direct = [m.strip() for m in (os.environ.get("TF_MISSES") or "").split(",") if m.strip()]
57
+ if direct:
58
+ fix_cmd = os.environ.get("TF_FIXCMD") or "amend-docs"
59
+ verdict = os.environ.get("TF_VERDICT") or "Verified"
60
+ closed = 0
61
+ for mid in direct:
62
+ fa = q("--next-fix-attempt", mid) or "1"
63
+ rec = {"kind": "miss-fix", "miss_id": mid, "fix_cmd": fix_cmd,
64
+ "fix_attempt": int(fa) if fa.isdigit() else 1,
65
+ "verdict_after": verdict, "reopened": False}
66
+ if started:
67
+ rec["fix_run_id"] = started
68
+ if emit("misses", rec):
69
+ closed += 1
70
+ print("%s: %d of %d miss-fix record(s) written (no run record — the amending command emits its own)"
71
+ % (fix_cmd, closed, len(direct)))
72
+ raise SystemExit(0)
73
+
74
+ ledger = {}
75
+ try:
76
+ ledger = json.load(open(os.path.join("docs", ".last-verify.json"), encoding="utf-8"))
77
+ except Exception:
78
+ pass
79
+ rows = ledger.get("rows") or {}
80
+ reqs = [r.strip().upper() for r in os.environ["TF_REQS"].split(",") if r.strip()] or sorted(rows)
81
+ subs = [s.strip() for s in os.environ["TF_SUBS"].split(",") if s.strip()]
82
+ files = os.environ["TF_FILES"]
83
+ run = {"kind": "run", "app": app, "cmd": "fix-issues", "mode": "fix", "ended": now, "reqs_touched": reqs, "reqs_count": len(reqs),
84
+ "subagents": subs, "files_written": int(files) if files.isdigit() else len(reqs), "build_result": os.environ["TF_BUILD"]}
85
+ if started: run["started"] = started
86
+ # one run record per start: a close called twice must not count the fix twice
87
+ runs_path = os.path.join(q("--where") or "docs/metrics", "runs.jsonl")
88
+ already = False
89
+ if started and os.path.isfile(runs_path):
90
+ for line in open(runs_path, encoding="utf-8", errors="replace"):
91
+ if '"cmd":"fix-issues"' in line and f'"started":"{started}"' in line:
92
+ already = True
93
+ break
94
+ ok = True if already else emit("runs", run)
95
+ if already:
96
+ print(f"fix-issues: run record for start {started} already exists; not written again")
97
+ closed, none = 0, []
98
+ for rid in reqs:
99
+ opened = q("--open-miss", rid)
100
+ if not opened:
101
+ none.append(rid); continue
102
+ mid = opened.split()[0]
103
+ fa = q("--next-fix-attempt", mid) or "1"
104
+ v = rows.get(rid)
105
+ # verdict_after takes a checklist status (SCHEMA §5.5): the ledger's verdict is mapped, never passed raw
106
+ # (MISS-TechieFlow-20260906-25: RENDER-FAIL was refused and twenty miss-fix records never landed)
107
+ after = ("Verified" if v == "PASS" else "FAIL" if v in ("FAIL", "BUILD-FAIL") else "Needs re-verify")
108
+ rec = {"kind": "miss-fix", "miss_id": mid, "req_id": rid, "fix_cmd": "fix-issues", "fix_attempt": int(fa) if fa.isdigit() else 1,
109
+ "verdict_after": after, "reopened": False}
110
+ if started: rec["fix_run_id"] = started
111
+ if emit("misses", rec): closed += 1
112
+ print(f"fix-issues: run record {'already there' if already else 'written' if ok else 'not written'}; {closed} miss-fix record(s) with the verifier's verdict"
113
+ + (f"; no open miss on {', '.join(none)} (log it first, then re-run)" if none else ""))
114
+ PY
115
+ exit 0
@@ -74,7 +74,7 @@ DRY = os.environ["TF_DRY"] == "1"
74
74
  # ones this audit exists to catch. The file system is the fact.
75
75
  STACKS = [
76
76
  ("dotnet", (".csproj", ".fsproj", ".vbproj", ".sln", ".slnx"),
77
- ["bin/", "obj/", "*.user", "TestResults/"]),
77
+ ["bin/", "obj/", "*.user", "TestResults/", ".vs/"]),
78
78
  ("node", ("package.json",), ["dist/", "build/", ".next/", ".nuxt/", ".turbo/"]),
79
79
  ("python", ("pyproject.toml", "setup.py", "requirements.txt"),
80
80
  ["__pycache__/", "*.py[cod]", ".venv/", "venv/", ".pytest_cache/", "*.egg-info/"]),
@@ -83,6 +83,14 @@ STACKS = [
83
83
  ("java", ("pom.xml", "build.gradle", "build.gradle.kts"), ["target/", "build/", ".gradle/"]),
84
84
  ]
85
85
 
86
+ # Per-developer IDE state, wanted whatever the stack is. It is not build output, but it is
87
+ # the same harm and this tool is the only thing looking: TfLens tracked three .bin files
88
+ # under .vs/ProjectEvaluation/, one carrying 246 absolute paths out of one developer's
89
+ # machine, while the audit ran on that repo every week and said nothing. `.vscode/` is NOT
90
+ # here -- a team often commits it deliberately, and a rule that fights the owner gets
91
+ # switched off. tests/regression/run.sh tf_014.
92
+ IDE_STATE = [".vs/", ".idea/"]
93
+
86
94
  # `package.json` alone does NOT make a repo a Node project, and this exception is
87
95
  # load-bearing rather than fussy: verify-phase §1 runs `npm init -y` in EVERY repo
88
96
  # to provision Playwright, so a bare package.json sits in essentially every .NET
@@ -114,14 +122,19 @@ def _is_real_node(root, files):
114
122
  # `npm init -y` writes exactly one script: a `test` stub that echoes and exits 1.
115
123
  return any(k in scripts for k in ("build", "dist", "bundle", "compile"))
116
124
 
125
+ # A NAME list, never a blanket dot-rule. `not d.startswith(".")` used to prune every
126
+ # dot-directory from the walk -- .vs/, .idea/, .gradle/, .terraform/, .pytest_cache/ --
127
+ # which is close to a complete list of the per-developer state a gitignore audit is FOR.
128
+ # The tool cannot report a folder it never visits, and its silence read exactly like a pass.
117
129
  SKIP_DIRS = {".git", "node_modules", ".tfcore", "bin", "obj", "target", "dist",
118
- "build", ".venv", "venv", "__pycache__", ".next"}
130
+ "build", ".venv", "venv", "__pycache__", ".next", ".claude", ".opencode",
131
+ ".vscode", ".github", ".husky", ".yarn"}
119
132
 
120
133
 
121
134
  def detect():
122
135
  found = set()
123
136
  for root, dirs, files in os.walk(REPO):
124
- dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith(".")]
137
+ dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
125
138
  if root.count(os.sep) - REPO.count(os.sep) > 3:
126
139
  dirs[:] = []
127
140
  for name, markers, _ in STACKS:
@@ -154,6 +167,24 @@ def covered(rule, lines):
154
167
  return False
155
168
 
156
169
 
170
+ def child_only_rules(rule, lines):
171
+ """Rules that ignore something INSIDE `rule` while `rule` itself stays open.
172
+
173
+ This shape is more dangerous than no rule at all, because it looks deliberate:
174
+ TfLens carried `/.vs/App.slnx`, which covers the solution-named subfolder and
175
+ leaves `.vs/ProjectEvaluation/` tracked. The next folder the IDE creates is not
176
+ covered either, and nobody re-reads a line that already mentions the directory."""
177
+ core = rule.strip("/*").rstrip("/")
178
+ out = []
179
+ for l in lines:
180
+ if not l or l.startswith("#"):
181
+ continue
182
+ c = l.lstrip("!").strip().replace("**/", "").strip("/")
183
+ if c != core and (c.startswith(core + "/") or c.startswith(core + "\\")):
184
+ out.append(l.strip())
185
+ return out
186
+
187
+
157
188
  def tracked_paths():
158
189
  """Read .git/index directly. Returns the set of tracked paths, or None when the
159
190
  index cannot be read (a bare repo, a worktree file, a version we do not know) —
@@ -210,8 +241,18 @@ wanted = []
210
241
  for name, _, rules in STACKS:
211
242
  if name in stacks:
212
243
  wanted += [(name, r) for r in rules]
244
+ if stacks:
245
+ wanted += [("ide", r) for r in IDE_STATE if not any(r == w for _n, w in wanted)]
213
246
 
214
247
  missing = [(s, r) for s, r in wanted if not covered(r, lines)]
248
+ # A rule naming a child of a directory that is itself unignored: report it separately,
249
+ # because "MISSING .vs/" reads like nobody thought about it when in fact somebody did
250
+ # and got it half right, which is the harder thing to spot in a review.
251
+ half_done = []
252
+ for _s, r in missing:
253
+ kids = child_only_rules(r, lines)
254
+ if kids:
255
+ half_done.append((r, kids))
215
256
 
216
257
  tracked = tracked_paths()
217
258
  offenders = {}
@@ -244,6 +285,12 @@ else:
244
285
  print(" ⚠ MISSING build-output rules: %s" % names)
245
286
  print(" add them with: bash .tfcore/utils/tf-gitignore-audit.sh . --fix")
246
287
 
288
+ for r, kids in half_done:
289
+ rc |= 1
290
+ print(" ⚠ %s is not ignored, but %s covers a child of it — the parent is open"
291
+ % (r, ", ".join(kids)))
292
+ print(" a rule on a child looks deliberate and leaves every sibling tracked")
293
+
247
294
  if tracked is None:
248
295
  print(" tracked-file check skipped (no readable .git/index) — rules alone do not")
249
296
  print(" prove build output is untracked; re-run where the index is readable")