@lovelybunch/api 1.0.73 → 1.0.74
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/dist/routes/api/v1/ai/route.js +79 -18
- package/dist/routes/api/v1/ai/tools.js +76 -16
- package/dist/routes/api/v1/mcp/index.js +129 -573
- package/dist/routes/api/v1/proposals/[id]/route.d.ts +12 -0
- package/dist/routes/api/v1/proposals/route.d.ts +12 -0
- package/package.json +6 -5
- package/static/assets/ActivityPage-AWTVFLmK.js +1 -0
- package/static/assets/{AgentDetailPage-WLAnnZep.js → AgentDetailPage-DtGFrHzZ.js} +1 -1
- package/static/assets/{AgentEditPage-DOemUkvg.js → AgentEditPage-1026aJox.js} +1 -1
- package/static/assets/{AgentsPage-Bage8eYW.js → AgentsPage-9Hre8AD1.js} +2 -2
- package/static/assets/{AgentsSettingsPage-Cw2MTnHU.js → AgentsSettingsPage-CILCHaO9.js} +1 -1
- package/static/assets/{ApiKeysSettingsPage-DCKd4LXE.js → ApiKeysSettingsPage-D-l8q30N.js} +1 -1
- package/static/assets/{ArchitectureEditPage-WY9k_1tR.js → ArchitectureEditPage-DiRR28Rx.js} +1 -1
- package/static/assets/{ArchitecturePage-Cj4dVDWO.js → ArchitecturePage-FcRJGw8O.js} +1 -1
- package/static/assets/{AuthSettingsPage-Bs5wL5Yj.js → AuthSettingsPage-D528tGhc.js} +1 -1
- package/static/assets/{CallbackPage-CtydJ4j3.js → CallbackPage-BMZOjCy_.js} +1 -1
- package/static/assets/CodePage-CCNmmkv_.js +2 -0
- package/static/assets/{CollapsibleSection-5qSoX47l.js → CollapsibleSection-CG5gAVWB.js} +1 -1
- package/static/assets/DashboardPage-D5C34QbO.js +41 -0
- package/static/assets/{GitPage-BG-ZSGfu.js → GitPage-ofZrdSSl.js} +1 -1
- package/static/assets/{GitSettingsPage-Cn-MciXq.js → GitSettingsPage-Cld_sN5t.js} +1 -1
- package/static/assets/{IdentityPage-DCpoDF2j.js → IdentityPage-Dj-Do8q7.js} +1 -1
- package/static/assets/{ImplementationStepsEditor-PM47REBn.js → ImplementationStepsEditor-BZQKPJ1C.js} +1 -1
- package/static/assets/{IntegrationsSettingsPage-DJpf2gZn.js → IntegrationsSettingsPage-DTT6nX3v.js} +1 -1
- package/static/assets/JobDetailPage-CWexORzH.js +1 -0
- package/static/assets/{KnowledgeDetailPage-CLizVIxC.js → KnowledgeDetailPage-C-a0njhy.js} +1 -1
- package/static/assets/{KnowledgeEditPage-B-sdY_DS.js → KnowledgeEditPage-B93ffsgz.js} +1 -1
- package/static/assets/{KnowledgePage-DIlUufGq.js → KnowledgePage-DS7xi1qJ.js} +1 -1
- package/static/assets/{LoginPage-DcTU__Dc.js → LoginPage-FUflG9B8.js} +1 -1
- package/static/assets/{McpSettingsPage-BPDLne4q.js → McpSettingsPage-A6uws8gQ.js} +1 -1
- package/static/assets/{NewAgentPage-8-abvhkI.js → NewAgentPage-CqBlVqdy.js} +1 -1
- package/static/assets/{NewKnowledgePage-Rvj741x1.js → NewKnowledgePage-ue0ZRgKf.js} +1 -1
- package/static/assets/{NewProposalPage-BLIqmuks.js → NewProposalPage-BDbStKts.js} +1 -1
- package/static/assets/{ProjectEditPage-DC5-v_2a.js → ProjectEditPage-_a9hJTgi.js} +1 -1
- package/static/assets/{ProjectPage-_L4wqwrB.js → ProjectPage-DAlEu9Af.js} +1 -1
- package/static/assets/{PromptsSettingsPage-K-qqpF2S.js → PromptsSettingsPage-4_C5zvq6.js} +1 -1
- package/static/assets/{ProposalDetailPage-BVXTjXw2.js → ProposalDetailPage-mZ9qj0wJ.js} +1 -1
- package/static/assets/{ProposalEditPage-Bs3ak0PG.js → ProposalEditPage-DqJ3Po23.js} +1 -1
- package/static/assets/{ProposalsPage-BvKlKvuo.js → ProposalsPage-DaR_KuZx.js} +1 -1
- package/static/assets/{ResourcesPage-BSyX_kHV.js → ResourcesPage-WMPlFn0S.js} +1 -1
- package/static/assets/{RoleEditPage-CnWierul.js → RoleEditPage-DIoamJfW.js} +1 -1
- package/static/assets/{RolePage-D8xB0I-F.js → RolePage-4aL0vR7F.js} +1 -1
- package/static/assets/{RulesSettingsPage-CZBQ0u-x.js → RulesSettingsPage-CerRq_kE.js} +1 -1
- package/static/assets/SchedulePage-DF9TqOA6.js +4 -0
- package/static/assets/{SourceInput-BoRGYtye.js → SourceInput-CXv23vkE.js} +1 -1
- package/static/assets/{TagInput-CdXzv6hj.js → TagInput-DL_48L0p.js} +1 -1
- package/static/assets/{TerminalPage-CqPXFOIN.js → TerminalPage-BL8pa_9k.js} +1 -1
- package/static/assets/{TerminalSessionPage-DR2cApWv.js → TerminalSessionPage-DcC_o_sA.js} +1 -1
- package/static/assets/{UserPreferencesPage-CPkulDiM.js → UserPreferencesPage-DoMyvwD4.js} +1 -1
- package/static/assets/{UserSettingsPage-BuMnU394.js → UserSettingsPage-CqibuEiq.js} +1 -1
- package/static/assets/{UtilitiesPage-BGiBB5PT.js → UtilitiesPage-6BGeolw9.js} +1 -1
- package/static/assets/{alert-CHpdnk6F.js → alert-DQ9shGSz.js} +1 -1
- package/static/assets/{arrow-down-BjGtxyJ7.js → arrow-down-D5oSUxtq.js} +1 -1
- package/static/assets/{arrow-left-DR0foZvi.js → arrow-left-BJ9vzpff.js} +1 -1
- package/static/assets/{arrow-up-DCGMV1HG.js → arrow-up-DWNinaN_.js} +1 -1
- package/static/assets/{badge-Cds9UgAB.js → badge-BLbqPEov.js} +1 -1
- package/static/assets/{browser-modal-Ck4-s_jh.js → browser-modal-Dh2dy_2x.js} +1 -1
- package/static/assets/{calendar-CWhxfYG2.js → calendar-CaBty4QE.js} +1 -1
- package/static/assets/{card-BdOrf6VU.js → card-BTg0Fecj.js} +1 -1
- package/static/assets/{chevron-left-Wnwkk8g7.js → chevron-left-V0REBjky.js} +1 -1
- package/static/assets/{chevrons-up-DbnuGl5g.js → chevrons-up-BvR9KFeO.js} +1 -1
- package/static/assets/{circle-alert-BPvQggmh.js → circle-alert-DR1DiLh5.js} +1 -1
- package/static/assets/{circle-check-CTlDV9K4.js → circle-check-DED-Ne-m.js} +1 -1
- package/static/assets/{circle-check-big-KMhw8TDM.js → circle-check-big-CFsUUo2_.js} +1 -1
- package/static/assets/{circle-play-tZLgOcAc.js → circle-play-DQ32zg60.js} +1 -1
- package/static/assets/{circle-x-D3tazxxl.js → circle-x-CDTWutj4.js} +1 -1
- package/static/assets/{clipboard-C86rsVqL.js → clipboard-CTFAlaYo.js} +1 -1
- package/static/assets/{clock-Qm5u6CAm.js → clock-pVWUoJDo.js} +1 -1
- package/static/assets/{download-Bo3vdlNo.js → download-Bt7lWWYL.js} +1 -1
- package/static/assets/{eye-Cr9Hfzxo.js → eye-o7gPa7E6.js} +1 -1
- package/static/assets/{folder-git-2-Em4ZglGs.js → folder-git-2-CHwnYB8a.js} +1 -1
- package/static/assets/{index-CQpPrvm_.js → index-DaqYJNAM.js} +30 -30
- package/static/assets/{label-nG86oxuW.js → label-BtnLfquf.js} +1 -1
- package/static/assets/{markdown-editor-CEQMlLWe.js → markdown-editor-Cmsc5nNu.js} +1 -1
- package/static/assets/{pause-DuR7ql7H.js → pause-EIDnvByV.js} +1 -1
- package/static/assets/{play-p4m8WHP3.js → play-D_fLXyLR.js} +1 -1
- package/static/assets/{plus-CD075YlZ.js → plus-ZCd709aN.js} +1 -1
- package/static/assets/{radio-group-DCKkxIgI.js → radio-group-STvlCDl-.js} +1 -1
- package/static/assets/{refresh-cw-D-IgYQ7y.js → refresh-cw-BF0hXtxu.js} +1 -1
- package/static/assets/{search-W3H-E0eW.js → search-kmqzieAc.js} +1 -1
- package/static/assets/{switch-E0GrYmgh.js → switch-CjDKvzd0.js} +1 -1
- package/static/assets/{tabs-DjZiD9WD.js → tabs-DPohYGac.js} +1 -1
- package/static/assets/{tag-D0iVh1-U.js → tag-Dg9notds.js} +1 -1
- package/static/assets/{terminal-preview-BQBOEop2.js → terminal-preview-CnytxbzD.js} +1 -1
- package/static/assets/{use-terminal-0TezQnxO.js → use-terminal-LpFJK0k7.js} +1 -1
- package/static/assets/{zap-C_5I7lLi.js → zap-DBKgrFQ_.js} +1 -1
- package/static/index.html +1 -1
- package/static/assets/ActivityPage-C_HqpJt2.js +0 -1
- package/static/assets/CodePage-BiRf5q_q.js +0 -2
- package/static/assets/DashboardPage-e9hNRsi2.js +0 -41
- package/static/assets/JobDetailPage-9shaUPlO.js +0 -1
- package/static/assets/SchedulePage-HBFJT_19.js +0 -4
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { promises as fs
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { getLogsDir } from '@lovelybunch/core';
|
|
8
|
-
import { proposalsReadOnlyTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool } from '@lovelybunch/mcp';
|
|
4
|
+
import { ZodError } from 'zod';
|
|
5
|
+
import { listProposals, getProposal, createProposal, updateProposal, deleteProposal, getContext, updateContext, appendContext, replaceContextSection, listKnowledge, getKnowledge, createKnowledge, updateKnowledge, listEvents, } from '@lovelybunch/core';
|
|
6
|
+
import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool } from '@lovelybunch/mcp';
|
|
9
7
|
import { FileStorageAdapter } from '../../../../lib/storage/file-storage.js';
|
|
10
8
|
const app = new Hono();
|
|
11
9
|
const storage = new FileStorageAdapter();
|
|
@@ -80,9 +78,9 @@ app.get('/', async (c) => {
|
|
|
80
78
|
}
|
|
81
79
|
const names = Object.keys(externalServers);
|
|
82
80
|
// Add built-in tools (include full JSON schema for parameters)
|
|
83
|
-
// Note: proposals
|
|
81
|
+
// Note: proposals now supports full CRUD, knowledge/project/architecture are read+write, events is read-only
|
|
84
82
|
const builtInTools = {
|
|
85
|
-
change_proposals:
|
|
83
|
+
change_proposals: proposalsFullTool,
|
|
86
84
|
knowledge_documents: knowledgeTool,
|
|
87
85
|
activity_events: eventsTool,
|
|
88
86
|
project_context: projectContextTool,
|
|
@@ -107,10 +105,10 @@ app.get('/', async (c) => {
|
|
|
107
105
|
*/
|
|
108
106
|
app.get('/schema', async (c) => {
|
|
109
107
|
try {
|
|
110
|
-
// Note: proposals
|
|
108
|
+
// Note: proposals now supports full CRUD, knowledge/project/architecture are read+write, events is read-only
|
|
111
109
|
const schema = {
|
|
112
110
|
tools: {
|
|
113
|
-
change_proposals:
|
|
111
|
+
change_proposals: proposalsFullTool,
|
|
114
112
|
knowledge_documents: knowledgeTool,
|
|
115
113
|
activity_events: eventsTool,
|
|
116
114
|
project_context: projectContextTool,
|
|
@@ -160,16 +158,17 @@ app.post('/execute', async (c) => {
|
|
|
160
158
|
return c.json({ success: false, error: 'Tool execution failed' }, 500);
|
|
161
159
|
}
|
|
162
160
|
});
|
|
163
|
-
// Proposals tool
|
|
161
|
+
// Proposals tool - full CRUD operations using @lovelybunch/core
|
|
164
162
|
async function executeProposalsTool(c, args) {
|
|
165
|
-
const { operation, id, filters } = args;
|
|
163
|
+
const { operation, id, filters, proposal, updates } = args;
|
|
166
164
|
try {
|
|
167
165
|
switch (operation) {
|
|
168
166
|
case 'list': {
|
|
169
|
-
const proposals = await
|
|
167
|
+
const proposals = await listProposals(filters || {});
|
|
170
168
|
return c.json({
|
|
171
169
|
success: true,
|
|
172
170
|
data: proposals,
|
|
171
|
+
count: proposals.length,
|
|
173
172
|
message: `Found ${proposals.length} proposals`
|
|
174
173
|
});
|
|
175
174
|
}
|
|
@@ -177,37 +176,86 @@ async function executeProposalsTool(c, args) {
|
|
|
177
176
|
if (!id) {
|
|
178
177
|
return c.json({ success: false, error: 'Proposal ID is required for get operation' }, 400);
|
|
179
178
|
}
|
|
180
|
-
const
|
|
181
|
-
if (!
|
|
179
|
+
const result = await getProposal(id);
|
|
180
|
+
if (!result) {
|
|
182
181
|
return c.json({ success: false, error: 'Proposal not found' }, 404);
|
|
183
182
|
}
|
|
184
183
|
return c.json({
|
|
185
184
|
success: true,
|
|
186
|
-
data:
|
|
185
|
+
data: result,
|
|
187
186
|
message: `Retrieved proposal ${id}`
|
|
188
187
|
});
|
|
189
188
|
}
|
|
189
|
+
case 'create': {
|
|
190
|
+
if (!proposal) {
|
|
191
|
+
return c.json({ success: false, error: 'Proposal data is required for create operation' }, 400);
|
|
192
|
+
}
|
|
193
|
+
const created = await createProposal(proposal);
|
|
194
|
+
return c.json({
|
|
195
|
+
success: true,
|
|
196
|
+
data: created,
|
|
197
|
+
message: `Created proposal ${created.id}`
|
|
198
|
+
}, 201);
|
|
199
|
+
}
|
|
200
|
+
case 'update': {
|
|
201
|
+
if (!id) {
|
|
202
|
+
return c.json({ success: false, error: 'Proposal ID is required for update operation' }, 400);
|
|
203
|
+
}
|
|
204
|
+
const updateData = updates || proposal;
|
|
205
|
+
if (!updateData) {
|
|
206
|
+
return c.json({ success: false, error: 'Update data is required for update operation' }, 400);
|
|
207
|
+
}
|
|
208
|
+
const updated = await updateProposal(id, updateData);
|
|
209
|
+
return c.json({
|
|
210
|
+
success: true,
|
|
211
|
+
data: updated,
|
|
212
|
+
message: `Updated proposal ${id}`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
case 'delete': {
|
|
216
|
+
if (!id) {
|
|
217
|
+
return c.json({ success: false, error: 'Proposal ID is required for delete operation' }, 400);
|
|
218
|
+
}
|
|
219
|
+
const deleted = await deleteProposal(id);
|
|
220
|
+
if (!deleted) {
|
|
221
|
+
return c.json({ success: false, error: 'Proposal not found' }, 404);
|
|
222
|
+
}
|
|
223
|
+
return c.json({
|
|
224
|
+
success: true,
|
|
225
|
+
message: `Deleted proposal ${id}`
|
|
226
|
+
});
|
|
227
|
+
}
|
|
190
228
|
default:
|
|
191
229
|
return c.json({
|
|
192
230
|
success: false,
|
|
193
|
-
error: `
|
|
231
|
+
error: `Unknown operation: ${operation}. Supported operations: list, get, create, update, delete`
|
|
194
232
|
}, 400);
|
|
195
233
|
}
|
|
196
234
|
}
|
|
197
235
|
catch (error) {
|
|
236
|
+
// Handle Zod validation errors specially
|
|
237
|
+
if (error instanceof ZodError) {
|
|
238
|
+
return c.json({
|
|
239
|
+
success: false,
|
|
240
|
+
error: 'Validation failed',
|
|
241
|
+
details: error.issues.map(e => ({
|
|
242
|
+
path: e.path.join('.'),
|
|
243
|
+
message: e.message
|
|
244
|
+
}))
|
|
245
|
+
}, 400);
|
|
246
|
+
}
|
|
198
247
|
console.error('Error executing proposals tool:', error);
|
|
199
248
|
return c.json({ success: false, error: error.message || 'Tool execution failed' }, 500);
|
|
200
249
|
}
|
|
201
250
|
}
|
|
251
|
+
// Knowledge tool executor - uses core knowledge functions
|
|
202
252
|
async function executeKnowledgeTool(c, args) {
|
|
203
253
|
const { operation, filename, title, content, metadata, summary, query } = args;
|
|
204
254
|
try {
|
|
205
|
-
const knowledgePath = getKnowledgePath();
|
|
206
|
-
await fs.mkdir(knowledgePath, { recursive: true });
|
|
207
255
|
switch (operation) {
|
|
208
256
|
case 'list': {
|
|
209
257
|
const searchQuery = query && typeof query === 'string' ? query : undefined;
|
|
210
|
-
const documents = await
|
|
258
|
+
const documents = await listKnowledge({ query: searchQuery });
|
|
211
259
|
return c.json({
|
|
212
260
|
success: true,
|
|
213
261
|
data: documents,
|
|
@@ -220,7 +268,7 @@ async function executeKnowledgeTool(c, args) {
|
|
|
220
268
|
if (!filename) {
|
|
221
269
|
return c.json({ success: false, error: 'Filename is required for get operation' }, 400);
|
|
222
270
|
}
|
|
223
|
-
const document = await
|
|
271
|
+
const document = await getKnowledge(filename);
|
|
224
272
|
if (!document) {
|
|
225
273
|
return c.json({ success: false, error: 'Knowledge document not found' }, 404);
|
|
226
274
|
}
|
|
@@ -230,95 +278,48 @@ async function executeKnowledgeTool(c, args) {
|
|
|
230
278
|
if (!title || !content) {
|
|
231
279
|
return c.json({ success: false, error: 'Title and content are required for create operation' }, 400);
|
|
232
280
|
}
|
|
233
|
-
const targetFilename = resolveFilename(filename, title);
|
|
234
|
-
const filePath = path.join(knowledgePath, targetFilename);
|
|
235
281
|
try {
|
|
236
|
-
await
|
|
237
|
-
return c.json({
|
|
282
|
+
const created = await createKnowledge({ title, content, filename, summary, metadata });
|
|
283
|
+
return c.json({
|
|
284
|
+
success: true,
|
|
285
|
+
data: created,
|
|
286
|
+
message: `Created knowledge document ${created.filename}`
|
|
287
|
+
}, 201);
|
|
238
288
|
}
|
|
239
|
-
catch {
|
|
240
|
-
|
|
289
|
+
catch (err) {
|
|
290
|
+
if (err.message?.includes('already exists')) {
|
|
291
|
+
return c.json({ success: false, error: err.message }, 409);
|
|
292
|
+
}
|
|
293
|
+
throw err;
|
|
241
294
|
}
|
|
242
|
-
const normalizedMetadata = normalizeKnowledgeMetadata({
|
|
243
|
-
version: '1.0',
|
|
244
|
-
summary,
|
|
245
|
-
...metadata
|
|
246
|
-
});
|
|
247
|
-
const markdown = matter.stringify(content, normalizedMetadata);
|
|
248
|
-
await fs.writeFile(filePath, markdown, 'utf-8');
|
|
249
|
-
const created = {
|
|
250
|
-
filename: targetFilename,
|
|
251
|
-
title,
|
|
252
|
-
metadata: normalizedMetadata,
|
|
253
|
-
content
|
|
254
|
-
};
|
|
255
|
-
return c.json({
|
|
256
|
-
success: true,
|
|
257
|
-
data: created,
|
|
258
|
-
message: `Created knowledge document ${targetFilename}`
|
|
259
|
-
}, 201);
|
|
260
295
|
}
|
|
261
296
|
case 'update': {
|
|
262
297
|
if (!filename || !content) {
|
|
263
298
|
return c.json({ success: false, error: 'Filename and content are required for update operation' }, 400);
|
|
264
299
|
}
|
|
265
|
-
const currentFilename = resolveFilename(filename, title || 'knowledge-entry');
|
|
266
|
-
const currentPath = path.join(knowledgePath, currentFilename);
|
|
267
|
-
let existingDocument = null;
|
|
268
300
|
try {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
throw error;
|
|
276
|
-
}
|
|
277
|
-
if (!existingDocument) {
|
|
278
|
-
return c.json({ success: false, error: 'Knowledge document not found' }, 404);
|
|
301
|
+
const updated = await updateKnowledge({ filename, content, title, summary, metadata });
|
|
302
|
+
return c.json({
|
|
303
|
+
success: true,
|
|
304
|
+
data: updated,
|
|
305
|
+
message: `Updated knowledge document ${updated.filename}`
|
|
306
|
+
});
|
|
279
307
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const nextPath = path.join(knowledgePath, nextFilename);
|
|
284
|
-
if (nextFilename !== currentFilename) {
|
|
285
|
-
try {
|
|
286
|
-
await fs.access(nextPath);
|
|
287
|
-
return c.json({ success: false, error: 'A document with the requested title already exists' }, 409);
|
|
308
|
+
catch (err) {
|
|
309
|
+
if (err.message?.includes('not found')) {
|
|
310
|
+
return c.json({ success: false, error: err.message }, 404);
|
|
288
311
|
}
|
|
289
|
-
|
|
290
|
-
|
|
312
|
+
if (err.message?.includes('already exists')) {
|
|
313
|
+
return c.json({ success: false, error: err.message }, 409);
|
|
291
314
|
}
|
|
315
|
+
throw err;
|
|
292
316
|
}
|
|
293
|
-
const mergedMetadata = normalizeKnowledgeMetadata({
|
|
294
|
-
...existingDocument.metadata,
|
|
295
|
-
...metadata,
|
|
296
|
-
summary,
|
|
297
|
-
updated: new Date().toISOString().split('T')[0]
|
|
298
|
-
});
|
|
299
|
-
const markdown = matter.stringify(content, mergedMetadata);
|
|
300
|
-
await fs.writeFile(nextPath, markdown, 'utf-8');
|
|
301
|
-
if (nextFilename !== currentFilename) {
|
|
302
|
-
await fs.unlink(currentPath);
|
|
303
|
-
}
|
|
304
|
-
const updated = {
|
|
305
|
-
filename: nextFilename,
|
|
306
|
-
title: nextTitle,
|
|
307
|
-
metadata: mergedMetadata,
|
|
308
|
-
content
|
|
309
|
-
};
|
|
310
|
-
return c.json({
|
|
311
|
-
success: true,
|
|
312
|
-
data: updated,
|
|
313
|
-
message: `Updated knowledge document ${nextFilename}`
|
|
314
|
-
});
|
|
315
317
|
}
|
|
316
318
|
case 'preview_update': {
|
|
317
319
|
if (!filename) {
|
|
318
320
|
return c.json({ success: false, error: 'Filename is required for preview update operation' }, 400);
|
|
319
321
|
}
|
|
320
|
-
const
|
|
321
|
-
const existingDocument = await readKnowledgeDocument(knowledgePath, currentFilename);
|
|
322
|
+
const existingDocument = await getKnowledge(filename);
|
|
322
323
|
if (!existingDocument) {
|
|
323
324
|
return c.json({ success: false, error: 'Knowledge document not found' }, 404);
|
|
324
325
|
}
|
|
@@ -331,14 +332,14 @@ async function executeKnowledgeTool(c, args) {
|
|
|
331
332
|
return c.json({
|
|
332
333
|
success: true,
|
|
333
334
|
data: {
|
|
334
|
-
filename:
|
|
335
|
+
filename: existingDocument.filename,
|
|
335
336
|
title: proposedTitle,
|
|
336
337
|
metadata: previewMetadata,
|
|
337
338
|
originalContent: existingDocument.content,
|
|
338
339
|
proposedContent: content || existingDocument.content,
|
|
339
340
|
summary: summary || null
|
|
340
341
|
},
|
|
341
|
-
message: `Prepared preview for ${
|
|
342
|
+
message: `Prepared preview for ${existingDocument.filename}`
|
|
342
343
|
});
|
|
343
344
|
}
|
|
344
345
|
default:
|
|
@@ -350,7 +351,7 @@ async function executeKnowledgeTool(c, args) {
|
|
|
350
351
|
return c.json({ success: false, error: error.message || 'Knowledge tool execution failed' }, 500);
|
|
351
352
|
}
|
|
352
353
|
}
|
|
353
|
-
// Events tool is READ-ONLY -
|
|
354
|
+
// Events tool is READ-ONLY - uses core listEvents function
|
|
354
355
|
async function executeEventsTool(c, args) {
|
|
355
356
|
const { operation, limit = 20, kind } = args;
|
|
356
357
|
if (operation !== 'list') {
|
|
@@ -360,66 +361,13 @@ async function executeEventsTool(c, args) {
|
|
|
360
361
|
}, 400);
|
|
361
362
|
}
|
|
362
363
|
try {
|
|
363
|
-
const
|
|
364
|
-
const currentFile = path.join(eventsDir, 'events-current.jsonl');
|
|
365
|
-
// Check if file exists
|
|
366
|
-
try {
|
|
367
|
-
await fs.access(currentFile);
|
|
368
|
-
}
|
|
369
|
-
catch {
|
|
370
|
-
return c.json({
|
|
371
|
-
success: true,
|
|
372
|
-
data: [],
|
|
373
|
-
message: 'No events found'
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
// Read all events from file
|
|
377
|
-
const allEvents = [];
|
|
378
|
-
const fileStream = createReadStream(currentFile);
|
|
379
|
-
const rl = readline.createInterface({
|
|
380
|
-
input: fileStream,
|
|
381
|
-
crlfDelay: Infinity,
|
|
382
|
-
});
|
|
383
|
-
for await (const line of rl) {
|
|
384
|
-
if (!line.trim())
|
|
385
|
-
continue;
|
|
386
|
-
try {
|
|
387
|
-
const event = JSON.parse(line);
|
|
388
|
-
// Apply kind filter if specified
|
|
389
|
-
if (kind && typeof kind === 'string') {
|
|
390
|
-
if (!event.kind || !event.kind.startsWith(kind)) {
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
allEvents.push(event);
|
|
395
|
-
}
|
|
396
|
-
catch {
|
|
397
|
-
// Skip malformed lines
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
// Sort by seq descending (most recent first) and limit
|
|
402
|
-
const cappedLimit = Math.min(Math.max(1, limit), 100);
|
|
403
|
-
const sortedEvents = allEvents
|
|
404
|
-
.sort((a, b) => (b.seq || 0) - (a.seq || 0))
|
|
405
|
-
.slice(0, cappedLimit);
|
|
406
|
-
// Format events for readability
|
|
407
|
-
const formattedEvents = sortedEvents.map(event => ({
|
|
408
|
-
seq: event.seq,
|
|
409
|
-
kind: event.kind,
|
|
410
|
-
actor: event.actor,
|
|
411
|
-
subject: event.subject,
|
|
412
|
-
timestamp: event.ts,
|
|
413
|
-
level: event.level,
|
|
414
|
-
tags: event.tags,
|
|
415
|
-
summary: event.payload?.summary || event.payload?.message || event.payload?.title || undefined
|
|
416
|
-
}));
|
|
364
|
+
const events = await listEvents({ limit, kind });
|
|
417
365
|
return c.json({
|
|
418
366
|
success: true,
|
|
419
|
-
data:
|
|
367
|
+
data: events,
|
|
420
368
|
message: kind
|
|
421
|
-
? `Found ${
|
|
422
|
-
: `Found ${
|
|
369
|
+
? `Found ${events.length} recent '${kind}' events`
|
|
370
|
+
: `Found ${events.length} recent events`
|
|
423
371
|
});
|
|
424
372
|
}
|
|
425
373
|
catch (error) {
|
|
@@ -427,319 +375,59 @@ async function executeEventsTool(c, args) {
|
|
|
427
375
|
return c.json({ success: false, error: error.message || 'Events tool execution failed' }, 500);
|
|
428
376
|
}
|
|
429
377
|
}
|
|
430
|
-
//
|
|
431
|
-
async function
|
|
432
|
-
const { operation, content, old_text, new_text } = args;
|
|
433
|
-
try {
|
|
434
|
-
const contextPath = getContextPath();
|
|
435
|
-
const filePath = path.join(contextPath, 'project.md');
|
|
436
|
-
switch (operation) {
|
|
437
|
-
case 'get': {
|
|
438
|
-
try {
|
|
439
|
-
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
440
|
-
const parsed = matter(fileContent);
|
|
441
|
-
return c.json({
|
|
442
|
-
success: true,
|
|
443
|
-
data: {
|
|
444
|
-
content: parsed.content,
|
|
445
|
-
frontmatter: parsed.data,
|
|
446
|
-
raw: fileContent
|
|
447
|
-
},
|
|
448
|
-
message: 'Retrieved project context'
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
catch (err) {
|
|
452
|
-
if (err.code === 'ENOENT') {
|
|
453
|
-
return c.json({
|
|
454
|
-
success: true,
|
|
455
|
-
data: { content: '', frontmatter: {}, raw: '' },
|
|
456
|
-
message: 'Project context document does not exist yet. You can create it with an update operation.'
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
throw err;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
case 'append': {
|
|
463
|
-
if (!content) {
|
|
464
|
-
return c.json({ success: false, error: 'Content is required for append operation' }, 400);
|
|
465
|
-
}
|
|
466
|
-
await fs.mkdir(contextPath, { recursive: true });
|
|
467
|
-
// Read existing content if file exists
|
|
468
|
-
let existingContent = '';
|
|
469
|
-
try {
|
|
470
|
-
existingContent = await fs.readFile(filePath, 'utf-8');
|
|
471
|
-
}
|
|
472
|
-
catch (err) {
|
|
473
|
-
if (err.code !== 'ENOENT')
|
|
474
|
-
throw err;
|
|
475
|
-
}
|
|
476
|
-
// Append new content with a newline separator
|
|
477
|
-
const newContent = existingContent
|
|
478
|
-
? existingContent.trimEnd() + '\n\n' + content
|
|
479
|
-
: content;
|
|
480
|
-
await fs.writeFile(filePath, newContent, 'utf-8');
|
|
481
|
-
return c.json({
|
|
482
|
-
success: true,
|
|
483
|
-
message: 'Appended content to project context document'
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
case 'replace_section': {
|
|
487
|
-
if (!old_text || !new_text) {
|
|
488
|
-
return c.json({ success: false, error: 'Both old_text and new_text are required for replace_section operation' }, 400);
|
|
489
|
-
}
|
|
490
|
-
// Read existing content
|
|
491
|
-
let existingContent = '';
|
|
492
|
-
try {
|
|
493
|
-
existingContent = await fs.readFile(filePath, 'utf-8');
|
|
494
|
-
}
|
|
495
|
-
catch (err) {
|
|
496
|
-
if (err.code === 'ENOENT') {
|
|
497
|
-
return c.json({ success: false, error: 'Project context document does not exist. Use append or update to create it first.' }, 404);
|
|
498
|
-
}
|
|
499
|
-
throw err;
|
|
500
|
-
}
|
|
501
|
-
// Check if old_text exists in the document
|
|
502
|
-
if (!existingContent.includes(old_text)) {
|
|
503
|
-
return c.json({
|
|
504
|
-
success: false,
|
|
505
|
-
error: 'Could not find the specified text in the document. The text may have been paraphrased or changed.',
|
|
506
|
-
fallback_markdown: new_text,
|
|
507
|
-
suggestion: 'Here is the replacement text. You can copy it and manually edit the document at /context/project'
|
|
508
|
-
}, 400);
|
|
509
|
-
}
|
|
510
|
-
// Replace the text
|
|
511
|
-
const updatedContent = existingContent.replace(old_text, new_text);
|
|
512
|
-
await fs.writeFile(filePath, updatedContent, 'utf-8');
|
|
513
|
-
return c.json({
|
|
514
|
-
success: true,
|
|
515
|
-
message: 'Replaced section in project context document'
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
case 'update': {
|
|
519
|
-
if (!content) {
|
|
520
|
-
return c.json({ success: false, error: 'Content is required for update operation' }, 400);
|
|
521
|
-
}
|
|
522
|
-
await fs.mkdir(contextPath, { recursive: true });
|
|
523
|
-
await fs.writeFile(filePath, content, 'utf-8');
|
|
524
|
-
return c.json({
|
|
525
|
-
success: true,
|
|
526
|
-
message: 'Updated project context document'
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
default:
|
|
530
|
-
return c.json({ success: false, error: `Unknown operation: ${operation}. Use 'get', 'append', 'replace_section', or 'update'.` }, 400);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
catch (error) {
|
|
534
|
-
console.error('Error executing project context tool:', error);
|
|
535
|
-
return c.json({ success: false, error: error.message || 'Project context tool execution failed' }, 500);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// Architecture context tool - read/write the architecture document
|
|
539
|
-
async function executeArchitectureContextTool(c, args) {
|
|
378
|
+
// Generic context tool executor - uses core context functions
|
|
379
|
+
async function executeContextTool(c, contextType, args) {
|
|
540
380
|
const { operation, content, old_text, new_text } = args;
|
|
381
|
+
const typeName = contextType.charAt(0).toUpperCase() + contextType.slice(1);
|
|
541
382
|
try {
|
|
542
|
-
const contextPath = getContextPath();
|
|
543
|
-
const filePath = path.join(contextPath, 'architecture.md');
|
|
544
383
|
switch (operation) {
|
|
545
384
|
case 'get': {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
success: true,
|
|
551
|
-
data: {
|
|
552
|
-
content: parsed.content,
|
|
553
|
-
frontmatter: parsed.data,
|
|
554
|
-
raw: fileContent
|
|
555
|
-
},
|
|
556
|
-
message: 'Retrieved architecture context'
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
catch (err) {
|
|
560
|
-
if (err.code === 'ENOENT') {
|
|
561
|
-
return c.json({
|
|
562
|
-
success: true,
|
|
563
|
-
data: { content: '', frontmatter: {}, raw: '' },
|
|
564
|
-
message: 'Architecture context document does not exist yet. You can create it with an update operation.'
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
throw err;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
case 'append': {
|
|
571
|
-
if (!content) {
|
|
572
|
-
return c.json({ success: false, error: 'Content is required for append operation' }, 400);
|
|
573
|
-
}
|
|
574
|
-
await fs.mkdir(contextPath, { recursive: true });
|
|
575
|
-
// Read existing content if file exists
|
|
576
|
-
let existingContent = '';
|
|
577
|
-
try {
|
|
578
|
-
existingContent = await fs.readFile(filePath, 'utf-8');
|
|
579
|
-
}
|
|
580
|
-
catch (err) {
|
|
581
|
-
if (err.code !== 'ENOENT')
|
|
582
|
-
throw err;
|
|
583
|
-
}
|
|
584
|
-
// Append new content with a newline separator
|
|
585
|
-
const newContent = existingContent
|
|
586
|
-
? existingContent.trimEnd() + '\n\n' + content
|
|
587
|
-
: content;
|
|
588
|
-
await fs.writeFile(filePath, newContent, 'utf-8');
|
|
589
|
-
return c.json({
|
|
590
|
-
success: true,
|
|
591
|
-
message: 'Appended content to architecture context document'
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
case 'replace_section': {
|
|
595
|
-
if (!old_text || !new_text) {
|
|
596
|
-
return c.json({ success: false, error: 'Both old_text and new_text are required for replace_section operation' }, 400);
|
|
597
|
-
}
|
|
598
|
-
// Read existing content
|
|
599
|
-
let existingContent = '';
|
|
600
|
-
try {
|
|
601
|
-
existingContent = await fs.readFile(filePath, 'utf-8');
|
|
602
|
-
}
|
|
603
|
-
catch (err) {
|
|
604
|
-
if (err.code === 'ENOENT') {
|
|
605
|
-
return c.json({ success: false, error: 'Architecture context document does not exist. Use append or update to create it first.' }, 404);
|
|
606
|
-
}
|
|
607
|
-
throw err;
|
|
608
|
-
}
|
|
609
|
-
// Check if old_text exists in the document
|
|
610
|
-
if (!existingContent.includes(old_text)) {
|
|
611
|
-
return c.json({
|
|
612
|
-
success: false,
|
|
613
|
-
error: 'Could not find the specified text in the document. The text may have been paraphrased or changed.',
|
|
614
|
-
fallback_markdown: new_text,
|
|
615
|
-
suggestion: 'Here is the replacement text. You can copy it and manually edit the document at /context/architecture'
|
|
616
|
-
}, 400);
|
|
617
|
-
}
|
|
618
|
-
// Replace the text
|
|
619
|
-
const updatedContent = existingContent.replace(old_text, new_text);
|
|
620
|
-
await fs.writeFile(filePath, updatedContent, 'utf-8');
|
|
621
|
-
return c.json({
|
|
622
|
-
success: true,
|
|
623
|
-
message: 'Replaced section in architecture context document'
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
case 'update': {
|
|
627
|
-
if (!content) {
|
|
628
|
-
return c.json({ success: false, error: 'Content is required for update operation' }, 400);
|
|
629
|
-
}
|
|
630
|
-
await fs.mkdir(contextPath, { recursive: true });
|
|
631
|
-
await fs.writeFile(filePath, content, 'utf-8');
|
|
385
|
+
const doc = await getContext(contextType);
|
|
386
|
+
const message = doc.raw
|
|
387
|
+
? `Retrieved ${contextType} context`
|
|
388
|
+
: `${typeName} context document does not exist yet. You can create it with an update operation.`;
|
|
632
389
|
return c.json({
|
|
633
390
|
success: true,
|
|
634
|
-
|
|
391
|
+
data: doc,
|
|
392
|
+
message
|
|
635
393
|
});
|
|
636
394
|
}
|
|
637
|
-
default:
|
|
638
|
-
return c.json({ success: false, error: `Unknown operation: ${operation}. Use 'get', 'append', 'replace_section', or 'update'.` }, 400);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
catch (error) {
|
|
642
|
-
console.error('Error executing architecture context tool:', error);
|
|
643
|
-
return c.json({ success: false, error: error.message || 'Architecture context tool execution failed' }, 500);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// Role context tool - read/write the role definition document
|
|
647
|
-
async function executeRoleContextTool(c, args) {
|
|
648
|
-
const { operation, content, old_text, new_text } = args;
|
|
649
|
-
try {
|
|
650
|
-
const contextPath = getContextPath();
|
|
651
|
-
const filePath = path.join(contextPath, 'role.md');
|
|
652
|
-
switch (operation) {
|
|
653
|
-
case 'get': {
|
|
654
|
-
try {
|
|
655
|
-
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
656
|
-
const parsed = matter(fileContent);
|
|
657
|
-
return c.json({
|
|
658
|
-
success: true,
|
|
659
|
-
data: {
|
|
660
|
-
content: parsed.content,
|
|
661
|
-
frontmatter: parsed.data,
|
|
662
|
-
raw: fileContent
|
|
663
|
-
},
|
|
664
|
-
message: 'Retrieved role context'
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
catch (err) {
|
|
668
|
-
if (err.code === 'ENOENT') {
|
|
669
|
-
return c.json({
|
|
670
|
-
success: true,
|
|
671
|
-
data: { content: '', frontmatter: {}, raw: '' },
|
|
672
|
-
message: 'Role context document does not exist yet. You can create it with an update operation.'
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
throw err;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
395
|
case 'append': {
|
|
679
396
|
if (!content) {
|
|
680
397
|
return c.json({ success: false, error: 'Content is required for append operation' }, 400);
|
|
681
398
|
}
|
|
682
|
-
await
|
|
683
|
-
// Read existing content if file exists
|
|
684
|
-
let existingContent = '';
|
|
685
|
-
try {
|
|
686
|
-
existingContent = await fs.readFile(filePath, 'utf-8');
|
|
687
|
-
}
|
|
688
|
-
catch (err) {
|
|
689
|
-
if (err.code !== 'ENOENT')
|
|
690
|
-
throw err;
|
|
691
|
-
}
|
|
692
|
-
// Append new content with a newline separator
|
|
693
|
-
const newContent = existingContent
|
|
694
|
-
? existingContent.trimEnd() + '\n\n' + content
|
|
695
|
-
: content;
|
|
696
|
-
await fs.writeFile(filePath, newContent, 'utf-8');
|
|
399
|
+
await appendContext(contextType, content);
|
|
697
400
|
return c.json({
|
|
698
401
|
success: true,
|
|
699
|
-
message:
|
|
402
|
+
message: `Appended content to ${contextType} context document`
|
|
700
403
|
});
|
|
701
404
|
}
|
|
702
405
|
case 'replace_section': {
|
|
703
406
|
if (!old_text || !new_text) {
|
|
704
407
|
return c.json({ success: false, error: 'Both old_text and new_text are required for replace_section operation' }, 400);
|
|
705
408
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
try {
|
|
709
|
-
existingContent = await fs.readFile(filePath, 'utf-8');
|
|
710
|
-
}
|
|
711
|
-
catch (err) {
|
|
712
|
-
if (err.code === 'ENOENT') {
|
|
713
|
-
return c.json({ success: false, error: 'Role context document does not exist. Use append or update to create it first.' }, 404);
|
|
714
|
-
}
|
|
715
|
-
throw err;
|
|
716
|
-
}
|
|
717
|
-
// Check if old_text exists in the document
|
|
718
|
-
if (!existingContent.includes(old_text)) {
|
|
409
|
+
const result = await replaceContextSection(contextType, old_text, new_text);
|
|
410
|
+
if (!result.success) {
|
|
719
411
|
return c.json({
|
|
720
412
|
success: false,
|
|
721
|
-
error:
|
|
722
|
-
fallback_markdown:
|
|
723
|
-
suggestion:
|
|
724
|
-
}, 400);
|
|
413
|
+
error: result.error,
|
|
414
|
+
fallback_markdown: result.fallbackMarkdown,
|
|
415
|
+
suggestion: result.suggestion
|
|
416
|
+
}, result.error?.includes('does not exist') ? 404 : 400);
|
|
725
417
|
}
|
|
726
|
-
// Replace the text
|
|
727
|
-
const updatedContent = existingContent.replace(old_text, new_text);
|
|
728
|
-
await fs.writeFile(filePath, updatedContent, 'utf-8');
|
|
729
418
|
return c.json({
|
|
730
419
|
success: true,
|
|
731
|
-
message:
|
|
420
|
+
message: `Replaced section in ${contextType} context document`
|
|
732
421
|
});
|
|
733
422
|
}
|
|
734
423
|
case 'update': {
|
|
735
424
|
if (!content) {
|
|
736
425
|
return c.json({ success: false, error: 'Content is required for update operation' }, 400);
|
|
737
426
|
}
|
|
738
|
-
await
|
|
739
|
-
await fs.writeFile(filePath, content, 'utf-8');
|
|
427
|
+
await updateContext(contextType, content);
|
|
740
428
|
return c.json({
|
|
741
429
|
success: true,
|
|
742
|
-
message:
|
|
430
|
+
message: `Updated ${contextType} context document`
|
|
743
431
|
});
|
|
744
432
|
}
|
|
745
433
|
default:
|
|
@@ -747,151 +435,19 @@ async function executeRoleContextTool(c, args) {
|
|
|
747
435
|
}
|
|
748
436
|
}
|
|
749
437
|
catch (error) {
|
|
750
|
-
console.error(
|
|
751
|
-
return c.json({ success: false, error: error.message ||
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
function getContextPath() {
|
|
755
|
-
return path.join(resolveGaitPath(), 'context');
|
|
756
|
-
}
|
|
757
|
-
function getKnowledgePath() {
|
|
758
|
-
return path.join(resolveGaitPath(), 'context', 'knowledge');
|
|
759
|
-
}
|
|
760
|
-
function slugify(value) {
|
|
761
|
-
const slug = value
|
|
762
|
-
.toLowerCase()
|
|
763
|
-
.replace(/[^a-z0-9\s-]/g, '')
|
|
764
|
-
.replace(/\s+/g, '-')
|
|
765
|
-
.replace(/--+/g, '-')
|
|
766
|
-
.replace(/^-|-$/g, '');
|
|
767
|
-
return slug || 'knowledge-entry';
|
|
768
|
-
}
|
|
769
|
-
function resolveFilename(input, fallbackTitle) {
|
|
770
|
-
if (input && input.trim().length > 0) {
|
|
771
|
-
const trimmed = path.basename(input.trim());
|
|
772
|
-
if (trimmed.endsWith('.md')) {
|
|
773
|
-
return trimmed;
|
|
774
|
-
}
|
|
775
|
-
return `${slugify(trimmed)}.md`;
|
|
776
|
-
}
|
|
777
|
-
const fallback = fallbackTitle && fallbackTitle.trim().length > 0
|
|
778
|
-
? path.basename(fallbackTitle.trim())
|
|
779
|
-
: 'knowledge-entry';
|
|
780
|
-
return `${slugify(fallback)}.md`;
|
|
781
|
-
}
|
|
782
|
-
function extractTitle(content, fallback) {
|
|
783
|
-
const headingMatch = content.match(/^#\s+(.+)$/m);
|
|
784
|
-
if (headingMatch && headingMatch[1]) {
|
|
785
|
-
return headingMatch[1].trim();
|
|
438
|
+
console.error(`Error executing ${contextType} context tool:`, error);
|
|
439
|
+
return c.json({ success: false, error: error.message || `${typeName} context tool execution failed` }, 500);
|
|
786
440
|
}
|
|
787
|
-
const baseName = fallback.replace('.md', '').replace(/[_-]/g, ' ');
|
|
788
|
-
return baseName.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase());
|
|
789
441
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const [raw, stats] = await Promise.all([
|
|
794
|
-
fs.readFile(filePath, 'utf-8'),
|
|
795
|
-
fs.stat(filePath)
|
|
796
|
-
]);
|
|
797
|
-
const { data, content } = matter(raw);
|
|
798
|
-
const metadata = normalizeKnowledgeMetadata(data);
|
|
799
|
-
// Use file mtime for updated timestamp instead of frontmatter
|
|
800
|
-
metadata.updated = stats.mtime.toISOString();
|
|
801
|
-
const title = extractTitle(content, actualFilename);
|
|
802
|
-
return {
|
|
803
|
-
filename: actualFilename,
|
|
804
|
-
title,
|
|
805
|
-
metadata,
|
|
806
|
-
content
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
function extractSnippet(content, query, maxLength = 200) {
|
|
810
|
-
const lowerContent = content.toLowerCase();
|
|
811
|
-
const lowerQuery = query.toLowerCase();
|
|
812
|
-
const terms = lowerQuery.split(/\s+/);
|
|
813
|
-
// Find the first occurrence of any search term
|
|
814
|
-
let bestIndex = -1;
|
|
815
|
-
let matchedTerm = '';
|
|
816
|
-
for (const term of terms) {
|
|
817
|
-
const index = lowerContent.indexOf(term);
|
|
818
|
-
if (index !== -1 && (bestIndex === -1 || index < bestIndex)) {
|
|
819
|
-
bestIndex = index;
|
|
820
|
-
matchedTerm = term;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
if (bestIndex === -1) {
|
|
824
|
-
// No match found, return beginning of content
|
|
825
|
-
return content.slice(0, maxLength) + (content.length > maxLength ? '...' : '');
|
|
826
|
-
}
|
|
827
|
-
// Extract snippet around the match
|
|
828
|
-
const start = Math.max(0, bestIndex - 60);
|
|
829
|
-
const end = Math.min(content.length, bestIndex + matchedTerm.length + 140);
|
|
830
|
-
let snippet = content.slice(start, end);
|
|
831
|
-
// Add ellipsis if needed
|
|
832
|
-
if (start > 0)
|
|
833
|
-
snippet = '...' + snippet;
|
|
834
|
-
if (end < content.length)
|
|
835
|
-
snippet = snippet + '...';
|
|
836
|
-
return snippet;
|
|
442
|
+
// Wrapper functions for each context type (maintain API compatibility)
|
|
443
|
+
async function executeProjectContextTool(c, args) {
|
|
444
|
+
return executeContextTool(c, 'project', args);
|
|
837
445
|
}
|
|
838
|
-
function
|
|
839
|
-
|
|
840
|
-
const terms = lowerQuery.split(/\s+/);
|
|
841
|
-
const matched = [];
|
|
842
|
-
const checkField = (value, fieldName) => {
|
|
843
|
-
if (value && terms.some(term => value.toLowerCase().includes(term))) {
|
|
844
|
-
matched.push(fieldName);
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
checkField(doc.title, 'title');
|
|
848
|
-
checkField(doc.content, 'content');
|
|
849
|
-
checkField(doc.metadata?.category, 'category');
|
|
850
|
-
checkField(doc.metadata?.summary, 'summary');
|
|
851
|
-
if (doc.metadata?.tags?.some(tag => terms.some(term => tag.toLowerCase().includes(term)))) {
|
|
852
|
-
matched.push('tags');
|
|
853
|
-
}
|
|
854
|
-
return matched;
|
|
446
|
+
async function executeArchitectureContextTool(c, args) {
|
|
447
|
+
return executeContextTool(c, 'architecture', args);
|
|
855
448
|
}
|
|
856
|
-
async function
|
|
857
|
-
|
|
858
|
-
const markdownFiles = files.filter(file => file.endsWith('.md'));
|
|
859
|
-
const documents = await Promise.all(markdownFiles.map(async (file) => {
|
|
860
|
-
try {
|
|
861
|
-
return await readKnowledgeDocument(basePath, file);
|
|
862
|
-
}
|
|
863
|
-
catch (error) {
|
|
864
|
-
console.error(`Failed to read knowledge document ${file}:`, error);
|
|
865
|
-
return null;
|
|
866
|
-
}
|
|
867
|
-
}));
|
|
868
|
-
const validDocs = documents.filter((doc) => doc !== null);
|
|
869
|
-
// If no query, return all documents sorted by filename
|
|
870
|
-
if (!query?.trim()) {
|
|
871
|
-
return validDocs.sort((a, b) => a.filename.localeCompare(b.filename));
|
|
872
|
-
}
|
|
873
|
-
// Use Fuse.js for fuzzy search
|
|
874
|
-
const fuse = new Fuse(validDocs, {
|
|
875
|
-
keys: [
|
|
876
|
-
{ name: 'title', weight: 2 },
|
|
877
|
-
{ name: 'content', weight: 1 },
|
|
878
|
-
{ name: 'metadata.summary', weight: 1.5 },
|
|
879
|
-
{ name: 'metadata.category', weight: 1 },
|
|
880
|
-
{ name: 'metadata.tags', weight: 1.5 }
|
|
881
|
-
],
|
|
882
|
-
threshold: 0.4,
|
|
883
|
-
includeScore: true,
|
|
884
|
-
ignoreLocation: true,
|
|
885
|
-
minMatchCharLength: 2
|
|
886
|
-
});
|
|
887
|
-
const results = fuse.search(query);
|
|
888
|
-
// Enhance results with snippets and matched fields
|
|
889
|
-
return results.map(result => ({
|
|
890
|
-
...result.item,
|
|
891
|
-
snippet: extractSnippet(result.item.content, query),
|
|
892
|
-
matchedIn: findMatchedFields(result.item, query),
|
|
893
|
-
score: result.score
|
|
894
|
-
}));
|
|
449
|
+
async function executeRoleContextTool(c, args) {
|
|
450
|
+
return executeContextTool(c, 'role', args);
|
|
895
451
|
}
|
|
896
452
|
/**
|
|
897
453
|
* GET /api/v1/mcp/raw-config
|