@esoteric-logic/praxis-harness 2.10.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/base/CLAUDE.md +37 -9
  2. package/base/hooks/auto-format.sh +1 -1
  3. package/base/hooks/dep-audit.sh +1 -1
  4. package/base/hooks/file-guard.sh +3 -3
  5. package/base/hooks/recursion-guard.sh +7 -1
  6. package/base/hooks/session-data-collect.sh +1 -1
  7. package/base/hooks/vault-checkpoint.sh +5 -5
  8. package/base/rules/api-quality.md +25 -0
  9. package/base/{skills → rules}/code-excellence.md +31 -2
  10. package/base/rules/coding.md +16 -0
  11. package/base/rules/css-quality.md +25 -0
  12. package/base/rules/go-quality.md +25 -0
  13. package/base/rules/java-quality.md +25 -0
  14. package/base/rules/observable-code.md +87 -0
  15. package/base/rules/python-quality.md +25 -0
  16. package/base/rules/refactor-triggers.md +59 -0
  17. package/base/rules/rust-quality.md +25 -0
  18. package/base/rules/shell-quality.md +26 -0
  19. package/base/rules/sql-quality.md +25 -0
  20. package/base/rules/typescript-quality.md +26 -0
  21. package/base/rules/writing-quality.md +122 -0
  22. package/base/skills/px-complexity-audit/SKILL.md +118 -0
  23. package/base/skills/px-discover/SKILL.md +4 -1
  24. package/base/skills/px-discuss/SKILL.md +4 -1
  25. package/base/skills/px-doc-lint/SKILL.md +107 -0
  26. package/base/skills/px-prose-review/SKILL.md +96 -0
  27. package/base/skills/px-quality-gate/SKILL.md +182 -0
  28. package/base/skills/px-risk/SKILL.md +4 -1
  29. package/base/skills/px-scaffold-new/SKILL.md +16 -14
  30. package/base/skills/px-session-retro/SKILL.md +1 -1
  31. package/base/skills/px-spec/SKILL.md +6 -2
  32. package/base/skills/px-verify/SKILL.md +2 -1
  33. package/bin/praxis.js +27 -6
  34. package/kits/api/install.sh +1 -1
  35. package/kits/api/teardown.sh +1 -1
  36. package/kits/code-quality/hooks/generate-baseline.sh +1 -1
  37. package/kits/code-quality/hooks/post-commit.sh +3 -2
  38. package/kits/code-quality/hooks/pre-push.sh +15 -15
  39. package/kits/code-quality/install.sh +1 -1
  40. package/kits/code-quality/teardown.sh +3 -3
  41. package/kits/data/install.sh +1 -1
  42. package/kits/data/teardown.sh +1 -1
  43. package/kits/infrastructure/install.sh +1 -1
  44. package/kits/infrastructure/teardown.sh +1 -1
  45. package/kits/security/install.sh +1 -1
  46. package/kits/security/teardown.sh +1 -1
  47. package/kits/web-designer/install.sh +1 -1
  48. package/kits/web-designer/teardown.sh +1 -1
  49. package/package.json +1 -1
  50. package/scripts/health-check.sh +21 -15
  51. package/scripts/install-tools.sh +5 -5
  52. package/scripts/lint-harness.sh +1 -1
  53. package/scripts/onboard-mcp.sh +1 -1
  54. package/scripts/test-harness.sh +1 -1
  55. package/scripts/update.sh +1 -1
  56. /package/base/{skills → rules}/engineering-judgment.md +0 -0
  57. /package/base/{skills → rules}/self-verify.md +0 -0
@@ -9,7 +9,10 @@ You are adding a risk register entry for the current project.
9
9
  **Step 1 — Detect project and existing risks**
10
10
  - Read vault_path from `~/.claude/praxis.config.json`
11
11
  - Detect project from CWD
