@techierathore/techieflow 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +5 -13
  2. package/.claude/commands/TechieFlow/agents/architect.md +1 -16
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +42 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +24 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_owner-language.md +26 -0
  7. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  8. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  9. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  10. package/.claude/commands/TechieFlow/tasks/amend-docs.md +21 -143
  11. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  12. package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +4 -0
  13. package/.claude/commands/TechieFlow/tasks/create-doc.md +4 -0
  14. package/.claude/commands/TechieFlow/tasks/create-project-brief.md +22 -0
  15. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  16. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  17. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  18. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  19. package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +4 -0
  20. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  21. package/.claude/commands/TechieFlow/tasks/generate-html.md +5 -1
  22. package/.claude/commands/TechieFlow/tasks/handoff-phase.md +18 -16
  23. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  24. package/.claude/commands/TechieFlow/tasks/metrics-report.md +3 -1
  25. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  26. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  27. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  28. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  29. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  30. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  31. package/.opencode/plugin/techieflow.js +40 -13
  32. package/.tfcore/agents/analyst.md +5 -13
  33. package/.tfcore/agents/architect.md +1 -16
  34. package/.tfcore/agents/flow-master.md +42 -215
  35. package/.tfcore/agents/verifier.md +24 -64
  36. package/.tfcore/core-config.yaml +10 -0
  37. package/.tfcore/hooks/block-git.sh +4 -4
  38. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  39. package/.tfcore/hooks/guard-build.sh +76 -0
  40. package/.tfcore/hooks/guard-db.sh +90 -0
  41. package/.tfcore/hooks/guard-metrics.sh +68 -0
  42. package/.tfcore/hooks/guard-status-html.sh +116 -71
  43. package/.tfcore/hooks/guard-status.sh +43 -16
  44. package/.tfcore/hooks/guard-verify-deps.sh +124 -0
  45. package/.tfcore/hooks/guard-verify.sh +70 -48
  46. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  47. package/.tfcore/install-manifest.yaml +0 -18
  48. package/.tfcore/routing.yaml +86 -14
  49. package/.tfcore/standards/coding-standards-core.md +61 -0
  50. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  51. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  52. package/.tfcore/tasks/_owner-language.md +26 -0
  53. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  54. package/.tfcore/tasks/_status-update-gate.md +10 -74
  55. package/.tfcore/tasks/_yolo-mode.md +20 -58
  56. package/.tfcore/tasks/amend-docs.md +21 -143
  57. package/.tfcore/tasks/build-phase.md +14 -224
  58. package/.tfcore/tasks/create-deep-research-prompt.md +4 -0
  59. package/.tfcore/tasks/create-doc.md +4 -0
  60. package/.tfcore/tasks/create-project-brief.md +22 -0
  61. package/.tfcore/tasks/day1-brownfield.md +31 -505
  62. package/.tfcore/tasks/day1-greenfield.md +28 -213
  63. package/.tfcore/tasks/deploy-checklist.md +19 -0
  64. package/.tfcore/tasks/devguide.md +10 -219
  65. package/.tfcore/tasks/facilitate-brainstorming-session.md +4 -0
  66. package/.tfcore/tasks/fix-issues.md +10 -129
  67. package/.tfcore/tasks/generate-html.md +5 -1
  68. package/.tfcore/tasks/handoff-phase.md +18 -16
  69. package/.tfcore/tasks/log-miss.md +19 -158
  70. package/.tfcore/tasks/metrics-report.md +3 -1
  71. package/.tfcore/tasks/mockups.md +12 -117
  72. package/.tfcore/tasks/refresh-status.md +9 -130
  73. package/.tfcore/tasks/split-brd.md +10 -126
  74. package/.tfcore/tasks/triage-and-fix.md +14 -0
  75. package/.tfcore/tasks/triage-issues.md +11 -152
  76. package/.tfcore/tasks/verify-phase.md +36 -572
  77. package/.tfcore/telemetry/SCHEMA.md +100 -8
  78. package/.tfcore/telemetry/tf-metrics.sh +564 -32
  79. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  80. package/.tfcore/templates/stack-questions.md +77 -0
  81. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  82. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  83. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  84. package/.tfcore/templates/v4custom/app-brief-tmpl.md +65 -0
  85. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  86. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  87. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  88. package/.tfcore/templates/v4custom/app-decision-request-tmpl.md +85 -0
  89. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  90. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  91. package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +86 -31
  92. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  93. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  94. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  95. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  96. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  97. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  98. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  99. package/.tfcore/templates/v4custom/html-render-shell.md +1 -1
  100. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  101. package/.tfcore/user-guide.md +0 -23
  102. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  103. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  104. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  105. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  106. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  107. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  108. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  109. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  110. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  111. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  112. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  113. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  114. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  115. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  116. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  117. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  118. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  119. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  120. package/.tfcore/utils/tf-brd-status.py +176 -0
  121. package/.tfcore/utils/tf-brd-status.sh +19 -0
  122. package/.tfcore/utils/tf-build-list.py +211 -0
  123. package/.tfcore/utils/tf-build-list.sh +21 -0
  124. package/.tfcore/utils/tf-build.sh +143 -0
  125. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  126. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  127. package/.tfcore/utils/tf-day1-files.py +208 -0
  128. package/.tfcore/utils/tf-day1-files.sh +20 -0
  129. package/.tfcore/utils/tf-devguide-list.py +249 -0
  130. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  131. package/.tfcore/utils/tf-doc-check.py +1369 -0
  132. package/.tfcore/utils/tf-doc-check.sh +29 -0
  133. package/.tfcore/utils/tf-emit.sh +545 -18
  134. package/.tfcore/utils/tf-fix-close.sh +115 -0
  135. package/.tfcore/utils/tf-gitignore-audit.sh +50 -3
  136. package/.tfcore/utils/tf-goal.sh +284 -66
  137. package/.tfcore/utils/tf-harness.sh +4 -9
  138. package/.tfcore/utils/tf-log-miss.py +197 -0
  139. package/.tfcore/utils/tf-log-miss.sh +14 -0
  140. package/.tfcore/utils/tf-misses-md.py +208 -0
  141. package/.tfcore/utils/tf-misses-md.sh +14 -0
  142. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  143. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  144. package/.tfcore/utils/tf-model-pick.sh +475 -0
  145. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  146. package/.tfcore/utils/tf-perf-grade.py +97 -0
  147. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  148. package/.tfcore/utils/tf-phase.sh +92 -0
  149. package/.tfcore/utils/tf-render-html.py +20 -0
  150. package/.tfcore/utils/tf-routing-bind.sh +97 -6
  151. package/.tfcore/utils/tf-routing.sh +13 -7
  152. package/.tfcore/utils/tf-selfcheck.sh +323 -0
  153. package/.tfcore/utils/tf-split-brd.py +309 -0
  154. package/.tfcore/utils/tf-split-brd.sh +22 -0
  155. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  156. package/.tfcore/utils/tf-status-facts.py +358 -0
  157. package/.tfcore/utils/tf-status-facts.sh +20 -0
  158. package/.tfcore/utils/tf-triage.py +243 -0
  159. package/.tfcore/utils/tf-triage.sh +16 -0
  160. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  161. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  162. package/.tfcore/utils/tf-verify-env.sh +88 -0
  163. package/.tfcore/utils/tf-verify-list.py +270 -0
  164. package/.tfcore/utils/tf-verify-list.sh +13 -0
  165. package/.tfcore/utils/tf-verify-screens.mjs +355 -0
  166. package/.tfcore/utils/tf-verify-screens.sh +31 -0
  167. package/.tfcore/utils/tf-verify-tests.sh +166 -0
  168. package/.tfcore/utils/tf-verify-verdict.py +292 -0
  169. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  170. package/.tfcore/utils/tf-yolo.sh +59 -0
  171. package/README.md +120 -1145
  172. package/docs/TechieFlow-Installation.md +1 -5
  173. package/opencode.jsonc +37 -33
  174. package/package.json +1 -5
  175. package/scripts/install.mjs +56 -46
  176. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  177. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  178. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  179. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  180. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  181. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  182. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  183. package/.codex/config.toml +0 -46
  184. package/.codex/hooks.json +0 -63
  185. package/.codex/rules/techieflow.rules +0 -8
  186. package/.tfcore/hooks/codex-adapter.py +0 -248
  187. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  188. package/.tfcore/tasks/author-brd.md +0 -79
  189. package/.tfcore/tasks/document-project.md +0 -343
  190. package/.tfcore/tasks/execute-checklist.md +0 -86
  191. package/.tfcore/tasks/index-docs.md +0 -173
  192. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  193. package/.tfcore/tasks/shard-doc.md +0 -185
  194. package/.tfcore/templates/architecture-tmpl.yaml +0 -650
  195. package/.tfcore/templates/brownfield-architecture-tmpl.yaml +0 -476
  196. package/.tfcore/templates/competitor-analysis-tmpl.yaml +0 -306
  197. package/.tfcore/templates/front-end-architecture-tmpl.yaml +0 -218
  198. package/.tfcore/templates/fullstack-architecture-tmpl.yaml +0 -823
  199. package/.tfcore/templates/market-research-tmpl.yaml +0 -252
  200. package/.tfcore/templates/project-brief-tmpl.yaml +0 -221
  201. package/.tfcore/utils/techieflow-doc-template.md +0 -325
  202. package/.tfcore/utils/tf-codex-bind.py +0 -196
  203. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  204. package/WORKFLOW.html +0 -1986
