mnotes-cli 1.9.1 → 1.10.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.
@@ -380,14 +380,14 @@ function cleanTmpDir(dir) {
380
380
  (0, vitest_1.expect)(result).toContain("https://notes.example.com");
381
381
  (0, vitest_1.expect)(result).toContain("ws-abc-123");
382
382
  });
383
- (0, vitest_1.it)("includes session lifecycle sections", () => {
383
+ (0, vitest_1.it)("includes session lifecycle tools and wiki framing", () => {
384
384
  const result = (0, claude_code_1.generateClaudeCodeTemplate)({
385
385
  url: "http://localhost:3000",
386
386
  workspaceId: "ws-test",
387
387
  });
388
- (0, vitest_1.expect)(result).toContain("Session Start");
389
- (0, vitest_1.expect)(result).toContain("Session End");
390
- (0, vitest_1.expect)(result).toContain("During Work");
388
+ (0, vitest_1.expect)(result).toContain("living wiki");
389
+ (0, vitest_1.expect)(result).toContain("Ingest Loop");
390
+ (0, vitest_1.expect)(result).toContain("Lint Loop");
391
391
  (0, vitest_1.expect)(result).toContain("project_context_load");
392
392
  (0, vitest_1.expect)(result).toContain("session_context_resume");
393
393
  (0, vitest_1.expect)(result).toContain("knowledge_store");
@@ -417,13 +417,30 @@ function cleanTmpDir(dir) {
417
417
  "recall_knowledge",
418
418
  "bulk_knowledge_recall",
419
419
  "knowledge_snapshot",
420
+ "scan_knowledge_conflicts",
420
421
  "session_log",
421
422
  "context_fetch",
423
+ "create_note",
424
+ "update_note",
425
+ "append_to_note",
426
+ "search_notes",
427
+ "daily_note",
428
+ "populate_graph",
429
+ "query_note_graph",
422
430
  ];
423
431
  for (const tool of expectedTools) {
424
432
  (0, vitest_1.expect)(result).toContain(tool);
425
433
  }
426
434
  });
435
+ (0, vitest_1.it)("does not reference phantom MCP tools", () => {
436
+ const result = (0, claude_code_1.generateClaudeCodeTemplate)({
437
+ url: "http://localhost:3000",
438
+ workspaceId: "ws-test",
439
+ });
440
+ // These tool names don't exist in the MCP server — guard against regressions.
441
+ (0, vitest_1.expect)(result).not.toMatch(/\bcreate_folder\b/);
442
+ (0, vitest_1.expect)(result).not.toMatch(/\bmove_note\b/);
443
+ });
427
444
  });
428
445
  // =============================================================
429
446
  // Template generation — generateCodexTemplate (AC-6.3)
@@ -615,7 +632,7 @@ function cleanTmpDir(dir) {
615
632
  const claudeMd = fs.readFileSync(path.join(tmpDir, "CLAUDE.md"), "utf-8");
616
633
  (0, vitest_1.expect)(claudeMd).toContain("<!-- m-notes:start -->");
617
634
  (0, vitest_1.expect)(claudeMd).toContain("<!-- m-notes:end -->");
618
- (0, vitest_1.expect)(claudeMd).toContain("m-notes AI Knowledge Base");
635
+ (0, vitest_1.expect)(claudeMd).toContain("m-notes Your Wiki");
619
636
  (0, vitest_1.expect)(claudeMd).toContain("ws-123");
620
637
  });
621
638
  (0, vitest_1.it)("replaces existing m-notes block on re-run", async () => {
@@ -55,20 +55,29 @@ const DEFAULT_OPTS = {
55
55
  // T-1: Template generation — hooks
56
56
  // =============================================================
57
57
  (0, vitest_1.describe)("hooks template", () => {
58
- (0, vitest_1.it)("generates SessionStart hook with correct URL and workspaceId", () => {
58
+ (0, vitest_1.it)("generates SessionStart hook referencing bash script", () => {
59
59
  const hooks = (0, hooks_1.generateHooksTemplate)(DEFAULT_OPTS);
60
60
  (0, vitest_1.expect)(hooks.SessionStart).toBeDefined();
61
61
  (0, vitest_1.expect)(hooks.SessionStart).toHaveLength(1);
62
62
  (0, vitest_1.expect)(hooks.SessionStart[0].matcher).toBe("");
63
63
  (0, vitest_1.expect)(hooks.SessionStart[0].hooks).toHaveLength(1);
64
64
  (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].type).toBe("command");
65
- (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].command).toContain("localhost:3000");
66
- (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].command).toContain("ws-test-123");
67
- });
68
- (0, vitest_1.it)("strips trailing slashes from URL", () => {
69
- const hooks = (0, hooks_1.generateHooksTemplate)({ url: "http://example.com///", workspaceId: "ws-1" });
70
- (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].command).toContain("http://example.com/api/mcp");
71
- (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].command).not.toContain("///");
65
+ (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].command).toBe(".claude/hooks/mnotes-session-start.sh");
66
+ });
67
+ (0, vitest_1.it)("generates hook scripts with correct URL, workspaceId, and Accept headers", () => {
68
+ const scripts = (0, hooks_1.generateHookScripts)(DEFAULT_OPTS);
69
+ (0, vitest_1.expect)(scripts).toHaveLength(2);
70
+ const startScript = scripts.find((s) => s.filename === "mnotes-session-start.sh");
71
+ (0, vitest_1.expect)(startScript.content).toContain("localhost:3000/api/mcp");
72
+ (0, vitest_1.expect)(startScript.content).toContain("ws-test-123");
73
+ (0, vitest_1.expect)(startScript.content).toContain('Accept: text/event-stream');
74
+ (0, vitest_1.expect)(startScript.content).toContain('Accept: application/json');
75
+ });
76
+ (0, vitest_1.it)("strips trailing slashes from URL in hook scripts", () => {
77
+ const scripts = (0, hooks_1.generateHookScripts)({ url: "http://example.com///", workspaceId: "ws-1" });
78
+ const startScript = scripts.find((s) => s.filename === "mnotes-session-start.sh");
79
+ (0, vitest_1.expect)(startScript.content).toContain("http://example.com/api/mcp");
80
+ (0, vitest_1.expect)(startScript.content).not.toContain("///");
72
81
  });
73
82
  });
74
83
  // =============================================================
@@ -79,11 +88,11 @@ const DEFAULT_OPTS = {
79
88
  const skills = (0, skills_1.generateSkillTemplates)(DEFAULT_OPTS);
80
89
  (0, vitest_1.expect)(skills).toHaveLength(2);
81
90
  });
