kodelyth-ecc 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/VERSION +1 -1
- package/bin/kodelyth-ecc.js +54 -5
- package/install.ps1 +30 -13
- package/install.sh +11 -4
- package/package.json +1 -1
- package/rules/common/agent-intent-routing.md +96 -195
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -47
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -41
- package/.github/ISSUE_TEMPLATE/good_first_issue.md +0 -36
- package/.github/ISSUE_TEMPLATE/new_agent.md +0 -52
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -53
- package/.github/workflows/ci.yml +0 -45
- package/.github/workflows/publish.yml +0 -60
- package/brand/concepts.svg +0 -109
- package/brand/convert.js +0 -161
- package/brand/favicon.svg +0 -7
- package/brand/fb-cover.svg +0 -34
- package/brand/fb-profile.svg +0 -21
- package/brand/kodelyth-dark.svg +0 -24
- package/brand/kodelyth-light.svg +0 -24
- package/brand/kodelyth-mark.svg +0 -20
- package/brand/package.json +0 -12
- package/docs/dashboard.md +0 -211
- package/docs/evolve.md +0 -303
- package/docs/mcp-clients.md +0 -167
- package/docs/mcp.md +0 -178
- package/docs/replay.md +0 -244
- package/docs/supply-chain.md +0 -207
- package/docs/swarm.md +0 -243
- package/skills/skill-comply/tests/test_grader.py +0 -137
- package/skills/skill-comply/tests/test_parser.py +0 -90
- package/social/card-agents.svg +0 -104
- package/social/card-install.svg +0 -58
- package/social/card-main.svg +0 -54
- package/social/facebook-v150.svg +0 -110
- package/social/fb-ad-main.svg +0 -128
- package/social/fb-post-features.svg +0 -118
- package/social/fb-post-launch.svg +0 -144
- package/social/fb-post-platforms.svg +0 -135
- package/social/github-social-preview.svg +0 -151
- package/social/hype-compound-learning.svg +0 -129
- package/social/hype-devil-mode.svg +0 -240
- package/social/hype-mcp-server.svg +0 -148
- package/social/hype-parallel-agents.svg +0 -162
- package/social/hype-stats-hero.svg +0 -138
- package/social/og-image.svg +0 -151
- package/social/readme-agents.svg +0 -138
- package/social/readme-hero.svg +0 -102
- package/social/section-agents.svg +0 -69
- package/social/section-author.svg +0 -66
- package/social/section-dashboard.svg +0 -71
- package/social/section-devil.svg +0 -66
- package/social/section-hooks.svg +0 -63
- package/social/section-install.svg +0 -54
- package/social/section-learning.svg +0 -64
- package/social/section-mcp.svg +0 -58
- package/social/section-memory.svg +0 -69
- package/social/section-parallel.svg +0 -84
- package/social/section-routing.svg +0 -63
- package/social/twitter-threads.md +0 -621
- package/social/x-card-agents-grid.svg +0 -120
- package/social/x-card-free.svg +0 -71
- package/social/x-card-hook.svg +0 -94
- package/tests/dashboard/data.test.js +0 -391
- package/tests/dashboard/server.test.js +0 -257
- package/tests/evolve/analyze.test.js +0 -169
- package/tests/evolve/proposals.test.js +0 -173
- package/tests/evolve/stats.test.js +0 -167
- package/tests/hooks/branch-name-check.test.js +0 -184
- package/tests/hooks/smart-suggest.test.js +0 -149
- package/tests/hooks/test-reminder.test.js +0 -181
- package/tests/mcp/catalog.test.js +0 -98
- package/tests/mcp/client.test.js +0 -113
- package/tests/mcp/resources-prompts.test.js +0 -70
- package/tests/mcp/tools.test.js +0 -159
- package/tests/memory/auto-recall.test.js +0 -132
- package/tests/memory/instincts.test.js +0 -258
- package/tests/memory/store.test.js +0 -203
- package/tests/replay/bundle.test.js +0 -181
- package/tests/replay/replay.test.js +0 -131
- package/tests/router/classify.test.js +0 -164
- package/tests/run-all.js +0 -55
- package/tests/safety/patterns.test.js +0 -94
- package/tests/safety/prompt-injection-guard.test.js +0 -111
- package/tests/safety/token-budget.test.js +0 -119
- package/tests/supply-chain/manifest.test.js +0 -147
- package/tests/supply-chain/sbom.test.js +0 -170
- package/tests/supply-chain/verify.test.js +0 -146
- package/tests/swarm/build-plan.test.js +0 -188
package/tests/mcp/client.test.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
// Tests for scripts/mcp/client.js — registry + connect helpers.
|
|
2
|
-
// Uses the LOCAL Kodelyth MCP server itself as a real test target so we
|
|
3
|
-
// exercise the full stdio handshake without needing external dependencies.
|
|
4
|
-
'use strict';
|
|
5
|
-
|
|
6
|
-
const test = require('node:test');
|
|
7
|
-
const assert = require('node:assert/strict');
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// Isolate the registry to a tmp dir so we don't pollute ~/.kodelyth.
|
|
13
|
-
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-mcp-client-'));
|
|
14
|
-
process.env.KODELYTH_MCP_CLIENT_DIR = TMP;
|
|
15
|
-
|
|
16
|
-
const C = require('../../scripts/mcp/client.js');
|
|
17
|
-
|
|
18
|
-
const ROOT = path.join(__dirname, '..', '..');
|
|
19
|
-
const LOCAL_SERVER = path.join(ROOT, 'scripts', 'mcp', 'server.js');
|
|
20
|
-
|
|
21
|
-
// ── Registry tests (no SDK required) ─────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
test('registry: starts empty', () => {
|
|
24
|
-
const reg = C.loadRegistry();
|
|
25
|
-
assert.deepEqual(reg.servers, {});
|
|
26
|
-
assert.deepEqual(C.listServers(), []);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('registry: addServer persists with sane defaults', () => {
|
|
30
|
-
const spec = C.addServer({
|
|
31
|
-
name: 'self',
|
|
32
|
-
command: process.execPath,
|
|
33
|
-
args: [LOCAL_SERVER],
|
|
34
|
-
env: { TEST_VAR: '1' },
|
|
35
|
-
description: 'local self-test target',
|
|
36
|
-
});
|
|
37
|
-
assert.equal(spec.name, 'self');
|
|
38
|
-
assert.equal(spec.command, process.execPath);
|
|
39
|
-
assert.deepEqual(spec.args, [LOCAL_SERVER]);
|
|
40
|
-
assert.equal(spec.env.TEST_VAR, '1');
|
|
41
|
-
assert.ok(spec.added_at);
|
|
42
|
-
|
|
43
|
-
// Persisted to disk:
|
|
44
|
-
assert.ok(fs.existsSync(C.REGISTRY_FILE));
|
|
45
|
-
const onDisk = JSON.parse(fs.readFileSync(C.REGISTRY_FILE, 'utf8'));
|
|
46
|
-
assert.equal(onDisk.servers.self.name, 'self');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('registry: rejects bad names', () => {
|
|
50
|
-
assert.throws(() => C.addServer({ name: '', command: 'x' }));
|
|
51
|
-
assert.throws(() => C.addServer({ name: 'has space',command: 'x' }));
|
|
52
|
-
assert.throws(() => C.addServer({ name: 'self', command: '' }));
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('registry: getServer / listServers reflect adds + removes', () => {
|
|
56
|
-
C.addServer({ name: 'second', command: '/usr/bin/true', args: [] });
|
|
57
|
-
const list = C.listServers();
|
|
58
|
-
assert.equal(list.length, 2, `expected 2, got ${list.length}`);
|
|
59
|
-
const names = list.map(s => s.name).sort();
|
|
60
|
-
assert.deepEqual(names, ['second', 'self']);
|
|
61
|
-
|
|
62
|
-
assert.ok(C.getServer('self'));
|
|
63
|
-
assert.equal(C.getServer('not-real'), null);
|
|
64
|
-
|
|
65
|
-
assert.equal(C.removeServer('second'), true);
|
|
66
|
-
assert.equal(C.removeServer('second'), false); // already gone
|
|
67
|
-
assert.equal(C.listServers().length, 1);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// ── End-to-end: connect to local kodelyth MCP server and call tools ──────────
|
|
71
|
-
// These tests require @modelcontextprotocol/sdk (optionalDependency).
|
|
72
|
-
// Skip gracefully when it isn't installed (e.g. CI without optional deps).
|
|
73
|
-
let hasSdk = false;
|
|
74
|
-
try { require('@modelcontextprotocol/sdk/client/index.js'); hasSdk = true; } catch (_) {}
|
|
75
|
-
|
|
76
|
-
test('client: connects to local kodelyth MCP server and lists tools', { skip: !hasSdk }, async () => {
|
|
77
|
-
// self is already registered from the earlier test (running in same file).
|
|
78
|
-
const out = await C.listTools('self');
|
|
79
|
-
assert.ok(out.tools.length > 0, 'expected at least one tool from local kodelyth server');
|
|
80
|
-
const names = new Set(out.tools.map(t => t.name));
|
|
81
|
-
assert.ok(names.has('catalog_stats'), 'catalog_stats tool must be exposed');
|
|
82
|
-
assert.ok(names.has('list_agents'), 'list_agents tool must be exposed');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('client: callTool on local server returns a real result', { skip: !hasSdk }, async () => {
|
|
86
|
-
const out = await C.callTool('self', 'catalog_stats', {});
|
|
87
|
-
assert.ok(Array.isArray(out.content));
|
|
88
|
-
assert.equal(out.content[0].type, 'text');
|
|
89
|
-
const data = JSON.parse(out.content[0].text);
|
|
90
|
-
assert.ok(data.agents > 0);
|
|
91
|
-
assert.ok(data.skills > 0);
|
|
92
|
-
assert.ok(data.commands > 0);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('client: listResources surfaces kodelyth:// URIs', { skip: !hasSdk }, async () => {
|
|
96
|
-
const out = await C.listResources('self');
|
|
97
|
-
assert.ok(out.resources.length > 0);
|
|
98
|
-
assert.ok(out.resources.every(r => r.uri.startsWith('kodelyth://')),
|
|
99
|
-
'every resource uri should start with kodelyth://');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('client: listPrompts surfaces routing-rule', { skip: !hasSdk }, async () => {
|
|
103
|
-
const out = await C.listPrompts('self');
|
|
104
|
-
const names = new Set(out.prompts.map(p => p.name));
|
|
105
|
-
assert.ok(names.has('routing-rule'));
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('client: connect fails clearly for unregistered server', async () => {
|
|
109
|
-
await assert.rejects(
|
|
110
|
-
() => C.connect('this-was-never-registered'),
|
|
111
|
-
err => err.code === 'MCP_SERVER_NOT_REGISTERED'
|
|
112
|
-
);
|
|
113
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// Tests for scripts/mcp/resources.js and scripts/mcp/prompts.js
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
|
|
7
|
-
const RES = require('../../scripts/mcp/resources');
|
|
8
|
-
const PROMPTS = require('../../scripts/mcp/prompts');
|
|
9
|
-
|
|
10
|
-
test('resources.buildList returns agents/skills/commands/rules/bundles', () => {
|
|
11
|
-
const list = RES.buildList();
|
|
12
|
-
assert.ok(list.length > 200, `expected >200 resources, got ${list.length}`);
|
|
13
|
-
const schemes = new Set(list.map(r => r.uri.split('://')[1].split('/')[0]));
|
|
14
|
-
for (const expected of ['agents', 'skills', 'commands', 'rules', 'bundles']) {
|
|
15
|
-
assert.ok(schemes.has(expected), `missing resource bucket: ${expected}`);
|
|
16
|
-
}
|
|
17
|
-
for (const r of list.slice(0, 5)) {
|
|
18
|
-
assert.match(r.uri, /^kodelyth:\/\//);
|
|
19
|
-
assert.equal(r.mimeType, 'text/markdown');
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('resources.readResource returns body for a known agent', () => {
|
|
24
|
-
const out = RES.readResource('kodelyth://agents/debug-detective');
|
|
25
|
-
assert.ok(out.contents.length === 1);
|
|
26
|
-
assert.equal(out.contents[0].mimeType, 'text/markdown');
|
|
27
|
-
assert.match(out.contents[0].text, /debug-detective/);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('resources.readResource rejects unknown URI scheme', () => {
|
|
31
|
-
assert.throws(() => RES.readResource('weird://foo/bar'));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('resources.readResource rejects unknown name', () => {
|
|
35
|
-
assert.throws(() => RES.readResource('kodelyth://agents/no-such-agent'));
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('PROMPT_DEFINITIONS exposes routing-rule and overviews', () => {
|
|
39
|
-
const names = PROMPTS.PROMPT_DEFINITIONS.map(p => p.name);
|
|
40
|
-
for (const expected of [
|
|
41
|
-
'routing-rule',
|
|
42
|
-
'agents-overview',
|
|
43
|
-
'skills-overview',
|
|
44
|
-
'commands-overview',
|
|
45
|
-
'handoff-chains',
|
|
46
|
-
'devil-mode',
|
|
47
|
-
]) {
|
|
48
|
-
assert.ok(names.includes(expected), `missing prompt: ${expected}`);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('routing-rule prompt returns the full intent routing rule body', () => {
|
|
53
|
-
const out = PROMPTS.PROMPT_HANDLERS['routing-rule']();
|
|
54
|
-
assert.ok(out.messages.length === 1);
|
|
55
|
-
assert.equal(out.messages[0].role, 'user');
|
|
56
|
-
assert.match(out.messages[0].content.text, /Agent Intent Routing/);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('agents-overview returns a summary of all agents', () => {
|
|
60
|
-
const out = PROMPTS.PROMPT_HANDLERS['agents-overview']();
|
|
61
|
-
const text = out.messages[0].content.text;
|
|
62
|
-
assert.match(text, /Kodelyth ECC — Agents/);
|
|
63
|
-
assert.match(text, /debug-detective/);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('devil-mode prompt returns the parallel command body', () => {
|
|
67
|
-
const out = PROMPTS.PROMPT_HANDLERS['devil-mode']();
|
|
68
|
-
const text = out.messages[0].content.text;
|
|
69
|
-
assert.match(text, /devil-mode/i);
|
|
70
|
-
});
|
package/tests/mcp/tools.test.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
// Tests for scripts/mcp/tools.js — pure-function MCP tool handlers.
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
|
|
10
|
-
// Isolate the memory store to a tmp dir so capture/recall don't pollute
|
|
11
|
-
// the user's real ~/.kodelyth/memory.
|
|
12
|
-
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-mcp-'));
|
|
13
|
-
process.env.KODELYTH_MEMORY_DIR = TMP;
|
|
14
|
-
|
|
15
|
-
// Load AFTER setting env so the memory store picks up the tmp dir.
|
|
16
|
-
const T = require('../../scripts/mcp/tools');
|
|
17
|
-
|
|
18
|
-
function parseJSON(toolResult) {
|
|
19
|
-
const text = toolResult.content[0].text;
|
|
20
|
-
return JSON.parse(text);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
test('TOOL_DEFINITIONS exposes the expected tool surface', () => {
|
|
24
|
-
const names = T.TOOL_DEFINITIONS.map(t => t.name);
|
|
25
|
-
for (const expected of [
|
|
26
|
-
'route_intent',
|
|
27
|
-
'list_agents',
|
|
28
|
-
'list_skills',
|
|
29
|
-
'list_commands',
|
|
30
|
-
'list_rules',
|
|
31
|
-
'list_bundles',
|
|
32
|
-
'get_agent',
|
|
33
|
-
'get_skill',
|
|
34
|
-
'get_command',
|
|
35
|
-
'get_rule',
|
|
36
|
-
'get_bundle',
|
|
37
|
-
'recall_memory',
|
|
38
|
-
'capture_memory',
|
|
39
|
-
'memory_stats',
|
|
40
|
-
'catalog_stats',
|
|
41
|
-
'audit_skill_match',
|
|
42
|
-
]) {
|
|
43
|
-
assert.ok(names.includes(expected), `missing tool: ${expected}`);
|
|
44
|
-
}
|
|
45
|
-
// Every tool has a handler entry.
|
|
46
|
-
for (const t of T.TOOL_DEFINITIONS) {
|
|
47
|
-
assert.equal(typeof T.TOOL_HANDLERS[t.name], 'function', `${t.name} handler missing`);
|
|
48
|
-
assert.ok(t.description && t.description.length > 5, `${t.name} description too short`);
|
|
49
|
-
assert.ok(t.inputSchema && t.inputSchema.type === 'object', `${t.name} inputSchema malformed`);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('route_intent rejects empty message', () => {
|
|
54
|
-
const r = T.tool_route_intent({});
|
|
55
|
-
assert.ok(r.isError, 'must error on missing message');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('route_intent surfaces a relevant agent for a debug task', () => {
|
|
59
|
-
const r = T.tool_route_intent({ message: 'i have a hard intermittent bug in production' });
|
|
60
|
-
const data = parseJSON(r);
|
|
61
|
-
assert.ok(Array.isArray(data.suggestions));
|
|
62
|
-
assert.ok(data.suggestions.length > 0, 'should produce at least one suggestion');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('route_intent honours top_k', () => {
|
|
66
|
-
const r = T.tool_route_intent({ message: 'review this code for security and quality issues', top_k: 2 });
|
|
67
|
-
const data = parseJSON(r);
|
|
68
|
-
assert.ok(data.suggestions.length <= 2);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('list_agents returns the full catalog', () => {
|
|
72
|
-
const r = T.tool_list_agents();
|
|
73
|
-
const data = parseJSON(r);
|
|
74
|
-
assert.ok(data.count >= 60);
|
|
75
|
-
assert.ok(Array.isArray(data.agents) && data.agents.length === data.count);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('get_agent returns body for known agent', () => {
|
|
79
|
-
const r = T.tool_get_agent({ name: 'debug-detective' });
|
|
80
|
-
assert.ok(!r.isError);
|
|
81
|
-
assert.match(r.content[0].text, /^# Agent: debug-detective/);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('get_agent errors on unknown agent', () => {
|
|
85
|
-
const r = T.tool_get_agent({ name: 'no-such-agent' });
|
|
86
|
-
assert.ok(r.isError);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('get_command tolerates leading slash', () => {
|
|
90
|
-
const r = T.tool_get_command({ name: '/code-review' });
|
|
91
|
-
assert.ok(!r.isError);
|
|
92
|
-
assert.match(r.content[0].text, /^# Command: \/code-review/);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('get_rule returns the intent routing rule', () => {
|
|
96
|
-
const r = T.tool_get_rule({ name: 'agent-intent-routing' });
|
|
97
|
-
assert.ok(!r.isError);
|
|
98
|
-
assert.match(r.content[0].text, /Agent Intent Routing/);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('get_bundle returns red-team cheat sheet', () => {
|
|
102
|
-
const r = T.tool_get_bundle({ name: 'red-team' });
|
|
103
|
-
assert.ok(!r.isError);
|
|
104
|
-
assert.ok(r.content[0].text.length > 100);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('catalog_stats returns counts', () => {
|
|
108
|
-
const r = T.tool_catalog_stats();
|
|
109
|
-
const s = parseJSON(r);
|
|
110
|
-
assert.ok(s.agents > 0 && s.skills > 0 && s.commands > 0);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('audit_skill_match suggests at least one skill for an observability task', () => {
|
|
114
|
-
const r = T.tool_audit_skill_match({ task: 'add structured logging and tracing to a fastapi service' });
|
|
115
|
-
const data = parseJSON(r);
|
|
116
|
-
assert.ok(Array.isArray(data.suggestions));
|
|
117
|
-
assert.ok(data.suggestions.length > 0, 'expected at least one skill suggestion');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test('capture_memory + recall_memory roundtrip in isolated tmp store', () => {
|
|
121
|
-
// Pre-seed a few unrelated memories so BM25 IDF can rise above the store's
|
|
122
|
-
// default minScore floor (with N=1 IDF stays ~0.29 which is filtered out).
|
|
123
|
-
for (const seed of [
|
|
124
|
-
{ problem: 'fix a flaky CI build on github actions', approach: 'set timeout to 60s and retry once' },
|
|
125
|
-
{ problem: 'wire up redis caching for product list', approach: 'use 60s ttl plus stale-while-revalidate' },
|
|
126
|
-
{ problem: 'migrate postgres timestamps to utc', approach: 'add at::timestamptz coercion in alembic migration' },
|
|
127
|
-
]) {
|
|
128
|
-
T.tool_capture_memory({ ...seed, tags: ['seed'], language: 'misc' });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const cap = T.tool_capture_memory({
|
|
132
|
-
problem: 'mcp test problem keyword splatzilla',
|
|
133
|
-
approach: 'documenting that capture_memory works end-to-end',
|
|
134
|
-
tags: ['mcp', 'test'],
|
|
135
|
-
language: 'javascript',
|
|
136
|
-
});
|
|
137
|
-
assert.ok(!cap.isError, 'capture must succeed');
|
|
138
|
-
const capData = parseJSON(cap);
|
|
139
|
-
assert.ok(capData.id && typeof capData.id === 'string');
|
|
140
|
-
assert.ok(capData.captured_at);
|
|
141
|
-
|
|
142
|
-
const rec = T.tool_recall_memory({ query: 'splatzilla', limit: 3 });
|
|
143
|
-
assert.ok(!rec.isError);
|
|
144
|
-
const recData = parseJSON(rec);
|
|
145
|
-
assert.ok(recData.count >= 1, 'should recall the memory we just captured');
|
|
146
|
-
assert.equal(recData.memories[0].problem, 'mcp test problem keyword splatzilla');
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('capture_memory rejects missing required fields', () => {
|
|
150
|
-
const r = T.tool_capture_memory({ problem: 'only problem, no approach' });
|
|
151
|
-
assert.ok(r.isError);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('memory_stats reflects at least one stored memory', () => {
|
|
155
|
-
const r = T.tool_memory_stats();
|
|
156
|
-
assert.ok(!r.isError);
|
|
157
|
-
const s = parseJSON(r);
|
|
158
|
-
assert.ok(typeof s === 'object');
|
|
159
|
-
});
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
// Tests for hooks/memory/auto-recall.js — UserPromptSubmit auto chat detection
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { spawnSync } = require('child_process');
|
|
10
|
-
|
|
11
|
-
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-recall-'));
|
|
12
|
-
process.env.KODELYTH_MEMORY_DIR = TMP;
|
|
13
|
-
|
|
14
|
-
const HOOK = path.join(__dirname, '..', '..', 'hooks', 'memory', 'auto-recall.js');
|
|
15
|
-
const store = require('../../scripts/memory/store');
|
|
16
|
-
|
|
17
|
-
function runHook(payload) {
|
|
18
|
-
const res = spawnSync(process.execPath, [HOOK], {
|
|
19
|
-
input: JSON.stringify(payload),
|
|
20
|
-
encoding: 'utf8',
|
|
21
|
-
env: { ...process.env, KODELYTH_MEMORY_DIR: TMP },
|
|
22
|
-
timeout: 5000,
|
|
23
|
-
});
|
|
24
|
-
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Seed memory once
|
|
28
|
-
store.capture({
|
|
29
|
-
problem: 'Stripe webhook signature failed in production',
|
|
30
|
-
approach: 'Switched body parser from json to raw, validated with constructEvent',
|
|
31
|
-
tags: ['payments', 'stripe', 'webhooks'],
|
|
32
|
-
project: '/test/payments',
|
|
33
|
-
language: 'typescript',
|
|
34
|
-
});
|
|
35
|
-
store.capture({
|
|
36
|
-
problem: 'Redis cluster split-brain after AZ failover',
|
|
37
|
-
approach: 'Configured min-slaves-to-write to 2 to prevent stale primary writes',
|
|
38
|
-
tags: ['redis', 'cluster', 'reliability'],
|
|
39
|
-
project: '/test/payments',
|
|
40
|
-
language: 'typescript',
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('auto-recall: returns nothing for empty prompt', () => {
|
|
44
|
-
const r = runHook({ prompt: '', cwd: '/test/payments', session_id: 's1' });
|
|
45
|
-
assert.equal(r.status, 0);
|
|
46
|
-
assert.equal(r.stdout, '');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('auto-recall: skips trivial prompts', () => {
|
|
50
|
-
for (const trivial of ['ok', 'yes', 'thanks', 'cool']) {
|
|
51
|
-
const r = runHook({ prompt: trivial, cwd: '/test/payments', session_id: 's-trivial' });
|
|
52
|
-
assert.equal(r.stdout, '', `should skip "${trivial}"`);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('auto-recall: skips agent invocation prompts', () => {
|
|
57
|
-
for (const cmd of ['use debug-detective', '@code-reviewer', '/help', 'invoke security-reviewer']) {
|
|
58
|
-
const r = runHook({ prompt: cmd, cwd: '/test/payments', session_id: 's-cmd' });
|
|
59
|
-
assert.equal(r.stdout, '', `should skip "${cmd}"`);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('auto-recall: skips short prompts', () => {
|
|
64
|
-
const r = runHook({ prompt: 'fix bug', cwd: '/test/payments', session_id: 's-short' });
|
|
65
|
-
assert.equal(r.stdout, '');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('auto-recall: surfaces relevant memory for a real query', () => {
|
|
69
|
-
const r = runHook({
|
|
70
|
-
prompt: 'I need to add Stripe webhooks for subscription renewal events',
|
|
71
|
-
cwd: '/test/payments',
|
|
72
|
-
session_id: 's-fire-1',
|
|
73
|
-
});
|
|
74
|
-
assert.equal(r.status, 0);
|
|
75
|
-
assert.ok(r.stdout.length > 0, 'should produce output');
|
|
76
|
-
const out = JSON.parse(r.stdout);
|
|
77
|
-
assert.ok(out.additionalContext.includes('Stripe webhook'));
|
|
78
|
-
assert.equal(out.meta.source, 'kodelyth-memory:auto-recall');
|
|
79
|
-
assert.equal(out.meta.recalledCount, 1);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('auto-recall: suppresses repeat in same session', () => {
|
|
83
|
-
// First call surfaces the memory
|
|
84
|
-
runHook({
|
|
85
|
-
prompt: 'help with stripe webhook integration',
|
|
86
|
-
cwd: '/test/payments',
|
|
87
|
-
session_id: 's-repeat',
|
|
88
|
-
});
|
|
89
|
-
// Second call same topic — should NOT surface again
|
|
90
|
-
const r2 = runHook({
|
|
91
|
-
prompt: 'about that stripe webhook problem, what about retries',
|
|
92
|
-
cwd: '/test/payments',
|
|
93
|
-
session_id: 's-repeat',
|
|
94
|
-
});
|
|
95
|
-
assert.equal(r2.stdout, '', 'repeat should be suppressed');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('auto-recall: surfaces different memory for different topic in same session', () => {
|
|
99
|
-
runHook({
|
|
100
|
-
prompt: 'how do I handle stripe webhook errors',
|
|
101
|
-
cwd: '/test/payments',
|
|
102
|
-
session_id: 's-multi',
|
|
103
|
-
});
|
|
104
|
-
// Different topic — redis — should surface
|
|
105
|
-
const r2 = runHook({
|
|
106
|
-
prompt: 'redis cluster is acting weird after failover today',
|
|
107
|
-
cwd: '/test/payments',
|
|
108
|
-
session_id: 's-multi',
|
|
109
|
-
});
|
|
110
|
-
assert.ok(r2.stdout.length > 0, 'different topic should surface');
|
|
111
|
-
const out = JSON.parse(r2.stdout);
|
|
112
|
-
assert.ok(out.additionalContext.toLowerCase().includes('redis'));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('auto-recall: returns nothing when no memory matches', () => {
|
|
116
|
-
const r = runHook({
|
|
117
|
-
prompt: 'what is the meaning of life and quantum entanglement',
|
|
118
|
-
cwd: '/test/payments',
|
|
119
|
-
session_id: 's-nomatch',
|
|
120
|
-
});
|
|
121
|
-
assert.equal(r.stdout, '');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('auto-recall: never crashes on malformed payload', () => {
|
|
125
|
-
const res = spawnSync(process.execPath, [HOOK], {
|
|
126
|
-
input: 'not-json-at-all',
|
|
127
|
-
encoding: 'utf8',
|
|
128
|
-
env: { ...process.env, KODELYTH_MEMORY_DIR: TMP },
|
|
129
|
-
timeout: 3000,
|
|
130
|
-
});
|
|
131
|
-
assert.equal(res.status, 0, 'must exit 0 even on garbage input');
|
|
132
|
-
});
|