@esoteric-logic/praxis-harness 2.9.0 → 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 +8 -0
- package/base/configs/registry.json +2 -1
- package/base/hooks/auto-format.sh +1 -0
- package/base/hooks/dep-audit.sh +20 -7
- package/base/hooks/on-stop-failure.sh +121 -0
- package/base/hooks/session-data-collect.sh +1 -0
- package/base/hooks/settings-hooks.json +11 -0
- package/base/hooks/vault-checkpoint.sh +1 -0
- package/base/lib/kit-check.sh +42 -0
- package/base/lib/output.sh +38 -0
- package/base/rules/desktop-protocol.md +64 -0
- package/bin/praxis-preflight.sh +5 -8
- package/kits/api/install.sh +3 -16
- package/kits/code-quality/hooks/pre-push.sh +31 -8
- package/kits/data/install.sh +6 -19
- package/kits/infrastructure/install.sh +2 -20
- package/kits/security/install.sh +5 -18
- package/package.json +1 -1
- package/scripts/onboard-mcp.sh +3 -18
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"},
|
package/base/hooks/dep-audit.sh
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# dep-audit.sh — PostToolUse:Write|Edit|MultiEdit hook
|
|
3
3
|
# Runs dependency vulnerability checks when manifest files are modified.
|
|
4
4
|
# Always exits 0 (advisory only — PostToolUse cannot hard-block).
|
|
5
|
-
set -
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
trap 'exit 0' ERR
|
|
6
7
|
|
|
7
8
|
INPUT=$(cat)
|
|
8
9
|
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null)
|
|
@@ -20,28 +21,40 @@ case "$BASENAME" in
|
|
|
20
21
|
package.json)
|
|
21
22
|
ECOSYSTEM="npm"
|
|
22
23
|
if command -v npm &>/dev/null && [[ -f "$DIR/package-lock.json" || -f "$DIR/node_modules/.package-lock.json" ]]; then
|
|
23
|
-
|
|
24
|
+
# npm audit exits non-zero when vulnerabilities exist — capture output regardless
|
|
25
|
+
AUDIT_RESULT=$(cd "$DIR" && npm audit --audit-level=high --json 2>/dev/null) || {
|
|
26
|
+
if [[ -n "$AUDIT_RESULT" ]]; then
|
|
27
|
+
: # non-zero with output = vulnerabilities found (expected)
|
|
28
|
+
else
|
|
29
|
+
echo "DEP-AUDIT (npm): audit command failed" >&2
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
24
32
|
fi
|
|
25
33
|
;;
|
|
26
34
|
go.mod)
|
|
27
35
|
ECOSYSTEM="go"
|
|
28
36
|
if command -v govulncheck &>/dev/null; then
|
|
29
|
-
AUDIT_RESULT=$(cd "$DIR" && govulncheck -json ./... 2>/dev/null ||
|
|
37
|
+
AUDIT_RESULT=$(cd "$DIR" && govulncheck -json ./... 2>/dev/null) || {
|
|
38
|
+
[[ -z "$AUDIT_RESULT" ]] && echo "DEP-AUDIT (go): govulncheck failed" >&2
|
|
39
|
+
}
|
|
30
40
|
elif command -v go &>/dev/null; then
|
|
31
|
-
|
|
32
|
-
AUDIT_RESULT=$(cd "$DIR" && go list -m -json all 2>/dev/null | head -c 5000 || true)
|
|
41
|
+
AUDIT_RESULT=$(cd "$DIR" && go list -m -json all 2>/dev/null | head -c 5000) || AUDIT_RESULT=""
|
|
33
42
|
fi
|
|
34
43
|
;;
|
|
35
44
|
requirements.txt|pyproject.toml)
|
|
36
45
|
ECOSYSTEM="python"
|
|
37
46
|
if command -v pip-audit &>/dev/null; then
|
|
38
|
-
AUDIT_RESULT=$(cd "$DIR" && pip-audit --format=json 2>/dev/null ||
|
|
47
|
+
AUDIT_RESULT=$(cd "$DIR" && pip-audit --format=json 2>/dev/null) || {
|
|
48
|
+
[[ -z "$AUDIT_RESULT" ]] && echo "DEP-AUDIT (python): pip-audit failed" >&2
|
|
49
|
+
}
|
|
39
50
|
fi
|
|
40
51
|
;;
|
|
41
52
|
Cargo.toml)
|
|
42
53
|
ECOSYSTEM="rust"
|
|
43
54
|
if command -v cargo-audit &>/dev/null; then
|
|
44
|
-
AUDIT_RESULT=$(cd "$DIR" && cargo audit --json 2>/dev/null ||
|
|
55
|
+
AUDIT_RESULT=$(cd "$DIR" && cargo audit --json 2>/dev/null) || {
|
|
56
|
+
[[ -z "$AUDIT_RESULT" ]] && echo "DEP-AUDIT (rust): cargo-audit failed" >&2
|
|
57
|
+
}
|
|
45
58
|
fi
|
|
46
59
|
;;
|
|
47
60
|
*)
|
|
@@ -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
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ════════════════════════════════════════════════════════════════
|
|
3
|
+
# Praxis — Shared Kit Install Check
|
|
4
|
+
# Source this file in kit install.sh scripts for consistent
|
|
5
|
+
# tool-checking output and counters.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# source "$(dirname "$0")/../../base/lib/kit-check.sh"
|
|
9
|
+
# check "jq" "brew install jq"
|
|
10
|
+
# check "curl" "pre-installed on macOS/Linux"
|
|
11
|
+
# kit_check_summary
|
|
12
|
+
# ════════════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
KIT_CHECK_PASS=0
|
|
15
|
+
KIT_CHECK_TOTAL=0
|
|
16
|
+
|
|
17
|
+
check() {
|
|
18
|
+
local cmd="$1"
|
|
19
|
+
local install_hint="$2"
|
|
20
|
+
local optional="${3:-}"
|
|
21
|
+
|
|
22
|
+
KIT_CHECK_TOTAL=$((KIT_CHECK_TOTAL + 1))
|
|
23
|
+
if command -v "$cmd" &>/dev/null; then
|
|
24
|
+
echo " ✓ $cmd found ($(command -v "$cmd"))"
|
|
25
|
+
KIT_CHECK_PASS=$((KIT_CHECK_PASS + 1))
|
|
26
|
+
else
|
|
27
|
+
if [[ "$optional" == "optional" ]]; then
|
|
28
|
+
echo " ⚠ $cmd not found (optional)"
|
|
29
|
+
else
|
|
30
|
+
echo " ✗ $cmd not found"
|
|
31
|
+
fi
|
|
32
|
+
echo " Install: $install_hint"
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
kit_check_summary() {
|
|
37
|
+
echo ""
|
|
38
|
+
echo " $KIT_CHECK_PASS/$KIT_CHECK_TOTAL tools found"
|
|
39
|
+
if [[ $KIT_CHECK_PASS -lt $KIT_CHECK_TOTAL ]]; then
|
|
40
|
+
echo " ⚠ Some tools missing. Install them before using kit commands."
|
|
41
|
+
fi
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════��════════════════════════════════════════════════════════
|
|
3
|
+
# Praxis — Shared Output Helpers
|
|
4
|
+
# Source this file instead of defining colors/helpers inline.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# source "$(dirname "$0")/../base/lib/output.sh"
|
|
8
|
+
# # or with absolute path:
|
|
9
|
+
# source "$HOME/.claude/lib/output.sh"
|
|
10
|
+
#
|
|
11
|
+
# Safe to source multiple times — guards against redefinition.
|
|
12
|
+
# ════��═══════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
# ─── Colors (skip if already set) ───
|
|
15
|
+
RED="${RED:-\033[0;31m}"
|
|
16
|
+
GREEN="${GREEN:-\033[0;32m}"
|
|
17
|
+
YELLOW="${YELLOW:-\033[0;33m}"
|
|
18
|
+
CYAN="${CYAN:-\033[0;36m}"
|
|
19
|
+
BOLD="${BOLD:-\033[1m}"
|
|
20
|
+
DIM="${DIM:-\033[2m}"
|
|
21
|
+
NC="${NC:-\033[0m}"
|
|
22
|
+
|
|
23
|
+
# ─── Output helpers (skip if already defined) ───
|
|
24
|
+
if ! declare -f ok &>/dev/null; then
|
|
25
|
+
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
26
|
+
fi
|
|
27
|
+
if ! declare -f warn &>/dev/null; then
|
|
28
|
+
warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
|
|
29
|
+
fi
|
|
30
|
+
if ! declare -f fail &>/dev/null; then
|
|
31
|
+
fail() { echo -e " ${RED}✗${NC} $1"; }
|
|
32
|
+
fi
|
|
33
|
+
if ! declare -f step &>/dev/null; then
|
|
34
|
+
step() { echo -e "\n${CYAN}${BOLD}$1${NC}"; }
|
|
35
|
+
fi
|
|
36
|
+
if ! declare -f dim &>/dev/null; then
|
|
37
|
+
dim() { echo -e " ${DIM}$1${NC}"; }
|
|
38
|
+
fi
|
|
@@ -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/bin/praxis-preflight.sh
CHANGED
|
@@ -4,13 +4,10 @@
|
|
|
4
4
|
# Also implements: set-key subcommand for secret management.
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
|
-
# ─── Colors ───
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
CYAN='\033[0;36m'
|
|
12
|
-
BOLD='\033[1m'
|
|
13
|
-
NC='\033[0m'
|
|
7
|
+
# ─── Colors (shared) ───
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
|
10
|
+
source "$REPO_DIR/base/lib/output.sh"
|
|
14
11
|
|
|
15
12
|
PASS=0
|
|
16
13
|
WARN=0
|
|
@@ -19,10 +16,10 @@ PRAXIS_DIR="$HOME/.praxis"
|
|
|
19
16
|
SECRETS_FILE="$PRAXIS_DIR/secrets"
|
|
20
17
|
REPORT_FILE="$PRAXIS_DIR/preflight-report.json"
|
|
21
18
|
|
|
19
|
+
# Override shared helpers with counter-incrementing versions
|
|
22
20
|
ok() { echo -e " ${GREEN}✓${NC} $1"; PASS=$((PASS + 1)); }
|
|
23
21
|
warn() { echo -e " ${YELLOW}⚠${NC} $1"; WARN=$((WARN + 1)); }
|
|
24
22
|
fail() { echo -e " ${RED}✗${NC} $1"; BLOCK=$((BLOCK + 1)); }
|
|
25
|
-
step() { echo -e "\n${CYAN}${BOLD}$1${NC}"; }
|
|
26
23
|
|
|
27
24
|
# ═══════════════════════════════════════════
|
|
28
25
|
# Subcommand: set-key
|
package/kits/api/install.sh
CHANGED
|
@@ -4,19 +4,7 @@ set -euo pipefail
|
|
|
4
4
|
echo "=== Praxis: Installing api kit ==="
|
|
5
5
|
echo ""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
TOTAL=0
|
|
9
|
-
|
|
10
|
-
check() {
|
|
11
|
-
TOTAL=$((TOTAL + 1))
|
|
12
|
-
if command -v "$1" &>/dev/null; then
|
|
13
|
-
echo " ✓ $1 found ($(command -v "$1"))"
|
|
14
|
-
PASS=$((PASS + 1))
|
|
15
|
-
else
|
|
16
|
-
echo " ✗ $1 not found"
|
|
17
|
-
echo " Install: $2"
|
|
18
|
-
fi
|
|
19
|
-
}
|
|
7
|
+
source "$(dirname "$0")/../../base/lib/kit-check.sh"
|
|
20
8
|
|
|
21
9
|
echo "Checking optional CLI tools..."
|
|
22
10
|
echo ""
|
|
@@ -24,10 +12,9 @@ echo ""
|
|
|
24
12
|
check "jq" "brew install jq OR apt-get install jq"
|
|
25
13
|
check "curl" "pre-installed on macOS/Linux"
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
echo " $PASS/$TOTAL tools found"
|
|
29
|
-
echo ""
|
|
15
|
+
kit_check_summary
|
|
30
16
|
|
|
17
|
+
echo ""
|
|
31
18
|
echo "Note: This kit uses Claude's built-in analysis capabilities."
|
|
32
19
|
echo "No external API linting tools required."
|
|
33
20
|
echo ""
|
|
@@ -8,6 +8,25 @@ BASELINE="$REPO_ROOT/.quality-baseline.json"
|
|
|
8
8
|
TMP="/tmp/praxis-quality-$$"
|
|
9
9
|
mkdir -p "$TMP"
|
|
10
10
|
|
|
11
|
+
# Safe jq wrapper: validates JSON before querying; returns fallback on parse failure
|
|
12
|
+
# and logs a warning so gate operators know a scan produced bad output.
|
|
13
|
+
safe_jq() {
|
|
14
|
+
local query="$1"
|
|
15
|
+
local file="$2"
|
|
16
|
+
local fallback="${3:-0}"
|
|
17
|
+
if [[ ! -s "$file" ]]; then
|
|
18
|
+
echo "$fallback"
|
|
19
|
+
return
|
|
20
|
+
fi
|
|
21
|
+
if ! jq empty "$file" 2>/dev/null; then
|
|
22
|
+
echo " ⚠ WARNING: $file is not valid JSON — treating as scan failure" >&2
|
|
23
|
+
GATE_WARNINGS+=("PARSE: $(basename "$file") produced invalid output")
|
|
24
|
+
echo "$fallback"
|
|
25
|
+
return
|
|
26
|
+
fi
|
|
27
|
+
jq -r "$query" "$file" 2>/dev/null || echo "$fallback"
|
|
28
|
+
}
|
|
29
|
+
|
|
11
30
|
echo ""
|
|
12
31
|
echo "Praxis Code Quality Gate"
|
|
13
32
|
echo "------------------------"
|
|
@@ -32,8 +51,8 @@ if [ -s "$TMP/code-files.txt" ]; then
|
|
|
32
51
|
FILES=$(cat "$TMP/code-files.txt" | tr '\n' ' ')
|
|
33
52
|
opengrep scan --config auto --json $FILES > "$TMP/sast.json" 2>/dev/null || true
|
|
34
53
|
|
|
35
|
-
CRITICAL=$(
|
|
36
|
-
HIGH=$(
|
|
54
|
+
CRITICAL=$(safe_jq '[.results[] | select(.extra.severity == "ERROR")] | length' "$TMP/sast.json")
|
|
55
|
+
HIGH=$(safe_jq '[.results[] | select(.extra.severity == "WARNING")] | length' "$TMP/sast.json")
|
|
37
56
|
|
|
38
57
|
[ "$CRITICAL" -gt 0 ] && GATE_FAILURES+=("SAST: $CRITICAL critical findings")
|
|
39
58
|
[ "$HIGH" -gt 0 ] && GATE_WARNINGS+=("SAST: $HIGH high findings")
|
|
@@ -44,7 +63,11 @@ SAST_PID=$!
|
|
|
44
63
|
# -- SECRETS (TruffleHog) --
|
|
45
64
|
echo " Secrets scan (TruffleHog)..."
|
|
46
65
|
trufflehog git file://. --since-commit HEAD~1 --only-verified --json > "$TMP/secrets.json" 2>/dev/null || true
|
|
47
|
-
|
|
66
|
+
if [[ -s "$TMP/secrets.json" ]]; then
|
|
67
|
+
SECRETS=$(jq -s 'length' "$TMP/secrets.json" 2>/dev/null || echo 0)
|
|
68
|
+
else
|
|
69
|
+
SECRETS=0
|
|
70
|
+
fi
|
|
48
71
|
[ "$SECRETS" -gt 0 ] && GATE_FAILURES+=("SECRETS: $SECRETS verified secrets found")
|
|
49
72
|
echo " Verified secrets: $SECRETS" &
|
|
50
73
|
SECRETS_PID=$!
|
|
@@ -52,8 +75,8 @@ SECRETS_PID=$!
|
|
|
52
75
|
# -- SCA (OSV-Scanner) --
|
|
53
76
|
echo " Dependency scan (OSV-Scanner)..."
|
|
54
77
|
osv-scanner scan --format json "$REPO_ROOT" > "$TMP/sca.json" 2>/dev/null || true
|
|
55
|
-
SCA_CRITICAL=$(
|
|
56
|
-
SCA_HIGH=$(
|
|
78
|
+
SCA_CRITICAL=$(safe_jq '[.vulns[]? | select(.database_specific.severity? == "CRITICAL")] | length' "$TMP/sca.json")
|
|
79
|
+
SCA_HIGH=$(safe_jq '[.vulns[]? | select(.database_specific.severity? == "HIGH")] | length' "$TMP/sca.json")
|
|
57
80
|
[ "$SCA_CRITICAL" -gt 0 ] && GATE_FAILURES+=("SCA: $SCA_CRITICAL critical CVEs in dependencies")
|
|
58
81
|
[ "$SCA_HIGH" -gt 0 ] && GATE_WARNINGS+=("SCA: $SCA_HIGH high CVEs in dependencies")
|
|
59
82
|
echo " Critical CVEs: $SCA_CRITICAL High CVEs: $SCA_HIGH" &
|
|
@@ -63,7 +86,7 @@ SCA_PID=$!
|
|
|
63
86
|
if [ -s "$TMP/iac-files.txt" ]; then
|
|
64
87
|
echo " IaC scan (Checkov)..."
|
|
65
88
|
checkov -d "$REPO_ROOT" --output json --quiet --compact 2>/dev/null > "$TMP/iac.json" || true
|
|
66
|
-
IaC_FAIL=$(
|
|
89
|
+
IaC_FAIL=$(safe_jq '.results.failed_checks | length' "$TMP/iac.json")
|
|
67
90
|
[ "$IaC_FAIL" -gt 0 ] && GATE_WARNINGS+=("IaC: $IaC_FAIL policy violations")
|
|
68
91
|
echo " Policy violations: $IaC_FAIL"
|
|
69
92
|
fi &
|
|
@@ -73,9 +96,9 @@ IaC_PID=$!
|
|
|
73
96
|
wait $SAST_PID $SECRETS_PID $SCA_PID $IaC_PID 2>/dev/null || true
|
|
74
97
|
|
|
75
98
|
# -- COVERAGE --
|
|
76
|
-
COVERAGE_THRESHOLD=$(
|
|
99
|
+
COVERAGE_THRESHOLD=$(safe_jq '.coverage.line_min' "$CONFIG" 80)
|
|
77
100
|
if [ -f "$REPO_ROOT/coverage/coverage-summary.json" ]; then
|
|
78
|
-
COVERAGE=$(
|
|
101
|
+
COVERAGE=$(safe_jq '.total.lines.pct' "$REPO_ROOT/coverage/coverage-summary.json" 100)
|
|
79
102
|
BELOW=$(echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l 2>/dev/null || echo 0)
|
|
80
103
|
[ "$BELOW" = "1" ] && GATE_WARNINGS+=("COVERAGE: ${COVERAGE}% below threshold ${COVERAGE_THRESHOLD}%")
|
|
81
104
|
echo " Coverage: ${COVERAGE}% (threshold: ${COVERAGE_THRESHOLD}%)"
|
package/kits/data/install.sh
CHANGED
|
@@ -4,32 +4,19 @@ set -euo pipefail
|
|
|
4
4
|
echo "=== Praxis: Installing data kit ==="
|
|
5
5
|
echo ""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
TOTAL=0
|
|
9
|
-
|
|
10
|
-
check() {
|
|
11
|
-
TOTAL=$((TOTAL + 1))
|
|
12
|
-
if command -v "$1" &>/dev/null; then
|
|
13
|
-
echo " ✓ $1 found ($(command -v "$1"))"
|
|
14
|
-
PASS=$((PASS + 1))
|
|
15
|
-
else
|
|
16
|
-
echo " ✗ $1 not found (optional)"
|
|
17
|
-
echo " Install: $2"
|
|
18
|
-
fi
|
|
19
|
-
}
|
|
7
|
+
source "$(dirname "$0")/../../base/lib/kit-check.sh"
|
|
20
8
|
|
|
21
9
|
echo "Checking optional CLI tools..."
|
|
22
10
|
echo ""
|
|
23
11
|
|
|
24
|
-
check "psql" "brew install postgresql OR apt-get install postgresql-client"
|
|
25
|
-
check "mysql" "brew install mysql-client OR apt-get install mysql-client"
|
|
26
|
-
check "mongosh" "brew install mongosh OR https://www.mongodb.com/try/download/shell"
|
|
12
|
+
check "psql" "brew install postgresql OR apt-get install postgresql-client" "optional"
|
|
13
|
+
check "mysql" "brew install mysql-client OR apt-get install mysql-client" "optional"
|
|
14
|
+
check "mongosh" "brew install mongosh OR https://www.mongodb.com/try/download/shell" "optional"
|
|
27
15
|
check "jq" "brew install jq OR apt-get install jq"
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
echo " $PASS/$TOTAL tools found"
|
|
31
|
-
echo ""
|
|
17
|
+
kit_check_summary
|
|
32
18
|
|
|
19
|
+
echo ""
|
|
33
20
|
echo "Note: This kit uses Claude's built-in analysis for schema and query review."
|
|
34
21
|
echo "Database CLI tools are needed only for live query testing."
|
|
35
22
|
echo ""
|
|
@@ -4,19 +4,7 @@ set -euo pipefail
|
|
|
4
4
|
echo "=== Praxis: Installing infrastructure kit ==="
|
|
5
5
|
echo ""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
TOTAL=0
|
|
9
|
-
|
|
10
|
-
check() {
|
|
11
|
-
TOTAL=$((TOTAL + 1))
|
|
12
|
-
if command -v "$1" &>/dev/null; then
|
|
13
|
-
echo " ✓ $1 found ($(command -v "$1"))"
|
|
14
|
-
PASS=$((PASS + 1))
|
|
15
|
-
else
|
|
16
|
-
echo " ✗ $1 not found"
|
|
17
|
-
echo " Install: $2"
|
|
18
|
-
fi
|
|
19
|
-
}
|
|
7
|
+
source "$(dirname "$0")/../../base/lib/kit-check.sh"
|
|
20
8
|
|
|
21
9
|
echo "Checking required CLI tools..."
|
|
22
10
|
echo ""
|
|
@@ -26,13 +14,7 @@ check "terraform" "https://developer.hashicorp.com/terraform/install"
|
|
|
26
14
|
check "tflint" "brew install tflint OR https://github.com/terraform-linters/tflint"
|
|
27
15
|
check "jq" "brew install jq OR apt-get install jq"
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
echo " $PASS/$TOTAL tools found"
|
|
31
|
-
echo ""
|
|
32
|
-
|
|
33
|
-
if [[ $PASS -lt $TOTAL ]]; then
|
|
34
|
-
echo " ⚠ Some tools missing. Install them before using infrastructure commands."
|
|
35
|
-
fi
|
|
17
|
+
kit_check_summary
|
|
36
18
|
|
|
37
19
|
echo ""
|
|
38
20
|
echo "Note: Skills chain phases are status: planned."
|
package/kits/security/install.sh
CHANGED
|
@@ -4,31 +4,18 @@ set -euo pipefail
|
|
|
4
4
|
echo "=== Praxis: Installing security kit ==="
|
|
5
5
|
echo ""
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
TOTAL=0
|
|
9
|
-
|
|
10
|
-
check() {
|
|
11
|
-
TOTAL=$((TOTAL + 1))
|
|
12
|
-
if command -v "$1" &>/dev/null; then
|
|
13
|
-
echo " ✓ $1 found ($(command -v "$1"))"
|
|
14
|
-
PASS=$((PASS + 1))
|
|
15
|
-
else
|
|
16
|
-
echo " ✗ $1 not found (optional)"
|
|
17
|
-
echo " Install: $2"
|
|
18
|
-
fi
|
|
19
|
-
}
|
|
7
|
+
source "$(dirname "$0")/../../base/lib/kit-check.sh"
|
|
20
8
|
|
|
21
9
|
echo "Checking optional CLI tools..."
|
|
22
10
|
echo ""
|
|
23
11
|
|
|
24
|
-
check "trivy" "brew install trivy OR https://aquasecurity.github.io/trivy"
|
|
25
|
-
check "deepsource" "curl -fsSL https://cli.deepsource.com/install | sh"
|
|
12
|
+
check "trivy" "brew install trivy OR https://aquasecurity.github.io/trivy" "optional"
|
|
13
|
+
check "deepsource" "curl -fsSL https://cli.deepsource.com/install | sh" "optional"
|
|
26
14
|
check "rg" "brew install ripgrep OR apt-get install ripgrep"
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
echo " $PASS/$TOTAL tools found"
|
|
30
|
-
echo ""
|
|
16
|
+
kit_check_summary
|
|
31
17
|
|
|
18
|
+
echo ""
|
|
32
19
|
echo "Note: This kit uses Claude's built-in analysis for most checks."
|
|
33
20
|
echo "External tools enhance scanning but are not required."
|
|
34
21
|
echo ""
|
package/package.json
CHANGED
package/scripts/onboard-mcp.sh
CHANGED
|
@@ -20,24 +20,9 @@ set -euo pipefail
|
|
|
20
20
|
CLAUDE_DIR="${CLAUDE_DIR:-$HOME/.claude}"
|
|
21
21
|
CONFIG_FILE="${CONFIG_FILE:-$CLAUDE_DIR/praxis.config.json}"
|
|
22
22
|
|
|
23
|
-
# ─── Colors (safe
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
YELLOW="${YELLOW:-\033[0;33m}"
|
|
27
|
-
CYAN="${CYAN:-\033[0;36m}"
|
|
28
|
-
BOLD="${BOLD:-\033[1m}"
|
|
29
|
-
NC="${NC:-\033[0m}"
|
|
30
|
-
|
|
31
|
-
# ─── Output helpers (no-op if already defined by install.sh) ───
|
|
32
|
-
if ! declare -f ok &>/dev/null; then
|
|
33
|
-
ok() { echo -e " $GREEN✓$NC $1"; }
|
|
34
|
-
fi
|
|
35
|
-
if ! declare -f warn &>/dev/null; then
|
|
36
|
-
warn() { echo -e " $YELLOW⚠$NC $1"; }
|
|
37
|
-
fi
|
|
38
|
-
if ! declare -f fail &>/dev/null; then
|
|
39
|
-
fail() { echo -e " $RED✗$NC $1"; }
|
|
40
|
-
fi
|
|
23
|
+
# ─── Colors & output helpers (safe to source multiple times) ───
|
|
24
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
25
|
+
source "$SCRIPT_DIR/../base/lib/output.sh"
|
|
41
26
|
|
|
42
27
|
# ═══════════════════════════════════════════
|
|
43
28
|
# Utilities
|