Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"command": "bash scripts/check_no_atlas_rehash.sh",
"timeout": 5000
},
{
"type": "command",
"command": "bash scripts/check_no_baseline_update.sh",
"timeout": 5000
},
{
"type": "command",
"command": "bash scripts/check_bash_no_write.sh",
Expand Down Expand Up @@ -69,6 +74,11 @@
"command": "bash scripts/check_no_edit_baseline.sh",
"timeout": 5000
},
{
"type": "command",
"command": "bash scripts/check_no_em_dashes_hook.sh",
"timeout": 5000
},
{
"type": "command",
"command": "bash scripts/check_pre_pr_review_triage_gate.sh",
Expand Down
61 changes: 58 additions & 3 deletions .opencode/plugins/synthorg-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
* Committed Claude Code hooks (from .claude/settings.json):
* PreToolUse (Bash): scripts/check_push_rebased.sh
* PreToolUse (Bash): scripts/check_no_atlas_rehash.sh
* PreToolUse (Bash): scripts/check_no_baseline_update.sh
* PreToolUse (Bash): scripts/check_bash_no_write.sh
* PreToolUse (Bash): scripts/check_git_c_cwd.sh
* PreToolUse (Bash | Edit): scripts/check_no_bulk_edit.py
* PreToolUse (Edit|Write): scripts/check_no_edit_migration.sh
* PreToolUse (Edit|Write): scripts/check_no_edit_baseline.sh
* PreToolUse (Edit|Write): scripts/check_no_em_dashes_hook.sh
* PreToolUse (Edit|Write): scripts/check_pre_pr_review_triage_gate.sh
* PostToolUse (Edit|Write): scripts/check_web_design_system.py
* PostToolUse (Edit|Write): scripts/check_backend_regional_defaults.py
Expand Down Expand Up @@ -193,16 +195,53 @@ export const SynthOrgHooks: Plugin = async ({ client, $, app }) => {
}
}

const payload = { tool_input: { file_path: filePath } } as Record<string, unknown>;
const filePathInput = { file_path: filePath } as Record<string, unknown>;

// Order must match `.claude/settings.json` PreToolUse Edit|Write:
// migration, baseline, em-dash (richer payload), triage-gate.
// The em-dash hook runs between baseline and triage-gate to enforce
// content-correctness before the workflow-lock fires.
for (const script of [
"scripts/check_no_edit_migration.sh",
"scripts/check_no_edit_baseline.sh",
"scripts/check_pre_pr_review_triage_gate.sh",
]) {
const outcome = runHookScript(
script,
payload.tool_input as Record<string, unknown>,
filePathInput,
5000,
);
const denyReason = denyReasonFromOutcome(outcome);
if (denyReason) {
throw new Error(denyReason);
}
}

// check_no_em_dashes_hook.sh: inspects the candidate content
// before it lands on disk (mirrors scripts/check_no_em_dashes.py).
const args = (output.args ?? {}) as Record<string, unknown>;
const emDashInput = { ...filePathInput } as Record<string, unknown>;
if (typeof args.content === "string") {
emDashInput.content = args.content;
}
if (typeof args.new_string === "string") {
emDashInput.new_string = args.new_string;
}
{
const outcome = runHookScript(
"scripts/check_no_em_dashes_hook.sh",
emDashInput,
5000,
);
const denyReason = denyReasonFromOutcome(outcome);
if (denyReason) {
throw new Error(denyReason);
}
}

{
const outcome = runHookScript(
"scripts/check_pre_pr_review_triage_gate.sh",
filePathInput,
5000,
);
const denyReason = denyReasonFromOutcome(outcome);
Expand Down Expand Up @@ -300,6 +339,22 @@ export const SynthOrgHooks: Plugin = async ({ client, $, app }) => {
}
}

// check_no_baseline_update.sh: block --update-baseline /
// --refresh-baseline invocations on gate scripts. Like the atlas
// hook above we invoke unconditionally because aliases /
// subprocess wrappers could hide the literal flag tokens.
{
const outcome = runHookScript(
"scripts/check_no_baseline_update.sh",
{ command },
5000,
);
const denyReason = denyReasonFromOutcome(outcome);
if (denyReason) {
throw new Error(denyReason);
}
}

