@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.
- package/base/CLAUDE.md +17 -1
- package/base/configs/mcp-servers.json +26 -0
- package/base/configs/registry.json +113 -0
- package/base/hooks/credential-guard.sh +98 -0
- package/base/hooks/dep-audit.sh +105 -0
- package/base/hooks/quality-check.sh +4 -3
- package/base/hooks/recursion-guard.sh +76 -0
- package/base/hooks/session-data-collect.sh +3 -0
- package/base/hooks/settings-hooks.json +18 -0
- package/base/rules/coding.md +2 -2
- package/base/rules/dependency-freshness.md +50 -0
- package/base/rules/live-docs-required.md +81 -0
- package/base/rules/security-posture.md +62 -0
- package/base/skills/blind-judge/SKILL.md +88 -0
- package/base/skills/context7-lookup/SKILL.md +17 -6
- package/base/skills/deliberate/SKILL.md +103 -0
- package/base/skills/dep-hygiene/SKILL.md +93 -0
- package/base/skills/pre-commit-lint/SKILL.md +2 -3
- package/base/skills/research/SKILL.md +106 -0
- package/base/skills/scaffold-new/SKILL.md +1 -4
- package/base/skills/scaffold-new/references/repo-CLAUDE-md-template.md +1 -1
- package/bin/praxis-preflight.sh +317 -0
- package/package.json +1 -1
- package/scripts/install-tools.sh +3 -23
- package/scripts/lint-harness.sh +1 -1
- package/scripts/test-harness.sh +1 -26
- package/base/configs/vale/.vale.ini +0 -17
- package/base/configs/vale/Praxis/AISlop.yml +0 -90
- package/base/configs/vale/Praxis/CopulaAvoidance.yml +0 -22
- package/base/configs/vale/Praxis/ElegantVariation.yml +0 -14
- package/base/configs/vale/Praxis/HeadingCase.yml +0 -26
- package/base/configs/vale/Praxis/Hedging.yml +0 -22
- package/base/configs/vale/Praxis/NaturalVoice.yml +0 -85
- package/base/configs/vale/Praxis/Precision.yml +0 -60
- package/base/configs/vale/Praxis/SentenceLength.yml +0 -6
- package/base/configs/vale/Praxis/Terminology.yml +0 -25
- package/base/configs/vale/Praxis/vocabularies/accept.txt +0 -32
- package/base/configs/vale/Praxis/vocabularies/reject.txt +0 -3
- 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 (
|
|
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
|
-
|
|
107
|
-
|
|
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+=("
|
|
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
|
|
@@ -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": [
|
package/base/rules/coding.md
CHANGED
|
@@ -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
|
|
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` / `
|
|
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.
|