12
- - Run: `obsidian search query="risk register {project-slug}" limit=3`
12
+ - Search vault for existing risks using configured backend:
13
+ - If `obsidian`: run `obsidian search query="risk register {project-slug}" limit=3`
14
+ - If `ripgrep`: run `rg --files-with-matches "risk" {vault_path}/specs/`
15
+ - If vault search fails: proceed without blocking
13
16
  - Check if `{vault_path}/specs/risk-register.md` exists
14
17
  - If found: read it to determine next sequential risk ID (R-01, R-02, etc.)
15
18
 
@@ -46,10 +46,10 @@ Read identity table from `~/.claude/rules/git-workflow.md`.
46
46
 
47
47
  ## Phase 1 — Gather Info
48
48
 
49
- Before asking, run vault duplicate check:
50
- ```bash
51
- obsidian search query="{slug}" path="01_Projects" limit=3
52
- ```
49
+ Before asking, run vault duplicate check using configured backend:
50
+ - If `obsidian`: `obsidian search query="{slug}" path="01_Projects" limit=3`
51
+ - If `ripgrep`: `rg --files-with-matches "{slug}" {vault_path}/`
52
+ - If vault search fails: warn and proceed without blocking.
53
53
 
54
54
  Ask in one message:
55
55
  - **Project name** — display name
@@ -71,7 +71,7 @@ today_date = current date YYYY-MM-DD
71
71
 
72
72
  ## Phase 2 — Scaffold repo CLAUDE.md
73
73
 
74
- 1. Read `references/repo-CLAUDE-md-template.md`
74
+ 1. Read `templates/project-index.md` (the repo CLAUDE.md template)
75
75
  2. Apply full substitution map. All placeholders must resolve.
76
76
  3. Scan output for remaining `{placeholder}` patterns. Resolve before writing.
77
77
  4. Write to `{repo_root}/CLAUDE.md`
@@ -85,19 +85,19 @@ Create directories:
85
85
  mkdir -p {vault_path}/plans {vault_path}/notes {vault_path}/specs {vault_path}/research
86
86
  ```
87
87
 
88
- Create files from templates in `references/`:
89
- - `_index.md` from `vault-index-template.md`
90
- - `status.md` from `vault-status-template.md` (`current_plan:` empty)
91
- - `tasks.md` from `vault-tasks-template.md`
92
- - `notes/learnings.md` from `vault-learnings-template.md`
93
- - `notes/decision-log.md` from `decision-log` template (append-only decision log)
94
- - `.gitignore` from `gitignore-template.txt` (new repos only)
88
+ Create files from templates in `templates/`:
89
+ - `_index.md` from `templates/_index.md`
90
+ - `status.md` from `templates/status.md` (`current_plan:` empty)
91
+ - `tasks.md` from `templates/tasks.md`
92
+ - `notes/learnings.md` scaffold minimal learnings file (no template exists; create with YAML frontmatter + empty `## Learnings` section)
93
+ - `notes/decision-log.md` from `templates/decision-log.md` (append-only decision log)
94
+ - `.gitignore` scaffold standard gitignore for detected stack (new repos only)
95
95
 
96
96
  ---
97
97
 
98
98
  ## Phase 3.5 — Scaffold claude-progress.json
99
99
 
100
- From `references/claude-progress-template.json`. Apply substitution map.
100
+ From `templates/claude-progress.json`. Apply substitution map.
101
101
  Write to `{vault_path}/claude-progress.json`.
102
102
 
103
103
  ---
@@ -105,7 +105,9 @@ Write to `{vault_path}/claude-progress.json`.
105
105
  ## Phase 4 — Vault Search Check
106
106
 
107
107
  Vault indexing is automatic for `obsidian` backend. No manual re-index needed.
108
- Verify the new project is searchable: `obsidian search query="{slug}" limit=1`
108
+ Verify the new project is searchable using configured backend:
109
+ - If `obsidian`: `obsidian search query="{slug}" limit=1`
110
+ - If `ripgrep`: `rg --files-with-matches "{slug}" {vault_path}/`
109
111
  On failure: warn, do not block.
