@esoteric-logic/praxis-harness 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/base/CLAUDE.md +148 -0
- package/base/commands/context-reset.md +72 -0
- package/base/commands/debug.md +63 -0
- package/base/commands/discover.md +49 -0
- package/base/commands/gsd-discuss.md +53 -0
- package/base/commands/gsd-execute.md +60 -0
- package/base/commands/gsd-verify.md +78 -0
- package/base/commands/kit.md +62 -0
- package/base/commands/plan.md +91 -0
- package/base/commands/ralph.md +110 -0
- package/base/commands/review.md +81 -0
- package/base/commands/risk.md +53 -0
- package/base/commands/ship.md +74 -0
- package/base/commands/spec.md +121 -0
- package/base/commands/standup.md +57 -0
- package/base/rules/architecture.md +51 -0
- package/base/rules/azure.md +90 -0
- package/base/rules/code-quality.md +65 -0
- package/base/rules/coding.md +139 -0
- package/base/rules/communication.md +69 -0
- package/base/rules/context-management.md +136 -0
- package/base/rules/execution-loop.md +84 -0
- package/base/rules/git-workflow.md +51 -0
- package/base/rules/github-actions.md +48 -0
- package/base/rules/powershell.md +72 -0
- package/base/rules/profile.md +31 -0
- package/base/rules/security.md +40 -0
- package/base/rules/terraform.md +48 -0
- package/base/rules/vault.md +134 -0
- package/base/skills/code-gc/SKILL.md +205 -0
- package/base/skills/code-simplifier/SKILL.md +132 -0
- package/base/skills/prd-writer/SKILL.md +108 -0
- package/base/skills/prd-writer/references/prd-template.md +22 -0
- package/base/skills/pre-commit-lint/SKILL.md +71 -0
- package/base/skills/scaffold-exist/SKILL.md +85 -0
- package/base/skills/scaffold-new/SKILL.md +177 -0
- package/base/skills/scaffold-new/references/claude-progress-template.json +24 -0
- package/base/skills/scaffold-new/references/gitignore-template.txt +65 -0
- package/base/skills/scaffold-new/references/repo-CLAUDE-md-template.md +87 -0
- package/base/skills/scaffold-new/references/vault-index-template.md +31 -0
- package/base/skills/scaffold-new/references/vault-learnings-template.md +21 -0
- package/base/skills/scaffold-new/references/vault-status-template.md +21 -0
- package/base/skills/scaffold-new/references/vault-tasks-template.md +20 -0
- package/base/skills/session-retro/SKILL.md +146 -0
- package/base/skills/subagent-review/SKILL.md +126 -0
- package/base/skills/vault-gc/SKILL.md +93 -0
- package/base/skills/verify-app/SKILL.md +156 -0
- package/bin/praxis.js +385 -0
- package/kits/infrastructure/KIT.md +66 -0
- package/kits/infrastructure/commands/infra-apply.md +44 -0
- package/kits/infrastructure/commands/infra-compliance.md +65 -0
- package/kits/infrastructure/commands/infra-drift.md +45 -0
- package/kits/infrastructure/commands/infra-plan.md +45 -0
- package/kits/infrastructure/install.sh +43 -0
- package/kits/infrastructure/rules/infrastructure.md +82 -0
- package/kits/infrastructure/teardown.sh +14 -0
- package/kits/web-designer/KIT.md +76 -0
- package/kits/web-designer/commands/web-audit.md +67 -0
- package/kits/web-designer/commands/web-component.md +54 -0
- package/kits/web-designer/commands/web-init.md +42 -0
- package/kits/web-designer/commands/web-tokens-sync.md +49 -0
- package/kits/web-designer/install.sh +41 -0
- package/kits/web-designer/rules/web-design.md +79 -0
- package/kits/web-designer/teardown.sh +26 -0
- package/package.json +28 -0
- package/scripts/health-check.sh +160 -0
- package/scripts/lint-harness.sh +195 -0
- package/scripts/onboard-mcp.sh +326 -0
- package/scripts/update.sh +88 -0
- package/templates/_index.md +33 -0
- package/templates/adr.md +28 -0
- package/templates/claude-progress.json +24 -0
- package/templates/plan.md +46 -0
- package/templates/project-index.md +31 -0
- package/templates/session-note.md +21 -0
- package/templates/status.md +27 -0
- package/templates/tasks.md +27 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ════════════════════════════════════════════════════════════════
|
|
5
|
+
# Praxis — Health Check
|
|
6
|
+
# Verifies install integrity: symlinks, config, tools, base layer
|
|
7
|
+
# ════════════════════════════════════════════════════════════════
|
|
8
|
+
|
|
9
|
+
CLAUDE_DIR="$HOME/.claude"
|
|
10
|
+
CONFIG_FILE="$CLAUDE_DIR/praxis.config.json"
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
PRAXIS_DIR="$(dirname "$SCRIPT_DIR")"
|
|
13
|
+
|
|
14
|
+
PASS=0
|
|
15
|
+
FAIL=0
|
|
16
|
+
TOTAL=0
|
|
17
|
+
|
|
18
|
+
check() {
|
|
19
|
+
TOTAL=$((TOTAL + 1))
|
|
20
|
+
if eval "$1" 2>/dev/null; then
|
|
21
|
+
echo " ✓ $2"
|
|
22
|
+
PASS=$((PASS + 1))
|
|
23
|
+
else
|
|
24
|
+
echo " ✗ $2"
|
|
25
|
+
FAIL=$((FAIL + 1))
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
echo "Praxis Health Check"
|
|
30
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
31
|
+
|
|
32
|
+
# ─── CLAUDE.md symlink ───
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Core:"
|
|
35
|
+
check "[[ -e '$CLAUDE_DIR/CLAUDE.md' ]]" "CLAUDE.md installed"
|
|
36
|
+
|
|
37
|
+
# ─── Rules symlinks ───
|
|
38
|
+
echo ""
|
|
39
|
+
echo "Rules:"
|
|
40
|
+
if [[ -d "$PRAXIS_DIR/base/rules" ]]; then
|
|
41
|
+
for rule in "$PRAXIS_DIR"/base/rules/*.md; do
|
|
42
|
+
[[ -f "$rule" ]] || continue
|
|
43
|
+
fname=$(basename "$rule")
|
|
44
|
+
check "[[ -e '$CLAUDE_DIR/rules/$fname' ]]" "rules/$fname installed"
|
|
45
|
+
done
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ─── Commands symlinks ───
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Commands:"
|
|
51
|
+
if [[ -d "$PRAXIS_DIR/base/commands" ]]; then
|
|
52
|
+
for cmd in "$PRAXIS_DIR"/base/commands/*.md; do
|
|
53
|
+
[[ -f "$cmd" ]] || continue
|
|
54
|
+
fname=$(basename "$cmd")
|
|
55
|
+
check "[[ -e '$CLAUDE_DIR/commands/$fname' ]]" "commands/$fname installed"
|
|
56
|
+
done
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ─── Skills symlinks ───
|
|
60
|
+
echo ""
|
|
61
|
+
echo "Skills:"
|
|
62
|
+
if [[ -d "$PRAXIS_DIR/base/skills" ]]; then
|
|
63
|
+
for skill_dir in "$PRAXIS_DIR"/base/skills/*/; do
|
|
64
|
+
[[ -d "$skill_dir" ]] || continue
|
|
65
|
+
skill_name=$(basename "$skill_dir")
|
|
66
|
+
check "[[ -e '$CLAUDE_DIR/skills/$skill_name' ]]" "skills/$skill_name installed"
|
|
67
|
+
done
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# ─── Kits symlink ───
|
|
71
|
+
echo ""
|
|
72
|
+
echo "Kits:"
|
|
73
|
+
check "[[ -e '$CLAUDE_DIR/kits' ]]" "kits directory installed"
|
|
74
|
+
|
|
75
|
+
# ─── Config ───
|
|
76
|
+
echo ""
|
|
77
|
+
echo "Config:"
|
|
78
|
+
check "[[ -f '$CONFIG_FILE' ]]" "praxis.config.json exists"
|
|
79
|
+
|
|
80
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
81
|
+
VAULT_PATH=$(jq -r '.vault_path // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
82
|
+
if [[ -n "$VAULT_PATH" ]]; then
|
|
83
|
+
check "[[ -d '$VAULT_PATH' ]]" "vault_path ($VAULT_PATH) is a real directory"
|
|
84
|
+
else
|
|
85
|
+
TOTAL=$((TOTAL + 1))
|
|
86
|
+
echo " ✗ vault_path not set in config"
|
|
87
|
+
FAIL=$((FAIL + 1))
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# ─── Required tools (conditional on backend) ───
|
|
92
|
+
echo ""
|
|
93
|
+
echo "Tools:"
|
|
94
|
+
VAULT_BACKEND=""
|
|
95
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
96
|
+
VAULT_BACKEND=$(jq -r '.vault_backend // "obsidian"' "$CONFIG_FILE" 2>/dev/null)
|
|
97
|
+
fi
|
|
98
|
+
if [[ "$VAULT_BACKEND" == "obsidian" ]]; then
|
|
99
|
+
check "command -v obsidian" "Obsidian CLI available"
|
|
100
|
+
else
|
|
101
|
+
check "command -v rg" "ripgrep available"
|
|
102
|
+
fi
|
|
103
|
+
check "command -v node" "node available"
|
|
104
|
+
check "command -v claude" "claude available"
|
|
105
|
+
check "command -v jq" "jq available"
|
|
106
|
+
|
|
107
|
+
# ─── MCP Servers (warn only) ───
|
|
108
|
+
echo ""
|
|
109
|
+
echo "MCP Servers:"
|
|
110
|
+
if command -v claude &>/dev/null; then
|
|
111
|
+
MCP_LIST=$(claude mcp list 2>/dev/null || true)
|
|
112
|
+
for server in context7 perplexity github; do
|
|
113
|
+
TOTAL=$((TOTAL + 1))
|
|
114
|
+
if echo "$MCP_LIST" | grep -q "$server"; then
|
|
115
|
+
echo " ✓ $server registered"
|
|
116
|
+
PASS=$((PASS + 1))
|
|
117
|
+
else
|
|
118
|
+
echo " ⚠ $server not registered (optional)"
|
|
119
|
+
PASS=$((PASS + 1)) # optional = pass either way
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
else
|
|
123
|
+
echo " ⚠ claude CLI not available — cannot check MCP servers"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# ─── Broken symlinks (relevant for git-clone/symlink installs) ───
|
|
127
|
+
echo ""
|
|
128
|
+
echo "Symlink integrity:"
|
|
129
|
+
BROKEN=0
|
|
130
|
+
for dir in "$CLAUDE_DIR/rules" "$CLAUDE_DIR/commands" "$CLAUDE_DIR/skills"; do
|
|
131
|
+
if [[ -d "$dir" ]]; then
|
|
132
|
+
while IFS= read -r link; do
|
|
133
|
+
if [[ -L "$link" && ! -e "$link" ]]; then
|
|
134
|
+
echo " ✗ Broken symlink: $link"
|
|
135
|
+
BROKEN=$((BROKEN + 1))
|
|
136
|
+
fi
|
|
137
|
+
done < <(find "$dir" -maxdepth 1 -type l 2>/dev/null)
|
|
138
|
+
fi
|
|
139
|
+
done
|
|
140
|
+
|
|
141
|
+
TOTAL=$((TOTAL + 1))
|
|
142
|
+
if [[ $BROKEN -eq 0 ]]; then
|
|
143
|
+
echo " ✓ No broken symlinks"
|
|
144
|
+
PASS=$((PASS + 1))
|
|
145
|
+
else
|
|
146
|
+
echo " ✗ $BROKEN broken symlink(s) found"
|
|
147
|
+
FAIL=$((FAIL + 1))
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# ─── Summary ───
|
|
151
|
+
echo ""
|
|
152
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
153
|
+
echo " Results: $PASS/$TOTAL passed"
|
|
154
|
+
if [[ $FAIL -gt 0 ]]; then
|
|
155
|
+
echo " $FAIL check(s) failed"
|
|
156
|
+
exit 1
|
|
157
|
+
else
|
|
158
|
+
echo " All checks passed"
|
|
159
|
+
exit 0
|
|
160
|
+
fi
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ════════════════════════════════════════════════════════════════
|
|
5
|
+
# Praxis — Harness Content Lint
|
|
6
|
+
# Validates frontmatter, placeholders, registry consistency, syntax
|
|
7
|
+
# ════════════════════════════════════════════════════════════════
|
|
8
|
+
|
|
9
|
+
REPO_PATH="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
10
|
+
|
|
11
|
+
ERRORS=0
|
|
12
|
+
WARNINGS=0
|
|
13
|
+
|
|
14
|
+
error() {
|
|
15
|
+
echo " ✗ ERROR: $1"
|
|
16
|
+
ERRORS=$((ERRORS + 1))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
warn() {
|
|
20
|
+
echo " ⚠ WARN: $1"
|
|
21
|
+
WARNINGS=$((WARNINGS + 1))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
ok() {
|
|
25
|
+
echo " ✓ $1"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
echo "Praxis Harness Lint"
|
|
29
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
30
|
+
echo " Repo: $REPO_PATH"
|
|
31
|
+
|
|
32
|
+
# ─── 1. Command frontmatter ───
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Commands (description: field):"
|
|
35
|
+
if [[ -d "$REPO_PATH/base/commands" ]]; then
|
|
36
|
+
for cmd in "$REPO_PATH"/base/commands/*.md; do
|
|
37
|
+
[[ -f "$cmd" ]] || continue
|
|
38
|
+
fname=$(basename "$cmd")
|
|
39
|
+
if head -5 "$cmd" | grep -q "^description:"; then
|
|
40
|
+
ok "$fname"
|
|
41
|
+
else
|
|
42
|
+
error "$fname missing description: in frontmatter"
|
|
43
|
+
fi
|
|
44
|
+
done
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# ─── 2. Skill frontmatter ───
|
|
48
|
+
echo ""
|
|
49
|
+
echo "Skills (name:, disable-model-invocation:, description:):"
|
|
50
|
+
if [[ -d "$REPO_PATH/base/skills" ]]; then
|
|
51
|
+
for skill_dir in "$REPO_PATH"/base/skills/*/; do
|
|
52
|
+
[[ -d "$skill_dir" ]] || continue
|
|
53
|
+
skill_name=$(basename "$skill_dir")
|
|
54
|
+
skill_file="$skill_dir/SKILL.md"
|
|
55
|
+
if [[ ! -f "$skill_file" ]]; then
|
|
56
|
+
error "skills/$skill_name/ missing SKILL.md"
|
|
57
|
+
continue
|
|
58
|
+
fi
|
|
59
|
+
# Check required frontmatter fields (within first 10 lines)
|
|
60
|
+
header=$(head -10 "$skill_file")
|
|
61
|
+
missing=""
|
|
62
|
+
echo "$header" | grep -q "^name:" || missing="$missing name:"
|
|
63
|
+
echo "$header" | grep -q "^disable-model-invocation:" || missing="$missing disable-model-invocation:"
|
|
64
|
+
echo "$header" | grep -q "^description:" || missing="$missing description:"
|
|
65
|
+
if [[ -z "$missing" ]]; then
|
|
66
|
+
ok "skills/$skill_name"
|
|
67
|
+
else
|
|
68
|
+
error "skills/$skill_name SKILL.md missing:$missing"
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# ─── 3. Kit frontmatter ───
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Kits (name:, version:, description:, activation:, skills_chain:):"
|
|
76
|
+
if [[ -d "$REPO_PATH/kits" ]]; then
|
|
77
|
+
for kit_dir in "$REPO_PATH"/kits/*/; do
|
|
78
|
+
[[ -d "$kit_dir" ]] || continue
|
|
79
|
+
kit_name=$(basename "$kit_dir")
|
|
80
|
+
kit_file="$kit_dir/KIT.md"
|
|
81
|
+
if [[ ! -f "$kit_file" ]]; then
|
|
82
|
+
error "kits/$kit_name/ missing KIT.md"
|
|
83
|
+
continue
|
|
84
|
+
fi
|
|
85
|
+
header=$(head -15 "$kit_file")
|
|
86
|
+
missing=""
|
|
87
|
+
echo "$header" | grep -q "^name:" || missing="$missing name:"
|
|
88
|
+
echo "$header" | grep -q "^version:" || missing="$missing version:"
|
|
89
|
+
echo "$header" | grep -q "^description:" || missing="$missing description:"
|
|
90
|
+
echo "$header" | grep -q "^activation:" || missing="$missing activation:"
|
|
91
|
+
echo "$header" | grep -q "^skills_chain:" || missing="$missing skills_chain:"
|
|
92
|
+
if [[ -z "$missing" ]]; then
|
|
93
|
+
ok "kits/$kit_name"
|
|
94
|
+
else
|
|
95
|
+
error "kits/$kit_name KIT.md missing:$missing"
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ─── 4. Placeholder scan ───
|
|
101
|
+
echo ""
|
|
102
|
+
echo "Placeholder scan ({placeholder} patterns):"
|
|
103
|
+
PLACEHOLDER_FOUND=0
|
|
104
|
+
# Scan base/, kits/, docs/, scripts/ — exclude templates/ and */references/
|
|
105
|
+
# Also exclude HTML comments and fenced code blocks
|
|
106
|
+
while IFS= read -r file; do
|
|
107
|
+
[[ -f "$file" ]] || continue
|
|
108
|
+
# Skip templates directory and references directories
|
|
109
|
+
[[ "$file" == *"/templates/"* ]] && continue
|
|
110
|
+
[[ "$file" == *"/references/"* ]] && continue
|
|
111
|
+
|
|
112
|
+
# Strip fenced code blocks (including indented), HTML comments,
|
|
113
|
+
# lines with inline backticks, and shell comments/echo lines
|
|
114
|
+
matches=$(sed -E \
|
|
115
|
+
-e '/^[[:space:]]*```/,/^[[:space:]]*```/d' \
|
|
116
|
+
-e '/<!--/,/-->/d' \
|
|
117
|
+
"$file" \
|
|
118
|
+
| grep -nE '\{[a-zA-Z_][a-zA-Z0-9_-]*\}' \
|
|
119
|
+
| grep -vE '`[^`]*\{[^}]+\}[^`]*`' \
|
|
120
|
+
| grep -vE '^[0-9]+:\s*#' \
|
|
121
|
+
| grep -vE '^[0-9]+:\s*echo ' \
|
|
122
|
+
| grep -vE '\{vault_path\}|\{today_date\}|\{project-slug\}|\{YYYY-MM-DD\}|\{kebab-title\}|\{date\}|\{[nN]\}|\{1-line\}|\{ISO timestamp\}|\{repo_root\}|\{identity_email\}|\{stack\}|\{placeholder\}|\{placeholders\}|\{http_code\}' \
|
|
123
|
+
|| true)
|
|
124
|
+
if [[ -n "$matches" ]]; then
|
|
125
|
+
rel_path="${file#"$REPO_PATH"/}"
|
|
126
|
+
while IFS= read -r match; do
|
|
127
|
+
error "$rel_path:$match"
|
|
128
|
+
PLACEHOLDER_FOUND=$((PLACEHOLDER_FOUND + 1))
|
|
129
|
+
done <<< "$matches"
|
|
130
|
+
fi
|
|
131
|
+
done < <(find "$REPO_PATH/base" "$REPO_PATH/docs" "$REPO_PATH/scripts" -name "*.md" -o -name "*.sh" 2>/dev/null; find "$REPO_PATH/kits" -name "*.md" -o -name "*.sh" 2>/dev/null)
|
|
132
|
+
|
|
133
|
+
if [[ $PLACEHOLDER_FOUND -eq 0 ]]; then
|
|
134
|
+
ok "No unreplaced placeholders found"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# ─── 5. Rules registry consistency ───
|
|
138
|
+
echo ""
|
|
139
|
+
echo "Rules registry (CLAUDE.md references vs disk):"
|
|
140
|
+
if [[ -f "$REPO_PATH/base/CLAUDE.md" ]]; then
|
|
141
|
+
# Extract rule filenames from CLAUDE.md registry tables
|
|
142
|
+
rule_refs=$(grep -oE '~/.claude/rules/[a-zA-Z0-9_-]+\.md' "$REPO_PATH/base/CLAUDE.md" \
|
|
143
|
+
| sed 's|~/.claude/rules/||' | sort -u)
|
|
144
|
+
for rule_file in $rule_refs; do
|
|
145
|
+
if [[ -f "$REPO_PATH/base/rules/$rule_file" ]]; then
|
|
146
|
+
ok "rules/$rule_file exists"
|
|
147
|
+
else
|
|
148
|
+
error "CLAUDE.md references rules/$rule_file but file not found"
|
|
149
|
+
fi
|
|
150
|
+
done
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# ─── 6. Shell script syntax ───
|
|
154
|
+
echo ""
|
|
155
|
+
echo "Shell syntax (bash -n):"
|
|
156
|
+
while IFS= read -r script; do
|
|
157
|
+
[[ -f "$script" ]] || continue
|
|
158
|
+
fname="${script#"$REPO_PATH"/}"
|
|
159
|
+
if bash -n "$script" 2>/dev/null; then
|
|
160
|
+
ok "$fname"
|
|
161
|
+
else
|
|
162
|
+
error "$fname has syntax errors"
|
|
163
|
+
fi
|
|
164
|
+
done < <(find "$REPO_PATH" -maxdepth 1 -name "*.sh" 2>/dev/null; find "$REPO_PATH/scripts" -name "*.sh" 2>/dev/null; find "$REPO_PATH/kits" -name "*.sh" 2>/dev/null)
|
|
165
|
+
|
|
166
|
+
# ─── 7. Template content warnings ───
|
|
167
|
+
echo ""
|
|
168
|
+
echo "Template content warnings:"
|
|
169
|
+
for check_file in "$REPO_PATH/base/rules/git-workflow.md" "$REPO_PATH/base/rules/profile.md"; do
|
|
170
|
+
if [[ -f "$check_file" ]]; then
|
|
171
|
+
fname="${check_file#"$REPO_PATH"/}"
|
|
172
|
+
if grep -qiE "you@company\.com|Your Name" "$check_file"; then
|
|
173
|
+
warn "$fname contains uncustomized template values (you@company.com or Your Name)"
|
|
174
|
+
else
|
|
175
|
+
ok "$fname — no template placeholders"
|
|
176
|
+
fi
|
|
177
|
+
fi
|
|
178
|
+
done
|
|
179
|
+
|
|
180
|
+
# ─── Summary ───
|
|
181
|
+
echo ""
|
|
182
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
183
|
+
echo " Errors: $ERRORS"
|
|
184
|
+
echo " Warnings: $WARNINGS"
|
|
185
|
+
if [[ $ERRORS -gt 0 ]]; then
|
|
186
|
+
echo " FAILED — fix errors above"
|
|
187
|
+
exit 1
|
|
188
|
+
else
|
|
189
|
+
if [[ $WARNINGS -gt 0 ]]; then
|
|
190
|
+
echo " PASSED with warnings"
|
|
191
|
+
else
|
|
192
|
+
echo " PASSED — all clean"
|
|
193
|
+
fi
|
|
194
|
+
exit 0
|
|
195
|
+
fi
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ════════════════════════════════════════════════════════════════
|
|
5
|
+
# Praxis — MCP Server Onboarding
|
|
6
|
+
# Configures cross-cutting MCP servers for Claude Code.
|
|
7
|
+
# Can be sourced (install.sh) or run standalone.
|
|
8
|
+
#
|
|
9
|
+
# Servers:
|
|
10
|
+
# context7 — live library docs (free, no key)
|
|
11
|
+
# perplexity — AI web search (requires API key)
|
|
12
|
+
# github — repo operations (requires PAT)
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# bash scripts/onboard-mcp.sh [context7|perplexity|github|all]
|
|
16
|
+
# source scripts/onboard-mcp.sh # then call functions directly
|
|
17
|
+
# ════════════════════════════════════════════════════════════════
|
|
18
|
+
|
|
19
|
+
# ─── Constants (match install.sh when sourced) ───
|
|
20
|
+
CLAUDE_DIR="${CLAUDE_DIR:-$HOME/.claude}"
|
|
21
|
+
CONFIG_FILE="${CONFIG_FILE:-$CLAUDE_DIR/praxis.config.json}"
|
|
22
|
+
|
|
23
|
+
# ─── Colors (safe defaults if not already set) ───
|
|
24
|
+
RED="${RED:-\033[0;31m}"
|
|
25
|
+
GREEN="${GREEN:-\033[0;32m}"
|
|
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
|
|
41
|
+
|
|
42
|
+
# ═══════════════════════════════════════════
|
|
43
|
+
# Utilities
|
|
44
|
+
# ═══════════════════════════════════════════
|
|
45
|
+
|
|
46
|
+
mcp_server_exists() {
|
|
47
|
+
local name="$1"
|
|
48
|
+
claude mcp list 2>/dev/null | grep -q "$name"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
open_url() {
|
|
52
|
+
local url="$1"
|
|
53
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
54
|
+
open "$url" 2>/dev/null || echo " Open: $url"
|
|
55
|
+
elif command -v xdg-open &>/dev/null; then
|
|
56
|
+
xdg-open "$url" 2>/dev/null || echo " Open: $url"
|
|
57
|
+
else
|
|
58
|
+
echo " Open: $url"
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
update_mcp_status() {
|
|
63
|
+
local key="$1"
|
|
64
|
+
local val="$2"
|
|
65
|
+
if [[ -f "$CONFIG_FILE" ]] && command -v jq &>/dev/null; then
|
|
66
|
+
local tmp="$CONFIG_FILE.tmp"
|
|
67
|
+
jq --arg k "$key" --arg v "$val" '.[$k] = $v' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# ═══════════════════════════════════════════
|
|
72
|
+
# Server: Context7 — live library docs
|
|
73
|
+
# ═══════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
onboard_context7() {
|
|
76
|
+
echo -e " ${BOLD}Context7${NC} — live library/API documentation"
|
|
77
|
+
|
|
78
|
+
# Check if running as Claude Code plugin (settings.json)
|
|
79
|
+
local settings_file="$CLAUDE_DIR/settings.json"
|
|
80
|
+
if [[ -f "$settings_file" ]] && command -v jq &>/dev/null; then
|
|
81
|
+
if jq -e '.enabledPlugins // [] | map(select(contains("context7"))) | length > 0' "$settings_file" &>/dev/null; then
|
|
82
|
+
ok "Context7 already active as Claude Code plugin (skipping MCP)"
|
|
83
|
+
update_mcp_status "context7_status" "plugin"
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Check if already registered as MCP server
|
|
89
|
+
if mcp_server_exists "context7"; then
|
|
90
|
+
ok "Context7 MCP already registered"
|
|
91
|
+
update_mcp_status "context7_status" "configured"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Configure
|
|
96
|
+
echo " Registering Context7 MCP server..."
|
|
97
|
+
if claude mcp add context7 -s user -- npx -y @upstash/context7-mcp 2>/dev/null; then
|
|
98
|
+
# Verify
|
|
99
|
+
if mcp_server_exists "context7"; then
|
|
100
|
+
ok "Context7 MCP registered"
|
|
101
|
+
update_mcp_status "context7_status" "configured"
|
|
102
|
+
else
|
|
103
|
+
warn "Context7 registered but not showing in mcp list"
|
|
104
|
+
update_mcp_status "context7_status" "unverified"
|
|
105
|
+
fi
|
|
106
|
+
else
|
|
107
|
+
fail "Context7 registration failed"
|
|
108
|
+
echo " Run manually: claude mcp add context7 -s user -- npx -y @upstash/context7-mcp"
|
|
109
|
+
update_mcp_status "context7_status" "failed"
|
|
110
|
+
return 1
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ═══════════════════════════════════════════
|
|
115
|
+
# Server: Perplexity — AI web search
|
|
116
|
+
# ═══════════════════════════════════════════
|
|
117
|
+
|
|
118
|
+
onboard_perplexity() {
|
|
119
|
+
echo -e " ${BOLD}Perplexity${NC} — AI-powered web search"
|
|
120
|
+
|
|
121
|
+
# Check if already registered
|
|
122
|
+
if mcp_server_exists "perplexity"; then
|
|
123
|
+
echo -e " Perplexity MCP is already registered."
|
|
124
|
+
read -p " Reconfigure? [y/N] " RECONFIG
|
|
125
|
+
if [[ ! "${RECONFIG:-N}" =~ ^[Yy]$ ]]; then
|
|
126
|
+
ok "Perplexity MCP — keeping existing config"
|
|
127
|
+
return 0
|
|
128
|
+
fi
|
|
129
|
+
echo " Removing existing config..."
|
|
130
|
+
claude mcp remove perplexity 2>/dev/null || true
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# Acquire API key
|
|
134
|
+
echo ""
|
|
135
|
+
echo " You need a Perplexity API key (starts with pplx-)."
|
|
136
|
+
echo " Opening API settings page..."
|
|
137
|
+
open_url "https://www.perplexity.ai/settings/api"
|
|
138
|
+
echo ""
|
|
139
|
+
read -s -p " Paste your Perplexity API key: " PPLX_KEY
|
|
140
|
+
echo ""
|
|
141
|
+
|
|
142
|
+
if [[ -z "$PPLX_KEY" ]]; then
|
|
143
|
+
warn "No key entered — skipping Perplexity"
|
|
144
|
+
update_mcp_status "perplexity_status" "skipped"
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Validate prefix
|
|
149
|
+
if [[ ! "$PPLX_KEY" =~ ^pplx- ]]; then
|
|
150
|
+
warn "Key doesn't start with 'pplx-' — this may not be a valid Perplexity key"
|
|
151
|
+
read -p " Continue anyway? [y/N] " CONTINUE
|
|
152
|
+
if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
|
|
153
|
+
unset PPLX_KEY
|
|
154
|
+
update_mcp_status "perplexity_status" "skipped"
|
|
155
|
+
return 0
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# Verify key with API
|
|
160
|
+
echo " Verifying API key..."
|
|
161
|
+
local http_code
|
|
162
|
+
local curl_fmt='%{http_code}'
|
|
163
|
+
http_code=$(curl -s -o /dev/null -w "$curl_fmt" \
|
|
164
|
+
-X POST "https://api.perplexity.ai/chat/completions" \
|
|
165
|
+
-H "Authorization: Bearer $PPLX_KEY" \
|
|
166
|
+
-H "Content-Type: application/json" \
|
|
167
|
+
-d '{"model":"sonar","messages":[{"role":"user","content":"ping"}],"max_tokens":1}' \
|
|
168
|
+
2>/dev/null || echo "000")
|
|
169
|
+
|
|
170
|
+
if [[ "$http_code" == "200" ]]; then
|
|
171
|
+
ok "API key verified"
|
|
172
|
+
elif [[ "$http_code" == "000" ]]; then
|
|
173
|
+
warn "Could not reach Perplexity API (network issue?) — proceeding anyway"
|
|
174
|
+
else
|
|
175
|
+
warn "API returned HTTP $http_code — key may be invalid"
|
|
176
|
+
read -p " Continue with this key? [y/N] " CONTINUE
|
|
177
|
+
if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
|
|
178
|
+
unset PPLX_KEY
|
|
179
|
+
update_mcp_status "perplexity_status" "skipped"
|
|
180
|
+
return 0
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Configure
|
|
185
|
+
echo " Registering Perplexity MCP server..."
|
|
186
|
+
if claude mcp add perplexity -s user -e PERPLEXITY_API_KEY="$PPLX_KEY" -- npx -y @pplx/mcp-server 2>/dev/null; then
|
|
187
|
+
if mcp_server_exists "perplexity"; then
|
|
188
|
+
ok "Perplexity MCP registered"
|
|
189
|
+
update_mcp_status "perplexity_status" "configured"
|
|
190
|
+
else
|
|
191
|
+
warn "Perplexity registered but not showing in mcp list"
|
|
192
|
+
update_mcp_status "perplexity_status" "unverified"
|
|
193
|
+
fi
|
|
194
|
+
else
|
|
195
|
+
fail "Perplexity registration failed"
|
|
196
|
+
echo " Run manually: claude mcp add perplexity -s user -e PERPLEXITY_API_KEY=\"\$KEY\" -- npx -y @pplx/mcp-server"
|
|
197
|
+
update_mcp_status "perplexity_status" "failed"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# Clear key from memory
|
|
201
|
+
unset PPLX_KEY
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# ═══════════════════════════════════════════
|
|
205
|
+
# Server: GitHub — repo operations
|
|
206
|
+
# ═══════════════════════════════════════════
|
|
207
|
+
|
|
208
|
+
onboard_github() {
|
|
209
|
+
echo -e " ${BOLD}GitHub${NC} — repo operations, PRs, issues, code search"
|
|
210
|
+
|
|
211
|
+
# Check if already registered
|
|
212
|
+
if mcp_server_exists "github"; then
|
|
213
|
+
echo -e " GitHub MCP is already registered."
|
|
214
|
+
read -p " Reconfigure? [y/N] " RECONFIG
|
|
215
|
+
if [[ ! "${RECONFIG:-N}" =~ ^[Yy]$ ]]; then
|
|
216
|
+
ok "GitHub MCP — keeping existing config"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
echo " Removing existing config..."
|
|
220
|
+
claude mcp remove github 2>/dev/null || true
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# Acquire PAT
|
|
224
|
+
echo ""
|
|
225
|
+
echo " You need a GitHub Personal Access Token (starts with ghp_ or github_pat_)."
|
|
226
|
+
echo " Opening GitHub token settings..."
|
|
227
|
+
open_url "https://github.com/settings/tokens"
|
|
228
|
+
echo ""
|
|
229
|
+
read -s -p " Paste your GitHub PAT: " GH_TOKEN
|
|
230
|
+
echo ""
|
|
231
|
+
|
|
232
|
+
if [[ -z "$GH_TOKEN" ]]; then
|
|
233
|
+
warn "No token entered — skipping GitHub"
|
|
234
|
+
update_mcp_status "github_mcp_status" "skipped"
|
|
235
|
+
return 0
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Validate prefix
|
|
239
|
+
if [[ ! "$GH_TOKEN" =~ ^(ghp_|github_pat_) ]]; then
|
|
240
|
+
warn "Token doesn't start with 'ghp_' or 'github_pat_' — this may not be a valid PAT"
|
|
241
|
+
read -p " Continue anyway? [y/N] " CONTINUE
|
|
242
|
+
if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
|
|
243
|
+
unset GH_TOKEN
|
|
244
|
+
update_mcp_status "github_mcp_status" "skipped"
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Verify token
|
|
250
|
+
echo " Verifying GitHub token..."
|
|
251
|
+
local gh_user
|
|
252
|
+
gh_user=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \
|
|
253
|
+
"https://api.github.com/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || echo "")
|
|
254
|
+
|
|
255
|
+
if [[ -n "$gh_user" ]]; then
|
|
256
|
+
ok "Authenticated as: $gh_user"
|
|
257
|
+
else
|
|
258
|
+
warn "Could not verify token — proceeding anyway"
|
|
259
|
+
fi
|
|
260
|
+
|
|
261
|
+
# Configure
|
|
262
|
+
echo " Registering GitHub MCP server..."
|
|
263
|
+
if claude mcp add github -s user -e GITHUB_PERSONAL_ACCESS_TOKEN="$GH_TOKEN" -- npx -y @modelcontextprotocol/server-github 2>/dev/null; then
|
|
264
|
+
if mcp_server_exists "github"; then
|
|
265
|
+
ok "GitHub MCP registered"
|
|
266
|
+
update_mcp_status "github_mcp_status" "configured"
|
|
267
|
+
else
|
|
268
|
+
warn "GitHub registered but not showing in mcp list"
|
|
269
|
+
update_mcp_status "github_mcp_status" "unverified"
|
|
270
|
+
fi
|
|
271
|
+
else
|
|
272
|
+
fail "GitHub registration failed"
|
|
273
|
+
echo " Run manually: claude mcp add github -s user -e GITHUB_PERSONAL_ACCESS_TOKEN=\"\$TOKEN\" -- npx -y @modelcontextprotocol/server-github"
|
|
274
|
+
update_mcp_status "github_mcp_status" "failed"
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Clear token from memory
|
|
278
|
+
unset GH_TOKEN
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# ═══════════════════════════════════════════
|
|
282
|
+
# Entrypoint (standalone mode only)
|
|
283
|
+
# ═══════════════════════════════════════════
|
|
284
|
+
|
|
285
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
286
|
+
TARGET="${1:-all}"
|
|
287
|
+
|
|
288
|
+
echo ""
|
|
289
|
+
echo -e "${BOLD}Praxis — MCP Server Onboarding${NC}"
|
|
290
|
+
echo ""
|
|
291
|
+
|
|
292
|
+
if ! command -v claude &>/dev/null; then
|
|
293
|
+
fail "Claude Code CLI not found. Install it first: npm install -g @anthropic-ai/claude-code"
|
|
294
|
+
exit 1
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
case "$TARGET" in
|
|
298
|
+
context7)
|
|
299
|
+
onboard_context7
|
|
300
|
+
;;
|
|
301
|
+
perplexity)
|
|
302
|
+
onboard_perplexity
|
|
303
|
+
;;
|
|
304
|
+
github)
|
|
305
|
+
onboard_github
|
|
306
|
+
;;
|
|
307
|
+
all)
|
|
308
|
+
onboard_context7
|
|
309
|
+
echo ""
|
|
310
|
+
read -p " Set up Perplexity? [y/N] " DO_PPLX
|
|
311
|
+
[[ "${DO_PPLX:-N}" =~ ^[Yy]$ ]] && onboard_perplexity
|
|
312
|
+
echo ""
|
|
313
|
+
read -p " Set up GitHub MCP? [y/N] " DO_GH
|
|
314
|
+
[[ "${DO_GH:-N}" =~ ^[Yy]$ ]] && onboard_github
|
|
315
|
+
;;
|
|
316
|
+
*)
|
|
317
|
+
echo "Usage: bash scripts/onboard-mcp.sh [context7|perplexity|github|all]"
|
|
318
|
+
exit 1
|
|
319
|
+
;;
|
|
320
|
+
esac
|
|
321
|
+
|
|
322
|
+
echo ""
|
|
323
|
+
echo -e "${BOLD}Current MCP servers:${NC}"
|
|
324
|
+
claude mcp list 2>/dev/null || echo " (none)"
|
|
325
|
+
echo ""
|
|
326
|
+
fi
|