@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,124 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TechieFlow PreToolUse hook — a verify run may START a dependency, never CREATE one,
|
|
3
|
+
# and never point the application at a different one.
|
|
4
|
+
#
|
|
5
|
+
# TfLens, 2026-09-01 (MISS-TfLens-20260901-02). The configured dev database was refusing
|
|
6
|
+
# connections. The agent ran `docker compose up -d db || docker compose up -d` — the
|
|
7
|
+
# service is named `postgres`, so the `||` fallback executed the BARE compose command and
|
|
8
|
+
# started every service, creating an application container and image the owner had to
|
|
9
|
+
# delete by hand. It then exported the connection-string variable to point the whole test
|
|
10
|
+
# suite at a different PostgreSQL and reported "689/689 pass" against it.
|
|
11
|
+
#
|
|
12
|
+
# Both halves are the same error. A verify run's entire product is trustworthy verdicts,
|
|
13
|
+
# and verdicts measured against a system nobody chose are not weaker evidence — they are
|
|
14
|
+
# evidence about a DIFFERENT system, reported under the checklist's name. The empty
|
|
15
|
+
# substitute database then produced RENDER-EMPTY on nine controls whose real cause was
|
|
16
|
+
# "no data", which is the plausible-wrong-number failure this framework exists to prevent,
|
|
17
|
+
# arriving inside the verifier itself.
|
|
18
|
+
#
|
|
19
|
+
# §1 was already strict about unasked-for FILES (tests/.artifacts/, guard-artifacts.sh) and
|
|
20
|
+
# silent about unasked-for INFRASTRUCTURE. A container, a volume and an image are exactly
|
|
21
|
+
# as much machine state as a stray directory. This hook is that rule, as a rule and not a
|
|
22
|
+
# paragraph (maintenance contract 3).
|
|
23
|
+
#
|
|
24
|
+
# REFUSES:
|
|
25
|
+
# - `docker compose up` / `docker-compose up` with no service named
|
|
26
|
+
# - any `|| docker compose up` fallback, which widens the command precisely when it failed
|
|
27
|
+
# - `docker compose run|create`, `docker run`, `docker volume create` — creating, not starting
|
|
28
|
+
# - an inline or exported connection-string variable in front of a test/run command
|
|
29
|
+
# ALLOWS: `docker start <name>`, `docker compose up <service>`, `docker compose start`,
|
|
30
|
+
# `docker ps|logs|inspect`, and anything that is not one of the above.
|
|
31
|
+
#
|
|
32
|
+
# Wired in .claude/settings.json -> hooks.PreToolUse (matcher "Bash"); OpenCode via
|
|
33
|
+
# .opencode/plugin/techieflow.js. Exit 2 + stderr = block and feed the message back.
|
|
34
|
+
# Fails OPEN (exit 0) if python3 or parseable JSON is unavailable — a guard that cannot
|
|
35
|
+
# read its input must not stop the run.
|
|
36
|
+
#
|
|
37
|
+
# Tested by tests/regression/run.sh tf_013.
|
|
38
|
+
|
|
39
|
+
INPUT="$(cat)"
|
|
40
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
41
|
+
|
|
42
|
+
TF_HOOK_INPUT="$INPUT" python3 - <<'PY'
|
|
43
|
+
import json, os, re, sys
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
data = json.loads(os.environ.get("TF_HOOK_INPUT", ""))
|
|
47
|
+
except Exception:
|
|
48
|
+
sys.exit(0)
|
|
49
|
+
|
|
50
|
+
cmd = (data.get("tool_input") or {}).get("command")
|
|
51
|
+
if not isinstance(cmd, str) or not cmd.strip():
|
|
52
|
+
sys.exit(0)
|
|
53
|
+
|
|
54
|
+
def block(what, instead):
|
|
55
|
+
print("BLOCKED by TechieFlow policy: %s\n"
|
|
56
|
+
"A verify run may START a service the project already defines; it may not CREATE "
|
|
57
|
+
"one, and it may never measure the application against a different instance — a "
|
|
58
|
+
"green suite against the wrong database is worse than a red one, because it is "
|
|
59
|
+
"quotable.\n%s\n"
|
|
60
|
+
"If the dependency is down and you cannot start the project's own definition of "
|
|
61
|
+
"it BY NAME, stop and ask, naming the one command the owner should run. Record "
|
|
62
|
+
"the resolved target (host, port, database — never credentials) in the verify "
|
|
63
|
+
"report, so which system was measured is on the face of it."
|
|
64
|
+
% (what, instead), file=sys.stderr)
|
|
65
|
+
sys.exit(2)
|
|
66
|
+
|
|
67
|
+
# --- creating infrastructure, rather than starting what the project already defines ------
|
|
68
|
+
COMPOSE = r"docker(?:\s+compose|-compose)"
|
|
69
|
+
|
|
70
|
+
# a bare `up`: no service name before the end of the segment. Flags are not service names.
|
|
71
|
+
for seg in re.split(r"(?:\|\||&&|;|\||\n)", cmd):
|
|
72
|
+
seg = seg.strip()
|
|
73
|
+
m = re.search(COMPOSE + r"\s+up\b(.*)$", seg, re.I)
|
|
74
|
+
if m:
|
|
75
|
+
rest = m.group(1)
|
|
76
|
+
# drop flags and their values; whatever is left is a service name
|
|
77
|
+
words = [w for w in re.split(r"\s+", rest.strip()) if w]
|
|
78
|
+
svc = []
|
|
79
|
+
skip = False
|
|
80
|
+
for w in words:
|
|
81
|
+
if skip:
|
|
82
|
+
skip = False
|
|
83
|
+
continue
|
|
84
|
+
if w.startswith("-"):
|
|
85
|
+
if "=" not in w and w in ("--scale", "--timeout", "--project-name", "-p", "--file", "-f"):
|
|
86
|
+
skip = True
|
|
87
|
+
continue
|
|
88
|
+
svc.append(w)
|
|
89
|
+
if not svc:
|
|
90
|
+
block("`%s` starts EVERY service in the compose file." % seg[:120],
|
|
91
|
+
"Name the service: `docker compose up -d <service>` — and check the name, "
|
|
92
|
+
"because a `|| docker compose up` fallback runs the bare command exactly "
|
|
93
|
+
"when the named one was wrong.")
|
|
94
|
+
|
|
95
|
+
if re.search(r"\|\|\s*" + COMPOSE + r"\s+up\b", cmd, re.I):
|
|
96
|
+
block("a `|| docker compose up` fallback widens the command when the named service failed.",
|
|
97
|
+
"Drop the fallback. If the named service does not exist, the name is wrong — fix "
|
|
98
|
+
"the name or ask; do not start everything instead.")
|
|
99
|
+
|
|
100
|
+
if re.search(COMPOSE + r"\s+(?:run|create)\b", cmd, re.I) or \
|
|
101
|
+
re.search(r"\bdocker\s+(?:run|create)\b", cmd, re.I) or \
|
|
102
|
+
re.search(r"\bdocker\s+volume\s+create\b", cmd, re.I):
|
|
103
|
+
block("this CREATES container or volume state the owner did not ask for.",
|
|
104
|
+
"Start what the project defines: `docker start <name>` or `docker compose up -d <service>`.")
|
|
105
|
+
|
|
106
|
+
# --- repointing the application at a different instance ----------------------------------
|
|
107
|
+
# The variable is whatever the project named it, so match the SHAPE of a connection string
|
|
108
|
+
# rather than a list of names that would never be complete.
|
|
109
|
+
CONNSTR = r"(?:Host|Server|Data\s*Source|Endpoint)\s*=|(?:postgres|postgresql|mysql|mongodb|mssql|sqlserver|redis)(?:\+srv)?://"
|
|
110
|
+
ASSIGN = r"(?:\bexport\s+|\bset\s+|^|\s|;|&&)([A-Za-z_][A-Za-z0-9_]*)\s*=\s*[\"']?([^\"'\s;&|]*)"
|
|
111
|
+
|
|
112
|
+
for m in re.finditer(ASSIGN, cmd):
|
|
113
|
+
name, val = m.group(1), m.group(2)
|
|
114
|
+
if re.search(CONNSTR, val, re.I) or (re.search(r"conn|db|database|datasource", name, re.I)
|
|
115
|
+
and re.search(r"[=:]//|Host\s*=|Port\s*=", val, re.I)):
|
|
116
|
+
block("`%s` points the application or its tests at a connection string chosen here, "
|
|
117
|
+
"not the one the project is configured with." % name,
|
|
118
|
+
"Use the app's own configured connection. TfLens's own test helper already "
|
|
119
|
+
"carries this rule — \"there is deliberately no default here … so tests and app "
|
|
120
|
+
"can never drift onto different servers again\" — and it cost a day to learn.")
|
|
121
|
+
|
|
122
|
+
sys.exit(0)
|
|
123
|
+
PY
|
|
124
|
+
exit $?
|
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# TechieFlow PreToolUse hook —
|
|
2
|
+
# TechieFlow PreToolUse hook — a `Verified` cell is written only by an executed verify run.
|
|
3
3
|
#
|
|
4
|
-
# `Verified` is the verifier's verdict
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
4
|
+
# `Verified` is the verifier's verdict. Since Sitting 4c (2026-09-06) the verify run's own script,
|
|
5
|
+
# tf-verify-verdict.sh, writes the cells from the evidence it collected and writes the ledger
|
|
6
|
+
# docs/.last-verify.json with every row's verdict. This hook refuses any Write/Edit/MultiEdit of a
|
|
7
|
+
# *-Checklist.md that INTRODUCES a `Verified` cell unless a ledger dated today lists that row as
|
|
8
|
+
# PASS. Owner decision 2026-09-06: a hand-written Verified the ledger does not list is refused.
|
|
9
|
+
# Two older ledger shapes still unlock: {"mode":"reconcile"} written by *refresh-status for a row
|
|
10
|
+
# whose Remarks already carry dated evidence, and a same-day ledger without a `rows` map from a
|
|
11
|
+
# framework version before this sitting (the row is then checked by date only, as before).
|
|
10
12
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# records verdicts — after actually booting the app and running the gates.
|
|
14
|
-
# Any Write/Edit/MultiEdit to a `*-Checklist.md` that INTRODUCES a `Verified`
|
|
15
|
-
# status cell is blocked unless a same-day ledger sits next to the checklist.
|
|
16
|
-
#
|
|
17
|
-
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Write|Edit|MultiEdit").
|
|
18
|
-
# Exit 2 + stderr = block the call and feed the message back to the agent.
|
|
13
|
+
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Write|Edit|MultiEdit") and in the
|
|
14
|
+
# OpenCode plugin. Exit 2 + stderr = block the call and feed the message back to the agent.
|
|
19
15
|
# Fails OPEN (exit 0) if python3 or parseable JSON is unavailable.
|
|
20
16
|
|
|
21
17
|
INPUT="$(cat)"
|
|
22
18
|
command -v python3 >/dev/null 2>&1 || exit 0
|
|
23
19
|
|
|
24
|
-
# stdin feeds python the SCRIPT (heredoc), so the payload travels via env var.
|
|
25
20
|
TF_HOOK_INPUT="$INPUT" python3 - <<'PY'
|
|
26
21
|
import datetime, json, os, re, sys
|
|
27
22
|
|
|
@@ -35,10 +30,19 @@ path = (ti.get("file_path") or "").replace("\\", "/")
|
|
|
35
30
|
if not path.rsplit("/", 1)[-1].lower().endswith("-checklist.md"):
|
|
36
31
|
sys.exit(0)
|
|
37
32
|
|
|
38
|
-
# A `Verified` STATUS CELL: a table cell holding exactly Verified (optional
|
|
39
|
-
# bold / trailing date qualifier). Remarks prose like "VERIFIED 2026-07-07"
|
|
40
|
-
# or "Needs re-verify" does not match.
|
|
41
33
|
CELL = re.compile(r"\|\s*\**Verified\**\s*(?:\([^)|]*\))?\s*\|")
|
|
34
|
+
ROW = re.compile(r"^\s*\|\s*`?(REQ-[A-Z]+-\d+)`?\s*\|", re.I)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def verified_ids(text):
|
|
38
|
+
"""the row ids whose STATUS cell reads Verified, in a piece of checklist text"""
|
|
39
|
+
out = set()
|
|
40
|
+
for line in text.splitlines():
|
|
41
|
+
m = ROW.match(line)
|
|
42
|
+
if m and CELL.search(line):
|
|
43
|
+
out.add(m.group(1).upper())
|
|
44
|
+
return out
|
|
45
|
+
|
|
42
46
|
|
|
43
47
|
new_pieces, old_pieces = [], []
|
|
44
48
|
if isinstance(ti.get("content"), str):
|
|
@@ -56,51 +60,69 @@ for e in ti.get("edits") or []:
|
|
|
56
60
|
|
|
57
61
|
n_new = sum(len(CELL.findall(p)) for p in new_pieces)
|
|
58
62
|
n_old = sum(len(CELL.findall(p)) for p in old_pieces)
|
|
63
|
+
if isinstance(ti.get("content"), str):
|
|
64
|
+
# a whole-file write: what is Verified on disk already does not count as new
|
|
65
|
+
try:
|
|
66
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
67
|
+
n_old = len(CELL.findall(f.read()))
|
|
68
|
+
except FileNotFoundError:
|
|
69
|
+
n_old = 0
|
|
59
70
|
if n_new <= n_old:
|
|
60
|
-
sys.exit(0) # no NEW Verified cell
|
|
71
|
+
sys.exit(0) # no NEW Verified cell (a demotion, a move, another edit)
|
|
72
|
+
|
|
73
|
+
new_ids = set().union(*(verified_ids(p) for p in new_pieces)) if new_pieces else set()
|
|
74
|
+
old_ids = set().union(*(verified_ids(p) for p in old_pieces)) if old_pieces else set()
|
|
75
|
+
if isinstance(ti.get("content"), str):
|
|
76
|
+
try:
|
|
77
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
78
|
+
old_ids = verified_ids(f.read())
|
|
79
|
+
except FileNotFoundError:
|
|
80
|
+
old_ids = set()
|
|
81
|
+
introduced = new_ids - old_ids
|
|
61
82
|
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
ledger_path = os.path.join(os.path.dirname(path) or ".", ".last-verify.json")
|
|
84
|
+
today = datetime.date.today().isoformat()
|
|
64
85
|
try:
|
|
65
86
|
with open(ledger_path, "r", encoding="utf-8") as f:
|
|
66
87
|
ledger = json.load(f)
|
|
67
|
-
if str(ledger.get("date", "")).strip() == datetime.date.today().isoformat():
|
|
68
|
-
sys.exit(0) # fresh ledger — an executed verify-phase (or documented reconcile) unlocked it
|
|
69
|
-
stale = f"exists but is dated {ledger.get('date')!r}, not today"
|
|
70
88
|
except FileNotFoundError:
|
|
71
|
-
|
|
89
|
+
ledger, problem = None, "does not exist"
|
|
72
90
|
except Exception:
|
|
73
|
-
|
|
91
|
+
ledger, problem = None, "exists but is not valid JSON"
|
|
92
|
+
|
|
93
|
+
if ledger is not None:
|
|
94
|
+
if str(ledger.get("date", "")).strip() != today:
|
|
95
|
+
problem = f"exists but is dated {ledger.get('date')!r}, not today"
|
|
96
|
+
elif ledger.get("mode") == "reconcile":
|
|
97
|
+
sys.exit(0) # *refresh-status reconciling a row to dated evidence already in its Remarks
|
|
98
|
+
elif "rows" not in ledger:
|
|
99
|
+
sys.exit(0) # a same-day ledger from a framework version before Sitting 4c: date unlocks
|
|
100
|
+
else:
|
|
101
|
+
rows = ledger.get("rows") or {}
|
|
102
|
+
not_listed = sorted(i for i in introduced if rows.get(i) != "PASS")
|
|
103
|
+
if not introduced:
|
|
104
|
+
# a Verified cell without a readable row id (a malformed line): refuse, nothing can vouch for it
|
|
105
|
+
not_listed = ["(row id not readable)"]
|
|
106
|
+
if not not_listed:
|
|
107
|
+
sys.exit(0)
|
|
108
|
+
problem = ("lists these rows as " + ", ".join(f"{i}: {rows.get(i, 'absent')}" for i in not_listed)
|
|
109
|
+
+ ", not PASS")
|
|
74
110
|
|
|
75
111
|
print(
|
|
76
112
|
"BLOCKED by TechieFlow policy: this write INTRODUCES a `Verified` status, "
|
|
77
|
-
f"but the verify
|
|
78
|
-
file=sys.stderr,
|
|
79
|
-
)
|
|
80
|
-
print(
|
|
81
|
-
" - `Verified` is the VERIFIER'S verdict. It is written only downstream of an "
|
|
82
|
-
"EXECUTED .tfcore/tasks/verify-phase.md run — boot the app, run the scoped "
|
|
83
|
-
"tests, apply the §4a data-render + §4b visual-truth gates, write the run "
|
|
84
|
-
"ledger (verify-phase §6), THEN record verdicts.",
|
|
85
|
-
file=sys.stderr,
|
|
86
|
-
)
|
|
87
|
-
print(
|
|
88
|
-
" - If you are the build orchestrator after your self-smoke: your ceiling is "
|
|
89
|
-
"`Implemented` (build-phase §6a / _smoke-test-policy). Chain the verifier by "
|
|
90
|
-
"EXECUTING verify-phase inline (build-phase §6b) — summarizing your smoke as "
|
|
91
|
-
"a verdict is self-attestation, the exact failure this hook exists to stop.",
|
|
113
|
+
f"but the verify ledger {ledger_path} {problem}.",
|
|
92
114
|
file=sys.stderr,
|
|
93
115
|
)
|
|
94
116
|
print(
|
|
95
|
-
" -
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
'date>"} first (refresh-status §4).',
|
|
117
|
+
" - `Verified` is written by the verify run's own script from the evidence it collected: "
|
|
118
|
+
"bash .tfcore/utils/tf-verify-verdict.sh <App> --apply, after boot, tests, screens, assets, "
|
|
119
|
+
"parity and perf have run (.tfcore/tasks/verify-phase.md). Writing it by hand is refused.",
|
|
99
120
|
file=sys.stderr,
|
|
100
121
|
)
|
|
101
122
|
print(
|
|
102
|
-
" -
|
|
103
|
-
"
|
|
123
|
+
" - A build, a fix or a smoke reaches at most `Implemented`; chain the verifier by executing "
|
|
124
|
+
"verify-phase.md. *refresh-status may reconcile a row that already carries dated verify evidence "
|
|
125
|
+
'by writing the ledger {"date":"<today>","mode":"reconcile","evidence":"<that date>"} first.',
|
|
104
126
|
file=sys.stderr,
|
|
105
127
|
)
|
|
106
128
|
sys.exit(2)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# TechieFlow hook — automatic sweep of expired run material (added 2026-08-26).
|
|
3
3
|
#
|
|
4
4
|
# Wired as a SessionStart hook in .claude/settings.json (Claude Code), as
|
|
5
|
-
#
|
|
5
|
+
# on the first root
|
|
6
6
|
# `session.created` in .opencode/plugin/techieflow.js (OpenCode).
|
|
7
7
|
#
|
|
8
8
|
# WHY: verify-phase.md §1 pins every run artifact under tests/.artifacts/ and
|
|
@@ -60,7 +60,7 @@ root = os.path.realpath(root)
|
|
|
60
60
|
DRY = os.environ.get("TF_SWEEP_DRY_RUN") == "1"
|
|
61
61
|
|
|
62
62
|
# Throttle: at most one sweep per hour per project, whichever harness fires it
|
|
63
|
-
# (
|
|
63
|
+
# (a harness may re-fire session-start on every prompt; a walk of a big tree on every
|
|
64
64
|
# turn is waste). TF_SWEEP_FORCE=1 bypasses. Dry runs never touch the stamp.
|
|
65
65
|
stamp = os.path.join(root, ".tfcore", ".session", "sweep.stamp")
|
|
66
66
|
if not DRY and os.environ.get("TF_SWEEP_FORCE") != "1":
|
|
@@ -91,9 +91,6 @@ files:
|
|
|
91
91
|
- path: .tfcore\tasks\test-design.md
|
|
92
92
|
hash: bcd13a95d296ce22
|
|
93
93
|
modified: false
|
|
94
|
-
- path: .tfcore\tasks\shard-doc.md
|
|
95
|
-
hash: a83c900f64ea3d4f
|
|
96
|
-
modified: false
|
|
97
94
|
- path: .tfcore\tasks\risk-profile.md
|
|
98
95
|
hash: addf5d143cfe6f12
|
|
99
96
|
modified: false
|
|
@@ -106,24 +103,12 @@ files:
|
|
|
106
103
|
- path: .tfcore\tasks\nfr-assess.md
|
|
107
104
|
hash: b9011d038b81a69b
|
|
108
105
|
modified: false
|
|
109
|
-
- path: .tfcore\tasks\kb-mode-interaction.md
|
|
110
|
-
hash: 4d6b921c24ba4999
|
|
111
|
-
modified: false
|
|
112
|
-
- path: .tfcore\tasks\index-docs.md
|
|
113
|
-
hash: 70b1d526b19d015e
|
|
114
|
-
modified: false
|
|
115
106
|
- path: .tfcore\tasks\generate-ai-frontend-prompt.md
|
|
116
107
|
hash: ca4cabd824ea1b60
|
|
117
108
|
modified: false
|
|
118
109
|
- path: .tfcore\tasks\facilitate-brainstorming-session.md
|
|
119
110
|
hash: 38594d876614e077
|
|
120
111
|
modified: false
|
|
121
|
-
- path: .tfcore\tasks\execute-checklist.md
|
|
122
|
-
hash: 8a704b6f2bc52e12
|
|
123
|
-
modified: false
|
|
124
|
-
- path: .tfcore\tasks\document-project.md
|
|
125
|
-
hash: 98f8790d20e83cf3
|
|
126
|
-
modified: false
|
|
127
112
|
- path: .tfcore\tasks\create-next-story.md
|
|
128
113
|
hash: 99e5cc3237a9cffd
|
|
129
114
|
modified: false
|
|
@@ -148,9 +133,6 @@ files:
|
|
|
148
133
|
- path: .tfcore\tasks\apply-qa-fixes.md
|
|
149
134
|
hash: 70e6143a80f7296b
|
|
150
135
|
modified: false
|
|
151
|
-
- path: .tfcore\tasks\advanced-elicitation.md
|
|
152
|
-
hash: d39118bf32237a21
|
|
153
|
-
modified: false
|
|
154
136
|
- path: .tfcore\data\test-priorities-matrix.md
|
|
155
137
|
hash: 1dd5698a46ab054e
|
|
156
138
|
modified: false
|
package/.tfcore/routing.yaml
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# A phase declares a TIER, never a model. The tiers map resolves a tier to a
|
|
8
8
|
# per-harness model id (Claude Code aliases: opus|sonnet|haiku|inherit|<id>,
|
|
9
9
|
# pinnable via ANTHROPIC_DEFAULT_*_MODEL; OpenCode ids: provider/model, list
|
|
10
|
-
# with `opencode models
|
|
10
|
+
# with `opencode models`).
|
|
11
11
|
# Routing is declared at the harness boundary and
|
|
12
12
|
# OBSERVED by telemetry (runs.jsonl model/tier fields) — it is never enforced
|
|
13
13
|
# mid-turn, because neither harness can switch a running turn's model.
|
|
@@ -23,26 +23,97 @@
|
|
|
23
23
|
# §5.3/§5.4 and the rollout order in §6. Flipping the flag alone changes
|
|
24
24
|
# nothing; that is deliberate.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# Owner decisions: 2026-09-05, Sonnet for every long-running Claude phase, Haiku for the cheap ones;
|
|
27
|
+
# 2026-09-06, the OpenAI models for OpenCode (gpt-5.6-terra for frontier and standard, gpt-5.6-luna
|
|
28
|
+
# for economy) after the OpenCode Go monthly limit was reached with MiMo.
|
|
29
|
+
#
|
|
30
|
+
# HOW TO CHANGE A MODEL (the plain version; the full guide is docs/TechieFlow-Routing-Guide.md):
|
|
31
|
+
# 1. This file is the FRAMEWORK DEFAULT. A project gets a copy at .tfcore/routing.yaml when it is
|
|
32
|
+
# scaffolded, and update-framework.sh never overwrites that copy. So to change one project,
|
|
33
|
+
# edit the project's copy; to change every future project, edit this one.
|
|
34
|
+
# 2. Edit the model id under `tiers:` for the harness you use (claude: opus|sonnet|haiku or a
|
|
35
|
+
# full id; opencode: provider/model as printed by `opencode models`).
|
|
36
|
+
# 3. In the project folder run: bash .tfcore/utils/tf-routing-bind.sh
|
|
37
|
+
# That regenerates the harness bindings (.claude/agents/*.md, .claude/commands/tf/*.md,
|
|
38
|
+
# .opencode/opencode.json). Nothing changes until it runs.
|
|
39
|
+
# 4. For one unattended run only, pass --model <id> to tf-goal.sh instead; the main agent uses
|
|
40
|
+
# it, the sub-agents still use the bindings from step 3.
|
|
41
|
+
# 5. When a model's usage limit is reached, the tier's `fallbacks:` list is the answer — see the
|
|
42
|
+
# block below. Nothing is automatic until a limit is actually hit.
|
|
43
|
+
enabled: true
|
|
28
44
|
# ---------------------------------------------------------------------------
|
|
29
45
|
# Tier → model id, per harness. STARTING VALUES — the owner tunes these.
|
|
30
|
-
# OpenCode ids
|
|
31
|
-
# Zen gateway); swap for anthropic/* ids if an Anthropic key is configured.
|
|
46
|
+
# OpenCode ids are provider/model as printed by `opencode models` on this machine.
|
|
32
47
|
# ---------------------------------------------------------------------------
|
|
33
48
|
tiers:
|
|
34
49
|
frontier:
|
|
35
|
-
claude:
|
|
36
|
-
opencode:
|
|
37
|
-
codex: gpt-5.6
|
|
50
|
+
claude: sonnet
|
|
51
|
+
opencode: openai/gpt-5.6-terra
|
|
38
52
|
standard:
|
|
39
53
|
claude: sonnet
|
|
40
|
-
opencode:
|
|
41
|
-
codex: gpt-5.6-terra
|
|
54
|
+
opencode: openai/gpt-5.6-terra
|
|
42
55
|
economy:
|
|
43
56
|
claude: haiku
|
|
44
|
-
opencode:
|
|
45
|
-
|
|
57
|
+
opencode: openai/gpt-5.6-luna
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Tier → the models to try INSTEAD, in order, while the tier model is limited.
|
|
61
|
+
# Comma-separated, one line per harness, same flat two-space shape as `tiers:`.
|
|
62
|
+
#
|
|
63
|
+
# WHAT USES IT. `bash .tfcore/utils/tf-model-pick.sh pick <tier> <harness>` returns the first
|
|
64
|
+
# model in "tier model, then this list" that is NOT on cooldown. tf-goal.sh puts a model on
|
|
65
|
+
# cooldown when a cycle ends in a usage limit, re-binds, and starts the next cycle on the next
|
|
66
|
+
# model; tf-routing-bind.sh reads the same answer, so the sub-agents move with the main agent
|
|
67
|
+
# instead of failing behind it. A cooldown expires by itself at the reset time the limit message
|
|
68
|
+
# stated, and the tier goes back to its own model.
|
|
69
|
+
#
|
|
70
|
+
# WHEN IT DOES NOT HELP, stated plainly: on Claude Code the 5-hour and weekly limits are the
|
|
71
|
+
# ACCOUNT's, not the model's, so a fallback to another Claude model is limited too. The supervisor
|
|
72
|
+
# finds that out the honest way — the next cycle hits the same limit, that model goes on cooldown
|
|
73
|
+
# as well, and once the chain is exhausted it sleeps until the reset exactly as it did before.
|
|
74
|
+
# A per-model limit (Opus exhausted while the account is fine) is where the Claude column earns
|
|
75
|
+
# its keep; a provider outage or a monthly balance is where the OpenCode column does.
|
|
76
|
+
#
|
|
77
|
+
# Empty list = no fallback for that tier; the supervisor sleeps. That is the safe default for
|
|
78
|
+
# anyone who does not want a phase silently running on a different model.
|
|
79
|
+
#
|
|
80
|
+
# WHERE THE "limited until <time>" RECORD IS KEPT. In this project, at
|
|
81
|
+
# .tfcore/.session/model-cooldown.json — the framework is installed per project, so its state is
|
|
82
|
+
# per project too, and one project's setting never reaches another. Uncomment the line below to
|
|
83
|
+
# move it: a path relative to the project root, or an absolute one. Several projects pointed at
|
|
84
|
+
# one file learn about a limit from each other instead of each finding it out for themselves.
|
|
85
|
+
# cooldown_file: .tfcore/.session/model-cooldown.json
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# The OpenCode lines below are OpenCode Go models (https://opencode.ai/docs/go — a $10/month plan
|
|
88
|
+
# with a per-model monthly allowance, which is exactly the limit these chains exist for). Each tier
|
|
89
|
+
# falls back to a DIFFERENT model, because the allowance that ran out is per model, and then to a
|
|
90
|
+
# different provider, because a whole plan can run out at once.
|
|
91
|
+
fallbacks:
|
|
92
|
+
frontier:
|
|
93
|
+
claude: opus, haiku
|
|
94
|
+
opencode: opencode-go/glm-5.3, opencode-go/kimi-k3, openai/gpt-5.6-sol
|
|
95
|
+
standard:
|
|
96
|
+
claude: haiku
|
|
97
|
+
opencode: opencode-go/mimo-v2.5, opencode-go/qwen3.8-max, openai/gpt-5.6-luna
|
|
98
|
+
economy:
|
|
99
|
+
claude: sonnet
|
|
100
|
+
opencode: opencode-go/deepseek-v4-flash, opencode-go/glm-5.3-flash, openai/gpt-5.6-luna
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# How each harness is PAID FOR. This is what stops one `cost_usd` column pooling
|
|
104
|
+
# three different kinds of number (SCHEMA.md §2.5b).
|
|
105
|
+
# subscription a flat fee buys a quota. Marginal dollars are zero; the unit that means
|
|
106
|
+
# anything is tokens. Claude Max, and any OpenCode provider signed in with OAuth.
|
|
107
|
+
# metered an API key billed per token. The dollars are real money and are summed.
|
|
108
|
+
# local a model on this machine. No quota, no bill.
|
|
109
|
+
# auto work it out per model — OpenCode's own auth.json says oauth or api, and a
|
|
110
|
+
# provider it has never heard of that answers locally is local.
|
|
111
|
+
# `auto` is right for OpenCode because one machine mixes all three at once. Claude Code is
|
|
112
|
+
# declared, because nothing in a transcript says which plan paid for it.
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
billing:
|
|
115
|
+
claude: subscription
|
|
116
|
+
opencode: auto
|
|
46
117
|
|
|
47
118
|
# ---------------------------------------------------------------------------
|
|
48
119
|
# Phase → tier. One line each; values: frontier | standard | economy | inherit.
|
|
@@ -52,13 +123,14 @@ tiers:
|
|
|
52
123
|
phases:
|
|
53
124
|
day1-greenfield: frontier
|
|
54
125
|
day1-brownfield: frontier
|
|
55
|
-
author-brd: frontier
|
|
56
126
|
amend-docs: frontier
|
|
57
127
|
mockups: standard
|
|
58
128
|
split-brd: standard
|
|
59
129
|
build-phase: standard
|
|
60
130
|
fix-issues: frontier
|
|
131
|
+
triage-and-fix: frontier
|
|
61
132
|
triage-issues: standard
|
|
133
|
+
log-miss: economy
|
|
62
134
|
verify-phase: standard
|
|
63
135
|
devguide: standard
|
|
64
136
|
productguide: economy
|
|
@@ -79,7 +151,7 @@ subagents:
|
|
|
79
151
|
tf-explorer: economy
|
|
80
152
|
|
|
81
153
|
# ---------------------------------------------------------------------------
|
|
82
|
-
# Tier → reasoning effort (used by Claude Code
|
|
154
|
+
# Tier → reasoning effort (used by Claude Code; ignored by OpenCode).
|
|
83
155
|
# ---------------------------------------------------------------------------
|
|
84
156
|
effort:
|
|
85
157
|
frontier: high
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# TechieFlow — Coding Standards, core
|
|
2
|
+
|
|
3
|
+
<!-- How this file is used and edited.
|
|
4
|
+
Applies to EVERY project, whatever the stack. It is loaded by the build and by the verifier's
|
|
5
|
+
standards check together with the stack file named in the project's Coding Standards document
|
|
6
|
+
(docs/<App>-Coding-Standards.md → "Standards applied"). Rules here are technology-neutral;
|
|
7
|
+
anything that names a language, framework or package belongs in a stack file
|
|
8
|
+
(.tfcore/standards/coding-standards-<stack>.md).
|
|
9
|
+
To change a rule: edit this file in the TechieFlow repository, then run update-framework.sh on
|
|
10
|
+
each project. Projects never edit their .tfcore/ copy. A rule an agent has ignored twice becomes
|
|
11
|
+
a script or a hook, or is deleted; it never gets a second paragraph. -->
|
|
12
|
+
|
|
13
|
+
## 1. Names
|
|
14
|
+
|
|
15
|
+
- One naming style per kind of thing, declared in the stack file, used everywhere. Never mix styles in one project.
|
|
16
|
+
- Names say what a thing is or does. No abbreviations a newcomer would have to ask about.
|
|
17
|
+
- Files are named after the one type they hold.
|
|
18
|
+
|
|
19
|
+
## 2. Layout
|
|
20
|
+
|
|
21
|
+
- Source under `src/`, tests under `tests/`. No other folders at the repository root unless the stack file allows one.
|
|
22
|
+
- Log files go under the build output folder, never at the repository root.
|
|
23
|
+
- The primary executable project carries the product name. A project named `<App>.App` is not allowed.
|
|
24
|
+
|
|
25
|
+
## 3. Dependencies and configuration
|
|
26
|
+
|
|
27
|
+
- No package is added without a row in the Architecture Decisions log saying why.
|
|
28
|
+
- One configuration mechanism per project. A second one is a defect.
|
|
29
|
+
- A missing feature in an internal library (TrBlazeUI, TechieRag, TechieFlow) is recorded in that library's feedback file and the feature is held. No workaround is written.
|
|
30
|
+
|
|
31
|
+
## 4. Code shape
|
|
32
|
+
|
|
33
|
+
- One type per file. Small methods. Early returns for validation. At most three levels of nesting.
|
|
34
|
+
- Every public member has a documentation comment in the language's own form.
|
|
35
|
+
- No commented-out code. Comments say why, not what.
|
|
36
|
+
|
|
37
|
+
## 5. Tests
|
|
38
|
+
|
|
39
|
+
- A test project exists from day one.
|
|
40
|
+
- Test names are short; the scenario lives in the test's documentation comment.
|
|
41
|
+
- Arrange, act, assert. One behaviour per test.
|
|
42
|
+
|
|
43
|
+
## 6. Security
|
|
44
|
+
|
|
45
|
+
- No credentials in code or in committed configuration. Secrets come from the mechanism in Stack Q2.
|
|
46
|
+
- Database access uses parameters, never string-built queries.
|
|
47
|
+
- Every input from outside the process is validated at the boundary. Security events are logged.
|
|
48
|
+
|
|
49
|
+
## 7. Logging
|
|
50
|
+
|
|
51
|
+
- Every executable head writes a rolling log file, wired at startup before anything else can fail, and logs unhandled exceptions at its boundary.
|
|
52
|
+
- Libraries log through the logging abstraction only; they never reference a logging implementation.
|
|
53
|
+
|
|
54
|
+
## 8. Testability
|
|
55
|
+
|
|
56
|
+
- Every interactive or data-bound control the verifier must reach carries a stable test id, named by intent (`LoginSubmit`, `EntriesGrid`), never by position.
|
|
57
|
+
|
|
58
|
+
## 9. Enforcement
|
|
59
|
+
|
|
60
|
+
- The machine-checkable subset lives in the repository's editor configuration and analyzer settings, as the stack file specifies.
|
|
61
|
+
- The verifier's standards check runs the greps listed in the stack file's Enforcement section and records findings in the checklist Remarks and PROJECT-STATUS.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# TechieFlow — Coding Standards, .NET
|
|
2
|
+
|
|
3
|
+
<!-- How this file is used and edited.
|
|
4
|
+
The .NET answer set's coding standards. Loaded with coding-standards-core.md when a project's
|
|
5
|
+
Coding Standards document names it. Owners in another stack write their own
|
|
6
|
+
coding-standards-<stack>.md with the same section names.
|
|
7
|
+
Seeded from the framework author's projects (the two sample files in the TechieFlow docs folder).
|
|
8
|
+
To change a rule: edit this file in the TechieFlow repository, then run update-framework.sh on
|
|
9
|
+
each project. Per-project choices this file leaves open are recorded in
|
|
10
|
+
docs/<App>-Coding-Standards.md → "Standards applied"; project-only rules go in "Project rules". -->
|
|
11
|
+
|
|
12
|
+
## 1. Names
|
|
13
|
+
|
|
14
|
+
**Never use underscores** in any identifier, database object or environment variable name.
|
|
15
|
+
|
|
16
|
+
| Kind | Convention | Example |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Classes, records, structs | PascalCase, descriptive | `SqlQueryBuilder`, not `SqlQB` |
|
|
19
|
+
| Interfaces | `I` prefix | `IQueryExecutor` |
|
|
20
|
+
| Methods | PascalCase verb phrase; async methods end in `Async` | `GetConnectionAsync()` |
|
|
21
|
+
| Properties | PascalCase, no prefix | `ConnectionString` |
|
|
22
|
+
| Instance fields | **per-project choice**: `obj` prefix (`objLogger`) or bare PascalCase (`Logger`). Default `obj`. Recorded in the project's Coding Standards | `private readonly ILogger<X> objLogger;` |
|
|
23
|
+
| Static and const fields | PascalCase, no prefix | `MaxRetryCount`, not `MAX_RETRY_COUNT` |
|
|
24
|
+
| Method parameters | `a` prefix + PascalCase | `LoginAsync(string aEmail)` |
|
|
25
|
+
| Local variables | `v` prefix + PascalCase | `var vResponse = …` |
|
|
26
|
+
| Booleans | same prefix, question form | `IsValid`, `vHasRows`, `aCanEdit` |
|
|
27
|
+
| Test methods | short PascalCase; scenario in the XML `<summary>` | `LoginRejectsBadPassword` |
|
|
28
|
+
| Environment variables | PascalCase, no separators; read through `IConfiguration` via a provider that maps them to `Section:Key` | `AppManagerBaseUrl` |
|
|
29
|
+
|
|
30
|
+
Rejected: `_field` prefixes, snake_case, Hungarian type prefixes (`strName`), `Method_State_Result` test names, `UPPER_SNAKE` or `Section__Key` environment variables.
|
|
31
|
+
|
|
32
|
+
Controller actions: the `a` prefix applies to `[FromRoute]`, `[FromQuery]` and `[FromBody]` parameters and flows into the OpenAPI schema. DTO **property** names stay PascalCase without prefix.
|
|
33
|
+
|
|
34
|
+
## 2. Database
|
|
35
|
+
|
|
36
|
+
- PascalCase, singular table names: `CustomerOrder`. PascalCase columns: `FirstName`.
|
|
37
|
+
- Primary key `<Table>Id`; foreign key `<ReferencedTable>Id`.
|
|
38
|
+
- Stored procedures and functions: verb prefix, PascalCase: `GetCustomerOrders`, `InsertOrder`.
|
|
39
|
+
- Index `IX<Table><Column>` · primary key `Pk<Table>` · foreign key `Fk<Table><Ref>` · unique `Uc<Table><Column>`.
|
|
40
|
+
- Data access is Dapper. Migrations live in a dedicated `<App>Db` project using DbUp, run at application startup or by the pipeline. Never a `database` folder of loose scripts at the root.
|
|
41
|
+
|
|
42
|
+
## 3. Projects and files
|
|
43
|
+
|
|
44
|
+
- `src/` and `tests/` at the root. The primary head is `src/<App>/<App>.csproj`; `<App>.App` is banned. Secondary heads take a descriptive suffix: `<App>.Api`, `<App>.Desktop`, `<App>.Cli`. Satellites: `<App>.Core`, `<App>UI` (Razor class library), `<App>.Tests`.
|
|
45
|
+
- One class per file; file-scoped namespaces; nullable reference types enabled.
|
|
46
|
+
- File order: usings, namespace, type; inside the type: fields, constructor, properties, methods.
|
|
47
|
+
- Configuration: `appsettings.json` layered by `appsettings.{Environment}.json`; development secrets in `dotnet user-secrets`, with a `secrets.example.json` listing every key. No second mechanism.
|
|
48
|
+
|
|
49
|
+
## 4. Code
|
|
50
|
+
|
|
51
|
+
- Methods under about 20 lines; single responsibility; at most three levels of nesting; early returns.
|
|
52
|
+
- `async`/`await` for all I/O; `ConfigureAwait(false)` in libraries; no `async void` outside event handlers.
|
|
53
|
+
- LINQ method syntax for simple queries; avoid multiple enumeration.
|
|
54
|
+
- `StringBuilder` in loops; dispose `IDisposable`; cache expensive work.
|
|
55
|
+
- XML documentation on every public member: `<summary>`, `<param>`, `<returns>`, `<exception>`; `<remarks>` where the flow is not obvious.
|
|
56
|
+
|
|
57
|
+
## 5. Tests
|
|
58
|
+
|
|
59
|
+
- xUnit. A test project from day one.
|
|
60
|
+
- Short PascalCase names, scenario in `<summary>`; arrange, act, assert; one assertion per test where practical.
|
|
61
|
+
|
|
62
|
+
## 6. Logging
|
|
63
|
+
|
|
64
|
+
- Serilog with a rolling file sink in every executable head (web, API, MAUI, desktop, console, background service), wired before anything else can fail: `Log.Logger = new LoggerConfiguration().MinimumLevel.Information().WriteTo.Console().WriteTo.File("<build output>/logs/<app>-.log", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 14).CreateLogger();` then `builder.Services.AddSerilog()` or `builder.Host.UseSerilog()`; MAUI: `builder.Logging.AddSerilog()` in `MauiProgram`, path under `FileSystem.AppDataDirectory`.
|
|
65
|
+
- `Log.Fatal` around startup, handlers for `AppDomain.CurrentDomain.UnhandledException` and `TaskScheduler.UnobservedTaskException`, `Log.CloseAndFlush()` on exit.
|
|
66
|
+
- Class libraries reference only `Microsoft.Extensions.Logging.Abstractions` and log through `ILogger<T>` with structured templates.
|
|
67
|
+
- Brownfield: an existing structured file-logging stack is compliant; new heads still use Serilog.
|
|
68
|
+
|
|
69
|
+
## 7. Testability
|
|
70
|
+
|
|
71
|
+
- Blazor: a stable `data-testid` (or element id) on every control the verifier must reach.
|
|
72
|
+
- MAUI: a stable, unique `AutomationId` on every interactive or data-bound control, named by intent (`LoginSubmitButton`, `ClientsGrid`), set on the element whose data the gate asserts.
|
|
73
|
+
|
|
74
|
+
## 8. Security
|
|
75
|
+
|
|
76
|
+
- No credentials in code. Parameterised queries. Validate inputs. Log security events.
|
|
77
|
+
|
|
78
|
+
## 9. Enforcement
|
|
79
|
+
|
|
80
|
+
`.editorconfig` at the repository root (created at day-1) enforces: file-scoped namespaces (warning), `Async` suffix (warning), `var` for locals (warning), nullable enabled, no `_` prefix on private fields (custom naming rule, warning). `StyleCop.Analyzers` is optional and off by default.
|
|
81
|
+
|
|
82
|
+
The verifier's standards check runs:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# underscore-prefixed fields
|
|
86
|
+
grep -rE "private(\s+readonly)?\s+\w+\s+_[a-z]" src/ 2>/dev/null
|
|
87
|
+
# underscores in test method names
|
|
88
|
+
grep -rE "public\s+(async\s+)?Task\s+\w+_\w+\s*\(" tests/ 2>/dev/null
|
|
89
|
+
# fields missing the obj prefix (obj-style projects only)
|
|
90
|
+
grep -rPE "private(\s+readonly)?\s+\w+\s+(?!obj)[A-Z]\w+\s*[;=]" src/ 2>/dev/null | grep -v "static\|const"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Severity: error for file-scoped namespace and underscore field prefix; warning for nullable and the `Async` suffix; the rest informational.
|