@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,390 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TechieFlow telemetry — per-repo setup. Called AUTOMATICALLY by
|
|
3
|
+
# update-framework.sh, scaffold-brownfield.sh and scaffold-greenfield.sh.
|
|
4
|
+
#
|
|
5
|
+
# You do NOT normally run this yourself. Refreshing the framework sets up
|
|
6
|
+
# telemetry as part of the same command — there is no separate install step.
|
|
7
|
+
#
|
|
8
|
+
# Run it directly only to CORRECT the project classification:
|
|
9
|
+
# .tfcore/telemetry/install-metrics.sh <repo> --type library
|
|
10
|
+
#
|
|
11
|
+
# WHAT IT DOES (idempotent, non-interactive, safe on every re-run):
|
|
12
|
+
# 1. creates docs/metrics/ and seeds the four empty streams
|
|
13
|
+
# 2. writes docs/metrics/README.md (for a human who finds the files)
|
|
14
|
+
# 3. records metrics.project_type in .tfcore/core-config.yaml — auto-detected
|
|
15
|
+
# ONCE, printed loudly, then never touched again (core-config.yaml is a
|
|
16
|
+
# preserved file, so the classification survives every framework refresh)
|
|
17
|
+
# 4. installs .git/hooks/pre-commit (and retires the old post-commit)
|
|
18
|
+
# 5. warns if a .gitignore pattern would swallow docs/metrics/
|
|
19
|
+
#
|
|
20
|
+
# IT NEVER INVOKES GIT. The hooks directory is located by reading the filesystem
|
|
21
|
+
# (.git/ as a directory, or the `gitdir:` pointer when .git is a file), so this
|
|
22
|
+
# script runs identically whether a human or an agent triggered the refresh, and
|
|
23
|
+
# .tfcore/hooks/block-git.sh stays exactly as it is. Installing a hook is a file
|
|
24
|
+
# copy; it is not a git operation.
|
|
25
|
+
#
|
|
26
|
+
# Telemetry has no veto: this script prints warnings, never aborts its caller.
|
|
27
|
+
set -uo pipefail
|
|
28
|
+
|
|
29
|
+
TEMPLATE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
30
|
+
|
|
31
|
+
TARGET=""
|
|
32
|
+
PTYPE=""
|
|
33
|
+
DRY=0
|
|
34
|
+
QUIET=0
|
|
35
|
+
while [[ $# -gt 0 ]]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
--type) PTYPE="${2:-}"; shift 2 ;;
|
|
38
|
+
--dry-run) DRY=1; shift ;;
|
|
39
|
+
--quiet) QUIET=1; shift ;;
|
|
40
|
+
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
|
41
|
+
*) TARGET="$1"; shift ;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
say() { [[ $QUIET -eq 1 ]] || echo "$@"; }
|
|
46
|
+
|
|
47
|
+
[[ -n "$TARGET" ]] || { echo "usage: install-metrics.sh <repo> [--type app|library|docs|framework] [--dry-run]" >&2; exit 1; }
|
|
48
|
+
[[ -d "$TARGET" ]] || { echo "not a directory: $TARGET" >&2; exit 1; }
|
|
49
|
+
cd "$TARGET" || exit 1
|
|
50
|
+
TARGET="$(pwd -P)"
|
|
51
|
+
|
|
52
|
+
[[ -d .tfcore ]] || { say " telemetry — no .tfcore/ here, skipped"; exit 0; }
|
|
53
|
+
|
|
54
|
+
CFG=".tfcore/core-config.yaml"
|
|
55
|
+
|
|
56
|
+
# --- 1. project_type -----------------------------------------------------
|
|
57
|
+
# Read whatever is already recorded. Once set, it is NEVER re-guessed — the
|
|
58
|
+
# owner's classification always wins over the heuristic.
|
|
59
|
+
EXISTING=""
|
|
60
|
+
if [[ -f "$CFG" ]]; then
|
|
61
|
+
EXISTING="$(sed -n '/^metrics:[[:space:]]*$/,/^[^[:space:]#]/p' "$CFG" 2>/dev/null \
|
|
62
|
+
| sed -n 's/^[[:space:]]\{1,\}project_type:[[:space:]]*["'"'"']\{0,1\}\([a-z]*\).*/\1/p' | head -1)"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Heuristic, run ONCE per repo and then never again (the answer is preserved in
|
|
66
|
+
# core-config.yaml). Kept deliberately cheap: build output and package caches are
|
|
67
|
+
# PRUNED and the walk is depth-limited, because an unpruned scan of a large repo on
|
|
68
|
+
# a /mnt/c mount takes minutes — and this runs inside every framework refresh.
|
|
69
|
+
_scan() { # _scan <maxdepth> <find-expr...> -> prints the first match, then stops
|
|
70
|
+
find . -maxdepth "$1" \
|
|
71
|
+
\( -type d \( -name bin -o -name obj -o -name node_modules -o -name .git \
|
|
72
|
+
-o -name .vs -o -name artifacts -o -name TestResults \) -prune \) \
|
|
73
|
+
-o "${@:2}" -print 2>/dev/null | head -1
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_csprojs() {
|
|
77
|
+
find . -maxdepth 5 \
|
|
78
|
+
\( -type d \( -name bin -o -name obj -o -name node_modules -o -name .git \
|
|
79
|
+
-o -name .vs -o -name artifacts \) -prune \) \
|
|
80
|
+
-o -name '*.csproj' -print 2>/dev/null | head -60
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
detect_type() {
|
|
84
|
+
# framework: this repo DEPLOYS the framework rather than consuming it
|
|
85
|
+
[[ -f scaffold-brownfield.sh && -d .tfcore/tasks ]] && { echo framework; return; }
|
|
86
|
+
|
|
87
|
+
local projects packable heads name
|
|
88
|
+
projects="$(_csprojs)"
|
|
89
|
+
|
|
90
|
+
# docs: a markdown/spec repo — no source at all
|
|
91
|
+
[[ -z "$projects" ]] && { echo docs; return; }
|
|
92
|
+
|
|
93
|
+
# Packable = genuinely published. IsPackable is usually <IsPackable>false</IsPackable>
|
|
94
|
+
# on test projects, so only an explicit `true` counts.
|
|
95
|
+
packable="$(printf '%s\n' "$projects" | xargs -r grep -lE \
|
|
96
|
+
'<(PackageId>|GeneratePackageOnBuild>[[:space:]]*true|IsPackable>[[:space:]]*true)' 2>/dev/null)"
|
|
97
|
+
|
|
98
|
+
# A shipped executable/hostable head: console/desktop (Exe), MAUI, or ASP.NET/Blazor host.
|
|
99
|
+
heads="$(printf '%s\n' "$projects" | xargs -r grep -lE \
|
|
100
|
+
'<OutputType>[[:space:]]*Exe|<UseMaui>[[:space:]]*true|Sdk="Microsoft\.NET\.Sdk\.(Web|BlazorWebAssembly)"' 2>/dev/null)"
|
|
101
|
+
|
|
102
|
+
# THE DECIDING TEST: is a packaged project the product, or merely a helper?
|
|
103
|
+
# A library repo's package carries the repo's own name (TrBlazeUI.Components);
|
|
104
|
+
# an app's packable support libs do not (AstroLyfe ships AstroCore, SwissEphStd
|
|
105
|
+
# and is still an app). Name-match beats "has any packable project", which would
|
|
106
|
+
# misclassify every app that factors a NuGet-published helper out of its solution.
|
|
107
|
+
name="$(ls docs/*-Checklist.md 2>/dev/null | head -1)"
|
|
108
|
+
name="$(basename "${name:-$PWD}" 2>/dev/null)"; name="${name%-Checklist.md}"
|
|
109
|
+
[[ -z "$name" ]] && name="$(basename "$PWD")"
|
|
110
|
+
if [[ -n "$packable" ]] && printf '%s\n' "$packable" \
|
|
111
|
+
| grep -qiE "/${name}(\.[A-Za-z0-9._-]+)?\.csproj$"; then
|
|
112
|
+
echo library; return
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# A shipped head means it is an app, however many helper packages ride along.
|
|
116
|
+
[[ -n "$heads" ]] && { echo app; return; }
|
|
117
|
+
|
|
118
|
+
# No head at all, but something is published -> a library.
|
|
119
|
+
[[ -n "$packable" ]] && { echo library; return; }
|
|
120
|
+
|
|
121
|
+
# Last resorts: runtime screens -> app; otherwise assume app and let the owner correct.
|
|
122
|
+
[[ -n "$(_scan 6 \( -name '*.razor' -o -name '*.xaml' \))" ]] && { echo app; return; }
|
|
123
|
+
echo app
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
GUESSED=0
|
|
127
|
+
UPGRADED=0
|
|
128
|
+
if [[ -n "$PTYPE" ]]; then
|
|
129
|
+
:
|
|
130
|
+
elif [[ "$EXISTING" == "docs" ]]; then
|
|
131
|
+
# `docs` AT SCAFFOLD TIME IS NOT A CLASSIFICATION — IT IS THE ABSENCE OF ONE.
|
|
132
|
+
#
|
|
133
|
+
# scaffold-greenfield.sh runs this script at scaffold time, when the repo is by
|
|
134
|
+
# definition docs-only: the day-1 documents exist and src/ does not. detect_type()
|
|
135
|
+
# correctly answers `docs`, writes it, and the "auto-detected once, then never
|
|
136
|
+
# re-guessed" rule freezes it there. So EVERY greenfield project is born labelled
|
|
137
|
+
# `docs` and stays that way until somebody notices — which cost TfLens 225 gate
|
|
138
|
+
# records (visual gates included) filed under a project_type whose definition says
|
|
139
|
+
# gates cannot fire, and whose figures never pool with the apps it belongs beside.
|
|
140
|
+
#
|
|
141
|
+
# So `docs` alone is re-examined on a later refresh, and ONLY upgrades: once real
|
|
142
|
+
# heads or a published package appear, the tree has answered the question that was
|
|
143
|
+
# unanswerable at scaffold time. Everything else is left exactly as it was —
|
|
144
|
+
# app/library/framework are never re-guessed, and an owner's `--type` always wins
|
|
145
|
+
# (it takes the branch above). A genuine docs repo never grows a head, so it is
|
|
146
|
+
# never touched.
|
|
147
|
+
REDETECTED="$(detect_type)"
|
|
148
|
+
if [[ -n "$REDETECTED" && "$REDETECTED" != "docs" ]]; then
|
|
149
|
+
PTYPE="$REDETECTED"; GUESSED=1; UPGRADED=1
|
|
150
|
+
else
|
|
151
|
+
PTYPE="$EXISTING"
|
|
152
|
+
fi
|
|
153
|
+
elif [[ -n "$EXISTING" ]]; then
|
|
154
|
+
PTYPE="$EXISTING"
|
|
155
|
+
else
|
|
156
|
+
PTYPE="$(detect_type)"
|
|
157
|
+
GUESSED=1
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
case "$PTYPE" in
|
|
161
|
+
app|library|docs|framework) ;;
|
|
162
|
+
*) echo " ⚠ telemetry — invalid project_type '$PTYPE' (app|library|docs|framework); leaving unset" >&2; PTYPE="$EXISTING" ;;
|
|
163
|
+
esac
|
|
164
|
+
|
|
165
|
+
# THE FRAMEWORK REPO CLASSIFIES ITSELF, AND NOTHING IS WRITTEN DOWN.
|
|
166
|
+
#
|
|
167
|
+
# This file is the template. scaffold-greenfield/brownfield copy .tfcore/ with
|
|
168
|
+
# `rsync -a --ignore-existing`, so core-config.yaml lands in a brand-new app
|
|
169
|
+
# verbatim — and an existing classification always wins over the heuristic
|
|
170
|
+
# (EXISTING above). Writing `project_type: framework` here would therefore stamp
|
|
171
|
+
# `framework` on every app scaffolded afterwards, silently, with no way for the
|
|
172
|
+
# owner to notice until a report pooled them wrongly.
|
|
173
|
+
#
|
|
174
|
+
# So the readers detect it structurally instead (tf-emit.sh _project_type(),
|
|
175
|
+
# tf-metrics.sh project_type()): scaffold-brownfield.sh beside .tfcore/tasks/ IS
|
|
176
|
+
# the framework template and nothing else is. Same answer, nothing to leak.
|
|
177
|
+
if [[ "$PTYPE" == "framework" && -f scaffold-brownfield.sh && -d .tfcore/tasks ]]; then
|
|
178
|
+
say " $CFG — project_type: framework, DETECTED from the tree, not recorded"
|
|
179
|
+
say " (this file is the scaffold template — writing it here would"
|
|
180
|
+
say " misclassify every app created from it)"
|
|
181
|
+
elif [[ -n "$PTYPE" && "$EXISTING" != "$PTYPE" ]]; then
|
|
182
|
+
if [[ $DRY -eq 1 ]]; then
|
|
183
|
+
if [[ $GUESSED -eq 1 ]]; then
|
|
184
|
+
say " $CFG — WOULD set metrics.project_type: $PTYPE (auto-detected)"
|
|
185
|
+
else
|
|
186
|
+
say " $CFG — WOULD set metrics.project_type: $PTYPE"
|
|
187
|
+
fi
|
|
188
|
+
elif [[ -n "$EXISTING" ]]; then
|
|
189
|
+
python3 - "$CFG" "$PTYPE" <<'PY' 2>/dev/null
|
|
190
|
+
import re, sys
|
|
191
|
+
p, t = sys.argv[1], sys.argv[2]
|
|
192
|
+
s = open(p, encoding="utf-8").read()
|
|
193
|
+
s = re.sub(r"(^metrics:[ \t]*\n(?:[ \t]+.*\n)*?[ \t]+project_type:[ \t]*)\S+",
|
|
194
|
+
lambda m: m.group(1) + t, s, count=1, flags=re.M)
|
|
195
|
+
open(p, "w", encoding="utf-8").write(s)
|
|
196
|
+
PY
|
|
197
|
+
if [[ $UPGRADED -eq 1 ]]; then
|
|
198
|
+
say " $CFG — metrics.project_type UPGRADED 'docs' → '$PTYPE'"
|
|
199
|
+
say " ('docs' was recorded at scaffold time, before this repo had any"
|
|
200
|
+
say " source. Records already written carry the old value — see"
|
|
201
|
+
say " .tfcore/telemetry/SCHEMA.md §1.)"
|
|
202
|
+
else
|
|
203
|
+
say " $CFG — metrics.project_type changed to '$PTYPE'"
|
|
204
|
+
fi
|
|
205
|
+
else
|
|
206
|
+
{
|
|
207
|
+
echo ""
|
|
208
|
+
echo "# metrics: telemetry classification. PRESERVED per-project — the framework"
|
|
209
|
+
echo "# scripts never overwrite core-config.yaml, so this survives every refresh."
|
|
210
|
+
echo "# app | library | docs | framework — see .tfcore/telemetry/SCHEMA.md §1."
|
|
211
|
+
echo "# Gate-catch metrics are only comparable between projects of the SAME type:"
|
|
212
|
+
echo "# a library has no screens, so it can never fail the visual-truth gate."
|
|
213
|
+
echo "metrics:"
|
|
214
|
+
echo " project_type: $PTYPE"
|
|
215
|
+
} >> "$CFG"
|
|
216
|
+
if [[ $GUESSED -eq 1 ]]; then
|
|
217
|
+
say " $CFG — metrics.project_type: $PTYPE (AUTO-DETECTED — correct it with:"
|
|
218
|
+
say " .tfcore/telemetry/install-metrics.sh . --type app|library|docs|framework)"
|
|
219
|
+
else
|
|
220
|
+
say " $CFG — metrics.project_type: $PTYPE"
|
|
221
|
+
fi
|
|
222
|
+
fi
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# --- 2. docs/metrics/ + the five streams ---------------------------------
|
|
226
|
+
if [[ $DRY -eq 1 ]]; then
|
|
227
|
+
[[ -d docs/metrics ]] && say " docs/metrics/ — present" \
|
|
228
|
+
|| say " docs/metrics/ — WOULD create + seed runs/gates/sessions/commits/misses.jsonl"
|
|
229
|
+
else
|
|
230
|
+
mkdir -p docs/metrics
|
|
231
|
+
NEW=()
|
|
232
|
+
for s in runs gates sessions commits misses; do
|
|
233
|
+
[[ -f "docs/metrics/$s.jsonl" ]] || { : > "docs/metrics/$s.jsonl"; NEW+=("$s"); }
|
|
234
|
+
done
|
|
235
|
+
[[ ${#NEW[@]} -gt 0 ]] && say " docs/metrics/ — seeded ${NEW[*]}" || say " docs/metrics/ — streams present"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# --- 3. docs/metrics/README.md -------------------------------------------
|
|
239
|
+
if [[ $DRY -eq 0 ]]; then
|
|
240
|
+
cat > docs/metrics/README.md <<'MD'
|
|
241
|
+
# docs/metrics — development telemetry
|
|
242
|
+
|
|
243
|
+
Append-only JSONL. **Tracked by git on purpose** — this is the project's own
|
|
244
|
+
development history, and it is the one thing the framework cannot reconstruct
|
|
245
|
+
after the fact.
|
|
246
|
+
|
|
247
|
+
| File | One record per | Written by |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| `runs.jsonl` | framework command run | the task, at completion |
|
|
250
|
+
| `gates.jsonl` | REQ verdict per verify run — **the primary stream** | `verify-phase` §6a, `triage-issues` |
|
|
251
|
+
| `sessions.jsonl` | agent session | the `SessionEnd` hook |
|
|
252
|
+
| `commits.jsonl` | commit | the repo's own `pre-commit` hook |
|
|
253
|
+
| `misses.jsonl` | a requirement/behaviour an agent MISSED, and what fixing it cost | `verify-phase`, `build-phase`, `triage-issues`, `fix-issues`, `amend-docs`, `*log-miss` |
|
|
254
|
+
|
|
255
|
+
`misses.jsonl` is the one stream with **three** record kinds — `miss` (opened: what
|
|
256
|
+
was missed, which phase/agent/model let it through, who found it), `miss-fix`
|
|
257
|
+
(closed: the repair run and its token/cost window, linked by `miss_id`) and
|
|
258
|
+
`miss-amend` (completes a field the `miss` left empty — it can fill a `null` and
|
|
259
|
+
can never overwrite a value, so it adds to the history without revising it). It is
|
|
260
|
+
what makes "how much did that miss cost to fix" answerable. SCHEMA.md §5.5.
|
|
261
|
+
|
|
262
|
+
Schema, enums, and every known limitation: `.tfcore/telemetry/SCHEMA.md`.
|
|
263
|
+
Report: `/TechieFlow:agents:flow-master *metrics <AppName>` (OpenCode: `/flow-master *metrics <AppName>`) → `METRICS.md`.
|
|
264
|
+
|
|
265
|
+
**All five files are created empty, on purpose, and an empty one is not a fault.**
|
|
266
|
+
The installer seeds the set so every repo has the same shape and no writer has to
|
|
267
|
+
guess whether its stream exists. A stream stays at zero bytes until something
|
|
268
|
+
actually happens: `gates.jsonl` until the first `*verify`, `runs.jsonl` until the
|
|
269
|
+
first framework command, `sessions.jsonl` until the first agent session ends, and
|
|
270
|
+
`commits.jsonl` until your first commit after telemetry was installed, and
|
|
271
|
+
`misses.jsonl` until an agent misses something (which it will). Commit
|
|
272
|
+
these empty files along with the rest — a tracked empty stream is what makes the
|
|
273
|
+
first record a one-line diff instead of a new file appearing from nowhere.
|
|
274
|
+
|
|
275
|
+
**Never edit these files by hand, never sort them, never compact them.** They are
|
|
276
|
+
a log. Rewriting one destroys exactly the history it exists to keep. To correct or
|
|
277
|
+
complete a record, append another one: a later `gates.jsonl` record supersedes an
|
|
278
|
+
earlier verdict, a `miss-fix` closes a `miss`, and
|
|
279
|
+
`bash .tfcore/utils/tf-emit.sh --amend <miss_id> <field> <value>` fills a field a
|
|
280
|
+
`miss` left empty (it refuses to overwrite one that is not empty). If nothing fits
|
|
281
|
+
what you need to correct, say so rather than editing — that is a framework defect
|
|
282
|
+
worth reporting, and it is how the amend path came to exist.
|
|
283
|
+
|
|
284
|
+
**No secrets, no content, no client data** — records carry IDs, counts, durations,
|
|
285
|
+
verdicts and file paths at most. Never requirement text, prompt text, file
|
|
286
|
+
contents, or commit subjects. Assume every line here could become public.
|
|
287
|
+
|
|
288
|
+
## Working on more than one machine
|
|
289
|
+
|
|
290
|
+
`.gitattributes` gives these streams `merge=union`, so two machines appending to
|
|
291
|
+
the same file keep **both** sides' lines instead of conflicting. Pull, push, carry
|
|
292
|
+
on — you never hand-resolve a log, which is the one way records get silently
|
|
293
|
+
dropped. Union merge can leave a record duplicated or out of chronological order;
|
|
294
|
+
every consumer sorts on `ts` and de-duplicates commits on `sha`, so neither costs
|
|
295
|
+
you anything.
|
|
296
|
+
|
|
297
|
+
**`commits.jsonl` needs no collecting.** The `pre-commit` hook *reconciles*: it
|
|
298
|
+
writes a record for every commit reachable from HEAD that the file does not
|
|
299
|
+
already have, then stages that one file so the records ship **inside** the commit
|
|
300
|
+
you are making. So after you pull another machine's work, your next commit here
|
|
301
|
+
records all of it. The commit log is itself an append-only log that push and pull
|
|
302
|
+
already replicate everywhere; this stream is a projection of it.
|
|
303
|
+
|
|
304
|
+
Three things worth knowing:
|
|
305
|
+
|
|
306
|
+
- **It stages exactly one path** — `docs/metrics/commits.jsonl`, nothing else. On
|
|
307
|
+
a partial commit (`git commit -- <paths>`) it writes the record but does **not**
|
|
308
|
+
stage, so it can never smuggle a file into a commit you deliberately scoped.
|
|
309
|
+
- **The lag is one commit, and it is committed rather than pending.** At
|
|
310
|
+
pre-commit time HEAD is still the previous commit, so the record for the commit
|
|
311
|
+
you are making ships in the next one. Your working tree is clean when the commit
|
|
312
|
+
finishes — that is the whole reason this is a pre-commit hook and not a
|
|
313
|
+
post-commit one.
|
|
314
|
+
- The hook lives in `.git/hooks/`, which is **not** part of the repository, so
|
|
315
|
+
every clone needs its own. `update-framework.sh <repo>` installs it, and
|
|
316
|
+
`tf-metrics.sh --report` warns when the clone you are standing in has none. If
|
|
317
|
+
you already have your own `pre-commit` hook, the installer leaves it alone and
|
|
318
|
+
tells you — add `bash .tfcore/telemetry/pre-commit` to it if you want both.
|
|
319
|
+
|
|
320
|
+
Merge commits, `--no-verify`, rebases and cherry-picks skip the hook entirely.
|
|
321
|
+
Nothing is lost: reconciling means the next ordinary commit — here or on any
|
|
322
|
+
machine that pulls — notices those commits are missing and writes them.
|
|
323
|
+
|
|
324
|
+
To fill in a machine's history immediately rather than waiting for a commit:
|
|
325
|
+
|
|
326
|
+
.tfcore/telemetry/tf-metrics.sh --backfill-commits .
|
|
327
|
+
|
|
328
|
+
Idempotent — already-recorded shas are skipped — so run it as often as you like.
|
|
329
|
+
It is also why the hook is optional: delete it and reconcile by hand instead.
|
|
330
|
+
MD
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
# --- 4. pre-commit hook — located WITHOUT invoking git -------------------
|
|
334
|
+
# Since 2026-08-11 the commit-telemetry hook is **pre**-commit, so the record
|
|
335
|
+
# ships INSIDE the commit and the working tree is clean afterwards. The old
|
|
336
|
+
# post-commit hook wrote its line after the commit was sealed, which left
|
|
337
|
+
# commits.jsonl permanently dirty with no reachable clean state. Our own
|
|
338
|
+
# post-commit is therefore REMOVED here; a foreign one is never touched.
|
|
339
|
+
HOOKS_DIR=""
|
|
340
|
+
if [[ -d .git ]]; then
|
|
341
|
+
HOOKS_DIR=".git/hooks"
|
|
342
|
+
elif [[ -f .git ]]; then
|
|
343
|
+
# worktree / submodule: ".git" is a file containing "gitdir: <path>"
|
|
344
|
+
GD="$(sed -n 's/^gitdir:[[:space:]]*//p' .git | head -1)"
|
|
345
|
+
[[ -n "$GD" ]] && { [[ "$GD" = /* ]] || GD="$TARGET/$GD"; [[ -d "$GD" ]] && HOOKS_DIR="$GD/hooks"; }
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if [[ -z "$HOOKS_DIR" ]]; then
|
|
349
|
+
say " pre-commit — not a work tree yet; commits.jsonl stays empty (re-run the updater after init)"
|
|
350
|
+
else
|
|
351
|
+
SRC="$TEMPLATE/.tfcore/telemetry/pre-commit"
|
|
352
|
+
DEST="$HOOKS_DIR/pre-commit"
|
|
353
|
+
OLD="$HOOKS_DIR/post-commit"
|
|
354
|
+
|
|
355
|
+
# 4a. retire OUR post-commit. Identified by the template's own marker, so a
|
|
356
|
+
# hook you wrote yourself is left exactly where it is.
|
|
357
|
+
if [[ -f "$OLD" ]] && grep -q 'TechieFlow telemetry' "$OLD" 2>/dev/null; then
|
|
358
|
+
if [[ $DRY -eq 1 ]]; then
|
|
359
|
+
say " $OLD — WOULD remove (superseded by pre-commit)"
|
|
360
|
+
else
|
|
361
|
+
rm -f "$OLD" && say " $OLD — removed (superseded by pre-commit)"
|
|
362
|
+
fi
|
|
363
|
+
fi
|
|
364
|
+
|
|
365
|
+
# 4b. install pre-commit. A pre-commit hook is where people put lint and test
|
|
366
|
+
# gates, so unlike the old post-commit path this NEVER replaces one it did
|
|
367
|
+
# not write. Losing someone's lint gate to a telemetry install would be a
|
|
368
|
+
# far worse outcome than not collecting commit records.
|
|
369
|
+
if [[ -f "$DEST" ]] && ! grep -q 'TechieFlow telemetry' "$DEST" 2>/dev/null; then
|
|
370
|
+
echo " ⚠ $DEST already exists and is NOT the TechieFlow hook — left untouched."
|
|
371
|
+
echo " Commit telemetry is not installed here. To have both, add this line to"
|
|
372
|
+
echo " your hook: bash .tfcore/telemetry/pre-commit"
|
|
373
|
+
echo " (or reconcile by hand: tf-metrics.sh --backfill-commits .)"
|
|
374
|
+
elif [[ -f "$DEST" ]] && cmp -s "$SRC" "$DEST"; then
|
|
375
|
+
say " $DEST — already current"
|
|
376
|
+
elif [[ $DRY -eq 1 ]]; then
|
|
377
|
+
say " $DEST — WOULD install the commit-telemetry hook"
|
|
378
|
+
else
|
|
379
|
+
mkdir -p "$HOOKS_DIR" 2>/dev/null
|
|
380
|
+
cp "$SRC" "$DEST" && chmod +x "$DEST" && say " $DEST — installed"
|
|
381
|
+
fi
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
# --- 5. the data must stay TRACKED --------------------------------------
|
|
385
|
+
if [[ -f .gitignore ]] && tr -d '\r' < .gitignore | grep -qE '^/?docs/?$|^/?docs/metrics'; then
|
|
386
|
+
echo " ⚠ .gitignore has a pattern that would swallow docs/metrics/ — the telemetry data"
|
|
387
|
+
echo " MUST be tracked. Add an explicit negation: !docs/metrics/ and !docs/metrics/**"
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
exit 0
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TechieFlow telemetry — pre-commit hook TEMPLATE.
|
|
3
|
+
# Installed into <app>/.git/hooks/pre-commit by .tfcore/telemetry/install-metrics.sh.
|
|
4
|
+
# It REPLACES the older post-commit hook (which that installer removes).
|
|
5
|
+
#
|
|
6
|
+
# WHY PRE-COMMIT, WHEN THE DESIGN SAID POST-COMMIT (owner decision, 2026-08-11).
|
|
7
|
+
#
|
|
8
|
+
# post-commit can only write the record for commit N *after* N exists, so that
|
|
9
|
+
# line can never be inside N. The file was therefore dirty the instant every
|
|
10
|
+
# commit finished — permanently, with no reachable clean state: committing the
|
|
11
|
+
# pending line creates a new commit, whose record is then pending in turn.
|
|
12
|
+
# "Just commit it first" is circular advice, and on a repo worked from more
|
|
13
|
+
# than one machine it also blocks `git pull` every time.
|
|
14
|
+
#
|
|
15
|
+
# Writing before the commit is sealed fixes that, at the price this hook is
|
|
16
|
+
# honest about: it stages one file into the commit you are making.
|
|
17
|
+
#
|
|
18
|
+
# WHAT IT STAGES, AND NOTHING ELSE: docs/metrics/commits.jsonl
|
|
19
|
+
#
|
|
20
|
+
# Not `git add -A`, not a directory, not anything you did not already have.
|
|
21
|
+
# If that file is unchanged the add is a no-op. It cannot introduce source
|
|
22
|
+
# changes into a commit, because it only ever touches this one path.
|
|
23
|
+
#
|
|
24
|
+
# THE LAG MOVES BUT DOES NOT GO AWAY. At pre-commit time HEAD is still the
|
|
25
|
+
# PREVIOUS commit, so this records everything up to N-1; commit N's own record
|
|
26
|
+
# ships inside N+1. The difference from before is that the record is *committed*
|
|
27
|
+
# rather than *pending* — the working tree is clean when the commit finishes.
|
|
28
|
+
#
|
|
29
|
+
# EVERY SKIP IS SELF-HEALING, because this reconciles from the log rather than
|
|
30
|
+
# appending one line. Merge commits (git runs pre-merge-commit instead),
|
|
31
|
+
# `--no-verify`, rebase, cherry-pick and partial commits all record nothing here
|
|
32
|
+
# — and the next ordinary commit, on this machine or any other that pulls, finds
|
|
33
|
+
# them missing from the file and writes them. Nothing needs to be caught up by
|
|
34
|
+
# hand, ever.
|
|
35
|
+
#
|
|
36
|
+
# NEVER STORES THE COMMIT SUBJECT — only its conventional-commit prefix, if any.
|
|
37
|
+
#
|
|
38
|
+
# IT NEVER FAILS YOUR COMMIT. A pre-commit hook that exits non-zero ABORTS the
|
|
39
|
+
# commit, and telemetry has no veto in this framework (.tfcore/tasks/
|
|
40
|
+
# _metrics-emit-gate.md). Every path here ends in `exit 0`.
|
|
41
|
+
|
|
42
|
+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
|
43
|
+
[[ -n "$ROOT" ]] || exit 0
|
|
44
|
+
EMIT="$ROOT/.tfcore/utils/tf-emit.sh"
|
|
45
|
+
[[ -f "$EMIT" ]] || exit 0
|
|
46
|
+
STREAM="docs/metrics/commits.jsonl"
|
|
47
|
+
|
|
48
|
+
# --- 1. reconcile the stream against the log ------------------------------
|
|
49
|
+
RECONCILED=0
|
|
50
|
+
METRICS="$ROOT/.tfcore/telemetry/tf-metrics.sh"
|
|
51
|
+
LIMIT="${TF_COMMIT_RECONCILE_LIMIT:-500}"
|
|
52
|
+
if [[ -f "$METRICS" ]] && command -v python3 >/dev/null 2>&1; then
|
|
53
|
+
bash "$METRICS" --backfill-commits "$ROOT" --limit "$LIMIT" --quiet >/dev/null 2>&1 && RECONCILED=1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [[ $RECONCILED -eq 0 ]]; then
|
|
57
|
+
# Fallback: record HEAD only, in pure bash. Reached when python3 is absent.
|
|
58
|
+
# HEAD is the PREVIOUS commit here — that is the one we can describe.
|
|
59
|
+
SHA="$(git rev-parse --short HEAD 2>/dev/null)"
|
|
60
|
+
if [[ -n "$SHA" ]] && ! grep -q "\"sha\":\"$SHA\"" "$ROOT/$STREAM" 2>/dev/null; then
|
|
61
|
+
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" || BRANCH="detached"
|
|
62
|
+
WHEN="$(TZ=UTC0 git log -1 --date=format-local:%Y-%m-%dT%H:%M:%SZ --format=%cd 2>/dev/null)"
|
|
63
|
+
SUBJ="$(git log -1 --format=%s 2>/dev/null)"
|
|
64
|
+
STAT="$(git show --shortstat --format= HEAD 2>/dev/null | tail -1)"
|
|
65
|
+
|
|
66
|
+
# Conventional-commit prefix ONLY — the rest of the subject is discarded here
|
|
67
|
+
# and never leaves this script.
|
|
68
|
+
PREFIX="$(printf '%s' "$SUBJ" | sed -n 's/^\(feat\|fix\|docs\|chore\|refactor\|test\|build\)\([(!: ].*\)\?$/\1/p' | head -1)"
|
|
69
|
+
FILES="$(printf '%s' "$STAT" | grep -oE '[0-9]+ files? changed' | grep -oE '^[0-9]+')"
|
|
70
|
+
INS="$(printf '%s' "$STAT" | grep -oE '[0-9]+ insertions?' | grep -oE '^[0-9]+')"
|
|
71
|
+
DEL="$(printf '%s' "$STAT" | grep -oE '[0-9]+ deletions?' | grep -oE '^[0-9]+')"
|
|
72
|
+
: "${FILES:=0}" "${INS:=0}" "${DEL:=0}"
|
|
73
|
+
if [[ -n "$PREFIX" ]]; then PJ="\"$PREFIX\""; else PJ="null"; fi
|
|
74
|
+
TS_FIELD=""
|
|
75
|
+
[[ -n "$WHEN" ]] && TS_FIELD="\"ts\":\"$WHEN\","
|
|
76
|
+
|
|
77
|
+
printf '{%s"kind":"commit","sha":"%s","files":%s,"insertions":%s,"deletions":%s,"subject_prefix":%s,"branch":"%s"}\n' \
|
|
78
|
+
"$TS_FIELD" "$SHA" "$FILES" "$INS" "$DEL" "$PJ" "$BRANCH" \
|
|
79
|
+
| TF_METRICS_ROOT="$ROOT" bash "$EMIT" commits >/dev/null 2>&1
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# --- 2. stage it, but ONLY on a full commit -------------------------------
|
|
84
|
+
# A partial commit (`git commit -- <paths>`, and some GUI flows) runs this hook
|
|
85
|
+
# against a TEMPORARY index; staging into it means quietly adding a file the
|
|
86
|
+
# author explicitly scoped out, and git's handling of hook-made changes to that
|
|
87
|
+
# temp index is not something to rely on. So: write the record either way, stage
|
|
88
|
+
# only when this is a normal, whole-index commit. The unstaged record is not
|
|
89
|
+
# lost — the next ordinary commit reconciles and stages it.
|
|
90
|
+
case "${GIT_INDEX_FILE:-}" in
|
|
91
|
+
""|.git/index|*/.git/index) git add -- "$STREAM" >/dev/null 2>&1 ;;
|
|
92
|
+
esac
|
|
93
|
+
|
|
94
|
+
exit 0
|