@@ -0,0 +1,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 — blocks self-attested `Verified` verdicts.
2
+ # TechieFlow PreToolUse hook — a `Verified` cell is written only by an executed verify run.
3
3
  #
4
- # `Verified` is the verifier's verdict, written ONLY by an EXECUTED verify-phase
5
- # run (chained inline by build-phase §6b, or a standalone `*verify`). On
6
- # 2026-07-09 a build orchestrator did its own smoke and wrote `Verified`
7
- # verdicts itself — the prose rule ("chain the verifier") was skipped silently.
8
- # This hook makes the gate MECHANICAL, the same way block-git.sh made the git
9
- # ban mechanical and guard-status.sh made the status shape mechanical.
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
- # How it works: verify-phase §6 writes a run ledger `docs/.last-verify.json`
12
- # ({"date","app","scope","booted","gates","evidence"}) immediately before it
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 introduced (demotions, moves, other edits)
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
- # Introducing `Verified` — require a same-day run ledger next to the checklist.
63
- ledger_path = os.path.join(os.path.dirname(path), ".last-verify.json")
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
- stale = "does not exist"
89
+ ledger, problem = None, "does not exist"
72
90
  except Exception:
73
- stale = "exists but is not valid JSON"
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 run ledger {ledger_path} {stale}.",
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
- " - If you are *refresh-status* reconciling a status column to PRE-EXISTING "
96
- "dated VERIFIED evidence already in that row's Remarks: write the ledger with "
97
- '{"date":"<today>","mode":"reconcile","evidence":"<the pre-existing remark '
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
- " - Writing the ledger WITHOUT having run verify-phase's steps is falsifying "
103
- "the audit record.",
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
- # `codex-adapter.py session-start` (Codex) and on the first root
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
- # (Codex re-fires session-start on every prompt; a walk of a big tree on every
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
@@ -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`; Codex model slugs: list with `codex debug 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
- enabled: false
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 below are from this machine's `opencode models` (opencode-go /
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: opus
36
- opencode: opencode-go/kimi-k3
37
- codex: gpt-5.6
50
+ claude: sonnet
51
+ opencode: openai/gpt-5.6-terra
38
52
  standard:
39
53
  claude: sonnet
40
- opencode: opencode-go/kimi-k2.7-code
41
- codex: gpt-5.6-terra
54
+ opencode: openai/gpt-5.6-terra
42
55
  economy:
43
56
  claude: haiku
44
- opencode: opencode-go/deepseek-v4-flash
45
- codex: gpt-5.6-luna
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 and Codex; ignored by OpenCode).
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.