110
112
 
111
113
  ---
@@ -48,7 +48,7 @@ Classify each: type, tag (`bugfix|convention|perf|security|tooling|arch|process`
48
48
  For each finding with clear root cause:
49
49
  - Project-specific → `{vault_path}/notes/learnings.md`
50
50
  - Global/harness pattern → harness project learnings.md
51
- - Check for duplicates via `obsidian search` before writing
51
+ - Check for duplicates via vault search (using configured backend) before writing. If vault search unavailable: skip duplicate check and proceed.
52
52
 
53
53
  Format:
54
54
  ```markdown
@@ -21,11 +21,15 @@ You are creating a spec for the current project.
21
21
  Ask the following in a single message:
22
22
  - What is this spec for? (one sentence)
23
23
  - Type: ADR (architecture decision) / RISK (risk register entry) / SPEC (technical spec)
24
- - Is there an existing related spec in `specs/`? (run `obsidian search query="{topic}" limit=3` first)
24
+ - Is there an existing related spec in `specs/`?
25
+ - Read `vault_backend` from `~/.claude/praxis.config.json`
26
+ - If `obsidian`: run `obsidian search query="{topic}" limit=3`
27
+ - If `ripgrep`: run `rg --files-with-matches "{topic}" {vault_path}/specs/`
28
+ - If vault search fails (e.g., Obsidian not running): warn "Vault search unavailable — skipping conflict check" and proceed without blocking
25
29
 
26
30
  **Step 2b — Cross-spec conflict check**
27
31
  After the vault search from Step 2, check for conflicts with accepted ADRs:
28
- - Run: `obsidian search query="{topic}" limit=10`
32
+ - Run vault search for topic (using backend detected above) with limit=10
29
33
  - For each result with `status: accepted` or `status: proposed`:
30
34
  - Compare the decision direction. Does the new spec contradict an accepted decision?
31
35
  - If conflict detected, present:
@@ -17,7 +17,8 @@ Execute in order, showing actual output (never assertions):
17
17
  - **DeepSource**: `deepsource issues list <file>` for files in diff (if deepsource CLI available)
18
18
  - If either tool finds HIGH/CRITICAL: treat as blocking — must fix before proceeding.
19
19
  - If tools not installed: skip with advisory note, do not block. DeepSource cloud validates on push.
20
- 6. **Functional check** ask: "Is there a smoke test, `terraform plan` output, or browser check needed for this milestone?" If yes: block until user confirms it passed. If no: proceed.
20
+ 6. **Quality gate** run `/px-quality-gate` on changed files BLOCK on naming/prose/structure violations. See `px-quality-gate` skill for full check list.
21
+ 7. **Functional check** — ask: "Is there a smoke test, `terraform plan` output, or browser check needed for this milestone?" If yes: block until user confirms it passed. If no: proceed.
21
22
 
22
23
  Read test/lint/build commands from the project CLAUDE.md `## Commands` section.
23
24
  If no commands are defined: warn and ask user for the correct commands.
package/bin/praxis.js CHANGED
@@ -19,7 +19,7 @@ function fail(msg) { console.error(' \x1b[31m\u2717\x1b[0m ' + msg); }
19
19
  function dim(msg) { console.log(' \x1b[2m' + msg + '\x1b[0m'); }
20
20
 
21
21
  function toolExists(name) {
22
- const r = spawnSync('which', [name], { stdio: 'pipe' });
22
+ const r = spawnSync('command', ['-v', name], { stdio: 'pipe', shell: true });
23
23
  return r.status === 0;
24
24
  }
25
25
 
@@ -94,12 +94,33 @@ async function install() {
94
94
  const hooksConfig = path.join(hooksDir, 'settings-hooks.json');
95
95
  const settingsFile = path.join(CLAUDE_DIR, 'settings.json');
96
96
  if (fs.existsSync(hooksConfig)) {
97
- const hooksCfg = JSON.parse(fs.readFileSync(hooksConfig, 'utf8'));
97
+ let hooksCfg;
98
+ try {
99
+ hooksCfg = JSON.parse(fs.readFileSync(hooksConfig, 'utf8'));
100
+ } catch (hookParseErr) {
101
+ fail('settings-hooks.json has invalid JSON: ' + hookParseErr.message);
102
+ hooksCfg = {};
103
+ }
98
104
  let settings = {};
99
105
  if (fs.existsSync(settingsFile)) {
100
- try { settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } catch { /* invalid JSON — use empty defaults */ }
106
+ const raw = fs.readFileSync(settingsFile, 'utf8');
107
+ try {
108
+ settings = JSON.parse(raw);
109
+ } catch (parseErr) {
110
+ // Preserve corrupt file as backup before overwriting
111
+ const backupPath = settingsFile + '.backup';
112
+ fs.writeFileSync(backupPath, raw);
113
+ fail('settings.json has invalid JSON — backed up to ' + backupPath);
114
+ }
115
+ }
116
+ // Deep merge: preserve existing keys, overlay hooks
117
+ for (const [key, value] of Object.entries(hooksCfg)) {
118
+ if (typeof value === 'object' && !Array.isArray(value) && settings[key] && typeof settings[key] === 'object') {
119
+ settings[key] = { ...settings[key], ...value };
120
+ } else {
121
+ settings[key] = value;
122
+ }
101
123
  }
102
- Object.assign(settings, hooksCfg);
103
124
  fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
104
125
  ok('hooks configuration merged into settings.json');
105
126
  }
@@ -261,7 +282,7 @@ function health() {
261
282
  } else {
262
283
  total++; fail('vault_path not set in config');
263
284
  }
264
- } catch { total++; fail('praxis.config.json is invalid JSON'); }
285
+ } catch (configErr) { total++; fail('praxis.config.json is invalid JSON: ' + configErr.message); }
265
286
  }
