slyplan-mcp 1.2.2 → 1.3.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/dist/cli.js +191 -58
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -5,78 +5,211 @@ import * as readline from 'readline';
|
|
|
5
5
|
import { createClient } from '@supabase/supabase-js';
|
|
6
6
|
const SUPABASE_URL = 'https://omfzpkwtuzucwwxmyuqt.supabase.co';
|
|
7
7
|
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9tZnpwa3d0dXp1Y3d3eG15dXF0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA5MjMwNDIsImV4cCI6MjA4NjQ5OTA0Mn0.KXGoUez7M45RtFM9qR7mjzGX6UhlaRE-gggAJxSkIHY';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
// Smart transcript-aware hook: checks if set_project + add_to_work_mode have been called.
|
|
9
|
+
// Silent when OK, reminds only when needed.
|
|
10
|
+
const PRE_HOOK_FILE_CONTENT = `#!/usr/bin/env node
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
|
|
13
|
+
let input = '';
|
|
14
|
+
process.stdin.setEncoding('utf8');
|
|
15
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
16
|
+
process.stdin.on('end', () => {
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(input);
|
|
19
|
+
|
|
20
|
+
if (!data.transcript_path) {
|
|
21
|
+
const output = JSON.stringify({
|
|
22
|
+
suppressOutput: true,
|
|
23
|
+
hookSpecificOutput: {
|
|
24
|
+
hookEventName: "PreToolCall",
|
|
25
|
+
additionalContext: "BLOCKED: Call list_projects + set_project before doing any work. This is not optional."
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
process.stdout.write(output);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const transcriptPath = data.transcript_path.replace(/^~/, process.env.HOME || process.env.USERPROFILE || '');
|
|
33
|
+
if (!fs.existsSync(transcriptPath)) { process.exit(0); }
|
|
34
|
+
|
|
35
|
+
const content = fs.readFileSync(transcriptPath, 'utf8');
|
|
36
|
+
const lines = content.split('\\n').filter(Boolean);
|
|
37
|
+
|
|
38
|
+
let hasSetProject = false;
|
|
39
|
+
let hasWorkModeNode = false;
|
|
40
|
+
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
try {
|
|
43
|
+
const entry = JSON.parse(line);
|
|
44
|
+
if (entry.type === 'assistant' && entry.message && Array.isArray(entry.message.content)) {
|
|
45
|
+
for (const block of entry.message.content) {
|
|
46
|
+
if (block.type !== 'tool_use') continue;
|
|
47
|
+
const name = block.name || '';
|
|
48
|
+
if (name.includes('set_project')) hasSetProject = true;
|
|
49
|
+
if (name.includes('add_to_work_mode')) hasWorkModeNode = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!hasSetProject) {
|
|
56
|
+
const output = JSON.stringify({
|
|
57
|
+
suppressOutput: true,
|
|
58
|
+
hookSpecificOutput: {
|
|
59
|
+
hookEventName: "PreToolCall",
|
|
60
|
+
additionalContext: "BLOCKED: Call list_projects + set_project before doing any work. This is not optional."
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
process.stdout.write(output);
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!hasWorkModeNode) {
|
|
68
|
+
const output = JSON.stringify({
|
|
69
|
+
suppressOutput: true,
|
|
70
|
+
hookSpecificOutput: {
|
|
71
|
+
hookEventName: "PreToolCall",
|
|
72
|
+
additionalContext: "SYNC NOW: You have a project set but no node in work mode. Call search + add_to_work_mode before continuing."
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
process.stdout.write(output);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process.exit(0);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
process.exit(0);
|
|
16
82
|
}
|
|
17
83
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
84
|
+
`;
|
|
85
|
+
// Smart transcript-aware hook: counts file changes since last SlyPlan sync.
|
|
86
|
+
// Silent when synced, reminds with count when unsynced.
|
|
87
|
+
const POST_HOOK_FILE_CONTENT = `#!/usr/bin/env node
|
|
88
|
+
const fs = require('fs');
|
|
89
|
+
|
|
90
|
+
let input = '';
|
|
91
|
+
process.stdin.setEncoding('utf8');
|
|
92
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
93
|
+
process.stdin.on('end', () => {
|
|
94
|
+
try {
|
|
95
|
+
const data = JSON.parse(input);
|
|
96
|
+
|
|
97
|
+
if (!data.transcript_path) { process.exit(0); }
|
|
98
|
+
|
|
99
|
+
const transcriptPath = data.transcript_path.replace(/^~/, process.env.HOME || process.env.USERPROFILE || '');
|
|
100
|
+
if (!fs.existsSync(transcriptPath)) { process.exit(0); }
|
|
101
|
+
|
|
102
|
+
const content = fs.readFileSync(transcriptPath, 'utf8');
|
|
103
|
+
const lines = content.split('\\n').filter(Boolean);
|
|
104
|
+
|
|
105
|
+
const FILE_TOOLS = ['Write', 'Edit', 'NotebookEdit'];
|
|
106
|
+
const SLYPLAN_SYNC_TOOLS = ['update_node', 'add_node', 'add_to_work_mode'];
|
|
107
|
+
|
|
108
|
+
let lastFileChangeIdx = -1;
|
|
109
|
+
let lastSlyplanSyncIdx = -1;
|
|
110
|
+
let fileChangeCount = 0;
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < lines.length; i++) {
|
|
113
|
+
try {
|
|
114
|
+
const entry = JSON.parse(lines[i]);
|
|
115
|
+
if (entry.type === 'assistant' && entry.message && Array.isArray(entry.message.content)) {
|
|
116
|
+
for (const block of entry.message.content) {
|
|
117
|
+
if (block.type !== 'tool_use') continue;
|
|
118
|
+
const name = block.name || '';
|
|
119
|
+
|
|
120
|
+
if (FILE_TOOLS.includes(name)) {
|
|
121
|
+
lastFileChangeIdx = i;
|
|
122
|
+
fileChangeCount++;
|
|
123
|
+
}
|
|
124
|
+
if (name === 'Bash' && block.input && typeof block.input.command === 'string') {
|
|
125
|
+
const cmd = block.input.command;
|
|
126
|
+
if (cmd.includes('git commit') || cmd.includes('mkdir')) {
|
|
127
|
+
lastFileChangeIdx = i;
|
|
128
|
+
fileChangeCount++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const st of SLYPLAN_SYNC_TOOLS) {
|
|
132
|
+
if (name.includes(st)) lastSlyplanSyncIdx = i;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (fileChangeCount === 0) { process.exit(0); }
|
|
140
|
+
if (lastSlyplanSyncIdx > lastFileChangeIdx) { process.exit(0); }
|
|
141
|
+
|
|
142
|
+
const output = JSON.stringify({
|
|
143
|
+
suppressOutput: true,
|
|
144
|
+
hookSpecificOutput: {
|
|
145
|
+
hookEventName: "PostToolUse",
|
|
146
|
+
additionalContext: "SYNC NOW: " + fileChangeCount + " file change(s) since last SlyPlan sync. Call update_node with current progress before continuing."
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
process.stdout.write(output);
|
|
150
|
+
process.exit(0);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
process.exit(0);
|
|
27
153
|
}
|
|
28
154
|
});
|
|
29
|
-
|
|
30
|
-
process.exit(0);
|
|
31
|
-
`.replace('PRE_DIRECTIVE_PLACEHOLDER', JSON.stringify(PRE_DIRECTIVE));
|
|
155
|
+
`;
|
|
32
156
|
const CLAUDEMD_SECTION = `
|
|
33
|
-
##
|
|
157
|
+
## Hook Compliance — HARD REQUIREMENTS
|
|
158
|
+
|
|
159
|
+
Hook messages containing **"BLOCKED"** or **"SYNC NOW"** are HARD REQUIREMENTS, not suggestions.
|
|
34
160
|
|
|
35
|
-
|
|
161
|
+
- **BLOCKED:** You MUST stop what you're doing and follow the instruction BEFORE doing anything else. No exceptions — regardless of whether you're mid-workflow, running a GSD command, or in a multi-step task.
|
|
162
|
+
- **SYNC NOW:** You MUST call \`update_node\` with updated progress BEFORE continuing with the next tool call. Max one file change between each sync.
|
|
163
|
+
- Hooks are transcript-aware — they only fire when sync is actually missing. If you see a message, it means you have NOT done the work.
|
|
164
|
+
- Ignoring hooks because you're "following a recipe" or "in the middle of something" is NEVER a valid reason.
|
|
36
165
|
|
|
37
|
-
|
|
38
|
-
- **Ved start av hver sesjon:** Kall \`list_projects\` + \`set_project\` FØR du gjør noe annet.
|
|
39
|
-
- Match prosjekt basert på cwd-mappenavn eller brukerens kontekst.
|
|
40
|
-
- Hvis tvetydig, spør brukeren. Cache prosjekt-ID resten av sesjonen.
|
|
166
|
+
## SlyPlan Sync — MANDATORY RULES
|
|
41
167
|
|
|
42
|
-
|
|
43
|
-
- Kall \`search\` for å finne noden som matcher oppgaven din.
|
|
44
|
-
- Hvis ingen node finnes: opprett med \`add_node\` umiddelbart.
|
|
45
|
-
- Kall \`add_to_work_mode\` på noden FØR du starter med Edit/Write/Bash.
|
|
46
|
-
- **Du skal ALDRI starte arbeid uten en aktiv work mode node.**
|
|
168
|
+
You MUST keep SlyPlan updated. This is not optional. Follow these rules:
|
|
47
169
|
|
|
48
|
-
###
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
170
|
+
### 1. Set project FIRST
|
|
171
|
+
- **At the start of every session:** Call \`list_projects\` + \`set_project\` BEFORE doing anything else.
|
|
172
|
+
- Match the project based on the cwd folder name or user context.
|
|
173
|
+
- If ambiguous, ask the user. Cache the project ID for the rest of the session.
|
|
52
174
|
|
|
53
|
-
###
|
|
54
|
-
-
|
|
55
|
-
-
|
|
175
|
+
### 2. Find or create a node BEFORE starting work
|
|
176
|
+
- Call \`search\` to find the node matching your task.
|
|
177
|
+
- If no node exists: create one with \`add_node\` immediately.
|
|
178
|
+
- Call \`add_to_work_mode\` on the node BEFORE starting with Edit/Write/Bash.
|
|
179
|
+
- **You must NEVER start work without an active work mode node.**
|
|
56
180
|
|
|
57
|
-
###
|
|
58
|
-
-
|
|
181
|
+
### 3. Update progress AS YOU GO
|
|
182
|
+
- After each meaningful change: call \`update_node\` with updated \`progress\` (0-100).
|
|
183
|
+
- Update \`description\` with what was actually done.
|
|
184
|
+
- Skip for trivial changes (typos, config tweaks).
|
|
59
185
|
|
|
60
|
-
###
|
|
61
|
-
-
|
|
62
|
-
-
|
|
186
|
+
### 4. NEVER mark anything as done without asking
|
|
187
|
+
- Use \`AskUserQuestion\` with choices: "Yes, mark as done" / "Not yet" / "Skip".
|
|
188
|
+
- **No exceptions.** Auto-marking done is forbidden.
|
|
63
189
|
|
|
64
|
-
###
|
|
190
|
+
### 5. Clean up work mode when done
|
|
191
|
+
- Call \`remove_from_work_mode\` when you're done with a node or switching to another task.
|
|
192
|
+
|
|
193
|
+
### 6. All work must be reflected
|
|
194
|
+
- Applies to ALL types of work. If code changed, SlyPlan MUST reflect it.
|
|
195
|
+
- Small fixes = \`plan\` node under existing phase. Large features = new \`phase\` with \`plan\` children.
|
|
196
|
+
|
|
197
|
+
### Node hierarchy
|
|
65
198
|
\`project\` > \`category\` > \`phase\` > \`plan\`
|
|
66
199
|
|
|
67
|
-
###
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
### MCP
|
|
71
|
-
|
|
|
72
|
-
|
|
73
|
-
| \`list_projects\` |
|
|
74
|
-
| \`set_project\` |
|
|
75
|
-
| \`search\` |
|
|
76
|
-
| \`add_node\` |
|
|
77
|
-
| \`update_node\` |
|
|
78
|
-
| \`add_to_work_mode\` |
|
|
79
|
-
| \`remove_from_work_mode\` |
|
|
200
|
+
### Performance rules
|
|
201
|
+
- NEVER use \`get_tree\` (returns ~18k tokens). Use \`search\` instead.
|
|
202
|
+
|
|
203
|
+
### MCP tools
|
|
204
|
+
| Tool | Usage |
|
|
205
|
+
|------|-------|
|
|
206
|
+
| \`list_projects\` | Find projects (session start) |
|
|
207
|
+
| \`set_project\` | Select active project |
|
|
208
|
+
| \`search\` | Find nodes by name/content |
|
|
209
|
+
| \`add_node\` | Create new node (project/category/phase/plan) |
|
|
210
|
+
| \`update_node\` | Update status, progress, description |
|
|
211
|
+
| \`add_to_work_mode\` | Mark node as active work |
|
|
212
|
+
| \`remove_from_work_mode\` | Remove from active work |
|
|
80
213
|
`;
|
|
81
214
|
// --- Helpers ---
|
|
82
215
|
function log(msg) {
|
|
@@ -321,7 +454,7 @@ async function runSetup() {
|
|
|
321
454
|
if (appendAnswer.toLowerCase() !== 'n') {
|
|
322
455
|
if (fs.existsSync(claudeMdPath)) {
|
|
323
456
|
const content = fs.readFileSync(claudeMdPath, 'utf8');
|
|
324
|
-
if (content.includes('## SlyPlan Sync')) {
|
|
457
|
+
if (content.includes('## SlyPlan Sync') || content.includes('## Hook Compliance')) {
|
|
325
458
|
log(' [=] CLAUDE.md already has SlyPlan Sync section — skipped');
|
|
326
459
|
}
|
|
327
460
|
else {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slyplan-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "MCP server for Slyplan — visual project management via Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"slyplan-mcp": "
|
|
7
|
+
"slyplan-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|