// check_bash_no_write.sh: block file writes via Bash
const bashWriteOutcome = runHookScript(
"scripts/check_bash_no_write.sh",
Expand Down
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ repos:
language: system
files: ^tests/.*\.py$

- id: baseline-growth
name: block growth of gate suppression baselines
entry: uv
args: [run, python, scripts/check_baseline_growth.py]
language: system
files: ^scripts/(([a-z_][a-z_]*_baseline\.(txt|json))|(_[a-z][a-z_]*_baseline\.py))$

- id: no-release-please-token
name: forbid new RELEASE_PLEASE_TOKEN references
entry: uv
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Web: see `web/CLAUDE.md`. CLI: see `cli/CLAUDE.md` (use `go -C cli`, never `cd c
- **Configuration Precedence (MANDATORY)**: DB > env > YAML > code default via `SettingsService`/`ConfigResolver`; no `os.environ.get` outside startup. See [docs/reference/configuration-precedence.md](docs/reference/configuration-precedence.md).
- **No Hardcoded Values (MANDATORY)**: numerics live in `settings/definitions/`; allowlist 0/1/-1, HTTP codes, hex masks, powers-of-2. Enforced by `scripts/check_no_magic_numbers.py`.
- **Doc Numeric Claims (MANDATORY)**: numerics in README + public docs sourced from `data/runtime_stats.yaml` via `<!--RS:NAME-->` markers. See `data/README.md`.
- **Test Regression (MANDATORY)**: timeout/slow failures = source-code regression; never edit `tests/baselines/unit_timing.json`.
- **Test Regression (MANDATORY)**: timeout/slow failures = source-code regression; never edit `tests/baselines/unit_timing.json` or any `scripts/*_baseline.{txt,json}` / `scripts/_*_baseline.py`. Both families are PreToolUse-blocked. Per-invocation bypass for gate baselines: `ALLOW_BASELINE_GROWTH=1 git commit ...` (requires explicit user approval).
- **Post-Implementation + Pre-PR Review (MANDATORY)**: after issue: branch + commit + push (no auto-PR); use `/pre-pr-review` (gh pr create is hookify-blocked). After PR: `/aurelio-review-pr` for external feedback. Fix EVERYTHING valid; no deferring.

## Quick Commands
Expand Down
4 changes: 2 additions & 2 deletions data/runtime_stats.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ stats:
raw: 7
display: "7"
convention_gates:
raw: 37
display: "37"
raw: 38
display: "38"

sources:
tests: "uv run python -m pytest --collect-only -q"
Expand Down
16 changes: 15 additions & 1 deletion docs/reference/convention-gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The gate's job is to catch the SECOND occurrence of the category; the audit's jo
All under `scripts/`. The list is generated by `ls scripts/check_*.py`; if an entry below disappears or a new check script lands, update this page in the same PR (and the meta-gate enforces this for the canonical doc set).

- `check_backend_regional_defaults.py`
- `check_baseline_growth.py`
- `check_boundary_typed.py`
- `check_currency_aggregation_invariant.py`
- `check_dead_api_endpoints.py`
Expand Down Expand Up @@ -47,7 +48,20 @@ All under `scripts/`. The list is generated by `ls scripts/check_*.py`; if an en
- `check_workflow_shell_git_commits.py`
- `check_workflow_tag_lifecycle.py`

(<!--RS:convention_gates-->37<!--/RS--> total `check_*.py` scripts: enforcement gates plus the meta-gate below.)
(<!--RS:convention_gates-->38<!--/RS--> total `check_*.py` scripts: enforcement gates plus the meta-gate below.)

## PreToolUse hooks (Claude Code + OpenCode)

Some conventions are also enforced *before* the file lands on disk so the offending content never reaches the diff. Bash scripts under `scripts/` registered in `.claude/settings.json` and `.opencode/plugins/synthorg-hooks.ts`:

- `check_no_edit_baseline.sh`: blocks `Edit` / `Write` on `tests/baselines/*.json`, `scripts/*_baseline.{txt,json}`, and `scripts/_*_baseline.py`.
- `check_no_baseline_update.sh`: blocks `Bash` invocations of `scripts/check_*.py --update-baseline` / `--update` / `--refresh-baseline`.
- `check_no_em_dashes_hook.sh`: blocks `Edit` / `Write` whose candidate content contains a U+2014 em-dash or one of its HTML entities. Mirrors the diff-time `check_no_em_dashes.py` pre-commit gate.
- `check_no_edit_migration.sh`: blocks `Edit` / `Write` on `src/synthorg/persistence/{sqlite,postgres}/revisions/*.sql` (use `atlas migrate diff` instead).
- `check_no_atlas_rehash.sh`: blocks `Bash` invocations of `atlas migrate hash` (rehashing breaks installed databases).
- `check_pre_pr_review_triage_gate.sh`: blocks `Edit` / `Write` outside `_audit/` while a `/pre-pr-review` triage table is pending user approval.

The hook layer is fail-closed: the OpenCode plugin treats hook execution errors as denials, so a misbehaving hook script blocks the action rather than letting it through.

## Registration procedure

Expand Down
Loading
Loading