Template Enforcement — enforce-template.sh
Purpose: Canonical reference for the PreToolUse hook that BLOCKS raw Write on four specific markdown classes, redirecting the agent to the right bootstrap flow. This is the "templates exist and must be used" policy — it does NOT touch ad-hoc docs (playbooks, engineering rules, runbook notes, session checkpoints).
Read when: a Write call on a markdown file is blocked · adding a new structured document class · deciding whether to add template enforcement for a new doc type.
Nav: Section Index | Docs Index
Hook source: src/core/hooks/enforce-template.sh
Design principle
Hard enforcement ONLY for SSOT documents — artifacts that feed the cognitive layer (tasks.md registry, ADR index, PRD hierarchy, outcome_history narratives). Everything else (engineering notes, runbooks, ad-hoc docs) is trusted to the agent's judgment. Over-enforcement on markdown creates friction without engineering value.
Four enforced classes
| Class | Path pattern | Required bootstrap | Template source |
|---|---|---|---|
| Task | docs/tasks/TASK-*.md |
cos task-create --title "..." --swimlane <lane> --kind <kind> |
task-detail.md |
| ADR | docs/architecture/adr/ADR-*.md |
copy from docs/governance/_templates/adr-template.md |
project-local template |
| PRD | docs/prd/NN-*.md |
cos setup --mode interactive (4-Q wizard) or --mode import-prd |
PRD classifier |
| Breakthrough | docs/insights/*.md |
cos_learn_narrative MCP tool |
written by tool |
Other markdown paths (playbooks, engineering rules, runbooks, questions.md, session checkpoints, top-level BLOG, README) are not enforced.
Soft edges
- Fires only on
Write(creation).Editof an existing file is always allowed — fixing a bootstrapped task file is free. - Fires only when the target file does not already exist.
- Skips internal paths:
.coding-os/**,.claude/**,.codex/**,tests/**,node_modules/**,.git/**,golden/**. 00-index.mdinside any class is allowed (it's the scaffold index, hand-writable).- If an ADR template file doesn't exist yet in the project, hook prints a soft reminder listing the required H2 sections but allows the write.
Escape hatch — one-shot override
For scaffold regenerators, migration scripts, or legitimate edge cases:
touch .coding-os/.template-override
# next Write on any enforced class is allowed; the marker is consumed.
The hook rm -fs the marker after the first Write, so subsequent writes re-enforce. This is the escape valve for programmatic writes (e.g., capture_golden.py, template bootstrappers).
Why these four, not "all markdown"
| Class | Why enforce |
|---|---|
| Task | cos task-create writes the docs/tasks/TASK-*.md detail file and the board DB row atomically. Hand-writing a task file drifts the board cache. |
| ADR | ADRs feed the doc RAG index; consistent H2 structure (## Status, ## Context, ## Decision, ## Consequences, ## Alternatives) makes them queryable via cos_doc_search |
| PRD | PRDs are multi-part by design (numbered NN-feature-name.md); the classifier routes sections across files. A single-file PRD is usually wrong |
| Breakthrough | Breakthrough files live alongside outcome_history DB rows. The MCP tool writes both atomically so they never desync |
Extending — adding a new enforced class
Example: runbooks under docs/runbooks/*.md should follow a template.
- Create the template:
docs/governance/_templates/runbook-template.mdwith required H2 sections. - Add a branch to
enforce-template.shfollowing the ADR pattern:if [[ "$FILE_PATH" == *docs/runbooks/*.md ]]; then TEMPLATE="docs/governance/_templates/runbook-template.md" if [[ -f "$TEMPLATE" ]]; then echo "BLOCKED: use the runbook template." >&2 echo " Template: $TEMPLATE" >&2 exit 2 fi exit 0 # allow if template absent fi - Test: write a new runbook without the template and confirm the block message appears in stderr.
Do NOT add a catch-all *.md branch. Every enforced class must justify its friction with a SSOT constraint; "consistency" alone is not enough.
Interaction with other hooks
enforce-templateruns BEFOREenforce-skillandenforce-doc-anchoron the same PreToolUse event chain. If a hand-written task file is rejected here, the agent never reaches the skill or anchor checks.block-protected-filesindependently blocks edits toCLAUDE.md,AGENTS.md, etc. Those are orthogonal — template enforcement doesn't cover the governance docs themselves.
Testing
uv run pytest tests/ -k template— adapter-level tests that install/uninstall the hook.- Manual:
touch /tmp/fake-task.mdthen attemptecho "# TASK-999" > docs/tasks/TASK-999-x.mdvia an agent Write — should be blocked.
Reference commits / rationale
The four classes were chosen because each has a DB mirror (tasks table, document_chunks, outcome_history) or a multi-file semantic (PRD). Enforcement makes the file structure a hard input contract for downstream indexing rather than a soft convention.