82
- (0, vitest_1.it)("generates store and recall skills", () => {
91
+ (0, vitest_1.it)("generates store and recall skills as SKILL.md inside their own folder (AC-1)", () => {
83
92
  const skills = (0, skills_1.generateSkillTemplates)(DEFAULT_OPTS);
84
- const filenames = skills.map((s) => s.filename);
85
- (0, vitest_1.expect)(filenames).toContain("mnotes-store.md");
86
- (0, vitest_1.expect)(filenames).toContain("mnotes-recall.md");
93
+ const paths = skills.map((s) => s.path);
94
+ (0, vitest_1.expect)(paths).toContain("mnotes-store/SKILL.md");
95
+ (0, vitest_1.expect)(paths).toContain("mnotes-recall/SKILL.md");
87
96
  });
88
97
  (0, vitest_1.it)("includes m-notes generated header (AC-8)", () => {
89
98
  const skills = (0, skills_1.generateSkillTemplates)(DEFAULT_OPTS);
@@ -97,12 +106,13 @@ const DEFAULT_OPTS = {
97
106
  (0, vitest_1.expect)(skill.content).toContain("ws-test-123");
98
107
  }
99
108
  });
100
- (0, vitest_1.it)("includes frontmatter with name and trigger", () => {
109
+ (0, vitest_1.it)("includes frontmatter with name and description and omits non-standard fields", () => {
101
110
  const skills = (0, skills_1.generateSkillTemplates)(DEFAULT_OPTS);
102
111
  for (const skill of skills) {
103
112
  (0, vitest_1.expect)(skill.content).toContain("---");
104
113
  (0, vitest_1.expect)(skill.content).toContain("name:");
105
- (0, vitest_1.expect)(skill.content).toContain("trigger:");
114
+ (0, vitest_1.expect)(skill.content).toContain("description:");
115
+ (0, vitest_1.expect)(skill.content).not.toMatch(/^trigger:/m);
106
116
  }
107
117
  });
108
118
  });
@@ -161,17 +171,26 @@ const DEFAULT_OPTS = {
161
171
  (0, vitest_1.afterEach)(() => {
162
172
  cleanTmpDir(tmpDir);
163
173
  });
164
- (0, vitest_1.it)("creates .claude/settings.json with hooks (AC-4)", () => {
174
+ (0, vitest_1.it)("creates .claude/settings.json with hooks and bash scripts (AC-4)", () => {
165
175
  const results = (0, wizard_1.scaffoldItems)(tmpDir, ["hooks"], DEFAULT_OPTS);
166
176
  (0, vitest_1.expect)(results).toHaveLength(1);
167
177
  (0, vitest_1.expect)(results[0].item).toBe("hooks");
168
- (0, vitest_1.expect)(results[0].filesWritten).toHaveLength(1);
178
+ // 2 bash scripts + settings.json
179
+ (0, vitest_1.expect)(results[0].filesWritten).toHaveLength(3);
169
180
  const settingsPath = path.join(tmpDir, ".claude", "settings.json");
170
181
  (0, vitest_1.expect)(fs.existsSync(settingsPath)).toBe(true);
171
182
  const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
172
183
  (0, vitest_1.expect)(settings.hooks).toBeDefined();
173
184
  (0, vitest_1.expect)(settings.hooks.SessionStart).toBeDefined();
174
185
  (0, vitest_1.expect)(settings.hooks.SessionStart).toHaveLength(1);
186
+ // Bash scripts exist and are executable
187
+ const startScript = path.join(tmpDir, ".claude", "hooks", "mnotes-session-start.sh");
188
+ const stopScript = path.join(tmpDir, ".claude", "hooks", "mnotes-session-stop.sh");
189
+ (0, vitest_1.expect)(fs.existsSync(startScript)).toBe(true);
190
+ (0, vitest_1.expect)(fs.existsSync(stopScript)).toBe(true);
191
+ const startContent = fs.readFileSync(startScript, "utf-8");
192
+ (0, vitest_1.expect)(startContent).toContain("Accept: text/event-stream");
193
+ (0, vitest_1.expect)(startContent).toContain("Accept: application/json");
175
194
  });
176
195
  (0, vitest_1.it)("merges hooks into existing settings.json (AC-5)", () => {
177
196
  const claudeDir = path.join(tmpDir, ".claude");
@@ -211,38 +230,38 @@ const DEFAULT_OPTS = {
211
230
  (0, vitest_1.afterEach)(() => {
212
231
  cleanTmpDir(tmpDir);
213
232
  });
214
- (0, vitest_1.it)("creates skill files in .claude/skills/ (AC-4)", () => {
233
+ (0, vitest_1.it)("creates skill files as .claude/skills/<name>/SKILL.md (AC-4)", () => {
215
234
  const results = (0, wizard_1.scaffoldItems)(tmpDir, ["skills"], DEFAULT_OPTS);
216
235
  (0, vitest_1.expect)(results).toHaveLength(1);
217
236
  (0, vitest_1.expect)(results[0].item).toBe("skills");
218
237
  (0, vitest_1.expect)(results[0].filesWritten.length).toBeGreaterThan(0);
219
238
  const skillsDir = path.join(tmpDir, ".claude", "skills");
220
- (0, vitest_1.expect)(fs.existsSync(path.join(skillsDir, "mnotes-store.md"))).toBe(true);
221
- (0, vitest_1.expect)(fs.existsSync(path.join(skillsDir, "mnotes-recall.md"))).toBe(true);
239
+ (0, vitest_1.expect)(fs.existsSync(path.join(skillsDir, "mnotes-store", "SKILL.md"))).toBe(true);
240
+ (0, vitest_1.expect)(fs.existsSync(path.join(skillsDir, "mnotes-recall", "SKILL.md"))).toBe(true);
222
241
  });
223
242
  (0, vitest_1.it)("generated skill files include header (AC-8)", () => {
224
243
  (0, wizard_1.scaffoldItems)(tmpDir, ["skills"], DEFAULT_OPTS);
225
- const storeContent = fs.readFileSync(path.join(tmpDir, ".claude", "skills", "mnotes-store.md"), "utf-8");
244
+ const storeContent = fs.readFileSync(path.join(tmpDir, ".claude", "skills", "mnotes-store", "SKILL.md"), "utf-8");
226
245
  (0, vitest_1.expect)(storeContent).toContain("Generated by m-notes CLI");
227
246
  });
228
247
  (0, vitest_1.it)("preserves user-created skill files (AC-5)", () => {
229
- const skillsDir = path.join(tmpDir, ".claude", "skills");
230
- fs.mkdirSync(skillsDir, { recursive: true });
231
- // Write a user-created file with the same name
232
- fs.writeFileSync(path.join(skillsDir, "mnotes-store.md"), "# My custom store skill\nCustom content here", "utf-8");
233
- const results = (0, wizard_1.scaffoldItems)(tmpDir, ["skills"], DEFAULT_OPTS);
248
+ const storeDir = path.join(tmpDir, ".claude", "skills", "mnotes-store");
249
+ fs.mkdirSync(storeDir, { recursive: true });
250
+ // Write a user-created file at the same path
251
+ fs.writeFileSync(path.join(storeDir, "SKILL.md"), "# My custom store skill\nCustom content here", "utf-8");
252
+ (0, wizard_1.scaffoldItems)(tmpDir, ["skills"], DEFAULT_OPTS);
234
253
  // The user file should be preserved
235
- const content = fs.readFileSync(path.join(skillsDir, "mnotes-store.md"), "utf-8");
254
+ const content = fs.readFileSync(path.join(storeDir, "SKILL.md"), "utf-8");
236
255
  (0, vitest_1.expect)(content).toContain("My custom store skill");
237
256
  (0, vitest_1.expect)(content).not.toContain("Generated by m-notes CLI");
238
257
  // The recall skill should still be written (new file)
239
- (0, vitest_1.expect)(fs.existsSync(path.join(skillsDir, "mnotes-recall.md"))).toBe(true);
258
+ (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-recall", "SKILL.md"))).toBe(true);
240
259
  });
241
260
  (0, vitest_1.it)("overwrites m-notes generated skill files on re-run (AC-5)", () => {
242
261
  (0, wizard_1.scaffoldItems)(tmpDir, ["skills"], DEFAULT_OPTS);
243
262
  // Re-run with different workspaceId
244
263
  (0, wizard_1.scaffoldItems)(tmpDir, ["skills"], { url: "http://new-url.com", workspaceId: "ws-new" });
245
- const content = fs.readFileSync(path.join(tmpDir, ".claude", "skills", "mnotes-store.md"), "utf-8");
264
+ const content = fs.readFileSync(path.join(tmpDir, ".claude", "skills", "mnotes-store", "SKILL.md"), "utf-8");
246
265
  (0, vitest_1.expect)(content).toContain("ws-new");
247
266
  });
248
267
  });
@@ -298,8 +317,8 @@ const DEFAULT_OPTS = {
298
317
  (0, vitest_1.expect)(items).toContain("agents");
299
318
  // Verify files exist
300
319
  (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "settings.json"))).toBe(true);
301
- (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-store.md"))).toBe(true);
302
- (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-recall.md"))).toBe(true);
320
+ (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-store", "SKILL.md"))).toBe(true);
321
+ (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-recall", "SKILL.md"))).toBe(true);
303
322
  (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "agents", "knowledge-manager.md"))).toBe(true);
304
323
  });
305
324
  (0, vitest_1.it)("scaffolds partial selection", () => {
@@ -386,8 +405,8 @@ const DEFAULT_OPTS = {
386
405
  (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, "CLAUDE.md"))).toBe(true);
387
406
  // All extras should exist
388
407
  (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "settings.json"))).toBe(true);
389
- (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-store.md"))).toBe(true);
390
- (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-recall.md"))).toBe(true);
408
+ (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-store", "SKILL.md"))).toBe(true);
409
+ (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-recall", "SKILL.md"))).toBe(true);
391
410
  (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "agents", "knowledge-manager.md"))).toBe(true);
392
411
  // Output should mention extras
393
412
  (0, vitest_1.expect)(output).toContain("Extras installed:");
@@ -465,7 +484,7 @@ const DEFAULT_OPTS = {
465
484
  }
466
485
  // All extras should exist
467
486
  (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "settings.json"))).toBe(true);
468
- (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-store.md"))).toBe(true);
487
+ (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "skills", "mnotes-store", "SKILL.md"))).toBe(true);
469
488
  (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "agents", "knowledge-manager.md"))).toBe(true);
470
489
  });
471
490
  });