266
287
 
267
288
  // Tools
@@ -386,7 +407,7 @@ else if (arg === '--version' || arg === '-v') { console.log(VERSION); }
386
407
  else if (commands[arg]) {
387
408
  const result = commands[arg]();
388
409
  if (result && typeof result.catch === 'function') {
389
- result.catch(err => { fail(err.message); process.exit(1); });
410
+ result.catch(err => { fail(err?.message || String(err)); process.exit(1); });
390
411
  }
391
412
  }
392
413
  else { fail('Unknown command: ' + arg); printHelp(); process.exit(1); }
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Installing api kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Removing api kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  # Run once on kit install to snapshot existing violations
3
3
  # Pre-push hook only blocks on NET NEW violations above this baseline
4
4
 
@@ -1,4 +1,5 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
2
3
  # Triggers Claude AI self-review after commit
3
4
  # This is advisory — does not block, surfaces findings for next commit
4
5
 
@@ -11,7 +12,7 @@ if ! command -v claude &>/dev/null; then
11
12
  fi
12
13
 
13
14
  CHANGED=$(git diff --name-only HEAD~1...HEAD 2>/dev/null | head -20)
14
- if [ -z "$CHANGED" ]; then
15
+ if [[ -z "$CHANGED" ]]; then
15
16
  exit 0
16
17
  fi
17
18
 
@@ -1,5 +1,5 @@
1
- #!/bin/bash
2
- set -uo pipefail
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
3
 
4
4
  REPO_ROOT=$(git rev-parse --show-toplevel)
5
5
  KIT_DIR="$REPO_ROOT/.claude/kits/code-quality"
@@ -33,7 +33,7 @@ echo "------------------------"
33
33
 
34
34
  # Get changed files only (diff-scoped scanning)
35
35
  CHANGED=$(git diff --name-only origin/HEAD...HEAD 2>/dev/null || git diff --name-only HEAD~1...HEAD 2>/dev/null || echo "")
