KeiSeiKit-1.0/hooks/numeric-claims-guard.sh
KeiSei84 abae256c1d feat(install): opt-in hook packs + stack profiles (public posture)
A fresh install now activates only the safety pack; discipline hooks and
agents are opt-in via an onboarding step (step 6) or `kei configure`.
"People don't need Rust-only" — they pick their own stack.

- _primitives/hook-packs.toml: SSoT mapping pack -> hooks, stack -> packs +
  agent groups. safety always on; evidence/observability/epistemic/
  orchestration/git-guard/stack-rust opt-in. rust-first/no-python only under
  the systems stack; git-guard (no-github-push) opt-in only, pulled by no stack.
- lib-profile: extract generic _toml_array (reused by lib-packs); profile_members
  becomes a thin wrapper (no behavior change).
- lib-packs: pack/stack/agent resolvers + selection loader.
- lib-hooks: filter_snippet_by_packs (install-time allowlist) + prune_kit_hooks
  (reconfigure removes deselected kit hooks, keeps foreign ones); activate_hooks
  rewired to prune + filter + merge. No custom settings.json fields (/doctor safe).
- lib-agents: install_manifests filters by stack agent set (empty = install all).
- onboarding: pick_stack step (reuse _onb_read_choice), persists stack_profile +
  enabled_packs to onboarding.toml; i18n STR_* added.
- bin/kei configure -> scripts/kei-configure.sh (re-pick without reinstall);
  install stamps ~/.claude/.kei-kit-dir.
- numeric-claims-guard: money regex no longer matches shell positionals ($1..$9);
  requires decimal / unit / 2+ digits / tilde. Real money + time still caught.
- gate one-liner added to 8 discipline hooks (runtime toggle via hooks-control).

Verified end-to-end (scratch HOME): fresh=safety only; evidence pack adds
numeric+citation; systems stack wires rust-first + 14 base/systems agents (no
data-science/swift); reconfigure-shrink prunes kit hooks but keeps a foreign
hook; settings schema clean; assembler golden 3/3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:27:14 +08:00

76 lines
3 KiB
Bash
Executable file

#!/usr/bin/env bash
# Runtime gate (hooks-control skill / KEI_DISABLED_HOOKS / KEI_HOOK_PROFILE).
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"; if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "numeric-claims-guard" || exit 0; fi
# RULE 0.18 — Numeric claim enforcement — block Edit/Write of numeric claims
# without evidence marker. Bypass: RULE_017_BYPASS=1 prefix (kept for compat).
#
# Reads tool-call JSON on stdin (Claude Code hook protocol).
set -euo pipefail
# Bypass check
if [[ "${RULE_017_BYPASS:-0}" = "1" ]]; then
exit 0
fi
# Read the tool input from stdin
INPUT="$(cat)"
# Extract the new content (Edit: new_string, Write: content)
NEW_CONTENT="$(printf '%s' "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)"
if [[ -z "$NEW_CONTENT" ]]; then
exit 0
fi
# Patterns that indicate a numeric claim
# - "~N min/hour/day/week"
# - "N MB/GB/LOC/tests/crates/atomars"
# - "~$N", "$N/mo"
# - "Nm Ns", "займёт N", "should take N"
NUMERIC_PATTERN='(~\s*[0-9]+(\.[0-9]+)?\s*(min|minute|hour|hr|day|week|month|sec|second|MB|GB|KB|LOC|line|test|crate|atomar|%|µs|ms|ns|TPS|req/s)|[0-9]+m\s*[0-9]+s|\$[0-9]+\.[0-9]+|\$[0-9]+/(mo|hr|day|run)|\$[0-9]{2,}|~\s*\$[0-9]+|should take|will take|takes about|займёт|за ~|estimated at|ETA[: ]|approximately\s+[0-9])'
# Markers that satisfy the rule
EVIDENCE_PATTERN='\[(REAL|FROM-JOURNAL|ESTIMATE-HTC)[: ]'
# Check if numeric pattern present
if ! echo "$NEW_CONTENT" | grep -iqE "$NUMERIC_PATTERN"; then
exit 0
fi
# Numeric pattern present — check for evidence marker
if echo "$NEW_CONTENT" | grep -qE "$EVIDENCE_PATTERN"; then
exit 0
fi
# Violation
MATCHED="$(echo "$NEW_CONTENT" | grep -iEo "$NUMERIC_PATTERN" | head -3 | tr '\n' '; ')"
cat >&2 <<EOF
═══════════════════════════════════════════════════════════════════
RULE 0.18 — Numeric claim without evidence marker.
═══════════════════════════════════════════════════════════════════
Found in Edit/Write content:
$MATCHED
Required: append ONE of these markers in the same paragraph:
[REAL: <file:line | commit | timestamp>]
[FROM-JOURNAL: ~/.claude/memory/time-metrics/<file>.jsonl#<id>]
[ESTIMATE-HTC: <one-sentence reason this can't be measured yet>]
Or write the actual measurement to a JSONL journal first:
echo '{"kind":"task","name":"...","duration_s":...}' \\
>> ~/.claude/memory/time-metrics/tasks.jsonl
Then cite that line.
Bypass (visible, per-call):
RULE_017_BYPASS=1 <command>
See: ~/.claude/rules/numeric-claims-evidence.md
═══════════════════════════════════════════════════════════════════
EOF
exit 2