@esoteric-logic/praxis-harness 2.5.1 → 2.6.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.
Files changed (39) hide show
  1. package/base/CLAUDE.md +17 -1
  2. package/base/configs/mcp-servers.json +26 -0
  3. package/base/configs/registry.json +113 -0
  4. package/base/hooks/credential-guard.sh +98 -0
  5. package/base/hooks/dep-audit.sh +105 -0
  6. package/base/hooks/quality-check.sh +4 -3
  7. package/base/hooks/recursion-guard.sh +76 -0
  8. package/base/hooks/session-data-collect.sh +3 -0
  9. package/base/hooks/settings-hooks.json +18 -0
  10. package/base/rules/coding.md +2 -2
  11. package/base/rules/dependency-freshness.md +50 -0
  12. package/base/rules/live-docs-required.md +81 -0
  13. package/base/rules/security-posture.md +62 -0
  14. package/base/skills/blind-judge/SKILL.md +88 -0
  15. package/base/skills/context7-lookup/SKILL.md +17 -6
  16. package/base/skills/deliberate/SKILL.md +103 -0
  17. package/base/skills/dep-hygiene/SKILL.md +93 -0
  18. package/base/skills/pre-commit-lint/SKILL.md +2 -3
  19. package/base/skills/research/SKILL.md +106 -0
  20. package/base/skills/scaffold-new/SKILL.md +1 -4
  21. package/base/skills/scaffold-new/references/repo-CLAUDE-md-template.md +1 -1
  22. package/bin/praxis-preflight.sh +317 -0
  23. package/package.json +1 -1
  24. package/scripts/install-tools.sh +3 -23
  25. package/scripts/lint-harness.sh +1 -1
  26. package/scripts/test-harness.sh +1 -26
  27. package/base/configs/vale/.vale.ini +0 -17
  28. package/base/configs/vale/Praxis/AISlop.yml +0 -90
  29. package/base/configs/vale/Praxis/CopulaAvoidance.yml +0 -22
  30. package/base/configs/vale/Praxis/ElegantVariation.yml +0 -14
  31. package/base/configs/vale/Praxis/HeadingCase.yml +0 -26
  32. package/base/configs/vale/Praxis/Hedging.yml +0 -22
  33. package/base/configs/vale/Praxis/NaturalVoice.yml +0 -85
  34. package/base/configs/vale/Praxis/Precision.yml +0 -60
  35. package/base/configs/vale/Praxis/SentenceLength.yml +0 -6
  36. package/base/configs/vale/Praxis/Terminology.yml +0 -25
  37. package/base/configs/vale/Praxis/vocabularies/accept.txt +0 -32
  38. package/base/configs/vale/Praxis/vocabularies/reject.txt +0 -3
  39. package/base/skills/prose-review/SKILL.md +0 -165
package/base/CLAUDE.md CHANGED
@@ -134,7 +134,7 @@ Kit manifests live in `~/.claude/kits/<name>/KIT.md`.
134
134
 
135
135
  ## Rules Registry — Load on Demand Only
136
136
 
137
- ### Universal — always active (7 rules)
137
+ ### Universal — always active (8 rules)
138
138
  | File | Purpose |
139
139
  |------|---------|
140
140
  | `~/.claude/rules/profile.md` | Who the user is, identities, working style |
@@ -144,6 +144,7 @@ Kit manifests live in `~/.claude/kits/<name>/KIT.md`.
144
144
  | `~/.claude/rules/vault.md` | Second brain integration — vault backend, file purposes |
145
145
  | `~/.claude/rules/context-management.md` | Context anti-rot, phase scoping, context reset protocol |
146
146
  | `~/.claude/rules/memory-boundary.md` | Auto-memory boundary, MEMORY.md cap, dream integration |
147
+ | `~/.claude/rules/security-posture.md` | Sandbox model, credential protection, protected paths |
147
148
 
148
149
  ### Scoped — load only when paths match
149
150
  | File | Loads when |
@@ -152,9 +153,24 @@ Kit manifests live in `~/.claude/kits/<name>/KIT.md`.
152
153
  | `~/.claude/rules/terraform.md` | `**/*.tf`, `**/*.tfvars` |
153
154
  | `~/.claude/rules/github-actions.md` | `.github/workflows/**` |
154
155
  | `~/.claude/rules/powershell.md` | `**/*.ps1`, `**/*.psm1` |
156
+ | `~/.claude/rules/dependency-freshness.md` | `package.json`, `go.mod`, `requirements.txt`, `Cargo.toml`, `pyproject.toml` |
157
+ | `~/.claude/rules/live-docs-required.md` | Dependency manifests, files importing external packages |
155
158
 
