@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
package/.tfcore/core-config.yaml
CHANGED
|
@@ -18,6 +18,16 @@ architecture:
|
|
|
18
18
|
architectureSharded: false
|
|
19
19
|
architectureShardedLocation: docs/architecture
|
|
20
20
|
customTechnicalDocuments: null
|
|
21
|
+
# appSize / appKind: set by the day-1 tasks (reset Session 3, 2026-09-04). Size is Small (up to 10
|
|
22
|
+
# screens, one role, 50 requirements), Medium (up to 20 screens, 100 requirements) or Large (split
|
|
23
|
+
# into phases). Kind is app or library. tf-doc-check.sh reads them when a document header lacks them.
|
|
24
|
+
appSize: null
|
|
25
|
+
appKind: null
|
|
26
|
+
# appPhase: the phase being worked on a Large project (reset Sitting 4b, 2026-09-05). 1 means the
|
|
27
|
+
# plain file names (docs/{AppName}-BRD.md, -Checklist, -UIDesign, -DevGuide); 2 onward means
|
|
28
|
+
# docs/{AppName}-P2-BRD.md and so on. Small and Medium projects leave it at 1. Set with
|
|
29
|
+
# tf-day1-files.sh {AppName} --phase N; handoff moves it on when a phase is all Verified.
|
|
30
|
+
appPhase: 1
|
|
21
31
|
# devLoadAlwaysFiles: the day-1 tasks rewrite this list with the project's concrete paths
|
|
22
32
|
# (docs/{AppName}-Coding-Standards.md + docs/{AppName}-Architecture.md). Empty until then —
|
|
23
33
|
# the stock paths (docs/architecture/coding-standards.md etc.) never exist in this flow.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
#
|
|
8
8
|
# 1. GIT GUARD. Git is MANUAL in TechieFlow — agents never COMMIT, PUSH, STAGE,
|
|
9
9
|
# RESET, CHECKOUT or otherwise WRITE to git, ever (.tfcore/tasks/_smoke-test-policy.md
|
|
10
|
-
#
|
|
10
|
+
# owner rule; this hook is the only place it is stated). Outside YOLO,
|
|
11
11
|
# agents never READ git either (status/log/diff/blame) — status evidence is the
|
|
12
12
|
# checklist table + working tree + a fresh build. In YOLO mode (below) READ-ONLY
|
|
13
13
|
# git/gh is allowed; WRITES stay blocked in every mode. settings.json's deny
|
|
@@ -55,7 +55,7 @@ block_git_msg() {
|
|
|
55
55
|
cat >&2 <<'MSG'
|
|
56
56
|
BLOCKED by TechieFlow policy: agents NEVER WRITE to git or gh — no commit / push / add / reset / checkout / stash / tag / merge / rebase / branch edits / gh pr|issue create|merge|close. Git is manual, owner-only, in EVERY mode including YOLO.
|
|
57
57
|
Do NOT retry with another git/gh form. Instead:
|
|
58
|
-
- Status updates / "what changed":
|
|
58
|
+
- Status updates / "what changed": bash .tfcore/utils/tf-status-facts.sh <App> prints the facts from the checklist; the working-tree files (ls -lt, find -newer, Read) are the as-built code — never commit history.
|
|
59
59
|
- Investigating code: read the files on disk at file:line — the working tree IS the as-built code.
|
|
60
60
|
- Committing/tagging: never yours. Record REQ IDs in the checklist Remarks, not in commits. The owner commits manually.
|
|
61
61
|
- If your command merely CONTAINS the word git (e.g. writing a doc, grepping for the string): use the Write/Edit/Grep tools instead of bash — that is the standing tool-preference rule anyway.
|
|
@@ -64,7 +64,7 @@ MSG
|
|
|
64
64
|
block_read_msg() {
|
|
65
65
|
if [[ "$STRICT_GIT" == "1" ]]; then
|
|
66
66
|
cat >&2 <<'MSG'
|
|
67
|
-
BLOCKED by TechieFlow
|
|
67
|
+
BLOCKED by TechieFlow policy: agents do not run any git or gh command, including read-only status/log/diff/blame, in any mode.
|
|
68
68
|
Use the checklist Requirements Status table, working-tree files, filesystem metadata, and fresh build/test evidence instead. The owner performs version-control operations manually.
|
|
69
69
|
MSG
|
|
70
70
|
return
|
|
@@ -72,7 +72,7 @@ MSG
|
|
|
72
72
|
cat >&2 <<'MSG'
|
|
73
73
|
BLOCKED by TechieFlow policy: agents do not READ git outside YOLO mode (no status/log/diff/show/blame/grep) — git is manual, owner-only.
|
|
74
74
|
Do NOT retry with another git/gh form. Instead:
|
|
75
|
-
- Status updates / "what changed":
|
|
75
|
+
- Status updates / "what changed": bash .tfcore/utils/tf-status-facts.sh <App> prints the facts from the checklist; the working-tree files (ls -lt, find -newer, Read) are the as-built code — never commit history.
|
|
76
76
|
- Investigating code: read the files on disk at file:line — the working tree IS the as-built code.
|
|
77
77
|
- Read-only git (status/log/diff/blame) becomes available only in YOLO / goal mode (`*yolo` → bash .tfcore/utils/tf-yolo.sh on). Git WRITES are never available.
|
|
78
78
|
MSG
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
# and overriding the config's pinned outputDir. This hook makes the rule
|
|
14
14
|
# MECHANICAL, the same way block-git.sh made the git ban mechanical.
|
|
15
15
|
#
|
|
16
|
-
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Bash");
|
|
17
|
-
#
|
|
16
|
+
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Bash");
|
|
17
|
+
# OpenCode via .opencode/plugin/techieflow.js.
|
|
18
18
|
# Exit 2 + stderr = block the call and feed the message back to the agent.
|
|
19
19
|
#
|
|
20
20
|
# What it blocks:
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TechieFlow PreToolUse hook — in YOLO mode a build, test or app run stays in the FOREGROUND.
|
|
3
|
+
#
|
|
4
|
+
# Why a hook (owner, 2026-09-06; FR-46): twice in one build (MISS-TechieFlow-20260905-23 and
|
|
5
|
+
# MISS-TechieFlow-20260906-05) the agent started the build as a background job and ended its
|
|
6
|
+
# turn "waiting for the build". The turn ending kills the job; the supervisor only re-prompts;
|
|
7
|
+
# three cycles were lost that way. A rule ignored twice becomes a hook, not a third paragraph.
|
|
8
|
+
#
|
|
9
|
+
# What it refuses, only while YOLO is on (TF_YOLO=1, the yolo.json flag, or a harness in
|
|
10
|
+
# bypass / --auto mode):
|
|
11
|
+
# - a Bash call with run_in_background true whose command builds, tests, runs or publishes:
|
|
12
|
+
# tf-build.sh, dotnet build|test|run|publish|watch, msbuild, npm|pnpm|yarn build|test|start,
|
|
13
|
+
# also through winrun / cmd.exe / powershell.exe
|
|
14
|
+
# - the same commands backgrounded inside the command text: nohup, setsid, disown, or a
|
|
15
|
+
# trailing / inline `&` (not `&&`)
|
|
16
|
+
# Everything else passes. Outside YOLO nothing changes (the owner may background what they like).
|
|
17
|
+
#
|
|
18
|
+
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Bash"); OpenCode via
|
|
19
|
+
# .opencode/plugin/techieflow.js. Exit 2 + stderr = block the call and tell the agent why.
|
|
20
|
+
# Fails OPEN (exit 0) if python3 or parseable JSON is unavailable.
|
|
21
|
+
|
|
22
|
+
INPUT="$(cat)"
|
|
23
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
24
|
+
ROOT="${CLAUDE_PROJECT_DIR:-${TF_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}}"
|
|
25
|
+
|
|
26
|
+
yolo=0
|
|
27
|
+
if [[ "${TF_YOLO:-0}" == "1" ]]; then yolo=1
|
|
28
|
+
elif [[ -f "$ROOT/.tfcore/utils/tf-yolo.sh" ]] && CLAUDE_PROJECT_DIR="$ROOT" bash "$ROOT/.tfcore/utils/tf-yolo.sh" is-on >/dev/null 2>&1; then yolo=1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
TF_HOOK_INPUT="$INPUT" TF_YOLO_ON="$yolo" python3 - <<'PY'
|
|
32
|
+
import json, os, re, sys
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
data = json.loads(os.environ.get("TF_HOOK_INPUT", ""))
|
|
36
|
+
except Exception:
|
|
37
|
+
sys.exit(0)
|
|
38
|
+
ti = data.get("tool_input") or {}
|
|
39
|
+
cmd = ti.get("command")
|
|
40
|
+
if not isinstance(cmd, str) or not cmd.strip():
|
|
41
|
+
sys.exit(0)
|
|
42
|
+
|
|
43
|
+
yolo = os.environ.get("TF_YOLO_ON") == "1" or str(data.get("permission_mode", "")) in ("bypassPermissions", "auto")
|
|
44
|
+
if not yolo:
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
BUILD = (r"\btf-build\.sh\b"
|
|
48
|
+
r"|\bdotnet\s+(build|test|run|publish|watch)\b"
|
|
49
|
+
r"|\bmsbuild\b"
|
|
50
|
+
r"|\b(npm|pnpm|yarn)\s+(run\s+)?(build|test|start|dev)\b"
|
|
51
|
+
# the verify tooling (Sitting 4c, MISS-TechieFlow-20260906-22): a backgrounded browser
|
|
52
|
+
# test run ended a verify turn "waiting for the tests" on 2026-09-06
|
|
53
|
+
r"|\b(npx\s+)?playwright\s+test\b"
|
|
54
|
+
r"|\btf-verify-(tests|screens|boot|env)\.sh\b"
|
|
55
|
+
r"|\btf-(assets|mockup-parity|perf)\.sh\b"
|
|
56
|
+
r"|\bpytest\b")
|
|
57
|
+
low = cmd.lower()
|
|
58
|
+
if not re.search(BUILD, low):
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
|
|
61
|
+
bg_flag = bool(ti.get("run_in_background"))
|
|
62
|
+
# `&` that is not `&&` and not inside `2>&1` / `>&2`
|
|
63
|
+
inline_bg = bool(re.search(r"(?<![&>\d])&(?![&\d])", low)) or bool(re.search(r"\b(nohup|setsid|disown)\b", low))
|
|
64
|
+
if not (bg_flag or inline_bg):
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
|
|
67
|
+
how = "run_in_background" if bg_flag else "nohup / setsid / a trailing &"
|
|
68
|
+
print("BLOCKED by TechieFlow policy: in YOLO mode a build, test or app run is NEVER started in "
|
|
69
|
+
f"the background ({how} here). Ending the turn to wait for it kills the job, and nobody "
|
|
70
|
+
"wakes you; three build cycles were lost that way on 2026-09-06. Run it in the "
|
|
71
|
+
"foreground and wait: bash .tfcore/utils/tf-build.sh [build|test], npx playwright test, or the "
|
|
72
|
+
"tf-verify-*.sh scripts (a timeout of ten minutes is fine), then act on the result line. Owner rule 2026-09-06 (FR-46).",
|
|
73
|
+
file=sys.stderr)
|
|
74
|
+
sys.exit(2)
|
|
75
|
+
PY
|
|
76
|
+
exit $?
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TechieFlow PreToolUse hook — database writes happen only from build-phase or fix-issues,
|
|
3
|
+
# and only through the migration path the Stack decisions name (owner, 2026-09-05;
|
|
4
|
+
# MISS-TechieFlow-20260905-09: a day-1 run created a user and patched two stored
|
|
5
|
+
# procedures in the development database).
|
|
6
|
+
#
|
|
7
|
+
# What it refuses, in every command:
|
|
8
|
+
# - a direct SQL write through a client: psql, pgcli, sqlite3, mysql, mariadb, sqlcmd,
|
|
9
|
+
# mongosh with INSERT / UPDATE / DELETE / CREATE / ALTER / DROP / TRUNCATE / GRANT /
|
|
10
|
+
# REPLACE / MERGE in the command text, a -f / -i script, or stdin redirected from a file
|
|
11
|
+
# - dropping or resetting a database
|
|
12
|
+
# What it allows only while .tfcore/.session/phase.json says build-phase or fix-issues
|
|
13
|
+
# (written by bash .tfcore/utils/tf-phase.sh start <command>, step 0 of every task):
|
|
14
|
+
# - a migration runner: dotnet ef database update, dotnet run --project <…Db|…Migration(s)…>,
|
|
15
|
+
# dbup, flyway migrate, liquibase update, alembic upgrade, prisma migrate, knex migrate,
|
|
16
|
+
# rails db:migrate, sequelize db:migrate
|
|
17
|
+
# Reads (SELECT, \d, .tables, dumps to stdout) pass. YOLO does not relax this.
|
|
18
|
+
#
|
|
19
|
+
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Bash"); OpenCode via
|
|
20
|
+
# .opencode/plugin/techieflow.js. Exit 2 + stderr = block the call and tell the agent why.
|
|
21
|
+
# Fails OPEN (exit 0) if python3 or parseable JSON is unavailable.
|
|
22
|
+
|
|
23
|
+
INPUT="$(cat)"
|
|
24
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
25
|
+
ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
26
|
+
|
|
27
|
+
TF_HOOK_INPUT="$INPUT" TF_ROOT="$ROOT" python3 - <<'PY'
|
|
28
|
+
import json, os, re, sys, time
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
data = json.loads(os.environ.get("TF_HOOK_INPUT", ""))
|
|
32
|
+
except Exception:
|
|
33
|
+
sys.exit(0)
|
|
34
|
+
cmd = (data.get("tool_input") or {}).get("command")
|
|
35
|
+
if not isinstance(cmd, str) or not cmd.strip():
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
|
|
38
|
+
WRITE_VERBS = r"\b(insert|update|delete|create|alter|drop|truncate|grant|revoke|replace|merge|upsert)\b"
|
|
39
|
+
CLIENTS = r"\b(psql|pgcli|sqlite3|mysql|mariadb|sqlcmd|mongosh|mongo)\b"
|
|
40
|
+
MIGRATORS = (r"\bdotnet\s+ef\s+database\s+(update|drop)\b"
|
|
41
|
+
r"|\bdotnet\s+run\b[^|;&]*--project\s+\S*(db|migration|migrations)\b"
|
|
42
|
+
r"|\bdbup\b|\bflyway\s+(migrate|clean|undo)\b|\bliquibase\s+(update|rollback|drop-all)\b"
|
|
43
|
+
r"|\balembic\s+(upgrade|downgrade)\b|\bprisma\s+(migrate|db\s+push)\b|\bknex\s+migrate\b"
|
|
44
|
+
r"|\brails\s+db:(migrate|reset|drop|schema:load)\b|\bsequelize\s+db:migrate\b|\bgoose\s+(up|down|reset)\b")
|
|
45
|
+
DROP_DB = r"\b(dropdb|drop\s+database|mongo(sh)?\b[^|;&]*dropDatabase)\b"
|
|
46
|
+
|
|
47
|
+
def phase():
|
|
48
|
+
p = os.path.join(os.environ.get("TF_ROOT", "."), ".tfcore", ".session", "phase.json")
|
|
49
|
+
try:
|
|
50
|
+
if time.time() - os.path.getmtime(p) > 24 * 3600:
|
|
51
|
+
return None
|
|
52
|
+
with open(p, encoding="utf-8") as fh:
|
|
53
|
+
return (json.load(fh) or {}).get("cmd")
|
|
54
|
+
except Exception:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
# Words inside an echo/printf string or a comment line are not commands: an echo naming the
|
|
58
|
+
# migration tool refused a verify run's read on 2026-09-06 (MISS-TechieFlow-20260906-20).
|
|
59
|
+
# Strip them before matching, so only a command that runs a migrator or writes through a
|
|
60
|
+
# client is refused.
|
|
61
|
+
low = cmd.lower()
|
|
62
|
+
low = re.sub(r"(?m)^\s*#.*$", "", low)
|
|
63
|
+
low = re.sub(r"\b(echo|printf)\s+(\"[^\"]*\"|'[^']*'|[^\n;|&]*)", r"\1", low)
|
|
64
|
+
direct_write = (re.search(CLIENTS, low)
|
|
65
|
+
and (re.search(WRITE_VERBS, low)
|
|
66
|
+
or re.search(r"\s-(f|i)\s+\S+\.sql\b", low)
|
|
67
|
+
or re.search(r"<\s*\S+\.sql\b", low)))
|
|
68
|
+
if direct_write or re.search(DROP_DB, low):
|
|
69
|
+
print("BLOCKED by TechieFlow policy: a direct database write (SQL through a client, or dropping "
|
|
70
|
+
"a database) is not allowed from any command. Schema and data changes go through the "
|
|
71
|
+
"migration path named in the Architecture's Stack decisions (for the .NET answer set, the "
|
|
72
|
+
"<App>Db DbUp project), run from *build-phase or *fix-issues. A read (SELECT, \\d, .tables) "
|
|
73
|
+
"is fine. Owner rule 2026-09-05.", file=sys.stderr)
|
|
74
|
+
sys.exit(2)
|
|
75
|
+
|
|
76
|
+
if re.search(MIGRATORS, low):
|
|
77
|
+
ph = phase()
|
|
78
|
+
if ph in ("build-phase", "fix-issues"):
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
who = f"the marker says {ph}" if ph else "no command marker is set"
|
|
81
|
+
print("BLOCKED by TechieFlow policy: a database migration runs only from *build-phase or "
|
|
82
|
+
f"*fix-issues, and {who}. Day-1, mockups, split-brd, amend-docs, devguide, verify, "
|
|
83
|
+
"triage and status commands write documents and records, never the database. If you are "
|
|
84
|
+
"inside build-phase or fix-issues, step 0 was skipped: run "
|
|
85
|
+
"bash .tfcore/utils/tf-phase.sh start <command> <App> and retry. Owner rule 2026-09-05.",
|
|
86
|
+
file=sys.stderr)
|
|
87
|
+
sys.exit(2)
|
|
88
|
+
sys.exit(0)
|
|
89
|
+
PY
|
|
90
|
+
exit $?
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TechieFlow PreToolUse hook — the telemetry streams are written ONLY by tf-emit.sh.
|
|
3
|
+
#
|
|
4
|
+
# docs/metrics/*.jsonl are append-only histories (SCHEMA.md §10, _metrics-emit-gate.md).
|
|
5
|
+
# This hook refuses every other way of writing them:
|
|
6
|
+
# - Write / Edit / MultiEdit whose file_path is a docs/metrics/*.jsonl file
|
|
7
|
+
# - a shell command that writes one: redirection (> >>), tee, cp/mv/install onto
|
|
8
|
+
# it, sed -i / perl -i, a python/node one-liner naming it, truncate/dd
|
|
9
|
+
# Reading them is fine. `bash .tfcore/utils/tf-emit.sh <stream>` never names the
|
|
10
|
+
# file on the command line, so it passes; so do tf-metrics.sh --report/--rollup.
|
|
11
|
+
#
|
|
12
|
+
# Wired in .claude/settings.json -> hooks.PreToolUse (matcher "Write|Edit|MultiEdit"
|
|
13
|
+
# AND matcher "Bash"); OpenCode via .opencode/plugin/techieflow.js.
|
|
14
|
+
# Exit 2 + stderr = block the call and feed the message back to the agent.
|
|
15
|
+
# Fails OPEN (exit 0) if python3 or parseable JSON is unavailable.
|
|
16
|
+
|
|
17
|
+
INPUT="$(cat)"
|
|
18
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
19
|
+
|
|
20
|
+
TF_HOOK_INPUT="$INPUT" python3 - <<'PY'
|
|
21
|
+
import json, os, re, sys
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
data = json.loads(os.environ.get("TF_HOOK_INPUT", ""))
|
|
25
|
+
except Exception:
|
|
26
|
+
sys.exit(0)
|
|
27
|
+
|
|
28
|
+
ti = data.get("tool_input") or {}
|
|
29
|
+
STREAM = r"docs/metrics/[\w.-]+\.jsonl"
|
|
30
|
+
# What makes a python/node command a WRITE rather than a read: an explicit write mode,
|
|
31
|
+
# something that emits, or a move onto the path. A read names none of these.
|
|
32
|
+
WRITEY = (r"(?:['\"][wax]b?\+?['\"]|\.write\b|\.writelines\b|json\.dump\b|"
|
|
33
|
+
r"\btruncate\b|\bos\.rename\b|\bos\.replace\b|\bshutil\.(?:copy|move)\b|"
|
|
34
|
+
r"createWriteStream|writeFileSync|appendFileSync)")
|
|
35
|
+
|
|
36
|
+
MSG = ("BLOCKED by TechieFlow policy: docs/metrics/*.jsonl are append-only telemetry "
|
|
37
|
+
"streams written only through bash .tfcore/utils/tf-emit.sh <stream> (stdin JSON). "
|
|
38
|
+
"Never Write, Edit, redirect or copy onto them. A wrong record is corrected by a "
|
|
39
|
+
"new record (miss-fix, miss-amend, a later gate), never by an edit.")
|
|
40
|
+
|
|
41
|
+
fp = ti.get("file_path") or ti.get("filePath")
|
|
42
|
+
if isinstance(fp, str) and re.search(STREAM + r"$", fp.replace("\\", "/"), re.I):
|
|
43
|
+
print(MSG, file=sys.stderr)
|
|
44
|
+
sys.exit(2)
|
|
45
|
+
|
|
46
|
+
cmd = ti.get("command")
|
|
47
|
+
if isinstance(cmd, str) and re.search(STREAM, cmd, re.I):
|
|
48
|
+
if re.search(
|
|
49
|
+
r"(>>?\s*[\"']?[^\s|;&]*" + STREAM + r")"
|
|
50
|
+
r"|(\btee\b[^|;&]*" + STREAM + r")"
|
|
51
|
+
r"|(\b(cp|mv|install)\b[^|;&]*" + STREAM + r")"
|
|
52
|
+
r"|(\b(sed|perl)\b[^|;&]*\s-[a-zA-Z]*i[a-zA-Z]*\b[^|;&]*" + STREAM + r")"
|
|
53
|
+
# A python/node command NAMING a stream used to be blocked outright, which also
|
|
54
|
+
# refused every READ -- and this file's own header says reading is fine. Checking
|
|
55
|
+
# a stream with a one-liner is a normal, frequent thing to do, and a guard that
|
|
56
|
+
# blocks it teaches people to route around the guard. So the clause now needs a
|
|
57
|
+
# write in the command as well as the path. MISS-TechieFlow-20260909-03.
|
|
58
|
+
r"|(\b(python3?|node)\b[^|;&]*" + STREAM + r"[^|;&]*" + WRITEY + r")"
|
|
59
|
+
r"|(\b(python3?|node)\b[^|;&]*" + WRITEY + r"[^|;&]*" + STREAM + r")"
|
|
60
|
+
r"|(\b(truncate|dd)\b[^|;&]*" + STREAM + r")",
|
|
61
|
+
cmd, re.I,
|
|
62
|
+
):
|
|
63
|
+
print(MSG, file=sys.stderr)
|
|
64
|
+
sys.exit(2)
|
|
65
|
+
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
PY
|
|
68
|
+
exit $?
|
|
@@ -1,102 +1,147 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# TechieFlow Stop hook — refuses to end a turn while
|
|
2
|
+
# TechieFlow Stop hook — refuses to end a turn while the status gate is incomplete.
|
|
3
3
|
#
|
|
4
|
-
# _status-update-gate.md
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# defeats the entire purpose of the gate.
|
|
4
|
+
# The gate (_status-update-gate.md) is six steps an agent can forget. This hook
|
|
5
|
+
# checks the RESULT of each step, so a forgotten step cannot end the turn. It
|
|
6
|
+
# prints one line per outstanding item, each naming the one command to run.
|
|
8
7
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
8
|
+
# 1. PROJECT-STATUS.html missing or older than PROJECT-STATUS.md
|
|
9
|
+
# -> bash .tfcore/utils/tf-render-html.sh PROJECT-STATUS.md
|
|
10
|
+
# Only when PROJECT-STATUS.md was written in this session (newer than the
|
|
11
|
+
# session pointer .tfcore/.session/<harness>.json):
|
|
12
|
+
# 2. tf-doc-check.sh FAILs on PROJECT-STATUS.md -> fix the file, re-run the check
|
|
13
|
+
# 3. docs/<App>-BRD.md older than docs/<App>-Checklist.md
|
|
14
|
+
# -> bash .tfcore/utils/tf-brd-status.sh <App>
|
|
15
|
+
# 4. no runs.jsonl record after the status write -> the run record (_metrics-emit-gate.md)
|
|
14
16
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# Wired in .claude/settings.json → hooks.Stop; Codex via .codex/hooks.json Stop →
|
|
21
|
-
# codex-adapter.py stop; OpenCode via .opencode/plugin/techieflow.js session.idle
|
|
22
|
-
# (one-shot nudge — OpenCode has no blocking Stop hook).
|
|
23
|
-
# Exit 2 + stderr = block the stop and feed the message back to the agent.
|
|
24
|
-
# Honours stop_hook_active so a wedged turn can still terminate (no loop).
|
|
17
|
+
# Wired in .claude/settings.json -> hooks.Stop; OpenCode via
|
|
18
|
+
# .opencode/plugin/techieflow.js session.idle (a nudge: OpenCode has no blocking
|
|
19
|
+
# Stop hook). Exit 2 + stderr = block the stop and feed the message to the agent.
|
|
20
|
+
# Honours stop_hook_active so a wedged turn can still end (no loop).
|
|
25
21
|
# Fails OPEN (exit 0) if python3 or parseable JSON is unavailable.
|
|
26
22
|
|
|
27
23
|
INPUT="$(cat)"
|
|
28
24
|
command -v python3 >/dev/null 2>&1 || exit 0
|
|
29
25
|
|
|
30
26
|
TF_HOOK_INPUT="$INPUT" TF_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}" python3 - <<'PY'
|
|
31
|
-
import
|
|
27
|
+
import datetime
|
|
28
|
+
import glob
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import subprocess
|
|
32
|
+
import sys
|
|
32
33
|
|
|
33
34
|
try:
|
|
34
35
|
data = json.loads(os.environ.get("TF_HOOK_INPUT", ""))
|
|
35
36
|
except Exception:
|
|
36
37
|
sys.exit(0)
|
|
37
38
|
|
|
38
|
-
# Already re-invoked once for this stop — let the turn end rather than loop.
|
|
39
39
|
if data.get("stop_hook_active"):
|
|
40
40
|
sys.exit(0)
|
|
41
41
|
|
|
42
42
|
root = os.environ.get("TF_PROJECT_DIR") or os.getcwd()
|
|
43
43
|
md = os.path.join(root, "PROJECT-STATUS.md")
|
|
44
44
|
html = os.path.join(root, "PROJECT-STATUS.html")
|
|
45
|
-
|
|
46
45
|
if not os.path.isfile(md):
|
|
47
46
|
sys.exit(0)
|
|
48
47
|
|
|
48
|
+
problems = []
|
|
49
49
|
md_mtime = os.path.getmtime(md)
|
|
50
50
|
|
|
51
|
+
# 1. HTML present and current
|
|
51
52
|
if not os.path.isfile(html):
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if md_mtime > html_mtime:
|
|
65
|
-
drift = int(md_mtime - html_mtime)
|
|
66
|
-
if drift >= 3600:
|
|
67
|
-
ago = f"{drift // 3600}h {(drift % 3600) // 60}m"
|
|
68
|
-
elif drift >= 60:
|
|
69
|
-
ago = f"{drift // 60}m"
|
|
53
|
+
problems.append("PROJECT-STATUS.html does not exist. Run: bash .tfcore/utils/tf-render-html.sh PROJECT-STATUS.md")
|
|
54
|
+
elif md_mtime > os.path.getmtime(html):
|
|
55
|
+
problems.append("PROJECT-STATUS.html is older than PROJECT-STATUS.md. Run: bash .tfcore/utils/tf-render-html.sh PROJECT-STATUS.md")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def session_start():
|
|
59
|
+
"""mtime of the harness's session pointer; None when there is none (checks 2-4 skipped)."""
|
|
60
|
+
d = os.path.join(root, ".tfcore", ".session")
|
|
61
|
+
if any(k.startswith("OPENCODE") for k in os.environ):
|
|
62
|
+
cands = [os.path.join(d, "opencode.json")]
|
|
63
|
+
elif os.environ.get("CLAUDECODE") or any(k.startswith("CLAUDE_CODE") for k in os.environ):
|
|
64
|
+
cands = [os.path.join(d, "claude-code.json")]
|
|
70
65
|
else:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
66
|
+
cands = [os.path.join(d, "claude-code.json"), os.path.join(d, "opencode.json")]
|
|
67
|
+
times = [os.path.getmtime(p) for p in cands if os.path.isfile(p)]
|
|
68
|
+
return max(times) if times else None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
start = session_start()
|
|
72
|
+
written_this_session = start is not None and md_mtime >= start
|
|
73
|
+
|
|
74
|
+
if written_this_session:
|
|
75
|
+
# 2. the checker passes on the status file
|
|
76
|
+
try:
|
|
77
|
+
chk = os.path.join(root, ".tfcore", "utils", "tf-doc-check.sh")
|
|
78
|
+
if os.path.isfile(chk):
|
|
79
|
+
out = subprocess.run(["bash", chk, "--quiet", "PROJECT-STATUS.md"], cwd=root,
|
|
80
|
+
capture_output=True, text=True, timeout=60).stdout
|
|
81
|
+
fails = [l for l in out.splitlines() if l.startswith("FAIL")]
|
|
82
|
+
if fails:
|
|
83
|
+
problems.append(f"PROJECT-STATUS.md fails the document check ({len(fails)} line(s)). Fix it, then run: bash .tfcore/utils/tf-doc-check.sh PROJECT-STATUS.md")
|
|
84
|
+
problems.extend(" " + l for l in fails[:5])
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
# 2b. the checker passes on every checklist written this session. Until 2026-09-06 only
|
|
89
|
+
# PROJECT-STATUS was checked here, so a build closed its gate with three Remarks cells over
|
|
90
|
+
# 60 words in the checklist and nothing refused the stop (MISS-TechieFlow-20260906-06).
|
|
91
|
+
try:
|
|
92
|
+
chk = os.path.join(root, ".tfcore", "utils", "tf-doc-check.sh")
|
|
93
|
+
if os.path.isfile(chk):
|
|
94
|
+
for cl in sorted(glob.glob(os.path.join(root, "docs", "*-Checklist.md"))):
|
|
95
|
+
if os.path.getmtime(cl) < start:
|
|
96
|
+
continue
|
|
97
|
+
rel = os.path.relpath(cl, root)
|
|
98
|
+
out = subprocess.run(["bash", chk, "--quiet", rel], cwd=root,
|
|
99
|
+
capture_output=True, text=True, timeout=120).stdout
|
|
100
|
+
fails = [l for l in out.splitlines() if l.startswith("FAIL")]
|
|
101
|
+
if fails:
|
|
102
|
+
problems.append(f"{rel} fails the document check ({len(fails)} line(s)). Fix the rows, then run: bash .tfcore/utils/tf-doc-check.sh {rel}")
|
|
103
|
+
problems.extend(" " + l for l in fails[:5])
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
# 3. the BRD's Development status table is not older than the checklist
|
|
108
|
+
try:
|
|
109
|
+
for cl in glob.glob(os.path.join(root, "docs", "*-Checklist.md")):
|
|
110
|
+
app = os.path.basename(cl)[: -len("-Checklist.md")]
|
|
111
|
+
brd = os.path.join(root, "docs", f"{app}-BRD.md")
|
|
112
|
+
if os.path.isfile(brd) and os.path.getmtime(cl) > os.path.getmtime(brd):
|
|
113
|
+
problems.append(f"docs/{app}-BRD.md is older than docs/{app}-Checklist.md. Run: bash .tfcore/utils/tf-brd-status.sh {app}")
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
# 4. a run record follows the status write (5 minutes of tolerance for emit-before-write)
|
|
118
|
+
try:
|
|
119
|
+
runs = os.path.join(root, "docs", "metrics", "runs.jsonl")
|
|
120
|
+
last = None
|
|
121
|
+
if os.path.isfile(runs):
|
|
122
|
+
with open(runs, encoding="utf-8", errors="replace") as f:
|
|
123
|
+
for line in f:
|
|
124
|
+
try:
|
|
125
|
+
ts = json.loads(line).get("ts")
|
|
126
|
+
except Exception:
|
|
127
|
+
continue
|
|
128
|
+
if ts:
|
|
129
|
+
last = ts
|
|
130
|
+
last_epoch = None
|
|
131
|
+
if last:
|
|
132
|
+
last_epoch = datetime.datetime.strptime(last[:19], "%Y-%m-%dT%H:%M:%S").replace(
|
|
133
|
+
tzinfo=datetime.timezone.utc).timestamp()
|
|
134
|
+
if last_epoch is None or last_epoch < md_mtime - 300:
|
|
135
|
+
problems.append("PROJECT-STATUS.md was written but no run record follows it in docs/metrics/runs.jsonl. Append the run record: .tfcore/tasks/_metrics-emit-gate.md")
|
|
136
|
+
except Exception:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
if not problems:
|
|
140
|
+
sys.exit(0)
|
|
141
|
+
|
|
142
|
+
print("BLOCKED by TechieFlow policy: the status gate is not complete (_status-update-gate.md).", file=sys.stderr)
|
|
143
|
+
for p in problems:
|
|
144
|
+
print(" - " + p, file=sys.stderr)
|
|
145
|
+
sys.exit(2)
|
|
101
146
|
PY
|
|
102
147
|
exit $?
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# TechieFlow PreToolUse hook — blocks convoluted PROJECT-STATUS.md writes.
|
|
3
3
|
#
|
|
4
|
-
# PROJECT-STATUS is a
|
|
5
|
-
# append-log.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# ban mechanical.
|
|
4
|
+
# PROJECT-STATUS is a one-page snapshot agents overwrite in place, never an
|
|
5
|
+
# append-log. The shape is the template .tfcore/templates/v4custom/app-project-status-tmpl.md;
|
|
6
|
+
# the steps are .tfcore/tasks/_status-update-gate.md. The prose rule alone kept
|
|
7
|
+
# failing (AstroLyfe's 286-line mess, repeat offenses after); this hook makes the
|
|
8
|
+
# shape mechanical, the same way block-git.sh made the git ban mechanical.
|
|
10
9
|
#
|
|
11
|
-
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Write|Edit|MultiEdit"
|
|
10
|
+
# Wired in .claude/settings.json → hooks.PreToolUse (matcher "Write|Edit|MultiEdit"
|
|
11
|
+
# AND matcher "Bash"); OpenCode via .opencode/plugin/techieflow.js.
|
|
12
12
|
# Exit 2 + stderr = block the call and feed the message back to the agent.
|
|
13
13
|
#
|
|
14
14
|
# What it blocks (only for files named PROJECT-STATUS.md):
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
# - Headings that name a command run (*verify / *build-phase / *fix-issues).
|
|
18
18
|
# - A paragraph stuffed into `current_phase:` (must be ONE short line).
|
|
19
19
|
# - A full-file Write longer than 120 lines (template shape is ~60).
|
|
20
|
+
# - (Bash) any shell command that WRITES the file — redirection, tee, cp, mv,
|
|
21
|
+
# sed -i, a Python/Node one-liner — because a shell write is invisible to the
|
|
22
|
+
# Write/Edit checks above (reset Session 3, 2026-09-04). Reading it is fine.
|
|
23
|
+
# Content rules (section word limits, the two command blocks, the five-row log)
|
|
24
|
+
# are checked by .tfcore/utils/tf-doc-check.sh at the status gate.
|
|
20
25
|
# Fails OPEN (exit 0) if python3 or parseable JSON is unavailable.
|
|
21
26
|
|
|
22
27
|
INPUT="$(cat)"
|
|
@@ -32,6 +37,31 @@ except Exception:
|
|
|
32
37
|
sys.exit(0)
|
|
33
38
|
|
|
34
39
|
ti = data.get("tool_input") or {}
|
|
40
|
+
|
|
41
|
+
# --- Bash branch: refuse shell writes to PROJECT-STATUS.md -------------------
|
|
42
|
+
cmd = ti.get("command")
|
|
43
|
+
if isinstance(cmd, str):
|
|
44
|
+
if re.search(r"PROJECT-STATUS\.md", cmd, re.I) and re.search(
|
|
45
|
+
r"(>>?\s*[\"']?[^\s|;&]*PROJECT-STATUS\.md)" # > / >> redirection
|
|
46
|
+
r"|(\btee\b[^|;&]*PROJECT-STATUS\.md)" # tee
|
|
47
|
+
r"|(\b(cp|mv|install)\b[^|;&]*PROJECT-STATUS\.md)" # copy / move onto it
|
|
48
|
+
r"|(\b(sed|perl)\b[^|;&]*\s-[a-zA-Z]*i[a-zA-Z]*\b[^|;&]*PROJECT-STATUS\.md)" # in-place edit
|
|
49
|
+
r"|(\b(python3?|node)\b[^|;&]*PROJECT-STATUS\.md)" # a one-liner that opens it
|
|
50
|
+
r"|(\b(truncate|dd)\b[^|;&]*PROJECT-STATUS\.md)",
|
|
51
|
+
cmd, re.I,
|
|
52
|
+
):
|
|
53
|
+
print(
|
|
54
|
+
"BLOCKED by TechieFlow policy: PROJECT-STATUS.md is written ONLY through "
|
|
55
|
+
"the harness Write/Edit tool, never through the shell (redirection, tee, "
|
|
56
|
+
"cp, mv, sed -i, a script). The shell path bypasses the shape guard, which "
|
|
57
|
+
"is how status files got spoiled. Write the file with the Write tool in the "
|
|
58
|
+
"template shape (.tfcore/templates/v4custom/app-project-status-tmpl.md), "
|
|
59
|
+
"then run: bash .tfcore/utils/tf-doc-check.sh PROJECT-STATUS.md",
|
|
60
|
+
file=sys.stderr,
|
|
61
|
+
)
|
|
62
|
+
sys.exit(2)
|
|
63
|
+
sys.exit(0)
|
|
64
|
+
|
|
35
65
|
path = (ti.get("file_path") or "").replace("\\", "/")
|
|
36
66
|
if path.rsplit("/", 1)[-1].lower() != "project-status.md":
|
|
37
67
|
sys.exit(0)
|
|
@@ -97,24 +127,21 @@ if is_full_write:
|
|
|
97
127
|
errors.append(
|
|
98
128
|
f"This write is {n_lines} lines. PROJECT-STATUS is a one-page snapshot "
|
|
99
129
|
"(~60 lines, hard cap 120). TRIM to the template shape — blow-by-blow "
|
|
100
|
-
"history lives in the checklist Remarks and
|
|
130
|
+
"history lives in the checklist Remarks and the telemetry streams, never here."
|
|
101
131
|
)
|
|
102
132
|
|
|
103
133
|
if errors:
|
|
104
134
|
print(
|
|
105
|
-
"BLOCKED by TechieFlow policy: PROJECT-STATUS is a
|
|
106
|
-
"
|
|
107
|
-
"(.tfcore/tasks/_status-update-gate.md).",
|
|
135
|
+
"BLOCKED by TechieFlow policy: PROJECT-STATUS is a one-page snapshot you "
|
|
136
|
+
"overwrite in place, never append to (.tfcore/tasks/_status-update-gate.md).",
|
|
108
137
|
file=sys.stderr,
|
|
109
138
|
)
|
|
110
139
|
for e in errors:
|
|
111
140
|
print(" - " + e, file=sys.stderr)
|
|
112
141
|
print(
|
|
113
|
-
"Fix and retry:
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"their content in place, add ONE Verification-log row for this run, and put "
|
|
117
|
-
"the detail in docs/<APP>-Checklist.md Remarks. Do NOT pad, do NOT narrate.",
|
|
142
|
+
"Fix and retry: run bash .tfcore/utils/tf-status-facts.sh <App> and write only "
|
|
143
|
+
"the eight template sections (.tfcore/templates/v4custom/app-project-status-tmpl.md), "
|
|
144
|
+
"each overwritten in place. The detail goes in the checklist Remarks, not here.",
|
|
118
145
|
file=sys.stderr,
|
|
119
146
|
)
|
|
120
147
|
sys.exit(2)
|