uni-harness 0.1.0

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.
@@ -0,0 +1,64 @@
1
+ ---
2
+ # ── Custom agent template (this file itself is inactive) ────────
3
+ # Claude Code treats a file without a `name` in its frontmatter as
4
+ # documentation and ignores it. To create an agent: copy this file →
5
+ # uncomment and fill the fields below → replace the body with that
6
+ # agent's system prompt.
7
+ #
8
+ # name: lowercase-hyphen identifier (becomes the call name) [required]
9
+ # description: WHEN to delegate to this agent — the main agent
10
+ # reads this sentence to decide auto-delegation [required]
11
+ # tools: allowed tools (omit to inherit all)
12
+ # disallowedTools: explicit denials (take precedence over tools)
13
+ # model: sonnet | opus | haiku (omit to inherit)
14
+ #
15
+ # name: code-reviewer
16
+ # description: Reviews code changes in a fresh context, without seeing the implementation process. Use after finishing a feature or fix, before commit/PR. Judges plan compliance and defects, and reports.
17
+ # tools: Read, Grep, Glob, Bash
18
+ #
19
+ # Note: creating the first active agent in agents/ requires one session
20
+ # restart. The harness hooks and permissions apply to agent tool calls too.
21
+ # ────────────────────────────────────────────────────────────────
22
+ ---
23
+
24
+ The body below is the system prompt of the example (fresh-context code
25
+ reviewer). Replace it entirely when creating a new agent.
26
+
27
+ ---
28
+
29
+ You are a code reviewer. You see only the result, not the implementer's
30
+ reasoning — that is your value. Find what the implementer missed, without
31
+ inheriting their assumptions.
32
+
33
+ ## Procedure
34
+
35
+ 1. Read the full change via `git diff` (or the scope you were given).
36
+ 2. If `plan.md` exists, read it and check the change against the plan and
37
+ requirements.
38
+ 3. Read enough surrounding code to judge each change in context.
39
+ 4. If `.harness/commands.env` has a TEST_CMD, run it and check the result.
40
+
41
+ ## Judging Criteria (in priority order)
42
+
43
+ 1. **Compliance**: implemented differently from the plan/requirements, or
44
+ requirements missing
45
+ 2. **Defects**: code that produces wrong results or crashes on real input —
46
+ report only what you can pair with a concrete failure scenario
47
+ 3. **Verification gaps**: changed behavior that no test covers
48
+ 4. **Simplification**: duplication replaceable by existing code (only when
49
+ confident)
50
+
51
+ ## Rules
52
+
53
+ - Report only. Never modify code.
54
+ - Anchor every finding to `file:line` with evidence. If it's speculation,
55
+ say so.
56
+ - If there is nothing to report, say so. Never invent findings to fill space.
57
+ - No style preferences — that's the linter's job.
58
+
59
+ ## Output Format
60
+
61
+ - **Verdict**: approvable / needs changes (one-line rationale)
62
+ - **Findings**: ordered by severity, each with location, problem, evidence,
63
+ failure scenario
64
+ - **Scope checked**: files read, verification commands run and their results
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ # ════════════════════════════════════════════════════════════════
3
+ # PreToolUse(Bash) guard — blocks destructive/irreversible commands
4
+ # before they run.
5
+ # exit 0 + JSON(deny) = blocked / exit 0 (no output) = normal flow
6
+ # ════════════════════════════════════════════════════════════════
7
+ set -euo pipefail
8
+
9
+ INPUT=$(cat)
10
+
11
+ python3 - "$INPUT" <<'PYEOF'
12
+ import json, re, sys
13
+
14
+ data = json.loads(sys.argv[1])
15
+ cmd = (data.get("tool_input") or {}).get("command", "") or ""
16
+
17
+ # DENY: irreversible/destructive patterns (extend per project as needed)
18
+ DENY_PATTERNS = [
19
+ (r"\brm\s+(-[a-zA-Z]*[rf][a-zA-Z]*\s+)+(/|\$HOME|~)(\s|$)", "recursive delete of root/home directory"),
20
+ (r"\bgit\s+push\s+.*--force(?!-with-lease)", "--force push (use --force-with-lease if needed)"),
21
+ (r"\bgit\s+reset\s+--hard\s+origin/", "hard reset to remote (loses all local changes)"),
22
+ (r"\bgit\s+(commit|push)\b[^|;&]*--no-verify\b", "bypassing verification hooks with --no-verify"),
23
+ (r"\bDROP\s+(TABLE|DATABASE)\b", "destructive database command"),
24
+ (r"\bchmod\s+(-[a-zA-Z]+\s+)*777\b", "opening full permissions (chmod 777)"),
25
+ (r"curl[^|;&]*\|\s*(sudo\s+)?(ba)?sh", "piping a remote script into a shell"),
26
+ (r"wget[^|;&]*\|\s*(sudo\s+)?(ba)?sh", "piping a remote script into a shell"),
27
+ (r">\s*/dev/sd[a-z]", "writing directly to a block device"),
28
+ (r"\bmkfs\b", "formatting a filesystem"),
29
+ ]
30
+
31
+ for pattern, reason in DENY_PATTERNS:
32
+ if re.search(pattern, cmd, re.IGNORECASE):
33
+ print(json.dumps({
34
+ "hookSpecificOutput": {
35
+ "hookEventName": "PreToolUse",
36
+ "permissionDecision": "deny",
37
+ "permissionDecisionReason":
38
+ f"[harness guard] Blocked: {reason}. "
39
+ "If this action is truly needed, ask the user to run it directly."
40
+ }
41
+ }))
42
+ sys.exit(0)
43
+
44
+ # No match -> exit silently (normal permission flow)
45
+ sys.exit(0)
46
+ PYEOF
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # ════════════════════════════════════════════════════════════════
3
+ # Observability log — records every tool call as JSONL + tripwires
4
+ # Log: .harness/logs/tool_calls.jsonl
5
+ # Tripwires: (1) warn when per-session tool calls exceed a ceiling
6
+ # (2) same failure 3x in a row -> tell Claude to stop and pivot
7
+ # ════════════════════════════════════════════════════════════════
8
+ set -uo pipefail
9
+
10
+ INPUT=$(cat)
11
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
12
+ LOG_DIR="$PROJECT_DIR/.harness/logs"
13
+ mkdir -p "$LOG_DIR"
14
+
15
+ python3 - "$INPUT" "$LOG_DIR" <<'PYEOF'
16
+ import json, sys, datetime, collections
17
+
18
+ data = json.loads(sys.argv[1])
19
+ log_dir = sys.argv[2]
20
+ log_path = f"{log_dir}/tool_calls.jsonl"
21
+
22
+ event = data.get("hook_event_name", "")
23
+ entry = {
24
+ "ts": datetime.datetime.now().isoformat(timespec="seconds"),
25
+ "session_id": data.get("session_id", ""),
26
+ "event": event,
27
+ "tool": data.get("tool_name", ""),
28
+ }
29
+ # If the call came from a subagent, record which one (for /ratchet diagnosis)
30
+ for k in ("agent_id", "agent_type"):
31
+ if data.get(k):
32
+ entry[k] = data[k]
33
+ ti = data.get("tool_input") or {}
34
+ # Privacy/size: log only a summary of the input
35
+ if "command" in ti:
36
+ entry["summary"] = str(ti["command"])[:200]
37
+ elif "file_path" in ti:
38
+ entry["summary"] = str(ti["file_path"])[:200]
39
+
40
+ if event == "PostToolUseFailure":
41
+ resp = data.get("tool_response") or data.get("error") or {}
42
+ entry["error"] = str(resp)[:300]
43
+
44
+ with open(log_path, "a") as f:
45
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
46
+
47
+ # ── tripwire checks ──────────────────────────────────────────
48
+ session = data.get("session_id", "")
49
+ lines = []
50
+ try:
51
+ with open(log_path) as f:
52
+ for line in f:
53
+ try:
54
+ e = json.loads(line)
55
+ if e.get("session_id") == session:
56
+ lines.append(e)
57
+ except Exception:
58
+ pass
59
+ except Exception:
60
+ lines = []
61
+
62
+ warnings = []
63
+
64
+ # (1) per-session tool-call ceiling (default 300, warn every 100)
65
+ n = len(lines)
66
+ if n > 0 and n % 100 == 0 and n >= 300:
67
+ warnings.append(f"This session has exceeded {n} tool calls. "
68
+ "Re-examine the plan — this may be a runaway loop.")
69
+
70
+ # (2) same failure 3x in a row
71
+ fails = [e for e in lines if e.get("event") == "PostToolUseFailure"][-3:]
72
+ if len(fails) == 3:
73
+ sigs = {(e.get("tool", ""), (e.get("error") or "")[:80]) for e in fails}
74
+ if len(sigs) == 1:
75
+ warnings.append("[tripwire] The same tool failed with the same error "
76
+ "3 times in a row. Stop repeating this approach; write "
77
+ "an escalation packet per the Work Loop section of "
78
+ "CLAUDE.md and report to the user.")
79
+
80
+ if warnings:
81
+ print(json.dumps({
82
+ "hookSpecificOutput": {
83
+ "hookEventName": event,
84
+ "additionalContext": "[harness tripwire] " + " ".join(warnings)
85
+ }
86
+ }, ensure_ascii=False))
87
+ PYEOF
88
+ exit 0
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # ════════════════════════════════════════════════════════════════
3
+ # PostToolUse(Edit|Write) verification sensor
4
+ # When a code file is modified: (1) run lint immediately and feed
5
+ # failures back to Claude, (2) record the file in the per-session
6
+ # pending_test marker. Tests run in batch at turn end (stop-gate.sh).
7
+ # Commands come from .harness/commands.env (LINT_CMD/TEST_CMD).
8
+ # ════════════════════════════════════════════════════════════════
9
+ set -uo pipefail
10
+
11
+ INPUT=$(cat)
12
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
13
+ ENV_FILE="$PROJECT_DIR/.harness/commands.env"
14
+ LOG_DIR="$PROJECT_DIR/.harness/logs"
15
+
16
+ # Commands not configured -> pass silently (harness not set up yet)
17
+ [ -f "$ENV_FILE" ] || exit 0
18
+ # shellcheck disable=SC1090
19
+ source "$ENV_FILE"
20
+
21
+ FILE_PATH=$(python3 -c '
22
+ import json,sys
23
+ d=json.loads(sys.argv[1])
24
+ print((d.get("tool_input") or {}).get("file_path",""))' "$INPUT" 2>/dev/null || echo "")
25
+ SESSION_ID=$(python3 -c '
26
+ import json,sys
27
+ print(json.loads(sys.argv[1]).get("session_id",""))' "$INPUT" 2>/dev/null || echo "")
28
+
29
+ # Not a code file -> pass (don't run verification on docs/config edits)
30
+ case "$FILE_PATH" in
31
+ *.py|*.ts|*.tsx|*.js|*.jsx|*.go|*.rs|*.java|*.rb|*.c|*.cpp|*.h|*.cs) ;;
32
+ *) exit 0 ;;
33
+ esac
34
+
35
+ # Record for the turn-end test gate (stop-gate.sh)
36
+ if [ -n "$SESSION_ID" ]; then
37
+ mkdir -p "$LOG_DIR"
38
+ printf '%s\n' "$FILE_PATH" >> "$LOG_DIR/pending_test.$SESSION_ID"
39
+ fi
40
+
41
+ # Lint is fast, so run it immediately (timeout may be absent on macOS)
42
+ TIMEOUT_BIN=$(command -v timeout || command -v gtimeout || true)
43
+ if [ -n "${LINT_CMD:-}" ]; then
44
+ LINT_OUT=$(cd "$PROJECT_DIR" && ${TIMEOUT_BIN:+"$TIMEOUT_BIN" 120} bash -c "$LINT_CMD" 2>&1 | tail -40)
45
+ LINT_RC=$?
46
+ if [ "$LINT_RC" -ne 0 ]; then
47
+ python3 - "[LINT FAILED rc=$LINT_RC]
48
+ $LINT_OUT" <<'PYEOF'
49
+ import json, sys
50
+ ctx = ("[harness sensor] The code you just modified failed lint. "
51
+ "Read the output below and fix it. This work is not done until it passes.\n"
52
+ + sys.argv[1])
53
+ print(json.dumps({
54
+ "hookSpecificOutput": {
55
+ "hookEventName": "PostToolUse",
56
+ "additionalContext": ctx[:8000]
57
+ }
58
+ }))
59
+ PYEOF
60
+ fi
61
+ fi
62
+ exit 0
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+ # ════════════════════════════════════════════════════════════════
3
+ # SessionStart context injection (startup|resume|clear|compact)
4
+ # (1) If an in-progress checkpoint (progress.json) exists, surface
5
+ # it so completed steps aren't repeated. Re-injected after
6
+ # compaction too.
7
+ # (2) If tool failures have piled up in the last 7 days of logs,
8
+ # suggest running /ratchet.
9
+ # (3) If the harness is installed but unconfigured (commands.env has
10
+ # no commands), have Claude offer to run /harness-init.
11
+ # ════════════════════════════════════════════════════════════════
12
+ set -uo pipefail
13
+
14
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
15
+
16
+ python3 - "$PROJECT_DIR" <<'PYEOF'
17
+ import json, sys, os, datetime
18
+
19
+ proj = sys.argv[1]
20
+ parts = []
21
+
22
+ # (3) harness installed but not configured -> offer /harness-init
23
+ # (checked first so a fresh install greets the user with setup)
24
+ try:
25
+ import re
26
+ with open(os.path.join(proj, ".harness/commands.env")) as f:
27
+ env = f.read()
28
+ if not re.search(r'^(LINT_CMD|TEST_CMD)=".+"', env, re.M):
29
+ parts.append(
30
+ "[harness] The harness is installed but not configured: "
31
+ ".harness/commands.env has no LINT_CMD/TEST_CMD, so the "
32
+ "verification sensors are inactive. Offer the user to run "
33
+ "/harness-init — it scans the repo, verifies the build/test/lint "
34
+ "commands by running them, and proposes the config as a diff for "
35
+ "approval. If the sensors are off intentionally, they can ignore this.")
36
+ except Exception:
37
+ pass
38
+
39
+ # (1) in-progress checkpoint
40
+ try:
41
+ with open(os.path.join(proj, "progress.json")) as f:
42
+ ckpt = json.load(f)
43
+ if ckpt.get("status") not in ("completed", "abandoned"):
44
+ parts.append(
45
+ "[harness memory] An in-progress checkpoint exists. Before starting "
46
+ "work, read progress.json / plan.md / decisions.jsonl, do not repeat "
47
+ "completed steps, and resume from the next step.\n"
48
+ f"Checkpoint summary: {json.dumps(ckpt, ensure_ascii=False)[:1500]}")
49
+ except Exception:
50
+ pass
51
+
52
+ # (2) accumulated failures in the last 7 days -> nudge toward /ratchet
53
+ try:
54
+ cutoff = datetime.datetime.now() - datetime.timedelta(days=7)
55
+ n = 0
56
+ with open(os.path.join(proj, ".harness/logs/tool_calls.jsonl")) as f:
57
+ for line in f:
58
+ try:
59
+ e = json.loads(line)
60
+ except Exception:
61
+ continue
62
+ if e.get("event") != "PostToolUseFailure":
63
+ continue
64
+ try:
65
+ ts = datetime.datetime.fromisoformat(e.get("ts", ""))
66
+ except Exception:
67
+ continue
68
+ if ts >= cutoff:
69
+ n += 1
70
+ if n >= 3:
71
+ parts.append(
72
+ f"[harness] The logs record {n} tool failures in the last 7 days. "
73
+ "If you see a repeating pattern, consider running /ratchet to turn "
74
+ "it into a rule/sensor. (Ignore if already handled.)")
75
+ except Exception:
76
+ pass
77
+
78
+ if parts:
79
+ print(json.dumps({
80
+ "hookSpecificOutput": {
81
+ "hookEventName": "SessionStart",
82
+ "additionalContext": "\n".join(parts)
83
+ }
84
+ }, ensure_ascii=False))
85
+ PYEOF
86
+ exit 0
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # ════════════════════════════════════════════════════════════════
3
+ # Stop test gate
4
+ # If code was modified this session (pending_test marker), run
5
+ # TEST_CMD in batch before the turn ends. On failure, block the
6
+ # stop and feed the output back. Blocking is capped at 3 per
7
+ # session — after that, instruct an escalation packet and let the
8
+ # stop through (prevents infinite loops).
9
+ # ════════════════════════════════════════════════════════════════
10
+ set -uo pipefail
11
+
12
+ INPUT=$(cat)
13
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
14
+ ENV_FILE="$PROJECT_DIR/.harness/commands.env"
15
+ LOG_DIR="$PROJECT_DIR/.harness/logs"
16
+
17
+ SESSION_ID=$(python3 -c '
18
+ import json,sys
19
+ print(json.loads(sys.argv[1]).get("session_id",""))' "$INPUT" 2>/dev/null || echo "")
20
+ [ -n "$SESSION_ID" ] || exit 0
21
+
22
+ MARKER="$LOG_DIR/pending_test.$SESSION_ID"
23
+ COUNTER="$LOG_DIR/stop_blocks.$SESSION_ID"
24
+
25
+ # No code modified this session -> pass
26
+ [ -s "$MARKER" ] || exit 0
27
+
28
+ # Not configured -> pass silently (the harness must not break the work itself)
29
+ [ -f "$ENV_FILE" ] || exit 0
30
+ # shellcheck disable=SC1090
31
+ source "$ENV_FILE"
32
+ if [ -z "${TEST_CMD:-}" ]; then
33
+ rm -f "$MARKER"
34
+ exit 0
35
+ fi
36
+
37
+ # timeout may be absent on macOS — use it only if available
38
+ TIMEOUT_BIN=$(command -v timeout || command -v gtimeout || true)
39
+ TEST_OUT=$(cd "$PROJECT_DIR" && ${TIMEOUT_BIN:+"$TIMEOUT_BIN" 600} bash -c "$TEST_CMD" 2>&1 | tail -60)
40
+ TEST_RC=$?
41
+
42
+ if [ "$TEST_RC" -eq 0 ]; then
43
+ rm -f "$MARKER" "$COUNTER"
44
+ exit 0
45
+ fi
46
+
47
+ COUNT=0
48
+ [ -f "$COUNTER" ] && COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0)
49
+ COUNT=$((COUNT + 1))
50
+
51
+ if [ "$COUNT" -ge 3 ]; then
52
+ # Retry ceiling reached: let the next stop through, but demand escalation
53
+ rm -f "$MARKER" "$COUNTER"
54
+ REASON="[harness stop-gate] This is the 3rd attempt to end the turn with \
55
+ failing tests. Stop repeating fix attempts. Write the escalation packet from \
56
+ the Work Loop section of CLAUDE.md, report it to the user, then stop.
57
+ $TEST_OUT"
58
+ else
59
+ echo "$COUNT" > "$COUNTER"
60
+ REASON="[harness stop-gate] Code modified in this session does not pass \
61
+ tests (rc=$TEST_RC, block $COUNT/3). Read the output below, fix it, then stop.
62
+ $TEST_OUT"
63
+ fi
64
+
65
+ python3 - "$REASON" <<'PYEOF'
66
+ import json, sys
67
+ print(json.dumps({"decision": "block", "reason": sys.argv[1][:8000]},
68
+ ensure_ascii=False))
69
+ PYEOF
70
+ exit 0
@@ -0,0 +1,76 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Bash",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-pre-bash.sh",
10
+ "timeout": 15,
11
+ "statusMessage": "harness: command safety check"
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "PostToolUse": [
17
+ {
18
+ "matcher": "Edit|Write",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/sensor-post-edit.sh",
23
+ "timeout": 180,
24
+ "statusMessage": "harness: lint sensor"
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "matcher": "*",
30
+ "hooks": [
31
+ {
32
+ "type": "command",
33
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/observe-log.sh",
34
+ "timeout": 15
35
+ }
36
+ ]
37
+ }
38
+ ],
39
+ "PostToolUseFailure": [
40
+ {
41
+ "matcher": "*",
42
+ "hooks": [
43
+ {
44
+ "type": "command",
45
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/observe-log.sh",
46
+ "timeout": 15
47
+ }
48
+ ]
49
+ }
50
+ ],
51
+ "Stop": [
52
+ {
53
+ "hooks": [
54
+ {
55
+ "type": "command",
56
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/stop-gate.sh",
57
+ "timeout": 720,
58
+ "statusMessage": "harness: pre-stop test gate"
59
+ }
60
+ ]
61
+ }
62
+ ],
63
+ "SessionStart": [
64
+ {
65
+ "matcher": "startup|resume|clear|compact",
66
+ "hooks": [
67
+ {
68
+ "type": "command",
69
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-start.sh",
70
+ "timeout": 10
71
+ }
72
+ ]
73
+ }
74
+ ]
75
+ }
76
+ }
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: checkpoint
3
+ description: Save current work state to plan.md / decisions.jsonl / progress.json checkpoints. Use after completing a meaningful step, before pausing a long task, or when the user invokes /checkpoint.
4
+ ---
5
+
6
+ # Checkpoint — Saving Work State
7
+
8
+ The filesystem is the memory. Maintain these three files at the project root.
9
+ The single test: **if this session died right now, could the next session
10
+ pick up where it left off?**
11
+
12
+ ## Procedure
13
+
14
+ 1. **plan.md** — update if the plan changed. Format:
15
+ ```markdown
16
+ # Plan: [task title]
17
+ - [x] completed step
18
+ - [ ] next step ← NEXT
19
+ - [ ] later step
20
+ ```
21
+
22
+ 2. **decisions.jsonl** — append only *settled decisions* from this step,
23
+ one per line (no exploratory reasoning or transient facts):
24
+ ```json
25
+ {"ts": "ISO8601", "decision": "what was decided", "why": "one-line rationale", "alternatives_rejected": ["rejected option"]}
26
+ ```
27
+
28
+ 3. **progress.json** — rewrite in full:
29
+ ```json
30
+ {
31
+ "task_id": "task identifier",
32
+ "status": "in_progress | completed | abandoned",
33
+ "completed_steps": ["list of completed steps"],
34
+ "next_step": "what to do next (be specific)",
35
+ "artifacts": ["files created/modified"],
36
+ "open_issues": ["unresolved items"],
37
+ "last_updated": "ISO8601"
38
+ }
39
+ ```
40
+
41
+ ## Rules
42
+
43
+ - When the task is fully done, set `status: "completed"`. The SessionStart
44
+ hook does not inject completed checkpoints — forget this closing step and
45
+ the next session will read stale state.
46
+ - Write checkpoints against the recovery test: the next session must be
47
+ able to resume from `next_step` alone.
48
+ - Suggest deleting completed checkpoints older than 3 weeks (stale state
49
+ causes bad decisions).
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: guide-audit
3
+ description: Audit the RULES/ANTI-PATTERNS in CLAUDE.md and classify each rule as keep / delete candidate / convert-to-sensor candidate. Use for the monthly review or when the user invokes /guide-audit. Proposes only — applying always requires user approval.
4
+ ---
5
+
6
+ # Guide Audit — Guide Hygiene
7
+
8
+ Goal: keep CLAUDE.md down to rules that are *still* needed. Rules that only
9
+ accumulate are debt — find the ones a sensor now enforces, the ones that
10
+ lost their grounding, and the ones that contradict each other.
11
+ **Never apply any deletion or edit without user approval.**
12
+
13
+ ## Procedure
14
+
15
+ For each entry in RULES and ANTI-PATTERNS, run five checks:
16
+
17
+ 1. **Verifiability** — can compliance be judged objectively?
18
+ (Vague rules like "write good code" fail this.)
19
+ 2. **Grounding** — did it come from an actually observed failure? Does it
20
+ carry a date and cause?
21
+ 3. **Contradiction** — does it conflict with another rule?
22
+ 4. **Sensor overlap** — is it already enforced automatically? Cross-check:
23
+ - what LINT_CMD/TEST_CMD in `.harness/commands.env` catches
24
+ - the DENY_PATTERNS in `guard-pre-bash.sh`
25
+ - the permissions (allow/ask/deny) in `settings.json`, if present
26
+ 5. **Freshness** — when was it last seen actually working (blocking or
27
+ catching a violation)? Cite `.harness/logs/tool_calls.jsonl` if evidence
28
+ exists.
29
+
30
+ ## Output Format
31
+
32
+ | Rule | Verdict | Rationale |
33
+ |---|---|---|
34
+ | (rule summary) | keep / delete candidate / convert-to-sensor candidate / rewrite candidate | one sentence |
35
+
36
+ - **Delete candidate**: already enforced by a sensor, or its originating
37
+ failure can no longer be traced.
38
+ - **Convert-to-sensor candidate**: repeatedly violated — include the
39
+ concrete promotion (linter rule, guard pattern, or permissions entry).
40
+ - **Rewrite candidate**: intent is valid but unverifiable as written —
41
+ include replacement wording that is observable.
42
+
43
+ ## Approval and Application
44
+
45
+ - Apply only what the user approves. Deletions remove the whole line;
46
+ sensor conversions are applied as a pair — "add sensor + delete rule"
47
+ (never the half-application where the rule is gone but the sensor
48
+ didn't land).
49
+ - If the audit leaves zero rules, an empty RULES section can be the right
50
+ answer. Never invent rules to fill space.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: harness-init
3
+ description: Scan the repository of a new project to fill in the PROJECT section of CLAUDE.md and .harness/commands.env, and propose initial RULES/ANTI-PATTERNS. Run once right after installing the harness, or whenever CLAUDE.md is still in placeholder state. Verifies detected commands by actually running them before applying.
4
+ ---
5
+
6
+ # Harness Init — Initialize the Harness by Scanning the Repo
7
+
8
+ Goal: fill CLAUDE.md and `.harness/commands.env` with commands that
9
+ **actually work**. Never write a guessed command — only ones verified by
10
+ running them.
11
+
12
+ ## Step 1: Scan
13
+
14
+ - Language/version: from manifests (package.json, pyproject.toml, go.mod,
15
+ Cargo.toml, *.csproj, ...). Prefer versions pinned in lockfiles/config.
16
+ - BUILD / TEST / LINT candidates: collect from manifest scripts, Makefile,
17
+ CI config (.github/workflows etc. — read only), and any existing README.
18
+ - One-line project purpose: from the README or top-level code structure.
19
+
20
+ ## Step 2: Verify (the core step)
21
+
22
+ - **Actually run** each collected TEST / LINT / BUILD command (with a
23
+ timeout). Record the results as they are:
24
+ - Passes → adopt the command.
25
+ - Fails but the command itself is valid (tests were already broken, etc.)
26
+ → adopt it, and report the current failing state to the user.
27
+ - Command missing or not runnable → leave that field blank and report.
28
+ Do not invent one.
29
+ - If dependencies need installing, do not run the install — tell the user
30
+ first.
31
+
32
+ ## Step 3: Initial Rule Candidates
33
+
34
+ - Skim past mistakes with `git log --oneline --grep="fix\|revert\|hotfix" -i`;
35
+ if a type repeats, draft ANTI-PATTERNS candidates (date = today, cite the
36
+ commit hashes in parentheses).
37
+ - Propose at most 3 RULES candidates grounded in this codebase. Generic
38
+ truisms ("write good code") are forbidden — only things observable in
39
+ this repository.
40
+
41
+ ## Existing Projects (brownfield)
42
+
43
+ - If CLAUDE.md existed before the harness was installed, never replace it.
44
+ Propose **appending** the harness protocol sections it lacks — PROJECT,
45
+ RULES, ANTI-PATTERNS, Work Loop, Checkpoints, "When a Mistake Is Found" —
46
+ while keeping every existing instruction intact. If an existing
47
+ instruction conflicts with a harness rule, surface the conflict and let
48
+ the user decide; never silently override either side.
49
+ - If the project already has its own hooks, skills, or agents under
50
+ `.claude/`, list them and check for overlap before proposing anything
51
+ (e.g. an existing post-edit test hook would double-run tests alongside
52
+ the harness sensor).
53
+ - If tests are currently failing in the existing project, say so and
54
+ recommend starting with LINT_CMD only (TEST_CMD empty) until the suite
55
+ is green — a stop gate that always fails would block every turn.
56
+
57
+ ## Step 4: Propose → Approve → Apply
58
+
59
+ - Show everything as a diff and get user approval:
60
+ 1. The PROJECT section of CLAUDE.md (PROJECT/LANGUAGE/BUILD/TEST/LINT)
61
+ 2. LINT_CMD / TEST_CMD in `.harness/commands.env` (identical to PROJECT)
62
+ 3. Initial RULES / ANTI-PATTERNS entries (if any)
63
+ - Write only what was approved. Afterwards, run TEST_CMD once more to
64
+ confirm the sensor configuration points at a command that really runs.
65
+
66
+ ## Forbidden
67
+
68
+ - Writing any command into CLAUDE.md or commands.env that was not executed.
69
+ - Installing dependencies or changing the environment without approval.
70
+ - Inventing generic rules to fill space (an empty RULES beats a fabricated one).