156
159
  ### Auto-invocable skills (replace former universal rules)
157
160
  | Skill | Triggers when |
158
161
  |-------|--------------|
159
162
  | `communication-standards` | Writing client-facing docs, proposals, status reports, commits, PRs |
160
163
  | `architecture-patterns` | Writing ADRs, specs, system design, risk docs, blocker reports |
164
+
165
+ ## Judgment & Research Commands
166
+
167
+ | Command | Purpose |
168
+ |---------|---------|
169
+ | `/duel` | Parallel Alpha/Beta implementation → blind scoring → synthesis |
170
+ | `/deliberate` | Multi-perspective decision analysis with scored option matrix |
171
+ | `/freshness` | Full dependency audit — CVEs, outdated packages, maintenance status |
172
+ | `/research <pkg>` | Live docs (Context7) + CVE/version/maintenance check (Perplexity Sonar) |
173
+
174
+ MCP server templates: `base/configs/mcp-servers.json` — declarative config for context7, github, perplexity-sonar.
175
+ Dependency registry: `base/configs/registry.json` — single source of truth for all tools, auth, hooks.
176
+ Preflight gate: `bin/praxis-preflight.sh` (alias: `praxis doctor`) — verifies auth, tools, MCP, keys.
@@ -0,0 +1,26 @@
1
+ {
2
+ "mcpServers": {
3
+ "context7": {
4
+ "command": "npx",
5
+ "args": ["-y", "@upstash/context7-mcp@latest"],
6
+ "description": "Real-time library documentation — always use before generating code that touches any npm/PyPI/crate dependency"
7
+ },
8
+ "github": {
9
+ "command": "npx",
10
+ "args": ["-y", "@modelcontextprotocol/server-github"],
11
+ "env": {
12
+ "GITHUB_TOKEN": "${GITHUB_TOKEN}"
13
+ },
14
+ "description": "GitHub MCP — issues, PRs, repos. Token auto-set from gh auth."
15
+ },
16
+ "perplexity-sonar": {
17
+ "command": "npx",
18
+ "args": ["-y", "mcp-perplexity-server"],
19
+ "env": {
20
+ "PERPLEXITY_API_KEY": "${PERPLEXITY_API_KEY}",
21
+ "PERPLEXITY_MODEL": "sonar-pro"
22
+ },
23
+ "description": "Web search for CVEs, changelogs, security advisories. Default sonar-pro. Override per-call: sonar (fast), sonar-reasoning (/deliberate only)."
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,113 @@
1
+ {
2
+ "version": "2.0",
3
+ "auth": {
4
+ "claude": {
5
+ "check": "claude auth status",
6
+ "fix": "claude auth login",
7
+ "blocking": true,
8
+ "description": "Claude Code OAuth — browser login, no API key needed"
9
+ },
10
+ "github": {
11
+ "check": "gh auth status",
12
+ "fix": "gh auth login",
13
+ "fallback_env": "GITHUB_TOKEN",
14
+ "auto_populate": "gh auth token",
15
+ "blocking": false,
16
+ "feature": "github-mcp"
17
+ }
18
+ },
19
+ "binaries": {
20
+ "required": [
21
+ {"name": "claude", "minVersion": "1.0.0", "install": "npm install -g @anthropic-ai/claude-code"},
22
+ {"name": "node", "minVersion": "18.0.0", "install": "brew install node"},
23
+ {"name": "npm", "minVersion": "9.0.0", "install": "npm install -g npm@latest"},
24
+ {"name": "git", "minVersion": "2.30.0", "install": "brew install git"},
25
+ {"name": "jq", "minVersion": "1.6", "install": "brew install jq"},
26
+ {"name": "gh", "minVersion": "2.0.0", "install": "brew install gh"}
27
+ ],
28
+ "optional": [
29
+ {"name": "trufflehog", "feature": "secret-scanning", "install": "brew install trufflehog"},
30
+ {"name": "gitleaks", "feature": "secret-scanning", "install": "brew install gitleaks"},
31
+ {"name": "osv-scanner", "feature": "dep-audit", "install": "go install github.com/google/osv-scanner/cmd/osv-scanner@latest"},
32
+ {"name": "pip-audit", "feature": "dep-audit-python", "install": "pip install pip-audit"},
33
+ {"name": "docker", "feature": "docker-sandbox", "install": "brew install --cask docker"}
34
+ ]
35
+ },
36
+ "env_vars": {
37
+ "optional": [
38
+ {
39
+ "name": "PERPLEXITY_API_KEY",
40
+ "description": "Perplexity Sonar — live CVE lookups, dep research",
41
+ "validation": "starts_with:pplx-",
42
+ "secure_store": true,
43
+ "masked_preview": 8,
44
+ "feature": "live-research",
45
+ "get_key_url": "https://www.perplexity.ai/settings/api"
46
+ },
47
+ {
48
+ "name": "GITHUB_TOKEN",
49
+ "description": "Auto-populated from gh auth token — for GitHub MCP",
50
+ "validation": "starts_with:gh",
51
+ "secure_store": true,
52
+ "masked_preview": 8,
53
+ "feature": "github-mcp",
54
+ "auto_populate": "gh auth token"
55
+ },
56
+ {
57
+ "name": "OBSIDIAN_VAULT",
58
+ "description": "Absolute path to Obsidian vault for long-term memory",
59
+ "validation": "is_directory",
60
+ "secure_store": false,
61
+ "feature": "obsidian-memory"
62
+ }
63
+ ]
64
+ },
65
+ "git_config": {
66
+ "required": [
67
+ {"key": "user.name", "description": "git commit author name"},
68
+ {"key": "user.email", "description": "git commit author email"}
69
+ ]
70
+ },
71
+ "mcp_servers": [
72
+ {
73
+ "name": "context7",
74
+ "package": "@upstash/context7-mcp@latest",
75
+ "requires_env": [],
76
+ "zero_config": true,
77
+ "feature": "live-docs"
78
+ },
79
+ {
80
+ "name": "github",
81
+ "package": "@modelcontextprotocol/server-github",
82
+ "requires_env": ["GITHUB_TOKEN"],
83
+ "feature": "github-mcp"
84
+ },
85
+ {
86
+ "name": "perplexity-sonar",
87
+ "package": "mcp-perplexity-server",
88
+ "requires_env": ["PERPLEXITY_API_KEY"],
89
+ "env_config": {"PERPLEXITY_MODEL": "sonar-pro"},
90
+ "feature": "live-research"
91
+ }
92
+ ],
93
+ "hooks": {
94
+ "required": [
95
+ {"path": "base/hooks/secret-scan.sh", "event": "PreToolUse", "matcher": "Write|Edit|MultiEdit"},
96
+ {"path": "base/hooks/file-guard.sh", "event": "PreToolUse", "matcher": "Write|Edit|MultiEdit"},
97
+ {"path": "base/hooks/identity-check.sh", "event": "PreToolUse", "matcher": "Bash"},
98
+ {"path": "base/hooks/credential-guard.sh", "event": "PreToolUse", "matcher": "Bash"},
99
+ {"path": "base/hooks/quality-check.sh", "event": "PostToolUse", "matcher": "Write|Edit|MultiEdit"},
100
+ {"path": "base/hooks/session-data-collect.sh", "event": "Stop", "matcher": ""}
101
+ ],
102
+ "optional": [
103
+ {"path": "base/hooks/recursion-guard.sh", "event": "PreToolUse", "matcher": "", "feature": "recursion-detection"},
104
+ {"path": "base/hooks/dep-audit.sh", "event": "PostToolUse", "matcher": "Write|Edit|MultiEdit", "feature": "dep-audit"}
105
+ ]
106
+ },
107
+ "claude_settings": {
108
+ "required": [
109
+ {"path": "sandbox.failIfUnavailable", "expected": true},
110
+ {"path": "sandbox.allowUnsandboxedCommands", "expected": false}
111
+ ]
112
+ }
113
+ }
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bash
2
+ # credential-guard.sh — PreToolUse:Bash hook
3
+ # Blocks Bash commands that access credential files or sensitive directories.
4
+ # Exit 0 = allow, Exit 2 = block with message.
5
+ set -euo pipefail
6
+
7
+ INPUT=$(cat)
8
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
9
+
10
+ if [[ -z "$COMMAND" ]]; then
11
+ exit 0
12
+ fi
13
+
14
+ # ── Allowlist: verification commands that reference credential paths safely ──
15
+ SAFE_PATTERNS=(
16
+ "ssh-keygen -l"
17
+ "ssh-keygen -lf"
18
+ "gh auth status"
19
+ "gh auth token"
20
+ "gh auth switch"
21
+ "aws sts get-caller-identity"
22
+ "az account show"
23
+ "gcloud auth list"
24
+ "kubectl config current-context"
25
+ "docker login --help"
26
+ "gpg --list-keys"
27
+ "security find-identity"
28
+ )
29
+
30
+ for safe in "${SAFE_PATTERNS[@]}"; do
31
+ if echo "$COMMAND" | grep -qF "$safe"; then
32
+ exit 0
33
+ fi
34
+ done
35
+
36
+ # ── Blocked paths: credential stores and private key locations ──
37
+ BLOCKED_PATHS=(
38
+ "$HOME/.ssh"
39
+ "$HOME/.aws"
40
+ "$HOME/.azure"
41
+ "$HOME/.kube"
42
+ "$HOME/.gnupg"
43
+ "$HOME/.docker/config.json"
44
+ "$HOME/Library/Keychains"
45
+ "$HOME/.config/gcloud"
46
+ "$HOME/.praxis/secrets"
47
+ )
48
+
49
+ # Expand ~ in commands for matching
50
+ EXPANDED_CMD=$(echo "$COMMAND" | sed "s|~|$HOME|g")
51
+
52
+ for blocked in "${BLOCKED_PATHS[@]}"; do
53
+ if echo "$EXPANDED_CMD" | grep -qF "$blocked"; then
54
+ echo "BLOCKED: Command references protected credential path: $blocked" >&2
55
+ echo "Praxis credential-guard prevents access to sensitive directories." >&2
56
+ echo "If this is a legitimate operation, use the allowlisted verification commands." >&2
57
+
58
+ # Log to audit trail
59
+ AUDIT_FILE="$HOME/.claude/praxis-audit.jsonl"
60
+ if command -v jq &>/dev/null; then
61
+ jq -nc \
62
+ --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
63
+ --arg tool "Bash" \
64
+ --arg event "credential-guard-block" \
65
+ --arg path "$blocked" \
66
+ --arg cmd "${COMMAND:0:200}" \
67
+ --arg session "$PPID" \
68
+ '{ts: $ts, tool: $tool, event: $event, blocked_path: $path, command_prefix: $cmd, session_id: $session}' \
69
+ >> "$AUDIT_FILE" 2>/dev/null || true
70
+ fi
71
+
72
+ exit 2
73
+ fi
74
+ done
75
+
76
+ # ── Blocked file patterns: private keys and env files ──
77
+ BLOCKED_FILE_PATTERNS=(
78
+ "_rsa[[:space:]]"
79
+ "_rsa$"
80
+ "_ed25519[[:space:]]"
81
+ "_ed25519$"
82
+ "\.pem[[:space:]]"
83
+ "\.pem$"
84
+ "\.p12[[:space:]]"
85
+ "\.p12$"
86
+ "\.pfx[[:space:]]"
87
+ "\.pfx$"
88
+ )
89
+
90
+ for pattern in "${BLOCKED_FILE_PATTERNS[@]}"; do
91
+ if echo "$EXPANDED_CMD" | grep -qE "$pattern"; then
92
+ echo "BLOCKED: Command references private key or certificate file pattern." >&2
93
+ echo "Praxis credential-guard prevents access to private key files." >&2
94
+ exit 2
95
+ fi
96
+ done
97
+
98
+ exit 0
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # dep-audit.sh — PostToolUse:Write|Edit|MultiEdit hook
3
+ # Runs dependency vulnerability checks when manifest files are modified.
4
+ # Always exits 0 (advisory only — PostToolUse cannot hard-block).
5
+ set -euo pipefail
6
+
7
+ INPUT=$(cat)
8
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null)
9
+
10
+ if [[ -z "$FILE_PATH" ]]; then
11
+ exit 0
12
+ fi
13
+
14
+ BASENAME=$(basename "$FILE_PATH")
15
+ DIR=$(dirname "$FILE_PATH")
16
+ AUDIT_RESULT=""
17
+ ECOSYSTEM=""
18
+
19
+ case "$BASENAME" in
20
+ package.json)
21
+ ECOSYSTEM="npm"
22
+ if command -v npm &>/dev/null && [[ -f "$DIR/package-lock.json" || -f "$DIR/node_modules/.package-lock.json" ]]; then
23
+ AUDIT_RESULT=$(cd "$DIR" && npm audit --audit-level=high --json 2>/dev/null || true)
24
+ fi
25
+ ;;
26
+ go.mod)
27
+ ECOSYSTEM="go"
28
+ if command -v govulncheck &>/dev/null; then
29
+ AUDIT_RESULT=$(cd "$DIR" && govulncheck -json ./... 2>/dev/null || true)
30
+ elif command -v go &>/dev/null; then
31
+ # Fallback: at least check for known issues via go list
32
+ AUDIT_RESULT=$(cd "$DIR" && go list -m -json all 2>/dev/null | head -c 5000 || true)
33
+ fi
34
+ ;;
35
+ requirements.txt|pyproject.toml)
36
+ ECOSYSTEM="python"
37
+ if command -v pip-audit &>/dev/null; then
38
+ AUDIT_RESULT=$(cd "$DIR" && pip-audit --format=json 2>/dev/null || true)
39
+ fi
40
+ ;;
41
+ Cargo.toml)
42
+ ECOSYSTEM="rust"
43
+ if command -v cargo-audit &>/dev/null; then
44
+ AUDIT_RESULT=$(cd "$DIR" && cargo audit --json 2>/dev/null || true)
45
+ fi
46
+ ;;
47
+ *)
48
+ # Not a dependency manifest — skip silently
49
+ exit 0
50
+ ;;
51
+ esac
52
+
53
+ if [[ -z "$ECOSYSTEM" ]]; then
54
+ exit 0
55
+ fi
56
+
57
+ # ── Parse results and warn on critical findings ──
58
+ CRITICAL_COUNT=0
59
+ HIGH_COUNT=0
60
+
61
+ if [[ -n "$AUDIT_RESULT" ]]; then
62
+ case "$ECOSYSTEM" in
63
+ npm)
64
+ CRITICAL_COUNT=$(echo "$AUDIT_RESULT" | jq -r '.metadata.vulnerabilities.critical // 0' 2>/dev/null || echo "0")
65
+ HIGH_COUNT=$(echo "$AUDIT_RESULT" | jq -r '.metadata.vulnerabilities.high // 0' 2>/dev/null || echo "0")
66
+ ;;
67
+ python)
68
+ VULN_COUNT=$(echo "$AUDIT_RESULT" | jq -r 'length // 0' 2>/dev/null || echo "0")
69
+ if [[ "$VULN_COUNT" -gt 0 ]]; then
70
+ CRITICAL_COUNT="$VULN_COUNT"
71
+ fi
72
+ ;;
73
+ rust|go)
74
+ # For cargo-audit and govulncheck, count any findings as high
75
+ if echo "$AUDIT_RESULT" | jq -e '.vulnerabilities // .findings // empty' &>/dev/null 2>&1; then
76
+ HIGH_COUNT=1
77
+ fi
78
+ ;;
79
+ esac
80
+ fi
81
+
82
+ # ── Report findings ──
83
+ if [[ "$CRITICAL_COUNT" -gt 0 ]]; then
84
+ echo "DEP-AUDIT ($ECOSYSTEM): $CRITICAL_COUNT CRITICAL vulnerabilities found in $BASENAME" >&2
85
+ echo "Run '/freshness' for a full audit report." >&2
86
+ fi
87
+
88
+ if [[ "$HIGH_COUNT" -gt 0 ]]; then
89
+ echo "DEP-AUDIT ($ECOSYSTEM): $HIGH_COUNT HIGH vulnerabilities found in $BASENAME" >&2
90
+ fi
91
+
92
+ # ── Write audit result to ~/.praxis/ ──
93
+ PRAXIS_DIR="$HOME/.praxis"
94
+ if [[ -d "$PRAXIS_DIR" ]] && command -v jq &>/dev/null; then
95
+ jq -nc \
96
+ --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
97
+ --arg ecosystem "$ECOSYSTEM" \
98
+ --arg file "$FILE_PATH" \
99
+ --argjson critical "${CRITICAL_COUNT:-0}" \
100
+ --argjson high "${HIGH_COUNT:-0}" \
101
+ '{timestamp: $ts, ecosystem: $ecosystem, file: $file, critical: $critical, high: $high}' \
102
+ > "$PRAXIS_DIR/dep-audit-latest.json" 2>/dev/null || true
103
+ fi
104
+
105
+ exit 0
@@ -103,10 +103,11 @@ case "$EXT" in
103
103
  fi
