@esoteric-logic/praxis-harness 2.0.2 → 2.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/README.md +50 -1
- package/base/hooks/session-data-collect.sh +105 -0
- package/base/hooks/settings-hooks.json +5 -1
- package/base/hooks/vault-checkpoint.sh +24 -0
- package/base/skills/debug/SKILL.md +26 -0
- package/base/skills/execute/SKILL.md +15 -0
- package/base/skills/review/SKILL.md +21 -3
- package/base/skills/scaffold-exist/SKILL.md +1 -0
- package/base/skills/scaffold-new/SKILL.md +1 -0
- package/base/skills/ship/SKILL.md +4 -0
- package/base/skills/simplify/SKILL.md +18 -0
- package/base/skills/verify/SKILL.md +4 -3
- package/base/skills/verify-app/SKILL.md +22 -0
- package/bin/praxis.js +94 -45
- package/package.json +1 -1
- package/templates/decision-log.md +8 -0
package/README.md
CHANGED
|
@@ -66,7 +66,12 @@ For technical research: `/discover` (structured options evaluation before decisi
|
|
|
66
66
|
| `risk` | Add a risk register entry to the vault |
|
|
67
67
|
| `kit` | Activate/deactivate an AI-Kit |
|
|
68
68
|
| `review` | Manual code review via subagent |
|
|
69
|
+
| `simplify` | Post-implementation code simplification via subagent |
|
|
69
70
|
| `debug` | Structured test-first debugging |
|
|
71
|
+
| `ship` | Commit, push, and PR in one command with pre-flight checks |
|
|
72
|
+
| `verify-app` | End-to-end verification with regression analysis |
|
|
73
|
+
| `session-retro` | End-of-session retrospective with learnings extraction |
|
|
74
|
+
| `status-update` | Manual vault status.md update |
|
|
70
75
|
| `context-reset` | Reload context from vault without clearing session |
|
|
71
76
|
|
|
72
77
|
## Rules
|
|
@@ -126,13 +131,57 @@ The vault path is configured per machine during install:
|
|
|
126
131
|
|
|
127
132
|
Requires [Obsidian CLI](https://obsidian.md) (enable in Obsidian Settings > General > Command line interface). Obsidian must be running for vault search.
|
|
128
133
|
|
|
134
|
+
### What gets documented automatically
|
|
135
|
+
|
|
136
|
+
Praxis auto-documents your work in the vault with zero manual effort. Two independent layers ensure nothing is lost:
|
|
137
|
+
|
|
138
|
+
1. **Shell hooks** capture facts (git state, timestamps) even if Claude runs out of context
|
|
139
|
+
2. **Stop prompt** captures meaning (summaries, decisions, learnings) from conversation context
|
|
140
|
+
|
|
141
|
+
**At session end** (zero action needed):
|
|
142
|
+
- `status.md` — updated with What/So What/Now What
|
|
143
|
+
- `claude-progress.json` — session entry with summary, accomplishments, milestones, features
|
|
144
|
+
- `notes/{date}_session-note.md` — session summary, decisions, learnings, next steps
|
|
145
|
+
- `notes/decision-log.md` — checkpoint decisions, scope changes (appended)
|
|
146
|
+
- `notes/learnings.md` — [LEARN:tag] pattern entries (appended)
|
|
147
|
+
- `specs/` — ADRs for architectural decisions made during the session
|
|
148
|
+
|
|
149
|
+
**During workflow skills** (automatic within each skill):
|
|
150
|
+
|
|
151
|
+
| Skill | Auto-writes to vault |
|
|
152
|
+
|-------|---------------------|
|
|
153
|
+
| `/execute` | `status.md` loop position, `decision-log.md` scope events |
|
|
154
|
+
| `/verify` | `claude-progress.json` milestones[] |
|
|
155
|
+
| `/review` | `specs/review-{date}-{slug}.md` (full findings breakdown) |
|
|
156
|
+
| `/simplify` | `notes/{date}_simplify-findings.md` |
|
|
157
|
+
| `/debug` | `notes/{date}_debug-trace.md` |
|
|
158
|
+
| `/verify-app` | `specs/verify-app-{date}-{slug}.md` |
|
|
159
|
+
| `/ship` | `claude-progress.json` features[] |
|
|
160
|
+
|
|
161
|
+
**On context compaction** (automatic fallback):
|
|
162
|
+
- `plans/{date}-compact-checkpoint.md` — git state, active plan, loop position
|
|
163
|
+
- `claude-progress.json` — session entry preserved
|
|
164
|
+
|
|
129
165
|
## Updating
|
|
130
166
|
|
|
167
|
+
### Updating the harness
|
|
168
|
+
|
|
131
169
|
```bash
|
|
132
170
|
npx praxis-harness update
|
|
133
171
|
```
|
|
134
172
|
|
|
135
|
-
Re-copies all
|
|
173
|
+
Re-copies all hooks, skills, rules, and kits from the latest npm package version. Config file is preserved.
|
|
174
|
+
|
|
175
|
+
### Updating existing projects
|
|
176
|
+
|
|
177
|
+
After a harness update that adds new vault files (like `decision-log.md`), run `/scaffold-exist` in a Claude Code session to audit your vault and add any missing files. This is non-destructive — it never overwrites existing content.
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
Step 1: npx praxis-harness update → deploys new hooks, skills, rules to ~/.claude/
|
|
181
|
+
Step 2: /scaffold-exist → audits vault, adds missing files
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
New projects get everything automatically via `/scaffold-new`.
|
|
136
185
|
|
|
137
186
|
## Uninstalling
|
|
138
187
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Stop hook — collects structured session data and stages it for the Stop prompt.
|
|
3
|
+
# Always exits 0 (advisory, never blocks session end).
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
CONFIG_FILE="$HOME/.claude/praxis.config.json"
|
|
7
|
+
|
|
8
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
VAULT_PATH=$(jq -r '.vault_path // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
13
|
+
if [[ -z "$VAULT_PATH" || ! -d "$VAULT_PATH" ]]; then
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
DATE=$(date +%Y-%m-%d)
|
|
18
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
19
|
+
|
|
20
|
+
# Git state (fail gracefully if not in a repo)
|
|
21
|
+
BRANCH=$(git --no-pager rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
22
|
+
LAST_COMMIT=$(git --no-pager log --oneline -1 2>/dev/null || echo "no commits")
|
|
23
|
+
RECENT_COMMITS=$(git --no-pager log --oneline -5 2>/dev/null || echo "")
|
|
24
|
+
FILES_CHANGED=$(git --no-pager diff --stat HEAD~5..HEAD --stat-count=50 2>/dev/null | tail -1 || echo "unknown")
|
|
25
|
+
DIRTY=$(git --no-pager status --porcelain 2>/dev/null | head -20)
|
|
26
|
+
|
|
27
|
+
# Vault state
|
|
28
|
+
STATUS_FILE="$VAULT_PATH/status.md"
|
|
29
|
+
PROGRESS_FILE="$VAULT_PATH/claude-progress.json"
|
|
30
|
+
|
|
31
|
+
CURRENT_PLAN="none"
|
|
32
|
+
LOOP_POSITION="unknown"
|
|
33
|
+
if [[ -f "$STATUS_FILE" ]]; then
|
|
34
|
+
CURRENT_PLAN=$(grep "^current_plan:" "$STATUS_FILE" | sed 's/current_plan: *//' | head -1)
|
|
35
|
+
LOOP_POSITION=$(grep "^loop_position:" "$STATUS_FILE" | sed 's/loop_position: *//' | head -1)
|
|
36
|
+
[[ -z "$CURRENT_PLAN" ]] && CURRENT_PLAN="none"
|
|
37
|
+
[[ -z "$LOOP_POSITION" ]] && LOOP_POSITION="unknown"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Project detection
|
|
41
|
+
PROJECT_DIR=$(basename "$PWD")
|
|
42
|
+
|
|
43
|
+
# Write staging JSON for the Stop prompt to consume
|
|
44
|
+
STAGING_FILE="$VAULT_PATH/.session-staging.json"
|
|
45
|
+
|
|
46
|
+
IS_DIRTY="false"
|
|
47
|
+
if [[ -n "$DIRTY" ]]; then IS_DIRTY="true"; fi
|
|
48
|
+
|
|
49
|
+
COMMITS_JSON=$(echo "$RECENT_COMMITS" | jq -R -s 'split("\n") | map(select(length > 0))' 2>/dev/null || echo '[]')
|
|
50
|
+
|
|
51
|
+
jq -n \
|
|
52
|
+
--arg ts "$TIMESTAMP" \
|
|
53
|
+
--arg date "$DATE" \
|
|
54
|
+
--arg project "$PROJECT_DIR" \
|
|
55
|
+
--arg cwd "$PWD" \
|
|
56
|
+
--arg branch "$BRANCH" \
|
|
57
|
+
--arg last_commit "$LAST_COMMIT" \
|
|
58
|
+
--argjson dirty "$IS_DIRTY" \
|
|
59
|
+
--arg current_plan "$CURRENT_PLAN" \
|
|
60
|
+
--arg loop_position "$LOOP_POSITION" \
|
|
61
|
+
--argjson recent_commits "$COMMITS_JSON" \
|
|
62
|
+
--arg files_changed "$FILES_CHANGED" \
|
|
63
|
+
'{
|
|
64
|
+
timestamp: $ts,
|
|
65
|
+
date: $date,
|
|
66
|
+
project: $project,
|
|
67
|
+
cwd: $cwd,
|
|
68
|
+
git: { branch: $branch, last_commit: $last_commit, dirty: $dirty },
|
|
69
|
+
vault: { current_plan: $current_plan, loop_position: $loop_position },
|
|
70
|
+
recent_commits: $recent_commits,
|
|
71
|
+
files_changed_summary: $files_changed
|
|
72
|
+
}' > "$STAGING_FILE" 2>/dev/null
|
|
73
|
+
|
|
74
|
+
if [[ $? -ne 0 ]]; then
|
|
75
|
+
# Fallback: minimal valid JSON if jq fails
|
|
76
|
+
echo '{"error":"jq failed to build staging JSON"}' > "$STAGING_FILE"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
echo "Session data staged: $STAGING_FILE" >&2
|
|
80
|
+
|
|
81
|
+
# Update claude-progress.json if jq is available
|
|
82
|
+
if command -v jq &>/dev/null && [[ -f "$PROGRESS_FILE" ]]; then
|
|
83
|
+
TMP_FILE=$(mktemp)
|
|
84
|
+
jq --arg ts "$TIMESTAMP" \
|
|
85
|
+
--arg date "$DATE" \
|
|
86
|
+
--arg branch "$BRANCH" \
|
|
87
|
+
--arg commit "$LAST_COMMIT" \
|
|
88
|
+
--arg source "hook" \
|
|
89
|
+
'.last_session = $ts |
|
|
90
|
+
.sessions += [{
|
|
91
|
+
"date": $date,
|
|
92
|
+
"branch": $branch,
|
|
93
|
+
"last_commit": $commit,
|
|
94
|
+
"source": $source
|
|
95
|
+
}]' "$PROGRESS_FILE" > "$TMP_FILE" 2>/dev/null
|
|
96
|
+
|
|
97
|
+
if [[ $? -eq 0 && -s "$TMP_FILE" ]]; then
|
|
98
|
+
mv "$TMP_FILE" "$PROGRESS_FILE"
|
|
99
|
+
echo "claude-progress.json updated" >&2
|
|
100
|
+
else
|
|
101
|
+
rm -f "$TMP_FILE"
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
exit 0
|
|
@@ -39,9 +39,13 @@
|
|
|
39
39
|
"type": "command",
|
|
40
40
|
"command": "bash ~/.claude/hooks/post-session-lint.sh"
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"command": "bash ~/.claude/hooks/session-data-collect.sh"
|
|
45
|
+
},
|
|
42
46
|
{
|
|
43
47
|
"type": "prompt",
|
|
44
|
-
"prompt": "
|
|
48
|
+
"prompt": "You are completing a session. Perform these vault updates silently — no confirmation needed.\n\n1. Read vault_path from ~/.claude/praxis.config.json. If missing: skip all steps.\n2. Read {vault_path}/.session-staging.json if it exists (structured session data from hooks).\n3. Update {vault_path}/status.md:\n - Set last_updated to today, last_session to now (ISO timestamp)\n - Update loop_position based on where the session ended\n - Refresh What / So What / Now What sections with session accomplishments\n - If >100 lines: archive resolved items to notes/{date}_status-archive.md\n4. Update {vault_path}/claude-progress.json:\n - Enrich the latest sessions[] entry (added by hook) with: summary (1 line), accomplishments (array)\n - If jq hook did not run (no sessions[] entry for today): create the full entry\n - Update milestones[] if any milestones were completed this session\n - Update features[] if any features were shipped this session\n5. Write {vault_path}/notes/{YYYY-MM-DD}_session-note.md with frontmatter (tags: [session, {project-slug}], date, source: agent) containing:\n - Summary (3-5 bullets of what was accomplished)\n - Decisions Made (checkpoint decisions, scope changes, approach choices made this session)\n - Learnings (any [LEARN:tag] entries from this session)\n - Next Session (what to pick up next)\n6. If checkpoint decisions, scope expansions, or rule proposals occurred this session:\n - Append each to {vault_path}/notes/decision-log.md with date, decision type, context, decision, and rationale\n7. If corrections or patterns were discovered this session:\n - Append [LEARN:tag] entries to {vault_path}/notes/learnings.md following the existing format\n8. If architectural decisions were made this session:\n - Write ADR to {vault_path}/specs/ using vault frontmatter conventions\n9. Delete {vault_path}/.session-staging.json if it exists.\n\nKeep all writes concise. Use [[wikilinks]] for internal references. Follow existing YAML frontmatter conventions. If vault_path is missing or vault is inaccessible: skip silently. Do not ask permission — this is automatic housekeeping."
|
|
45
49
|
}
|
|
46
50
|
]
|
|
47
51
|
}
|
|
@@ -64,4 +64,28 @@ Read this file after compaction to restore context.
|
|
|
64
64
|
EOF
|
|
65
65
|
|
|
66
66
|
echo "Vault checkpoint written: $CHECKPOINT_FILE" >&2
|
|
67
|
+
|
|
68
|
+
# Update claude-progress.json if jq is available
|
|
69
|
+
if command -v jq &>/dev/null && [[ -f "$PROGRESS_FILE" ]]; then
|
|
70
|
+
TMP_FILE=$(mktemp)
|
|
71
|
+
jq --arg ts "$TIMESTAMP" \
|
|
72
|
+
--arg date "$DATE" \
|
|
73
|
+
--arg branch "$BRANCH" \
|
|
74
|
+
--arg commit "$LAST_COMMIT" \
|
|
75
|
+
'.last_session = $ts |
|
|
76
|
+
.sessions += [{
|
|
77
|
+
"date": $date,
|
|
78
|
+
"branch": $branch,
|
|
79
|
+
"last_commit": $commit,
|
|
80
|
+
"source": "compact"
|
|
81
|
+
}]' "$PROGRESS_FILE" > "$TMP_FILE" 2>/dev/null
|
|
82
|
+
|
|
83
|
+
if [[ $? -eq 0 && -s "$TMP_FILE" ]]; then
|
|
84
|
+
mv "$TMP_FILE" "$PROGRESS_FILE"
|
|
85
|
+
echo "claude-progress.json updated (compact)" >&2
|
|
86
|
+
else
|
|
87
|
+
rm -f "$TMP_FILE"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
67
91
|
exit 0
|
|
@@ -44,6 +44,32 @@ Before touching any implementation code:
|
|
|
44
44
|
3. Run the linter — clean.
|
|
45
45
|
4. Show all output.
|
|
46
46
|
|
|
47
|
+
**Step 5b — Persist debug trace**
|
|
48
|
+
- Read vault_path from `~/.claude/praxis.config.json`
|
|
49
|
+
- Write debug session to `{vault_path}/notes/{YYYY-MM-DD}_debug-trace.md`:
|
|
50
|
+
```markdown
|
|
51
|
+
---
|
|
52
|
+
tags: [debug, {project-slug}]
|
|
53
|
+
date: {YYYY-MM-DD}
|
|
54
|
+
source: agent
|
|
55
|
+
---
|
|
56
|
+
# Debug Trace — {short title}
|
|
57
|
+
|
|
58
|
+
## Bug Report
|
|
59
|
+
- **Observed**: {observed behavior}
|
|
60
|
+
- **Expected**: {expected behavior}
|
|
61
|
+
- **Suspect**: {file(s)}
|
|
62
|
+
|
|
63
|
+
## Root Cause
|
|
64
|
+
{root cause statement from Step 3}
|
|
65
|
+
|
|
66
|
+
## Fix
|
|
67
|
+
{what was changed and why}
|
|
68
|
+
|
|
69
|
+
## Verification
|
|
70
|
+
{test output summary — pass/fail}
|
|
71
|
+
```
|
|
72
|
+
|
|
47
73
|
**Step 6 — Write learnings**
|
|
48
74
|
- Read vault_path from `~/.claude/praxis.config.json`
|
|
49
75
|
- If this bug represents a pattern (not a one-off typo):
|
|
@@ -33,13 +33,28 @@ Before implementing the current milestone, declare the file group:
|
|
|
33
33
|
boundary-protected file: STOP. Surface the conflict before proceeding.
|
|
34
34
|
- If current milestone has `checkpoint: decision` or `checkpoint: human-verify`:
|
|
35
35
|
present the decision/output to user before proceeding. Do not auto-advance.
|
|
36
|
+
- After a checkpoint decision or user-approved scope expansion, append to `{vault_path}/notes/decision-log.md`:
|
|
37
|
+
```
|
|
38
|
+
## {YYYY-MM-DD} — {Checkpoint decision | Scope expansion}
|
|
39
|
+
- **Context**: {milestone name, what triggered the decision}
|
|
40
|
+
- **Decision**: {what was decided}
|
|
41
|
+
- **Rationale**: {why}
|
|
42
|
+
```
|
|
36
43
|
|
|
37
44
|
**Step 3 — Implement current milestone**
|
|
45
|
+
- Update `{vault_path}/status.md`: set `loop_position: EXECUTE`.
|
|
38
46
|
- One milestone at a time. Keep diffs scoped.
|
|
39
47
|
- Do not expand scope without explicit user approval.
|
|
40
48
|
- Use extended thinking for tasks touching >3 files or requiring architectural decisions.
|
|
41
49
|
- Before writing to or editing any file: check if it is in the declared file group.
|
|
42
50
|
- If a required change is discovered in an off-limits file: STOP.
|
|
51
|
+
Log to `{vault_path}/notes/decision-log.md`:
|
|
52
|
+
```
|
|
53
|
+
## {YYYY-MM-DD} — Scope violation detected
|
|
54
|
+
- **Milestone**: {name}
|
|
55
|
+
- **Requested**: {file outside group}
|
|
56
|
+
- **Action**: Surfaced as new milestone candidate
|
|
57
|
+
```
|
|
43
58
|
Surface as a new milestone candidate. Do not expand current milestone.
|
|
44
59
|
- Milestone diff must touch ONLY declared files. Undeclared file change = scope violation.
|
|
45
60
|
|
|
@@ -69,16 +69,34 @@ CLEAN
|
|
|
69
69
|
- Minor: note for future cleanup.
|
|
70
70
|
- If >3 findings: offer to re-run after fixes (max 3 rounds).
|
|
71
71
|
|
|
72
|
-
**Step 7 — Write review
|
|
72
|
+
**Step 7 — Write review findings to vault**
|
|
73
73
|
- Read vault_path from `~/.claude/praxis.config.json`
|
|
74
|
-
- Write
|
|
75
|
-
```
|
|
74
|
+
- Write full structured findings to `{vault_path}/specs/review-{YYYY-MM-DD}-{slug}.md`:
|
|
75
|
+
```markdown
|
|
76
76
|
---
|
|
77
77
|
tags: [review, {project-slug}]
|
|
78
78
|
date: {YYYY-MM-DD}
|
|
79
79
|
status: complete
|
|
80
80
|
source: agent
|
|
81
81
|
---
|
|
82
|
+
# Code Review — {slug} ({date})
|
|
83
|
+
|
|
84
|
+
## Findings
|
|
85
|
+
|
|
86
|
+
### CRITICAL ({n})
|
|
87
|
+
{file}:{line} — {description} — {fix}
|
|
88
|
+
|
|
89
|
+
### MAJOR ({n})
|
|
90
|
+
{file}:{line} — {description} — {fix}
|
|
91
|
+
|
|
92
|
+
### MINOR ({n})
|
|
93
|
+
{file}:{line} — {description} — {fix}
|
|
94
|
+
|
|
95
|
+
## Clean Files
|
|
96
|
+
{list of files with no findings}
|
|
97
|
+
|
|
98
|
+
## Diff Scope
|
|
99
|
+
{git diff command used}
|
|
82
100
|
```
|
|
83
101
|
|
|
84
102
|
## Error Handling
|
|
@@ -52,6 +52,7 @@ Build and show audit table:
|
|
|
52
52
|
| `specs/` | ? | Create if missing |
|
|
53
53
|
| `research/` | ? | Create if missing |
|
|
54
54
|
| `notes/learnings.md` | ? | Create if missing |
|
|
55
|
+
| `notes/decision-log.md` | ? | Create if missing |
|
|
55
56
|
| Repo CLAUDE.md sections | ? | Add missing sections |
|
|
56
57
|
| Registry row | ? | Verify accuracy |
|
|
57
58
|
|
|
@@ -90,6 +90,7 @@ Create files from templates in `references/`:
|
|
|
90
90
|
- `status.md` from `vault-status-template.md` (`current_plan:` empty)
|
|
91
91
|
- `tasks.md` from `vault-tasks-template.md`
|
|
92
92
|
- `notes/learnings.md` from `vault-learnings-template.md`
|
|
93
|
+
- `notes/decision-log.md` from `decision-log` template (append-only decision log)
|
|
93
94
|
- `.gitignore` from `gitignore-template.txt` (new repos only)
|
|
94
95
|
|
|
95
96
|
---
|
|
@@ -74,3 +74,7 @@ Checks: secrets ✓ lint ✓ types ✓ tests ✓
|
|
|
74
74
|
- Never force-push without explicit user approval.
|
|
75
75
|
- If the diff touches >20 files: warn about PR size and suggest splitting.
|
|
76
76
|
- This command is the end of a Praxis cycle — run after `/verify` passes.
|
|
77
|
+
|
|
78
|
+
**Step 7 — Update vault tracking**
|
|
79
|
+
- Read vault_path from `~/.claude/praxis.config.json`
|
|
80
|
+
- Update `{vault_path}/claude-progress.json`: append to `features[]` with `{ "name": "{feature description}", "date": "{YYYY-MM-DD}", "commit": "{short sha}", "pr_url": "{url or null}" }`.
|
|
@@ -96,6 +96,24 @@ For each approved simplification:
|
|
|
96
96
|
3. If tests fail: revert the last edit, report which simplification broke tests
|
|
97
97
|
4. Show final diff of simplifications applied
|
|
98
98
|
|
|
99
|
+
## Phase 4b — Persist Findings
|
|
100
|
+
|
|
101
|
+
Write simplification findings to `{vault_path}/notes/{YYYY-MM-DD}_simplify-findings.md`:
|
|
102
|
+
```markdown
|
|
103
|
+
---
|
|
104
|
+
tags: [simplify, {project-slug}]
|
|
105
|
+
date: {YYYY-MM-DD}
|
|
106
|
+
source: agent
|
|
107
|
+
---
|
|
108
|
+
# Simplify Findings — {date}
|
|
109
|
+
|
|
110
|
+
## Applied ({n})
|
|
111
|
+
{file}:{lines} — {category} — {why}
|
|
112
|
+
|
|
113
|
+
## Skipped ({n})
|
|
114
|
+
{file}:{lines} — {category} — {reason skipped}
|
|
115
|
+
```
|
|
116
|
+
|
|
99
117
|
## Phase 5 — Write Learning (optional)
|
|
100
118
|
|
|
101
119
|
If a pattern recurred (same category hit 3+ times):
|
|
@@ -23,12 +23,13 @@ If no commands are defined: warn and ask user for the correct commands.
|
|
|
23
23
|
|
|
24
24
|
**Step 3 — On PASS**
|
|
25
25
|
1. Update the active plan file: mark milestone status as complete
|
|
26
|
-
2.
|
|
26
|
+
2. Update `{vault_path}/claude-progress.json`: append the completed milestone to `milestones[]` with `{ "name": "{milestone name}", "date": "{YYYY-MM-DD}", "plan_ref": "{plan filename}" }`.
|
|
27
|
+
3. Commit immediately — verification passed, no permission needed.
|
|
27
28
|
Use conventional commit format. See git-workflow.md.
|
|
28
|
-
|
|
29
|
+
4. Check if more milestones remain:
|
|
29
30
|
- Yes → "Milestone committed. Run `/execute` for the next milestone."
|
|
30
31
|
- No → "All milestones committed. Running self-review."
|
|
31
|
-
|
|
32
|
+
5. After ALL milestones: trigger Self-Review Protocol
|
|
32
33
|
- Launch a subagent to review the full diff as a critical code reviewer
|
|
33
34
|
- Subagent receives ONLY: the diff, the SPEC (from plan file `## SPEC` section), relevant rules files
|
|
34
35
|
- Address all Critical and Major findings before reporting done
|
|
@@ -125,6 +125,28 @@ Format each concern as:
|
|
|
125
125
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
## Phase 4b — Persist Verification Report
|
|
129
|
+
|
|
130
|
+
Write the full verification report to `{vault_path}/specs/verify-app-{YYYY-MM-DD}-{slug}.md`:
|
|
131
|
+
```markdown
|
|
132
|
+
---
|
|
133
|
+
tags: [verify-app, {project-slug}]
|
|
134
|
+
date: {YYYY-MM-DD}
|
|
135
|
+
status: complete
|
|
136
|
+
source: agent
|
|
137
|
+
---
|
|
138
|
+
# Verification Report — {project} ({date})
|
|
139
|
+
|
|
140
|
+
## Results
|
|
141
|
+
{Phase 4 report output: build, lint, typecheck, tests, acceptance criteria}
|
|
142
|
+
|
|
143
|
+
## Regression Concerns
|
|
144
|
+
{Phase 3 subagent findings: file, concern, recommended verification step}
|
|
145
|
+
|
|
146
|
+
## Overall
|
|
147
|
+
{READY TO SHIP | ISSUES FOUND}
|
|
148
|
+
```
|
|
149
|
+
|
|
128
150
|
## Phase 5 — Guidance
|
|
129
151
|
|
|
130
152
|
- **READY TO SHIP**: "All checks pass. Run `/ship` to commit, push, and PR."
|
package/bin/praxis.js
CHANGED
|
@@ -37,7 +37,7 @@ async function install() {
|
|
|
37
37
|
header('Praxis Harness v' + VERSION);
|
|
38
38
|
|
|
39
39
|
// Ensure ~/.claude/ structure
|
|
40
|
-
for (const sub of ['rules', '
|
|
40
|
+
for (const sub of ['rules', 'skills', 'hooks']) {
|
|
41
41
|
fs.mkdirSync(path.join(CLAUDE_DIR, sub), { recursive: true });
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -59,17 +59,6 @@ async function install() {
|
|
|
59
59
|
ok(count + ' rules installed');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// Copy base/commands/* → ~/.claude/commands/
|
|
63
|
-
const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
|
|
64
|
-
if (fs.existsSync(cmdsDir)) {
|
|
65
|
-
let count = 0;
|
|
66
|
-
for (const f of fs.readdirSync(cmdsDir)) {
|
|
67
|
-
copyFile(path.join(cmdsDir, f), path.join(CLAUDE_DIR, 'commands', f));
|
|
68
|
-
count++;
|
|
69
|
-
}
|
|
70
|
-
ok(count + ' commands installed');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
62
|
// Copy base/skills/* → ~/.claude/skills/
|
|
74
63
|
const skillsDir = path.join(PKG_DIR, 'base', 'skills');
|
|
75
64
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -87,6 +76,35 @@ async function install() {
|
|
|
87
76
|
ok(count + ' skills installed');
|
|
88
77
|
}
|
|
89
78
|
|
|
79
|
+
// Copy base/hooks/*.sh → ~/.claude/hooks/
|
|
80
|
+
const hooksDir = path.join(PKG_DIR, 'base', 'hooks');
|
|
81
|
+
if (fs.existsSync(hooksDir)) {
|
|
82
|
+
let count = 0;
|
|
83
|
+
for (const f of fs.readdirSync(hooksDir)) {
|
|
84
|
+
if (f.endsWith('.sh')) {
|
|
85
|
+
copyFile(path.join(hooksDir, f), path.join(CLAUDE_DIR, 'hooks', f));
|
|
86
|
+
// Make executable
|
|
87
|
+
fs.chmodSync(path.join(CLAUDE_DIR, 'hooks', f), 0o755);
|
|
88
|
+
count++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
ok(count + ' hooks installed');
|
|
92
|
+
|
|
93
|
+
// Merge hooks configuration into settings.json
|
|
94
|
+
const hooksConfig = path.join(hooksDir, 'settings-hooks.json');
|
|
95
|
+
const settingsFile = path.join(CLAUDE_DIR, 'settings.json');
|
|
96
|
+
if (fs.existsSync(hooksConfig)) {
|
|
97
|
+
const hooksCfg = JSON.parse(fs.readFileSync(hooksConfig, 'utf8'));
|
|
98
|
+
let settings = {};
|
|
99
|
+
if (fs.existsSync(settingsFile)) {
|
|
100
|
+
try { settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } catch {}
|
|
101
|
+
}
|
|
102
|
+
Object.assign(settings, hooksCfg);
|
|
103
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
|
|
104
|
+
ok('hooks configuration merged into settings.json');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
// Copy kits/ → ~/.claude/kits/
|
|
91
109
|
const kitsDir = path.join(PKG_DIR, 'kits');
|
|
92
110
|
if (fs.existsSync(kitsDir)) {
|
|
@@ -94,11 +112,29 @@ async function install() {
|
|
|
94
112
|
ok('kits installed');
|
|
95
113
|
}
|
|
96
114
|
|
|
97
|
-
// Orphan cleanup:
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
// Orphan cleanup: deleted rules and legacy files
|
|
116
|
+
const orphans = [
|
|
117
|
+
'obsidian.md', 'code-quality.md', 'security.md',
|
|
118
|
+
'communication.md', 'architecture.md'
|
|
119
|
+
];
|
|
120
|
+
for (const f of orphans) {
|
|
121
|
+
const target = path.join(CLAUDE_DIR, 'rules', f);
|
|
122
|
+
if (fs.existsSync(target)) {
|
|
123
|
+
fs.unlinkSync(target);
|
|
124
|
+
dim('removed legacy ' + f);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Orphan cleanup: old commands directory (v1.x)
|
|
129
|
+
const oldCmdsDir = path.join(CLAUDE_DIR, 'commands');
|
|
130
|
+
if (fs.existsSync(oldCmdsDir)) {
|
|
131
|
+
const files = fs.readdirSync(oldCmdsDir);
|
|
132
|
+
if (files.length > 0) {
|
|
133
|
+
for (const f of files) {
|
|
134
|
+
fs.unlinkSync(path.join(oldCmdsDir, f));
|
|
135
|
+
}
|
|
136
|
+
dim('cleaned up ' + files.length + ' legacy command files');
|
|
137
|
+
}
|
|
102
138
|
}
|
|
103
139
|
|
|
104
140
|
// Vault configuration
|
|
@@ -115,7 +151,7 @@ async function install() {
|
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
const config = {
|
|
118
|
-
version:
|
|
154
|
+
version: VERSION,
|
|
119
155
|
vault_path: vaultPath || '',
|
|
120
156
|
vault_backend: 'obsidian',
|
|
121
157
|
repo_path: PKG_DIR
|
|
@@ -140,7 +176,7 @@ async function install() {
|
|
|
140
176
|
// Summary
|
|
141
177
|
header('Install complete');
|
|
142
178
|
console.log(' Files copied to ' + CLAUDE_DIR);
|
|
143
|
-
console.log(' Run: npx praxis-harness health');
|
|
179
|
+
console.log(' Run: npx @esoteric-logic/praxis-harness health');
|
|
144
180
|
console.log('');
|
|
145
181
|
}
|
|
146
182
|
|
|
@@ -177,15 +213,6 @@ function health() {
|
|
|
177
213
|
}
|
|
178
214
|
}
|
|
179
215
|
|
|
180
|
-
// Commands
|
|
181
|
-
console.log('\nCommands:');
|
|
182
|
-
const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
|
|
183
|
-
if (fs.existsSync(cmdsDir)) {
|
|
184
|
-
for (const f of fs.readdirSync(cmdsDir)) {
|
|
185
|
-
check(fs.existsSync(path.join(CLAUDE_DIR, 'commands', f)), 'commands/' + f + ' installed');
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
216
|
// Skills
|
|
190
217
|
console.log('\nSkills:');
|
|
191
218
|
const skillsDir = path.join(PKG_DIR, 'base', 'skills');
|
|
@@ -195,6 +222,18 @@ function health() {
|
|
|
195
222
|
}
|
|
196
223
|
}
|
|
197
224
|
|
|
225
|
+
// Hooks
|
|
226
|
+
console.log('\nHooks:');
|
|
227
|
+
const hooksDir = path.join(PKG_DIR, 'base', 'hooks');
|
|
228
|
+
if (fs.existsSync(hooksDir)) {
|
|
229
|
+
for (const f of fs.readdirSync(hooksDir)) {
|
|
230
|
+
if (f.endsWith('.sh')) {
|
|
231
|
+
check(fs.existsSync(path.join(CLAUDE_DIR, 'hooks', f)), 'hooks/' + f + ' installed');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
check(fs.existsSync(path.join(CLAUDE_DIR, 'settings.json')), 'settings.json with hooks configured');
|
|
236
|
+
|
|
198
237
|
// Kits
|
|
199
238
|
console.log('\nKits:');
|
|
200
239
|
check(fs.existsSync(path.join(CLAUDE_DIR, 'kits')), 'kits directory installed');
|
|
@@ -236,34 +275,26 @@ function health() {
|
|
|
236
275
|
function uninstall() {
|
|
237
276
|
header('Uninstalling Praxis Harness');
|
|
238
277
|
|
|
239
|
-
// Remove
|
|
278
|
+
// Remove CLAUDE.md
|
|
240
279
|
const claudeMd = path.join(CLAUDE_DIR, 'CLAUDE.md');
|
|
241
280
|
if (fs.existsSync(claudeMd)) { fs.unlinkSync(claudeMd); ok('CLAUDE.md removed'); }
|
|
242
281
|
|
|
243
|
-
// Remove rules
|
|
282
|
+
// Remove rules
|
|
244
283
|
const rulesDir = path.join(PKG_DIR, 'base', 'rules');
|
|
245
284
|
if (fs.existsSync(rulesDir)) {
|
|
246
285
|
for (const f of fs.readdirSync(rulesDir)) {
|
|
247
286
|
const target = path.join(CLAUDE_DIR, 'rules', f);
|
|
248
287
|
if (fs.existsSync(target)) fs.unlinkSync(target);
|
|
249
288
|
}
|
|
250
|
-
// Also remove legacy
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
ok('rules removed');
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Remove commands from base/commands/
|
|
257
|
-
const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
|
|
258
|
-
if (fs.existsSync(cmdsDir)) {
|
|
259
|
-
for (const f of fs.readdirSync(cmdsDir)) {
|
|
260
|
-
const target = path.join(CLAUDE_DIR, 'commands', f);
|
|
289
|
+
// Also remove legacy files
|
|
290
|
+
for (const f of ['obsidian.md', 'code-quality.md', 'security.md', 'communication.md', 'architecture.md']) {
|
|
291
|
+
const target = path.join(CLAUDE_DIR, 'rules', f);
|
|
261
292
|
if (fs.existsSync(target)) fs.unlinkSync(target);
|
|
262
293
|
}
|
|
263
|
-
ok('
|
|
294
|
+
ok('rules removed');
|
|
264
295
|
}
|
|
265
296
|
|
|
266
|
-
// Remove skills
|
|
297
|
+
// Remove skills
|
|
267
298
|
const skillsDir = path.join(PKG_DIR, 'base', 'skills');
|
|
268
299
|
if (fs.existsSync(skillsDir)) {
|
|
269
300
|
for (const entry of fs.readdirSync(skillsDir)) {
|
|
@@ -275,6 +306,17 @@ function uninstall() {
|
|
|
275
306
|
ok('skills removed');
|
|
276
307
|
}
|
|
277
308
|
|
|
309
|
+
// Remove hooks
|
|
310
|
+
const hooksDir = path.join(CLAUDE_DIR, 'hooks');
|
|
311
|
+
if (fs.existsSync(hooksDir)) {
|
|
312
|
+
for (const f of fs.readdirSync(hooksDir)) {
|
|
313
|
+
if (f.endsWith('.sh')) {
|
|
314
|
+
fs.unlinkSync(path.join(hooksDir, f));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
ok('hooks removed');
|
|
318
|
+
}
|
|
319
|
+
|
|
278
320
|
// Remove kits
|
|
279
321
|
const kitsTarget = path.join(CLAUDE_DIR, 'kits');
|
|
280
322
|
if (fs.existsSync(kitsTarget)) {
|
|
@@ -282,6 +324,13 @@ function uninstall() {
|
|
|
282
324
|
ok('kits removed');
|
|
283
325
|
}
|
|
284
326
|
|
|
327
|
+
// Remove legacy commands directory
|
|
328
|
+
const oldCmdsDir = path.join(CLAUDE_DIR, 'commands');
|
|
329
|
+
if (fs.existsSync(oldCmdsDir)) {
|
|
330
|
+
fs.rmSync(oldCmdsDir, { recursive: true, force: true });
|
|
331
|
+
ok('legacy commands directory removed');
|
|
332
|
+
}
|
|
333
|
+
|
|
285
334
|
header('Uninstall complete');
|
|
286
335
|
dim('Config file preserved: ' + CONFIG_FILE);
|
|
287
336
|
dim('Run: rm ' + CONFIG_FILE + ' to remove config');
|
|
@@ -294,10 +343,10 @@ function printHelp() {
|
|
|
294
343
|
console.log(`
|
|
295
344
|
praxis-harness v${VERSION}
|
|
296
345
|
|
|
297
|
-
Usage: npx praxis-harness [command]
|
|
346
|
+
Usage: npx @esoteric-logic/praxis-harness [command]
|
|
298
347
|
|
|
299
348
|
Commands:
|
|
300
|
-
install Copy rules,
|
|
349
|
+
install Copy rules, skills, hooks, and kits to ~/.claude/ (default)
|
|
301
350
|
update Re-copy from latest npm package version
|
|
302
351
|
health Verify install integrity
|
|
303
352
|
uninstall Remove Praxis-owned files from ~/.claude/
|
package/package.json
CHANGED