@agilsee/mcp-orchestrator 0.5.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/bin/cli.js +490 -0
- package/dist/index.js +454 -0
- package/dist/memory/memory-manager.js +234 -0
- package/dist/server/web-server.js +574 -0
- package/dist/tools/aggregate-patterns.js +101 -0
- package/dist/tools/analyze-history.js +213 -0
- package/dist/tools/auto-dispatch.js +199 -0
- package/dist/tools/check-energy.js +49 -0
- package/dist/tools/cross-search.js +171 -0
- package/dist/tools/get-focus.js +7 -0
- package/dist/tools/get-identity.js +7 -0
- package/dist/tools/get-project-status.js +35 -0
- package/dist/tools/list-projects.js +21 -0
- package/dist/tools/list-recent-tasks.js +59 -0
- package/dist/tools/log-insight.js +43 -0
- package/dist/tools/qcc-create.js +82 -0
- package/dist/tools/qcc-status.js +164 -0
- package/dist/tools/qcc-update.js +188 -0
- package/dist/tools/smart-bootstrap.js +255 -0
- package/dist/tools/summarize-session.js +161 -0
- package/dist/tools/switch-focus.js +40 -0
- package/dist/tools/workflow-router.js +438 -0
- package/package.json +44 -0
- package/templates/index.ts.template +42 -0
- package/templates/shared/get-claude-md.ts +12 -0
- package/templates/shared/get-current-state.ts +21 -0
- package/templates/shared/get-mistakes.ts +18 -0
- package/templates/shared/log-task.ts +27 -0
- package/templates/shared/predict-impact.ts +67 -0
- package/templates/shared/record-mistake.ts +40 -0
- package/templates/shared/update-state.ts +83 -0
- package/templates/stacks/express/config.json +9 -0
- package/templates/stacks/express/list-routes.ts +56 -0
- package/templates/stacks/express/symbol-index.ts +70 -0
- package/templates/stacks/laravel/config.json +9 -0
- package/templates/stacks/laravel/list-routes.ts +19 -0
- package/templates/stacks/laravel/symbol-index.ts +64 -0
- package/templates/stacks/nextjs/config.json +9 -0
- package/templates/stacks/nextjs/list-routes.ts +67 -0
- package/templates/stacks/nextjs/symbol-index.ts +78 -0
- package/templates/stacks/react/config.json +10 -0
- package/templates/stacks/react/list-routes.ts +44 -0
- package/templates/stacks/react/symbol-index.ts +81 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { getIdentity } from "./tools/get-identity.js";
|
|
6
|
+
import { getCurrentFocus } from "./tools/get-focus.js";
|
|
7
|
+
import { listProjects } from "./tools/list-projects.js";
|
|
8
|
+
import { getProjectStatus } from "./tools/get-project-status.js";
|
|
9
|
+
import { listRecentTasks } from "./tools/list-recent-tasks.js";
|
|
10
|
+
import { switchFocus } from "./tools/switch-focus.js";
|
|
11
|
+
import { logInsight } from "./tools/log-insight.js";
|
|
12
|
+
import { checkEnergy } from "./tools/check-energy.js";
|
|
13
|
+
import { aggregatePatterns } from "./tools/aggregate-patterns.js";
|
|
14
|
+
import { analyzeHistory } from "./tools/analyze-history.js";
|
|
15
|
+
import { summarizeLastSession } from "./tools/summarize-session.js";
|
|
16
|
+
import { qccCreate } from "./tools/qcc-create.js";
|
|
17
|
+
import { qccUpdate } from "./tools/qcc-update.js";
|
|
18
|
+
import { qccStatus } from "./tools/qcc-status.js";
|
|
19
|
+
import { smartBootstrap } from "./tools/smart-bootstrap.js";
|
|
20
|
+
import { workflowRouter } from "./tools/workflow-router.js";
|
|
21
|
+
import { saveMemory, searchMemory, listMemories, deleteMemory } from "./memory/memory-manager.js";
|
|
22
|
+
import { autoDispatch } from "./tools/auto-dispatch.js";
|
|
23
|
+
import { crossSearch } from "./tools/cross-search.js";
|
|
24
|
+
const CLAUDE_HOME = process.env.CLAUDE_HOME ?? `${process.env.USERPROFILE ?? process.env.HOME}/.claude`;
|
|
25
|
+
const server = new Server({ name: "orchestrator", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
26
|
+
const tools = [
|
|
27
|
+
{
|
|
28
|
+
name: "get_identity",
|
|
29
|
+
description: "Dapatkan identitas agent (nama, author, spesialisasi). Baca agent-identity.json.",
|
|
30
|
+
inputSchema: { type: "object", properties: {} },
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "get_current_focus",
|
|
34
|
+
description: "Dapatkan focus profile aktif + project yang sedang jadi prioritas kerja. Baca current-focus.json.",
|
|
35
|
+
inputSchema: { type: "object", properties: {} },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "list_projects",
|
|
39
|
+
description: "List semua project terdaftar di project-registry.json dengan metadata singkat (stack, status, group).",
|
|
40
|
+
inputSchema: { type: "object", properties: {} },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "get_project_status",
|
|
44
|
+
description: "Baca CURRENT_STATE.md project tertentu, return task aktif + yang baru selesai.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
slug: { type: "string", description: "Project slug, misal 'app-fe'" },
|
|
49
|
+
},
|
|
50
|
+
required: ["slug"],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "list_recent_tasks",
|
|
55
|
+
description: "Parse AUDIT_LOG.md (satu project atau semua) → return task terbaru diurutkan desc. Lebih hemat daripada Read file manual.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
slug: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Optional: project slug tertentu. Kosong = gabungan semua project.",
|
|
62
|
+
},
|
|
63
|
+
limit: { type: "number", description: "Jumlah entry, default 10" },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "switch_focus",
|
|
69
|
+
description: "Ganti focus profile aktif (sesuai key di current-focus.json). Update current-focus.json.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
profile: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Profile key sesuai yang didefinisikan di current-focus.json — atau 'general' untuk mode default.",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ["profile"],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "log_insight",
|
|
83
|
+
description: "Catat insight lintas project (pattern, feedback, gotcha, workflow). Simpan ke INSIGHTS.md global.",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
type: {
|
|
88
|
+
type: "string",
|
|
89
|
+
enum: ["cross_project_pattern", "feedback", "workflow", "gotcha"],
|
|
90
|
+
description: "Tipe insight",
|
|
91
|
+
},
|
|
92
|
+
title: { type: "string", description: "Judul singkat" },
|
|
93
|
+
detail: { type: "string", description: "Penjelasan detail" },
|
|
94
|
+
projects: {
|
|
95
|
+
type: "array",
|
|
96
|
+
items: { type: "string" },
|
|
97
|
+
description: "Project yang terlibat",
|
|
98
|
+
},
|
|
99
|
+
source: { type: "string", description: "Sumber: user feedback, bug trace, review, dll" },
|
|
100
|
+
},
|
|
101
|
+
required: ["type", "title", "detail", "projects"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "check_energy",
|
|
106
|
+
description: "Cek apakah sekarang di luar jam kerja developer (Senin-Jumat 08:00-17:00 WIB). Jika ya, return warning. WAJIB panggil di awal sesi.",
|
|
107
|
+
inputSchema: { type: "object", properties: {} },
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "aggregate_patterns",
|
|
111
|
+
description: "Scan MISTAKES.md dari semua project, deteksi cross-project pattern. Bisa filter by keyword untuk cek apakah bug sudah pernah terjadi di project lain.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
keyword: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Optional: keyword untuk search across semua MISTAKES.md",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "analyze_history",
|
|
124
|
+
description: "Otak MCP: analisis AUDIT_LOG + MISTAKES semua project → return hotspot files, repeat bugs, warnings, dan suggestions. Panggil di awal sesi atau sebelum mulai task untuk dapat konteks sejarah.",
|
|
125
|
+
inputSchema: { type: "object", properties: {} },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "summarize_last_session",
|
|
129
|
+
description: "Narrative TL;DR dari sesi sebelumnya — baca AUDIT_LOG + MISTAKES + CURRENT_STATE untuk semua active_projects di current-focus.json. Return: recent tasks, open bugs, active tasks, plus TL;DR terstruktur. WAJIB panggil di awal sesi baru, setelah get_current_focus.",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
limit: {
|
|
134
|
+
type: "number",
|
|
135
|
+
description: "Jumlah recent task & mistake per project, default 5",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "smart_bootstrap",
|
|
142
|
+
description: "1 panggilan = semua context sesi. Gabungan: identity + focus + energy + project status + QCC aktif + recent mistakes + suggested next action + suggested skill. WAJIB panggil di awal sesi, gantikan 5 tool calls terpisah.",
|
|
143
|
+
inputSchema: { type: "object", properties: {} },
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "workflow_router",
|
|
147
|
+
description: "Auto-detect intent user → return workflow terstruktur (urutan tool + skill yang harus dipanggil). Intent: debug, new-feature, fix-bug, qcc, refactor, explore, review. Panggil SEBELUM mulai kerja supaya ada arah.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
intent: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "Apa yang mau dilakukan, misal 'debug order timeout', 'bikin fitur export PDF', 'fix bug invoice'",
|
|
154
|
+
},
|
|
155
|
+
module: {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Optional: module/area terkait (order, quotation, invoice, sample, dll)",
|
|
158
|
+
},
|
|
159
|
+
detail: {
|
|
160
|
+
type: "string",
|
|
161
|
+
description: "Optional: detail tambahan (function name, error message, dll)",
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
required: ["intent"],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "qcc_create",
|
|
169
|
+
description: "Buat QCC cycle baru (Step 1: Tema & Focus Target). Auto-generate QCC ID, init structure 8 langkah di QCC_CYCLES.md.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
slug: { type: "string", description: "Project slug, misal 'app-fe'" },
|
|
174
|
+
theme: { type: "string", description: "Tema QCC — deskripsi masalah utama" },
|
|
175
|
+
focus_target: { type: "string", description: "Fokus target spesifik (modul/area/metric)" },
|
|
176
|
+
},
|
|
177
|
+
required: ["slug", "theme", "focus_target"],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "qcc_update",
|
|
182
|
+
description: "Update step tertentu dalam QCC cycle (Step 2-8). Setiap step punya structure data berbeda: kondisi, SMART target, fishbone, rencana, pelaksanaan, evaluasi, validasi.",
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
slug: { type: "string", description: "Project slug" },
|
|
187
|
+
qcc_id: { type: "string", description: "QCC ID, misal 'QCC-001'" },
|
|
188
|
+
data: {
|
|
189
|
+
type: "object",
|
|
190
|
+
description: "Step data. Harus include 'step' (2-8) + field sesuai step. Step 2: {step,findings[]}, Step 3: {step,smart:{specific,measurable,achievable,relevant,timebound}}, Step 4: {step,root_problem,causes:[{category,items[]}]}, Step 5: {step,countermeasures:[{cause,action,pic,target_date}]}, Step 6: {step,execution:{no,status,date?,notes?}}, Step 7: {step,evaluation:{before,after,target_achieved,notes?}}, Step 8: {step,validation:{validated_by,date?,standardization?,horizontal_deploy?}}",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
required: ["slug", "qcc_id", "data"],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "qcc_status",
|
|
198
|
+
description: "Lihat status QCC cycles — semua project atau project tertentu. Return: summary per cycle, steps done/pending, current step.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
slug: { type: "string", description: "Optional: project slug. Kosong = semua project." },
|
|
203
|
+
qcc_id: { type: "string", description: "Optional: QCC ID tertentu untuk detail." },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "save_memory",
|
|
209
|
+
description: "Simpan memory persisten (keputusan, insight, bug, konteks, catatan) ke Loki.js DB. Memory otomatis searchable di sesi berikutnya. Gunakan untuk menyimpan informasi penting yang harus diingat antar sesi.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
type: {
|
|
214
|
+
type: "string",
|
|
215
|
+
enum: ["session_summary", "decision", "insight", "bug", "context", "user_note"],
|
|
216
|
+
description: "Tipe memory: session_summary (ringkasan sesi), decision (keputusan teknis), insight (temuan), bug (bug ditemukan), context (konteks project), user_note (catatan user)",
|
|
217
|
+
},
|
|
218
|
+
project: { type: "string", description: "Optional: project slug (misal 'app-fe', 'app-be')" },
|
|
219
|
+
title: { type: "string", description: "Judul singkat memory" },
|
|
220
|
+
content: { type: "string", description: "Isi detail memory" },
|
|
221
|
+
tags: {
|
|
222
|
+
type: "array",
|
|
223
|
+
items: { type: "string" },
|
|
224
|
+
description: "Tags untuk search, boleh bilingual: ['order', 'pesanan', 'timeout']",
|
|
225
|
+
},
|
|
226
|
+
session_id: { type: "string", description: "Optional: session identifier" },
|
|
227
|
+
},
|
|
228
|
+
required: ["type", "title", "content"],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "search_memory",
|
|
233
|
+
description: "Cari memory dengan full-text search (TF-IDF ranking + fuzzy match). Bisa filter by type & project. WAJIB panggil di awal sesi untuk load konteks dari sesi sebelumnya.",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
query: { type: "string", description: "Search query — bisa keyword, kalimat, bahkan typo" },
|
|
238
|
+
type: {
|
|
239
|
+
type: "string",
|
|
240
|
+
enum: ["session_summary", "decision", "insight", "bug", "context", "user_note"],
|
|
241
|
+
description: "Optional: filter by type",
|
|
242
|
+
},
|
|
243
|
+
project: { type: "string", description: "Optional: filter by project slug" },
|
|
244
|
+
limit: { type: "number", description: "Max results, default 10" },
|
|
245
|
+
fuzzy: { type: "number", description: "Fuzzy tolerance 0-1, default 0.2" },
|
|
246
|
+
},
|
|
247
|
+
required: ["query"],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "list_memories",
|
|
252
|
+
description: "List semua memory dengan pagination. Return preview (150 char), bukan full content — hemat token. Untuk detail, pakai search_memory.",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
type: {
|
|
257
|
+
type: "string",
|
|
258
|
+
enum: ["session_summary", "decision", "insight", "bug", "context", "user_note"],
|
|
259
|
+
description: "Optional: filter by type",
|
|
260
|
+
},
|
|
261
|
+
project: { type: "string", description: "Optional: filter by project slug" },
|
|
262
|
+
limit: { type: "number", description: "Max results per page, default 20" },
|
|
263
|
+
offset: { type: "number", description: "Skip N results, default 0" },
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "delete_memory",
|
|
269
|
+
description: "Hapus memory by ID. Gunakan list_memories atau search_memory dulu untuk dapat ID.",
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: {
|
|
273
|
+
id: { type: "string", description: "Memory ID, misal 'mem_abc123_xyz'" },
|
|
274
|
+
},
|
|
275
|
+
required: ["id"],
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "auto_dispatch",
|
|
280
|
+
description: "AUTONOMOUS DISPATCHER — Analisis query user, detect module & intent, search memory, lalu return dispatch plan berisi EXACT MCP tool calls yang harus dieksekusi di agent masing-masing. Panggil ini ketika user melaporkan bug/masalah/request yang melibatkan project. Agent Claude WAJIB eksekusi tool calls sesuai plan yang dikembalikan.",
|
|
281
|
+
inputSchema: {
|
|
282
|
+
type: "object",
|
|
283
|
+
properties: {
|
|
284
|
+
query: {
|
|
285
|
+
type: "string",
|
|
286
|
+
description: "Query user apa adanya, misal 'ada bug di invoice', 'trace flow order', 'bikin fitur export PDF'",
|
|
287
|
+
},
|
|
288
|
+
force_projects: {
|
|
289
|
+
type: "array",
|
|
290
|
+
items: { type: "string" },
|
|
291
|
+
description: "Optional: paksa dispatch ke project tertentu. Default: auto-detect dari module.",
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
required: ["query"],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "cross_search",
|
|
299
|
+
description: "Cari informasi di SEMUA project sekaligus — MISTAKES.md, CURRENT_STATE.md, AUDIT_LOG.md, dan opsional code files. Berguna untuk: cek apakah bug sudah pernah terjadi di project lain, find cross-project patterns, atau cari keyword di seluruh ekosistem.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
query: {
|
|
304
|
+
type: "string",
|
|
305
|
+
description: "Search query — keyword atau kalimat",
|
|
306
|
+
},
|
|
307
|
+
scope: {
|
|
308
|
+
type: "string",
|
|
309
|
+
description: "Optional: filter by project slug atau group name (sesuai field 'group' di project-registry.json). Default: semua active projects.",
|
|
310
|
+
},
|
|
311
|
+
include_code: {
|
|
312
|
+
type: "boolean",
|
|
313
|
+
description: "Optional: juga scan code files (lebih lambat). Default: false — hanya scan docs.",
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
required: ["query"],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
321
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
322
|
+
const { name, arguments: args } = request.params;
|
|
323
|
+
try {
|
|
324
|
+
let result;
|
|
325
|
+
switch (name) {
|
|
326
|
+
case "get_identity":
|
|
327
|
+
result = await getIdentity(CLAUDE_HOME);
|
|
328
|
+
break;
|
|
329
|
+
case "get_current_focus":
|
|
330
|
+
result = await getCurrentFocus(CLAUDE_HOME);
|
|
331
|
+
break;
|
|
332
|
+
case "list_projects":
|
|
333
|
+
result = await listProjects(CLAUDE_HOME);
|
|
334
|
+
break;
|
|
335
|
+
case "get_project_status":
|
|
336
|
+
result = await getProjectStatus(CLAUDE_HOME, String(args?.slug ?? ""));
|
|
337
|
+
break;
|
|
338
|
+
case "list_recent_tasks":
|
|
339
|
+
result = await listRecentTasks(CLAUDE_HOME, args?.slug ? String(args.slug) : undefined, typeof args?.limit === "number" ? args.limit : 10);
|
|
340
|
+
break;
|
|
341
|
+
case "switch_focus":
|
|
342
|
+
result = await switchFocus(CLAUDE_HOME, String(args?.profile ?? ""));
|
|
343
|
+
break;
|
|
344
|
+
case "log_insight":
|
|
345
|
+
result = await logInsight(CLAUDE_HOME, {
|
|
346
|
+
type: String(args?.type ?? "feedback"),
|
|
347
|
+
title: String(args?.title ?? ""),
|
|
348
|
+
detail: String(args?.detail ?? ""),
|
|
349
|
+
projects: Array.isArray(args?.projects) ? args.projects.map(String) : [],
|
|
350
|
+
source: args?.source ? String(args.source) : undefined,
|
|
351
|
+
});
|
|
352
|
+
break;
|
|
353
|
+
case "check_energy":
|
|
354
|
+
result = checkEnergy();
|
|
355
|
+
break;
|
|
356
|
+
case "aggregate_patterns":
|
|
357
|
+
result = await aggregatePatterns(CLAUDE_HOME, args?.keyword ? String(args.keyword) : undefined);
|
|
358
|
+
break;
|
|
359
|
+
case "analyze_history":
|
|
360
|
+
result = await analyzeHistory(CLAUDE_HOME);
|
|
361
|
+
break;
|
|
362
|
+
case "summarize_last_session":
|
|
363
|
+
result = await summarizeLastSession(CLAUDE_HOME, typeof args?.limit === "number" ? args.limit : 5);
|
|
364
|
+
break;
|
|
365
|
+
case "smart_bootstrap":
|
|
366
|
+
result = await smartBootstrap(CLAUDE_HOME);
|
|
367
|
+
break;
|
|
368
|
+
case "workflow_router":
|
|
369
|
+
result = await workflowRouter(CLAUDE_HOME, {
|
|
370
|
+
intent: String(args?.intent ?? ""),
|
|
371
|
+
module: args?.module ? String(args.module) : undefined,
|
|
372
|
+
detail: args?.detail ? String(args.detail) : undefined,
|
|
373
|
+
});
|
|
374
|
+
break;
|
|
375
|
+
case "qcc_create":
|
|
376
|
+
result = await qccCreate(CLAUDE_HOME, {
|
|
377
|
+
slug: String(args?.slug ?? ""),
|
|
378
|
+
theme: String(args?.theme ?? ""),
|
|
379
|
+
focus_target: String(args?.focus_target ?? ""),
|
|
380
|
+
});
|
|
381
|
+
break;
|
|
382
|
+
case "qcc_update":
|
|
383
|
+
result = await qccUpdate(CLAUDE_HOME, {
|
|
384
|
+
slug: String(args?.slug ?? ""),
|
|
385
|
+
qcc_id: String(args?.qcc_id ?? ""),
|
|
386
|
+
data: args?.data,
|
|
387
|
+
});
|
|
388
|
+
break;
|
|
389
|
+
case "qcc_status":
|
|
390
|
+
result = await qccStatus(CLAUDE_HOME, args?.slug ? String(args.slug) : undefined, args?.qcc_id ? String(args.qcc_id) : undefined);
|
|
391
|
+
break;
|
|
392
|
+
case "save_memory":
|
|
393
|
+
result = await saveMemory(CLAUDE_HOME, {
|
|
394
|
+
type: String(args?.type ?? "user_note"),
|
|
395
|
+
project: args?.project ? String(args.project) : undefined,
|
|
396
|
+
title: String(args?.title ?? ""),
|
|
397
|
+
content: String(args?.content ?? ""),
|
|
398
|
+
tags: Array.isArray(args?.tags) ? args.tags.map(String) : undefined,
|
|
399
|
+
session_id: args?.session_id ? String(args.session_id) : undefined,
|
|
400
|
+
});
|
|
401
|
+
break;
|
|
402
|
+
case "search_memory":
|
|
403
|
+
result = await searchMemory(CLAUDE_HOME, {
|
|
404
|
+
query: String(args?.query ?? ""),
|
|
405
|
+
type: args?.type ? String(args.type) : undefined,
|
|
406
|
+
project: args?.project ? String(args.project) : undefined,
|
|
407
|
+
limit: typeof args?.limit === "number" ? args.limit : 10,
|
|
408
|
+
fuzzy: typeof args?.fuzzy === "number" ? args.fuzzy : 0.2,
|
|
409
|
+
});
|
|
410
|
+
break;
|
|
411
|
+
case "list_memories":
|
|
412
|
+
result = await listMemories(CLAUDE_HOME, {
|
|
413
|
+
type: args?.type ? String(args.type) : undefined,
|
|
414
|
+
project: args?.project ? String(args.project) : undefined,
|
|
415
|
+
limit: typeof args?.limit === "number" ? args.limit : 20,
|
|
416
|
+
offset: typeof args?.offset === "number" ? args.offset : 0,
|
|
417
|
+
});
|
|
418
|
+
break;
|
|
419
|
+
case "delete_memory":
|
|
420
|
+
result = await deleteMemory(CLAUDE_HOME, String(args?.id ?? ""));
|
|
421
|
+
break;
|
|
422
|
+
case "auto_dispatch":
|
|
423
|
+
result = await autoDispatch(CLAUDE_HOME, {
|
|
424
|
+
query: String(args?.query ?? ""),
|
|
425
|
+
force_projects: Array.isArray(args?.force_projects) ? args.force_projects.map(String) : undefined,
|
|
426
|
+
});
|
|
427
|
+
break;
|
|
428
|
+
case "cross_search":
|
|
429
|
+
result = await crossSearch(CLAUDE_HOME, {
|
|
430
|
+
query: String(args?.query ?? ""),
|
|
431
|
+
scope: args?.scope ? String(args.scope) : undefined,
|
|
432
|
+
include_code: args?.include_code === true,
|
|
433
|
+
});
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
437
|
+
}
|
|
438
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{
|
|
444
|
+
type: "text",
|
|
445
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
isError: true,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
const transport = new StdioServerTransport();
|
|
453
|
+
await server.connect(transport);
|
|
454
|
+
console.error(`[orchestrator-mcp] connected. CLAUDE_HOME=${CLAUDE_HOME}`);
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Manager — Loki.js (storage) + MiniSearch (search)
|
|
3
|
+
*
|
|
4
|
+
* Auto-persist ke ~/.claude/data/memory.json
|
|
5
|
+
* Full-text search dengan TF-IDF ranking + fuzzy match
|
|
6
|
+
*/
|
|
7
|
+
import loki from "lokijs";
|
|
8
|
+
import MiniSearch from "minisearch";
|
|
9
|
+
import { mkdirSync, existsSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
let db = null;
|
|
12
|
+
let searchIndex = null;
|
|
13
|
+
let memoriesCollection = null;
|
|
14
|
+
let isInitialized = false;
|
|
15
|
+
function getDbPath(claudeHome) {
|
|
16
|
+
const dataDir = join(claudeHome, "data");
|
|
17
|
+
if (!existsSync(dataDir)) {
|
|
18
|
+
mkdirSync(dataDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
return join(dataDir, "memory.json");
|
|
21
|
+
}
|
|
22
|
+
function generateId() {
|
|
23
|
+
const ts = Date.now().toString(36);
|
|
24
|
+
const rand = Math.random().toString(36).substring(2, 8);
|
|
25
|
+
return `mem_${ts}_${rand}`;
|
|
26
|
+
}
|
|
27
|
+
function rebuildSearchIndex(entries) {
|
|
28
|
+
searchIndex = new MiniSearch({
|
|
29
|
+
fields: ["title", "content", "tags_str", "project", "type"],
|
|
30
|
+
storeFields: ["id", "title", "type", "project", "created_at", "tags"],
|
|
31
|
+
searchOptions: {
|
|
32
|
+
boost: { title: 3, tags_str: 2, content: 1 },
|
|
33
|
+
prefix: true,
|
|
34
|
+
fuzzy: 0.2,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const docs = entries.map((e) => ({
|
|
38
|
+
id: e.id,
|
|
39
|
+
title: e.title,
|
|
40
|
+
content: e.content,
|
|
41
|
+
tags_str: (e.tags || []).join(" "),
|
|
42
|
+
project: e.project || "",
|
|
43
|
+
type: e.type,
|
|
44
|
+
created_at: e.created_at,
|
|
45
|
+
tags: e.tags,
|
|
46
|
+
}));
|
|
47
|
+
searchIndex.addAll(docs);
|
|
48
|
+
}
|
|
49
|
+
export function initMemory(claudeHome) {
|
|
50
|
+
if (isInitialized && db)
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const dbPath = getDbPath(claudeHome);
|
|
54
|
+
db = new loki(dbPath, {
|
|
55
|
+
autoload: true,
|
|
56
|
+
autosave: true,
|
|
57
|
+
autosaveInterval: 5000,
|
|
58
|
+
autoloadCallback: (err) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
reject(err);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
memoriesCollection = db.getCollection("memories");
|
|
64
|
+
if (!memoriesCollection) {
|
|
65
|
+
memoriesCollection = db.addCollection("memories", {
|
|
66
|
+
indices: ["type", "project", "created_at"],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// Build search index from existing data
|
|
70
|
+
rebuildSearchIndex(memoriesCollection.find());
|
|
71
|
+
isInitialized = true;
|
|
72
|
+
resolve();
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function saveMemory(claudeHome, entry) {
|
|
78
|
+
await initMemory(claudeHome);
|
|
79
|
+
const now = new Date().toISOString();
|
|
80
|
+
const id = entry.id || generateId();
|
|
81
|
+
// Check if updating existing
|
|
82
|
+
const existing = memoriesCollection.findOne({ id });
|
|
83
|
+
if (existing) {
|
|
84
|
+
existing.title = entry.title;
|
|
85
|
+
existing.content = entry.content;
|
|
86
|
+
existing.type = entry.type;
|
|
87
|
+
existing.project = entry.project || existing.project;
|
|
88
|
+
existing.tags = entry.tags || existing.tags;
|
|
89
|
+
existing.updated_at = now;
|
|
90
|
+
memoriesCollection.update(existing);
|
|
91
|
+
// Rebuild search index
|
|
92
|
+
rebuildSearchIndex(memoriesCollection.find());
|
|
93
|
+
await new Promise((resolve, reject) => {
|
|
94
|
+
db.saveDatabase((err) => {
|
|
95
|
+
if (err)
|
|
96
|
+
reject(err);
|
|
97
|
+
else
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
id,
|
|
104
|
+
total_memories: memoriesCollection.count(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// New entry
|
|
108
|
+
const doc = {
|
|
109
|
+
id,
|
|
110
|
+
type: entry.type,
|
|
111
|
+
project: entry.project || "",
|
|
112
|
+
title: entry.title,
|
|
113
|
+
content: entry.content,
|
|
114
|
+
tags: entry.tags || [],
|
|
115
|
+
session_id: entry.session_id || "",
|
|
116
|
+
created_at: now,
|
|
117
|
+
updated_at: now,
|
|
118
|
+
};
|
|
119
|
+
memoriesCollection.insert(doc);
|
|
120
|
+
// Add to search index
|
|
121
|
+
searchIndex.add({
|
|
122
|
+
id: doc.id,
|
|
123
|
+
title: doc.title,
|
|
124
|
+
content: doc.content,
|
|
125
|
+
tags_str: doc.tags.join(" "),
|
|
126
|
+
project: doc.project,
|
|
127
|
+
type: doc.type,
|
|
128
|
+
created_at: doc.created_at,
|
|
129
|
+
tags: doc.tags,
|
|
130
|
+
});
|
|
131
|
+
// Force save (with promise wrapper for async safety)
|
|
132
|
+
await new Promise((resolve, reject) => {
|
|
133
|
+
db.saveDatabase((err) => {
|
|
134
|
+
if (err)
|
|
135
|
+
reject(err);
|
|
136
|
+
else
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
id,
|
|
143
|
+
total_memories: memoriesCollection.count(),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export async function searchMemory(claudeHome, options) {
|
|
147
|
+
await initMemory(claudeHome);
|
|
148
|
+
const limit = options.limit || 10;
|
|
149
|
+
// Full-text search with MiniSearch
|
|
150
|
+
let results = searchIndex.search(options.query, {
|
|
151
|
+
fuzzy: options.fuzzy ?? 0.2,
|
|
152
|
+
prefix: true,
|
|
153
|
+
});
|
|
154
|
+
// Filter by type if specified
|
|
155
|
+
if (options.type) {
|
|
156
|
+
results = results.filter((r) => r.type === options.type);
|
|
157
|
+
}
|
|
158
|
+
// Filter by project if specified
|
|
159
|
+
if (options.project) {
|
|
160
|
+
results = results.filter((r) => r.project === options.project);
|
|
161
|
+
}
|
|
162
|
+
// Limit results
|
|
163
|
+
const topResults = results.slice(0, limit);
|
|
164
|
+
// Fetch full content for top results
|
|
165
|
+
const detailed = topResults.map((r) => {
|
|
166
|
+
const full = memoriesCollection.findOne({ id: r.id });
|
|
167
|
+
return {
|
|
168
|
+
id: r.id,
|
|
169
|
+
score: Math.round(r.score * 100) / 100,
|
|
170
|
+
title: r.title,
|
|
171
|
+
type: r.type,
|
|
172
|
+
project: r.project || "",
|
|
173
|
+
content: full?.content || "",
|
|
174
|
+
tags: full?.tags || [],
|
|
175
|
+
created_at: full?.created_at || "",
|
|
176
|
+
updated_at: full?.updated_at || "",
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
results: detailed,
|
|
181
|
+
total_found: results.length,
|
|
182
|
+
query: options.query,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export async function listMemories(claudeHome, options) {
|
|
186
|
+
await initMemory(claudeHome);
|
|
187
|
+
let query = {};
|
|
188
|
+
if (options?.type)
|
|
189
|
+
query.type = options.type;
|
|
190
|
+
if (options?.project)
|
|
191
|
+
query.project = options.project;
|
|
192
|
+
const allResults = memoriesCollection
|
|
193
|
+
.chain()
|
|
194
|
+
.find(query)
|
|
195
|
+
.simplesort("created_at", { desc: true })
|
|
196
|
+
.data();
|
|
197
|
+
const offset = options?.offset || 0;
|
|
198
|
+
const limit = options?.limit || 20;
|
|
199
|
+
const sliced = allResults.slice(offset, offset + limit);
|
|
200
|
+
// Return summary (tanpa full content, hemat token)
|
|
201
|
+
const memories = sliced.map((m) => ({
|
|
202
|
+
id: m.id,
|
|
203
|
+
type: m.type,
|
|
204
|
+
project: m.project,
|
|
205
|
+
title: m.title,
|
|
206
|
+
tags: m.tags,
|
|
207
|
+
preview: m.content.substring(0, 150) + (m.content.length > 150 ? "..." : ""),
|
|
208
|
+
created_at: m.created_at,
|
|
209
|
+
updated_at: m.updated_at,
|
|
210
|
+
}));
|
|
211
|
+
return {
|
|
212
|
+
memories,
|
|
213
|
+
total: allResults.length,
|
|
214
|
+
showing: `${offset + 1}-${offset + sliced.length} of ${allResults.length}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export async function deleteMemory(claudeHome, id) {
|
|
218
|
+
await initMemory(claudeHome);
|
|
219
|
+
const existing = memoriesCollection.findOne({ id });
|
|
220
|
+
if (!existing) {
|
|
221
|
+
return { success: false, message: `Memory '${id}' not found` };
|
|
222
|
+
}
|
|
223
|
+
memoriesCollection.remove(existing);
|
|
224
|
+
rebuildSearchIndex(memoriesCollection.find());
|
|
225
|
+
await new Promise((resolve, reject) => {
|
|
226
|
+
db.saveDatabase((err) => {
|
|
227
|
+
if (err)
|
|
228
|
+
reject(err);
|
|
229
|
+
else
|
|
230
|
+
resolve();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
return { success: true, message: `Memory '${id}' deleted` };
|
|
234
|
+
}
|