@techierathore/techieflow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/TechieFlow/agents/analyst.md +97 -0
- package/.claude/commands/TechieFlow/agents/architect.md +84 -0
- package/.claude/commands/TechieFlow/agents/flow-master.md +217 -0
- package/.claude/commands/TechieFlow/agents/verifier.md +72 -0
- package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +138 -0
- package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +98 -0
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +78 -0
- package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +77 -0
- package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +117 -0
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +149 -0
- package/.claude/commands/TechieFlow/tasks/author-brd.md +79 -0
- package/.claude/commands/TechieFlow/tasks/build-phase.md +231 -0
- package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +278 -0
- package/.claude/commands/TechieFlow/tasks/create-doc.md +101 -0
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +507 -0
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +224 -0
- package/.claude/commands/TechieFlow/tasks/devguide.md +224 -0
- package/.claude/commands/TechieFlow/tasks/document-project.md +343 -0
- package/.claude/commands/TechieFlow/tasks/execute-checklist.md +86 -0
- package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +136 -0
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +134 -0
- package/.claude/commands/TechieFlow/tasks/generate-html.md +115 -0
- package/.claude/commands/TechieFlow/tasks/handoff-phase.md +126 -0
- package/.claude/commands/TechieFlow/tasks/index-docs.md +173 -0
- package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +75 -0
- package/.claude/commands/TechieFlow/tasks/log-miss.md +160 -0
- package/.claude/commands/TechieFlow/tasks/metrics-report.md +143 -0
- package/.claude/commands/TechieFlow/tasks/mockups.md +122 -0
- package/.claude/commands/TechieFlow/tasks/productguide.md +105 -0
- package/.claude/commands/TechieFlow/tasks/refresh-status.md +135 -0
- package/.claude/commands/TechieFlow/tasks/render-workflow-docs.md +117 -0
- package/.claude/commands/TechieFlow/tasks/shard-doc.md +185 -0
- package/.claude/commands/TechieFlow/tasks/split-brd.md +131 -0
- package/.claude/commands/TechieFlow/tasks/triage-issues.md +157 -0
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +579 -0
- package/.claude/commands/generate-html.md +124 -0
- package/.codex/config.toml +46 -0
- package/.codex/hooks.json +63 -0
- package/.codex/rules/techieflow.rules +8 -0
- package/.opencode/command/generate-html.md +129 -0
- package/.opencode/plugin/techieflow.js +350 -0
- package/.tfcore/TOKEN-GUIDE.md +59 -0
- package/.tfcore/agents/analyst.md +97 -0
- package/.tfcore/agents/architect.md +84 -0
- package/.tfcore/agents/flow-master.md +217 -0
- package/.tfcore/agents/verifier.md +72 -0
- package/.tfcore/checklists/architect-checklist.md +438 -0
- package/.tfcore/core-config.yaml +47 -0
- package/.tfcore/data/brainstorming-techniques.md +36 -0
- package/.tfcore/data/elicitation-methods.md +154 -0
- package/.tfcore/data/techieflow-kb.md +807 -0
- package/.tfcore/data/technical-preferences.md +3 -0
- package/.tfcore/enhanced-ide-development-workflow.md +248 -0
- package/.tfcore/hooks/block-git.sh +301 -0
- package/.tfcore/hooks/codex-adapter.py +248 -0
- package/.tfcore/hooks/guard-artifacts.sh +101 -0
- package/.tfcore/hooks/guard-status-html.sh +102 -0
- package/.tfcore/hooks/guard-status.sh +124 -0
- package/.tfcore/hooks/guard-verify.sh +108 -0
- package/.tfcore/hooks/metrics-session.sh +129 -0
- package/.tfcore/hooks/session-pointer.sh +52 -0
- package/.tfcore/hooks/sweep-artifacts.sh +198 -0
- package/.tfcore/install-manifest.yaml +231 -0
- package/.tfcore/routing.yaml +105 -0
- package/.tfcore/tasks/_metrics-emit-gate.md +138 -0
- package/.tfcore/tasks/_smoke-test-policy.md +98 -0
- package/.tfcore/tasks/_status-update-gate.md +78 -0
- package/.tfcore/tasks/_yolo-mode.md +77 -0
- package/.tfcore/tasks/advanced-elicitation.md +117 -0
- package/.tfcore/tasks/amend-docs.md +149 -0
- package/.tfcore/tasks/author-brd.md +79 -0
- package/.tfcore/tasks/build-phase.md +231 -0
- package/.tfcore/tasks/create-deep-research-prompt.md +278 -0
- package/.tfcore/tasks/create-doc.md +101 -0
- package/.tfcore/tasks/day1-brownfield.md +507 -0
- package/.tfcore/tasks/day1-greenfield.md +224 -0
- package/.tfcore/tasks/devguide.md +224 -0
- package/.tfcore/tasks/document-project.md +343 -0
- package/.tfcore/tasks/execute-checklist.md +86 -0
- package/.tfcore/tasks/facilitate-brainstorming-session.md +136 -0
- package/.tfcore/tasks/fix-issues.md +134 -0
- package/.tfcore/tasks/generate-html.md +115 -0
- package/.tfcore/tasks/handoff-phase.md +126 -0
- package/.tfcore/tasks/index-docs.md +173 -0
- package/.tfcore/tasks/kb-mode-interaction.md +75 -0
- package/.tfcore/tasks/log-miss.md +160 -0
- package/.tfcore/tasks/metrics-report.md +143 -0
- package/.tfcore/tasks/mockups.md +122 -0
- package/.tfcore/tasks/productguide.md +105 -0
- package/.tfcore/tasks/refresh-status.md +135 -0
- package/.tfcore/tasks/render-workflow-docs.md +117 -0
- package/.tfcore/tasks/shard-doc.md +185 -0
- package/.tfcore/tasks/split-brd.md +131 -0
- package/.tfcore/tasks/triage-issues.md +157 -0
- package/.tfcore/tasks/verify-phase.md +579 -0
- package/.tfcore/telemetry/SCHEMA.md +678 -0
- package/.tfcore/telemetry/install-metrics.sh +390 -0
- package/.tfcore/telemetry/pre-commit +94 -0
- package/.tfcore/telemetry/tf-metrics.sh +1415 -0
- package/.tfcore/templates/architecture-tmpl.yaml +650 -0
- package/.tfcore/templates/brainstorming-output-tmpl.yaml +156 -0
- package/.tfcore/templates/brownfield-architecture-tmpl.yaml +476 -0
- package/.tfcore/templates/competitor-analysis-tmpl.yaml +306 -0
- package/.tfcore/templates/front-end-architecture-tmpl.yaml +218 -0
- package/.tfcore/templates/fullstack-architecture-tmpl.yaml +823 -0
- package/.tfcore/templates/market-research-tmpl.yaml +252 -0
- package/.tfcore/templates/project-brief-tmpl.yaml +221 -0
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +61 -0
- package/.tfcore/templates/v4custom/app-architecture-tmpl.md +127 -0
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +204 -0
- package/.tfcore/templates/v4custom/app-checklist-tmpl.md +90 -0
- package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +33 -0
- package/.tfcore/templates/v4custom/app-devguide-tmpl.md +167 -0
- package/.tfcore/templates/v4custom/app-editorconfig-tmpl.editorconfig +44 -0
- package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +31 -0
- package/.tfcore/templates/v4custom/app-productguide-tmpl.md +54 -0
- package/.tfcore/templates/v4custom/app-project-status-tmpl.md +98 -0
- package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +48 -0
- package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +67 -0
- package/.tfcore/templates/v4custom/build-invocation-ladder.md +190 -0
- package/.tfcore/templates/v4custom/html-render-shell.md +502 -0
- package/.tfcore/templates/v4custom/metrics-report-template.md +273 -0
- package/.tfcore/templates/v4custom/opencode-operating-contract.md +56 -0
- package/.tfcore/user-guide.md +633 -0
- package/.tfcore/utils/techieflow-doc-template.md +325 -0
- package/.tfcore/utils/tf-assets.sh +310 -0
- package/.tfcore/utils/tf-codex-bind.py +196 -0
- package/.tfcore/utils/tf-codex-telemetry.py +60 -0
- package/.tfcore/utils/tf-emit.sh +1075 -0
- package/.tfcore/utils/tf-gitignore-audit.sh +281 -0
- package/.tfcore/utils/tf-goal.sh +421 -0
- package/.tfcore/utils/tf-harness.sh +160 -0
- package/.tfcore/utils/tf-mockup-parity.mjs +555 -0
- package/.tfcore/utils/tf-mockup-parity.sh +65 -0
- package/.tfcore/utils/tf-opencode-audit.py +126 -0
- package/.tfcore/utils/tf-perf.sh +241 -0
- package/.tfcore/utils/tf-render-html.py +715 -0
- package/.tfcore/utils/tf-render-html.sh +28 -0
- package/.tfcore/utils/tf-routing-bind.sh +216 -0
- package/.tfcore/utils/tf-routing.sh +247 -0
- package/.tfcore/utils/tf-yolo.sh +119 -0
- package/.tfcore/working-in-the-brownfield.md +606 -0
- package/LICENSE +201 -0
- package/README.md +1212 -0
- package/WORKFLOW.html +1986 -0
- package/docs/TechieFlow-Installation.md +299 -0
- package/opencode.jsonc +929 -0
- package/package.json +60 -0
- package/scripts/install.mjs +992 -0
- package/scripts/test-install.mjs +505 -0
- package/scripts/validate.mjs +155 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TechieFlow — build-output ignore audit. Run by the scaffolds and by
|
|
3
|
+
# update-framework.sh on every refresh; safe to run by hand at any time.
|
|
4
|
+
#
|
|
5
|
+
# bash .tfcore/utils/tf-gitignore-audit.sh [<repo>] [--fix] [--dry-run]
|
|
6
|
+
#
|
|
7
|
+
# WHAT IT ASKS, and it is two questions rather than one:
|
|
8
|
+
#
|
|
9
|
+
# 1. Does .gitignore carry build-output rules for THE STACK THIS REPO IS
|
|
10
|
+
# ACTUALLY WRITTEN IN?
|
|
11
|
+
# 2. Is that build output already TRACKED?
|
|
12
|
+
#
|
|
13
|
+
# The second half is the half that is usually forgotten, and it is the half that
|
|
14
|
+
# matters: **a tracked file is never ignored, no matter what the ignore file
|
|
15
|
+
# says.** Adding the rule later fixes nothing on its own.
|
|
16
|
+
#
|
|
17
|
+
# WHY IT EXISTS (TfLens TF-007 companion 1, located 2026-08-29). The day-1 scaffold
|
|
18
|
+
# generated a complete, careful `.gitignore` — `.tfcore/`, `.claude/`,
|
|
19
|
+
# `node_modules/`, `tests/.artifacts/`, `playwright-report/`, `logs/` — every
|
|
20
|
+
# section framework-managed and labelled as such. It contained **no `bin/`, no
|
|
21
|
+
# `obj/`, and no rule of any kind for the stack the project was written in**, in a
|
|
22
|
+
# repository whose `core-config.yaml` and four `.csproj` files said .NET throughout.
|
|
23
|
+
#
|
|
24
|
+
# The consequence was mechanical and immediate: the first build produced build
|
|
25
|
+
# output, and a commit named — with some irony — "Updated git ignore" swept **1,041**
|
|
26
|
+
# build-output files into the index. Four later commits reached **1,962**.
|
|
27
|
+
#
|
|
28
|
+
# That is not merely untidy. Those files carry the static-web-assets manifest, whose
|
|
29
|
+
# content roots are MACHINE-ABSOLUTE: `/mnt/c/…` + `/home/<user>/.nuget/…` after a
|
|
30
|
+
# WSL build, `C:\…` + `C:\Users\<user>\.nuget\…` after a Windows build. Committing
|
|
31
|
+
# them ships one machine's absolute paths to another — a plausible route to exactly
|
|
32
|
+
# the asset 404 that TF-007 itself is about.
|
|
33
|
+
#
|
|
34
|
+
# WHOSE FAULT IT IS, stated plainly because it was first stated wrongly. The agent
|
|
35
|
+
# that ran day-1 generated that file and did not read it, having just chosen the
|
|
36
|
+
# stack and written the solution itself. **That agent is responsible.** This script
|
|
37
|
+
# makes the mistake harder to make — which is worth doing precisely because it is so
|
|
38
|
+
# easy to make — but a generator's omission is never a defence for the agent
|
|
39
|
+
# operating the generator.
|
|
40
|
+
#
|
|
41
|
+
# NO GIT. This script contains no git command and never will: `_metrics-emit-gate.md`
|
|
42
|
+
# constraint 1 reserves that for `tf-metrics.sh` alone, and `block-git.sh` is right to
|
|
43
|
+
# block it. The tracked-file check reads `.git/index` directly — a binary file with a
|
|
44
|
+
# documented format, and reading a file is not a git operation. Remediation is PRINTED
|
|
45
|
+
# for the owner to run, never executed.
|
|
46
|
+
#
|
|
47
|
+
# Exit codes: 0 clean (or --fix applied) · 1 rules missing · 2 build output is TRACKED
|
|
48
|
+
# (owner action required) · 3 both.
|
|
49
|
+
|
|
50
|
+
set -uo pipefail
|
|
51
|
+
|
|
52
|
+
REPO="."; FIX=0; DRY=0
|
|
53
|
+
while [[ $# -gt 0 ]]; do
|
|
54
|
+
case "$1" in
|
|
55
|
+
--fix) FIX=1; shift ;;
|
|
56
|
+
--dry-run) DRY=1; shift ;;
|
|
57
|
+
-h|--help) sed -n '2,45p' "$0"; exit 0 ;;
|
|
58
|
+
*) REPO="$1"; shift ;;
|
|
59
|
+
esac
|
|
60
|
+
done
|
|
61
|
+
[[ -d "$REPO" ]] || { echo "tf-gitignore-audit: not a directory: $REPO" >&2; exit 3; }
|
|
62
|
+
command -v python3 >/dev/null 2>&1 || { echo "tf-gitignore-audit: python3 required" >&2; exit 0; }
|
|
63
|
+
|
|
64
|
+
TF_REPO="$REPO" TF_FIX="$FIX" TF_DRY="$DRY" python3 - <<'PY'
|
|
65
|
+
import os, struct, sys
|
|
66
|
+
|
|
67
|
+
REPO = os.path.abspath(os.environ["TF_REPO"])
|
|
68
|
+
FIX = os.environ["TF_FIX"] == "1"
|
|
69
|
+
DRY = os.environ["TF_DRY"] == "1"
|
|
70
|
+
|
|
71
|
+
# --- the stacks, and what each one leaves behind ---------------------------
|
|
72
|
+
# Detected FROM THE TREE, never from a config value: a scaffold copies
|
|
73
|
+
# core-config.yaml between projects, and the day-1 tasks that write it are the very
|
|
74
|
+
# ones this audit exists to catch. The file system is the fact.
|
|
75
|
+
STACKS = [
|
|
76
|
+
("dotnet", (".csproj", ".fsproj", ".vbproj", ".sln", ".slnx"),
|
|
77
|
+
["bin/", "obj/", "*.user", "TestResults/"]),
|
|
78
|
+
("node", ("package.json",), ["dist/", "build/", ".next/", ".nuxt/", ".turbo/"]),
|
|
79
|
+
("python", ("pyproject.toml", "setup.py", "requirements.txt"),
|
|
80
|
+
["__pycache__/", "*.py[cod]", ".venv/", "venv/", ".pytest_cache/", "*.egg-info/"]),
|
|
81
|
+
("go", ("go.mod",), ["/bin/", "*.test"]),
|
|
82
|
+
("rust", ("Cargo.toml",), ["target/"]),
|
|
83
|
+
("java", ("pom.xml", "build.gradle", "build.gradle.kts"), ["target/", "build/", ".gradle/"]),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# `package.json` alone does NOT make a repo a Node project, and this exception is
|
|
87
|
+
# load-bearing rather than fussy: verify-phase §1 runs `npm init -y` in EVERY repo
|
|
88
|
+
# to provision Playwright, so a bare package.json sits in essentially every .NET
|
|
89
|
+
# project the framework has ever touched. Treating that as a Node stack would append
|
|
90
|
+
# `dist/`, `build/`, `.next/`, `.nuxt/`, `.turbo/` to 19 repos that build none of
|
|
91
|
+
# them — noise in a file the owner reads, and `build/` is a plausible real directory
|
|
92
|
+
# name to shadow by accident.
|
|
93
|
+
#
|
|
94
|
+
# So Node is claimed only on evidence of an actual build: a `build` script, or a
|
|
95
|
+
# bundler/framework config on disk. This was caught by the dry run before the
|
|
96
|
+
# propagation, which is what dry runs are for.
|
|
97
|
+
BUNDLER_CONFIGS = ("next.config.js", "next.config.mjs", "next.config.ts",
|
|
98
|
+
"nuxt.config.js", "nuxt.config.ts", "vite.config.js",
|
|
99
|
+
"vite.config.ts", "webpack.config.js", "angular.json",
|
|
100
|
+
"svelte.config.js", "rollup.config.js", "gatsby-config.js",
|
|
101
|
+
"remix.config.js", "astro.config.mjs")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _is_real_node(root, files):
|
|
105
|
+
if any(f in BUNDLER_CONFIGS for f in files):
|
|
106
|
+
return True
|
|
107
|
+
pj = os.path.join(root, "package.json")
|
|
108
|
+
try:
|
|
109
|
+
import json as _json
|
|
110
|
+
data = _json.load(open(pj, encoding="utf-8"))
|
|
111
|
+
except Exception:
|
|
112
|
+
return False
|
|
113
|
+
scripts = data.get("scripts") or {}
|
|
114
|
+
# `npm init -y` writes exactly one script: a `test` stub that echoes and exits 1.
|
|
115
|
+
return any(k in scripts for k in ("build", "dist", "bundle", "compile"))
|
|
116
|
+
|
|
117
|
+
SKIP_DIRS = {".git", "node_modules", ".tfcore", "bin", "obj", "target", "dist",
|
|
118
|
+
"build", ".venv", "venv", "__pycache__", ".next"}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def detect():
|
|
122
|
+
found = set()
|
|
123
|
+
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(".")]
|
|
125
|
+
if root.count(os.sep) - REPO.count(os.sep) > 3:
|
|
126
|
+
dirs[:] = []
|
|
127
|
+
for name, markers, _ in STACKS:
|
|
128
|
+
for f in files:
|
|
129
|
+
if f in markers or any(f.endswith(m) for m in markers if m.startswith(".")):
|
|
130
|
+
if name == "node" and not _is_real_node(root, files):
|
|
131
|
+
continue # a Playwright-only package.json is not a Node app
|
|
132
|
+
found.add(name)
|
|
133
|
+
return found
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def gitignore_lines():
|
|
137
|
+
p = os.path.join(REPO, ".gitignore")
|
|
138
|
+
try:
|
|
139
|
+
return [l.strip().replace("\r", "") for l in open(p, encoding="utf-8", errors="replace")]
|
|
140
|
+
except FileNotFoundError:
|
|
141
|
+
return []
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def covered(rule, lines):
|
|
145
|
+
"""Is this rule already expressed? Match tolerantly — an owner may legitimately
|
|
146
|
+
have written `**/bin/`, `/bin`, or `bin` — and NEVER rewrite existing content."""
|
|
147
|
+
core = rule.strip("/*").rstrip("/")
|
|
148
|
+
for l in lines:
|
|
149
|
+
if not l or l.startswith("#"):
|
|
150
|
+
continue
|
|
151
|
+
c = l.lstrip("!").strip().replace("**/", "").strip("/")
|
|
152
|
+
if c == core or c == rule.strip("/") or c.rstrip("/") == core:
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def tracked_paths():
|
|
158
|
+
"""Read .git/index directly. Returns the set of tracked paths, or None when the
|
|
159
|
+
index cannot be read (a bare repo, a worktree file, a version we do not know) —
|
|
160
|
+
None means 'not checked', never 'nothing tracked'."""
|
|
161
|
+
gitdir = os.path.join(REPO, ".git")
|
|
162
|
+
if os.path.isfile(gitdir): # a worktree: .git is a pointer file
|
|
163
|
+
try:
|
|
164
|
+
for line in open(gitdir, encoding="utf-8"):
|
|
165
|
+
if line.startswith("gitdir:"):
|
|
166
|
+
gd = line.split(":", 1)[1].strip()
|
|
167
|
+
gitdir = gd if os.path.isabs(gd) else os.path.join(REPO, gd)
|
|
168
|
+
break
|
|
169
|
+
except Exception:
|
|
170
|
+
return None
|
|
171
|
+
idx = os.path.join(gitdir, "index")
|
|
172
|
+
try:
|
|
173
|
+
raw = open(idx, "rb").read()
|
|
174
|
+
except Exception:
|
|
175
|
+
return None
|
|
176
|
+
if len(raw) < 12 or raw[:4] != b"DIRC":
|
|
177
|
+
return None
|
|
178
|
+
ver, n = struct.unpack(">II", raw[4:12])
|
|
179
|
+
if ver not in (2, 3, 4):
|
|
180
|
+
return None
|
|
181
|
+
if ver == 4:
|
|
182
|
+
return None # path-compressed; not worth guessing
|
|
183
|
+
out, pos = set(), 12
|
|
184
|
+
try:
|
|
185
|
+
for _ in range(n):
|
|
186
|
+
start = pos
|
|
187
|
+
pos += 62 # fixed entry header
|
|
188
|
+
end = raw.index(b"\x00", pos)
|
|
189
|
+
out.add(raw[pos:end].decode("utf-8", "replace"))
|
|
190
|
+
pos = end + 1
|
|
191
|
+
pos = start + ((pos - start + 7) // 8) * 8 # 8-byte padding
|
|
192
|
+
except Exception:
|
|
193
|
+
return None
|
|
194
|
+
return out
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def matches(path, rule):
|
|
198
|
+
core = rule.strip("/*").rstrip("/")
|
|
199
|
+
if rule.startswith("*."):
|
|
200
|
+
return path.endswith(rule[1:])
|
|
201
|
+
if "[" in rule:
|
|
202
|
+
return path.endswith((".pyc", ".pyo", ".pyd"))
|
|
203
|
+
parts = path.split("/")
|
|
204
|
+
return core in parts[:-1] or parts[-1] == core
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
stacks = detect()
|
|
208
|
+
lines = gitignore_lines()
|
|
209
|
+
wanted = []
|
|
210
|
+
for name, _, rules in STACKS:
|
|
211
|
+
if name in stacks:
|
|
212
|
+
wanted += [(name, r) for r in rules]
|
|
213
|
+
|
|
214
|
+
missing = [(s, r) for s, r in wanted if not covered(r, lines)]
|
|
215
|
+
|
|
216
|
+
tracked = tracked_paths()
|
|
217
|
+
offenders = {}
|
|
218
|
+
if tracked is not None:
|
|
219
|
+
for s, r in wanted:
|
|
220
|
+
hits = [p for p in tracked if matches(p, r)]
|
|
221
|
+
if hits:
|
|
222
|
+
offenders[r] = hits
|
|
223
|
+
|
|
224
|
+
label = ", ".join(sorted(stacks)) or "none detected"
|
|
225
|
+
print(" gitignore audit — stack: %s" % label)
|
|
226
|
+
|
|
227
|
+
rc = 0
|
|
228
|
+
if not stacks:
|
|
229
|
+
print(" no application stack detected yet (docs-only repo, or day-1 has not run)")
|
|
230
|
+
elif not missing:
|
|
231
|
+
print(" build-output rules present for every detected stack")
|
|
232
|
+
else:
|
|
233
|
+
rc |= 1
|
|
234
|
+
names = " ".join(r for _, r in missing)
|
|
235
|
+
if FIX and not DRY:
|
|
236
|
+
with open(os.path.join(REPO, ".gitignore"), "a", encoding="utf-8", newline="\n") as fh:
|
|
237
|
+
fh.write("\n# Build output — %s (managed by scaffold/update-framework.sh)\n" % label)
|
|
238
|
+
fh.write("\n".join(r for _, r in missing) + "\n")
|
|
239
|
+
print(" ADDED build-output rules: %s" % names)
|
|
240
|
+
rc &= ~1
|
|
241
|
+
elif DRY:
|
|
242
|
+
print(" WOULD add build-output rules: %s" % names)
|
|
243
|
+
else:
|
|
244
|
+
print(" ⚠ MISSING build-output rules: %s" % names)
|
|
245
|
+
print(" add them with: bash .tfcore/utils/tf-gitignore-audit.sh . --fix")
|
|
246
|
+
|
|
247
|
+
if tracked is None:
|
|
248
|
+
print(" tracked-file check skipped (no readable .git/index) — rules alone do not")
|
|
249
|
+
print(" prove build output is untracked; re-run where the index is readable")
|
|
250
|
+
elif offenders:
|
|
251
|
+
rc |= 2
|
|
252
|
+
total = sum(len(v) for v in offenders.values())
|
|
253
|
+
print(" ⚠ %d build-output file(s) are ALREADY TRACKED. An ignore rule does NOT" % total)
|
|
254
|
+
print(" untrack a file — until these are removed from the index, every rule")
|
|
255
|
+
print(" above is inert and one machine's absolute paths keep shipping to the next.")
|
|
256
|
+
for r, hits in sorted(offenders.items()):
|
|
257
|
+
ex = sorted(hits)[:3]
|
|
258
|
+
print(" %-14s %4d file(s) e.g. %s" % (r, len(hits), ", ".join(ex)))
|
|
259
|
+
print(" OWNER ACTION (this script never runs git, and never will):")
|
|
260
|
+
# The path printed must be the OUTPUT directory itself, never an ancestor:
|
|
261
|
+
# `git rm -r --cached src/App` would untrack the source alongside the artefacts,
|
|
262
|
+
# which is a far worse outcome than the problem being fixed.
|
|
263
|
+
targets = set()
|
|
264
|
+
for r, hits in sorted(offenders.items()):
|
|
265
|
+
core = r.strip("/*").rstrip("/")
|
|
266
|
+
for h in hits:
|
|
267
|
+
parts = h.split("/")
|
|
268
|
+
if core in parts[:-1]:
|
|
269
|
+
targets.add("/".join(parts[:parts.index(core) + 1]))
|
|
270
|
+
else:
|
|
271
|
+
targets.add(h) # a file rule (*.user, *.test)
|
|
272
|
+
for t in sorted(targets)[:12]:
|
|
273
|
+
print(" git rm -r --cached '%s'" % t)
|
|
274
|
+
if len(targets) > 12:
|
|
275
|
+
print(" ... and %d more path(s); see the full list above" % (len(targets) - 12))
|
|
276
|
+
print(" then commit. The working files stay on disk; only the index entries go.")
|
|
277
|
+
else:
|
|
278
|
+
print(" no build output is tracked")
|
|
279
|
+
|
|
280
|
+
raise SystemExit(rc)
|
|
281
|
+
PY
|