@prajwolkc/stk 0.6.1 → 0.7.1
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/commands/brain.js +36 -3
- package/dist/mcp/server.js +14 -1553
- package/dist/mcp/tools/brain.d.ts +2 -0
- package/dist/mcp/tools/brain.js +557 -0
- package/dist/mcp/tools/data.d.ts +2 -0
- package/dist/mcp/tools/data.js +385 -0
- package/dist/mcp/tools/github.d.ts +2 -0
- package/dist/mcp/tools/github.js +95 -0
- package/dist/mcp/tools/infra.d.ts +2 -0
- package/dist/mcp/tools/infra.js +263 -0
- package/dist/mcp/tools/ops.d.ts +2 -0
- package/dist/mcp/tools/ops.js +411 -0
- package/dist/mcp/tools/security.d.ts +2 -0
- package/dist/mcp/tools/security.js +25 -0
- package/dist/mcp/types.d.ts +2 -0
- package/dist/mcp/types.js +1 -0
- package/dist/services/brain-cloud.d.ts +13 -0
- package/dist/services/brain-cloud.js +131 -0
- package/dist/services/brain-extract.d.ts +14 -0
- package/dist/services/brain-extract.js +253 -0
- package/dist/services/brain-search.d.ts +33 -0
- package/dist/services/brain-search.js +153 -0
- package/dist/services/brain-store.d.ts +25 -0
- package/dist/services/brain-store.js +42 -0
- package/dist/services/brain.d.ts +19 -68
- package/dist/services/brain.js +18 -542
- package/dist/services/metrics.d.ts +35 -0
- package/dist/services/metrics.js +78 -0
- package/dist/services/security.d.ts +19 -0
- package/dist/services/security.js +194 -0
- package/package.json +2 -1
- package/src/data/seed-patterns.json +1802 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { loadConfig, enabledServices } from "../../lib/config.js";
|
|
3
|
+
import { getLocalBrainClient, ingestProject, loadBrainStore, saveBrainStore, syncBrain, pushToCloud, pullFromCloud, brainCheck, brainDiagnose, reviewDiff } from "../../services/brain.js";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
function getBrainClient() {
|
|
6
|
+
return getLocalBrainClient();
|
|
7
|
+
}
|
|
8
|
+
export function registerBrainTools(server) {
|
|
9
|
+
// ──────────────────────────────────────────
|
|
10
|
+
// Tool: stk_brain_search
|
|
11
|
+
// ──────────────────────────────────────────
|
|
12
|
+
server.tool("stk_brain_search", "Search the knowledge base for SaaS patterns, best practices, and architecture examples from top open-source projects (LangChain, Ollama, Transformers, llama.cpp, vLLM, AutoGen, OpenAI Cookbook). Use this when you need to know how successful projects solve specific problems.", {
|
|
13
|
+
query: z.string().describe("What to search for (e.g., 'authentication', 'real-time updates', 'payment integration')"),
|
|
14
|
+
category: z.string().optional().describe("Filter: architecture, auth, payments, database, api, deployment, testing, performance, security, ml, realtime, general"),
|
|
15
|
+
}, async ({ query, category }) => {
|
|
16
|
+
const brain = getBrainClient();
|
|
17
|
+
// Try ilike search on content and title
|
|
18
|
+
const words = query.split(" ").filter(w => w.length > 2);
|
|
19
|
+
const searchWord = words[0] ?? query;
|
|
20
|
+
const params = {
|
|
21
|
+
or: `(title.ilike.%${searchWord}%,content.ilike.%${searchWord}%)`,
|
|
22
|
+
limit: "10",
|
|
23
|
+
order: "source",
|
|
24
|
+
};
|
|
25
|
+
if (category)
|
|
26
|
+
params["category"] = `eq.${category}`;
|
|
27
|
+
const { data, ok } = await brain.query("knowledge", params);
|
|
28
|
+
if (!ok)
|
|
29
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Query failed", data }) }] };
|
|
30
|
+
return {
|
|
31
|
+
content: [{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: JSON.stringify({
|
|
34
|
+
query,
|
|
35
|
+
results: (data ?? []).map((r) => ({ source: r.source, category: r.category, title: r.title, content: r.content, tags: r.tags })),
|
|
36
|
+
total: data?.length ?? 0,
|
|
37
|
+
}, null, 2),
|
|
38
|
+
}],
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
// ──────────────────────────────────────────
|
|
42
|
+
// Tool: stk_brain_patterns
|
|
43
|
+
// ──────────────────────────────────────────
|
|
44
|
+
server.tool("stk_brain_patterns", "Get best practice patterns for a specific feature. Returns how top projects implement auth, payments, real-time, caching, APIs, etc.", {
|
|
45
|
+
feature: z.string().describe("The feature or pattern (e.g., 'authentication', 'webhooks', 'caching', 'model serving', 'fine-tuning')"),
|
|
46
|
+
}, async ({ feature }) => {
|
|
47
|
+
const brain = getBrainClient();
|
|
48
|
+
const { data, ok } = await brain.query("knowledge", {
|
|
49
|
+
or: `(title.ilike.%${feature}%,content.ilike.%${feature}%)`,
|
|
50
|
+
limit: "15",
|
|
51
|
+
order: "source",
|
|
52
|
+
});
|
|
53
|
+
if (!ok)
|
|
54
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Query failed" }) }] };
|
|
55
|
+
// Group by source
|
|
56
|
+
const grouped = {};
|
|
57
|
+
for (const item of data ?? []) {
|
|
58
|
+
if (!grouped[item.source])
|
|
59
|
+
grouped[item.source] = [];
|
|
60
|
+
grouped[item.source].push({ title: item.title, category: item.category, content: item.content });
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: JSON.stringify({
|
|
66
|
+
feature,
|
|
67
|
+
patterns: grouped,
|
|
68
|
+
totalSources: Object.keys(grouped).length,
|
|
69
|
+
totalPatterns: data?.length ?? 0,
|
|
70
|
+
}, null, 2),
|
|
71
|
+
}],
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
// ──────────────────────────────────────────
|
|
75
|
+
// Tool: stk_brain_stack
|
|
76
|
+
// ──────────────────────────────────────────
|
|
77
|
+
server.tool("stk_brain_stack", "Get recommendations specific to YOUR stack (Supabase + Vercel + Stripe + Node.js). Filters knowledge for patterns matching your technology choices.", {
|
|
78
|
+
question: z.string().describe("What you want to build or solve (e.g., 'add user auth', 'implement webhooks', 'optimize queries')"),
|
|
79
|
+
}, async ({ question }) => {
|
|
80
|
+
const brain = getBrainClient();
|
|
81
|
+
const words = question.split(" ").filter(w => w.length > 3);
|
|
82
|
+
const searchWord = words[0] ?? question;
|
|
83
|
+
const { data, ok } = await brain.query("knowledge", {
|
|
84
|
+
or: `(content.ilike.%${searchWord}%,title.ilike.%${searchWord}%)`,
|
|
85
|
+
limit: "10",
|
|
86
|
+
});
|
|
87
|
+
if (!ok)
|
|
88
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Query failed" }) }] };
|
|
89
|
+
return {
|
|
90
|
+
content: [{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: JSON.stringify({
|
|
93
|
+
question,
|
|
94
|
+
stack: ["Supabase", "Vercel", "Stripe", "Node.js/Express"],
|
|
95
|
+
recommendations: (data ?? []).map((r) => ({ source: r.source, title: r.title, content: r.content, relevance: r.category })),
|
|
96
|
+
total: data?.length ?? 0,
|
|
97
|
+
}, null, 2),
|
|
98
|
+
}],
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
// ──────────────────────────────────────────
|
|
102
|
+
// Tool: stk_brain_learn
|
|
103
|
+
// ──────────────────────────────────────────
|
|
104
|
+
server.tool("stk_brain_learn", "Save new knowledge to the brain. Use this to remember patterns, solutions, or learnings for future reference across all projects.", {
|
|
105
|
+
title: z.string().describe("Short title"),
|
|
106
|
+
content: z.string().describe("The knowledge — pattern, solution, or best practice"),
|
|
107
|
+
source: z.string().optional().describe("Where this came from (e.g., 'project:worldchat', 'github:vercel/next.js')"),
|
|
108
|
+
category: z.string().optional().describe("Category: architecture, auth, payments, database, api, deployment, testing, performance, security, ml, general"),
|
|
109
|
+
tags: z.array(z.string()).optional().describe("Tags for searchability"),
|
|
110
|
+
}, async ({ title, content, source, category, tags }) => {
|
|
111
|
+
const brain = getBrainClient();
|
|
112
|
+
const { data, ok } = await brain.insert("knowledge", {
|
|
113
|
+
title,
|
|
114
|
+
content,
|
|
115
|
+
source: source ?? "manual",
|
|
116
|
+
category: category ?? "general",
|
|
117
|
+
tags: tags ?? [],
|
|
118
|
+
created_at: new Date().toISOString(),
|
|
119
|
+
});
|
|
120
|
+
if (!ok)
|
|
121
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Insert failed", data }) }] };
|
|
122
|
+
return {
|
|
123
|
+
content: [{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: JSON.stringify({ learned: true, title, message: "Knowledge saved. I can recall this in future conversations." }, null, 2),
|
|
126
|
+
}],
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
// ──────────────────────────────────────────
|
|
130
|
+
// Tool: stk_brain_stats
|
|
131
|
+
// ──────────────────────────────────────────
|
|
132
|
+
server.tool("stk_brain_stats", "Check what the brain knows — total knowledge entries, categories, sources, and coverage.", {}, async () => {
|
|
133
|
+
const brain = getBrainClient();
|
|
134
|
+
const { data, count } = await brain.query("knowledge", { select: "category,source", limit: "1000" });
|
|
135
|
+
const categories = {};
|
|
136
|
+
const sources = {};
|
|
137
|
+
for (const row of data ?? []) {
|
|
138
|
+
categories[row.category] = (categories[row.category] || 0) + 1;
|
|
139
|
+
sources[row.source] = (sources[row.source] || 0) + 1;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
content: [{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: JSON.stringify({
|
|
145
|
+
totalKnowledge: count ?? data?.length ?? 0,
|
|
146
|
+
categories,
|
|
147
|
+
sources,
|
|
148
|
+
topSources: Object.entries(sources)
|
|
149
|
+
.sort(([, a], [, b]) => b - a)
|
|
150
|
+
.slice(0, 10)
|
|
151
|
+
.map(([name, count]) => ({ name, count })),
|
|
152
|
+
}, null, 2),
|
|
153
|
+
}],
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
// ──────────────────────────────────────────
|
|
157
|
+
// Tool: stk_brain_check (PROACTIVE — call before writing code)
|
|
158
|
+
// ──────────────────────────────────────────
|
|
159
|
+
server.tool("stk_brain_check", "PROACTIVE: Call this BEFORE implementing a feature or making changes. Searches the brain for known gotchas, past bugs, and patterns relevant to what you're about to build. Returns warnings that can prevent mistakes. Use this whenever you start a non-trivial coding task.", {
|
|
160
|
+
task: z.string().describe("What you're about to implement (e.g., 'add email verification', 'update user model', 'add webhook endpoint')"),
|
|
161
|
+
}, async ({ task }) => {
|
|
162
|
+
const results = brainCheck(task);
|
|
163
|
+
if (results.length === 0) {
|
|
164
|
+
return {
|
|
165
|
+
content: [{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: JSON.stringify({
|
|
168
|
+
task,
|
|
169
|
+
warnings: [],
|
|
170
|
+
message: "No known gotchas found. Proceed carefully.",
|
|
171
|
+
}, null, 2),
|
|
172
|
+
}],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const warnings = results.slice(0, 8).map(r => ({
|
|
176
|
+
title: r.entry.title,
|
|
177
|
+
warning: r.entry.content,
|
|
178
|
+
relevance: r.score,
|
|
179
|
+
matchedTerms: r.matchedTerms,
|
|
180
|
+
source: r.entry.source,
|
|
181
|
+
category: r.entry.category,
|
|
182
|
+
}));
|
|
183
|
+
return {
|
|
184
|
+
content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify({
|
|
187
|
+
task,
|
|
188
|
+
warnings,
|
|
189
|
+
totalMatches: results.length,
|
|
190
|
+
message: `Found ${results.length} relevant entries. Review warnings before coding.`,
|
|
191
|
+
}, null, 2),
|
|
192
|
+
}],
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
// ──────────────────────────────────────────
|
|
196
|
+
// Tool: stk_brain_diagnose (REACTIVE — call when you hit an error)
|
|
197
|
+
// ──────────────────────────────────────────
|
|
198
|
+
server.tool("stk_brain_diagnose", "REACTIVE: Call this when you encounter an error or bug. Searches the brain for matching patterns from past issues and returns known solutions. Use this before debugging from scratch — the answer may already be in the brain.", {
|
|
199
|
+
error: z.string().describe("The error message, bug description, or unexpected behavior you're seeing"),
|
|
200
|
+
}, async ({ error }) => {
|
|
201
|
+
const results = brainDiagnose(error);
|
|
202
|
+
if (results.length === 0) {
|
|
203
|
+
return {
|
|
204
|
+
content: [{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: JSON.stringify({
|
|
207
|
+
error,
|
|
208
|
+
solutions: [],
|
|
209
|
+
message: "No matching patterns found in the brain. This is a new issue — debug it, fix it, then use stk_brain_learn to save the solution.",
|
|
210
|
+
}, null, 2),
|
|
211
|
+
}],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const solutions = results.slice(0, 5).map(r => ({
|
|
215
|
+
title: r.entry.title,
|
|
216
|
+
solution: r.entry.content,
|
|
217
|
+
relevance: r.score,
|
|
218
|
+
matchedTerms: r.matchedTerms,
|
|
219
|
+
source: r.entry.source,
|
|
220
|
+
}));
|
|
221
|
+
return {
|
|
222
|
+
content: [{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: JSON.stringify({
|
|
225
|
+
error,
|
|
226
|
+
solutions,
|
|
227
|
+
totalMatches: results.length,
|
|
228
|
+
message: `Found ${results.length} matching patterns. Apply the most relevant solution.`,
|
|
229
|
+
}, null, 2),
|
|
230
|
+
}],
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
// ──────────────────────────────────────────
|
|
234
|
+
// Tool: stk_brain_ingest
|
|
235
|
+
// ──────────────────────────────────────────
|
|
236
|
+
server.tool("stk_brain_ingest", "Scan the current project and ingest architecture knowledge into the local brain (~/.stk/brain.json). Automatically reads CLAUDE.md, package.json, Prisma schema, Dockerfile, CI config, and route files. Run this when setting up stk in a new project or after major changes.", {
|
|
237
|
+
force: z.boolean().optional().default(false).describe("Re-ingest even if already ingested"),
|
|
238
|
+
}, async ({ force }) => {
|
|
239
|
+
const store = loadBrainStore();
|
|
240
|
+
const { projectName, entries, filesScanned } = ingestProject(process.cwd());
|
|
241
|
+
if (store.projects[projectName] && !force) {
|
|
242
|
+
const existing = store.projects[projectName];
|
|
243
|
+
return {
|
|
244
|
+
content: [{
|
|
245
|
+
type: "text",
|
|
246
|
+
text: JSON.stringify({
|
|
247
|
+
alreadyIngested: true,
|
|
248
|
+
projectName,
|
|
249
|
+
entries: existing.entries.length,
|
|
250
|
+
ingestedAt: existing.ingestedAt,
|
|
251
|
+
message: "Already ingested. Use force: true to re-ingest.",
|
|
252
|
+
}, null, 2),
|
|
253
|
+
}],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (entries.length === 0) {
|
|
257
|
+
return {
|
|
258
|
+
content: [{
|
|
259
|
+
type: "text",
|
|
260
|
+
text: JSON.stringify({ error: "No knowledge extracted. Make sure you're in a project directory with recognizable files." }),
|
|
261
|
+
}],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
store.projects[projectName] = {
|
|
265
|
+
ingestedAt: new Date().toISOString(),
|
|
266
|
+
projectPath: process.cwd(),
|
|
267
|
+
entries,
|
|
268
|
+
};
|
|
269
|
+
saveBrainStore(store);
|
|
270
|
+
const categories = {};
|
|
271
|
+
for (const e of entries) {
|
|
272
|
+
categories[e.category] = (categories[e.category] || 0) + 1;
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
content: [{
|
|
276
|
+
type: "text",
|
|
277
|
+
text: JSON.stringify({
|
|
278
|
+
ingested: true,
|
|
279
|
+
projectName,
|
|
280
|
+
totalEntries: entries.length,
|
|
281
|
+
filesScanned,
|
|
282
|
+
categories,
|
|
283
|
+
storedAt: "~/.stk/brain.json",
|
|
284
|
+
}, null, 2),
|
|
285
|
+
}],
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
// ──────────────────────────────────────────
|
|
289
|
+
// Tool: stk_brain_sync
|
|
290
|
+
// ──────────────────────────────────────────
|
|
291
|
+
server.tool("stk_brain_sync", "Sync brain knowledge between local (~/.stk/brain.json) and cloud (Supabase). Push shares your knowledge with other developers. Pull downloads knowledge from the cloud. Sync does both.", {
|
|
292
|
+
action: z.enum(["sync", "push", "pull"]).optional().default("sync").describe("sync: push+pull, push: local→cloud, pull: cloud→local"),
|
|
293
|
+
}, async ({ action }) => {
|
|
294
|
+
let result;
|
|
295
|
+
if (action === "push")
|
|
296
|
+
result = await pushToCloud();
|
|
297
|
+
else if (action === "pull")
|
|
298
|
+
result = await pullFromCloud();
|
|
299
|
+
else
|
|
300
|
+
result = await syncBrain();
|
|
301
|
+
return {
|
|
302
|
+
content: [{
|
|
303
|
+
type: "text",
|
|
304
|
+
text: JSON.stringify({
|
|
305
|
+
action,
|
|
306
|
+
pushed: result.pushed,
|
|
307
|
+
pulled: result.pulled,
|
|
308
|
+
errors: result.errors.length > 0 ? result.errors : undefined,
|
|
309
|
+
ok: result.errors.length === 0,
|
|
310
|
+
}, null, 2),
|
|
311
|
+
}],
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
// ──────────────────────────────────────────
|
|
315
|
+
// Tool: stk_brain_review
|
|
316
|
+
// ──────────────────────────────────────────
|
|
317
|
+
server.tool("stk_brain_review", "Review code changes against the brain's knowledge base. Checks a git diff or PR for known gotchas per file. Use before merging PRs or after making changes.", {
|
|
318
|
+
diff: z.string().optional().describe("Raw git diff output"),
|
|
319
|
+
pr: z.number().optional().describe("GitHub PR number to review"),
|
|
320
|
+
}, async ({ diff, pr }) => {
|
|
321
|
+
let diffContent = diff ?? "";
|
|
322
|
+
// If PR number, fetch diff from GitHub
|
|
323
|
+
if (pr && !diff) {
|
|
324
|
+
const token = process.env.GITHUB_TOKEN;
|
|
325
|
+
let repo = process.env.GITHUB_REPO ?? "";
|
|
326
|
+
if (!repo) {
|
|
327
|
+
try {
|
|
328
|
+
repo = execSync("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().replace(/.*github\.com[:/]/, "").replace(/\.git$/, "");
|
|
329
|
+
}
|
|
330
|
+
catch { /* */ }
|
|
331
|
+
}
|
|
332
|
+
if (repo && token) {
|
|
333
|
+
try {
|
|
334
|
+
const res = await fetch(`https://api.github.com/repos/${repo}/pulls/${pr}`, {
|
|
335
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github.diff" },
|
|
336
|
+
});
|
|
337
|
+
if (res.ok)
|
|
338
|
+
diffContent = await res.text();
|
|
339
|
+
}
|
|
340
|
+
catch { /* */ }
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// If no diff provided, get latest commit diff
|
|
344
|
+
if (!diffContent) {
|
|
345
|
+
try {
|
|
346
|
+
diffContent = execSync("git diff HEAD~1", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 1024 * 1024 * 5 });
|
|
347
|
+
}
|
|
348
|
+
catch { /* */ }
|
|
349
|
+
}
|
|
350
|
+
if (!diffContent) {
|
|
351
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "No diff available. Provide diff text, PR number, or ensure you're in a git repo." }) }] };
|
|
352
|
+
}
|
|
353
|
+
const results = reviewDiff(diffContent);
|
|
354
|
+
const filesWithWarnings = results.filter(r => r.warnings.length > 0);
|
|
355
|
+
return {
|
|
356
|
+
content: [{
|
|
357
|
+
type: "text",
|
|
358
|
+
text: JSON.stringify({
|
|
359
|
+
files: results,
|
|
360
|
+
summary: {
|
|
361
|
+
filesReviewed: results.length,
|
|
362
|
+
filesWithWarnings: filesWithWarnings.length,
|
|
363
|
+
totalWarnings: filesWithWarnings.reduce((s, r) => s + r.warnings.length, 0),
|
|
364
|
+
},
|
|
365
|
+
}, null, 2),
|
|
366
|
+
}],
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
// ──────────────────────────────────────────
|
|
370
|
+
// Tool: stk_brain_claudemd
|
|
371
|
+
// ──────────────────────────────────────────
|
|
372
|
+
server.tool("stk_brain_claudemd", "Auto-generate a CLAUDE.md file for the current project. Analyzes the tech stack, project structure, services, and brain knowledge to create comprehensive project instructions for Claude Code.", {
|
|
373
|
+
projectName: z.string().optional().describe("Project name (auto-detected from stk.config.json if omitted)"),
|
|
374
|
+
write: z.boolean().optional().default(false).describe("Actually write the CLAUDE.md file to disk. If false, just returns the content for review."),
|
|
375
|
+
}, async ({ projectName, write }) => {
|
|
376
|
+
const config = loadConfig();
|
|
377
|
+
const name = projectName ?? config.name ?? "my-project";
|
|
378
|
+
const enabled = enabledServices(config);
|
|
379
|
+
// Detect project info
|
|
380
|
+
let gitBranch = "";
|
|
381
|
+
let gitRemote = "";
|
|
382
|
+
let packageJson = {};
|
|
383
|
+
try {
|
|
384
|
+
gitBranch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
385
|
+
}
|
|
386
|
+
catch { }
|
|
387
|
+
try {
|
|
388
|
+
gitRemote = execSync("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
389
|
+
}
|
|
390
|
+
catch { }
|
|
391
|
+
try {
|
|
392
|
+
const { readFileSync } = await import("fs");
|
|
393
|
+
packageJson = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
394
|
+
}
|
|
395
|
+
catch { }
|
|
396
|
+
// Detect tech stack
|
|
397
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
398
|
+
const stack = [];
|
|
399
|
+
if (deps?.["next"])
|
|
400
|
+
stack.push("Next.js");
|
|
401
|
+
if (deps?.["react"])
|
|
402
|
+
stack.push("React");
|
|
403
|
+
if (deps?.["vue"])
|
|
404
|
+
stack.push("Vue");
|
|
405
|
+
if (deps?.["express"])
|
|
406
|
+
stack.push("Express.js");
|
|
407
|
+
if (deps?.["fastify"])
|
|
408
|
+
stack.push("Fastify");
|
|
409
|
+
if (deps?.["@supabase/supabase-js"])
|
|
410
|
+
stack.push("Supabase");
|
|
411
|
+
if (deps?.["stripe"])
|
|
412
|
+
stack.push("Stripe");
|
|
413
|
+
if (deps?.["prisma"] || deps?.["@prisma/client"])
|
|
414
|
+
stack.push("Prisma");
|
|
415
|
+
if (deps?.["mongoose"])
|
|
416
|
+
stack.push("Mongoose/MongoDB");
|
|
417
|
+
if (deps?.["redis"] || deps?.["ioredis"])
|
|
418
|
+
stack.push("Redis");
|
|
419
|
+
if (deps?.["tailwindcss"])
|
|
420
|
+
stack.push("Tailwind CSS");
|
|
421
|
+
if (deps?.["typescript"])
|
|
422
|
+
stack.push("TypeScript");
|
|
423
|
+
if (deps?.["zod"])
|
|
424
|
+
stack.push("Zod");
|
|
425
|
+
if (stack.length === 0 && Object.keys(deps ?? {}).length > 0) {
|
|
426
|
+
stack.push("Node.js");
|
|
427
|
+
}
|
|
428
|
+
// Detect file structure
|
|
429
|
+
let fileStructure = "";
|
|
430
|
+
try {
|
|
431
|
+
fileStructure = execSync("find . -maxdepth 3 -type f -name '*.ts' -o -name '*.js' -o -name '*.tsx' -o -name '*.jsx' -o -name '*.json' -o -name '*.css' -o -name '*.html' | grep -v node_modules | grep -v dist | grep -v .git | sort | head -40", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
432
|
+
}
|
|
433
|
+
catch { }
|
|
434
|
+
// Build CLAUDE.md
|
|
435
|
+
const lines = [];
|
|
436
|
+
lines.push(`# ${name}\n`);
|
|
437
|
+
// Project overview
|
|
438
|
+
lines.push(`## Overview\n`);
|
|
439
|
+
if (packageJson.description)
|
|
440
|
+
lines.push(`${packageJson.description}\n`);
|
|
441
|
+
if (stack.length > 0)
|
|
442
|
+
lines.push(`**Tech Stack:** ${stack.join(", ")}\n`);
|
|
443
|
+
if (gitRemote)
|
|
444
|
+
lines.push(`**Repo:** ${gitRemote}`);
|
|
445
|
+
if (gitBranch)
|
|
446
|
+
lines.push(`**Main Branch:** ${gitBranch}`);
|
|
447
|
+
lines.push("");
|
|
448
|
+
// Services
|
|
449
|
+
if (enabled.length > 0) {
|
|
450
|
+
lines.push(`## Services\n`);
|
|
451
|
+
for (const svc of enabled) {
|
|
452
|
+
const svcName = svc.charAt(0).toUpperCase() + svc.slice(1);
|
|
453
|
+
lines.push(`- **${svcName}** — configured and monitored via stk`);
|
|
454
|
+
}
|
|
455
|
+
lines.push("");
|
|
456
|
+
}
|
|
457
|
+
// Project structure
|
|
458
|
+
if (fileStructure) {
|
|
459
|
+
lines.push(`## Project Structure\n`);
|
|
460
|
+
lines.push("```");
|
|
461
|
+
lines.push(fileStructure);
|
|
462
|
+
lines.push("```\n");
|
|
463
|
+
}
|
|
464
|
+
// Development commands
|
|
465
|
+
lines.push(`## Development\n`);
|
|
466
|
+
if (packageJson.scripts) {
|
|
467
|
+
lines.push("```bash");
|
|
468
|
+
if (packageJson.scripts.dev)
|
|
469
|
+
lines.push(`npm run dev # Start dev server`);
|
|
470
|
+
if (packageJson.scripts.build)
|
|
471
|
+
lines.push(`npm run build # Build for production`);
|
|
472
|
+
if (packageJson.scripts.test)
|
|
473
|
+
lines.push(`npm test # Run tests`);
|
|
474
|
+
if (packageJson.scripts.start)
|
|
475
|
+
lines.push(`npm start # Start production server`);
|
|
476
|
+
lines.push("```\n");
|
|
477
|
+
}
|
|
478
|
+
// stk tools available
|
|
479
|
+
lines.push(`## stk Tools Available\n`);
|
|
480
|
+
lines.push(`This project uses stk for infrastructure monitoring. Available commands:\n`);
|
|
481
|
+
lines.push(`- \`stk_health\` — Check service health`);
|
|
482
|
+
lines.push(`- \`stk_status\` — Full project overview`);
|
|
483
|
+
lines.push(`- \`stk_logs\` — Production logs`);
|
|
484
|
+
lines.push(`- \`stk_db\` — Query database`);
|
|
485
|
+
lines.push(`- \`stk_alerts\` — Check for problems`);
|
|
486
|
+
lines.push(`- \`stk_deploy\` — Deploy to production`);
|
|
487
|
+
lines.push(`- \`stk_brain_search\` — Search knowledge base`);
|
|
488
|
+
lines.push(`- \`stk_brain_learn\` — Save patterns for future use`);
|
|
489
|
+
lines.push("");
|
|
490
|
+
// Conventions
|
|
491
|
+
lines.push(`## Conventions\n`);
|
|
492
|
+
if (deps?.["typescript"]) {
|
|
493
|
+
lines.push(`- TypeScript strict mode enabled`);
|
|
494
|
+
}
|
|
495
|
+
lines.push(`- Use existing patterns in the codebase before introducing new ones`);
|
|
496
|
+
lines.push(`- Check stk_health before debugging production issues`);
|
|
497
|
+
lines.push(`- Use stk_brain_search to find how other projects solve similar problems`);
|
|
498
|
+
lines.push(`- Save useful patterns with stk_brain_learn for future reference`);
|
|
499
|
+
lines.push("");
|
|
500
|
+
// Environment
|
|
501
|
+
lines.push(`## Environment Variables\n`);
|
|
502
|
+
lines.push(`Environment variables are managed via \`.env\` files. See \`.env.example\` for required variables.`);
|
|
503
|
+
if (enabled.includes("vercel"))
|
|
504
|
+
lines.push(`Use \`stk_env_sync\` to compare local vs remote env vars.`);
|
|
505
|
+
lines.push("");
|
|
506
|
+
const content = lines.join("\n");
|
|
507
|
+
if (write) {
|
|
508
|
+
const { writeFileSync } = await import("fs");
|
|
509
|
+
writeFileSync("CLAUDE.md", content);
|
|
510
|
+
return {
|
|
511
|
+
content: [{
|
|
512
|
+
type: "text",
|
|
513
|
+
text: JSON.stringify({ written: true, path: "CLAUDE.md", lines: content.split("\n").length }, null, 2),
|
|
514
|
+
}],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
content: [{
|
|
519
|
+
type: "text",
|
|
520
|
+
text: JSON.stringify({ preview: true, content, note: "Call again with write: true to save to disk" }, null, 2),
|
|
521
|
+
}],
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
// ──────────────────────────────────────────
|
|
525
|
+
// Tool: stk_brain_team
|
|
526
|
+
// ──────────────────────────────────────────
|
|
527
|
+
server.tool("stk_brain_team", "Show team brain contributions — who learned what, from which projects, and when. Tracks knowledge sharing across team members.", {}, async () => {
|
|
528
|
+
const store = loadBrainStore();
|
|
529
|
+
const allEntries = [...store.global];
|
|
530
|
+
for (const proj of Object.values(store.projects))
|
|
531
|
+
allEntries.push(...proj.entries);
|
|
532
|
+
const contributors = {};
|
|
533
|
+
for (const entry of allEntries) {
|
|
534
|
+
const contributor = entry.contributor ?? "auto";
|
|
535
|
+
if (!contributors[contributor]) {
|
|
536
|
+
contributors[contributor] = { count: 0, lastActive: entry.created_at, categories: {} };
|
|
537
|
+
}
|
|
538
|
+
contributors[contributor].count++;
|
|
539
|
+
if (entry.created_at > contributors[contributor].lastActive) {
|
|
540
|
+
contributors[contributor].lastActive = entry.created_at;
|
|
541
|
+
}
|
|
542
|
+
contributors[contributor].categories[entry.category] = (contributors[contributor].categories[entry.category] || 0) + 1;
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
content: [{
|
|
546
|
+
type: "text",
|
|
547
|
+
text: JSON.stringify({
|
|
548
|
+
totalEntries: allEntries.length,
|
|
549
|
+
contributors,
|
|
550
|
+
projects: Object.entries(store.projects).map(([name, p]) => ({
|
|
551
|
+
name, entries: p.entries.length, ingestedAt: p.ingestedAt,
|
|
552
|
+
})),
|
|
553
|
+
}, null, 2),
|
|
554
|
+
}],
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
} // end registerBrainTools
|