104
104
  ;;
105
105
  md)
106
- if command -v vale &>/dev/null; then
107
- LINT_OUT=$(vale --output=line "$FILE_PATH" 2>&1) || true
106
+ # Markdown linting handled by markdownlint if available
107
+ if command -v markdownlint &>/dev/null; then
108
+ LINT_OUT=$(markdownlint "$FILE_PATH" 2>&1) || true
108
109
  if [ -n "$LINT_OUT" ]; then
109
- ISSUES+=("vale: $LINT_OUT")
110
+ ISSUES+=("markdownlint: $LINT_OUT")
110
111
  fi
111
112
  fi
112
113
  ;;
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # recursion-guard.sh — PreToolUse hook (all matchers)
3
+ # Detects repetitive tool invocations and blocks infinite loops.
4
+ # Tracks per-session state in /tmp. Lightweight — sub-millisecond.
5
+ # Exit 0 = allow (with optional warning), Exit 2 = block.
6
+ set -euo pipefail
7
+
8
+ INPUT=$(cat)
9
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"' 2>/dev/null)
10
+
11
+ # ── Configuration ──
12
+ WARN_SAME_CMD=5
13
+ BLOCK_SAME_CMD=8
14
+ WARN_SAME_FILE=15
15
+ BLOCK_SAME_FILE=25
16
+
17
+ # ── State file scoped to session ──
18
+ STATE_FILE="/tmp/praxis-recursion-${PPID}.json"
19
+
20
+ # Initialize state if missing
21
+ if [[ ! -f "$STATE_FILE" ]]; then
22
+ echo '{"commands":{},"files":{}}' > "$STATE_FILE"
23
+ fi
24
+
25
+ # ── Extract key based on tool type ──
26
+ case "$TOOL_NAME" in
27
+ Bash)
28
+ KEY=$(echo "$INPUT" | jq -r '.tool_input.command // "unknown"' 2>/dev/null)
29
+ CATEGORY="commands"
30
+ WARN_THRESHOLD=$WARN_SAME_CMD
31
+ BLOCK_THRESHOLD=$BLOCK_SAME_CMD
32
+ ;;
33
+ Write|Edit|MultiEdit)
34
+ KEY=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // "unknown"' 2>/dev/null)
35
+ CATEGORY="files"
36
+ WARN_THRESHOLD=$WARN_SAME_FILE
37
+ BLOCK_THRESHOLD=$BLOCK_SAME_FILE
38
+ ;;
39
+ *)
40
+ # Other tools: track but with higher thresholds
41
+ KEY=$(echo "$INPUT" | jq -r '.tool_input | tostring' 2>/dev/null | head -c 200)
42
+ CATEGORY="commands"
43
+ WARN_THRESHOLD=$WARN_SAME_CMD
44
+ BLOCK_THRESHOLD=$BLOCK_SAME_CMD
45
+ ;;
46
+ esac
47
+
48
+ # Truncate key to prevent state file bloat
49
+ KEY="${KEY:0:300}"
50
+
51
+ # ── Increment counter ──
52
+ # Use a hash of the key for safe JSON field names
53
+ KEY_HASH=$(echo -n "$KEY" | md5 2>/dev/null || echo -n "$KEY" | md5sum 2>/dev/null | cut -d' ' -f1 || echo "fallback")
54
+
55
+ COUNT=$(jq -r --arg cat "$CATEGORY" --arg key "$KEY_HASH" \
56
+ '.[$cat][$key] // 0' "$STATE_FILE" 2>/dev/null || echo "0")
57
+ COUNT=$((COUNT + 1))
58
+
59
+ jq --arg cat "$CATEGORY" --arg key "$KEY_HASH" --argjson count "$COUNT" \
60
+ '.[$cat][$key] = $count' "$STATE_FILE" > "${STATE_FILE}.tmp" 2>/dev/null \
61
+ && mv "${STATE_FILE}.tmp" "$STATE_FILE" 2>/dev/null || true
62
+
63
+ # ── Check thresholds ──
64
+ if [[ $COUNT -ge $BLOCK_THRESHOLD ]]; then
65
+ echo "BLOCKED: Repetition detected — $TOOL_NAME invoked $COUNT times with same input." >&2
66
+ echo "This looks like an infinite loop. Stop and reassess your approach." >&2
67
+ echo "Threshold: $BLOCK_THRESHOLD for $CATEGORY." >&2
68
+ exit 2
69
+ fi
70
+
71
+ if [[ $COUNT -ge $WARN_THRESHOLD ]]; then
72
+ echo "WARNING: $TOOL_NAME invoked $COUNT times with same input (block at $BLOCK_THRESHOLD)." >&2
73
+ echo "Consider whether you are in a loop." >&2
74
+ fi
75
+
76
+ exit 0
@@ -102,4 +102,7 @@ if command -v jq &>/dev/null && [[ -f "$PROGRESS_FILE" ]]; then
102
102
  fi
