@esoteric-logic/praxis-harness 2.9.1 → 2.10.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.
package/base/CLAUDE.md CHANGED
@@ -56,6 +56,13 @@ Vault path and backend are machine-specific. Read from `~/.claude/praxis.config.
56
56
  If config file is missing: tell the user to run `praxis/install.sh`.
57
57
  All `{vault_path}` references in rules and skills resolve from this config.
58
58
 
59
+ ## Model & Context Policy
60
+ - Default model: `claude-opus-4-6` (set `ANTHROPIC_MODEL` in shell profile)
61
+ - Sub-agents spawned by Praxis skills: use `haiku` for polling, search, and lint tasks
62
+ - Compact trigger: when context approaches ceiling, finish the current milestone first
63
+ - Never compact mid-plan — complete the milestone, write phase summary to vault, then compact
64
+ - After compaction: re-bootstrap from § After Compaction below, re-run quality checks fresh
65
+
59
66
  ## Durable Memory
60
67
  Context is volatile. Files are permanent. Act accordingly.
61
68
 
@@ -164,6 +171,7 @@ Kit manifests live in `~/.claude/kits/<name>/KIT.md`.
164
171
  | `~/.claude/rules/powershell.md` | `**/*.ps1`, `**/*.psm1` |
165
172
  | `~/.claude/rules/dependency-freshness.md` | `package.json`, `go.mod`, `requirements.txt`, `Cargo.toml`, `pyproject.toml` |
166
173
  | `~/.claude/rules/live-docs-required.md` | Dependency manifests, files importing external packages |
174
+ | `~/.claude/rules/desktop-protocol.md` | Claude Desktop ↔ Claude Code handoff sessions |
167
175
 
168
176
  ### Auto-invocable skills (replace former universal rules)
169
177
  | Skill | Triggers when |
@@ -101,7 +101,8 @@
101
101
  {"path": "base/hooks/file-guard.sh", "event": "PreToolUse", "matcher": "Write|Edit|MultiEdit"},
102
102
  {"path": "base/hooks/identity-check.sh", "event": "PreToolUse", "matcher": "Bash"},
103
103
  {"path": "base/hooks/credential-guard.sh", "event": "PreToolUse", "matcher": "Bash"},
104
- {"path": "base/hooks/session-data-collect.sh", "event": "Stop", "matcher": ""}
104
+ {"path": "base/hooks/session-data-collect.sh", "event": "Stop", "matcher": ""},
105
+ {"path": "base/hooks/on-stop-failure.sh", "event": "StopFailure", "matcher": ""}
105
106
  ],
106
107
  "optional": [
107
108
  {"path": "base/hooks/recursion-guard.sh", "event": "PreToolUse", "matcher": "", "feature": "recursion-detection"},
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env bash
2
+ # on-stop-failure.sh — StopFailure hook (Auto mode failure classifier)
3
+ # Reads Claude's stop context, classifies the failure, and either:
4
+ # - Auto-repairs (transient: test fail, lint fail, tool error) → exit 0 (continue)
5
+ # - Escalates to human (boundary/spec/security violation) → exit 1 (halt)
6
+ # Tracks repair attempts per failure fingerprint to prevent infinite loops.
7
+ # Reuses PPID-scoped state pattern from recursion-guard.sh.
8
+ set -euo pipefail
9
+
10
+ if ! command -v jq &>/dev/null; then
11
+ echo "on-stop-failure: jq required but not found. Cannot classify failure." >&2
12
+ exit 1
13
+ fi
14
+
15
+ # ── Parse input (single jq call) ──
16
+ INPUT=$(cat)
17
+ PARSED=$(echo "$INPUT" | jq -r '[
18
+ (.exit_code // 1 | tostring),
19
+ (.stop_reason // ""),
20
+ (.last_tool_use.name // ""),
21
+ (.last_tool_use.error // "")
22
+ ] | join("\t")' 2>/dev/null || echo "1\t\t\t")
23
+
24
+ IFS=$'\t' read -r EXIT_CODE STOP_REASON LAST_TOOL TOOL_ERROR <<< "$PARSED"
25
+
26
+ MAX_REPAIR_ATTEMPTS=3
27
+ MAX_FINGERPRINT_LEN=200
28
+
29
+ # ── Repair attempt tracker (PPID-scoped, same pattern as recursion-guard.sh) ──
30
+ REPAIR_STATE="/tmp/praxis-repair-${PPID}.json"
31
+ if [[ ! -f "$REPAIR_STATE" ]]; then
32
+ echo '{"repair_attempts":0,"last_fingerprint":""}' > "$REPAIR_STATE"
33
+ fi
34
+
35
+ # Compare raw fingerprint strings — no hash needed for equality checks
36
+ FINGERPRINT="${EXIT_CODE}:${LAST_TOOL}:${STOP_REASON:0:$MAX_FINGERPRINT_LEN}"
37
+
38
+ # Single jq call to read both fields
39
+ STATE_READ=$(jq -r '[(.last_fingerprint // ""), (.repair_attempts // 0 | tostring)] | join("\t")' "$REPAIR_STATE" 2>/dev/null || echo $'\t0')
40
+ IFS=$'\t' read -r LAST_FINGERPRINT CURRENT_ATTEMPTS <<< "$STATE_READ"
41
+
42
+ # Same fingerprint = looping on the same error
43
+ if [[ "$FINGERPRINT" == "$LAST_FINGERPRINT" ]]; then
44
+ CURRENT_ATTEMPTS=$((CURRENT_ATTEMPTS + 1))
45
+ else
46
+ CURRENT_ATTEMPTS=1
47
+ fi
48
+
49
+ # Atomic state file update (tmp + mv)
50
+ TMP_STATE="${REPAIR_STATE}.tmp"
51
+ jq -n --argjson attempts "$CURRENT_ATTEMPTS" --arg fp "$FINGERPRINT" \
52
+ '{"repair_attempts": $attempts, "last_fingerprint": $fp}' \
53
+ > "$TMP_STATE" && mv "$TMP_STATE" "$REPAIR_STATE"
54
+
55
+ # ── Hard escalation: too many repair attempts on same failure ──
56
+ if [[ $CURRENT_ATTEMPTS -gt $MAX_REPAIR_ATTEMPTS ]]; then
57
+ echo "AUTO-REPAIR EXHAUSTED: $CURRENT_ATTEMPTS attempts on same failure." >&2
58
+ echo "Failure: exit=$EXIT_CODE tool=$LAST_TOOL" >&2
59
+ echo "Halting — human review required." >&2
60
+
61
+ CONFIG="$HOME/.claude/praxis.config.json"
62
+ if [[ -f "$CONFIG" ]]; then
63
+ VAULT_PATH=$(jq -r '.vault_path // ""' "$CONFIG" 2>/dev/null)
64
+ if [[ -n "$VAULT_PATH" && -d "$VAULT_PATH" ]]; then
65
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
66
+ ESCALATION_FILE="$VAULT_PATH/notes/escalations.md"
67
+
68
+ if [[ ! -f "$ESCALATION_FILE" ]]; then
69
+ printf -- "---\ntags: [escalation, auto-repair]\ndate: %s\nsource: agent\n---\n\n# Auto-Repair Escalations\n" \
70
+ "$(date +%Y-%m-%d)" > "$ESCALATION_FILE"
71
+ fi
72
+
73
+ printf "\n## %s\n- **Failure**: exit=%s tool=%s\n- **Attempts**: %s\n- **Reason**: %s\n- **Status**: HALTED — needs human review\n" \
74
+ "$TIMESTAMP" "$EXIT_CODE" "$LAST_TOOL" "$CURRENT_ATTEMPTS" "${STOP_REASON:0:$MAX_FINGERPRINT_LEN}" \
75
+ >> "$ESCALATION_FILE"
76
+ fi
77
+ fi
78
+
79
+ exit 1
80
+ fi
81
+
82
+ # ── Hard escalation patterns (single regex, never auto-repair these) ──
83
+ HARD_REGEX="BLOCKED:|secret detected|Protected path|out of scope|permission denied|identity mismatch"
84
+ COMBINED_CONTEXT="$STOP_REASON $TOOL_ERROR"
85
+
86
+ if echo "$COMBINED_CONTEXT" | grep -qiE "$HARD_REGEX"; then
87
+ MATCHED=$(echo "$COMBINED_CONTEXT" | grep -oiE "$HARD_REGEX" | head -1)
88
+ echo "ESCALATING: Hard violation — '$MATCHED' matched." >&2
89
+ echo "Auto-repair suppressed. Human review required." >&2
90
+ exit 1
91
+ fi
92
+
93
+ # Exit code 2 = guard hard-block (recursion-guard.sh, secret-scan.sh, file-guard.sh convention)
94
+ if [[ "$EXIT_CODE" == "2" ]]; then
95
+ echo "ESCALATING: Exit code 2 = guard hard-block. No auto-repair." >&2
96
+ exit 1
97
+ fi
98
+
99
+ # ── Transient failure → emit repair prompt (Auto mode continues) ──
100
+ # Claude Code reads stdout from StopFailure hooks as an injected prompt.
101
+
102
+ cat <<REPAIR
103
+ Auto-repair attempt $CURRENT_ATTEMPTS of $MAX_REPAIR_ATTEMPTS.
104
+
105
+ Failure context:
106
+ - Exit code: $EXIT_CODE
107
+ - Last tool: $LAST_TOOL
108
+ - Stop reason: ${STOP_REASON:0:500}
109
+ - Tool error: ${TOOL_ERROR:0:$MAX_FINGERPRINT_LEN}
110
+
111
+ Instructions:
112
+ 1. Read the error above carefully. Identify the root cause from actual output.
113
+ 2. Apply the minimum fix required. Do not expand scope.
114
+ 3. Re-run validation (tests + lint) after fixing.
115
+ 4. If this is attempt $CURRENT_ATTEMPTS of $MAX_REPAIR_ATTEMPTS and the fix is not obvious:
116
+ report What / So What / Now What and halt.
117
+
118
+ Do NOT re-attempt the same approach that just failed.
119
+ REPAIR
120
+
121
+ exit 0
@@ -68,6 +68,17 @@
68
68
  ]
69
69
  }
70
70
  ],
71
+ "StopFailure": [
72
+ {
73
+ "matcher": "",
74
+ "hooks": [
75
+ {
76
+ "type": "command",
77
+ "command": "bash ~/.claude/hooks/on-stop-failure.sh"
78
+ }
79
+ ]
80
+ }
81
+ ],
71
82
  "PreCompact": [
72
83
  {
73
84
  "matcher": "",
@@ -0,0 +1,64 @@
1
+ # Desktop Protocol — Rules
2
+ # Scope: Sessions involving Claude Desktop ↔ Claude Code handoff
3
+ # Defines role boundaries and structured handoff format
4
+
5
+ ## Role Boundaries
6
+
7
+ | Surface | Role | Responsible for |
8
+ |---------|------|-----------------|
9
+ | Claude Desktop | Architect / Reviewer | ADR review, security audit, diff validation, prompt engineering |
10
+ | Claude Code | Executor | File writes, git ops, test runs, tool use, vault updates |
11
+
12
+ Desktop generates structured intent. Code executes it.
13
+ Never use Desktop for file writes. Never use Code for open-ended architecture debate.
14
+
15
+ ## Handoff Format — Desktop → Code
16
+
17
+ When Desktop completes a review or decision, it emits a structured handoff block.
18
+ Code reads this block as its initial task context.
19
+
20
+ ```
21
+ HANDOFF:
22
+ TASK: [one-line description]
23
+ SPEC: [link to vault plan or spec file]
24
+ CONSTRAINTS: [hard limits — what must NOT change]
25
+ ACCEPTANCE: [how to know it's done]
26
+ CONTEXT: [optional — key decisions or rationale from review]
27
+ ```
28
+
29
+ Code MUST NOT start implementation without at least TASK and ACCEPTANCE filled.
30
+ If SPEC is missing, Code asks before proceeding.
31
+
32
+ ## Handoff Format — Code → Desktop
33
+
34
+ When Code completes implementation and wants architectural review:
35
+
36
+ ```
37
+ REVIEW-REQUEST:
38
+ TASK: [what was implemented]
39
+ DIFF: [git diff summary or branch name]
40
+ SPEC: [link to plan that drove this work]
41
+ QUESTIONS: [specific things to review — not "does this look good"]
42
+ ```
43
+
44
+ ## Vault Write-Back
45
+
46
+ After Desktop review, append the decision to `{vault_path}/notes/review-decisions.md`:
47
+
48
+ ```
49
+ ## YYYY-MM-DD | [task-slug]
50
+ - **Decision**: APPROVED | CHANGES_REQUESTED | DEFERRED
51
+ - **Rationale**: [1-2 sentences]
52
+ - **Action items**: [if CHANGES_REQUESTED — specific items for Code to address]
53
+ ```
54
+
55
+ ## Conventions — WARN on violation
56
+
57
+ - Desktop review decisions that affect architecture go to `{vault_path}/specs/` as ADRs
58
+ - Desktop review decisions that are tactical (naming, style, small refactors) stay in `review-decisions.md`
59
+ - Code must not self-approve architectural changes — route through Desktop review
60
+ - If Desktop is unavailable, Code may proceed but must flag the decision in `status.md` for later review
61
+
62
+ ## Removal Condition
63
+ Remove when a unified Claude surface handles both architecture review and code execution
64
+ natively, eliminating the need for explicit handoff protocols.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esoteric-logic/praxis-harness",
3
- "version": "2.9.1",
3
+ "version": "2.10.0",
4
4
  "description": "Layered Claude Code harness — workflow discipline, AI-Kits, persistent vault integration",
5
5
  "bin": {
6
6
  "praxis-harness": "./bin/praxis.js"