@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
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
// Intent patterns and their workflows
|
|
4
|
+
const WORKFLOW_TEMPLATES = {
|
|
5
|
+
debug: (args) => [
|
|
6
|
+
{
|
|
7
|
+
order: 1,
|
|
8
|
+
action: "Cek known bugs terkait",
|
|
9
|
+
type: "mcp_tool",
|
|
10
|
+
tool: "get_mistakes",
|
|
11
|
+
args_hint: { topic: args.module ?? "" },
|
|
12
|
+
reason: "Jangan debug masalah yang sudah pernah ditemukan",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
order: 2,
|
|
16
|
+
action: "Cek QCC cycle aktif yang related",
|
|
17
|
+
type: "mcp_tool",
|
|
18
|
+
tool: "qcc_status",
|
|
19
|
+
args_hint: args.module ? { slug: args.module } : {},
|
|
20
|
+
reason: "Bug ini mungkin sudah di-track dalam QCC",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
order: 3,
|
|
24
|
+
action: "Systematic debugging",
|
|
25
|
+
type: "skill",
|
|
26
|
+
skill: "/debug",
|
|
27
|
+
reason: "Trace root cause secara terstruktur",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
order: 4,
|
|
31
|
+
action: "Cari function terkait",
|
|
32
|
+
type: "mcp_tool",
|
|
33
|
+
tool: "find_symbol",
|
|
34
|
+
args_hint: { name: args.detail ?? "" },
|
|
35
|
+
reason: "Trace function yang bermasalah",
|
|
36
|
+
gate: "Jika perlu trace code",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
order: 5,
|
|
40
|
+
action: "Cek blast radius sebelum fix",
|
|
41
|
+
type: "mcp_tool",
|
|
42
|
+
tool: "predict_impact",
|
|
43
|
+
args_hint: { symbol: args.detail ?? "" },
|
|
44
|
+
reason: "Pastikan fix tidak break hal lain",
|
|
45
|
+
gate: "Jika akan modify function",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
order: 6,
|
|
49
|
+
action: "Catat bug yang ditemukan",
|
|
50
|
+
type: "mcp_tool",
|
|
51
|
+
tool: "record_mistake",
|
|
52
|
+
reason: "Supaya tidak terulang — auto cross-project check",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
order: 7,
|
|
56
|
+
action: "Update QCC jika ada cycle aktif",
|
|
57
|
+
type: "mcp_tool",
|
|
58
|
+
tool: "qcc_update",
|
|
59
|
+
reason: "Track di QCC Step 6 (Pelaksanaan)",
|
|
60
|
+
gate: "Jika ada QCC cycle terkait",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
order: 8,
|
|
64
|
+
action: "Log task selesai",
|
|
65
|
+
type: "mcp_tool",
|
|
66
|
+
tool: "log_task",
|
|
67
|
+
reason: "Audit trail",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
"new-feature": (args) => [
|
|
71
|
+
{
|
|
72
|
+
order: 1,
|
|
73
|
+
action: "Brainstorm requirements",
|
|
74
|
+
type: "skill",
|
|
75
|
+
skill: "/brainstorm",
|
|
76
|
+
reason: "Pahami requirements sebelum coding",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
order: 2,
|
|
80
|
+
action: "Buat implementation plan",
|
|
81
|
+
type: "skill",
|
|
82
|
+
skill: "/write-plan",
|
|
83
|
+
reason: "Breakdown task, identify files",
|
|
84
|
+
gate: "Setelah brainstorm di-approve",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
order: 3,
|
|
88
|
+
action: "Cek impact ke existing code",
|
|
89
|
+
type: "mcp_tool",
|
|
90
|
+
tool: "predict_impact",
|
|
91
|
+
reason: "Pastikan fitur baru tidak break existing",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
order: 4,
|
|
95
|
+
action: "Eksekusi plan",
|
|
96
|
+
type: "skill",
|
|
97
|
+
skill: "/execute-plan",
|
|
98
|
+
reason: "Coding terstruktur sesuai plan",
|
|
99
|
+
gate: "Setelah plan di-approve",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
order: 5,
|
|
103
|
+
action: "Code review",
|
|
104
|
+
type: "skill",
|
|
105
|
+
skill: "/code-review",
|
|
106
|
+
reason: "Review + security audit",
|
|
107
|
+
gate: "Setelah coding selesai",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
order: 6,
|
|
111
|
+
action: "Log task selesai",
|
|
112
|
+
type: "mcp_tool",
|
|
113
|
+
tool: "log_task",
|
|
114
|
+
reason: "Audit trail",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
"fix-bug": (args) => [
|
|
118
|
+
{
|
|
119
|
+
order: 1,
|
|
120
|
+
action: "Cek known bugs",
|
|
121
|
+
type: "mcp_tool",
|
|
122
|
+
tool: "get_mistakes",
|
|
123
|
+
args_hint: { topic: args.module ?? "" },
|
|
124
|
+
reason: "Mungkin sudah pernah ditemukan",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
order: 2,
|
|
128
|
+
action: "Trace bug",
|
|
129
|
+
type: "skill",
|
|
130
|
+
skill: "/trace-bug",
|
|
131
|
+
reason: "Trace dari Blade → Controller → API",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
order: 3,
|
|
135
|
+
action: "Cek blast radius",
|
|
136
|
+
type: "mcp_tool",
|
|
137
|
+
tool: "predict_impact",
|
|
138
|
+
args_hint: { symbol: args.detail ?? "" },
|
|
139
|
+
reason: "Fix tidak boleh break hal lain",
|
|
140
|
+
gate: "Sebelum modify code",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
order: 4,
|
|
144
|
+
action: "Fix & test",
|
|
145
|
+
type: "manual",
|
|
146
|
+
reason: "Apply fix, test di browser",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
order: 5,
|
|
150
|
+
action: "Catat bug",
|
|
151
|
+
type: "mcp_tool",
|
|
152
|
+
tool: "record_mistake",
|
|
153
|
+
reason: "Knowledge base untuk future reference",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
order: 6,
|
|
157
|
+
action: "Log task",
|
|
158
|
+
type: "mcp_tool",
|
|
159
|
+
tool: "log_task",
|
|
160
|
+
reason: "Audit trail",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
qcc: (args) => [
|
|
164
|
+
{
|
|
165
|
+
order: 1,
|
|
166
|
+
action: "Buat QCC cycle baru",
|
|
167
|
+
type: "mcp_tool",
|
|
168
|
+
tool: "qcc_create",
|
|
169
|
+
reason: "Step 1: Tema & Focus Target",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
order: 2,
|
|
173
|
+
action: "Auto-analyze kondisi",
|
|
174
|
+
type: "mcp_tool",
|
|
175
|
+
tool: "qcc_analyze",
|
|
176
|
+
args_hint: { scope: "conditions", keyword: args.module ?? "" },
|
|
177
|
+
reason: "Step 2: Data dari MISTAKES.md + AUDIT_LOG.md",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
order: 3,
|
|
181
|
+
action: "Define target SMART",
|
|
182
|
+
type: "mcp_tool",
|
|
183
|
+
tool: "qcc_update",
|
|
184
|
+
args_hint: { step: "3" },
|
|
185
|
+
reason: "Step 3: S/M/A/R/T",
|
|
186
|
+
gate: "Developer input needed",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
order: 4,
|
|
190
|
+
action: "Auto-analyze fishbone",
|
|
191
|
+
type: "mcp_tool",
|
|
192
|
+
tool: "qcc_analyze",
|
|
193
|
+
args_hint: { scope: "fishbone", keyword: args.module ?? "" },
|
|
194
|
+
reason: "Step 4: Auto-categorize causes",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
order: 5,
|
|
198
|
+
action: "Rencana penanggulangan",
|
|
199
|
+
type: "mcp_tool",
|
|
200
|
+
tool: "qcc_update",
|
|
201
|
+
args_hint: { step: "5" },
|
|
202
|
+
reason: "Step 5: Countermeasures",
|
|
203
|
+
gate: "Developer input needed",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
order: 6,
|
|
207
|
+
action: "Track pelaksanaan",
|
|
208
|
+
type: "mcp_tool",
|
|
209
|
+
tool: "qcc_update",
|
|
210
|
+
args_hint: { step: "6" },
|
|
211
|
+
reason: "Step 6: Update per-action",
|
|
212
|
+
gate: "Ongoing — update as work progresses",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
order: 7,
|
|
216
|
+
action: "Auto-evaluate before/after",
|
|
217
|
+
type: "mcp_tool",
|
|
218
|
+
tool: "qcc_evaluate",
|
|
219
|
+
reason: "Step 7: Compare MISTAKES.md data",
|
|
220
|
+
gate: "Setelah semua countermeasures done",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
order: 8,
|
|
224
|
+
action: "Validasi & standardisasi",
|
|
225
|
+
type: "mcp_tool",
|
|
226
|
+
tool: "qcc_update",
|
|
227
|
+
args_hint: { step: "8" },
|
|
228
|
+
reason: "Step 8: Sign-off + horizontal deploy",
|
|
229
|
+
gate: "Setelah evaluasi confirm target tercapai",
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
refactor: (args) => [
|
|
233
|
+
{
|
|
234
|
+
order: 1,
|
|
235
|
+
action: "Cek impact sebelum refactor",
|
|
236
|
+
type: "mcp_tool",
|
|
237
|
+
tool: "predict_impact",
|
|
238
|
+
args_hint: { symbol: args.detail ?? "" },
|
|
239
|
+
reason: "Tahu blast radius sebelum mulai",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
order: 2,
|
|
243
|
+
action: "Buat plan",
|
|
244
|
+
type: "skill",
|
|
245
|
+
skill: "/write-plan",
|
|
246
|
+
reason: "Refactor harus terstruktur",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
order: 3,
|
|
250
|
+
action: "Eksekusi",
|
|
251
|
+
type: "skill",
|
|
252
|
+
skill: "/execute-plan",
|
|
253
|
+
reason: "Step-by-step sesuai plan",
|
|
254
|
+
gate: "Setelah plan di-approve",
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
order: 4,
|
|
258
|
+
action: "Code review",
|
|
259
|
+
type: "skill",
|
|
260
|
+
skill: "/code-review",
|
|
261
|
+
reason: "Pastikan refactor tidak introduce bugs",
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
order: 5,
|
|
265
|
+
action: "Log task",
|
|
266
|
+
type: "mcp_tool",
|
|
267
|
+
tool: "log_task",
|
|
268
|
+
reason: "Audit trail",
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
explore: (args) => [
|
|
272
|
+
{
|
|
273
|
+
order: 1,
|
|
274
|
+
action: "Generate system map",
|
|
275
|
+
type: "mcp_tool",
|
|
276
|
+
tool: "generate_system_map",
|
|
277
|
+
reason: "Overview arsitektur project",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
order: 2,
|
|
281
|
+
action: "List routes",
|
|
282
|
+
type: "mcp_tool",
|
|
283
|
+
tool: "list_routes",
|
|
284
|
+
reason: "Semua endpoint FE",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
order: 3,
|
|
288
|
+
action: "Cek current state",
|
|
289
|
+
type: "mcp_tool",
|
|
290
|
+
tool: "get_current_state",
|
|
291
|
+
reason: "Task aktif & yang baru selesai",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
order: 4,
|
|
295
|
+
action: "Cek known bugs",
|
|
296
|
+
type: "mcp_tool",
|
|
297
|
+
tool: "get_mistakes",
|
|
298
|
+
args_hint: args.module ? { topic: args.module } : {},
|
|
299
|
+
reason: "Known issues di area ini",
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
review: (_args) => [
|
|
303
|
+
{
|
|
304
|
+
order: 1,
|
|
305
|
+
action: "Code review + security audit",
|
|
306
|
+
type: "skill",
|
|
307
|
+
skill: "/code-review",
|
|
308
|
+
reason: "Review perubahan yang sudah dibuat",
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
order: 2,
|
|
312
|
+
action: "Cek blast radius",
|
|
313
|
+
type: "mcp_tool",
|
|
314
|
+
tool: "predict_impact",
|
|
315
|
+
reason: "Pastikan perubahan tidak impact modul lain",
|
|
316
|
+
gate: "Jika ada function yang diubah",
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
order: 3,
|
|
320
|
+
action: "Log review",
|
|
321
|
+
type: "mcp_tool",
|
|
322
|
+
tool: "log_task",
|
|
323
|
+
reason: "Audit trail",
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
// --- Response Mode (Caveman-style compression per intent) ---
|
|
328
|
+
const INTENSITY_MAP = {
|
|
329
|
+
debug: "ultra",
|
|
330
|
+
"fix-bug": "ultra",
|
|
331
|
+
review: "ultra",
|
|
332
|
+
refactor: "full",
|
|
333
|
+
"new-feature": "full",
|
|
334
|
+
explore: "lite",
|
|
335
|
+
qcc: "off",
|
|
336
|
+
};
|
|
337
|
+
const RESPONSE_RULES = {
|
|
338
|
+
ultra: [
|
|
339
|
+
"Maks 2 kalimat per poin. No intro, no recap, no filler.",
|
|
340
|
+
"Code snippet > penjelasan panjang. Simbol OK: → ✅ ❌ ⚠️",
|
|
341
|
+
"Jangan ulangi yang sudah user tahu. Fragment > kalimat lengkap.",
|
|
342
|
+
"Bilingual OK: Indo + English technical terms.",
|
|
343
|
+
],
|
|
344
|
+
full: [
|
|
345
|
+
"Fragment > kalimat lengkap. No filler (Great question, Let me explain...).",
|
|
346
|
+
"Jawab langsung, skip pengantar dan penutup.",
|
|
347
|
+
"Code snippet > penjelasan panjang kalau bisa.",
|
|
348
|
+
"Bilingual OK: Indo + English technical terms.",
|
|
349
|
+
],
|
|
350
|
+
lite: [
|
|
351
|
+
"Kalimat lengkap tapi ringkas. Skip filler dan basa-basi.",
|
|
352
|
+
"Boleh jelaskan jika konteks baru bagi user.",
|
|
353
|
+
"Bilingual OK: Indo + English technical terms.",
|
|
354
|
+
],
|
|
355
|
+
off: [],
|
|
356
|
+
};
|
|
357
|
+
// Intent detection keywords
|
|
358
|
+
const INTENT_KEYWORDS = {
|
|
359
|
+
debug: ["debug", "error", "bug", "gagal", "tidak jalan", "broken", "issue", "trace", "investigate"],
|
|
360
|
+
"new-feature": ["fitur baru", "new feature", "tambah fitur", "implement", "bikin", "buat baru", "create"],
|
|
361
|
+
"fix-bug": ["fix", "perbaiki", "hotfix", "patch", "benerin"],
|
|
362
|
+
qcc: ["qcc", "quality control", "improvement", "8 langkah", "fishbone", "root cause"],
|
|
363
|
+
refactor: ["refactor", "cleanup", "restructure", "reorganize", "split", "extract"],
|
|
364
|
+
explore: ["explore", "overview", "arsitektur", "architecture", "map", "understanding", "pahami"],
|
|
365
|
+
review: ["review", "audit", "cek code", "check code"],
|
|
366
|
+
};
|
|
367
|
+
export async function workflowRouter(claudeHome, args) {
|
|
368
|
+
const intentLower = args.intent.toLowerCase();
|
|
369
|
+
// Detect intent
|
|
370
|
+
let detectedIntent = "explore"; // default
|
|
371
|
+
let bestScore = 0;
|
|
372
|
+
for (const [intent, keywords] of Object.entries(INTENT_KEYWORDS)) {
|
|
373
|
+
let score = 0;
|
|
374
|
+
for (const kw of keywords) {
|
|
375
|
+
if (intentLower.includes(kw))
|
|
376
|
+
score += kw.split(" ").length; // multi-word = higher score
|
|
377
|
+
}
|
|
378
|
+
if (score > bestScore) {
|
|
379
|
+
bestScore = score;
|
|
380
|
+
detectedIntent = intent;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Get workflow template
|
|
384
|
+
const templateFn = WORKFLOW_TEMPLATES[detectedIntent];
|
|
385
|
+
if (!templateFn) {
|
|
386
|
+
return {
|
|
387
|
+
intent: args.intent,
|
|
388
|
+
workflow_name: "unknown",
|
|
389
|
+
response_mode: { intensity: "full", rules: RESPONSE_RULES.full },
|
|
390
|
+
steps: [],
|
|
391
|
+
estimated_calls: 0,
|
|
392
|
+
token_budget: "—",
|
|
393
|
+
notes: [`Intent "${detectedIntent}" tidak dikenali. Coba: debug, new-feature, fix-bug, qcc, refactor, explore, review.`],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const steps = templateFn(args);
|
|
397
|
+
// Check if there's active QCC that might be related
|
|
398
|
+
const notes = [];
|
|
399
|
+
try {
|
|
400
|
+
const focusRaw = await readFile(join(claudeHome, "current-focus.json"), "utf-8");
|
|
401
|
+
const focus = JSON.parse(focusRaw);
|
|
402
|
+
const profile = focus.profiles?.[focus.active_profile ?? ""] ?? {};
|
|
403
|
+
const activeProjects = profile.active_projects?.map((p) => p.slug ?? p) ?? [];
|
|
404
|
+
for (const slug of activeProjects) {
|
|
405
|
+
try {
|
|
406
|
+
const qccPath = join(claudeHome, "project-docs", slug, "QCC_CYCLES.md");
|
|
407
|
+
const qcc = await readFile(qccPath, "utf-8");
|
|
408
|
+
if (qcc.includes("**Status**: active")) {
|
|
409
|
+
const match = qcc.match(/\[(QCC-\d+)\] Tema: (.+)/);
|
|
410
|
+
if (match) {
|
|
411
|
+
notes.push(`Ada QCC aktif: ${match[1]} "${match[2].trim()}". Hasil kerja ini mungkin bisa update QCC.`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch { /* no qcc file */ }
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch { /* no focus */ }
|
|
419
|
+
// Estimate tokens
|
|
420
|
+
const toolCalls = steps.filter((s) => s.type === "mcp_tool").length;
|
|
421
|
+
const skillCalls = steps.filter((s) => s.type === "skill").length;
|
|
422
|
+
const estimatedTokens = toolCalls * 200 + skillCalls * 800;
|
|
423
|
+
// Determine response mode based on intent
|
|
424
|
+
const intensity = INTENSITY_MAP[detectedIntent] ?? "full";
|
|
425
|
+
const responseMode = {
|
|
426
|
+
intensity,
|
|
427
|
+
rules: RESPONSE_RULES[intensity] ?? [],
|
|
428
|
+
};
|
|
429
|
+
return {
|
|
430
|
+
intent: args.intent,
|
|
431
|
+
workflow_name: detectedIntent,
|
|
432
|
+
response_mode: responseMode,
|
|
433
|
+
steps,
|
|
434
|
+
estimated_calls: toolCalls + skillCalls,
|
|
435
|
+
token_budget: `~${estimatedTokens} tokens (${toolCalls} MCP tools × 200 + ${skillCalls} skills × 800)`,
|
|
436
|
+
notes,
|
|
437
|
+
};
|
|
438
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agilsee/mcp-orchestrator",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "MCP Orchestrator — identity, focus, workflow routing, QCC tracking, response compression. Multi-project memory layer for Claude Code with Loki.js + MiniSearch.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-orchestrator": "dist/index.js",
|
|
9
|
+
"asap-mcp": "bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"bin/",
|
|
14
|
+
"templates/"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"claude",
|
|
24
|
+
"orchestrator",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"ai-agent",
|
|
27
|
+
"memory",
|
|
28
|
+
"loki"
|
|
29
|
+
],
|
|
30
|
+
"author": "agilsee",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
37
|
+
"lokijs": "^1.5.12",
|
|
38
|
+
"minisearch": "^7.2.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.11.0",
|
|
42
|
+
"typescript": "^5.4.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 { join } from "path";
|
|
6
|
+
import { readFile } from "fs/promises";
|
|
7
|
+
// __STACK_IMPORTS__
|
|
8
|
+
|
|
9
|
+
const PROJECT_SLUG = process.env.PROJECT_SLUG ?? "__PROJECT_SLUG__";
|
|
10
|
+
const PROJECT_PATH = process.env.PROJECT_PATH ?? "__PROJECT_PATH__";
|
|
11
|
+
const CLAUDE_HOME = process.env.CLAUDE_HOME ?? `${process.env.USERPROFILE ?? process.env.HOME}/.claude`;
|
|
12
|
+
const DOCS_PATH = join(CLAUDE_HOME, "project-docs", PROJECT_SLUG);
|
|
13
|
+
|
|
14
|
+
// __SYMBOL_INDEX_INIT__
|
|
15
|
+
|
|
16
|
+
const server = new Server(
|
|
17
|
+
{ name: "__PROJECT_SLUG__", version: "0.1.0" },
|
|
18
|
+
{ capabilities: { tools: {} } }
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const tools = __TOOLS_ARRAY__;
|
|
22
|
+
|
|
23
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
24
|
+
|
|
25
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
26
|
+
const { name, arguments: args } = request.params;
|
|
27
|
+
try {
|
|
28
|
+
let result: unknown;
|
|
29
|
+
switch (name) {
|
|
30
|
+
// __TOOL_HANDLERS__
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
33
|
+
}
|
|
34
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const transport = new StdioServerTransport();
|
|
41
|
+
await server.connect(transport);
|
|
42
|
+
console.error(`[${PROJECT_SLUG}-mcp] connected. path=${PROJECT_PATH}`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
export async function getClaudeMd(docsPath: string) {
|
|
5
|
+
const path = join(docsPath, "CLAUDE.md");
|
|
6
|
+
try {
|
|
7
|
+
const content = await readFile(path, "utf-8");
|
|
8
|
+
return { file: path, length: content.length, content };
|
|
9
|
+
} catch {
|
|
10
|
+
return { file: path, content: null, note: "CLAUDE.md belum ada" };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
export async function getCurrentState(docsPath: string) {
|
|
5
|
+
const path = join(docsPath, "CURRENT_STATE.md");
|
|
6
|
+
try {
|
|
7
|
+
const content = await readFile(path, "utf-8");
|
|
8
|
+
const active = extractSection(content, "Task Aktif");
|
|
9
|
+
const completed = extractSection(content, "Terakhir Selesai");
|
|
10
|
+
const blockers = extractSection(content, "Blockers");
|
|
11
|
+
return { file: path, active_task: active, recent_completed: completed?.slice(0, 2000) ?? null, blockers };
|
|
12
|
+
} catch {
|
|
13
|
+
return { file: path, active_task: null, recent_completed: null, blockers: null, note: "CURRENT_STATE.md belum ada" };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function extractSection(content: string, heading: string): string | null {
|
|
18
|
+
const regex = new RegExp(`##\\s+${heading}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`);
|
|
19
|
+
const match = content.match(regex);
|
|
20
|
+
return match ? match[1].trim() : null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
export async function getMistakes(docsPath: string, topic?: string) {
|
|
5
|
+
const path = join(docsPath, "MISTAKES.md");
|
|
6
|
+
try {
|
|
7
|
+
const content = await readFile(path, "utf-8");
|
|
8
|
+
if (!topic) {
|
|
9
|
+
return { file: path, length: content.length, preview: content.slice(0, 3000), truncated: content.length > 3000 };
|
|
10
|
+
}
|
|
11
|
+
const sections = content.split(/(?=^##\s)/m);
|
|
12
|
+
const lower = topic.toLowerCase();
|
|
13
|
+
const matches = sections.filter((s) => s.toLowerCase().includes(lower));
|
|
14
|
+
return { file: path, topic, match_count: matches.length, matches: matches.map((s) => s.slice(0, 1200)) };
|
|
15
|
+
} catch {
|
|
16
|
+
return { file: path, content: null, note: "MISTAKES.md belum ada" };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
|
|
4
|
+
interface LogTaskArgs {
|
|
5
|
+
task_id: string;
|
|
6
|
+
action: string;
|
|
7
|
+
files: string[];
|
|
8
|
+
result: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function logTask(docsPath: string, args: LogTaskArgs) {
|
|
12
|
+
const path = join(docsPath, "AUDIT_LOG.md");
|
|
13
|
+
await mkdir(dirname(path), { recursive: true });
|
|
14
|
+
|
|
15
|
+
let content: string;
|
|
16
|
+
try {
|
|
17
|
+
content = await readFile(path, "utf-8");
|
|
18
|
+
} catch {
|
|
19
|
+
content = "# Audit Log\n\n> Auto-updated oleh agent setelah task selesai.\n\n| Date | Action | Files Touched | Result |\n|---|---|---|---|\n";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
23
|
+
const entry = `| ${date} | ${args.task_id} ${args.action} | ${args.files.join(", ")} | ${args.result} |`;
|
|
24
|
+
content = content.trimEnd() + "\n" + entry + "\n";
|
|
25
|
+
await writeFile(path, content, "utf-8");
|
|
26
|
+
return { file: path, entry, message: `Logged: ${args.task_id} — ${args.action}` };
|
|
27
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
2
|
+
import { join, relative } from "path";
|
|
3
|
+
|
|
4
|
+
interface Caller { file: string; line: number; snippet: string; pattern: string; }
|
|
5
|
+
interface ImpactResult {
|
|
6
|
+
symbol: string;
|
|
7
|
+
callers: Caller[];
|
|
8
|
+
summary: { total_callers: number; files_affected: number; severity: "low" | "medium" | "high" };
|
|
9
|
+
truncated: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const IGNORED_DIRS = new Set(["vendor", "node_modules", ".git", "storage", "bootstrap", "public", "tests", "dist", "build", ".next", "__pycache__", "venv"]);
|
|
13
|
+
const MAX_CALLERS = 200;
|
|
14
|
+
|
|
15
|
+
export async function predictImpact(
|
|
16
|
+
projectPath: string,
|
|
17
|
+
scanRoots: string[],
|
|
18
|
+
allowedExts: string[],
|
|
19
|
+
symbol: string
|
|
20
|
+
): Promise<ImpactResult> {
|
|
21
|
+
if (!symbol) throw new Error("symbol is required");
|
|
22
|
+
const callers: Caller[] = [];
|
|
23
|
+
let truncated = false;
|
|
24
|
+
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25
|
+
const regex = new RegExp(`\\b${escaped}\\b`);
|
|
26
|
+
|
|
27
|
+
for (const root of scanRoots) {
|
|
28
|
+
const full = join(projectPath, root);
|
|
29
|
+
try { await stat(full); } catch { continue; }
|
|
30
|
+
await scanDir(full, projectPath, regex, symbol, allowedExts, callers);
|
|
31
|
+
if (callers.length >= MAX_CALLERS) { truncated = true; break; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const fileSet = new Set(callers.map((c) => c.file));
|
|
35
|
+
return {
|
|
36
|
+
symbol,
|
|
37
|
+
callers: callers.slice(0, MAX_CALLERS),
|
|
38
|
+
summary: {
|
|
39
|
+
total_callers: callers.length,
|
|
40
|
+
files_affected: fileSet.size,
|
|
41
|
+
severity: callers.length >= 20 || fileSet.size >= 10 ? "high" : callers.length >= 5 || fileSet.size >= 3 ? "medium" : "low",
|
|
42
|
+
},
|
|
43
|
+
truncated,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function scanDir(dir: string, root: string, regex: RegExp, symbol: string, exts: string[], out: Caller[]): Promise<void> {
|
|
48
|
+
if (out.length >= MAX_CALLERS) return;
|
|
49
|
+
let entries;
|
|
50
|
+
try { entries = await readdir(dir, { withFileTypes: true }); } catch { return; }
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
|
|
53
|
+
const full = join(dir, entry.name);
|
|
54
|
+
if (entry.isDirectory()) { await scanDir(full, root, regex, symbol, exts, out); }
|
|
55
|
+
else if (entry.isFile() && exts.some((e) => entry.name.endsWith(e))) {
|
|
56
|
+
let content: string;
|
|
57
|
+
try { content = await readFile(full, "utf-8"); } catch { continue; }
|
|
58
|
+
const lines = content.split("\n");
|
|
59
|
+
for (let i = 0; i < lines.length; i++) {
|
|
60
|
+
if (regex.test(lines[i])) {
|
|
61
|
+
out.push({ file: relative(root, full).replace(/\\/g, "/"), line: i + 1, snippet: lines[i].trim().slice(0, 200), pattern: "reference" });
|
|
62
|
+
if (out.length >= MAX_CALLERS) return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|