103
103
  fi
104
104
 
105
+ # Clean up recursion guard state files from this session
106
+ rm -f /tmp/praxis-recursion-*.json 2>/dev/null || true
107
+
105
108
  exit 0
@@ -27,6 +27,15 @@
27
27
  "command": "bash ~/.claude/hooks/identity-check.sh"
28
28
  }
29
29
  ]
30
+ },
31
+ {
32
+ "matcher": "Bash",
33
+ "hooks": [
34
+ {
35
+ "type": "command",
36
+ "command": "bash ~/.claude/hooks/credential-guard.sh"
37
+ }
38
+ ]
30
39
  }
31
40
  ],
32
41
  "PostToolUse": [
@@ -38,6 +47,15 @@
38
47
  "command": "bash ~/.claude/hooks/quality-check.sh"
39
48
  }
40
49
  ]
50
+ },
51
+ {
52
+ "matcher": "Write|Edit|MultiEdit",
53
+ "hooks": [
54
+ {
55
+ "type": "command",
56
+ "command": "bash ~/.claude/hooks/dep-audit.sh"
57
+ }
58
+ ]
41
59
  }
42
60
  ],
43
61
  "Stop": [
@@ -117,7 +117,7 @@ Before adding any new package:
117
117
 
118
118
  Style, formatting, import ordering, complexity thresholds, and security patterns
119
119
  are enforced by automated tooling (golangci-lint, shellcheck, hadolint, tflint,
120
- semgrep, vale). Rules files cover ONLY:
120
+ semgrep). Rules files cover ONLY:
121
121
  - Architecture decisions tooling cannot express