36
- if [ -z "$CHANGED" ]; then
36
+ if [[ -z "$CHANGED" ]]; then
37
37
  echo " No changed files detected — skipping gate"
38
38
  exit 0
39
39
  fi
@@ -46,7 +46,7 @@ GATE_FAILURES=()
46
46
  GATE_WARNINGS=()
47
47
 
48
48
  # -- SAST (OpenGrep) --
49
- if [ -s "$TMP/code-files.txt" ]; then
49
+ if [[ -s "$TMP/code-files.txt" ]]; then
50
50
  echo " SAST scan (OpenGrep)..."
51
51
  FILES=$(cat "$TMP/code-files.txt" | tr '\n' ' ')
52
52
  opengrep scan --config auto --json $FILES > "$TMP/sast.json" 2>/dev/null || true
@@ -54,8 +54,8 @@ if [ -s "$TMP/code-files.txt" ]; then
54
54
  CRITICAL=$(safe_jq '[.results[] | select(.extra.severity == "ERROR")] | length' "$TMP/sast.json")
55
55
  HIGH=$(safe_jq '[.results[] | select(.extra.severity == "WARNING")] | length' "$TMP/sast.json")
56
56
 
57
- [ "$CRITICAL" -gt 0 ] && GATE_FAILURES+=("SAST: $CRITICAL critical findings")
58
- [ "$HIGH" -gt 0 ] && GATE_WARNINGS+=("SAST: $HIGH high findings")
57
+ [[ "$CRITICAL" -gt 0 ]] && GATE_FAILURES+=("SAST: $CRITICAL critical findings")
58
+ [[ "$HIGH" -gt 0 ]] && GATE_WARNINGS+=("SAST: $HIGH high findings")
59
59
  echo " Critical: $CRITICAL High: $HIGH"
60
60
  fi &
61
61
  SAST_PID=$!
@@ -68,7 +68,7 @@ if [[ -s "$TMP/secrets.json" ]]; then
68
68
  else
69
69
  SECRETS=0
70
70
  fi
71
- [ "$SECRETS" -gt 0 ] && GATE_FAILURES+=("SECRETS: $SECRETS verified secrets found")
71
+ [[ "$SECRETS" -gt 0 ]] && GATE_FAILURES+=("SECRETS: $SECRETS verified secrets found")
72
72
  echo " Verified secrets: $SECRETS" &
73
73
  SECRETS_PID=$!
74
74
 
@@ -77,17 +77,17 @@ echo " Dependency scan (OSV-Scanner)..."
77
77
  osv-scanner scan --format json "$REPO_ROOT" > "$TMP/sca.json" 2>/dev/null || true
78
78
  SCA_CRITICAL=$(safe_jq '[.vulns[]? | select(.database_specific.severity? == "CRITICAL")] | length' "$TMP/sca.json")
79
79
  SCA_HIGH=$(safe_jq '[.vulns[]? | select(.database_specific.severity? == "HIGH")] | length' "$TMP/sca.json")
80
- [ "$SCA_CRITICAL" -gt 0 ] && GATE_FAILURES+=("SCA: $SCA_CRITICAL critical CVEs in dependencies")
81
- [ "$SCA_HIGH" -gt 0 ] && GATE_WARNINGS+=("SCA: $SCA_HIGH high CVEs in dependencies")
80
+ [[ "$SCA_CRITICAL" -gt 0 ]] && GATE_FAILURES+=("SCA: $SCA_CRITICAL critical CVEs in dependencies")
81
+ [[ "$SCA_HIGH" -gt 0 ]] && GATE_WARNINGS+=("SCA: $SCA_HIGH high CVEs in dependencies")
82
82
  echo " Critical CVEs: $SCA_CRITICAL High CVEs: $SCA_HIGH" &
83
83
  SCA_PID=$!
84
84
 
85
85
  # -- IaC (Checkov) --
86
- if [ -s "$TMP/iac-files.txt" ]; then
86
+ if [[ -s "$TMP/iac-files.txt" ]]; then
87
87
  echo " IaC scan (Checkov)..."
