@pcircle/memesh 2.8.11 โ†’ 2.9.1

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 (79) hide show
  1. package/LICENSE +21 -661
  2. package/README.de.md +171 -0
  3. package/README.es.md +171 -0
  4. package/README.fr.md +171 -0
  5. package/README.id.md +171 -0
  6. package/README.ja.md +171 -0
  7. package/README.ko.md +171 -0
  8. package/README.md +73 -100
  9. package/README.th.md +171 -0
  10. package/README.vi.md +171 -0
  11. package/README.zh-CN.md +171 -0
  12. package/README.zh-TW.md +71 -98
  13. package/dist/knowledge-graph/index.d.ts +22 -1
  14. package/dist/knowledge-graph/index.d.ts.map +1 -1
  15. package/dist/knowledge-graph/index.js +144 -3
  16. package/dist/knowledge-graph/index.js.map +1 -1
  17. package/dist/mcp/ServerInitializer.d.ts.map +1 -1
  18. package/dist/mcp/ServerInitializer.js +1 -1
  19. package/dist/mcp/ServerInitializer.js.map +1 -1
  20. package/dist/mcp/ToolDefinitions.d.ts.map +1 -1
  21. package/dist/mcp/ToolDefinitions.js +47 -55
  22. package/dist/mcp/ToolDefinitions.js.map +1 -1
  23. package/dist/mcp/ToolRouter.d.ts.map +1 -1
  24. package/dist/mcp/ToolRouter.js +4 -4
  25. package/dist/mcp/ToolRouter.js.map +1 -1
  26. package/dist/mcp/daemon/StdioProxyClient.d.ts.map +1 -1
  27. package/dist/mcp/daemon/StdioProxyClient.js +9 -1
  28. package/dist/mcp/daemon/StdioProxyClient.js.map +1 -1
  29. package/dist/mcp/handlers/BuddyHandlers.d.ts +3 -1
  30. package/dist/mcp/handlers/BuddyHandlers.d.ts.map +1 -1
  31. package/dist/mcp/handlers/BuddyHandlers.js +6 -5
  32. package/dist/mcp/handlers/BuddyHandlers.js.map +1 -1
  33. package/dist/mcp/handlers/ToolHandlers.d.ts.map +1 -1
  34. package/dist/mcp/handlers/ToolHandlers.js +1 -2
  35. package/dist/mcp/handlers/ToolHandlers.js.map +1 -1
  36. package/dist/mcp/resources/quick-reference.md +1 -1
  37. package/dist/mcp/schemas/OutputSchemas.d.ts +116 -53
  38. package/dist/mcp/schemas/OutputSchemas.d.ts.map +1 -1
  39. package/dist/mcp/schemas/OutputSchemas.js +64 -26
  40. package/dist/mcp/schemas/OutputSchemas.js.map +1 -1
  41. package/dist/mcp/server-bootstrap.js +89 -9
  42. package/dist/mcp/server-bootstrap.js.map +1 -1
  43. package/dist/mcp/tools/buddy-do.d.ts +2 -1
  44. package/dist/mcp/tools/buddy-do.d.ts.map +1 -1
  45. package/dist/mcp/tools/buddy-do.js +91 -4
  46. package/dist/mcp/tools/buddy-do.js.map +1 -1
  47. package/dist/mcp/tools/buddy-remember.d.ts +0 -5
  48. package/dist/mcp/tools/buddy-remember.d.ts.map +1 -1
  49. package/dist/mcp/tools/buddy-remember.js.map +1 -1
  50. package/dist/mcp/tools/memesh-agent-register.d.ts +20 -0
  51. package/dist/mcp/tools/memesh-agent-register.d.ts.map +1 -0
  52. package/dist/mcp/tools/memesh-agent-register.js +80 -0
  53. package/dist/mcp/tools/memesh-agent-register.js.map +1 -0
  54. package/dist/mcp/tools/memesh-cloud-sync.js +27 -8
  55. package/dist/mcp/tools/memesh-cloud-sync.js.map +1 -1
  56. package/dist/mcp/tools/memesh-metrics.d.ts +13 -0
  57. package/dist/mcp/tools/memesh-metrics.d.ts.map +1 -0
  58. package/dist/mcp/tools/memesh-metrics.js +193 -0
  59. package/dist/mcp/tools/memesh-metrics.js.map +1 -0
  60. package/dist/memory/UnifiedMemoryStore.d.ts +1 -1
  61. package/dist/memory/UnifiedMemoryStore.d.ts.map +1 -1
  62. package/dist/memory/UnifiedMemoryStore.js +4 -3
  63. package/dist/memory/UnifiedMemoryStore.js.map +1 -1
  64. package/package.json +9 -12
  65. package/plugin.json +2 -2
  66. package/scripts/hooks/README.md +230 -0
  67. package/scripts/hooks/__tests__/hook-test-harness.js +218 -0
  68. package/scripts/hooks/__tests__/hooks.test.js +267 -0
  69. package/scripts/hooks/hook-utils.js +899 -0
  70. package/scripts/hooks/post-commit.js +307 -0
  71. package/scripts/hooks/post-tool-use.js +812 -0
  72. package/scripts/hooks/pre-tool-use.js +462 -0
  73. package/scripts/hooks/session-start.js +544 -0
  74. package/scripts/hooks/stop.js +673 -0
  75. package/scripts/hooks/subagent-stop.js +184 -0
  76. package/scripts/hooks/templates/planning-template.md +46 -0
  77. package/scripts/postinstall-lib.js +8 -4
  78. package/scripts/postinstall-new.js +110 -7
  79. package/scripts/skills/comprehensive-code-review/SKILL.md +276 -0
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Hook Test Harness โ€” Simulates Claude Code hook execution.
5
+ *
6
+ * Pipes mock stdin JSON to a hook script and validates the output.
7
+ * Does NOT require Claude Code runtime.
8
+ *
9
+ * Usage:
10
+ * node hook-test-harness.js <hook-script> <mock-stdin-json>
11
+ * node hook-test-harness.js ../pre-tool-use.js '{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}'
12
+ *
13
+ * Or programmatically:
14
+ * import { runHook, assertJSON, assertContains } from './hook-test-harness.js';
15
+ */
16
+
17
+ import { execFile } from 'child_process';
18
+ import path from 'path';
19
+ import { fileURLToPath } from 'url';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+
24
+ // ============================================================================
25
+ // Core Test Functions
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Run a hook script with mock stdin and capture output.
30
+ *
31
+ * @param {string} hookPath - Path to hook script (relative to hooks/ dir or absolute)
32
+ * @param {Object|string} stdinData - JSON data to pipe as stdin
33
+ * @param {Object} options - Options
34
+ * @param {number} options.timeout - Timeout in ms (default: 10000)
35
+ * @param {Object} options.env - Additional environment variables
36
+ * @returns {Promise<{ stdout: string, stderr: string, exitCode: number, parsed: Object|null }>}
37
+ */
38
+ export function runHook(hookPath, stdinData, options = {}) {
39
+ const { timeout = 10000, env = {} } = options;
40
+
41
+ // Resolve hook path relative to hooks directory
42
+ const resolvedPath = path.isAbsolute(hookPath)
43
+ ? hookPath
44
+ : path.resolve(__dirname, '..', hookPath);
45
+
46
+ const stdinStr = typeof stdinData === 'string'
47
+ ? stdinData
48
+ : JSON.stringify(stdinData);
49
+
50
+ return new Promise((resolve) => {
51
+ const child = execFile('node', [resolvedPath], {
52
+ encoding: 'utf-8',
53
+ timeout,
54
+ env: { ...process.env, ...env },
55
+ }, (error, stdout, stderr) => {
56
+ let parsed = null;
57
+ try {
58
+ if (stdout.trim()) {
59
+ parsed = JSON.parse(stdout.trim());
60
+ }
61
+ } catch {
62
+ // Not JSON output โ€” that's fine for some hooks
63
+ }
64
+
65
+ resolve({
66
+ stdout: stdout || '',
67
+ stderr: stderr || '',
68
+ exitCode: error ? (error.code || 1) : 0,
69
+ parsed,
70
+ });
71
+ });
72
+
73
+ // Pipe stdin
74
+ if (child.stdin) {
75
+ child.stdin.write(stdinStr);
76
+ child.stdin.end();
77
+ }
78
+ });
79
+ }
80
+
81
+ // ============================================================================
82
+ // Assertion Helpers
83
+ // ============================================================================
84
+
85
+ /**
86
+ * Assert that the hook output is valid JSON with expected structure.
87
+ * @param {Object} result - Result from runHook()
88
+ * @param {string} hookEventName - Expected hookEventName
89
+ * @returns {boolean}
90
+ */
91
+ export function assertHookResponse(result, hookEventName) {
92
+ if (!result.parsed) {
93
+ console.error(` FAIL: No JSON output`);
94
+ return false;
95
+ }
96
+
97
+ const output = result.parsed.hookSpecificOutput;
98
+ if (!output) {
99
+ console.error(` FAIL: Missing hookSpecificOutput`);
100
+ return false;
101
+ }
102
+
103
+ if (output.hookEventName !== hookEventName) {
104
+ console.error(` FAIL: hookEventName is "${output.hookEventName}", expected "${hookEventName}"`);
105
+ return false;
106
+ }
107
+
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Assert that stdout contains a substring.
113
+ * @param {Object} result - Result from runHook()
114
+ * @param {string} substring - Expected substring
115
+ * @returns {boolean}
116
+ */
117
+ export function assertContains(result, substring) {
118
+ const fullOutput = result.stdout + result.stderr;
119
+ if (!fullOutput.includes(substring)) {
120
+ console.error(` FAIL: Output does not contain "${substring}"`);
121
+ return false;
122
+ }
123
+ return true;
124
+ }
125
+
126
+ /**
127
+ * Assert hook exited silently (no stdout, exit 0).
128
+ * @param {Object} result - Result from runHook()
129
+ * @returns {boolean}
130
+ */
131
+ export function assertSilent(result) {
132
+ if (result.stdout.trim() !== '') {
133
+ console.error(` FAIL: Expected silent exit, got stdout: ${result.stdout.substring(0, 100)}`);
134
+ return false;
135
+ }
136
+ return true;
137
+ }
138
+
139
+ // ============================================================================
140
+ // Test Runner
141
+ // ============================================================================
142
+
143
+ /**
144
+ * Simple test runner for hook tests.
145
+ * @param {string} suiteName - Test suite name
146
+ * @param {Array<{name: string, fn: Function}>} tests - Test cases
147
+ */
148
+ export async function runTests(suiteName, tests) {
149
+ console.log(`\n ${suiteName}`);
150
+ console.log(' ' + 'โ”€'.repeat(50));
151
+
152
+ let passed = 0;
153
+ let failed = 0;
154
+
155
+ for (const test of tests) {
156
+ try {
157
+ const result = await test.fn();
158
+ if (result !== false) {
159
+ console.log(` โœ… ${test.name}`);
160
+ passed++;
161
+ } else {
162
+ console.log(` โŒ ${test.name}`);
163
+ failed++;
164
+ }
165
+ } catch (error) {
166
+ console.log(` โŒ ${test.name}`);
167
+ console.error(` Error: ${error.message}`);
168
+ failed++;
169
+ }
170
+ }
171
+
172
+ console.log(' ' + 'โ”€'.repeat(50));
173
+ console.log(` Results: ${passed} passed, ${failed} failed\n`);
174
+
175
+ return { passed, failed };
176
+ }
177
+
178
+ // ============================================================================
179
+ // CLI Mode
180
+ // ============================================================================
181
+
182
+ async function main() {
183
+ const args = process.argv.slice(2);
184
+
185
+ if (args.length < 2) {
186
+ console.log('Usage: node hook-test-harness.js <hook-script> <mock-stdin-json>');
187
+ console.log('');
188
+ console.log('Examples:');
189
+ console.log(' node hook-test-harness.js pre-tool-use.js \'{"tool_name":"Bash","tool_input":{"command":"git commit -m test"}}\'');
190
+ console.log(' node hook-test-harness.js post-tool-use.js \'{"tool_name":"Read","tool_input":{"file_path":"/tmp/test.js"}}\'');
191
+ process.exit(0);
192
+ }
193
+
194
+ const [hookScript, stdinJSON] = args;
195
+
196
+ console.log(`\nRunning: ${hookScript}`);
197
+ console.log(`Stdin: ${stdinJSON.substring(0, 100)}...`);
198
+ console.log('');
199
+
200
+ const result = await runHook(hookScript, stdinJSON);
201
+
202
+ console.log(`Exit code: ${result.exitCode}`);
203
+ if (result.stdout.trim()) {
204
+ console.log(`Stdout: ${result.stdout.trim()}`);
205
+ }
206
+ if (result.stderr.trim()) {
207
+ console.log(`Stderr: ${result.stderr.trim()}`);
208
+ }
209
+ if (result.parsed) {
210
+ console.log(`Parsed JSON:`);
211
+ console.log(JSON.stringify(result.parsed, null, 2));
212
+ }
213
+ }
214
+
215
+ // Run CLI mode if invoked directly
216
+ if (process.argv[1] && process.argv[1].includes('hook-test-harness')) {
217
+ main().catch(console.error);
218
+ }
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Hook Tests โ€” Validates hook behavior without Claude Code runtime.
5
+ *
6
+ * Run: node scripts/hooks/__tests__/hooks.test.js
7
+ */
8
+
9
+ import { runHook, assertHookResponse, assertSilent, runTests } from './hook-test-harness.js';
10
+
11
+ // ============================================================================
12
+ // PreToolUse Tests
13
+ // ============================================================================
14
+
15
+ const preToolUseTests = [
16
+ {
17
+ name: 'Git commit without review triggers reminder or silent exit',
18
+ fn: async () => {
19
+ const result = await runHook('pre-tool-use.js', {
20
+ tool_name: 'Bash',
21
+ tool_input: { command: 'git commit -m "test commit"' },
22
+ });
23
+ // Should produce JSON output with review reminder
24
+ // OR exit silently if codeReviewDone=true from prior test
25
+ if (result.parsed) {
26
+ return assertHookResponse(result, 'PreToolUse');
27
+ }
28
+ // Silent exit is acceptable (review already done or no session file)
29
+ return assertSilent(result);
30
+ },
31
+ },
32
+ {
33
+ name: 'Non-git-commit Bash exits silently',
34
+ fn: async () => {
35
+ const result = await runHook('pre-tool-use.js', {
36
+ tool_name: 'Bash',
37
+ tool_input: { command: 'ls -la' },
38
+ });
39
+ return assertSilent(result);
40
+ },
41
+ },
42
+ {
43
+ name: 'Non-Bash tool exits silently',
44
+ fn: async () => {
45
+ const result = await runHook('pre-tool-use.js', {
46
+ tool_name: 'Read',
47
+ tool_input: { file_path: '/tmp/test.js' },
48
+ });
49
+ return assertSilent(result);
50
+ },
51
+ },
52
+ {
53
+ name: 'Git amend exits silently',
54
+ fn: async () => {
55
+ const result = await runHook('pre-tool-use.js', {
56
+ tool_name: 'Bash',
57
+ tool_input: { command: 'git commit --amend -m "fix"' },
58
+ });
59
+ return assertSilent(result);
60
+ },
61
+ },
62
+ {
63
+ name: 'Empty stdin exits without error',
64
+ fn: async () => {
65
+ const result = await runHook('pre-tool-use.js', '');
66
+ return result.exitCode === 0;
67
+ },
68
+ },
69
+ ];
70
+
71
+ // ============================================================================
72
+ // PreToolUse โ€” Smart Router Tests (1B)
73
+ // ============================================================================
74
+
75
+ const smartRouterTests = [
76
+ {
77
+ name: 'Task(Explore) gets model routing or silent exit',
78
+ fn: async () => {
79
+ const result = await runHook('pre-tool-use.js', {
80
+ tool_name: 'Task',
81
+ tool_input: { subagent_type: 'Explore', prompt: 'find auth code' },
82
+ });
83
+ // Either: hook produces routing output with haiku, or exits silently (no config)
84
+ if (!result.parsed) {
85
+ return assertSilent(result); // No output = no config, valid
86
+ }
87
+ const output = result.parsed?.hookSpecificOutput;
88
+ if (!output) return false; // Parsed but no hookSpecificOutput = malformed
89
+ // If routing was applied, model should be haiku
90
+ if (output.updatedInput?.model) {
91
+ return output.updatedInput.model === 'haiku';
92
+ }
93
+ // Output present but no model routing = other handler fired, OK
94
+ return true;
95
+ },
96
+ },
97
+ {
98
+ name: 'Task with explicit model preserves user choice',
99
+ fn: async () => {
100
+ const result = await runHook('pre-tool-use.js', {
101
+ tool_name: 'Task',
102
+ tool_input: { subagent_type: 'Explore', model: 'opus', prompt: 'deep analysis' },
103
+ });
104
+ // Should NOT override user's explicit model
105
+ if (result.parsed?.hookSpecificOutput?.updatedInput?.model) {
106
+ // Any model override when user specified 'opus' is wrong
107
+ return false;
108
+ }
109
+ // No model override = correct behavior
110
+ return true;
111
+ },
112
+ },
113
+ {
114
+ name: 'Task(Plan) gets planning template injected',
115
+ fn: async () => {
116
+ const result = await runHook('pre-tool-use.js', {
117
+ tool_name: 'Task',
118
+ tool_input: { subagent_type: 'Plan', prompt: 'plan the auth refactor' },
119
+ });
120
+ if (!result.parsed) {
121
+ // No output = template file not found. Acceptable but log it.
122
+ return assertSilent(result);
123
+ }
124
+ const output = result.parsed?.hookSpecificOutput;
125
+ if (!output) return false; // Parsed but no hookSpecificOutput = malformed
126
+ // Prompt should contain original + template content
127
+ if (output.updatedInput?.prompt) {
128
+ const prompt = output.updatedInput.prompt;
129
+ // Must contain original prompt AND some template content
130
+ return prompt.includes('plan the auth refactor') &&
131
+ (prompt.includes('Required Plan Sections') || prompt.includes('---'));
132
+ }
133
+ // No prompt modification = handler didn't fire, unexpected
134
+ return false;
135
+ },
136
+ },
137
+ {
138
+ name: 'EnterPlanMode gets context with template reference',
139
+ fn: async () => {
140
+ const result = await runHook('pre-tool-use.js', {
141
+ tool_name: 'EnterPlanMode',
142
+ tool_input: {},
143
+ });
144
+ if (!result.parsed) {
145
+ // No output = template file not found. Acceptable.
146
+ return assertSilent(result);
147
+ }
148
+ const output = result.parsed?.hookSpecificOutput;
149
+ if (!output) return false; // Parsed but no hookSpecificOutput = malformed
150
+ if (output.additionalContext) {
151
+ return output.additionalContext.includes('PLANNING MODE');
152
+ }
153
+ // hookSpecificOutput without additionalContext = wrong handler response
154
+ return false;
155
+ },
156
+ },
157
+ {
158
+ name: 'Non-Task tool is not affected by routing',
159
+ fn: async () => {
160
+ const result = await runHook('pre-tool-use.js', {
161
+ tool_name: 'Grep',
162
+ tool_input: { pattern: 'test', path: '/tmp' },
163
+ });
164
+ return assertSilent(result);
165
+ },
166
+ },
167
+ ];
168
+
169
+ // ============================================================================
170
+ // PostToolUse Tests
171
+ // ============================================================================
172
+
173
+ const postToolUseTests = [
174
+ {
175
+ name: 'Read tool exits silently',
176
+ fn: async () => {
177
+ const result = await runHook('post-tool-use.js', {
178
+ tool_name: 'Read',
179
+ tool_input: { file_path: '/tmp/test.js' },
180
+ success: true,
181
+ });
182
+ return assertSilent(result);
183
+ },
184
+ },
185
+ {
186
+ name: 'Bash tool exits silently',
187
+ fn: async () => {
188
+ const result = await runHook('post-tool-use.js', {
189
+ tool_name: 'Bash',
190
+ tool_input: { command: 'echo hello' },
191
+ success: true,
192
+ });
193
+ return assertSilent(result);
194
+ },
195
+ },
196
+ {
197
+ name: 'Empty stdin exits without error',
198
+ fn: async () => {
199
+ const result = await runHook('post-tool-use.js', '');
200
+ return result.exitCode === 0;
201
+ },
202
+ },
203
+ ];
204
+
205
+ // ============================================================================
206
+ // PostCommit Tests
207
+ // ============================================================================
208
+
209
+ const postCommitTests = [
210
+ {
211
+ name: 'Non-git-commit exits silently',
212
+ fn: async () => {
213
+ const result = await runHook('post-commit.js', {
214
+ tool_name: 'Read',
215
+ tool_input: { file_path: '/tmp/test.js' },
216
+ success: true,
217
+ });
218
+ return assertSilent(result);
219
+ },
220
+ },
221
+ {
222
+ name: 'Failed command exits silently',
223
+ fn: async () => {
224
+ const result = await runHook('post-commit.js', {
225
+ tool_name: 'Bash',
226
+ tool_input: { command: 'git commit -m "test"' },
227
+ success: false,
228
+ });
229
+ return assertSilent(result);
230
+ },
231
+ },
232
+ ];
233
+
234
+ // ============================================================================
235
+ // Run All Tests
236
+ // ============================================================================
237
+
238
+ async function main() {
239
+ console.log('\n๐Ÿงช Hook Test Suite\n');
240
+
241
+ let totalPassed = 0;
242
+ let totalFailed = 0;
243
+
244
+ const suites = [
245
+ { name: 'PreToolUse Hook (Code Review)', tests: preToolUseTests },
246
+ { name: 'PreToolUse Hook (Smart Router)', tests: smartRouterTests },
247
+ { name: 'PostToolUse Hook', tests: postToolUseTests },
248
+ { name: 'PostCommit Hook', tests: postCommitTests },
249
+ ];
250
+
251
+ for (const suite of suites) {
252
+ const { passed, failed } = await runTests(suite.name, suite.tests);
253
+ totalPassed += passed;
254
+ totalFailed += failed;
255
+ }
256
+
257
+ console.log('โ•'.repeat(55));
258
+ console.log(` Total: ${totalPassed} passed, ${totalFailed} failed`);
259
+ console.log('โ•'.repeat(55));
260
+
261
+ process.exit(totalFailed > 0 ? 1 : 0);
262
+ }
263
+
264
+ main().catch(error => {
265
+ console.error('Test runner error:', error);
266
+ process.exit(1);
267
+ });