122
122
  - Permission boundaries and file-group scoping
123
123
  - Error learning from real session failures
@@ -130,7 +130,7 @@ Do NOT duplicate in CLAUDE.md what hooks already enforce.
130
130
  ## Verification Commands
131
131
 
132
132
  Single-file (matches PostToolUse hooks):
133
- - `go vet <file>` / `shellcheck <file>` / `hadolint Dockerfile` / `vale <file>.md`
133
+ - `go vet <file>` / `shellcheck <file>` / `hadolint Dockerfile` / `markdownlint <file>.md`
134
134
 
135
135
  Project-level (matches pre-commit + /verify):
136
136
  - `golangci-lint run`
@@ -0,0 +1,50 @@
1
+ # Dependency Freshness — Rules
2
+ # Scope: Projects with dependency manifests (package.json, go.mod, requirements.txt, Cargo.toml, pyproject.toml)
3
+ # Enforces live version verification and freshness SLAs
4
+
5
+ ## Invariants — BLOCK on violation
6
+
7
+ ### Never hardcode versions from memory
8
+ All package versions in dependency manifests must be verified live against the registry
9
+ at the time of generation. Never use a version number from training data.
10
+
11
+ Verification commands by ecosystem:
12
+ - npm: `npm view <package> version`
13
+ - PyPI: `pip index versions <package>`
14
+ - Go: `go list -m -versions <module>`
15
+ - Cargo: `cargo search <crate>`
16
+
17
+ State in output: "Verified: <package>@<version> as of [date]"
18
+
19
+ ### Freshness SLAs
20
+ When a vulnerability or outdated dependency is identified:
21
+ - **CRITICAL CVE**: Fix same day. No exceptions.
22
+ - **HIGH CVE**: Fix within 1 week. Log in vault `tasks.md` if deferred.
23
+ - **Major version drift > 2**: Flag for review. Do not silently leave 3+ major versions behind.
24
+ - **Unmaintained (no release in 12+ months)**: Flag as risk. Recommend alternative if available.
25
+
26
+ Enforcement: `dep-audit.sh` hook (PostToolUse:Write) runs ecosystem auditors when manifests change.
27
+ On-demand: `/freshness` skill runs a full audit with structured report.
28
+
29
+ ## Conventions — WARN on violation
30
+
31
+ ### Production vs Development pinning
32
+ - Development deps: `^major.minor.0` ranges acceptable
33
+ - Production and security-sensitive deps: pin exact verified version
34
+ - Always commit lockfiles (`package-lock.json`, `uv.lock`, `Cargo.lock`, `go.sum`)
35
+
36
+ ### Pre-recommend checklist
37
+ Before suggesting any new dependency:
38
+ - [ ] Verified current version via registry (not training data)
39
+ - [ ] < 5 open CVEs in last 12 months
40
+ - [ ] Active maintenance (commit in last 90 days)
41
+ - [ ] > 1000 weekly downloads OR explicitly justified
42
+ - [ ] Not in archived/unmaintained state
43
+
44
+ See `base/rules/live-docs-required.md` for the full Context7 + Perplexity verification protocol.
45
+
46
+ ---
47
+
48
+ ## Removal Condition
49
+ Permanent. Dependency freshness is a supply chain security concern
50
+ that applies to any project with external dependencies.
@@ -0,0 +1,81 @@
1
+ # Live Documentation Requirement — Rules
2
+ # Scope: All code touching external libraries, frameworks, or APIs
3
+ # Enforces Context7-first + Perplexity Sonar fallback for documentation freshness
4
+
5
+ ## Invariants — BLOCK on violation
6
+
7
+ ### Context7 is ALWAYS required before generating dependency code
8
+ Before writing any code that uses an external library, framework, or API:
9
+ 1. Call `resolve-library-id` with the package name to get the Context7 ID
10
+ 2. Call `get-library-docs` with that ID and the specific topic
11
+ 3. Use ONLY the returned documentation — not training-data memory
12
+ 4. Cite the doc version in a comment: `// Docs: <library>@<version> via Context7`
13
+
14
+ **This is non-negotiable.** Training data has a cutoff. Official docs do not.
15
+
16
+ ### Perplexity Sonar fallback sequence
17
+ If `resolve-library-id` returns no results for a library:
18
+ 1. Query Sonar: `"<library> official documentation site:docs.<domain> OR site:github.com"`
19
+ 2. Use the returned URL as the documentation source
20
+ 3. State in output: "Context7 had no index for <library> — sourced from [URL] via Sonar"
21
+
22
+ Never silently fall back to training data. Always disclose the documentation source.
23
+
24
+ ### Mandatory version verification before install suggestions
25
+ Before suggesting `npm install <package>`, `pip install <package>`, or any dependency addition:
26
+ 1. Context7: resolve the library, confirm current major version
27
+ 2. Sonar (`sonar` model): search `"<package> latest version site:npmjs.com"` to confirm with date
28
+ 3. State in output: "Verified: <package>@<version> as of [date]"
29
+ 4. Never suggest a version from memory alone
30
+
31
+ ## Conventions — WARN on violation
32
+
33
+ ### Auto-trigger conditions
34
+ The research pipeline fires AUTOMATICALLY (do not wait to be asked) when:
35
+ - Any `npm install <package>` or `pip install <package>` suggestion is generated
36
+ - Any new dependency is added to a manifest file
37
+ - Any framework or library recommendation appears in a plan
38
+ - Any code imports an external package not previously verified in this session
39
+
40
+ ### Sonar model selection
41
+ Use the appropriate Perplexity model for each query type:
42
+
43
+ | Query type | Model | Why |
44
+ |------------|-------|-----|
45
+ | Version lookup, changelog | `sonar` | Fast, cheap, factual |
46
+ | CVE research, security analysis | `sonar-pro` | Better citations, less hallucination on security claims |
47
+ | "Should I use X vs Y?" decisions | `sonar-reasoning` | Extended thinking for tradeoff analysis |
48
+ | Breach/incident research | `sonar-pro` + `search_recency_filter: "month"` | Restricts to recent results only |
49
+
50
+ ### Query templates (use verbatim)
51
+ - Latest version: `"[package] npm latest version site:npmjs.com OR site:github.com"`
52
+ - CVE check: `"[package] CVE vulnerability 2025 2026 site:nvd.nist.gov OR site:github.com/advisories"`
53
+ - Breaking changes: `"[package] breaking changes migration [version]"`
54
+ - Maintenance: `"[package] last commit release 2025 2026 archived"`
55
+
56
+ ### When to use Perplexity Sonar alongside Context7
57
+ - **Always**: Version verification (Context7 confirms API surface, Sonar confirms release date)
58
+ - **On new deps**: CVE check before adding to any manifest
59
+ - **On major upgrades**: Breaking changes search before recommending migration
60
+ - **On unfamiliar packages**: Maintenance and activity check
61
+
62
+ ### Session caching rules
63
+ - Context7 docs: valid for the entire session — do NOT re-fetch same library
64
+ - Sonar version checks: valid for the session (versions don't change mid-session)
65
+ - Sonar CVE checks: re-fetch if > 24 hours elapsed (use session timestamp)
66
+ - On new session: all caches reset, re-fetch everything fresh
67
+
68
+ ### When Context7 is unavailable
69
+ If the MCP server is not running or returns errors:
70
+ 1. State that docs could not be verified via Context7
71
+ 2. Fall back to Perplexity Sonar for documentation lookup
72
+ 3. If both are unavailable: flag the specific method/API as "unverified against current version"
73
+ 4. Proceed with best-knowledge implementation but mark for review
74
+
75
+ For comprehensive research (CVEs + maintenance + version), use `/research <package>`.
76
+
77
+ ---
78
+
79
+ ## Removal Condition
80
+ Remove when training data freshness is guaranteed to match live documentation,
81
+ or when a built-in documentation verification mechanism replaces MCP-based lookups.