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.
Files changed (2) hide show
  1. package/dist/cli.js +191 -58
  2. 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
- const PRE_DIRECTIVE = "ACTION REQUIRED: If you haven't set a SlyPlan project this session, call list_projects + set_project NOW. Then call search to find the node for your current task and add_to_work_mode. If no node exists, create one with add_node first. Do this BEFORE continuing with other work.";
9
- const POST_DIRECTIVE = "ACTION REQUIRED: You just made changes. Call search to find the SlyPlan node for this task, then update_node with current progress. Skip only for trivial edits (typos, config tweaks). Never mark done without asking the user first.";
10
- const POST_HOOK_FILE_CONTENT = `#!/usr/bin/env node
11
- const output = JSON.stringify({
12
- suppressOutput: true,
13
- hookSpecificOutput: {
14
- hookEventName: "PostToolUse",
15
- additionalContext: POST_DIRECTIVE_PLACEHOLDER
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
- process.stdout.write(output);
19
- process.exit(0);
20
- `.replace('POST_DIRECTIVE_PLACEHOLDER', JSON.stringify(POST_DIRECTIVE));
21
- const PRE_HOOK_FILE_CONTENT = `#!/usr/bin/env node
22
- const output = JSON.stringify({
23
- suppressOutput: true,
24
- hookSpecificOutput: {
25
- hookEventName: "PreToolCall",
26
- additionalContext: PRE_DIRECTIVE_PLACEHOLDER
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
- process.stdout.write(output);
30
- process.exit(0);
31
- `.replace('PRE_DIRECTIVE_PLACEHOLDER', JSON.stringify(PRE_DIRECTIVE));
155
+ `;
32
156
  const CLAUDEMD_SECTION = `
33
- ## SlyPlan SyncOBLIGATORISKE REGLER
157
+ ## Hook ComplianceHARD REQUIREMENTS
158
+
159
+ Hook messages containing **"BLOCKED"** or **"SYNC NOW"** are HARD REQUIREMENTS, not suggestions.
34
160
 
35
- DU SKAL holde SlyPlan oppdatert. Dette er ikke valgfritt. Følg disse reglene:
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
- ### 1. Sett prosjekt FØRST
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
- ### 2. Finn eller opprett node FØR du begynner arbeid
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
- ### 3. Oppdater progress UNDERVEIS
49
- - Etter hver meningsfull endring: kall \`update_node\` med oppdatert \`progress\` (0-100).
50
- - Oppdater \`description\` med hva som faktisk ble gjort.
51
- - Hopp over for trivielle endringer (typos, config-tweaks).
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
- ### 4. ALDRI merk noe som done uten å spørre
54
- - Bruk \`AskUserQuestion\` med valg: "Yes, mark as done" / "Not yet" / "Skip".
55
- - **Ingen unntak.** Auto-marking done er forbudt.
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
- ### 5. Rydd opp work mode når du er ferdig
58
- - Kall \`remove_from_work_mode\` når du er ferdig med en node eller bytter til annen oppgave.
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
- ### 6. Alt arbeid skal reflekteres
61
- - Gjelder ALL type arbeid. Hvis kode endret seg, SKAL SlyPlan reflektere det.
62
- - Små fix = \`plan\`-node under eksisterende phase. Stor feature = ny \`phase\` med \`plan\`-children.
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
- ### Node-hierarki
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
- ### Ytelsesregler
68
- - ALDRI bruk \`get_tree\` (returnerer ~18k tokens). Bruk \`search\` i stedet.
69
-
70
- ### MCP-verktøy
71
- | Verktøy | Bruk |
72
- |---------|------|
73
- | \`list_projects\` | Finn prosjekter (sesjonstart) |
74
- | \`set_project\` | Velg aktivt prosjekt |
75
- | \`search\` | Finn noder etter navn/innhold |
76
- | \`add_node\` | Opprett ny node (project/category/phase/plan) |
77
- | \`update_node\` | Oppdater status, progress, description |
78
- | \`add_to_work_mode\` | Marker node som aktivt arbeid |
79
- | \`remove_from_work_mode\` | Fjern fra aktivt arbeid |
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.2.2",
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": "./dist/index.js"
7
+ "slyplan-mcp": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist"