@prajwolkc/stk 0.5.0 → 0.6.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/init.js +246 -30
- package/dist/mcp/server.js +155 -1
- package/dist/services/brain.d.ts +13 -0
- package/dist/services/brain.js +87 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,15 +1,165 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
4
5
|
import { basename } from "path";
|
|
5
6
|
import { CONFIG_FILE, KNOWN_SERVICES } from "../lib/config.js";
|
|
6
7
|
import { templates, listTemplates } from "../templates/index.js";
|
|
8
|
+
import { ingestProject, loadBrainStore, saveBrainStore, pullFromCloud, pushToCloud } from "../services/brain.js";
|
|
7
9
|
const DEPLOY_PROVIDERS = ["railway", "vercel", "fly", "render", "aws"];
|
|
10
|
+
/** Scan project files to detect what stack is being used */
|
|
11
|
+
function detectStackFromFiles() {
|
|
12
|
+
const detected = {};
|
|
13
|
+
const stack = [];
|
|
14
|
+
let projectType = "unknown";
|
|
15
|
+
// 1. Check env vars (existing behavior)
|
|
16
|
+
const envChecks = {
|
|
17
|
+
railway: ["RAILWAY_API_TOKEN"],
|
|
18
|
+
vercel: ["VERCEL_TOKEN"],
|
|
19
|
+
fly: ["FLY_API_TOKEN"],
|
|
20
|
+
render: ["RENDER_API_KEY"],
|
|
21
|
+
aws: ["AWS_ACCESS_KEY_ID"],
|
|
22
|
+
database: ["DATABASE_URL"],
|
|
23
|
+
mongodb: ["MONGODB_URL", "MONGO_URL"],
|
|
24
|
+
redis: ["REDIS_URL"],
|
|
25
|
+
supabase: ["SUPABASE_URL"],
|
|
26
|
+
r2: ["CLOUDFLARE_ACCOUNT_ID"],
|
|
27
|
+
stripe: ["STRIPE_SECRET_KEY"],
|
|
28
|
+
};
|
|
29
|
+
for (const [service, vars] of Object.entries(envChecks)) {
|
|
30
|
+
if (vars.some((v) => process.env[v])) {
|
|
31
|
+
detected[service] = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// 2. Scan package.json files for dependencies
|
|
35
|
+
const pkgPaths = [
|
|
36
|
+
"package.json",
|
|
37
|
+
"node-backend/package.json",
|
|
38
|
+
"backend/package.json",
|
|
39
|
+
"server/package.json",
|
|
40
|
+
"api/package.json",
|
|
41
|
+
"frontend/package.json",
|
|
42
|
+
"web/package.json",
|
|
43
|
+
"client/package.json",
|
|
44
|
+
];
|
|
45
|
+
const allDeps = {};
|
|
46
|
+
for (const p of pkgPaths) {
|
|
47
|
+
if (existsSync(p)) {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(readFileSync(p, "utf-8"));
|
|
50
|
+
Object.assign(allDeps, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
|
|
51
|
+
}
|
|
52
|
+
catch { /* skip */ }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Detect framework
|
|
56
|
+
if (allDeps["next"]) {
|
|
57
|
+
stack.push("Next.js");
|
|
58
|
+
projectType = "fullstack";
|
|
59
|
+
}
|
|
60
|
+
else if (allDeps["react"]) {
|
|
61
|
+
stack.push("React");
|
|
62
|
+
}
|
|
63
|
+
if (allDeps["vue"]) {
|
|
64
|
+
stack.push("Vue");
|
|
65
|
+
}
|
|
66
|
+
if (allDeps["angular"] || allDeps["@angular/core"]) {
|
|
67
|
+
stack.push("Angular");
|
|
68
|
+
}
|
|
69
|
+
if (allDeps["express"]) {
|
|
70
|
+
stack.push("Express");
|
|
71
|
+
projectType = projectType === "unknown" ? "api" : projectType;
|
|
72
|
+
}
|
|
73
|
+
if (allDeps["fastify"]) {
|
|
74
|
+
stack.push("Fastify");
|
|
75
|
+
projectType = projectType === "unknown" ? "api" : projectType;
|
|
76
|
+
}
|
|
77
|
+
if (allDeps["hono"]) {
|
|
78
|
+
stack.push("Hono");
|
|
79
|
+
}
|
|
80
|
+
// Detect ORM/DB
|
|
81
|
+
if (allDeps["prisma"] || allDeps["@prisma/client"]) {
|
|
82
|
+
stack.push("Prisma");
|
|
83
|
+
detected.database = true;
|
|
84
|
+
}
|
|
85
|
+
if (allDeps["mongoose"]) {
|
|
86
|
+
stack.push("Mongoose");
|
|
87
|
+
detected.mongodb = true;
|
|
88
|
+
}
|
|
89
|
+
if (allDeps["typeorm"]) {
|
|
90
|
+
stack.push("TypeORM");
|
|
91
|
+
detected.database = true;
|
|
92
|
+
}
|
|
93
|
+
if (allDeps["drizzle-orm"]) {
|
|
94
|
+
stack.push("Drizzle");
|
|
95
|
+
detected.database = true;
|
|
96
|
+
}
|
|
97
|
+
if (allDeps["@supabase/supabase-js"]) {
|
|
98
|
+
stack.push("Supabase SDK");
|
|
99
|
+
detected.supabase = true;
|
|
100
|
+
}
|
|
101
|
+
// Detect billing/payments
|
|
102
|
+
if (allDeps["stripe"] || allDeps["@stripe/stripe-js"]) {
|
|
103
|
+
stack.push("Stripe");
|
|
104
|
+
detected.stripe = true;
|
|
105
|
+
}
|
|
106
|
+
// Detect queue/cache
|
|
107
|
+
if (allDeps["bullmq"] || allDeps["bull"] || allDeps["ioredis"]) {
|
|
108
|
+
stack.push("Redis/BullMQ");
|
|
109
|
+
detected.redis = true;
|
|
110
|
+
}
|
|
111
|
+
// Detect auth
|
|
112
|
+
if (allDeps["jsonwebtoken"])
|
|
113
|
+
stack.push("JWT Auth");
|
|
114
|
+
if (allDeps["passport"])
|
|
115
|
+
stack.push("Passport.js");
|
|
116
|
+
if (allDeps["next-auth"] || allDeps["@auth/core"])
|
|
117
|
+
stack.push("Auth.js");
|
|
118
|
+
// 3. Scan for config files that indicate deploy providers
|
|
119
|
+
if (existsSync("railway.json") || existsSync("railway.toml"))
|
|
120
|
+
detected.railway = true;
|
|
121
|
+
if (existsSync("vercel.json") || existsSync(".vercel"))
|
|
122
|
+
detected.vercel = true;
|
|
123
|
+
if (existsSync("fly.toml"))
|
|
124
|
+
detected.fly = true;
|
|
125
|
+
if (existsSync("render.yaml"))
|
|
126
|
+
detected.render = true;
|
|
127
|
+
if (existsSync("Dockerfile"))
|
|
128
|
+
stack.push("Docker");
|
|
129
|
+
// 4. Scan for Prisma schema
|
|
130
|
+
const prismaPaths = [
|
|
131
|
+
"prisma/schema.prisma",
|
|
132
|
+
"node-backend/prisma/schema.prisma",
|
|
133
|
+
"backend/prisma/schema.prisma",
|
|
134
|
+
"src/prisma/schema.prisma",
|
|
135
|
+
];
|
|
136
|
+
for (const p of prismaPaths) {
|
|
137
|
+
if (existsSync(p)) {
|
|
138
|
+
detected.database = true;
|
|
139
|
+
if (!stack.includes("Prisma"))
|
|
140
|
+
stack.push("Prisma");
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// 5. Detect monorepo vs single
|
|
145
|
+
const hasMultiplePkgs = pkgPaths.filter(p => existsSync(p)).length > 1;
|
|
146
|
+
if (hasMultiplePkgs)
|
|
147
|
+
projectType = "fullstack";
|
|
148
|
+
// 6. Detect if frontend-only (static)
|
|
149
|
+
if (stack.length > 0 && !allDeps["express"] && !allDeps["fastify"] && !allDeps["hono"] && !allDeps["next"]) {
|
|
150
|
+
if (allDeps["react"] || allDeps["vue"] || allDeps["angular"]) {
|
|
151
|
+
if (projectType === "unknown")
|
|
152
|
+
projectType = "static";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { detected, stack, projectType };
|
|
156
|
+
}
|
|
8
157
|
export const initCommand = new Command("init")
|
|
9
|
-
.description("Initialize stk config
|
|
158
|
+
.description("Initialize stk config — auto-detects stack, ingests knowledge, syncs brain")
|
|
10
159
|
.option("--force", "overwrite existing config")
|
|
11
160
|
.option("-t, --template <name>", `use a starter template (${listTemplates().join(", ")})`)
|
|
12
161
|
.option("--list-templates", "show available templates")
|
|
162
|
+
.option("--skip-brain", "skip brain ingest and sync")
|
|
13
163
|
.action(async (opts) => {
|
|
14
164
|
// List templates
|
|
15
165
|
if (opts.listTemplates) {
|
|
@@ -52,31 +202,24 @@ export const initCommand = new Command("init")
|
|
|
52
202
|
console.log(` ${icon} ${enabled ? chalk.white(name) : chalk.dim(name)}`);
|
|
53
203
|
}
|
|
54
204
|
console.log();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
console.log();
|
|
205
|
+
// Run brain steps even for template init
|
|
206
|
+
if (!opts.skipBrain) {
|
|
207
|
+
await runBrainSteps(projectName);
|
|
208
|
+
}
|
|
60
209
|
return;
|
|
61
210
|
}
|
|
62
|
-
// Auto-detect
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
r2: ["CLOUDFLARE_ACCOUNT_ID"],
|
|
75
|
-
stripe: ["STRIPE_SECRET_KEY"],
|
|
76
|
-
};
|
|
77
|
-
for (const [service, vars] of Object.entries(envChecks)) {
|
|
78
|
-
if (vars.some((v) => process.env[v])) {
|
|
79
|
-
detected[service] = true;
|
|
211
|
+
// ─── Smart Auto-detect Init ───
|
|
212
|
+
console.log();
|
|
213
|
+
const detectSpinner = ora(" Scanning project...").start();
|
|
214
|
+
const { detected, stack, projectType } = detectStackFromFiles();
|
|
215
|
+
detectSpinner.succeed(" Project scanned");
|
|
216
|
+
// Show detected stack
|
|
217
|
+
if (stack.length > 0) {
|
|
218
|
+
console.log();
|
|
219
|
+
console.log(chalk.bold(" Detected stack:"));
|
|
220
|
+
console.log(` ${chalk.cyan(stack.join(" + "))}`);
|
|
221
|
+
if (projectType !== "unknown") {
|
|
222
|
+
console.log(` ${chalk.dim(`Project type: ${projectType}`)}`);
|
|
80
223
|
}
|
|
81
224
|
}
|
|
82
225
|
const config = {
|
|
@@ -94,18 +237,91 @@ export const initCommand = new Command("init")
|
|
|
94
237
|
console.log();
|
|
95
238
|
console.log(` ${chalk.green("✓")} Created ${chalk.bold(CONFIG_FILE)}`);
|
|
96
239
|
console.log();
|
|
97
|
-
console.log(chalk.bold("
|
|
240
|
+
console.log(chalk.bold(" Services:"));
|
|
98
241
|
const serviceNames = Object.entries(config.services);
|
|
99
242
|
for (const [name, enabled] of serviceNames) {
|
|
100
243
|
const icon = enabled ? chalk.green("✓") : chalk.dim("○");
|
|
101
244
|
const label = enabled
|
|
102
245
|
? chalk.white(name)
|
|
103
|
-
: chalk.dim(
|
|
246
|
+
: chalk.dim(name);
|
|
104
247
|
console.log(` ${icon} ${label}`);
|
|
105
248
|
}
|
|
249
|
+
// ─── Brain Steps ───
|
|
250
|
+
if (!opts.skipBrain) {
|
|
251
|
+
await runBrainSteps(projectName);
|
|
252
|
+
}
|
|
106
253
|
console.log();
|
|
107
|
-
console.log(chalk.dim(`
|
|
108
|
-
console.log(chalk.dim(` Or try: ${chalk.white("stk init --template saas")} for a pre-configured stack.`));
|
|
109
|
-
console.log(chalk.dim(` Then run ${chalk.white("stk health")} to verify.`));
|
|
254
|
+
console.log(chalk.dim(` Run ${chalk.white("stk health")} to verify services.`));
|
|
110
255
|
console.log();
|
|
111
256
|
});
|
|
257
|
+
/** Ingest project, pull cloud knowledge, push new knowledge */
|
|
258
|
+
async function runBrainSteps(projectName) {
|
|
259
|
+
console.log();
|
|
260
|
+
console.log(chalk.bold(" Brain setup:"));
|
|
261
|
+
// Step 1: Ingest project
|
|
262
|
+
const ingestSpinner = ora(" Scanning project architecture...").start();
|
|
263
|
+
try {
|
|
264
|
+
const { entries, filesScanned } = ingestProject(process.cwd());
|
|
265
|
+
if (entries.length > 0) {
|
|
266
|
+
const store = loadBrainStore();
|
|
267
|
+
store.projects[projectName] = {
|
|
268
|
+
ingestedAt: new Date().toISOString(),
|
|
269
|
+
projectPath: process.cwd(),
|
|
270
|
+
entries,
|
|
271
|
+
};
|
|
272
|
+
saveBrainStore(store);
|
|
273
|
+
ingestSpinner.succeed(` Ingested ${chalk.white(entries.length)} knowledge entries from ${filesScanned.length} files`);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
ingestSpinner.warn(" No project files found to ingest");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
ingestSpinner.warn(" Ingest skipped (no recognizable files)");
|
|
281
|
+
}
|
|
282
|
+
// Step 2: Pull cloud brain (learn from other projects)
|
|
283
|
+
const pullSpinner = ora(" Pulling knowledge from cloud brain...").start();
|
|
284
|
+
try {
|
|
285
|
+
const pullResult = await pullFromCloud();
|
|
286
|
+
if (pullResult.errors.length > 0) {
|
|
287
|
+
pullSpinner.warn(" Cloud brain not available (set SUPABASE_URL + SUPABASE_SERVICE_KEY to enable)");
|
|
288
|
+
}
|
|
289
|
+
else if (pullResult.pulled > 0) {
|
|
290
|
+
pullSpinner.succeed(` Pulled ${chalk.white(pullResult.pulled)} entries from cloud (learned from other projects)`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
pullSpinner.succeed(" Cloud brain in sync");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
pullSpinner.warn(" Cloud sync skipped (no connection)");
|
|
298
|
+
}
|
|
299
|
+
// Step 3: Push new knowledge to cloud
|
|
300
|
+
const pushSpinner = ora(" Sharing knowledge to cloud brain...").start();
|
|
301
|
+
try {
|
|
302
|
+
const pushResult = await pushToCloud();
|
|
303
|
+
if (pushResult.errors.length > 0) {
|
|
304
|
+
pushSpinner.warn(" Push skipped");
|
|
305
|
+
}
|
|
306
|
+
else if (pushResult.pushed > 0) {
|
|
307
|
+
pushSpinner.succeed(` Pushed ${chalk.white(pushResult.pushed)} entries to cloud`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
pushSpinner.succeed(" All knowledge already shared");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
pushSpinner.warn(" Push skipped (no connection)");
|
|
315
|
+
}
|
|
316
|
+
// Show what we know from other projects
|
|
317
|
+
const store = loadBrainStore();
|
|
318
|
+
const otherProjects = Object.keys(store.projects).filter(p => p !== projectName);
|
|
319
|
+
if (otherProjects.length > 0) {
|
|
320
|
+
console.log();
|
|
321
|
+
console.log(chalk.bold(" Knowledge available from other projects:"));
|
|
322
|
+
for (const proj of otherProjects) {
|
|
323
|
+
const p = store.projects[proj];
|
|
324
|
+
console.log(` ${chalk.green("●")} ${chalk.white(proj)} — ${p.entries.length} entries`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -11,7 +11,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { loadConfig, enabledServices } from "../lib/config.js";
|
|
13
13
|
import { getChecker, allCheckerNames, loadPluginCheckers } from "../services/registry.js";
|
|
14
|
-
import { getLocalBrainClient, ingestProject, loadBrainStore, saveBrainStore, syncBrain, pushToCloud, pullFromCloud } from "../services/brain.js";
|
|
14
|
+
import { getLocalBrainClient, ingestProject, loadBrainStore, saveBrainStore, syncBrain, pushToCloud, pullFromCloud, brainCheck, brainDiagnose } from "../services/brain.js";
|
|
15
15
|
import { execSync } from "child_process";
|
|
16
16
|
const server = new McpServer({
|
|
17
17
|
name: "stk",
|
|
@@ -1163,6 +1163,83 @@ server.tool("stk_brain_stats", "Check what the brain knows — total knowledge e
|
|
|
1163
1163
|
};
|
|
1164
1164
|
});
|
|
1165
1165
|
// ──────────────────────────────────────────
|
|
1166
|
+
// Tool: stk_brain_check
|
|
1167
|
+
// ──────────────────────────────────────────
|
|
1168
|
+
server.tool("stk_brain_check", "PROACTIVE GOTCHA DETECTION — Run this BEFORE implementing any feature or fixing any bug. Searches the brain for known gotchas, pitfalls, and patterns related to your task. Returns warnings ranked by relevance so you can avoid repeating past mistakes. Claude should call this automatically before writing code.", {
|
|
1169
|
+
task: z.string().describe("What you're about to implement or fix (e.g., 'add email verification', 'update user model', 'fix auth redirect')"),
|
|
1170
|
+
}, async ({ task }) => {
|
|
1171
|
+
const results = brainCheck(task);
|
|
1172
|
+
if (results.length === 0) {
|
|
1173
|
+
return {
|
|
1174
|
+
content: [{
|
|
1175
|
+
type: "text",
|
|
1176
|
+
text: JSON.stringify({
|
|
1177
|
+
task,
|
|
1178
|
+
warnings: [],
|
|
1179
|
+
message: "No known gotchas found. Proceed with caution — this might be new territory.",
|
|
1180
|
+
}, null, 2),
|
|
1181
|
+
}],
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
const warnings = results.slice(0, 5).map(r => ({
|
|
1185
|
+
title: r.entry.title,
|
|
1186
|
+
content: r.entry.content,
|
|
1187
|
+
relevance: r.score,
|
|
1188
|
+
matchedTerms: r.matchedTerms,
|
|
1189
|
+
source: r.entry.source,
|
|
1190
|
+
category: r.entry.category,
|
|
1191
|
+
}));
|
|
1192
|
+
return {
|
|
1193
|
+
content: [{
|
|
1194
|
+
type: "text",
|
|
1195
|
+
text: JSON.stringify({
|
|
1196
|
+
task,
|
|
1197
|
+
warnings,
|
|
1198
|
+
totalMatches: results.length,
|
|
1199
|
+
message: `Found ${results.length} relevant pattern(s). Review warnings before coding.`,
|
|
1200
|
+
}, null, 2),
|
|
1201
|
+
}],
|
|
1202
|
+
};
|
|
1203
|
+
});
|
|
1204
|
+
// ──────────────────────────────────────────
|
|
1205
|
+
// Tool: stk_brain_diagnose
|
|
1206
|
+
// ──────────────────────────────────────────
|
|
1207
|
+
server.tool("stk_brain_diagnose", "ERROR PATTERN MATCHING — Run this when you encounter an error or bug. Searches the brain for matching patterns from past issues and returns known solutions. Claude should call this automatically when debugging.", {
|
|
1208
|
+
error: z.string().describe("The error message, symptom, or bug description (e.g., 'redirect not working after verification', 'emails not sending', 'prisma field undefined')"),
|
|
1209
|
+
}, async ({ error }) => {
|
|
1210
|
+
const results = brainDiagnose(error);
|
|
1211
|
+
if (results.length === 0) {
|
|
1212
|
+
return {
|
|
1213
|
+
content: [{
|
|
1214
|
+
type: "text",
|
|
1215
|
+
text: JSON.stringify({
|
|
1216
|
+
error,
|
|
1217
|
+
solutions: [],
|
|
1218
|
+
message: "No matching patterns found. This is a new issue — after fixing it, use stk_brain_learn to save the pattern.",
|
|
1219
|
+
}, null, 2),
|
|
1220
|
+
}],
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
const solutions = results.slice(0, 5).map(r => ({
|
|
1224
|
+
title: r.entry.title,
|
|
1225
|
+
solution: r.entry.content,
|
|
1226
|
+
relevance: r.score,
|
|
1227
|
+
matchedTerms: r.matchedTerms,
|
|
1228
|
+
source: r.entry.source,
|
|
1229
|
+
}));
|
|
1230
|
+
return {
|
|
1231
|
+
content: [{
|
|
1232
|
+
type: "text",
|
|
1233
|
+
text: JSON.stringify({
|
|
1234
|
+
error,
|
|
1235
|
+
solutions,
|
|
1236
|
+
totalMatches: results.length,
|
|
1237
|
+
message: `Found ${results.length} matching pattern(s). Apply relevant solutions.`,
|
|
1238
|
+
}, null, 2),
|
|
1239
|
+
}],
|
|
1240
|
+
};
|
|
1241
|
+
});
|
|
1242
|
+
// ──────────────────────────────────────────
|
|
1166
1243
|
// Tool: stk_brain_ingest
|
|
1167
1244
|
// ──────────────────────────────────────────
|
|
1168
1245
|
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.", {
|
|
@@ -1244,6 +1321,83 @@ server.tool("stk_brain_sync", "Sync brain knowledge between local (~/.stk/brain.
|
|
|
1244
1321
|
};
|
|
1245
1322
|
});
|
|
1246
1323
|
// ──────────────────────────────────────────
|
|
1324
|
+
// Tool: stk_brain_check (PROACTIVE — call before writing code)
|
|
1325
|
+
// ──────────────────────────────────────────
|
|
1326
|
+
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.", {
|
|
1327
|
+
task: z.string().describe("What you're about to implement (e.g., 'add email verification', 'update user model', 'add webhook endpoint')"),
|
|
1328
|
+
}, async ({ task }) => {
|
|
1329
|
+
const results = brainCheck(task);
|
|
1330
|
+
if (results.length === 0) {
|
|
1331
|
+
return {
|
|
1332
|
+
content: [{
|
|
1333
|
+
type: "text",
|
|
1334
|
+
text: JSON.stringify({
|
|
1335
|
+
task,
|
|
1336
|
+
warnings: [],
|
|
1337
|
+
message: "No known gotchas found. Proceed carefully.",
|
|
1338
|
+
}, null, 2),
|
|
1339
|
+
}],
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
const warnings = results.slice(0, 8).map(r => ({
|
|
1343
|
+
title: r.entry.title,
|
|
1344
|
+
warning: r.entry.content,
|
|
1345
|
+
relevance: r.score,
|
|
1346
|
+
matchedTerms: r.matchedTerms,
|
|
1347
|
+
source: r.entry.source,
|
|
1348
|
+
category: r.entry.category,
|
|
1349
|
+
}));
|
|
1350
|
+
return {
|
|
1351
|
+
content: [{
|
|
1352
|
+
type: "text",
|
|
1353
|
+
text: JSON.stringify({
|
|
1354
|
+
task,
|
|
1355
|
+
warnings,
|
|
1356
|
+
totalMatches: results.length,
|
|
1357
|
+
message: `Found ${results.length} relevant entries. Review warnings before coding.`,
|
|
1358
|
+
}, null, 2),
|
|
1359
|
+
}],
|
|
1360
|
+
};
|
|
1361
|
+
});
|
|
1362
|
+
// ──────────────────────────────────────────
|
|
1363
|
+
// Tool: stk_brain_diagnose (REACTIVE — call when you hit an error)
|
|
1364
|
+
// ──────────────────────────────────────────
|
|
1365
|
+
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.", {
|
|
1366
|
+
error: z.string().describe("The error message, bug description, or unexpected behavior you're seeing"),
|
|
1367
|
+
}, async ({ error }) => {
|
|
1368
|
+
const results = brainDiagnose(error);
|
|
1369
|
+
if (results.length === 0) {
|
|
1370
|
+
return {
|
|
1371
|
+
content: [{
|
|
1372
|
+
type: "text",
|
|
1373
|
+
text: JSON.stringify({
|
|
1374
|
+
error,
|
|
1375
|
+
solutions: [],
|
|
1376
|
+
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.",
|
|
1377
|
+
}, null, 2),
|
|
1378
|
+
}],
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
const solutions = results.slice(0, 5).map(r => ({
|
|
1382
|
+
title: r.entry.title,
|
|
1383
|
+
solution: r.entry.content,
|
|
1384
|
+
relevance: r.score,
|
|
1385
|
+
matchedTerms: r.matchedTerms,
|
|
1386
|
+
source: r.entry.source,
|
|
1387
|
+
}));
|
|
1388
|
+
return {
|
|
1389
|
+
content: [{
|
|
1390
|
+
type: "text",
|
|
1391
|
+
text: JSON.stringify({
|
|
1392
|
+
error,
|
|
1393
|
+
solutions,
|
|
1394
|
+
totalMatches: results.length,
|
|
1395
|
+
message: `Found ${results.length} matching patterns. Apply the most relevant solution.`,
|
|
1396
|
+
}, null, 2),
|
|
1397
|
+
}],
|
|
1398
|
+
};
|
|
1399
|
+
});
|
|
1400
|
+
// ──────────────────────────────────────────
|
|
1247
1401
|
// Tool: stk_brain_claudemd
|
|
1248
1402
|
// ──────────────────────────────────────────
|
|
1249
1403
|
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.", {
|
package/dist/services/brain.d.ts
CHANGED
|
@@ -63,4 +63,17 @@ export declare function pushToCloud(): Promise<SyncResult>;
|
|
|
63
63
|
export declare function pullFromCloud(): Promise<SyncResult>;
|
|
64
64
|
/** Full sync: push local → cloud, then pull cloud → local */
|
|
65
65
|
export declare function syncBrain(): Promise<SyncResult>;
|
|
66
|
+
interface ScoredEntry {
|
|
67
|
+
entry: KnowledgeEntry;
|
|
68
|
+
score: number;
|
|
69
|
+
matchedTerms: string[];
|
|
70
|
+
}
|
|
71
|
+
/** Search brain with relevance scoring — returns entries ranked by how many terms match */
|
|
72
|
+
export declare function smartSearch(terms: string[], category?: string): ScoredEntry[];
|
|
73
|
+
/** Extract relevant terms from a task description */
|
|
74
|
+
export declare function extractTerms(description: string): string[];
|
|
75
|
+
/** Proactive check — find gotchas relevant to a task before coding */
|
|
76
|
+
export declare function brainCheck(taskDescription: string): ScoredEntry[];
|
|
77
|
+
/** Diagnose an error — find matching patterns from past issues */
|
|
78
|
+
export declare function brainDiagnose(error: string): ScoredEntry[];
|
|
66
79
|
export {};
|
package/dist/services/brain.js
CHANGED
|
@@ -524,3 +524,90 @@ export async function syncBrain() {
|
|
|
524
524
|
errors: [...pushResult.errors, ...pullResult.errors],
|
|
525
525
|
};
|
|
526
526
|
}
|
|
527
|
+
/** Search brain with relevance scoring — returns entries ranked by how many terms match */
|
|
528
|
+
export function smartSearch(terms, category) {
|
|
529
|
+
const store = loadBrainStore();
|
|
530
|
+
let entries = getAllEntries(store);
|
|
531
|
+
if (category) {
|
|
532
|
+
entries = entries.filter(e => e.category === category);
|
|
533
|
+
}
|
|
534
|
+
const normalizedTerms = terms.map(t => t.toLowerCase());
|
|
535
|
+
const scored = [];
|
|
536
|
+
for (const entry of entries) {
|
|
537
|
+
const searchText = `${entry.title} ${entry.content} ${entry.tags.join(" ")}`.toLowerCase();
|
|
538
|
+
const matchedTerms = [];
|
|
539
|
+
let score = 0;
|
|
540
|
+
for (const term of normalizedTerms) {
|
|
541
|
+
if (searchText.includes(term)) {
|
|
542
|
+
matchedTerms.push(term);
|
|
543
|
+
// Title match scores higher
|
|
544
|
+
if (entry.title.toLowerCase().includes(term))
|
|
545
|
+
score += 3;
|
|
546
|
+
// Tag match scores high
|
|
547
|
+
else if (entry.tags.some(t => t.toLowerCase().includes(term)))
|
|
548
|
+
score += 2;
|
|
549
|
+
// Content match
|
|
550
|
+
else
|
|
551
|
+
score += 1;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// Boost gotcha/debugging entries
|
|
555
|
+
if (entry.tags.some(t => ["gotcha", "debugging", "bug", "fix", "issue"].includes(t))) {
|
|
556
|
+
score *= 1.5;
|
|
557
|
+
}
|
|
558
|
+
if (score > 0) {
|
|
559
|
+
scored.push({ entry, score, matchedTerms });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return scored.sort((a, b) => b.score - a.score);
|
|
563
|
+
}
|
|
564
|
+
/** Extract relevant terms from a task description */
|
|
565
|
+
export function extractTerms(description) {
|
|
566
|
+
const stopWords = new Set([
|
|
567
|
+
"a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
|
|
568
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "could",
|
|
569
|
+
"should", "may", "might", "shall", "can", "need", "must", "to", "of",
|
|
570
|
+
"in", "for", "on", "with", "at", "by", "from", "as", "into", "about",
|
|
571
|
+
"like", "through", "after", "before", "between", "under", "above",
|
|
572
|
+
"and", "but", "or", "not", "no", "so", "if", "then", "than", "too",
|
|
573
|
+
"very", "just", "also", "how", "what", "when", "where", "why", "which",
|
|
574
|
+
"that", "this", "these", "those", "it", "its", "i", "we", "you", "they",
|
|
575
|
+
"me", "us", "my", "our", "your", "add", "implement", "create", "build",
|
|
576
|
+
"make", "want", "get", "set", "use", "new", "update", "change",
|
|
577
|
+
]);
|
|
578
|
+
const words = description.toLowerCase()
|
|
579
|
+
.replace(/[^a-z0-9\s-]/g, " ")
|
|
580
|
+
.split(/\s+/)
|
|
581
|
+
.filter(w => w.length > 2 && !stopWords.has(w));
|
|
582
|
+
// Also extract multi-word phrases
|
|
583
|
+
const phrases = [];
|
|
584
|
+
const desc = description.toLowerCase();
|
|
585
|
+
const commonPhrases = [
|
|
586
|
+
"email verification", "password reset", "auth state", "user select",
|
|
587
|
+
"prisma select", "react context", "protected route", "refresh token",
|
|
588
|
+
"rate limit", "soft delete", "multi-tenant", "org scoping",
|
|
589
|
+
"file upload", "webhook", "cron job", "background job",
|
|
590
|
+
"api key", "role based", "permission", "middleware",
|
|
591
|
+
];
|
|
592
|
+
for (const phrase of commonPhrases) {
|
|
593
|
+
if (desc.includes(phrase))
|
|
594
|
+
phrases.push(phrase);
|
|
595
|
+
}
|
|
596
|
+
return [...new Set([...words, ...phrases])];
|
|
597
|
+
}
|
|
598
|
+
/** Proactive check — find gotchas relevant to a task before coding */
|
|
599
|
+
export function brainCheck(taskDescription) {
|
|
600
|
+
const terms = extractTerms(taskDescription);
|
|
601
|
+
return smartSearch(terms);
|
|
602
|
+
}
|
|
603
|
+
/** Diagnose an error — find matching patterns from past issues */
|
|
604
|
+
export function brainDiagnose(error) {
|
|
605
|
+
const terms = extractTerms(error);
|
|
606
|
+
// Add error-specific terms
|
|
607
|
+
const errorTerms = error.toLowerCase()
|
|
608
|
+
.replace(/[^a-z0-9\s]/g, " ")
|
|
609
|
+
.split(/\s+/)
|
|
610
|
+
.filter(w => w.length > 3);
|
|
611
|
+
const allTerms = [...new Set([...terms, ...errorTerms])];
|
|
612
|
+
return smartSearch(allTerms);
|
|
613
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prajwolkc/stk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "One CLI to deploy, monitor, debug, and learn about your entire stack. Infrastructure monitoring, knowledge base brain, deploy watching, and GitHub issues — all from one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|