88
88
  checkov -d "$REPO_ROOT" --output json --quiet --compact 2>/dev/null > "$TMP/iac.json" || true
89
89
  IaC_FAIL=$(safe_jq '.results.failed_checks | length' "$TMP/iac.json")
90
- [ "$IaC_FAIL" -gt 0 ] && GATE_WARNINGS+=("IaC: $IaC_FAIL policy violations")
90
+ [[ "$IaC_FAIL" -gt 0 ]] && GATE_WARNINGS+=("IaC: $IaC_FAIL policy violations")
91
91
  echo " Policy violations: $IaC_FAIL"
92
92
  fi &
93
93
  IaC_PID=$!
@@ -97,10 +97,10 @@ wait $SAST_PID $SECRETS_PID $SCA_PID $IaC_PID 2>/dev/null || true
97
97
 
98
98
  # -- COVERAGE --
99
99
  COVERAGE_THRESHOLD=$(safe_jq '.coverage.line_min' "$CONFIG" 80)
100
- if [ -f "$REPO_ROOT/coverage/coverage-summary.json" ]; then
100
+ if [[ -f "$REPO_ROOT/coverage/coverage-summary.json" ]]; then
101
101
  COVERAGE=$(safe_jq '.total.lines.pct' "$REPO_ROOT/coverage/coverage-summary.json" 100)
102
102
  BELOW=$(echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l 2>/dev/null || echo 0)
103
- [ "$BELOW" = "1" ] && GATE_WARNINGS+=("COVERAGE: ${COVERAGE}% below threshold ${COVERAGE_THRESHOLD}%")
103
+ [[ "$BELOW" == "1" ]] && GATE_WARNINGS+=("COVERAGE: ${COVERAGE}% below threshold ${COVERAGE_THRESHOLD}%")
104
104
  echo " Coverage: ${COVERAGE}% (threshold: ${COVERAGE_THRESHOLD}%)"
105
105
  fi
106
106
 
@@ -108,12 +108,12 @@ fi
108
108
  echo ""
109
109
  echo "------------------------"
110
110
 
