kodelyth-ecc 1.5.10 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +19 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +447 -0
- package/CLAUDE.md +48 -16
- package/README.md +265 -81
- package/VERSION +1 -1
- package/actions/ecc-review/README.md +243 -0
- package/actions/ecc-review/action.yml +158 -0
- package/actions/ecc-review/post-comment.js +171 -0
- package/actions/ecc-review/run-review.js +285 -0
- package/agents/backdoor-hunter.md +260 -0
- package/agents/chaos-engineer.md +251 -0
- package/agents/code-stealer-detector.md +228 -0
- package/agents/jailbreak-tester.md +222 -0
- package/agents/license-violation-finder.md +212 -0
- package/agents/prompt-injection-hunter.md +126 -0
- package/agents/secret-hunter.md +209 -0
- package/agents/supply-chain-auditor.md +195 -0
- package/bin/kodelyth-ecc.js +875 -1
- package/bundles/enterprise.md +172 -0
- package/bundles/indie-hacker.md +106 -0
- package/bundles/red-team.md +138 -0
- package/cat +0 -0
- package/commands/dashboard.md +67 -0
- package/commands/devil-mode.md +121 -0
- package/commands/memory-evolve.md +71 -0
- package/commands/replay.md +61 -0
- package/commands/route-model.md +48 -0
- package/commands/swarm.md +68 -0
- package/commands/verify-supply-chain.md +59 -0
- package/docs/dashboard.md +211 -0
- package/docs/evolve.md +303 -0
- package/docs/mcp-clients.md +167 -0
- package/docs/mcp.md +178 -0
- package/docs/replay.md +244 -0
- package/docs/supply-chain.md +207 -0
- package/docs/swarm.md +243 -0
- package/hooks/hooks.json +52 -0
- package/hooks/memory/auto-recall.js +29 -1
- package/hooks/safety/README.md +124 -0
- package/hooks/safety/lib/patterns.js +179 -0
- package/hooks/safety/prompt-injection-guard.js +179 -0
- package/hooks/safety/token-budget.js +229 -0
- package/install.ps1 +199 -2
- package/install.sh +285 -3
- package/package.json +30 -4
- package/rules/common/agent-intent-routing.md +182 -0
- package/rules/common/cost-aware-model-routing.md +152 -0
- package/scripts/dashboard/data.js +353 -0
- package/scripts/dashboard/server.js +324 -0
- package/scripts/dashboard/static/index.html +645 -0
- package/scripts/evolve/analyze.js +303 -0
- package/scripts/evolve/proposals.js +162 -0
- package/scripts/evolve/stats.js +219 -0
- package/scripts/mcp/catalog.js +244 -0
- package/scripts/mcp/client.js +181 -0
- package/scripts/mcp/prompts.js +133 -0
- package/scripts/mcp/resources.js +94 -0
- package/scripts/mcp/server.js +153 -0
- package/scripts/mcp/tools.js +465 -0
- package/scripts/replay/bundle.js +191 -0
- package/scripts/replay/replay.js +107 -0
- package/scripts/router/classify.js +232 -0
- package/scripts/supply-chain/manifest.js +154 -0
- package/scripts/supply-chain/sbom.js +202 -0
- package/scripts/supply-chain/verify.js +102 -0
- package/scripts/swarm/build-plan.js +193 -0
- package/skills/cost-aware-model-routing/SKILL.md +153 -0
- package/skills/kodelyth-quickstart/SKILL.md +7 -0
- package/skills/observability-dashboard/SKILL.md +119 -0
- package/skills/self-evolving-memory/SKILL.md +175 -0
- package/skills/session-replay/SKILL.md +199 -0
- package/skills/supply-chain-verification/SKILL.md +201 -0
- package/skills/swarm-orchestrator/SKILL.md +177 -0
- package/social/card-install.svg +1 -1
- package/social/facebook-group/POST.md +121 -0
- package/social/facebook-group/fb-1-3am-debug.png +0 -0
- package/social/facebook-group/fb-1-3am-debug.svg +97 -0
- package/social/facebook-group/fb-2-cpu-upgrade.png +0 -0
- package/social/facebook-group/fb-2-cpu-upgrade.svg +132 -0
- package/social/facebook-group/fb-3-before-after.png +0 -0
- package/social/facebook-group/fb-3-before-after.svg +94 -0
- package/social/facebook-v150.svg +5 -5
- package/social/github-social-preview.svg +119 -100
- package/social/readme-hero.svg +11 -11
- package/social/section-agents.svg +57 -0
- package/social/section-author.svg +54 -0
- package/social/section-dashboard.svg +59 -0
- package/social/section-devil.svg +54 -0
- package/social/section-hooks.svg +51 -0
- package/social/section-install.svg +42 -0
- package/social/section-learning.svg +52 -0
- package/social/section-mcp.svg +46 -0
- package/social/section-memory.svg +57 -0
- package/social/section-parallel.svg +72 -0
- package/social/section-routing.svg +51 -0
- package/social/x-card-agents-grid.svg +6 -6
- package/tests/dashboard/data.test.js +235 -0
- package/tests/dashboard/server.test.js +240 -0
- package/tests/evolve/analyze.test.js +169 -0
- package/tests/evolve/proposals.test.js +173 -0
- package/tests/evolve/stats.test.js +159 -0
- package/tests/mcp/catalog.test.js +98 -0
- package/tests/mcp/client.test.js +109 -0
- package/tests/mcp/resources-prompts.test.js +70 -0
- package/tests/mcp/tools.test.js +159 -0
- package/tests/replay/bundle.test.js +181 -0
- package/tests/replay/replay.test.js +131 -0
- package/tests/router/classify.test.js +164 -0
- package/tests/safety/patterns.test.js +94 -0
- package/tests/safety/prompt-injection-guard.test.js +111 -0
- package/tests/safety/token-budget.test.js +119 -0
- package/tests/supply-chain/manifest.test.js +147 -0
- package/tests/supply-chain/sbom.test.js +170 -0
- package/tests/supply-chain/verify.test.js +146 -0
- package/tests/swarm/build-plan.test.js +188 -0
- package/wiki/Agent-Reference.md +58 -7
- package/wiki/FAQ.md +204 -7
- package/wiki/Home.md +104 -28
- package/wiki/Hook-Reference.md +1 -1
- package/wiki/Installation-Guide.md +109 -6
- package/wiki/Platform-Support.md +239 -25
- package/wiki/Skill-Reference.md +101 -6
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Kodelyth ECC — MCP Server
|
|
4
|
+
// Universal adapter that exposes ECC agents, skills, commands, rules, and
|
|
5
|
+
// the local memory store to any MCP-compatible client (Claude Desktop,
|
|
6
|
+
// LangGraph, AutoGen, CrewAI, OpenAI Agents SDK, etc.).
|
|
7
|
+
//
|
|
8
|
+
// Transport: stdio (JSON-RPC over stdin/stdout).
|
|
9
|
+
// Run via:
|
|
10
|
+
// npx kodelyth-ecc mcp
|
|
11
|
+
// node scripts/mcp/server.js
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const TOOLS = require('./tools');
|
|
19
|
+
const PROMPTS = require('./prompts');
|
|
20
|
+
const RESOURCES = require('./resources');
|
|
21
|
+
const catalog = require('./catalog');
|
|
22
|
+
|
|
23
|
+
// ── Lazy SDK load with friendly install hint ─────────────────────────────────
|
|
24
|
+
function loadSdk() {
|
|
25
|
+
try {
|
|
26
|
+
return {
|
|
27
|
+
Server: require('@modelcontextprotocol/sdk/server/index.js').Server,
|
|
28
|
+
StdioServerTransport: require('@modelcontextprotocol/sdk/server/stdio.js').StdioServerTransport,
|
|
29
|
+
schemas: require('@modelcontextprotocol/sdk/types.js'),
|
|
30
|
+
};
|
|
31
|
+
} catch (e) {
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
'\n❌ Kodelyth MCP server requires `@modelcontextprotocol/sdk`.\n' +
|
|
34
|
+
' Install it once with:\n\n' +
|
|
35
|
+
' npm install @modelcontextprotocol/sdk\n\n' +
|
|
36
|
+
' Or, if you ran via npx, the dependency should auto-install on next run.\n' +
|
|
37
|
+
` Underlying error: ${e.message}\n\n`
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── Read package metadata for server identity ────────────────────────────────
|
|
44
|
+
function loadIdentity() {
|
|
45
|
+
try {
|
|
46
|
+
const pkg = require(path.join(catalog.ROOT, 'package.json'));
|
|
47
|
+
return { name: 'kodelyth-ecc', version: pkg.version || '0.0.0' };
|
|
48
|
+
} catch {
|
|
49
|
+
return { name: 'kodelyth-ecc', version: '0.0.0' };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
54
|
+
async function main() {
|
|
55
|
+
const sdk = loadSdk();
|
|
56
|
+
const { Server, StdioServerTransport, schemas } = sdk;
|
|
57
|
+
const {
|
|
58
|
+
ListToolsRequestSchema,
|
|
59
|
+
CallToolRequestSchema,
|
|
60
|
+
ListResourcesRequestSchema,
|
|
61
|
+
ReadResourceRequestSchema,
|
|
62
|
+
ListPromptsRequestSchema,
|
|
63
|
+
GetPromptRequestSchema,
|
|
64
|
+
} = schemas;
|
|
65
|
+
|
|
66
|
+
const identity = loadIdentity();
|
|
67
|
+
|
|
68
|
+
const server = new Server(
|
|
69
|
+
{ name: identity.name, version: identity.version },
|
|
70
|
+
{
|
|
71
|
+
capabilities: {
|
|
72
|
+
tools: {},
|
|
73
|
+
resources: {},
|
|
74
|
+
prompts: {},
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// ── Tools ──────────────────────────────────────────────────────────────────
|
|
80
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
81
|
+
tools: TOOLS.TOOL_DEFINITIONS.map(t => ({
|
|
82
|
+
name: t.name,
|
|
83
|
+
description: t.description,
|
|
84
|
+
inputSchema: t.inputSchema,
|
|
85
|
+
})),
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
89
|
+
const { name, arguments: args } = req.params || {};
|
|
90
|
+
const handler = TOOLS.TOOL_HANDLERS[name];
|
|
91
|
+
if (!handler) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return handler(args || {});
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: 'text', text: `Tool ${name} failed: ${e.message}` }],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ── Resources ──────────────────────────────────────────────────────────────
|
|
108
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
109
|
+
resources: RESOURCES.buildList(),
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
113
|
+
const uri = req.params && req.params.uri;
|
|
114
|
+
return RESOURCES.readResource(uri);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ── Prompts ────────────────────────────────────────────────────────────────
|
|
118
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
119
|
+
prompts: PROMPTS.PROMPT_DEFINITIONS.map(p => ({
|
|
120
|
+
name: p.name,
|
|
121
|
+
description: p.description,
|
|
122
|
+
arguments: p.arguments || [],
|
|
123
|
+
})),
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
127
|
+
const name = req.params && req.params.name;
|
|
128
|
+
const handler = PROMPTS.PROMPT_HANDLERS[name];
|
|
129
|
+
if (!handler) throw new Error(`Unknown prompt: ${name}`);
|
|
130
|
+
return handler(req.params.arguments || {});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
134
|
+
const transport = new StdioServerTransport();
|
|
135
|
+
await server.connect(transport);
|
|
136
|
+
|
|
137
|
+
// Stay alive until stdio closes.
|
|
138
|
+
process.stderr.write(
|
|
139
|
+
`[kodelyth-mcp] ready · ${identity.version} · ` +
|
|
140
|
+
`${TOOLS.TOOL_DEFINITIONS.length} tools · ` +
|
|
141
|
+
`${PROMPTS.PROMPT_DEFINITIONS.length} prompts · ` +
|
|
142
|
+
`${RESOURCES.buildList().length} resources\n`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (require.main === module) {
|
|
147
|
+
main().catch(e => {
|
|
148
|
+
process.stderr.write(`[kodelyth-mcp] fatal: ${e.stack || e.message}\n`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { main };
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kodelyth ECC — MCP Tools
|
|
3
|
+
// Pure-function tool handlers. No SDK imports here so we can unit-test directly.
|
|
4
|
+
// Each handler: (args) => { content: [{type, text}], isError?: bool }
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const catalog = require('./catalog');
|
|
11
|
+
const memoryStore = require('../memory/store');
|
|
12
|
+
|
|
13
|
+
// ── Token utilities (shared with route_intent) ───────────────────────────────
|
|
14
|
+
const STOP = new Set([
|
|
15
|
+
'the','a','an','and','or','but','if','then','else','for','to','of','in','on',
|
|
16
|
+
'is','are','was','were','be','been','being','have','has','had','do','does',
|
|
17
|
+
'did','will','would','should','can','could','may','might','must','this','that',
|
|
18
|
+
'these','those','it','its','as','at','by','from','with','about','i','you','we',
|
|
19
|
+
'they','he','she','my','your','our','their','use','using','used','help','need',
|
|
20
|
+
'want','make','please','some','any','all','very','really','just','also','here',
|
|
21
|
+
'there','when','what','which','who','how','why','code','codebase','project',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function tokens(text) {
|
|
25
|
+
return String(text || '')
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9_\-/.\s]/g, ' ')
|
|
28
|
+
.split(/\s+/)
|
|
29
|
+
.filter(t => t.length >= 2 && t.length <= 40 && !STOP.has(t));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function jaccard(a, b) {
|
|
33
|
+
if (!a.size || !b.size) return 0;
|
|
34
|
+
let inter = 0;
|
|
35
|
+
for (const x of a) if (b.has(x)) inter++;
|
|
36
|
+
const union = a.size + b.size - inter;
|
|
37
|
+
return union ? inter / union : 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Wrap result text in MCP content shape ────────────────────────────────────
|
|
41
|
+
function ok(text) { return { content: [{ type: 'text', text }] }; }
|
|
42
|
+
function err(text) { return { content: [{ type: 'text', text }], isError: true }; }
|
|
43
|
+
|
|
44
|
+
function toJSON(obj) { return ok(JSON.stringify(obj, null, 2)); }
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// TOOL: route_intent
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Suggests the best ECC agent for a user message. Uses simple token overlap
|
|
50
|
+
// against agent name + description. This is intentionally thin — the heavy
|
|
51
|
+
// routing rule lives in rules/common/agent-intent-routing.md (also exposed
|
|
52
|
+
// as an MCP prompt). Clients should layer their own LLM judgment on top.
|
|
53
|
+
function tool_route_intent({ message, top_k = 3 } = {}) {
|
|
54
|
+
if (!message || typeof message !== 'string') {
|
|
55
|
+
return err('route_intent: `message` is required (string).');
|
|
56
|
+
}
|
|
57
|
+
const k = Math.max(1, Math.min(20, Number(top_k) || 3));
|
|
58
|
+
|
|
59
|
+
const queryTokens = new Set(tokens(message));
|
|
60
|
+
if (queryTokens.size === 0) {
|
|
61
|
+
return err('route_intent: message had no usable tokens after normalization.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const agents = catalog.loadAgents();
|
|
65
|
+
const scored = agents.map(a => {
|
|
66
|
+
const haystack = `${a.name} ${a.description}`;
|
|
67
|
+
const aTokens = new Set(tokens(haystack));
|
|
68
|
+
// Boost name-match heavily — direct mention of an agent name should dominate.
|
|
69
|
+
const nameTokens = new Set(tokens(a.name));
|
|
70
|
+
const nameHits = [...queryTokens].filter(t => nameTokens.has(t)).length;
|
|
71
|
+
const score = jaccard(queryTokens, aTokens) + nameHits * 0.5;
|
|
72
|
+
return { agent: a.name, score, description: a.description.slice(0, 200) };
|
|
73
|
+
})
|
|
74
|
+
.filter(r => r.score > 0)
|
|
75
|
+
.sort((a, b) => b.score - a.score)
|
|
76
|
+
.slice(0, k);
|
|
77
|
+
|
|
78
|
+
return toJSON({
|
|
79
|
+
message,
|
|
80
|
+
suggestions: scored,
|
|
81
|
+
routing_rule: 'rules/common/agent-intent-routing.md',
|
|
82
|
+
note: 'These are token-overlap suggestions only. Read the full routing rule (exposed as MCP prompt `routing-rule`) for tier-based intent matching.',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// TOOL: list_agents / list_skills / list_commands / list_rules / list_bundles
|
|
88
|
+
// =============================================================================
|
|
89
|
+
function tool_list_agents() {
|
|
90
|
+
const agents = catalog.loadAgents().map(a => ({
|
|
91
|
+
name: a.name,
|
|
92
|
+
description: a.description,
|
|
93
|
+
relpath: a.relpath,
|
|
94
|
+
}));
|
|
95
|
+
return toJSON({ count: agents.length, agents });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function tool_list_skills() {
|
|
99
|
+
const skills = catalog.loadSkills().map(s => ({
|
|
100
|
+
name: s.name,
|
|
101
|
+
description: s.description,
|
|
102
|
+
origin: s.origin,
|
|
103
|
+
relpath: s.relpath,
|
|
104
|
+
}));
|
|
105
|
+
return toJSON({ count: skills.length, skills });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function tool_list_commands() {
|
|
109
|
+
const cmds = catalog.loadCommands().map(c => ({
|
|
110
|
+
name: c.name,
|
|
111
|
+
description: c.description,
|
|
112
|
+
argument_hint: c.argumentHint,
|
|
113
|
+
relpath: c.relpath,
|
|
114
|
+
}));
|
|
115
|
+
return toJSON({ count: cmds.length, commands: cmds });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function tool_list_rules() {
|
|
119
|
+
const rules = catalog.loadAllRules().map(r => ({
|
|
120
|
+
name: r.name,
|
|
121
|
+
relpath: r.relpath,
|
|
122
|
+
bytes: r.body.length,
|
|
123
|
+
}));
|
|
124
|
+
return toJSON({ count: rules.length, rules });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function tool_list_bundles() {
|
|
128
|
+
const bundles = catalog.loadBundles().map(b => ({
|
|
129
|
+
name: b.name,
|
|
130
|
+
relpath: b.relpath,
|
|
131
|
+
bytes: b.body.length,
|
|
132
|
+
}));
|
|
133
|
+
return toJSON({ count: bundles.length, bundles });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// =============================================================================
|
|
137
|
+
// TOOL: get_agent / get_skill / get_command / get_rule / get_bundle
|
|
138
|
+
// =============================================================================
|
|
139
|
+
function tool_get_agent({ name } = {}) {
|
|
140
|
+
if (!name) return err('get_agent: `name` is required.');
|
|
141
|
+
const a = catalog.findAgent(name);
|
|
142
|
+
if (!a) return err(`get_agent: agent "${name}" not found.`);
|
|
143
|
+
return ok(`# Agent: ${a.name}\n\n**Description:** ${a.description}\n\n---\n\n${a.body}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function tool_get_skill({ name } = {}) {
|
|
147
|
+
if (!name) return err('get_skill: `name` is required.');
|
|
148
|
+
const s = catalog.findSkill(name);
|
|
149
|
+
if (!s) return err(`get_skill: skill "${name}" not found.`);
|
|
150
|
+
return ok(`# Skill: ${s.name}\n\n**Description:** ${s.description}\n\n---\n\n${s.body}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function tool_get_command({ name } = {}) {
|
|
154
|
+
if (!name) return err('get_command: `name` is required.');
|
|
155
|
+
const c = catalog.findCommand(name);
|
|
156
|
+
if (!c) return err(`get_command: command "${name}" not found.`);
|
|
157
|
+
const hint = c.argumentHint ? `\n**Arguments:** ${c.argumentHint}` : '';
|
|
158
|
+
return ok(`# Command: /${c.name}\n\n**Description:** ${c.description}${hint}\n\n---\n\n${c.body}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function tool_get_rule({ name } = {}) {
|
|
162
|
+
if (!name) return err('get_rule: `name` is required.');
|
|
163
|
+
const r = catalog.loadRule(name);
|
|
164
|
+
if (!r) return err(`get_rule: rule "${name}" not found.`);
|
|
165
|
+
return ok(r.body);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function tool_get_bundle({ name } = {}) {
|
|
169
|
+
if (!name) return err('get_bundle: `name` is required.');
|
|
170
|
+
const b = catalog.loadBundles().find(x => x.name === name);
|
|
171
|
+
if (!b) return err(`get_bundle: bundle "${name}" not found.`);
|
|
172
|
+
return ok(b.body);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// =============================================================================
|
|
176
|
+
// TOOL: recall_memory
|
|
177
|
+
// =============================================================================
|
|
178
|
+
function tool_recall_memory({ query, project_root, limit = 5 } = {}) {
|
|
179
|
+
if (!query || typeof query !== 'string') {
|
|
180
|
+
return err('recall_memory: `query` is required (string).');
|
|
181
|
+
}
|
|
182
|
+
const lim = Math.max(1, Math.min(50, Number(limit) || 5));
|
|
183
|
+
let results;
|
|
184
|
+
if (project_root) {
|
|
185
|
+
results = memoryStore.recallForProject(project_root, query, { limit: lim });
|
|
186
|
+
} else {
|
|
187
|
+
results = memoryStore.recall(query, { limit: lim });
|
|
188
|
+
}
|
|
189
|
+
return toJSON({
|
|
190
|
+
query,
|
|
191
|
+
project_root: project_root || null,
|
|
192
|
+
count: results.length,
|
|
193
|
+
memories: results.map(m => ({
|
|
194
|
+
id: m.id,
|
|
195
|
+
problem: m.problem,
|
|
196
|
+
approach: m.approach,
|
|
197
|
+
tags: m.tags || [],
|
|
198
|
+
language: m.language || null,
|
|
199
|
+
project_path: m.project_path || null,
|
|
200
|
+
score: m.score || 0,
|
|
201
|
+
captured_at: m.captured_at || null,
|
|
202
|
+
})),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// TOOL: capture_memory
|
|
208
|
+
// =============================================================================
|
|
209
|
+
function tool_capture_memory({
|
|
210
|
+
problem, approach, tags = [], language = null, project_root = null,
|
|
211
|
+
files = [], gotchas = [],
|
|
212
|
+
} = {}) {
|
|
213
|
+
if (!problem || !approach) {
|
|
214
|
+
return err('capture_memory: both `problem` and `approach` are required.');
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const memory = memoryStore.capture({
|
|
218
|
+
problem,
|
|
219
|
+
approach,
|
|
220
|
+
tags: Array.isArray(tags) ? tags : String(tags).split(',').map(t => t.trim()).filter(Boolean),
|
|
221
|
+
language: language || null,
|
|
222
|
+
project: project_root || null,
|
|
223
|
+
files: Array.isArray(files) ? files : [],
|
|
224
|
+
gotchas: Array.isArray(gotchas) ? gotchas : [],
|
|
225
|
+
source: 'mcp',
|
|
226
|
+
});
|
|
227
|
+
return toJSON({
|
|
228
|
+
ok: true,
|
|
229
|
+
id: memory.id,
|
|
230
|
+
problem: memory.problem,
|
|
231
|
+
captured_at: memory.captured_at,
|
|
232
|
+
});
|
|
233
|
+
} catch (e) {
|
|
234
|
+
return err(`capture_memory: ${e.message}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// =============================================================================
|
|
239
|
+
// TOOL: memory_stats
|
|
240
|
+
// =============================================================================
|
|
241
|
+
function tool_memory_stats() {
|
|
242
|
+
try {
|
|
243
|
+
return toJSON(memoryStore.stats());
|
|
244
|
+
} catch (e) {
|
|
245
|
+
return err(`memory_stats: ${e.message}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// =============================================================================
|
|
250
|
+
// TOOL: catalog_stats
|
|
251
|
+
// =============================================================================
|
|
252
|
+
function tool_catalog_stats() {
|
|
253
|
+
return toJSON(catalog.stats());
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// =============================================================================
|
|
257
|
+
// TOOL: audit_skill_match
|
|
258
|
+
// =============================================================================
|
|
259
|
+
// Given a description of work, surface skills whose description or body
|
|
260
|
+
// overlaps. Useful for clients deciding which skills to attach as context.
|
|
261
|
+
function tool_audit_skill_match({ task, top_k = 5 } = {}) {
|
|
262
|
+
if (!task || typeof task !== 'string') {
|
|
263
|
+
return err('audit_skill_match: `task` is required (string).');
|
|
264
|
+
}
|
|
265
|
+
const k = Math.max(1, Math.min(20, Number(top_k) || 5));
|
|
266
|
+
const queryTokens = new Set(tokens(task));
|
|
267
|
+
if (queryTokens.size === 0) {
|
|
268
|
+
return err('audit_skill_match: task had no usable tokens after normalization.');
|
|
269
|
+
}
|
|
270
|
+
const skills = catalog.loadSkills();
|
|
271
|
+
const scored = skills.map(s => {
|
|
272
|
+
// Cap body slice to keep matching cheap on large skill files.
|
|
273
|
+
const haystack = `${s.name} ${s.description} ${s.body.slice(0, 2000)}`;
|
|
274
|
+
const sTokens = new Set(tokens(haystack));
|
|
275
|
+
const score = jaccard(queryTokens, sTokens);
|
|
276
|
+
return { skill: s.name, score, description: s.description.slice(0, 200) };
|
|
277
|
+
})
|
|
278
|
+
.filter(r => r.score > 0)
|
|
279
|
+
.sort((a, b) => b.score - a.score)
|
|
280
|
+
.slice(0, k);
|
|
281
|
+
|
|
282
|
+
return toJSON({ task, suggestions: scored });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// =============================================================================
|
|
286
|
+
// Tool definitions (exported for SDK registration)
|
|
287
|
+
// =============================================================================
|
|
288
|
+
const TOOL_DEFINITIONS = [
|
|
289
|
+
{
|
|
290
|
+
name: 'route_intent',
|
|
291
|
+
description: 'Suggest the best ECC agent for a user message via token-overlap scoring. Read the full routing rule via the `routing-rule` MCP prompt for tier-based intent matching.',
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: 'object',
|
|
294
|
+
properties: {
|
|
295
|
+
message: { type: 'string', description: 'The user message or task description.' },
|
|
296
|
+
top_k: { type: 'integer', description: 'How many agents to return (1-20, default 3).', default: 3 },
|
|
297
|
+
},
|
|
298
|
+
required: ['message'],
|
|
299
|
+
},
|
|
300
|
+
handler: tool_route_intent,
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'list_agents',
|
|
304
|
+
description: 'List all ECC agents (name, description, relative path).',
|
|
305
|
+
inputSchema: { type: 'object', properties: {} },
|
|
306
|
+
handler: tool_list_agents,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: 'list_skills',
|
|
310
|
+
description: 'List all ECC skills (name, description, origin, relative path).',
|
|
311
|
+
inputSchema: { type: 'object', properties: {} },
|
|
312
|
+
handler: tool_list_skills,
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'list_commands',
|
|
316
|
+
description: 'List all ECC slash commands.',
|
|
317
|
+
inputSchema: { type: 'object', properties: {} },
|
|
318
|
+
handler: tool_list_commands,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'list_rules',
|
|
322
|
+
description: 'List all ECC rule files in rules/common/.',
|
|
323
|
+
inputSchema: { type: 'object', properties: {} },
|
|
324
|
+
handler: tool_list_rules,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'list_bundles',
|
|
328
|
+
description: 'List ECC power bundles (indie-hacker, red-team, enterprise).',
|
|
329
|
+
inputSchema: { type: 'object', properties: {} },
|
|
330
|
+
handler: tool_list_bundles,
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'get_agent',
|
|
334
|
+
description: 'Fetch the full markdown body of a single agent.',
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: { name: { type: 'string', description: 'Agent name (e.g. "debug-detective").' } },
|
|
338
|
+
required: ['name'],
|
|
339
|
+
},
|
|
340
|
+
handler: tool_get_agent,
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: 'get_skill',
|
|
344
|
+
description: 'Fetch the full markdown body of a single skill.',
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: 'object',
|
|
347
|
+
properties: { name: { type: 'string', description: 'Skill name (e.g. "agentic-engineering").' } },
|
|
348
|
+
required: ['name'],
|
|
349
|
+
},
|
|
350
|
+
handler: tool_get_skill,
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'get_command',
|
|
354
|
+
description: 'Fetch the full markdown body of a single slash command.',
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: 'object',
|
|
357
|
+
properties: { name: { type: 'string', description: 'Command name with or without leading slash (e.g. "code-review" or "/code-review").' } },
|
|
358
|
+
required: ['name'],
|
|
359
|
+
},
|
|
360
|
+
handler: tool_get_command,
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: 'get_rule',
|
|
364
|
+
description: 'Fetch the full markdown body of a single ECC rule file.',
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: 'object',
|
|
367
|
+
properties: { name: { type: 'string', description: 'Rule name (e.g. "agent-intent-routing").' } },
|
|
368
|
+
required: ['name'],
|
|
369
|
+
},
|
|
370
|
+
handler: tool_get_rule,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'get_bundle',
|
|
374
|
+
description: 'Fetch the cheat sheet for a power bundle (indie-hacker, red-team, enterprise).',
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: { name: { type: 'string', description: 'Bundle name (e.g. "indie-hacker").' } },
|
|
378
|
+
required: ['name'],
|
|
379
|
+
},
|
|
380
|
+
handler: tool_get_bundle,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: 'recall_memory',
|
|
384
|
+
description: 'BM25 search across the local Kodelyth memory store. Returns top-N matching memories.',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
properties: {
|
|
388
|
+
query: { type: 'string', description: 'Search query.' },
|
|
389
|
+
project_root: { type: 'string', description: 'Optional absolute path to scope to a project first.' },
|
|
390
|
+
limit: { type: 'integer', description: 'Max results (1-50, default 5).', default: 5 },
|
|
391
|
+
},
|
|
392
|
+
required: ['query'],
|
|
393
|
+
},
|
|
394
|
+
handler: tool_recall_memory,
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: 'capture_memory',
|
|
398
|
+
description: 'Append a new memory entry to the local Kodelyth memory store.',
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: 'object',
|
|
401
|
+
properties: {
|
|
402
|
+
problem: { type: 'string', description: 'Short problem description.' },
|
|
403
|
+
approach: { type: 'string', description: 'How the problem was solved or the lesson learned.' },
|
|
404
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },
|
|
405
|
+
language: { type: 'string', description: 'Optional primary language (e.g. "typescript").' },
|
|
406
|
+
project_root: { type: 'string', description: 'Optional project absolute path.' },
|
|
407
|
+
files: { type: 'array', items: { type: 'string' }, description: 'Optional related files.' },
|
|
408
|
+
gotchas: { type: 'array', items: { type: 'string' }, description: 'Optional gotchas.' },
|
|
409
|
+
},
|
|
410
|
+
required: ['problem', 'approach'],
|
|
411
|
+
},
|
|
412
|
+
handler: tool_capture_memory,
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: 'memory_stats',
|
|
416
|
+
description: 'Summary of the local Kodelyth memory store (counts, projects, etc.).',
|
|
417
|
+
inputSchema: { type: 'object', properties: {} },
|
|
418
|
+
handler: tool_memory_stats,
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: 'catalog_stats',
|
|
422
|
+
description: 'Summary of how many ECC agents, skills, commands, rules, and bundles are loaded.',
|
|
423
|
+
inputSchema: { type: 'object', properties: {} },
|
|
424
|
+
handler: tool_catalog_stats,
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: 'audit_skill_match',
|
|
428
|
+
description: 'Suggest ECC skills whose description/body overlap a task. Use to decide which skills to attach as context.',
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: 'object',
|
|
431
|
+
properties: {
|
|
432
|
+
task: { type: 'string', description: 'The task or work description.' },
|
|
433
|
+
top_k: { type: 'integer', description: 'Max skills to return (1-20, default 5).', default: 5 },
|
|
434
|
+
},
|
|
435
|
+
required: ['task'],
|
|
436
|
+
},
|
|
437
|
+
handler: tool_audit_skill_match,
|
|
438
|
+
},
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
const TOOL_HANDLERS = Object.fromEntries(
|
|
442
|
+
TOOL_DEFINITIONS.map(t => [t.name, t.handler])
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
module.exports = {
|
|
446
|
+
TOOL_DEFINITIONS,
|
|
447
|
+
TOOL_HANDLERS,
|
|
448
|
+
// Exposed individually for tests:
|
|
449
|
+
tool_route_intent,
|
|
450
|
+
tool_list_agents,
|
|
451
|
+
tool_list_skills,
|
|
452
|
+
tool_list_commands,
|
|
453
|
+
tool_list_rules,
|
|
454
|
+
tool_list_bundles,
|
|
455
|
+
tool_get_agent,
|
|
456
|
+
tool_get_skill,
|
|
457
|
+
tool_get_command,
|
|
458
|
+
tool_get_rule,
|
|
459
|
+
tool_get_bundle,
|
|
460
|
+
tool_recall_memory,
|
|
461
|
+
tool_capture_memory,
|
|
462
|
+
tool_memory_stats,
|
|
463
|
+
tool_catalog_stats,
|
|
464
|
+
tool_audit_skill_match,
|
|
465
|
+
};
|