codeimpact 0.3.0 → 0.3.2

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/index.js CHANGED
@@ -8300,7 +8300,7 @@ function buildFeatureAgentDefinition(feature) {
8300
8300
  created_by: "code-impact"
8301
8301
  },
8302
8302
  scope: {
8303
- includedPaths: [...feature.paths, ...feature.testFiles],
8303
+ includedPaths: [...feature.paths, ...deriveTestGlobs(feature)],
8304
8304
  excludedPaths: feature.paths.map((p) => p.replace("/**", "/__mocks__/**"))
8305
8305
  },
8306
8306
  allowedTools: [
@@ -8473,6 +8473,25 @@ function renderAgentsShim(agents, config2) {
8473
8473
  }
8474
8474
  return lines.join("\n");
8475
8475
  }
8476
+ function deriveTestGlobs(feature) {
8477
+ if (feature.testFiles.length === 0) return [];
8478
+ const dirCounts = /* @__PURE__ */ new Map();
8479
+ for (const tf of feature.testFiles) {
8480
+ const parts = tf.split("/");
8481
+ const dir = parts.slice(0, -1).join("/");
8482
+ dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
8483
+ }
8484
+ const globs = [];
8485
+ for (const [dir, count] of dirCounts) {
8486
+ if (count >= 2) {
8487
+ globs.push(`${dir}/**`);
8488
+ } else {
8489
+ const file2 = feature.testFiles.find((f) => f.startsWith(dir + "/"));
8490
+ if (file2) globs.push(file2);
8491
+ }
8492
+ }
8493
+ return globs;
8494
+ }
8476
8495
  function parseAgentMd(content) {
8477
8496
  try {
8478
8497
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
@@ -10464,7 +10483,9 @@ function findSubPackageJsons(projectPath) {
10464
10483
  const entries = readdirSync10(dir, { withFileTypes: true });
10465
10484
  for (const entry of entries) {
10466
10485
  if (!entry.isDirectory()) continue;
10467
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
10486
+ const lower = entry.name.toLowerCase();
10487
+ if (lower === "node_modules" || lower === ".git" || lower === "dist") continue;
10488
+ if (EXCLUDED_FEATURE_DIRS.has(lower)) continue;
10468
10489
  const subDir = join30(dir, entry.name);
10469
10490
  const pkgJson = join30(subDir, "package.json");
10470
10491
  if (existsSync29(pkgJson) && subDir !== projectPath) {
@@ -10571,6 +10592,9 @@ function groupByDirectory(files, projectPath) {
10571
10592
  if (srcIdx >= 0 && parts.length > srcIdx + 2) {
10572
10593
  dirKey = parts.slice(0, srcIdx + 2).join("/");
10573
10594
  } else if (parts.length >= 2 && parts[0]) {
10595
+ const topDir = parts[0].toLowerCase();
10596
+ if (EXCLUDED_FEATURE_DIRS.has(topDir)) continue;
10597
+ if (!SOURCE_TOP_DIRS.has(topDir)) continue;
10574
10598
  dirKey = parts[0];
10575
10599
  } else {
10576
10600
  continue;
@@ -10672,15 +10696,33 @@ function findTestFiles(featurePaths, allFiles) {
10672
10696
  return testFiles;
10673
10697
  }
10674
10698
  function isUtilityDir(dirName) {
10675
- return UTILITY_DIRS.has(dirName.toLowerCase());
10699
+ const lower = dirName.toLowerCase();
10700
+ return UTILITY_DIRS.has(lower) || EXCLUDED_FEATURE_DIRS.has(lower);
10676
10701
  }
10677
10702
  function slugify6(name) {
10678
10703
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
10679
10704
  }
10680
- var UTILITY_DIRS;
10705
+ var SOURCE_TOP_DIRS, UTILITY_DIRS, EXCLUDED_FEATURE_DIRS;
10681
10706
  var init_feature_detector = __esm({
10682
10707
  "src/core/agents/feature-detector.ts"() {
10683
10708
  "use strict";
10709
+ SOURCE_TOP_DIRS = /* @__PURE__ */ new Set([
10710
+ "src",
10711
+ "app",
10712
+ "apps",
10713
+ "packages",
10714
+ "services",
10715
+ "modules",
10716
+ "test",
10717
+ "tests",
10718
+ "spec",
10719
+ "e2e",
10720
+ "api",
10721
+ "lib",
10722
+ "cmd",
10723
+ "internal",
10724
+ "pkg"
10725
+ ]);
10684
10726
  UTILITY_DIRS = /* @__PURE__ */ new Set([
10685
10727
  "lib",
10686
10728
  "libs",
@@ -10699,6 +10741,47 @@ var init_feature_detector = __esm({
10699
10741
  "vendor",
10700
10742
  "third-party"
10701
10743
  ]);
10744
+ EXCLUDED_FEATURE_DIRS = /* @__PURE__ */ new Set([
10745
+ // Runtime-generated / workspace
10746
+ "knowledge",
10747
+ ".code-impact",
10748
+ ".codeimpact",
10749
+ ".claude",
10750
+ ".cursor",
10751
+ // Documentation
10752
+ "doc",
10753
+ "docs",
10754
+ "documentation",
10755
+ // Build / output
10756
+ "dist",
10757
+ "build",
10758
+ "out",
10759
+ ".next",
10760
+ ".nuxt",
10761
+ ".output",
10762
+ // Dependencies
10763
+ "node_modules",
10764
+ ".yarn",
10765
+ ".pnpm-store",
10766
+ // CI / config
10767
+ ".github",
10768
+ ".gitlab",
10769
+ ".circleci",
10770
+ "scripts",
10771
+ ".husky",
10772
+ // Test fixtures (not features themselves)
10773
+ "fixtures",
10774
+ "base",
10775
+ "__fixtures__",
10776
+ "__snapshots__",
10777
+ // Data / migrations
10778
+ "migrations",
10779
+ "seeds",
10780
+ "data",
10781
+ // IDE / editor
10782
+ ".vscode",
10783
+ ".idea"
10784
+ ]);
10702
10785
  }
10703
10786
  });
10704
10787
 
@@ -11109,7 +11192,7 @@ var init_token_budget = __esm({
11109
11192
  });
11110
11193
 
11111
11194
  // src/core/agents/generator.ts
11112
- import { mkdirSync as mkdirSync17 } from "fs";
11195
+ import { existsSync as existsSync32, readFileSync as readFileSync26, mkdirSync as mkdirSync17 } from "fs";
11113
11196
  import { join as join33 } from "path";
11114
11197
  function generateProjectFiles(input) {
11115
11198
  const { projectPath, intelligence, technologies, features, index } = input;
@@ -11118,7 +11201,7 @@ function generateProjectFiles(input) {
11118
11201
  const result = { filesWritten: [], filesSkipped: [] };
11119
11202
  mkdirSync17(paths.projectDir, { recursive: true });
11120
11203
  const skillPath = join33(paths.projectDir, "SKILL.md");
11121
- writeMarkedFile2(skillPath, renderProjectSkill(intelligence, technologies, config2), config2, result, "project_skill");
11204
+ writeMarkedFile2(skillPath, renderProjectSkill(intelligence, technologies, config2, projectPath), config2, result, "project_skill");
11122
11205
  const convPath = join33(paths.projectDir, "CONVENTIONS.md");
11123
11206
  writeMarkedFile2(convPath, renderConventions(intelligence, config2), config2, result, "project_conventions");
11124
11207
  const archPath = join33(paths.projectDir, "ARCHITECTURE.md");
@@ -11139,7 +11222,7 @@ function generateFeatureFiles(input) {
11139
11222
  }
11140
11223
  return result;
11141
11224
  }
11142
- function renderProjectSkill(intel, technologies, config2) {
11225
+ function renderProjectSkill(intel, technologies, config2, projectPath) {
11143
11226
  const frontmatter = [
11144
11227
  "---",
11145
11228
  "name: project-overview",
@@ -11156,9 +11239,13 @@ function renderProjectSkill(intel, technologies, config2) {
11156
11239
  const lines = [];
11157
11240
  lines.push("## Tech Stack");
11158
11241
  if (technologies.length > 0) {
11159
- const topTechs = technologies.slice(0, 15);
11160
- for (const tech of topTechs) {
11161
- lines.push(`- ${tech.name} ${tech.version} (from ${tech.source})`);
11242
+ const directDeps = filterDirectDependencies(technologies, projectPath);
11243
+ if (directDeps.length > 0) {
11244
+ for (const tech of directDeps.slice(0, 20)) {
11245
+ lines.push(`- ${tech.name} ${tech.version}`);
11246
+ }
11247
+ } else {
11248
+ lines.push(`- Languages: ${intel.codebase.languages.join(", ")}`);
11162
11249
  }
11163
11250
  } else {
11164
11251
  lines.push(`- Languages: ${intel.codebase.languages.join(", ")}`);
@@ -11186,7 +11273,9 @@ function renderProjectSkill(intel, technologies, config2) {
11186
11273
  }
11187
11274
  } else {
11188
11275
  for (const dir of intel.codebase.keyDirectories) {
11189
- lines.push(`| ${dir}/ | Source code |`);
11276
+ const dirName = dir.split("/").pop() || dir;
11277
+ const purpose = inferDirPurpose(dirName);
11278
+ lines.push(`| ${dir}/ | ${purpose} |`);
11190
11279
  }
11191
11280
  }
11192
11281
  lines.push("");
@@ -11309,8 +11398,9 @@ function renderFeatureSkill(feature, technologies, config2) {
11309
11398
  lines.push(`- ${p}`);
11310
11399
  }
11311
11400
  if (feature.testFiles.length > 0) {
11312
- for (const tf of feature.testFiles) {
11313
- lines.push(`- ${tf}`);
11401
+ const testGlobs = summarizeTestFiles(feature.testFiles);
11402
+ for (const tg of testGlobs) {
11403
+ lines.push(`- ${tg}`);
11314
11404
  }
11315
11405
  }
11316
11406
  lines.push("");
@@ -11335,46 +11425,15 @@ function renderFeatureSkill(feature, technologies, config2) {
11335
11425
  lines.push("");
11336
11426
  }
11337
11427
  lines.push("## Rules");
11338
- if (technologies.length > 0) {
11339
- for (const tech of technologies) {
11340
- if (tech.name === "express") {
11341
- lines.push("- Use Router() for modular route definitions");
11342
- lines.push("- Always register error handler middleware last");
11343
- } else if (tech.name === "better-sqlite3") {
11344
- lines.push("- Use db.prepare().all() for SELECT, db.prepare().run() for INSERT/UPDATE/DELETE");
11345
- lines.push("- better-sqlite3 is synchronous \u2014 do NOT use async/await");
11346
- } else if (tech.name === "stripe") {
11347
- lines.push("- Always verify webhook signatures before processing events");
11348
- lines.push("- Use idempotency keys for payment creation");
11349
- } else if (tech.name === "jsonwebtoken") {
11350
- lines.push("- Always verify tokens before trusting decoded data");
11351
- lines.push("- Set explicit expiration times on all tokens");
11352
- } else {
11353
- lines.push(`- Follow ${tech.name} v${tech.version} API conventions`);
11354
- }
11355
- }
11356
- } else {
11357
- lines.push("- Follow project coding conventions for this feature");
11428
+ const rules = deriveRules(feature, technologies);
11429
+ for (const rule of rules) {
11430
+ lines.push(`- ${rule}`);
11358
11431
  }
11359
11432
  lines.push("");
11360
11433
  lines.push("## Pitfalls");
11361
- if (technologies.length > 0) {
11362
- for (const tech of technologies) {
11363
- if (tech.name === "express") {
11364
- lines.push("- Forgetting to call next() in middleware causes request to hang");
11365
- } else if (tech.name === "better-sqlite3") {
11366
- lines.push("- db.exec() returns nothing \u2014 using it for SELECT gives undefined");
11367
- } else if (tech.name === "stripe") {
11368
- lines.push("- Stripe webhook events may arrive out of order \u2014 handle idempotently");
11369
- } else if (tech.name === "jsonwebtoken") {
11370
- lines.push("- Using jwt.decode() without verify() is a security vulnerability");
11371
- }
11372
- }
11373
- if (technologies.every((t) => !["express", "better-sqlite3", "stripe", "jsonwebtoken"].includes(t.name))) {
11374
- lines.push("- Check research docs for version-specific breaking changes");
11375
- }
11376
- } else {
11377
- lines.push("- Check for null/undefined before property access on external data");
11434
+ const pitfalls = derivePitfalls(feature, technologies);
11435
+ for (const pitfall of pitfalls) {
11436
+ lines.push(`- ${pitfall}`);
11378
11437
  }
11379
11438
  lines.push("");
11380
11439
  return { frontmatter, autoContent: lines.join("\n") };
@@ -11424,15 +11483,348 @@ function parseAutoContentSections(autoContent) {
11424
11483
  }
11425
11484
  return sections;
11426
11485
  }
11486
+ function summarizeTestFiles(testFiles) {
11487
+ if (testFiles.length === 0) return [];
11488
+ const dirCounts = /* @__PURE__ */ new Map();
11489
+ for (const tf of testFiles) {
11490
+ const parts = tf.split("/");
11491
+ const dir = parts.slice(0, -1).join("/");
11492
+ dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
11493
+ }
11494
+ const globs = [];
11495
+ for (const [dir, count] of dirCounts) {
11496
+ if (count >= 2) {
11497
+ globs.push(`${dir}/**`);
11498
+ } else {
11499
+ const file2 = testFiles.find((f) => f.startsWith(dir + "/"));
11500
+ if (file2) globs.push(file2);
11501
+ }
11502
+ }
11503
+ return globs;
11504
+ }
11505
+ function inferDirPurpose(dirName) {
11506
+ const purposes = {
11507
+ core: "Core business logic and domain modules",
11508
+ server: "Server, transports, and request handling",
11509
+ storage: "Database access and persistence",
11510
+ indexing: "Code indexing and symbol extraction",
11511
+ api: "API routes and handlers",
11512
+ auth: "Authentication and authorization",
11513
+ billing: "Payment processing",
11514
+ test: "Tests and evaluation harness",
11515
+ doc: "Documentation",
11516
+ knowledge: "AI knowledge system",
11517
+ cli: "Command-line interface",
11518
+ base: "Base configuration and fixtures",
11519
+ src: "Application source code",
11520
+ lib: "Shared utilities"
11521
+ };
11522
+ return purposes[dirName.toLowerCase()] || `${dirName} module`;
11523
+ }
11524
+ function deriveRules(feature, technologies) {
11525
+ const rules = [];
11526
+ if (WELL_KNOWN_FEATURES.has(feature.name)) {
11527
+ rules.push(...getFeatureNameRules(feature.name));
11528
+ const techRules = [];
11529
+ for (const tech of technologies) {
11530
+ const knowledge = TECH_KNOWLEDGE[tech.name];
11531
+ if (knowledge) techRules.push(...knowledge.rules);
11532
+ }
11533
+ for (const tr of techRules.slice(0, 2)) {
11534
+ if (!rules.some((r) => r.includes(tr.split(" ")[0] || ""))) {
11535
+ rules.push(tr);
11536
+ }
11537
+ }
11538
+ } else {
11539
+ for (const tech of technologies) {
11540
+ const knowledge = TECH_KNOWLEDGE[tech.name];
11541
+ if (knowledge) rules.push(...knowledge.rules);
11542
+ }
11543
+ if (rules.length === 0) {
11544
+ rules.push(...getFeatureNameRules(feature.name));
11545
+ }
11546
+ }
11547
+ if (feature.paths.length > 0) {
11548
+ const scope = feature.paths[0]?.replace("/**", "") || "";
11549
+ rules.push(`Changes here should not import from other feature directories \u2014 keep ${scope} self-contained`);
11550
+ }
11551
+ return rules;
11552
+ }
11553
+ function derivePitfalls(feature, technologies) {
11554
+ const pitfalls = [];
11555
+ if (WELL_KNOWN_FEATURES.has(feature.name)) {
11556
+ pitfalls.push(...getFeatureNamePitfalls(feature.name));
11557
+ for (const tech of technologies) {
11558
+ const knowledge = TECH_KNOWLEDGE[tech.name];
11559
+ if (knowledge) {
11560
+ for (const p of knowledge.pitfalls.slice(0, 1)) {
11561
+ if (!pitfalls.includes(p)) pitfalls.push(p);
11562
+ }
11563
+ }
11564
+ }
11565
+ } else {
11566
+ for (const tech of technologies) {
11567
+ const knowledge = TECH_KNOWLEDGE[tech.name];
11568
+ if (knowledge) pitfalls.push(...knowledge.pitfalls);
11569
+ }
11570
+ if (pitfalls.length === 0) {
11571
+ pitfalls.push(...getFeatureNamePitfalls(feature.name));
11572
+ }
11573
+ }
11574
+ return pitfalls;
11575
+ }
11576
+ function getFeatureNameRules(name) {
11577
+ const rules = {
11578
+ server: [
11579
+ "All tool/resource handlers must validate inputs before processing",
11580
+ "Return structured error responses \u2014 never throw raw errors to clients"
11581
+ ],
11582
+ storage: [
11583
+ "All database access should go through this module \u2014 no direct imports elsewhere",
11584
+ "Use transactions for multi-step operations to ensure consistency"
11585
+ ],
11586
+ indexing: [
11587
+ "Index operations should be idempotent \u2014 reindexing same file produces same result",
11588
+ "Handle parse errors gracefully \u2014 a broken file should not crash the indexer"
11589
+ ],
11590
+ core: [
11591
+ "Core modules should have no side effects on import",
11592
+ "Export types alongside functions for downstream consumers"
11593
+ ],
11594
+ test: [
11595
+ "Tests should be deterministic \u2014 no reliance on external services or timing",
11596
+ "Clean up temporary files and directories in afterEach/afterAll hooks"
11597
+ ],
11598
+ knowledge: [
11599
+ "Skill files must follow the agentskills.io SKILL.md format",
11600
+ "Generated content must use marker comments to preserve manual edits"
11601
+ ],
11602
+ doc: [
11603
+ "Generated docs must be kept in sync with source code changes",
11604
+ "Use relative paths for internal links"
11605
+ ],
11606
+ auth: [
11607
+ "Never store plain-text passwords \u2014 use bcrypt or argon2",
11608
+ "Validate and sanitize all user inputs before processing"
11609
+ ],
11610
+ api: [
11611
+ "All endpoints must validate request bodies/params before processing",
11612
+ "Return consistent error response shapes across all endpoints"
11613
+ ]
11614
+ };
11615
+ return rules[name] || [
11616
+ "Follow existing patterns in this module for consistency",
11617
+ "Export public API from index.ts \u2014 keep internal helpers unexported"
11618
+ ];
11619
+ }
11620
+ function getFeatureNamePitfalls(name) {
11621
+ const pitfalls = {
11622
+ server: [
11623
+ "Unhandled promise rejections in handlers crash the process \u2014 always catch async errors",
11624
+ "Adding middleware order matters \u2014 auth before route handlers"
11625
+ ],
11626
+ storage: [
11627
+ "Forgetting to close database connections leaks file handles",
11628
+ "Concurrent writes without transactions may cause data corruption"
11629
+ ],
11630
+ indexing: [
11631
+ "Large files can cause out-of-memory \u2014 set size limits on parsed content",
11632
+ "File paths must be normalized (forward slashes) for cross-platform compatibility"
11633
+ ],
11634
+ core: [
11635
+ "Circular imports between core modules cause runtime errors \u2014 check dependency direction",
11636
+ "Changing public API signatures breaks downstream consumers"
11637
+ ],
11638
+ test: [
11639
+ "Tests sharing mutable state between runs cause flaky failures",
11640
+ "Temp directories not cleaned up fill disk over repeated test runs"
11641
+ ]
11642
+ };
11643
+ return pitfalls[name] || [
11644
+ "Check for null/undefined before property access on external data",
11645
+ "Changing exports may break other modules that depend on this feature"
11646
+ ];
11647
+ }
11427
11648
  function capitalize(str) {
11428
11649
  return str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
11429
11650
  }
11651
+ function filterDirectDependencies(technologies, projectPath) {
11652
+ const directNames = /* @__PURE__ */ new Set();
11653
+ for (const tech of technologies) {
11654
+ if (tech.source === "package.json") {
11655
+ directNames.add(tech.name);
11656
+ }
11657
+ }
11658
+ if (directNames.size === 0) {
11659
+ const searchPaths = [
11660
+ projectPath ? join33(projectPath, "package.json") : "",
11661
+ join33(process.cwd(), "package.json")
11662
+ ].filter(Boolean);
11663
+ for (const p of searchPaths) {
11664
+ try {
11665
+ if (existsSync32(p)) {
11666
+ const pkg = JSON.parse(readFileSync26(p, "utf-8"));
11667
+ for (const d of Object.keys(pkg.dependencies || {})) directNames.add(d);
11668
+ for (const d of Object.keys(pkg.devDependencies || {})) directNames.add(d);
11669
+ break;
11670
+ }
11671
+ } catch {
11672
+ }
11673
+ }
11674
+ }
11675
+ const skipPatterns = [
11676
+ /^@types\//,
11677
+ /^@esbuild\//,
11678
+ /^@rollup\//,
11679
+ /^@swc\//,
11680
+ /^@parcel\//,
11681
+ /^@biomejs\//
11682
+ ];
11683
+ if (directNames.size > 0) {
11684
+ return technologies.filter((t) => directNames.has(t.name)).filter((t) => !skipPatterns.some((p) => p.test(t.name)));
11685
+ }
11686
+ return technologies.filter((t) => !skipPatterns.some((p) => p.test(t.name))).slice(0, 20);
11687
+ }
11688
+ var TECH_KNOWLEDGE, WELL_KNOWN_FEATURES;
11430
11689
  var init_generator = __esm({
11431
11690
  "src/core/agents/generator.ts"() {
11432
11691
  "use strict";
11433
11692
  init_workspace2();
11434
11693
  init_marker_writer();
11435
11694
  init_token_budget();
11695
+ TECH_KNOWLEDGE = {
11696
+ "express": {
11697
+ rules: [
11698
+ "Use Router() for modular route definitions",
11699
+ "Always register error handler middleware last (4 args: err, req, res, next)"
11700
+ ],
11701
+ pitfalls: [
11702
+ "Forgetting to call next() in middleware causes request to hang",
11703
+ "Async errors in handlers need explicit try/catch or express-async-errors"
11704
+ ]
11705
+ },
11706
+ "hono": {
11707
+ rules: [
11708
+ "Use app.route() for modular route grouping",
11709
+ "Return c.json() or c.text() \u2014 do not use res.send()"
11710
+ ],
11711
+ pitfalls: [
11712
+ "Hono middleware must call next() or return a Response \u2014 skipping both hangs the request"
11713
+ ]
11714
+ },
11715
+ "better-sqlite3": {
11716
+ rules: [
11717
+ "Use db.prepare().all() for SELECT, db.prepare().run() for INSERT/UPDATE/DELETE",
11718
+ "better-sqlite3 is synchronous \u2014 do NOT use async/await with db calls",
11719
+ "All database access should go through a single module"
11720
+ ],
11721
+ pitfalls: [
11722
+ "db.exec() returns nothing \u2014 using it for SELECT gives undefined",
11723
+ 'WAL mode must be set once after opening: db.pragma("journal_mode = WAL")'
11724
+ ]
11725
+ },
11726
+ "stripe": {
11727
+ rules: [
11728
+ "Always verify webhook signatures before processing events",
11729
+ "Use idempotency keys for payment creation to prevent double-charges"
11730
+ ],
11731
+ pitfalls: [
11732
+ "Stripe webhook events may arrive out of order \u2014 handle idempotently",
11733
+ "stripe.webhooks.constructEvent() throws on invalid signature \u2014 wrap in try/catch"
11734
+ ]
11735
+ },
11736
+ "jsonwebtoken": {
11737
+ rules: [
11738
+ "Always use jwt.verify() \u2014 never trust jwt.decode() alone",
11739
+ "Set explicit expiration (expiresIn) on all tokens"
11740
+ ],
11741
+ pitfalls: [
11742
+ "Using jwt.decode() without verify() is a security vulnerability",
11743
+ 'The "none" algorithm attack \u2014 always specify algorithms: ["HS256"]'
11744
+ ]
11745
+ },
11746
+ "@modelcontextprotocol/sdk": {
11747
+ rules: [
11748
+ "Use server.tool() to register MCP tools with zod schema validation",
11749
+ "All tool handlers must return { content: [...] } response objects"
11750
+ ],
11751
+ pitfalls: [
11752
+ "Tool names must be unique across the server \u2014 duplicates silently overwrite",
11753
+ "Forgetting to call server.connect(transport) means no requests are handled"
11754
+ ]
11755
+ },
11756
+ "prisma": {
11757
+ rules: [
11758
+ "Run npx prisma generate after schema changes",
11759
+ "Use transactions for multi-table operations: prisma.$transaction()"
11760
+ ],
11761
+ pitfalls: [
11762
+ "Forgetting prisma generate after schema change causes type mismatches at runtime",
11763
+ "N+1 queries \u2014 use include/select to eagerly load relations"
11764
+ ]
11765
+ },
11766
+ "zod": {
11767
+ rules: [
11768
+ "Define schemas once, derive TypeScript types with z.infer<typeof schema>",
11769
+ "Use .safeParse() in handlers for graceful error handling"
11770
+ ],
11771
+ pitfalls: [
11772
+ ".parse() throws on invalid data \u2014 use .safeParse() in request handlers"
11773
+ ]
11774
+ },
11775
+ "vitest": {
11776
+ rules: [
11777
+ "Use describe/it/expect patterns for test organization",
11778
+ "Use vi.mock() for module mocking, vi.fn() for function stubs"
11779
+ ],
11780
+ pitfalls: [
11781
+ "vi.mock() is hoisted to top of file \u2014 cannot access variables from outer scope"
11782
+ ]
11783
+ },
11784
+ "web-tree-sitter": {
11785
+ rules: [
11786
+ "Initialize Parser with await Parser.init() before use",
11787
+ "Load language WASM files with Parser.Language.load()"
11788
+ ],
11789
+ pitfalls: [
11790
+ "Parser.init() must complete before any parsing \u2014 race condition if not awaited",
11791
+ "WASM files must be bundled/copied to dist \u2014 not resolved from node_modules at runtime"
11792
+ ]
11793
+ },
11794
+ "@xenova/transformers": {
11795
+ rules: [
11796
+ "Use pipeline() for high-level tasks, AutoModel for custom inference",
11797
+ "Cache downloaded models by setting env.cacheDir"
11798
+ ],
11799
+ pitfalls: [
11800
+ "First model load downloads weights (~100MB+) \u2014 cache for subsequent runs",
11801
+ "ONNX runtime may fail on some architectures \u2014 test on target platform"
11802
+ ]
11803
+ },
11804
+ "chokidar": {
11805
+ rules: [
11806
+ "Use chokidar.watch() with ignored patterns to skip node_modules",
11807
+ 'Handle both "add" and "change" events for file watching'
11808
+ ],
11809
+ pitfalls: [
11810
+ "Not closing watcher on process exit leaks file handles",
11811
+ "Rapid file changes may fire multiple events \u2014 debounce handlers"
11812
+ ]
11813
+ }
11814
+ };
11815
+ WELL_KNOWN_FEATURES = /* @__PURE__ */ new Set([
11816
+ "server",
11817
+ "storage",
11818
+ "indexing",
11819
+ "core",
11820
+ "test",
11821
+ "knowledge",
11822
+ "doc",
11823
+ "auth",
11824
+ "api",
11825
+ "billing",
11826
+ "cli"
11827
+ ]);
11436
11828
  }
11437
11829
  });
11438
11830
 
@@ -11842,7 +12234,7 @@ var init_lifecycle = __esm({
11842
12234
  });
11843
12235
 
11844
12236
  // src/core/agents/orchestrator.ts
11845
- import { existsSync as existsSync34, readdirSync as readdirSync12 } from "fs";
12237
+ import { existsSync as existsSync34, readFileSync as readFileSync27, readdirSync as readdirSync12 } from "fs";
11846
12238
  import { join as join35, relative as relative9 } from "path";
11847
12239
  function agentsInit(projectPath) {
11848
12240
  const paths = initAgentWorkspace(projectPath);
@@ -11877,11 +12269,13 @@ async function agentsGenerate(options) {
11877
12269
  result.technologies = technologies.length;
11878
12270
  const importGraph = options.importGraph || /* @__PURE__ */ new Map();
11879
12271
  const indexedFiles = options.indexedFiles || getIndexedFilesFromFS(projectPath);
12272
+ const fileTechMap = buildFileTechMap(projectPath, indexedFiles, technologies);
11880
12273
  const featureInput = {
11881
12274
  projectPath,
11882
12275
  config: config2.feature_detection,
11883
12276
  importGraph,
11884
- indexedFiles
12277
+ indexedFiles,
12278
+ fileTechMap
11885
12279
  };
11886
12280
  const features = detectFeatures(featureInput);
11887
12281
  result.features = features.length;
@@ -12099,30 +12493,57 @@ function filterTopTechnologies(technologies) {
12099
12493
  return technologies.filter((t) => !skipPatterns.some((p) => p.test(t.name))).filter((t) => t.source.includes("lock") || t.source === "package.json").slice(0, 20);
12100
12494
  }
12101
12495
  function createMinimalIntelligence(projectPath, technologies, features) {
12496
+ const sourceFiles = getIndexedFilesFromFS(projectPath);
12497
+ const codeFiles = sourceFiles.filter(
12498
+ (f) => /\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|cs|cpp|c|h)$/.test(f) && !f.includes("node_modules") && !f.includes("dist")
12499
+ );
12500
+ let totalLines = 0;
12501
+ for (const f of codeFiles.slice(0, 500)) {
12502
+ try {
12503
+ const content = readFileSync27(join35(projectPath, f), "utf-8");
12504
+ totalLines += content.split("\n").length;
12505
+ } catch {
12506
+ }
12507
+ }
12508
+ const extCounts = /* @__PURE__ */ new Map();
12509
+ for (const f of codeFiles) {
12510
+ const ext = f.split(".").pop() || "";
12511
+ const lang = extToLanguage(ext);
12512
+ if (lang) extCounts.set(lang, (extCounts.get(lang) || 0) + 1);
12513
+ }
12514
+ const languages = [...extCounts.entries()].sort((a, b) => b[1] - a[1]).map(([lang]) => lang);
12515
+ const layers = features.map((f) => ({
12516
+ name: f.name.charAt(0).toUpperCase() + f.name.slice(1),
12517
+ directory: f.paths[0]?.replace("/**", "") || f.name,
12518
+ purpose: inferLayerPurpose(f.name),
12519
+ fileCount: f.fileCount
12520
+ }));
12521
+ const testFramework = detectTestFramework(projectPath);
12102
12522
  return {
12103
12523
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
12104
12524
  codebase: {
12105
- fileCount: 0,
12106
- totalLines: 0,
12107
- languages: [...new Set(technologies.map((t) => {
12108
- if (t.source.includes("package")) return "typescript";
12109
- if (t.source.includes("Cargo")) return "rust";
12110
- if (t.source.includes("go.")) return "go";
12111
- if (t.source.includes("requirements") || t.source.includes("pyproject")) return "python";
12112
- return "unknown";
12113
- }))],
12525
+ fileCount: codeFiles.length,
12526
+ totalLines,
12527
+ languages: languages.length > 0 ? languages : ["typescript"],
12114
12528
  keyDirectories: features.map((f) => f.paths[0]?.replace("/**", "") || ""),
12115
12529
  symbolCount: 0,
12116
12530
  description: "",
12117
12531
  architectureNotes: ""
12118
12532
  },
12119
- architecture: null,
12533
+ architecture: {
12534
+ layers,
12535
+ dataFlow: inferDataFlow(features),
12536
+ keyComponents: [],
12537
+ patternCategories: {},
12538
+ topPatterns: [],
12539
+ functionStats: { total: 0, exported: 0 }
12540
+ },
12120
12541
  dependencyHotspots: [],
12121
12542
  patterns: [],
12122
12543
  decisions: [],
12123
12544
  riskFiles: [],
12124
12545
  deadCode: null,
12125
- tests: { framework: "unknown", testCount: 0, coverageGaps: [], uncoveredFunctions: [] },
12546
+ tests: { framework: testFramework, testCount: 0, coverageGaps: [], uncoveredFunctions: [] },
12126
12547
  recentBugs: [],
12127
12548
  changeHotspots: [],
12128
12549
  activeFeature: null,
@@ -12134,6 +12555,96 @@ function createMinimalIntelligence(projectPath, technologies, features) {
12134
12555
  }))
12135
12556
  };
12136
12557
  }
12558
+ function buildFileTechMap(projectPath, indexedFiles, technologies) {
12559
+ const techNames = new Set(technologies.map((t) => t.name));
12560
+ const fileTechMap = /* @__PURE__ */ new Map();
12561
+ const sourceFiles = indexedFiles.filter(
12562
+ (f) => /\.(ts|tsx|js|jsx)$/.test(f) && !f.includes("node_modules") && !f.includes("dist")
12563
+ );
12564
+ for (const file2 of sourceFiles.slice(0, 300)) {
12565
+ try {
12566
+ const content = readFileSync27(join35(projectPath, file2), "utf-8");
12567
+ const techs = /* @__PURE__ */ new Set();
12568
+ const importRegex = /(?:from\s+['"]|require\s*\(\s*['"])([^./'"@][^'"]*|@[^/'"]+\/[^'"]+)/g;
12569
+ let match;
12570
+ while ((match = importRegex.exec(content)) !== null) {
12571
+ const pkg = match[1] || "";
12572
+ const normalized = pkg.startsWith("@") ? pkg.split("/").slice(0, 2).join("/") : pkg.split("/")[0] || "";
12573
+ if (normalized && techNames.has(normalized)) {
12574
+ techs.add(normalized);
12575
+ }
12576
+ }
12577
+ if (techs.size > 0) {
12578
+ fileTechMap.set(file2, [...techs]);
12579
+ }
12580
+ } catch {
12581
+ }
12582
+ }
12583
+ return fileTechMap;
12584
+ }
12585
+ function extToLanguage(ext) {
12586
+ const map2 = {
12587
+ ts: "typescript",
12588
+ tsx: "typescript",
12589
+ js: "javascript",
12590
+ jsx: "javascript",
12591
+ py: "python",
12592
+ go: "go",
12593
+ rs: "rust",
12594
+ java: "java",
12595
+ rb: "ruby",
12596
+ php: "php",
12597
+ cs: "c#",
12598
+ cpp: "c++",
12599
+ c: "c",
12600
+ h: "c"
12601
+ };
12602
+ return map2[ext] || null;
12603
+ }
12604
+ function inferLayerPurpose(featureName) {
12605
+ const purposes = {
12606
+ core: "Core business logic and domain modules",
12607
+ server: "MCP server, transports, and request handling",
12608
+ storage: "Database access and persistence layer",
12609
+ indexing: "Code indexing, parsing, and symbol extraction",
12610
+ api: "API routes, handlers, and middleware",
12611
+ auth: "Authentication and authorization",
12612
+ billing: "Payment processing and subscription management",
12613
+ test: "Test fixtures, harness, and evaluation scenarios",
12614
+ doc: "Documentation generation and management",
12615
+ knowledge: "AI knowledge system and skill management",
12616
+ cli: "Command-line interface",
12617
+ base: "Base configuration and shared utilities"
12618
+ };
12619
+ return purposes[featureName] || `${featureName} module`;
12620
+ }
12621
+ function inferDataFlow(features) {
12622
+ const names = features.map((f) => f.name);
12623
+ const flow = [];
12624
+ if (names.includes("server") || names.includes("api")) flow.push("Request");
12625
+ if (names.includes("server")) flow.push("Server");
12626
+ if (names.includes("api")) flow.push("API");
12627
+ if (names.includes("core")) flow.push("Core");
12628
+ if (names.includes("storage")) flow.push("Storage");
12629
+ if (names.includes("indexing")) flow.push("Indexer");
12630
+ return flow.length >= 2 ? flow : [];
12631
+ }
12632
+ function detectTestFramework(projectPath) {
12633
+ const pkgPath = join35(projectPath, "package.json");
12634
+ if (existsSync34(pkgPath)) {
12635
+ try {
12636
+ const pkg = JSON.parse(readFileSync27(pkgPath, "utf-8"));
12637
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
12638
+ if (allDeps["vitest"]) return "vitest";
12639
+ if (allDeps["jest"]) return "jest";
12640
+ if (allDeps["mocha"]) return "mocha";
12641
+ if (allDeps["tap"]) return "tap";
12642
+ if (pkg.scripts?.test?.includes("--test")) return "node:test";
12643
+ } catch {
12644
+ }
12645
+ }
12646
+ return "unknown";
12647
+ }
12137
12648
  var init_orchestrator = __esm({
12138
12649
  "src/core/agents/orchestrator.ts"() {
12139
12650
  "use strict";
@@ -12153,7 +12664,7 @@ var init_orchestrator = __esm({
12153
12664
 
12154
12665
  // src/core/agents/migration.ts
12155
12666
  import { existsSync as existsSync35, readFileSync as readFileSync28, writeFileSync as writeFileSync20, mkdirSync as mkdirSync18, cpSync, readdirSync as readdirSync13 } from "fs";
12156
- import { join as join36, relative as relative10, dirname as dirname14 } from "path";
12667
+ import { join as join36, relative as relative10, dirname as dirname15 } from "path";
12157
12668
  function migrateKnowledge(options) {
12158
12669
  const { projectPath, dryRun = false, backup = false } = options;
12159
12670
  const result = {
@@ -12195,7 +12706,7 @@ function migrateKnowledge(options) {
12195
12706
  continue;
12196
12707
  }
12197
12708
  if (!dryRun) {
12198
- mkdirSync18(dirname14(targetPath), { recursive: true });
12709
+ mkdirSync18(dirname15(targetPath), { recursive: true });
12199
12710
  cpSync(skillFile, targetPath);
12200
12711
  }
12201
12712
  result.migrated.push({
@@ -42751,22 +43262,32 @@ ${END_MARKER}`;
42751
43262
  return { content: `${block}
42752
43263
  `, changed: true };
42753
43264
  }
42754
- const start = existing.indexOf(START_MARKER);
42755
- const end = existing.indexOf(END_MARKER);
42756
- if (start >= 0 && end > start) {
42757
- const before = existing.slice(0, start).trimEnd();
42758
- const after = existing.slice(end + END_MARKER.length).trimStart();
42759
- const content2 = `${before}
42760
-
42761
- ${block}
42762
-
42763
- ${after}`.trim() + "\n";
42764
- return { content: content2, changed: content2 !== existing };
43265
+ let cleaned = existing;
43266
+ let hadMarkers = false;
43267
+ while (true) {
43268
+ const start = cleaned.indexOf(START_MARKER);
43269
+ const end = cleaned.indexOf(END_MARKER, start >= 0 ? start : 0);
43270
+ if (start >= 0 && end > start) {
43271
+ hadMarkers = true;
43272
+ cleaned = cleaned.slice(0, start) + cleaned.slice(end + END_MARKER.length);
43273
+ } else if (end >= 0 && start < 0) {
43274
+ hadMarkers = true;
43275
+ cleaned = cleaned.slice(0, end) + cleaned.slice(end + END_MARKER.length);
43276
+ } else {
43277
+ break;
43278
+ }
42765
43279
  }
42766
- const content = `${existing.trimEnd()}
43280
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
43281
+ let content;
43282
+ if (!cleaned) {
43283
+ content = `${block}
43284
+ `;
43285
+ } else {
43286
+ content = `${cleaned}
42767
43287
 
42768
43288
  ${block}
42769
43289
  `;
43290
+ }
42770
43291
  return { content, changed: content !== existing };
42771
43292
  }
42772
43293
  function writeManagedFile(targetPath, section, options) {
@@ -42799,93 +43320,61 @@ function renderPlatformSection(platform, paths, skillIndex, evolutionGuidance) {
42799
43320
  let attentionSection = "";
42800
43321
  if (evolutionGuidance && evolutionGuidance.length > 0) {
42801
43322
  attentionSection = `
42802
-
42803
43323
  ### Skills Needing Attention
42804
43324
  ${evolutionGuidance.map((g) => `- ${g}`).join("\n")}`;
42805
43325
  }
42806
- return `# CodeImpact Knowledge System
43326
+ return `# CodeImpact \u2014 AI-Powered Codebase Intelligence
43327
+
43328
+ You have access to **CodeImpact**, a persistent knowledge system that understands this codebase. It provides project skills, architecture knowledge, and self-learning agents.
42807
43329
 
42808
- You are part of a **self-improving knowledge system**. Skills you create persist across sessions. Future AI sessions benefit from the knowledge you build now.
43330
+ ## IMPORTANT: Session Start
43331
+
43332
+ 1. **Read project knowledge** before making changes:
43333
+ - \`.code-impact/project/SKILL.md\` \u2014 Tech stack, architecture, key directories
43334
+ - \`.code-impact/project/CONVENTIONS.md\` \u2014 Coding standards and patterns
43335
+ - \`.code-impact/project/ARCHITECTURE.md\` \u2014 System layers and data flow
43336
+ 2. **Read feature knowledge** for the files you're modifying:
43337
+ - \`.code-impact/features/{feature}/SKILL.md\` \u2014 Feature-specific rules, pitfalls, and research refs
43338
+ - Features: check \`.code-impact/features/\` for available feature directories
43339
+ 3. Run \`${tool("memory_status")}\` for project overview and recent changes
43340
+
43341
+ ## MCP Tools (Use FIRST before built-in tools)
42809
43342
 
42810
- ## Tools
42811
43343
  | Task | Tool |
42812
43344
  |------|------|
42813
- | Find code | \`${tool("memory_query")}\` |
42814
- | Check code | \`${tool("memory_review")}\` |
42815
- | Verify code | \`${tool("memory_verify")}\` |
43345
+ | Search code semantically | \`${tool("memory_query")}\` |
43346
+ | Review code before changes | \`${tool("memory_review")}\` |
43347
+ | Verify before committing | \`${tool("memory_verify")}\` |
42816
43348
  | Project status | \`${tool("memory_status")}\` |
42817
43349
  | Impact analysis | \`${tool("memory_blast_radius")}\` |
43350
+ | Agent system | \`${tool("memory_agents")}\` |
42818
43351
  | Build knowledge | \`${tool("memory_evolve")}\` |
42819
43352
 
42820
- ## Session Start
42821
- 1. Run \`${tool("memory_status")}\` \u2014 check \`knowledge_gaps\` for uncovered technologies and high-risk files.
42822
- 2. Read relevant skills from \`knowledge/skills/\` for the current task.
42823
-
42824
- ## Skill Creation Protocol
43353
+ ## Agent System
42825
43354
 
42826
- **After completing any task involving 3+ files**, create or improve a skill.
43355
+ This project has feature-level agents in \`.code-impact/features/\`. Each agent has:
43356
+ - **SKILL.md** \u2014 Rules, pitfalls, and technology-specific guidance
43357
+ - **AGENT.md** \u2014 Scope, allowed tools, and lessons learned from past mistakes
42827
43358
 
42828
- ### To create a new skill:
42829
- \`${tool("memory_evolve")}\` with:
42830
- - action="create_skill"
42831
- - name="technology-or-area-name" (slug format)
42832
- - description="One line: when to use this skill"
42833
- - scope="technology|feature|risk|core"
42834
- - content="Full markdown body (see format below)"
43359
+ Before modifying files, check the relevant feature's SKILL.md for rules and pitfalls.
43360
+ After completing a task, record the outcome via \`${tool("memory_agents")}\` with action="record_outcome".
42835
43361
 
42836
- ### To improve an existing skill:
42837
- \`${tool("memory_evolve")}\` with:
42838
- - action="improve_skill", skill_id="skill-name"
42839
- - Patch mode: old_text="exact text to replace", new_text="replacement"
42840
- - Append mode: section="pitfalls", content="New pitfall to add"
43362
+ ## Skill Management
42841
43363
 
42842
- ### To discover gaps:
42843
- \`${tool("memory_evolve")}\` with action="list_signals"
43364
+ After completing any task involving 3+ files, create or improve a skill:
42844
43365
 
42845
- ### Skill Format (agentskills.io SKILL.md)
42846
-
42847
- \`\`\`markdown
42848
- ---
42849
- name: better-sqlite3-patterns
42850
- description: Synchronous database patterns. Use when working with database queries or schema changes.
42851
- version: 1.0
42852
- metadata:
42853
- scope: technology
42854
- created_by: ai
42855
- ---
42856
-
42857
- # better-sqlite3 Patterns
42858
-
42859
- ## When to Use
42860
- When modifying database queries, adding tables, or working with files that import from src/storage/database.ts.
42861
-
42862
- ## Key Facts
42863
- - Database: .codeimpact/codeimpact.db (SQLite, WAL mode)
42864
- - API: better-sqlite3 (synchronous, NOT async)
42865
-
42866
- ## Rules
42867
- - ALL database access goes through database.ts \u2014 never import better-sqlite3 directly.
42868
- - Use db.prepare().all() for SELECT, db.prepare().run() for INSERT/UPDATE/DELETE.
42869
-
42870
- ## Pitfalls
42871
- - db.exec() returns nothing. If you use it for SELECT, you get undefined.
42872
- - better-sqlite3 is synchronous. Do NOT wrap in async/await.
42873
-
42874
- ## Verification
42875
- - npx tsc --noEmit passes with no type errors on database code.
42876
- \`\`\`
43366
+ \`${tool("memory_evolve")}\` with action="create_skill" (new) or action="improve_skill" (update)
42877
43367
 
42878
43368
  ### Quality Rules
42879
- - **Under 5000 tokens** per skill
42880
- - **Be specific**: "Use db.prepare().all() for SELECT" not "follow project patterns"
42881
- - **Pitfalls with symptoms**: "you'll get undefined" not "don't misuse"
42882
- - **Every line earns its tokens** \u2014 no filler, no generic advice the AI already knows
43369
+ - Under 5000 tokens per skill
43370
+ - Be specific: "Use db.prepare().all() for SELECT" not "follow project patterns"
43371
+ - Include pitfalls with symptoms: "you'll get undefined" not "don't misuse"
42883
43372
 
42884
43373
  ## Existing Skills
42885
43374
  ${skillList}
42886
43375
  ${attentionSection}
42887
43376
 
42888
- ## Workspace
43377
+ ## Knowledge Workspace
42889
43378
  \`${paths.root.replace(/\\/g, "/")}\``;
42890
43379
  }
42891
43380
  var PlatformRuleSync = class {