111
- if [ ${#GATE_WARNINGS[@]} -gt 0 ]; then
111
+ if [[ ${#GATE_WARNINGS[@]} -gt 0 ]]; then
112
112
  echo "Warnings:"
113
113
  for w in "${GATE_WARNINGS[@]}"; do echo " $w"; done
114
114
  fi
115
115
 
116
- if [ ${#GATE_FAILURES[@]} -gt 0 ]; then
116
+ if [[ ${#GATE_FAILURES[@]} -gt 0 ]]; then
117
117
  echo "Gate BLOCKED:"
118
118
  for f in "${GATE_FAILURES[@]}"; do echo " $f"; done
119
119
  echo ""
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "Installing code-quality kit dependencies..."
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "Removing code-quality kit hooks..."
@@ -6,8 +6,8 @@ echo "Removing code-quality kit hooks..."
6
6
  REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
7
7
  HOOKS_DIR="$REPO_ROOT/.git/hooks"
8
8
 
9
- [ -f "$HOOKS_DIR/pre-push" ] && rm "$HOOKS_DIR/pre-push" && echo " pre-push hook removed"
10
- [ -f "$HOOKS_DIR/post-commit" ] && rm "$HOOKS_DIR/post-commit" && echo " post-commit hook removed"
9
+ [[ -f "$HOOKS_DIR/pre-push" ]] && rm "$HOOKS_DIR/pre-push" && echo " pre-push hook removed"
10
+ [[ -f "$HOOKS_DIR/post-commit" ]] && rm "$HOOKS_DIR/post-commit" && echo " post-commit hook removed"
11
11
 
12
12
  echo ""
13
13
  echo "Note: CLI tools (opengrep, trufflehog, osv-scanner, checkov) are system-level"
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Installing data kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Removing data kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Installing infrastructure kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Removing infrastructure kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Installing security kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Removing security kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Installing web-designer kit ==="
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  echo "=== Praxis: Removing web-designer kit dependencies ==="
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esoteric-logic/praxis-harness",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Layered Claude Code harness — workflow discipline, AI-Kits, persistent vault integration",
5
5
  "bin": {
6
6
  "praxis-harness": "./bin/praxis.js"
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  # ════════════════════════════════════════════════════════════════
@@ -16,23 +16,29 @@ FAIL=0
16
16
  TOTAL=0
17
17
 
18
18
  check() {
19
+ local label="$2"
19
20
  TOTAL=$((TOTAL + 1))
20
- if eval "$1" 2>/dev/null; then
21
- echo " ✓ $2"
21
+ if "$@" 2>/dev/null; then
22
+ echo " ✓ $label"
22
23
  PASS=$((PASS + 1))
23
24
  else
24
- echo " ✗ $2"
25
+ echo " ✗ $label"
25
26
  FAIL=$((FAIL + 1))
26
27
  fi
27
28
  }
28
29
 
30
+ check_exists() { [[ -e "$1" ]]; }
31
+ check_file() { [[ -f "$1" ]]; }
32
+ check_dir() { [[ -d "$1" ]]; }
33
+ check_cmd() { command -v "$1" &>/dev/null; }
34
+
29
35
  echo "Praxis Health Check"
30
36
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
31
37
 
32
38
  # ─── CLAUDE.md symlink ───
33
39
  echo ""
34
40
  echo "Core:"
35
- check "[[ -e '$CLAUDE_DIR/CLAUDE.md' ]]" "CLAUDE.md installed"
41
+ check check_exists "$CLAUDE_DIR/CLAUDE.md" "CLAUDE.md installed"
36
42
 
37
43
  # ─── Rules symlinks ───
38
44
  echo ""
@@ -41,7 +47,7 @@ if [[ -d "$PRAXIS_DIR/base/rules" ]]; then
41
47
  for rule in "$PRAXIS_DIR"/base/rules/*.md; do
42
48
  [[ -f "$rule" ]] || continue
43
49
  fname=$(basename "$rule")
44
- check "[[ -e '$CLAUDE_DIR/rules/$fname' ]]" "rules/$fname installed"
50
+ check check_exists "$CLAUDE_DIR/rules/$fname" "rules/$fname installed"
45
51
  done
46
52
  fi
47
53
 
@@ -52,7 +58,7 @@ if [[ -d "$PRAXIS_DIR/base/commands" ]]; then
52
58
  for cmd in "$PRAXIS_DIR"/base/commands/*.md; do
53
59
  [[ -f "$cmd" ]] || continue
54
60
  fname=$(basename "$cmd")
55
- check "[[ -e '$CLAUDE_DIR/commands/$fname' ]]" "commands/$fname installed"
61
+ check check_exists "$CLAUDE_DIR/commands/$fname" "commands/$fname installed"
56
62
  done
57
63
  fi
58
64
 
@@ -63,24 +69,24 @@ if [[ -d "$PRAXIS_DIR/base/skills" ]]; then
63
69
  for skill_dir in "$PRAXIS_DIR"/base/skills/*/; do
64
70
  [[ -d "$skill_dir" ]] || continue
65
71
  skill_name=$(basename "$skill_dir")
66
- check "[[ -e '$CLAUDE_DIR/skills/$skill_name' ]]" "skills/$skill_name installed"
72
+ check check_exists "$CLAUDE_DIR/skills/$skill_name" "skills/$skill_name installed"
67
73
  done
68
74
  fi
69
75
 
70
76
  # ─── Kits symlink ───
71
77
  echo ""
72
78
  echo "Kits:"
73
- check "[[ -e '$CLAUDE_DIR/kits' ]]" "kits directory installed"
79
+ check check_exists "$CLAUDE_DIR/kits" "kits directory installed"
74
80
 
75
81
  # ─── Config ───
76
82
  echo ""
77
83
  echo "Config:"
78
- check "[[ -f '$CONFIG_FILE' ]]" "praxis.config.json exists"
84
+ check check_file "$CONFIG_FILE" "praxis.config.json exists"
79
85
 
80
86
  if [[ -f "$CONFIG_FILE" ]]; then
81
87
  VAULT_PATH=$(jq -r '.vault_path // empty' "$CONFIG_FILE" 2>/dev/null)
82
88
  if [[ -n "$VAULT_PATH" ]]; then
83
- check "[[ -d '$VAULT_PATH' ]]" "vault_path ($VAULT_PATH) is a real directory"
89
+ check check_dir "$VAULT_PATH" "vault_path ($VAULT_PATH) is a real directory"
84
90
  else
85
91
  TOTAL=$((TOTAL + 1))
86
92
  echo " ✗ vault_path not set in config"
@@ -91,10 +97,10 @@ fi
91
97
  # ─── Required tools ───
92
98
  echo ""
93
99
  echo "Tools:"
94
- check "command -v obsidian" "Obsidian CLI available"
95
- check "command -v node" "node available"
96
- check "command -v claude" "claude available"
97
- check "command -v jq" "jq available"
100
+ check check_cmd obsidian "Obsidian CLI available"
101
+ check check_cmd node "node available"
102
+ check check_cmd claude "claude available"
103
+ check check_cmd jq "jq available"
98
104
 
99
105
  # ─── MCP Servers (warn only) ───
100
106
  echo ""
@@ -17,7 +17,7 @@ for arg in "$@"; do
17
17
  done
18
18
 
19
19
  # Auto-detect if no flags
20
- if [ $# -eq 0 ]; then
20
+ if [[ $# -eq 0 ]]; then
21
21
  command -v go &>/dev/null && INSTALL_GO=true
22
22
  command -v terraform &>/dev/null && INSTALL_TF=true
23
23
  fi
@@ -43,7 +43,7 @@ install_go() { go install "$@" 2>/dev/null || true; }
43
43
 
44
44
  # ── Core (always) ──
45
45
  echo "── Installing core tools ──"
46
- if [ "$PKG" = "brew" ]; then
46
+ if [[ "$PKG" == "brew" ]]; then
47
47
  install_brew shellcheck shfmt jq gitleaks
48
48
  else
49
49
  sudo apt-get update -qq
@@ -60,7 +60,7 @@ if $INSTALL_GO; then
60
60
  echo "── Installing Go quality tools ──"
61
61
  install_go golang.org/x/tools/cmd/goimports@latest
62
62
  install_go golang.org/x/vuln/cmd/govulncheck@latest
63
- if [ "$PKG" = "brew" ]; then
63
+ if [[ "$PKG" == "brew" ]]; then
64
64
  install_brew golangci-lint
65
65
  else
66
66
  curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" 2>/dev/null || true
@@ -71,7 +71,7 @@ fi
71
71
  if $INSTALL_TF; then
72
72
  echo ""
73
73
  echo "── Installing Terraform quality tools ──"
74
- if [ "$PKG" = "brew" ]; then
74
+ if [[ "$PKG" == "brew" ]]; then
75
75
  install_brew tflint trivy infracost
76
76
  else
77
77
  echo "NOTE: Install tflint, trivy, infracost manually for Linux"
@@ -82,7 +82,7 @@ fi
82
82
  if command -v docker &>/dev/null; then
83
83
  echo ""
84
84
  echo "── Installing container tools ──"
85
- if [ "$PKG" = "brew" ]; then
85
+ if [[ "$PKG" == "brew" ]]; then
86
86
  install_brew hadolint
87
87
  fi
88
88
  fi
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  # ════════════════════════════════════════════════════════════════
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  # ════════════════════════════════════════════════════════════════
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  # ════════════════════════════════════════════════════════════════
package/scripts/update.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  # ════════════════════════════════════════════════════════════════
File without changes
File without changes