claude-prism 1.2.7 → 1.4.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/.claude-plugin/plugin.json +10 -0
- package/CHANGELOG.md +57 -1
- package/README.md +79 -19
- package/bin/cli.mjs +1 -1
- package/hooks/plan-enforcement.mjs +7 -2
- package/hooks/precompact-handler.mjs +44 -0
- package/hooks/session-end-handler.mjs +57 -0
- package/hooks/subagent-scope-injector.mjs +53 -0
- package/hooks/task-plan-sync.mjs +143 -0
- package/lib/config.mjs +8 -3
- package/lib/handoff.mjs +204 -0
- package/lib/installer.mjs +137 -34
- package/lib/messages.mjs +5 -1
- package/lib/webhook.mjs +57 -0
- package/package.json +4 -1
- package/plugin-hooks.json +82 -0
- package/scripts/post-tool.mjs +7 -0
- package/scripts/pre-tool.mjs +9 -0
- package/scripts/precompact.mjs +20 -0
- package/scripts/session-end.mjs +19 -0
- package/scripts/subagent-start.mjs +19 -0
- package/scripts/task-completed.mjs +19 -0
- package/templates/commands/claude-prism/checkpoint.md +1 -1
- package/templates/commands/claude-prism/doctor.md +2 -2
- package/templates/commands/claude-prism/help.md +1 -1
- package/templates/commands/claude-prism/plan.md +3 -3
- package/templates/commands/claude-prism/prism.md +3 -3
- package/templates/commands/claude-prism/stats.md +2 -2
- package/templates/rules.md +10 -3
- package/templates/runners/precompact.mjs +19 -0
- package/templates/runners/session-end.mjs +19 -0
- package/templates/runners/subagent-start.mjs +19 -0
- package/templates/runners/task-completed.mjs +19 -0
- package/templates/settings.json +44 -0
- package/templates/skills/prism/SKILL.md +3 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-prism",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "EUDEC methodology framework for AI coding agents",
|
|
5
|
+
"author": { "name": "lazysaturday91" },
|
|
6
|
+
"repository": "https://github.com/lazysaturday91/claude-prism",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"skills": "./templates/skills/",
|
|
9
|
+
"hooks": "./plugin-hooks.json"
|
|
10
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.0] — 2026-03-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **4 new hook events** — PreCompact, SessionEnd, SubagentStart, TaskCompleted
|
|
12
|
+
- `precompact-handler` — auto-generates `docs/HANDOFF.md` before context compaction
|
|
13
|
+
- `session-end-handler` — saves HANDOFF + appends session summary to `docs/PROJECT-MEMORY.md`
|
|
14
|
+
- `subagent-scope-injector` — injects current plan batch context into subagent via `additionalContext`
|
|
15
|
+
- `task-plan-sync` — auto-updates plan file checkboxes on task completion (fuzzy keyword match)
|
|
16
|
+
- **Native Claude Code plugin** — `.claude-plugin/plugin.json` + `plugin-hooks.json`
|
|
17
|
+
- `claude plugin install claude-prism` for plugin mode
|
|
18
|
+
- `prism init` (CLI mode) remains for CLAUDE.md injection
|
|
19
|
+
- 6 plugin script runners in `scripts/`
|
|
20
|
+
- **HTTP webhook dispatcher** (`lib/webhook.mjs`) — non-blocking fire-and-forget notifications
|
|
21
|
+
- Configure via `.prism/config.json` `webhooks` array
|
|
22
|
+
- Events: `compaction`, `session-end`, `batch-complete`
|
|
23
|
+
- **HANDOFF.md generator** (`lib/handoff.mjs`) — shared logic for auto-generating session handoff documents
|
|
24
|
+
- Plan progress parsing (checkbox counting, batch detection)
|
|
25
|
+
- Git status integration (branch, uncommitted, recent commits)
|
|
26
|
+
- **Checkpoint integration** in EUDEC rules — `Esc+Esc` / `/rewind` references in EXECUTE protocol
|
|
27
|
+
- 30 new tests covering all new handlers, utilities, plugin structure, and installer paths
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- `templates/settings.json` — 6 events (was 2)
|
|
31
|
+
- `lib/installer.mjs` — installs 6 runners, 7 rules, 8 libs (was 2/3/6)
|
|
32
|
+
- `lib/config.mjs` — defaults include `webhooks` and 4 new hook configs
|
|
33
|
+
- `lib/messages.mjs` — 4 new message templates
|
|
34
|
+
- `package.json` `files` includes `scripts/`, `.claude-plugin/`, `plugin-hooks.json`
|
|
35
|
+
|
|
36
|
+
## [1.3.0] — 2026-03-03
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- **`.prism/` brand directory** — unified home for all Prism project files
|
|
40
|
+
- `.prism/config.json` — hook configuration (committed to git, visible on GitHub)
|
|
41
|
+
- `.prism/.version` — installed version (gitignored via `.prism/.gitignore`)
|
|
42
|
+
- `.prism/plans/` — plan files (committed)
|
|
43
|
+
- **3-stage migration chain** in `prism update`:
|
|
44
|
+
- `.prism.json` → `.claude-prism.json` → `.prism/config.json`
|
|
45
|
+
- `.claude/.prism-version` → `.prism/.version`
|
|
46
|
+
- `docs/plans/` → `.prism/plans/` (moves files, cleans empty dirs)
|
|
47
|
+
- **Backward-compatible fallback** in plan-enforcement hook (`docs/plans/` still checked)
|
|
48
|
+
- Migration tests (4 new: config, version, plans, `.claude-prism.json` detection)
|
|
49
|
+
- `.prism/.gitignore` auto-creation (ignores `.version`)
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- Config path: `.claude-prism.json` → `.prism/config.json`
|
|
53
|
+
- Version path: `.claude/.prism-version` → `.prism/.version`
|
|
54
|
+
- Plans path: `docs/plans/` → `.prism/plans/`
|
|
55
|
+
- `.claude-prism.json` removed from `.gitignore` (config is now committed via `.prism/`)
|
|
56
|
+
- All templates, commands, and docs updated to reference new paths
|
|
57
|
+
- `prism doctor` now detects legacy `.claude-prism.json` as a migration target
|
|
58
|
+
- `prism stats` reads plans from `.prism/plans/` with `docs/plans/` fallback
|
|
59
|
+
- `prism uninstall` cleans both new and legacy paths
|
|
60
|
+
|
|
61
|
+
### Migration
|
|
62
|
+
Existing users: run `prism update` — all files are automatically migrated. No manual steps needed. The `docs/plans/` fallback ensures hooks work even without migration.
|
|
63
|
+
|
|
8
64
|
## [1.2.6] — 2026-02-28
|
|
9
65
|
|
|
10
66
|
### Fixed
|
|
@@ -56,7 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
56
112
|
- session.mjs included in installed lib files
|
|
57
113
|
- CHANGELOG.md (this file)
|
|
58
114
|
- GitHub Actions CI workflow (test on push/PR)
|
|
59
|
-
- Config schema versioning (
|
|
115
|
+
- Config schema versioning (config version field)
|
|
60
116
|
- Verification Fallback Ladder (7-level, from automated tests to manual diff)
|
|
61
117
|
- Quality Gates between DECOMPOSE→EXECUTE and EXECUTE→CHECKPOINT
|
|
62
118
|
- Goal Recitation mechanism at batch boundaries
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
**EUDEC methodology framework for AI coding agents.**
|
|
20
20
|
|
|
21
|
-
Installs the EUDEC methodology — **Essence, Understand, Decompose, Execute, Checkpoint** — directly into your project's Claude Code environment. Includes a session transition protocol (Handoff) that bookends the core cycle.
|
|
21
|
+
Installs the EUDEC methodology — **Essence, Understand, Decompose, Execute, Checkpoint** — directly into your project's Claude Code environment. Includes a session transition protocol (Handoff) that bookends the core cycle. Seven hooks enforce the methodology and automate session management.
|
|
22
22
|
|
|
23
23
|
## The Problem
|
|
24
24
|
|
|
@@ -79,23 +79,37 @@ Injected into `CLAUDE.md`, EUDEC is a behavioral framework that corrects how AI
|
|
|
79
79
|
|
|
80
80
|
**Quality gates** between phases prevent executing on broken baselines.
|
|
81
81
|
|
|
82
|
-
**New in v1.
|
|
82
|
+
**New in v1.4.0:**
|
|
83
|
+
- **Native Claude Code plugin** — `claude plugin install claude-prism` for zero-config setup
|
|
84
|
+
- **4 new hook events** — PreCompact (auto-HANDOFF), SessionEnd (session protection), SubagentStart (scope injection), TaskCompleted (plan auto-update)
|
|
85
|
+
- **HTTP webhooks** — fire-and-forget notifications on compaction, session-end, batch-complete
|
|
86
|
+
- **Checkpoint integration** — `Esc+Esc` / `/rewind` references complement Git-as-Memory
|
|
87
|
+
|
|
88
|
+
**v1.3.0:**
|
|
89
|
+
- `.prism/` brand directory — config, version, and plans live under `.prism/`
|
|
90
|
+
- Automatic 3-stage migration from legacy paths
|
|
91
|
+
|
|
92
|
+
**v1.2.5:**
|
|
83
93
|
- **Analysis-only branch**: When no code change is needed, UNDERSTAND reports findings without entering DECOMPOSE/EXECUTE/CHECKPOINT
|
|
84
94
|
- **Git-as-Memory**: Commits after each batch as rollback points; `git diff` summaries maintain context in long sessions
|
|
85
95
|
- **Verification scoping**: Build check output filtered to changed files only — pre-existing errors are ignored
|
|
86
96
|
- **Agent failure recovery**: 3-step protocol when delegated agents produce incomplete results
|
|
87
97
|
|
|
88
|
-
### 2.
|
|
98
|
+
### 2. Seven Focused Hooks
|
|
89
99
|
|
|
90
|
-
Hooks enforce the methodology at critical points
|
|
100
|
+
Hooks enforce the methodology at critical points:
|
|
91
101
|
|
|
92
|
-
| Hook | What It Does |
|
|
102
|
+
| Hook | Event | What It Does |
|
|
93
103
|
|---|---|---|
|
|
94
|
-
| **commit-guard** | Blocks commits when tests failed or haven't run |
|
|
95
|
-
| **
|
|
96
|
-
| **
|
|
104
|
+
| **commit-guard** | PreToolUse | Blocks commits when tests failed or haven't run |
|
|
105
|
+
| **plan-enforcement** | PreToolUse | Warns when editing 6+ files without a plan |
|
|
106
|
+
| **test-tracker** | PostToolUse | Records test pass/fail results |
|
|
107
|
+
| **precompact-handler** | PreCompact | Auto-generates `docs/HANDOFF.md` before compaction |
|
|
108
|
+
| **session-end-handler** | SessionEnd | Saves HANDOFF + appends to `docs/PROJECT-MEMORY.md` |
|
|
109
|
+
| **scope-injector** | SubagentStart | Injects current plan batch context into subagent |
|
|
110
|
+
| **plan-sync** | TaskCompleted | Auto-updates plan file checkboxes on task completion |
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
The original three hooks (commit-guard, test-tracker, plan-enforcement) are deterministic enforcers. The four new hooks (v1.4.0) are **session lifecycle automations** — they don't block or warn, they auto-save context and sync plan state.
|
|
99
113
|
|
|
100
114
|
### 3. Slash Commands
|
|
101
115
|
|
|
@@ -150,6 +164,16 @@ prism analytics --detail # Include per-session breakdown
|
|
|
150
164
|
|
|
151
165
|
## Installation
|
|
152
166
|
|
|
167
|
+
### Option A: Plugin Mode (recommended)
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
claude plugin install claude-prism
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Plugin mode auto-registers hooks and skills. Run `prism init` additionally if you want CLAUDE.md methodology injection (plugins cannot modify CLAUDE.md).
|
|
174
|
+
|
|
175
|
+
### Option B: CLI Mode
|
|
176
|
+
|
|
153
177
|
```bash
|
|
154
178
|
npx claude-prism init # Install with hooks (prompts for HUD)
|
|
155
179
|
npx claude-prism init --hud # Install + auto-enable HUD
|
|
@@ -162,15 +186,19 @@ npx claude-prism init --dry-run # Preview what would be installed
|
|
|
162
186
|
|
|
163
187
|
```
|
|
164
188
|
your-project/
|
|
165
|
-
├── CLAUDE.md # EUDEC methodology injected
|
|
166
|
-
├── .
|
|
189
|
+
├── CLAUDE.md # EUDEC methodology injected (CLI mode only)
|
|
190
|
+
├── .prism/
|
|
191
|
+
│ ├── config.json # Hook configuration (committed)
|
|
192
|
+
│ ├── .version # Installed version (gitignored)
|
|
193
|
+
│ ├── .gitignore # Ignores .version
|
|
194
|
+
│ └── plans/ # Plan files (created during work)
|
|
167
195
|
├── .claude/
|
|
168
196
|
│ ├── commands/claude-prism/ # 9 slash commands
|
|
169
|
-
│ ├── hooks/ # pre-tool
|
|
170
|
-
│
|
|
171
|
-
│ ├──
|
|
172
|
-
│
|
|
173
|
-
└──
|
|
197
|
+
│ ├── hooks/ # 6 runners (pre-tool, post-tool, precompact,
|
|
198
|
+
│ │ # session-end, subagent-start, task-completed)
|
|
199
|
+
│ ├── rules/ # 7 rule modules
|
|
200
|
+
│ ├── lib/ # 8 shared dependencies
|
|
201
|
+
│ └── settings.json # Hook registration (6 events)
|
|
174
202
|
|
|
175
203
|
~/.claude/ # (global install / HUD)
|
|
176
204
|
├── commands/claude-prism/ # 9 slash commands (--global)
|
|
@@ -180,7 +208,7 @@ your-project/
|
|
|
180
208
|
|
|
181
209
|
## Configuration
|
|
182
210
|
|
|
183
|
-
Edit `.
|
|
211
|
+
Edit `.prism/config.json`:
|
|
184
212
|
|
|
185
213
|
```json
|
|
186
214
|
{
|
|
@@ -188,8 +216,19 @@ Edit `.claude-prism.json`:
|
|
|
188
216
|
"hooks": {
|
|
189
217
|
"commit-guard": { "enabled": true, "maxTestAge": 300 },
|
|
190
218
|
"test-tracker": { "enabled": true },
|
|
191
|
-
"plan-enforcement": { "enabled": true, "warnAt": 6 }
|
|
192
|
-
|
|
219
|
+
"plan-enforcement": { "enabled": true, "warnAt": 6 },
|
|
220
|
+
"precompact-handler": { "enabled": true },
|
|
221
|
+
"session-end-handler": { "enabled": true },
|
|
222
|
+
"subagent-scope-injector": { "enabled": true },
|
|
223
|
+
"task-plan-sync": { "enabled": true, "matchThreshold": 0.3 }
|
|
224
|
+
},
|
|
225
|
+
"webhooks": [
|
|
226
|
+
{
|
|
227
|
+
"url": "https://your-server.com/webhook",
|
|
228
|
+
"events": ["compaction", "session-end", "batch-complete"],
|
|
229
|
+
"headers": { "Authorization": "Bearer token" }
|
|
230
|
+
}
|
|
231
|
+
]
|
|
193
232
|
}
|
|
194
233
|
```
|
|
195
234
|
|
|
@@ -198,6 +237,8 @@ Edit `.claude-prism.json`:
|
|
|
198
237
|
| `version` | 1 | Config schema version (for future migrations) |
|
|
199
238
|
| `commit-guard.maxTestAge` | 300 | Seconds before test run is considered stale |
|
|
200
239
|
| `plan-enforcement.warnAt` | 6 | Unique source file count that triggers plan warning |
|
|
240
|
+
| `task-plan-sync.matchThreshold` | 0.3 | Keyword overlap ratio for fuzzy task matching |
|
|
241
|
+
| `webhooks` | `[]` | HTTP endpoints for event notifications |
|
|
201
242
|
|
|
202
243
|
## CLI Commands
|
|
203
244
|
|
|
@@ -235,6 +276,25 @@ prism hud disable # Deactivate HUD statusline
|
|
|
235
276
|
|
|
236
277
|
Prism auto-detects [oh-my-claudecode](https://github.com/Yeachan-Heo/oh-my-claudecode). When present, `prism stats` and `prism doctor` show OMC version. No configuration needed.
|
|
237
278
|
|
|
279
|
+
## Upgrading
|
|
280
|
+
|
|
281
|
+
### To v1.4.0
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
npx claude-prism update
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
v1.4.0 adds 4 new hook runners + 4 rule files + 2 new libs. `prism update` installs them automatically. Existing hooks and configuration are preserved.
|
|
288
|
+
|
|
289
|
+
**Optional**: Install as a native plugin for auto-registration:
|
|
290
|
+
```bash
|
|
291
|
+
claude plugin install claude-prism
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### To v1.3.0
|
|
295
|
+
|
|
296
|
+
v1.3.0 moves project files to `.prism/` directory. Migration is automatic via `prism update`.
|
|
297
|
+
|
|
238
298
|
## Design Philosophy
|
|
239
299
|
|
|
240
300
|
EUDEC is the product. Everything else serves it.
|
package/bin/cli.mjs
CHANGED
|
@@ -340,7 +340,7 @@ Options:
|
|
|
340
340
|
if (/EACCES|permission/i.test(msg)) {
|
|
341
341
|
process.stderr.write('💡 Check directory permissions\n');
|
|
342
342
|
} else if (/JSON|parse/i.test(msg)) {
|
|
343
|
-
process.stderr.write('💡 Config file may be corrupted. Try `prism reset` or delete .
|
|
343
|
+
process.stderr.write('💡 Config file may be corrupted. Try `prism reset` or delete .prism/config.json\n');
|
|
344
344
|
} else if (/ENOENT.*package\.json/i.test(msg)) {
|
|
345
345
|
process.stderr.write('💡 Not in a project directory?\n');
|
|
346
346
|
}
|
|
@@ -39,13 +39,18 @@ export const planEnforcement = {
|
|
|
39
39
|
const threshold = config.warnAt || 6;
|
|
40
40
|
if (files.length < threshold) return { type: 'pass' };
|
|
41
41
|
|
|
42
|
-
// Check for plan file in docs/plans/
|
|
42
|
+
// Check for plan file in .prism/plans/ (with docs/plans/ fallback)
|
|
43
43
|
const projectRoot = config.projectRoot || process.cwd();
|
|
44
|
-
const plansDir = join(projectRoot, '
|
|
44
|
+
const plansDir = join(projectRoot, '.prism', 'plans');
|
|
45
45
|
if (existsSync(plansDir)) {
|
|
46
46
|
const plans = readdirSync(plansDir).filter(f => f.endsWith('.md'));
|
|
47
47
|
if (plans.length > 0) return { type: 'pass' };
|
|
48
48
|
}
|
|
49
|
+
const legacyPlansDir = join(projectRoot, 'docs', 'plans');
|
|
50
|
+
if (existsSync(legacyPlansDir)) {
|
|
51
|
+
const plans = readdirSync(legacyPlansDir).filter(f => f.endsWith('.md'));
|
|
52
|
+
if (plans.length > 0) return { type: 'pass' };
|
|
53
|
+
}
|
|
49
54
|
|
|
50
55
|
return {
|
|
51
56
|
type: 'warn',
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-prism — PreCompact Handler
|
|
3
|
+
* Auto-generates HANDOFF.md before context compaction
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { generateHandoff } from '../lib/handoff.mjs';
|
|
9
|
+
import { dispatchWebhook } from '../lib/webhook.mjs';
|
|
10
|
+
import { getMessage } from '../lib/messages.mjs';
|
|
11
|
+
|
|
12
|
+
export const precompactHandler = {
|
|
13
|
+
name: 'precompact-handler',
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} input - { session_id, trigger, custom_instructions }
|
|
17
|
+
* @param {Object} config - Prism config
|
|
18
|
+
* @returns {Object} Hook output with additionalContext
|
|
19
|
+
*/
|
|
20
|
+
evaluate(input, config) {
|
|
21
|
+
const projectRoot = config.projectRoot || process.cwd();
|
|
22
|
+
|
|
23
|
+
// Generate HANDOFF.md
|
|
24
|
+
const handoffContent = generateHandoff(projectRoot);
|
|
25
|
+
const docsDir = join(projectRoot, 'docs');
|
|
26
|
+
if (!existsSync(docsDir)) {
|
|
27
|
+
mkdirSync(docsDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
writeFileSync(join(docsDir, 'HANDOFF.md'), handoffContent);
|
|
30
|
+
|
|
31
|
+
// Webhook dispatch (fire-and-forget)
|
|
32
|
+
dispatchWebhook(config, 'compaction', {
|
|
33
|
+
session_id: input.session_id,
|
|
34
|
+
trigger: input.trigger || 'auto',
|
|
35
|
+
}).catch(() => {});
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
hookSpecificOutput: {
|
|
39
|
+
hookEventName: 'PreCompact',
|
|
40
|
+
additionalContext: getMessage('en', 'precompact-handler.info.saved')
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-prism — SessionEnd Handler
|
|
3
|
+
* Saves HANDOFF.md and appends session summary to PROJECT-MEMORY.md
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { writeFileSync, appendFileSync, mkdirSync, existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { generateHandoff, getActivePlanInfo } from '../lib/handoff.mjs';
|
|
9
|
+
import { dispatchWebhook } from '../lib/webhook.mjs';
|
|
10
|
+
|
|
11
|
+
export const sessionEndHandler = {
|
|
12
|
+
name: 'session-end-handler',
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} input - { session_id, reason }
|
|
16
|
+
* @param {Object} config - Prism config
|
|
17
|
+
* @returns {Object} Empty output (SessionEnd has no visible output)
|
|
18
|
+
*/
|
|
19
|
+
evaluate(input, config) {
|
|
20
|
+
const projectRoot = config.projectRoot || process.cwd();
|
|
21
|
+
|
|
22
|
+
// 1. Generate HANDOFF.md
|
|
23
|
+
const handoffContent = generateHandoff(projectRoot);
|
|
24
|
+
const docsDir = join(projectRoot, 'docs');
|
|
25
|
+
if (!existsSync(docsDir)) {
|
|
26
|
+
mkdirSync(docsDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
writeFileSync(join(docsDir, 'HANDOFF.md'), handoffContent);
|
|
29
|
+
|
|
30
|
+
// 2. Append session summary to PROJECT-MEMORY.md
|
|
31
|
+
const planInfo = getActivePlanInfo(projectRoot);
|
|
32
|
+
const memoryPath = join(docsDir, 'PROJECT-MEMORY.md');
|
|
33
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
34
|
+
let entry = `\n## Session ${date}\n`;
|
|
35
|
+
entry += `- Reason: ${input.reason || 'unknown'}\n`;
|
|
36
|
+
if (planInfo) {
|
|
37
|
+
const pct = planInfo.total > 0 ? Math.round((planInfo.done / planInfo.total) * 100) : 0;
|
|
38
|
+
entry += `- Plan: \`${planInfo.planName}\` — ${planInfo.done}/${planInfo.total} (${pct}%)\n`;
|
|
39
|
+
}
|
|
40
|
+
entry += `- Session ID: ${input.session_id || 'unknown'}\n`;
|
|
41
|
+
|
|
42
|
+
if (existsSync(memoryPath)) {
|
|
43
|
+
appendFileSync(memoryPath, entry);
|
|
44
|
+
} else {
|
|
45
|
+
writeFileSync(memoryPath, `# PROJECT-MEMORY\n\nCumulative knowledge across sessions.\n${entry}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Webhook dispatch
|
|
49
|
+
dispatchWebhook(config, 'session-end', {
|
|
50
|
+
session_id: input.session_id,
|
|
51
|
+
reason: input.reason,
|
|
52
|
+
plan_progress: planInfo ? `${planInfo.done}/${planInfo.total}` : null,
|
|
53
|
+
}).catch(() => {});
|
|
54
|
+
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-prism — SubagentStart Scope Injector
|
|
3
|
+
* Injects current plan batch context into subagent
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getMessage } from '../lib/messages.mjs';
|
|
7
|
+
import { getActivePlanInfo } from '../lib/handoff.mjs';
|
|
8
|
+
|
|
9
|
+
export const scopeInjector = {
|
|
10
|
+
name: 'subagent-scope-injector',
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} input - { session_id, agent_id, agent_type }
|
|
14
|
+
* @param {Object} config - Prism config
|
|
15
|
+
* @returns {Object|null} Hook output with additionalContext or null
|
|
16
|
+
*/
|
|
17
|
+
evaluate(input, config) {
|
|
18
|
+
const projectRoot = config.projectRoot || process.cwd();
|
|
19
|
+
const planInfo = getActivePlanInfo(projectRoot);
|
|
20
|
+
|
|
21
|
+
if (!planInfo || planInfo.total === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pct = Math.round((planInfo.done / planInfo.total) * 100);
|
|
26
|
+
const parts = [
|
|
27
|
+
`Plan: ${planInfo.planName} (${planInfo.done}/${planInfo.total}, ${pct}%)`
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (planInfo.nextBatch) {
|
|
31
|
+
parts.push(`Current batch: ${planInfo.nextBatch}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (planInfo.currentBatchFiles && planInfo.currentBatchFiles.length > 0) {
|
|
35
|
+
const uniqueFiles = [...new Set(planInfo.currentBatchFiles)].slice(0, 10);
|
|
36
|
+
parts.push(`Files in scope: ${uniqueFiles.join(', ')}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (planInfo.nextTasks.length > 0) {
|
|
40
|
+
parts.push('Tasks:');
|
|
41
|
+
for (const task of planInfo.nextTasks.slice(0, 3)) {
|
|
42
|
+
parts.push(` - ${task}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
hookSpecificOutput: {
|
|
48
|
+
hookEventName: 'SubagentStart',
|
|
49
|
+
additionalContext: getMessage('en', 'subagent-scope-injector.info.scope') + '\n' + parts.join('\n')
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-prism — TaskCompleted Plan Sync
|
|
3
|
+
* Auto-updates plan file checkboxes when tasks complete
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { dispatchWebhook } from '../lib/webhook.mjs';
|
|
9
|
+
import { getMessage } from '../lib/messages.mjs';
|
|
10
|
+
|
|
11
|
+
export const planSync = {
|
|
12
|
+
name: 'task-plan-sync',
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} input - { task_id, task_subject, task_description, team_name? }
|
|
16
|
+
* @param {Object} config - Prism config
|
|
17
|
+
* @returns {Object|null} Hook output with additionalContext
|
|
18
|
+
*/
|
|
19
|
+
evaluate(input, config) {
|
|
20
|
+
const projectRoot = config.projectRoot || process.cwd();
|
|
21
|
+
const plansDir = join(projectRoot, '.prism', 'plans');
|
|
22
|
+
|
|
23
|
+
if (!existsSync(plansDir)) return null;
|
|
24
|
+
|
|
25
|
+
const planFiles = readdirSync(plansDir)
|
|
26
|
+
.filter(f => f.endsWith('.md'))
|
|
27
|
+
.sort()
|
|
28
|
+
.reverse();
|
|
29
|
+
|
|
30
|
+
if (planFiles.length === 0) return null;
|
|
31
|
+
|
|
32
|
+
const planPath = join(plansDir, planFiles[0]);
|
|
33
|
+
const content = readFileSync(planPath, 'utf8');
|
|
34
|
+
const subject = input.task_subject || '';
|
|
35
|
+
|
|
36
|
+
if (!subject) return null;
|
|
37
|
+
|
|
38
|
+
// Find matching unchecked task by keyword overlap
|
|
39
|
+
const lines = content.split('\n');
|
|
40
|
+
let matched = false;
|
|
41
|
+
let matchedTask = '';
|
|
42
|
+
let batchDone = 0;
|
|
43
|
+
let batchTotal = 0;
|
|
44
|
+
let currentBatchName = '';
|
|
45
|
+
let matchedBatchName = '';
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < lines.length; i++) {
|
|
48
|
+
// Track batch headers
|
|
49
|
+
const batchMatch = lines[i].match(/^#{1,3}\s+(Batch\s+\d+[:\s]*.+)/i);
|
|
50
|
+
if (batchMatch) {
|
|
51
|
+
// Check if previous batch is now complete
|
|
52
|
+
if (matched && batchTotal > 0) {
|
|
53
|
+
matchedBatchName = currentBatchName;
|
|
54
|
+
}
|
|
55
|
+
currentBatchName = batchMatch[1];
|
|
56
|
+
batchDone = 0;
|
|
57
|
+
batchTotal = 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const checkboxMatch = lines[i].match(/^([-*]\s+)\[ \]\s+(.+)/);
|
|
61
|
+
if (checkboxMatch) {
|
|
62
|
+
batchTotal++;
|
|
63
|
+
const taskText = checkboxMatch[2];
|
|
64
|
+
|
|
65
|
+
if (!matched && matchTask(subject, taskText, config.matchThreshold || 0.3)) {
|
|
66
|
+
lines[i] = lines[i].replace('[ ]', '[x]');
|
|
67
|
+
matched = true;
|
|
68
|
+
matchedTask = taskText;
|
|
69
|
+
matchedBatchName = currentBatchName;
|
|
70
|
+
batchDone++; // This one is now done
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
const doneMatch = lines[i].match(/^[-*]\s+\[x\]/);
|
|
74
|
+
if (doneMatch) {
|
|
75
|
+
batchTotal++;
|
|
76
|
+
batchDone++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!matched) return null;
|
|
82
|
+
|
|
83
|
+
// Write updated plan
|
|
84
|
+
writeFileSync(planPath, lines.join('\n'));
|
|
85
|
+
|
|
86
|
+
// Count overall progress
|
|
87
|
+
let total = 0, done = 0;
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
if (line.match(/^[-*]\s+\[x\]/)) { total++; done++; }
|
|
90
|
+
else if (line.match(/^[-*]\s+\[ \]/)) { total++; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if batch is complete
|
|
94
|
+
const batchComplete = batchTotal > 0 && batchDone >= batchTotal;
|
|
95
|
+
if (batchComplete) {
|
|
96
|
+
dispatchWebhook(config, 'batch-complete', {
|
|
97
|
+
batch: matchedBatchName,
|
|
98
|
+
plan: planFiles[0],
|
|
99
|
+
progress: `${done}/${total}`,
|
|
100
|
+
}).catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
104
|
+
return {
|
|
105
|
+
hookSpecificOutput: {
|
|
106
|
+
hookEventName: 'TaskCompleted',
|
|
107
|
+
additionalContext: getMessage('en', 'task-plan-sync.info.updated', {
|
|
108
|
+
task: matchedTask.slice(0, 60),
|
|
109
|
+
done: String(done),
|
|
110
|
+
total: String(total),
|
|
111
|
+
pct: String(pct),
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Match task subject against plan task text using keyword overlap
|
|
120
|
+
* @param {string} subject - Task subject from TaskCompleted event
|
|
121
|
+
* @param {string} taskText - Task text from plan file
|
|
122
|
+
* @param {number} threshold - Minimum overlap ratio (0-1)
|
|
123
|
+
* @returns {boolean}
|
|
124
|
+
*/
|
|
125
|
+
function matchTask(subject, taskText, threshold) {
|
|
126
|
+
const normalize = (s) => s.toLowerCase()
|
|
127
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
128
|
+
.split(/\s+/)
|
|
129
|
+
.filter(w => w.length > 2);
|
|
130
|
+
|
|
131
|
+
const subjectWords = new Set(normalize(subject));
|
|
132
|
+
const taskWords = normalize(taskText);
|
|
133
|
+
|
|
134
|
+
if (subjectWords.size === 0 || taskWords.length === 0) return false;
|
|
135
|
+
|
|
136
|
+
let overlap = 0;
|
|
137
|
+
for (const word of taskWords) {
|
|
138
|
+
if (subjectWords.has(word)) overlap++;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const ratio = overlap / Math.max(subjectWords.size, taskWords.length);
|
|
142
|
+
return ratio >= threshold;
|
|
143
|
+
}
|
package/lib/config.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* claude-prism — Configuration Loader
|
|
3
|
-
* Reads .
|
|
3
|
+
* Reads .prism/config.json from project root, with defaults
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileSync, existsSync } from 'fs';
|
|
@@ -10,15 +10,20 @@ const DEFAULTS = {
|
|
|
10
10
|
sourceExtensions: ['ts', 'tsx', 'js', 'jsx', 'py', 'go', 'rs', 'java', 'c', 'cpp', 'h', 'svelte', 'vue', 'rb', 'kt', 'swift', 'php', 'cs', 'scala', 'ex', 'clj', 'zig', 'lua', 'dart'],
|
|
11
11
|
testPatterns: ['test', 'spec', '_test'],
|
|
12
12
|
customRules: [],
|
|
13
|
+
webhooks: [],
|
|
13
14
|
hooks: {
|
|
14
15
|
'commit-guard': { enabled: true, maxTestAge: 300 },
|
|
15
16
|
'test-tracker': { enabled: true },
|
|
16
|
-
'plan-enforcement': { enabled: true, warnAt: 6 }
|
|
17
|
+
'plan-enforcement': { enabled: true, warnAt: 6 },
|
|
18
|
+
'precompact-handler': { enabled: true },
|
|
19
|
+
'session-end-handler': { enabled: true },
|
|
20
|
+
'subagent-scope-injector': { enabled: true },
|
|
21
|
+
'task-plan-sync': { enabled: true, matchThreshold: 0.3 }
|
|
17
22
|
}
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
export function loadConfig(projectRoot) {
|
|
21
|
-
const configPath = join(projectRoot, '.
|
|
26
|
+
const configPath = join(projectRoot, '.prism', 'config.json');
|
|
22
27
|
|
|
23
28
|
if (!existsSync(configPath)) {
|
|
24
29
|
return JSON.parse(JSON.stringify(DEFAULTS));
|