@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.
@@ -1,15 +1,165 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
- import { writeFileSync, existsSync } from "fs";
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 for the current project")
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
- console.log(chalk.dim(` Deploy branch: ${config.deploy?.branch ?? "main"}`));
56
- console.log(chalk.dim(` Deploy providers: ${config.deploy?.providers?.join(", ") || "none"}`));
57
- console.log();
58
- console.log(chalk.dim(` Set your env vars, then run ${chalk.white("stk health")} to verify.`));
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 init
63
- const detected = {};
64
- const envChecks = {
65
- railway: ["RAILWAY_API_TOKEN"],
66
- vercel: ["VERCEL_TOKEN"],
67
- fly: ["FLY_API_TOKEN"],
68
- render: ["RENDER_API_KEY"],
69
- aws: ["AWS_ACCESS_KEY_ID"],
70
- database: ["DATABASE_URL"],
71
- mongodb: ["MONGODB_URL", "MONGO_URL"],
72
- redis: ["REDIS_URL"],
73
- supabase: ["SUPABASE_URL"],
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(" Detected services:"));
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(`${name} (not detected — enable in config)`);
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(` Edit ${CONFIG_FILE} to enable/disable services or add config.`));
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
+ }
@@ -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.", {
@@ -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 {};
@@ -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.5.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",