@@ -102,13 +102,23 @@ function scaffoldItems(dir, items, opts) {
102
102
  return results;
103
103
  }
104
104
  /**
105
- * Merges hooks into `.claude/settings.json`.
105
+ * Merges hooks into `.claude/settings.json` and writes bash scripts to `.claude/hooks/`.
106
106
  * Preserves all existing settings and hooks.
107
107
  */
108
108
  function scaffoldHooks(dir, opts) {
109
109
  const settingsPath = path.join(dir, ".claude", "settings.json");
110
110
  const claudeDir = path.join(dir, ".claude");
111
- fs.mkdirSync(claudeDir, { recursive: true });
111
+ const hooksDir = path.join(claudeDir, "hooks");
112
+ fs.mkdirSync(hooksDir, { recursive: true });
113
+ const filesWritten = [];
114
+ // 1. Write bash scripts to .claude/hooks/
115
+ const scripts = (0, index_1.generateHookScripts)(opts);
116
+ for (const script of scripts) {
117
+ const scriptPath = path.join(hooksDir, script.filename);
118
+ fs.writeFileSync(scriptPath, script.content, { mode: 0o755 });
119
+ filesWritten.push(scriptPath);
120
+ }
121
+ // 2. Merge hook entries into settings.json
112
122
  let existing = {};
113
123
  try {
114
124
  const raw = fs.readFileSync(settingsPath, "utf-8");
@@ -158,7 +168,8 @@ function scaffoldHooks(dir, opts) {
158
168
  mnotesMetadata.items.push("hooks");
159
169
  }
160
170
  fs.writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
161
- return { item: "hooks", filesWritten: [settingsPath] };
171
+ filesWritten.push(settingsPath);
172
+ return { item: "hooks", filesWritten };
162
173
  }
163
174
  /**
164
175
  * Writes skill files to `.claude/skills/`.
@@ -170,8 +181,9 @@ function scaffoldSkills(dir, opts) {
170
181
  const skills = (0, index_1.generateSkillTemplates)(opts);
171
182
  const filesWritten = [];
172
183
  for (const skill of skills) {
173
- const filePath = path.join(skillsDir, skill.filename);
184
+ const filePath = path.join(skillsDir, skill.path);
174
185
  if (shouldWriteGeneratedFile(filePath)) {
186
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
175
187
  fs.writeFileSync(filePath, skill.content, "utf-8");
176
188
  filesWritten.push(filePath);
177
189
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Template for Claude Code session hooks.
3
- * Written to `.claude/settings.json` under the `hooks` field.
3
+ * Generates bash scripts in `.claude/hooks/` and hook entries for `.claude/settings.json`.
4
4
  * Generated by m-notes CLI — do not edit manually.
5
5
  */
6
6
  export interface HooksTemplateOpts {
@@ -19,11 +19,17 @@ export interface ClaudeCodeHooks {
19
19
  SessionStart?: ClaudeCodeHookEntry[];
20
20
  [key: string]: ClaudeCodeHookEntry[] | undefined;
21
21
  }
22
+ export interface HookScript {
23
+ filename: string;
24
+ content: string;
25
+ }
26
+ /**
27
+ * Generates bash scripts to be written to `.claude/hooks/`.
28
+ */
29
+ export declare function generateHookScripts(opts: HooksTemplateOpts): HookScript[];
22
30
  /**
23
31
  * Generates the hooks object to merge into `.claude/settings.json`.
24
- * Currently provides a SessionStart hook that auto-loads project context.
25
- *
26
- * Claude Code hooks format requires: { matcher, hooks[] } wrapper.
32
+ * References bash scripts in `.claude/hooks/` instead of inline commands.
27
33
  */
28
34
  export declare function generateHooksTemplate(opts: HooksTemplateOpts): ClaudeCodeHooks;
29
35
  /** Header comment for the hooks section, used for identification. */
@@ -1,31 +1,69 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Template for Claude Code session hooks.
4
- * Written to `.claude/settings.json` under the `hooks` field.
4
+ * Generates bash scripts in `.claude/hooks/` and hook entries for `.claude/settings.json`.
5
5
  * Generated by m-notes CLI — do not edit manually.
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.HOOKS_HEADER = void 0;
9
+ exports.generateHookScripts = generateHookScripts;
9
10
  exports.generateHooksTemplate = generateHooksTemplate;
10
11
  /**
11
- * Generates the hooks object to merge into `.claude/settings.json`.
12
- * Currently provides a SessionStart hook that auto-loads project context.
13
- *
14
- * Claude Code hooks format requires: { matcher, hooks[] } wrapper.
12
+ * Generates bash scripts to be written to `.claude/hooks/`.
15
13
  */
16
- function generateHooksTemplate(opts) {
14
+ function generateHookScripts(opts) {
17
15
  const baseUrl = opts.url.replace(/\/+$/, "");
18
16
  const mcpUrl = `${baseUrl}/api/mcp`;
19
- const authHeader = `Authorization: Bearer $MNOTES_API_KEY`;
20
- function mcpCall(method, args) {
21
- const payload = JSON.stringify({
17
+ function mcpPayload(method, args) {
18
+ return JSON.stringify({
22
19
  jsonrpc: "2.0",
23
20
  method: "tools/call",
24
21
  params: { name: method, arguments: args },
25
22
  id: 1,
26
23
  });
27
- return `curl -s -H "${authHeader}" "${mcpUrl}" -d '${payload}' -H "Content-Type: application/json" > /dev/null 2>&1 || true`;
28
24
  }
25
+ const sessionStartScript = `#!/usr/bin/env bash
26
+ # Generated by m-notes CLI — do not edit manually.
27
+ # Loads project context on Claude Code session start.
28
+ set -euo pipefail
29
+
30
+ MNOTES_URL="${mcpUrl}"
31
+ PAYLOAD='${mcpPayload("project_context_load", { workspaceId: opts.workspaceId, query: "session start" })}'
32
+
33
+ curl -s \\
34
+ -H "Authorization: Bearer $MNOTES_API_KEY" \\
35
+ -H "Content-Type: application/json" \\
36
+ -H "Accept: text/event-stream" \\
37
+ -H "Accept: application/json" \\
38
+ "$MNOTES_URL" \\
39
+ -d "$PAYLOAD" > /dev/null 2>&1 || true
40
+ `;
41
+ const sessionStopScript = `#!/usr/bin/env bash
42
+ # Generated by m-notes CLI — do not edit manually.
43
+ # Logs session end to m-notes.
44
+ set -euo pipefail
45
+
46
+ MNOTES_URL="${mcpUrl}"
47
+ PAYLOAD='${mcpPayload("session_log", { workspaceId: opts.workspaceId, summary: "Session ended", decisions: [], actions: [] })}'
48
+
49
+ curl -s \\
50
+ -H "Authorization: Bearer $MNOTES_API_KEY" \\
51
+ -H "Content-Type: application/json" \\
52
+ -H "Accept: text/event-stream" \\
53
+ -H "Accept: application/json" \\
54
+ "$MNOTES_URL" \\
55
+ -d "$PAYLOAD" > /dev/null 2>&1 || true
56
+ `;
57
+ return [
58
+ { filename: "mnotes-session-start.sh", content: sessionStartScript },
59
+ { filename: "mnotes-session-stop.sh", content: sessionStopScript },
60
+ ];
61
+ }
62
+ /**
63
+ * Generates the hooks object to merge into `.claude/settings.json`.
64
+ * References bash scripts in `.claude/hooks/` instead of inline commands.
65
+ */
66
+ function generateHooksTemplate(opts) {
29
67
  return {
30
68
  SessionStart: [
31
69
  {
@@ -33,10 +71,7 @@ function generateHooksTemplate(opts) {
33
71
  hooks: [
34
72
  {
35
73
  type: "command",
36
- command: mcpCall("project_context_load", {
37
- workspaceId: opts.workspaceId,
38
- query: "session start",
39
- }),
74
+ command: ".claude/hooks/mnotes-session-start.sh",
40
75
  },
41
76
  ],
42
77
  },
@@ -47,12 +82,7 @@ function generateHooksTemplate(opts) {
47
82
  hooks: [
48
83
  {
49
84
  type: "command",
50
- command: mcpCall("session_log", {
51
- workspaceId: opts.workspaceId,
52
- summary: "Session ended",
53
- decisions: [],
54
- actions: [],
55
- }),
85
+ command: ".claude/hooks/mnotes-session-stop.sh",
56
86
  },
57
87
  ],
58
88
  },
@@ -1,5 +1,5 @@
1
- export { generateHooksTemplate, HOOKS_HEADER } from "./hooks";
2
- export type { HooksTemplateOpts, ClaudeCodeHooks, ClaudeCodeHook } from "./hooks";
1
+ export { generateHooksTemplate, generateHookScripts, HOOKS_HEADER } from "./hooks";
2
+ export type { HooksTemplateOpts, ClaudeCodeHooks, ClaudeCodeHook, HookScript } from "./hooks";
3
3
  export { generateSkillTemplates } from "./skills";
4
4
  export type { SkillTemplateOpts, SkillFile } from "./skills";
5
5
  export { generateAgentTemplates } from "./agents";
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateAgentTemplates = exports.generateSkillTemplates = exports.HOOKS_HEADER = exports.generateHooksTemplate = void 0;
3
+ exports.generateAgentTemplates = exports.generateSkillTemplates = exports.HOOKS_HEADER = exports.generateHookScripts = exports.generateHooksTemplate = void 0;
4
4
  var hooks_1 = require("./hooks");
5
5
  Object.defineProperty(exports, "generateHooksTemplate", { enumerable: true, get: function () { return hooks_1.generateHooksTemplate; } });
6
+ Object.defineProperty(exports, "generateHookScripts", { enumerable: true, get: function () { return hooks_1.generateHookScripts; } });
6
7
  Object.defineProperty(exports, "HOOKS_HEADER", { enumerable: true, get: function () { return hooks_1.HOOKS_HEADER; } });
7
8
  var skills_1 = require("./skills");
8
9
  Object.defineProperty(exports, "generateSkillTemplates", { enumerable: true, get: function () { return skills_1.generateSkillTemplates; } });
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Templates for Claude Code skills.
3
- * Written to `.claude/skills/` as markdown files.
3
+ * Written to `.claude/skills/<skill-name>/SKILL.md` Claude Code's skill loader
4
+ * only discovers skills that live in their own folder as `SKILL.md`.
4
5
  * Generated by m-notes CLI — do not edit manually.
5
6
  */
6
7
  export interface SkillTemplateOpts {
@@ -8,11 +9,14 @@ export interface SkillTemplateOpts {
8
9
  workspaceId: string;
9
10
  }
10
11
  export interface SkillFile {
11
- filename: string;
12
+ /** Relative path within `.claude/skills/`, e.g. `mnotes-store/SKILL.md`. */
13
+ path: string;
12
14
  content: string;
13
15
  }
14
16
  /**
15
17
  * Generates skill files for m-notes integration.
16
- * Returns an array of { filename, content } pairs.
18
+ * Returns an array of { path, content } pairs. `path` is relative to
19
+ * `.claude/skills/` and always ends in `SKILL.md` so Claude Code's loader
20
+ * discovers the skill.
17
21
  */
18
22
  export declare function generateSkillTemplates(opts: SkillTemplateOpts): SkillFile[];
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Templates for Claude Code skills.
4
- * Written to `.claude/skills/` as markdown files.
4
+ * Written to `.claude/skills/<skill-name>/SKILL.md` Claude Code's skill loader
5
+ * only discovers skills that live in their own folder as `SKILL.md`.
5
6
  * Generated by m-notes CLI — do not edit manually.
6
7
  */
7
8
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -9,17 +10,18 @@ exports.generateSkillTemplates = generateSkillTemplates;
9
10
  const GENERATED_HEADER = "<!-- Generated by m-notes CLI. Do not edit manually. -->";
10
11
  /**
11
12
  * Generates skill files for m-notes integration.
12
- * Returns an array of { filename, content } pairs.
13
+ * Returns an array of { path, content } pairs. `path` is relative to
14
+ * `.claude/skills/` and always ends in `SKILL.md` so Claude Code's loader
15
+ * discovers the skill.
13
16
  */
14
17
  function generateSkillTemplates(opts) {
15
18
  return [
16
19
  {
17
- filename: "mnotes-store.md",
20
+ path: "mnotes-store/SKILL.md",
18
21
  content: `${GENERATED_HEADER}
19
22
  ---
20
23
  name: mnotes-store
21
- description: Store a knowledge entry in m-notes
22
- trigger: When the user says /store or asks to save/store knowledge, decisions, patterns, or context
24
+ description: Store a knowledge entry in m-notes. Use when the user says /store or asks to save/store knowledge, decisions, patterns, or context.
23
25
  ---
24
26
 
25
27
  # Store Knowledge in m-notes
@@ -44,12 +46,11 @@ Use the \`knowledge_store\` MCP tool to save knowledge.
44
46
  `,
45
47
  },
46
48
  {
47
- filename: "mnotes-recall.md",
49
+ path: "mnotes-recall/SKILL.md",
48
50
  content: `${GENERATED_HEADER}
49
51
  ---
50
52
  name: mnotes-recall
51
- description: Recall knowledge from m-notes using semantic search
52
- trigger: When the user says /recall or asks to find/recall/search knowledge, or needs context about a topic
53
+ description: Recall knowledge from m-notes using semantic search. Use when the user says /recall or asks to find/recall/search knowledge, or needs context about a topic.
53
54
  ---
54
55
 
55
56
  # Recall Knowledge from m-notes
@@ -2,107 +2,131 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateClaudeCodeTemplate = generateClaudeCodeTemplate;
4
4
  function generateClaudeCodeTemplate(opts) {
5
- return `# m-notes Persistent Knowledge Base (MANDATORY)
5
+ return `<!-- m-notes instructions v3 -->
6
+ # m-notes — Your Wiki (MANDATORY)
6
7
 
7
8
  **Server**: ${opts.url}
8
9
  **Workspace**: ${opts.workspaceId}
9
10
 
10
- > **IMPORTANT**: You MUST use m-notes tools throughout every session. This is your persistent memory across sessions — without it you start from zero every time. The instructions below are NOT optional.
11
+ > You are not a reader of a scratchpad. You are the **author and maintainer of a living wiki**. Every session, three things compound: the wiki grows, links densify, contradictions get resolved. This is your persistent memory — without it you start from zero every conversation.
12
+
13
+ ## The Three Layers
14
+
15
+ 1. **Raw sources** — user messages, pasted docs, URLs, files. Immutable. You never edit these.
16
+ 2. **The wiki** — your notes. You *write* these. They are interlinked with \`[[wikilinks]]\`, tagged for retrieval, and updated whenever sources change.
17
+ 3. **Schema pages** — notes tagged \`type:config\` (or in a \`_schema/\` folder). The user's rules for how the wiki is organized. **Read these before editing the wiki.**
11
18
 
12
19
  ## Rules (follow strictly)
13
20
 
14
- 1. **ALWAYS recall before researching.** Before exploring the codebase, reading files, or making assumptions about architecture — call \`recall_knowledge\` first. Past you already figured things out; don't waste tokens rediscovering them.
15
- 2. **ALWAYS store what you learn.** When you discover something non-obvious (architecture decisions, gotchas, patterns, bug root causes, dependency quirks) — store it immediately with \`knowledge_store\`. If it took effort to figure out, it's worth storing.
16
- 3. **ALWAYS log sessions.** Before the session ends, call \`session_log\` with a summary of what was accomplished, decisions made, and next steps. Future sessions depend on this.
17
- 4. **ALWAYS load context at session start.** Call \`project_context_load\` at the beginning of every session. The SessionStart hook does this automatically, but if you're resuming mid-conversation, call \`session_context_resume\`.
21
+ 1. **ALWAYS recall before researching.** Before exploring the codebase or making assumptions — call \`recall_knowledge\` and \`search_notes\`. Past you already figured things out.
22
+ 2. **ALWAYS store what you learn.** Non-obvious discoveries \`knowledge_store\` immediately. Anything worth re-finding belongs in the wiki.
23
+ 3. **ALWAYS log sessions.** Call \`session_log\` before the session ends.
24
+ 4. **ALWAYS load context at session start.** Call \`project_context_load\`. Use \`session_context_resume\` if mid-conversation.
25
+ 5. **INGEST sources coherently.** When the user drops a URL, long paste, or file: don't just dump it into one note. Run the **ingest loop** (below).
26
+ 6. **LINT the wiki periodically.** On session start for large workspaces, and after any ingest, run the **lint loop** (below).
27
+ 7. **READ schema before editing.** Before creating or updating notes, call \`search_notes\` for \`type:config\` notes. Follow their conventions. If none exist, offer to create a starter schema note.
28
+ 8. **EVERY note must link.** Outbound \`[[wikilinks]]\` are mandatory — an orphan note is invisible.
29
+
30
+ ## The Ingest Loop
31
+
32
+ When the user supplies a source (URL, paste, file):
33
+
34
+ 1. **Find related pages**: \`search_notes\` + \`query_graph\` to identify 3–15 existing notes affected.
35
+ 2. **Plan the edits**: which existing notes need \`append_to_note\` / \`update_note\`, which new notes need \`create_note\`, and what \`[[wikilinks]]\` connect them.
36
+ 3. **Apply**: execute the plan. Every touched note gets:
37
+ - A \`source/<slug>\` tag or a "Sources" section listing provenance
38
+ - At least one \`[[wikilink]]\` to another touched note
39
+ 4. **Summarize**: tell the user which notes were created vs. updated.
40
+
41
+ A single source should rarely touch fewer than 3 notes — that's a sign you're treating the wiki as a dumping ground.
42
+
43
+ ## The Lint Loop
44
+
45
+ Periodically (session start on large workspaces; after ingests):
46
+
47
+ - **Contradictions** → call \`scan_knowledge_conflicts\`. Resolve, don't stack.
48
+ - **Orphans** → \`query_note_graph\` for notes with zero inbound or outbound links. Link them or delete.
49
+ - **Broken wikilinks** → \`[[X]]\` targets that don't exist. Create a stub note or fix the link.
50
+ - **Stale** → notes untouched >90 days referenced by new sources. Update them.
51
+
52
+ Report findings as a short list; ask before bulk-deleting.
18
53
 
19
54
  ## When to Store Knowledge
20
55
 
21
- Store knowledge **proactively** — don't wait for the user to ask. Store when you:
22
- - Make or discover an architecture decision → \`arch/{component}\`
23
- - Find a code pattern or convention → \`pattern/{name}\`
24
- - Debug and fix a bug → \`bug/{id-or-description}\`
25
- - Learn something about a dependency → \`dep/{package}\`
26
- - Make a product or tech decision → \`decision/{topic}\`
27
- - Build understanding of a domain area → \`context/{area}\`
28
- - Discover a gotcha or footgun → \`gotcha/{description}\`
29
- - Complete a task or story → \`task/{id}\`
56
+ Store **proactively** — don't wait to be asked. Categories:
57
+ - Architecture decision → \`arch/{component}\`
58
+ - Code pattern or convention → \`pattern/{name}\`
59
+ - Debugged bug → \`bug/{id}\`
60
+ - Dependency quirk → \`dep/{package}\`
61
+ - Product/tech decision → \`decision/{topic}\`
62
+ - Domain understanding → \`context/{area}\`
63
+ - Gotcha or footgun → \`gotcha/{description}\`
64
+ - Completed task → \`task/{id}\`
65
+
66
+ **Promotion rule**: if a knowledge entry is recalled 3+ times, promote it to a full note with \`create_note\` — it has earned a wiki page.
30
67
 
31
68
  ## When to Recall Knowledge
32
69
 
33
- Recall knowledge **before acting**. Specifically:
34
- - Before making a tech decision → recall \`arch/*\` and \`decision/*\`
35
- - Before touching a module → recall relevant \`pattern/*\` and \`context/*\`
36
- - Before debugging recall \`bug/*\` for similar past issues
37
- - Before adding a dependency → recall \`dep/*\`
38
- - At session start → \`project_context_load\` loads everything relevant
39
- - When the user asks about past work → \`session_context_resume\`
40
-
41
- ## Notes Your Working Documents
42
-
43
- m-notes is not just a knowledge base it's a full note-taking system. **Use notes actively** to create and maintain living documents:
44
-
45
- ### When to Create Notes
46
- - **Meeting notes** → \`create_note\` after any planning discussion or decision
47
- - **Investigation logs** → create a note when debugging, append findings as you go with \`append_to_note\`
48
- - **Design docs** → write architecture or design decisions as full notes, not just knowledge entries
49
- - **Task summaries** → after completing a story/task, create a note summarizing what was done
50
- - **Checklists and plans** → create notes with markdown checklists for multi-step work
51
- - **Daily notes** → use \`daily_note\` to create/get today's note for quick captures
52
-
53
- ### When to Edit Notes
54
- - **Append progress** → use \`append_to_note\` to add to existing notes as work progresses
55
- - **Update docs** → when code changes invalidate existing notes, update them with \`update_note\`
56
- - **Tag and organize** → use \`manage_tags\` and folder tools to keep notes findable
57
-
58
- ### Note vs Knowledge Entry
59
- | Use a **note** when... | Use **knowledge_store** when... |
70
+ Recall **before acting**:
71
+ - Tech decision → \`arch/*\` + \`decision/*\`
72
+ - Touching a module → relevant \`pattern/*\` + \`context/*\`
73
+ - Debugging → \`bug/*\` for similar past issues
74
+ - Adding a dependency → \`dep/*\`
75
+ - Session start → \`project_context_load\` (automatic via SessionStart hook)
76
+ - User asks about past work → \`session_context_resume\`
77
+
78
+ ## Notes vs. Knowledge Entries
79
+
80
+ In the wiki model, **notes are primary**. Knowledge entries are fast-capture for things that haven't earned a page yet.
81
+
82
+ | Use a **note** when... | Use \`knowledge_store\` when... |
60
83
  |---|---|
61
- | Content is long-form (paragraphs, lists, docs) | Content is a single fact or decision |
62
- | Document will be updated over time | Entry is a permanent record |
63
- | Needs folder organization | Needs key/tag retrieval |
64
- | Meeting notes, plans, investigations | Architecture decisions, gotchas, patterns |
84
+ | Content is long-form or evolving | Content is a single fact |
85
+ | Needs \`[[wikilinks]]\` and backlinks | Key/tag retrieval is enough |
86
+ | Subject will be referenced repeatedly | One-shot capture |
87
+ | Meeting notes, investigations, design docs | Architecture decisions, gotchas |
88
+
89
+ **When in doubt, create a note.**
65
90
 
66
- **When in doubt, create a note.** Notes are searchable, linkable, and visible in the UI.
91
+ ### Note Creation
92
+ - **Meetings / planning** → \`create_note\`
93
+ - **Investigations** → \`create_note\` then \`append_to_note\` as you go
94
+ - **Design / architecture** → full notes, not just knowledge entries
95
+ - **Task summaries** → after completing a story
96
+ - **Daily captures** → \`daily_note\`
67
97
 
68
- ## Knowledge Graph — Build Structured Memory
98
+ ### Note Maintenance
99
+ - \`append_to_note\` — add progress
100
+ - \`update_note\` — supersede stale content
101
+ - \`manage_tags\` / folder tools — keep notes findable
69
102
 
70
- Beyond flat knowledge entries, you have a **knowledge graph** for structured relationships between concepts. Use it to map how things connect architecture components, dependencies, decisions, and patterns.
103
+ ## Knowledge GraphNon-Optional
71
104
 
72
- ### When to Build the Graph
73
- - **Session start**: If the graph is empty, call \`populate_graph\` to initialize from existing notes and wikilinks.
74
- - **Architecture decisions**: Create concept nodes for components, link them with "related" or "parent" edges.
75
- - **Dependency discovery**: Create nodes for packages, link to the components that use them.
76
- - **Bug investigations**: Link bug nodes to the components and patterns involved.
77
- - **Any time you see a relationship**: If A relates to B, create an edge. The graph gets more valuable over time.
105
+ The graph is how the wiki stays navigable. **Every note you write or edit must have at least one outbound wikilink.**
106
+
107
+ ### When to Build
108
+ - **Session start**: call \`populate_graph\` if the graph is empty (idempotent).
109
+ - **Architecture decisions**: create concept nodes, link with \`related\` / \`parent\`.
110
+ - **Dependency discovery**: create nodes for packages, link to components using them.
111
+ - **Bug investigations**: link bug nodes to affected components/patterns.
112
+ - **Any A↔B relationship**: create the edge. The graph compounds.
78
113
 
79
114
  ### Graph Tools
80
115
  | Tool | When to use |
81
116
  |------|------------|
82
- | \`populate_graph\` | Initialize graph from existing notes (idempotent, safe to re-run) |
83
- | \`create_node\` | Create a concept, tag, or note-linked node |
84
- | \`create_edge\` | Link two nodes (types: wikilink, related, parent, tagged, custom) |
85
- | \`query_graph\` | Search the graph by node type, label, or connectivity |
86
- | \`get_neighbors\` | Explore nodes connected to a specific node |
87
- | \`query_note_graph\` | Get the connection subgraph around a note |
88
-
89
- ### Node Types
90
- - **note** — linked to an existing note (set \`noteId\`)
91
- - **tag** — represents a tag or category
92
- - **concept** — free-form concept (architecture component, pattern, decision)
93
-
94
- ### Edge Types
95
- - **wikilink** — note links to another note
96
- - **related** — general relationship
97
- - **parent** — hierarchical (component contains sub-component)
98
- - **tagged** — node is tagged with a category
99
- - **custom** — any other relationship (describe in metadata)
100
-
101
- ### Example: Mapping Architecture
117
+ | \`populate_graph\` | Initialize from existing notes (idempotent) |
118
+ | \`create_node\` | Add a concept/tag/note-linked node |
119
+ | \`create_edge\` | Link two nodes (wikilink, related, parent, tagged, custom) |
120
+ | \`query_graph\` | Search by type, label, or connectivity |
121
+ | \`get_neighbors\` | Explore connections from a node |
122
+ | \`query_note_graph\` | Get subgraph around a note |
123
+
124
+ Node types: **note**, **tag**, **concept**. Edge types: **wikilink**, **related**, **parent**, **tagged**, **custom**.
125
+
102
126
  \`\`\`
103
- create_node({ label: "Auth Module", nodeType: "concept", workspaceId: "..." })
104
- create_node({ label: "PostgreSQL", nodeType: "concept", workspaceId: "..." })
105
- create_edge({ sourceId: authNodeId, targetId: pgNodeId, edgeType: "related", workspaceId: "..." })
127
+ create_node({ label: "Auth Module", nodeType: "concept", workspaceId: "${opts.workspaceId}" })
128
+ create_node({ label: "PostgreSQL", nodeType: "concept", workspaceId: "${opts.workspaceId}" })
129
+ create_edge({ sourceId: authNodeId, targetId: pgNodeId, edgeType: "related", workspaceId: "${opts.workspaceId}" })
106
130
  \`\`\`
107
131
 
108
132
  ## MCP Tools Reference
@@ -110,49 +134,44 @@ create_edge({ sourceId: authNodeId, targetId: pgNodeId, edgeType: "related", wor
110
134
  ### Session & Context
111
135
  | Tool | When to use |
112
136
  |------|------------|
113
- | \`project_context_load\` | Session start — loads project context |
114
- | \`session_context_resume\` | Resume from previous session |
115
- | \`session_log\` | Log session summary at end |
137
+ | \`project_context_load\` | Session start |
138
+ | \`session_context_resume\` | Resume mid-conversation |
139
+ | \`session_log\` | Log summary at end |
116
140
 
117
- ### Knowledge (quick structured entries)
141
+ ### Knowledge (fast capture)
118
142
  | Tool | When to use |
119
143
  |------|------------|
120
- | \`knowledge_store\` | Store a knowledge entry (key, content, tags) |
121
- | \`recall_knowledge\` | Semantic search across stored knowledge |
122
- | \`bulk_knowledge_recall\` | Recall by tag patterns (e.g., all \`arch/*\`) |
123
- | \`knowledge_snapshot\` | Export all knowledge at once |
144
+ | \`knowledge_store\` | Store a single fact |
145
+ | \`recall_knowledge\` | Semantic search |
146
+ | \`bulk_knowledge_recall\` | Recall by tag pattern (e.g., \`arch/*\`) |
147
+ | \`knowledge_snapshot\` | Export all knowledge |
148
+ | \`scan_knowledge_conflicts\` | Lint: find contradictions |
124
149
 
125
- ### Notes (full documents)
150
+ ### Notes (wiki pages)
126
151
  | Tool | When to use |
127
152
  |------|------------|
128
- | \`create_note\` | Create a new note (title, content, folderId) |
129
- | \`update_note\` | Replace note content |
130
- | \`append_to_note\` | Add content to an existing note |
131
- | \`get_note\` | Read a note by ID |
132
- | \`get_note_by_title\` | Find a note by title |
133
- | \`search_notes\` | Full-text or semantic search |
134
- | \`list_notes\` | List notes in a folder |
135
- | \`daily_note\` | Create or get today's daily note |
136
- | \`manage_tags\` | Add/remove tags on notes |
137
- | \`pin_note\` / \`toggle_star\` | Pin or star important notes |
153
+ | \`create_note\` | New page |
154
+ | \`update_note\` | Replace content |
155
+ | \`append_to_note\` | Add to existing page |
156
+ | \`get_note\` / \`get_note_by_title\` | Read |
157
+ | \`search_notes\` | FTS + semantic search |
158
+ | \`list_notes\` | List by folder |
159
+ | \`daily_note\` | Today's capture note |
160
+ | \`manage_tags\` | Add/remove tags |
161
+ | \`pin_note\` / \`toggle_star\` | Elevate importance |
138
162
 
139
163
  ### Organization
140
164
  | Tool | When to use |
141
165
  |------|------------|
142
- | \`list_folders\` | List folders in workspace |
143
- | \`create_folder\` | Create a new folder |
144
- | \`move_note\` | Move note to a different folder |
166
+ | \`list_folders\` / \`manage_folders\` / \`move_folder\` / \`bulk_move\` | Folder + note-move ops |
145
167
  | \`context_fetch\` | Search notes by query |
146
168
 
147
169
  ### Knowledge Graph
148
170
  | Tool | When to use |
149
171
  |------|------------|
150
- | \`populate_graph\` | Initialize graph from notes (run once at start) |
151
- | \`create_node\` | Add a concept, tag, or note-linked node |
152
- | \`create_edge\` | Link two nodes with a typed relationship |
153
- | \`query_graph\` | Search graph by type, label, or connectivity |
154
- | \`get_neighbors\` | Explore connections from a node |
155
- | \`query_note_graph\` | Get subgraph around a specific note |
172
+ | \`populate_graph\` | Initialize graph |
173
+ | \`create_node\` / \`create_edge\` | Build structure |
174
+ | \`query_graph\` / \`get_neighbors\` / \`query_note_graph\` | Explore |
156
175
 
157
176
  All tools require \`workspaceId: "${opts.workspaceId}"\`.`;
158
177
  }
@@ -2,63 +2,67 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateCodexTemplate = generateCodexTemplate;
4
4
  function generateCodexTemplate(opts) {
5
- return `# m-notes AI Knowledge Base
5
+ return `<!-- m-notes instructions v3 -->
6
+ # m-notes — Your Wiki
6
7
 
7
8
  Server: ${opts.url}
8
9
  Workspace: ${opts.workspaceId}
9
10
 
11
+ You are the author and maintainer of a living wiki. Raw sources are immutable inputs; your notes are the wiki; notes tagged \`type:config\` are the rules you follow when editing.
12
+
10
13
  ## Session Lifecycle
11
14
 
12
15
  ### Session Start
13
- Call \`project_context_load\` with workspaceId "${opts.workspaceId}" and a query describing your current task.
14
- To resume a previous session, call \`session_context_resume\` with workspaceId "${opts.workspaceId}".
16
+ - Call \`project_context_load\` with workspaceId "${opts.workspaceId}".
17
+ - To resume, call \`session_context_resume\`.
18
+ - If the graph is empty, call \`populate_graph\` (idempotent).
19
+ - Read any notes tagged \`type:config\` before editing.
15
20
 
16
21
  ### During Work
17
- Store discoveries, decisions, and patterns via \`knowledge_store\`:
18
- - workspaceId: "${opts.workspaceId}"
19
- - key: "<category>/<name>"
20
- - content: what you learned
21
- - tags: [category]
22
+ - Recall before researching: \`recall_knowledge\`, \`search_notes\`.
23
+ - Store discoveries via \`knowledge_store\` (key: \`<category>/<name>\`, tags: [category]).
24
+ - When the user supplies a source (URL/paste/file), run the **ingest loop**:
25
+ 1. \`search_notes\` + \`query_graph\` to find 3–15 related notes
26
+ 2. Plan creates/updates with \`[[wikilinks]]\` connecting touched notes
27
+ 3. Apply — each touched note gets a \`source/<slug>\` tag
28
+ - Periodically run the **lint loop**: \`scan_knowledge_conflicts\`, find orphans via \`query_note_graph\`, fix broken \`[[wikilinks]]\`, update stale notes.
29
+ - Every new or edited note must have at least one outbound \`[[wikilink]]\`.
22
30
 
23
31
  ### Key Naming Conventions
24
- - arch/{component} -- architecture decisions (e.g. arch/database, arch/auth)
32
+ - arch/{component} -- architecture decisions
25
33
  - pattern/{name} -- code patterns and idioms
26
- - bug/{id} -- bug investigations and fixes
27
- - dep/{package} -- dependency notes and version constraints
28
- - decision/{topic} -- product/tech decisions with rationale
29
- - context/{area} -- project context and domain knowledge
34
+ - bug/{id} -- bug investigations
35
+ - dep/{package} -- dependency notes
36
+ - decision/{topic} -- product/tech decisions
37
+ - context/{area} -- domain knowledge
30
38
 
31
39
  ### Session End
32
- Call \`session_log\` with workspaceId "${opts.workspaceId}", a summary, decisions, and actions.
40
+ - Call \`session_log\` with workspaceId "${opts.workspaceId}", summary, decisions, actions.
33
41
 
34
42
  ## Knowledge Graph
35
43
 
36
- Build structured relationships between concepts as you work. The graph maps how architecture components, dependencies, decisions, and patterns connect.
37
-
38
- ### When to Build
39
- - Session start: call \`populate_graph\` if graph is empty (idempotent)
40
- - When you discover relationships between components, patterns, or decisions: create nodes and edges
41
- - Architecture decisions: create concept nodes, link with "related" or "parent" edges
44
+ Every note belongs to the graph. Build relationships proactively.
42
45
 
43
- ### Node types: note, tag, concept
44
- ### Edge types: wikilink, related, parent, tagged, custom
46
+ - Node types: note, tag, concept
47
+ - Edge types: wikilink, related, parent, tagged, custom
45
48
 
46
49
  Example:
47
50
  create_node({ label: "Auth Module", nodeType: "concept", workspaceId: "${opts.workspaceId}" })
48
51
  create_edge({ sourceId: "...", targetId: "...", edgeType: "related", workspaceId: "${opts.workspaceId}" })
49
52
 
50
53
  ## Available MCP Tools
51
- - project_context_load -- load project context at session start
52
- - session_context_resume -- resume from previous session
53
- - knowledge_store -- store knowledge entries
54
- - recall_knowledge -- semantic search for knowledge
55
- - bulk_knowledge_recall -- recall by tag patterns
54
+ - project_context_load -- load context at session start
55
+ - session_context_resume -- resume previous session
56
+ - knowledge_store -- store knowledge entry
57
+ - recall_knowledge -- semantic search
58
+ - bulk_knowledge_recall -- recall by tag pattern
56
59
  - knowledge_snapshot -- export all knowledge
60
+ - scan_knowledge_conflicts -- lint for contradictions
57
61
  - session_log -- log session summary
62
+ - create_note / update_note / append_to_note -- note authoring
63
+ - search_notes / get_note / list_notes -- note retrieval
58
64
  - context_fetch -- search notes by query
59
- - populate_graph -- initialize knowledge graph from notes
60
- - create_node -- add a concept/tag/note node to the graph
61
- - create_edge -- link two nodes with a typed relationship
62
- - query_graph -- search graph by type, label, or connectivity
63
- - get_neighbors -- explore connections from a node`;
65
+ - populate_graph -- initialize graph (idempotent)
66
+ - create_node / create_edge -- build graph structure
67
+ - query_graph / get_neighbors / query_note_graph -- explore graph`;
64
68
  }
@@ -2,11 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateOpenClawTemplate = generateOpenClawTemplate;
4
4
  function generateOpenClawTemplate(opts) {
5
- return `# m-notes Knowledge Base
5
+ return `<!-- m-notes instructions v3 -->
6
+ # m-notes — Your Wiki
6
7
 
7
8
  **Server**: ${opts.url}
8
9
  **Workspace**: ${opts.workspaceId}
9
10
 
11
+ You are the author of a living wiki. Notes are interlinked with \`[[wikilinks]]\`. Sources are immutable; your writing is the wiki.
12
+
13
+ ## Core Loops
14
+
15
+ **Ingest** — when given a source (URL/paste): find 3+ related notes via \`recall_knowledge\`, update or create, link them with \`[[wikilinks]]\`, tag each with \`source/<slug>\`.
16
+
17
+ **Lint** — periodically check for contradictions, orphan notes, broken wikilinks, stale entries.
18
+
10
19
  ## Quick Reference
11
20
 
12
21
  Store knowledge:
@@ -33,11 +42,11 @@ Call recall_knowledge with:
33
42
 
34
43
  ## Knowledge Graph
35
44
 
36
- Build structured relationships as you work:
45
+ Every note should link to at least one other. Build the graph proactively:
37
46
  \`\`\`
38
47
  Call populate_graph with:
39
48
  - workspaceId: "${opts.workspaceId}"
40
- (initializes graph from existing notes — run once)
49
+ (initializes from existing notes — idempotent)
41
50
 
42
51
  Call create_node with:
43
52
  - label: "<concept name>"
@@ -52,14 +61,14 @@ Call create_edge with:
52
61
  \`\`\`
53
62
 
54
63
  ## Available MCP Tools
55
- - \`knowledge_store\` -- Store knowledge entries
56
- - \`recall_knowledge\` -- Semantic search for knowledge
57
- - \`bulk_knowledge_recall\` -- Recall by tag patterns
64
+ - \`knowledge_store\` -- Store knowledge
65
+ - \`recall_knowledge\` -- Semantic search
66
+ - \`bulk_knowledge_recall\` -- Recall by tag pattern
58
67
  - \`knowledge_snapshot\` -- Export all knowledge
59
- - \`context_fetch\` -- Search notes by query
60
- - \`populate_graph\` -- Initialize knowledge graph from notes
61
- - \`create_node\` -- Add concept/tag/note node to the graph
62
- - \`create_edge\` -- Link two nodes with a typed relationship
63
- - \`query_graph\` -- Search graph by type, label, or connectivity
64
- - \`get_neighbors\` -- Explore connections from a node`;
68
+ - \`scan_knowledge_conflicts\` -- Lint: find contradictions
69
+ - \`context_fetch\` / \`search_notes\` -- Search notes
70
+ - \`create_note\` / \`update_note\` / \`append_to_note\` -- Note authoring
71
+ - \`populate_graph\` -- Initialize graph
72
+ - \`create_node\` / \`create_edge\` -- Build graph structure
73
+ - \`query_graph\` / \`get_neighbors\` / \`query_note_graph\` -- Explore graph`;
65
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnotes-cli",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "CLI for m-notes AI knowledge base — manage notes, search, and CRUD from the terminal",
5
5
  "bin": {
6
6
  "mnotes": "./dist/index.js"