codesift-mcp 0.5.24 → 0.5.28

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.
Files changed (221) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/commands.d.ts.map +1 -1
  3. package/dist/cli/commands.js +28 -0
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/cli/help.js +1 -1
  6. package/dist/cli/help.js.map +1 -1
  7. package/dist/cli/hooks.d.ts +4 -0
  8. package/dist/cli/hooks.d.ts.map +1 -1
  9. package/dist/cli/hooks.js +15 -4
  10. package/dist/cli/hooks.js.map +1 -1
  11. package/dist/cli/journal-commands.d.ts +9 -0
  12. package/dist/cli/journal-commands.d.ts.map +1 -0
  13. package/dist/cli/journal-commands.js +160 -0
  14. package/dist/cli/journal-commands.js.map +1 -0
  15. package/dist/cli/wiki-commands.d.ts.map +1 -1
  16. package/dist/cli/wiki-commands.js +5 -0
  17. package/dist/cli/wiki-commands.js.map +1 -1
  18. package/dist/instructions.d.ts +1 -1
  19. package/dist/instructions.d.ts.map +1 -1
  20. package/dist/instructions.js +41 -47
  21. package/dist/instructions.js.map +1 -1
  22. package/dist/parser/extractors/_shared.d.ts +2 -0
  23. package/dist/parser/extractors/_shared.d.ts.map +1 -1
  24. package/dist/parser/extractors/_shared.js +4 -0
  25. package/dist/parser/extractors/_shared.js.map +1 -1
  26. package/dist/parser/extractors/typescript.d.ts.map +1 -1
  27. package/dist/parser/extractors/typescript.js +606 -31
  28. package/dist/parser/extractors/typescript.js.map +1 -1
  29. package/dist/parser/parse-worker.d.ts +2 -0
  30. package/dist/parser/parse-worker.d.ts.map +1 -0
  31. package/dist/parser/parse-worker.js +47 -0
  32. package/dist/parser/parse-worker.js.map +1 -0
  33. package/dist/parser/parser-manager.d.ts +10 -1
  34. package/dist/parser/parser-manager.d.ts.map +1 -1
  35. package/dist/parser/parser-manager.js +40 -2
  36. package/dist/parser/parser-manager.js.map +1 -1
  37. package/dist/parser/parser-pool.d.ts +31 -0
  38. package/dist/parser/parser-pool.d.ts.map +1 -0
  39. package/dist/parser/parser-pool.js +211 -0
  40. package/dist/parser/parser-pool.js.map +1 -0
  41. package/dist/register-tool-loaders.d.ts +1 -1
  42. package/dist/register-tool-loaders.d.ts.map +1 -1
  43. package/dist/register-tool-loaders.js +2 -2
  44. package/dist/register-tool-loaders.js.map +1 -1
  45. package/dist/register-tools.d.ts.map +1 -1
  46. package/dist/register-tools.js +382 -35
  47. package/dist/register-tools.js.map +1 -1
  48. package/dist/retrieval/codebase-retrieval.d.ts.map +1 -1
  49. package/dist/retrieval/codebase-retrieval.js +9 -1
  50. package/dist/retrieval/codebase-retrieval.js.map +1 -1
  51. package/dist/search/chunker.d.ts +1 -0
  52. package/dist/search/chunker.d.ts.map +1 -1
  53. package/dist/search/chunker.js +8 -1
  54. package/dist/search/chunker.js.map +1 -1
  55. package/dist/server-helpers.d.ts +27 -0
  56. package/dist/server-helpers.d.ts.map +1 -1
  57. package/dist/server-helpers.js +90 -9
  58. package/dist/server-helpers.js.map +1 -1
  59. package/dist/server.js +31 -0
  60. package/dist/server.js.map +1 -1
  61. package/dist/storage/index-store.d.ts +41 -4
  62. package/dist/storage/index-store.d.ts.map +1 -1
  63. package/dist/storage/index-store.js +89 -6
  64. package/dist/storage/index-store.js.map +1 -1
  65. package/dist/storage/watcher.d.ts +33 -6
  66. package/dist/storage/watcher.d.ts.map +1 -1
  67. package/dist/storage/watcher.js +73 -34
  68. package/dist/storage/watcher.js.map +1 -1
  69. package/dist/storage/workspace-resolver.d.ts +15 -0
  70. package/dist/storage/workspace-resolver.d.ts.map +1 -0
  71. package/dist/storage/workspace-resolver.js +130 -0
  72. package/dist/storage/workspace-resolver.js.map +1 -0
  73. package/dist/tools/_helpers.d.ts +37 -0
  74. package/dist/tools/_helpers.d.ts.map +1 -0
  75. package/dist/tools/_helpers.js +31 -0
  76. package/dist/tools/_helpers.js.map +1 -0
  77. package/dist/tools/constant-resolution-tools.d.ts +8 -0
  78. package/dist/tools/constant-resolution-tools.d.ts.map +1 -0
  79. package/dist/tools/constant-resolution-tools.js +68 -0
  80. package/dist/tools/constant-resolution-tools.js.map +1 -0
  81. package/dist/tools/graph-tools.d.ts +27 -5
  82. package/dist/tools/graph-tools.d.ts.map +1 -1
  83. package/dist/tools/graph-tools.js +141 -18
  84. package/dist/tools/graph-tools.js.map +1 -1
  85. package/dist/tools/hotspot-tools.d.ts +6 -0
  86. package/dist/tools/hotspot-tools.d.ts.map +1 -1
  87. package/dist/tools/hotspot-tools.js +48 -8
  88. package/dist/tools/hotspot-tools.js.map +1 -1
  89. package/dist/tools/impact-tools.d.ts.map +1 -1
  90. package/dist/tools/impact-tools.js +9 -2
  91. package/dist/tools/impact-tools.js.map +1 -1
  92. package/dist/tools/index-tools.d.ts +15 -1
  93. package/dist/tools/index-tools.d.ts.map +1 -1
  94. package/dist/tools/index-tools.js +161 -16
  95. package/dist/tools/index-tools.js.map +1 -1
  96. package/dist/tools/journal-generator-helpers.d.ts +55 -0
  97. package/dist/tools/journal-generator-helpers.d.ts.map +1 -0
  98. package/dist/tools/journal-generator-helpers.js +250 -0
  99. package/dist/tools/journal-generator-helpers.js.map +1 -0
  100. package/dist/tools/journal-generator.d.ts +48 -0
  101. package/dist/tools/journal-generator.d.ts.map +1 -0
  102. package/dist/tools/journal-generator.js +204 -0
  103. package/dist/tools/journal-generator.js.map +1 -0
  104. package/dist/tools/journal-git-client.d.ts +20 -0
  105. package/dist/tools/journal-git-client.d.ts.map +1 -0
  106. package/dist/tools/journal-git-client.js +57 -0
  107. package/dist/tools/journal-git-client.js.map +1 -0
  108. package/dist/tools/journal-llm-client.d.ts +57 -0
  109. package/dist/tools/journal-llm-client.d.ts.map +1 -0
  110. package/dist/tools/journal-llm-client.js +175 -0
  111. package/dist/tools/journal-llm-client.js.map +1 -0
  112. package/dist/tools/journal-migrator.d.ts +22 -0
  113. package/dist/tools/journal-migrator.d.ts.map +1 -0
  114. package/dist/tools/journal-migrator.js +183 -0
  115. package/dist/tools/journal-migrator.js.map +1 -0
  116. package/dist/tools/journal-phase-detector.d.ts +26 -0
  117. package/dist/tools/journal-phase-detector.d.ts.map +1 -0
  118. package/dist/tools/journal-phase-detector.js +126 -0
  119. package/dist/tools/journal-phase-detector.js.map +1 -0
  120. package/dist/tools/journal-sentinel.d.ts +15 -0
  121. package/dist/tools/journal-sentinel.d.ts.map +1 -0
  122. package/dist/tools/journal-sentinel.js +110 -0
  123. package/dist/tools/journal-sentinel.js.map +1 -0
  124. package/dist/tools/journal-templates.d.ts +28 -0
  125. package/dist/tools/journal-templates.d.ts.map +1 -0
  126. package/dist/tools/journal-templates.js +93 -0
  127. package/dist/tools/journal-templates.js.map +1 -0
  128. package/dist/tools/pattern-tools.d.ts +2 -0
  129. package/dist/tools/pattern-tools.d.ts.map +1 -1
  130. package/dist/tools/pattern-tools.js +73 -0
  131. package/dist/tools/pattern-tools.js.map +1 -1
  132. package/dist/tools/perf-tools.d.ts.map +1 -1
  133. package/dist/tools/perf-tools.js +6 -0
  134. package/dist/tools/perf-tools.js.map +1 -1
  135. package/dist/tools/plan-turn-tools.d.ts.map +1 -1
  136. package/dist/tools/plan-turn-tools.js +38 -2
  137. package/dist/tools/plan-turn-tools.js.map +1 -1
  138. package/dist/tools/project-tools.d.ts +6 -0
  139. package/dist/tools/project-tools.d.ts.map +1 -1
  140. package/dist/tools/project-tools.js +65 -25
  141. package/dist/tools/project-tools.js.map +1 -1
  142. package/dist/tools/python-constants-tools.d.ts +6 -0
  143. package/dist/tools/python-constants-tools.d.ts.map +1 -1
  144. package/dist/tools/python-constants-tools.js.map +1 -1
  145. package/dist/tools/react-tools.d.ts +44 -1
  146. package/dist/tools/react-tools.d.ts.map +1 -1
  147. package/dist/tools/react-tools.js +142 -9
  148. package/dist/tools/react-tools.js.map +1 -1
  149. package/dist/tools/search-tools.d.ts.map +1 -1
  150. package/dist/tools/search-tools.js +21 -2
  151. package/dist/tools/search-tools.js.map +1 -1
  152. package/dist/tools/status-tools.d.ts +10 -0
  153. package/dist/tools/status-tools.d.ts.map +1 -1
  154. package/dist/tools/status-tools.js +53 -2
  155. package/dist/tools/status-tools.js.map +1 -1
  156. package/dist/tools/symbol-tools.d.ts +13 -0
  157. package/dist/tools/symbol-tools.d.ts.map +1 -1
  158. package/dist/tools/symbol-tools.js +38 -0
  159. package/dist/tools/symbol-tools.js.map +1 -1
  160. package/dist/tools/typescript-constants-tools.d.ts +6 -0
  161. package/dist/tools/typescript-constants-tools.d.ts.map +1 -0
  162. package/dist/tools/typescript-constants-tools.js +687 -0
  163. package/dist/tools/typescript-constants-tools.js.map +1 -0
  164. package/dist/tools/wiki-hub-ranker.d.ts +36 -0
  165. package/dist/tools/wiki-hub-ranker.d.ts.map +1 -0
  166. package/dist/tools/wiki-hub-ranker.js +72 -0
  167. package/dist/tools/wiki-hub-ranker.js.map +1 -0
  168. package/dist/tools/wiki-lint.d.ts +7 -2
  169. package/dist/tools/wiki-lint.d.ts.map +1 -1
  170. package/dist/tools/wiki-lint.js +73 -1
  171. package/dist/tools/wiki-lint.js.map +1 -1
  172. package/dist/tools/wiki-manifest.d.ts +129 -21
  173. package/dist/tools/wiki-manifest.d.ts.map +1 -1
  174. package/dist/tools/wiki-manifest.js +16 -9
  175. package/dist/tools/wiki-manifest.js.map +1 -1
  176. package/dist/tools/wiki-module-builder.d.ts +42 -0
  177. package/dist/tools/wiki-module-builder.d.ts.map +1 -0
  178. package/dist/tools/wiki-module-builder.js +449 -0
  179. package/dist/tools/wiki-module-builder.js.map +1 -0
  180. package/dist/tools/wiki-overview-sources.d.ts +26 -0
  181. package/dist/tools/wiki-overview-sources.d.ts.map +1 -0
  182. package/dist/tools/wiki-overview-sources.js +128 -0
  183. package/dist/tools/wiki-overview-sources.js.map +1 -0
  184. package/dist/tools/wiki-page-generators.d.ts +19 -3
  185. package/dist/tools/wiki-page-generators.d.ts.map +1 -1
  186. package/dist/tools/wiki-page-generators.js +156 -22
  187. package/dist/tools/wiki-page-generators.js.map +1 -1
  188. package/dist/tools/wiki-tools.d.ts +20 -0
  189. package/dist/tools/wiki-tools.d.ts.map +1 -1
  190. package/dist/tools/wiki-tools.js +181 -29
  191. package/dist/tools/wiki-tools.js.map +1 -1
  192. package/dist/tools/workspace-scope-helper.d.ts +21 -0
  193. package/dist/tools/workspace-scope-helper.d.ts.map +1 -0
  194. package/dist/tools/workspace-scope-helper.js +50 -0
  195. package/dist/tools/workspace-scope-helper.js.map +1 -0
  196. package/dist/tools/workspace-tools.d.ts +74 -0
  197. package/dist/tools/workspace-tools.d.ts.map +1 -0
  198. package/dist/tools/workspace-tools.js +366 -0
  199. package/dist/tools/workspace-tools.js.map +1 -0
  200. package/dist/types.d.ts +65 -0
  201. package/dist/types.d.ts.map +1 -1
  202. package/dist/utils/import-graph.d.ts +27 -0
  203. package/dist/utils/import-graph.d.ts.map +1 -1
  204. package/dist/utils/import-graph.js +331 -8
  205. package/dist/utils/import-graph.js.map +1 -1
  206. package/dist/utils/ts-imports.d.ts +30 -0
  207. package/dist/utils/ts-imports.d.ts.map +1 -0
  208. package/dist/utils/ts-imports.js +168 -0
  209. package/dist/utils/ts-imports.js.map +1 -0
  210. package/dist/utils/tsconfig-paths.d.ts +59 -0
  211. package/dist/utils/tsconfig-paths.d.ts.map +1 -0
  212. package/dist/utils/tsconfig-paths.js +176 -0
  213. package/dist/utils/tsconfig-paths.js.map +1 -0
  214. package/dist/utils/walk.d.ts.map +1 -1
  215. package/dist/utils/walk.js +1 -0
  216. package/dist/utils/walk.js.map +1 -1
  217. package/package.json +13 -3
  218. package/rules/codesift.md +19 -2
  219. package/rules/codesift.mdc +2 -2
  220. package/rules/codex.md +13 -2
  221. package/rules/gemini.md +13 -2
@@ -246,6 +246,71 @@ const FRAMEWORK_TOOL_GROUPS = {
246
246
  "extract_kotlin_serialization_contract",
247
247
  "trace_flow_chain",
248
248
  ],
249
+ // Python — detected by pyproject.toml (poetry/pdm/uv/hatch) or requirements.txt
250
+ "pyproject.toml": [
251
+ "get_model_graph",
252
+ "get_test_fixtures",
253
+ "find_framework_wiring",
254
+ "run_ruff",
255
+ "parse_pyproject",
256
+ "find_python_callers",
257
+ "analyze_django_settings",
258
+ "run_mypy",
259
+ "run_pyright",
260
+ "analyze_python_deps",
261
+ "trace_fastapi_depends",
262
+ "analyze_async_correctness",
263
+ "get_pydantic_models",
264
+ "python_audit",
265
+ ],
266
+ "requirements.txt": [
267
+ "get_model_graph",
268
+ "get_test_fixtures",
269
+ "find_framework_wiring",
270
+ "run_ruff",
271
+ "find_python_callers",
272
+ "analyze_django_settings",
273
+ "run_mypy",
274
+ "run_pyright",
275
+ "analyze_python_deps",
276
+ "trace_fastapi_depends",
277
+ "analyze_async_correctness",
278
+ "get_pydantic_models",
279
+ "python_audit",
280
+ ],
281
+ // TypeScript baseline — auto-enabled on any project with tsconfig.json.
282
+ // Promotes 3 high-value but historically dark tools so that vanilla TS /
283
+ // library / monorepo repos get a proper entry point beyond search_text.
284
+ "tsconfig.json": [
285
+ "dependency_audit",
286
+ "check_boundaries",
287
+ "architecture_summary",
288
+ ],
289
+ // Monorepo signals — orchestration-level analysis is most useful when
290
+ // there are >1 packages. Each of these files alone is enough to fire.
291
+ "pnpm-workspace.yaml": [
292
+ "check_boundaries",
293
+ "architecture_summary",
294
+ ],
295
+ "lerna.json": [
296
+ "check_boundaries",
297
+ "architecture_summary",
298
+ ],
299
+ "nx.json": [
300
+ "check_boundaries",
301
+ "architecture_summary",
302
+ ],
303
+ "turbo.json": [
304
+ "check_boundaries",
305
+ "architecture_summary",
306
+ ],
307
+ // Prisma — root-level schema. Nested prisma/schema.prisma handled in
308
+ // detectAutoLoadTools after the loop. drizzle-kit dep handled in
309
+ // detectFromPackageJson.
310
+ "schema.prisma": [
311
+ "analyze_prisma_schema",
312
+ "migration_lint",
313
+ ],
249
314
  };
250
315
  /**
251
316
  * React-specific tools — auto-enabled when a React project is detected.
@@ -282,6 +347,22 @@ const HONO_TOOLS = [
282
347
  "detect_hono_modules",
283
348
  "find_dead_hono_routes",
284
349
  ];
350
+ /**
351
+ * Monorepo tools — auto-enabled when `pkg.workspaces` field is present
352
+ * (mirror of the file-based monorepo signals in FRAMEWORK_TOOL_GROUPS).
353
+ */
354
+ const MONOREPO_TOOLS = [
355
+ "check_boundaries",
356
+ "architecture_summary",
357
+ ];
358
+ /**
359
+ * Prisma tools — auto-enabled when `prisma` or `drizzle-kit` is in deps
360
+ * (mirror of the schema.prisma file signal in FRAMEWORK_TOOL_GROUPS).
361
+ */
362
+ const PRISMA_TOOLS = [
363
+ "analyze_prisma_schema",
364
+ "migration_lint",
365
+ ];
285
366
  const AUTO_LOAD_CACHE_TTL_MS = 5_000;
286
367
  const autoLoadToolsCache = new Map();
287
368
  /**
@@ -290,6 +371,8 @@ const autoLoadToolsCache = new Map();
290
371
  * Exported for unit testing.
291
372
  */
292
373
  export async function detectAutoLoadTools(cwd) {
374
+ // Kill switch: spec D-FB. Skip workspace walk entirely when set.
375
+ const killSwitchOff = process.env.CODESIFT_DISABLE_MONOREPO !== "1";
293
376
  const { existsSync, readFileSync, readdirSync } = await import("node:fs");
294
377
  const { join } = await import("node:path");
295
378
  const toEnable = [];
@@ -298,30 +381,79 @@ export async function detectAutoLoadTools(cwd) {
298
381
  toEnable.push(...tools);
299
382
  }
300
383
  }
301
- // React + Hono detection: both need to read package.json for dep signals.
302
- const pkgPath = join(cwd, "package.json");
303
- if (existsSync(pkgPath)) {
384
+ // Nested Prisma schema `prisma/schema.prisma` is the more common layout
385
+ // than root-level. The root-level case is covered by FRAMEWORK_TOOL_GROUPS.
386
+ if (existsSync(join(cwd, "prisma", "schema.prisma"))) {
387
+ toEnable.push(...PRISMA_TOOLS);
388
+ }
389
+ const detectFromPackageJson = (pkgRoot) => {
390
+ const enabled = [];
391
+ const pkgPath = join(pkgRoot, "package.json");
392
+ if (!existsSync(pkgPath))
393
+ return enabled;
304
394
  try {
305
395
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
306
396
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
307
- // React: dep + .tsx/.jsx files
308
- const hasReact = !!(allDeps["react"] || allDeps["next"] || allDeps["@remix-run/react"]);
309
- if (hasReact && hasJsxFilesShallow(cwd, readdirSync)) {
310
- toEnable.push(...REACT_TOOLS);
311
- }
312
- // Hono: any hono package is enough — the framework is only pulled in
313
- // when used, and all 5 hidden tools degrade gracefully on non-Hono repos
314
- // so false positives are harmless (return "no Hono detected" errors).
397
+ const hasReact = !!(allDeps["react"] ||
398
+ allDeps["next"] ||
399
+ allDeps["@remix-run/react"] ||
400
+ allDeps["@xyflow/react"] ||
401
+ allDeps["preact"]);
402
+ if (hasReact && hasJsxFilesShallow(pkgRoot, readdirSync)) {
403
+ enabled.push(...REACT_TOOLS);
404
+ }
315
405
  const hasHono = !!(allDeps["hono"] ||
316
406
  allDeps["@hono/zod-openapi"] ||
317
407
  allDeps["@hono/node-server"] ||
318
408
  allDeps["hono-openapi"] ||
319
409
  allDeps["chanfana"]);
320
- if (hasHono) {
321
- toEnable.push(...HONO_TOOLS);
410
+ if (hasHono)
411
+ enabled.push(...HONO_TOOLS);
412
+ // Prisma / Drizzle — schema-driven DB stacks. analyze_prisma_schema
413
+ // works for Prisma; migration_lint (squawk) works for any SQL migration
414
+ // dir, useful for both. We don't condition on @prisma/client because
415
+ // it's runtime-only — the schema work belongs to the `prisma` CLI dep.
416
+ const hasPrismaLike = !!(allDeps["prisma"] ||
417
+ allDeps["@prisma/client"] ||
418
+ allDeps["drizzle-kit"] ||
419
+ allDeps["drizzle-orm"]);
420
+ if (hasPrismaLike)
421
+ enabled.push(...PRISMA_TOOLS);
422
+ // npm/yarn/pnpm workspaces field — content-based monorepo signal.
423
+ // Complements the file-based signals (pnpm-workspace.yaml, lerna.json,
424
+ // nx.json, turbo.json) so plain `"workspaces": [...]` setups also fire.
425
+ const hasWorkspaces = Array.isArray(pkg.workspaces) ||
426
+ (pkg.workspaces && typeof pkg.workspaces === "object" &&
427
+ Array.isArray(pkg.workspaces.packages));
428
+ if (hasWorkspaces)
429
+ enabled.push(...MONOREPO_TOOLS);
430
+ }
431
+ catch {
432
+ /* malformed package.json */
433
+ }
434
+ return enabled;
435
+ };
436
+ // Root-level detection (existing behavior — unchanged for flat repos).
437
+ toEnable.push(...detectFromPackageJson(cwd));
438
+ // Monorepo workspace walk (Task 15). When monorepo detected at cwd, union
439
+ // each workspace's framework signals into the auto-load set. Hard cap at 50
440
+ // workspaces to bound startup cost (sync FS reads at module-init time);
441
+ // very large monorepos (>50 packages) fall back to root-only auto-load —
442
+ // discover_tools / describe_tools still work for any framework tool needed
443
+ // at query time.
444
+ if (killSwitchOff) {
445
+ try {
446
+ const { resolveWorkspaces } = await import("./storage/workspace-resolver.js");
447
+ const resolved = await resolveWorkspaces(cwd);
448
+ if (resolved && resolved.workspaces.length > 0 && resolved.workspaces.length <= 50) {
449
+ for (const ws of resolved.workspaces) {
450
+ toEnable.push(...detectFromPackageJson(ws.root));
451
+ }
322
452
  }
323
453
  }
324
- catch { /* malformed package.json */ }
454
+ catch {
455
+ /* monorepo resolver failure is non-fatal; flat-repo behavior preserved */
456
+ }
325
457
  }
326
458
  return toEnable;
327
459
  }
@@ -527,11 +659,15 @@ const TOOL_DEFINITIONS = [
527
659
  path: z.string().describe("Absolute path to the folder to index"),
528
660
  incremental: zBool().describe("Only re-index changed files"),
529
661
  include_paths: z.union([z.array(z.string()), z.string().transform((s) => JSON.parse(s))]).optional().describe("Glob patterns to include. Can be passed as JSON string."),
662
+ max_files: z.number().int().positive().optional().describe("Cap on files indexed. Default 50000 (or CODESIFT_MAX_FILES env). Walker stops at this count and returns partial results — protects against OOM on huge repos. Use include_paths to scope instead of raising this for large vendored trees."),
663
+ watch: zBool().describe("Whether to set up a chokidar file watcher for incremental updates after indexing. Default true. Pass false for bulk/CI indexing scenarios — file watchers consume system file descriptors (1+ per repo on macOS FSEvents); indexing many repos with watchers active can exhaust the system file table (ENFILE)."),
530
664
  })),
531
665
  handler: async (args) => {
532
666
  const result = await indexFolder(args.path, {
533
667
  incremental: args.incremental,
534
668
  include_paths: args.include_paths,
669
+ max_files: args.max_files,
670
+ watch: args.watch,
535
671
  });
536
672
  // Auto-enable framework tools based on indexed path (not CWD)
537
673
  try {
@@ -697,15 +833,40 @@ const TOOL_DEFINITIONS = [
697
833
  auto_group: zBool().describe("Auto group_by_file when >50 matches."),
698
834
  ranked: z.boolean().optional().describe("Classify hits by containing symbol and rank by centrality"),
699
835
  })),
700
- handler: (args) => searchText(args.repo, args.query, {
701
- regex: args.regex,
702
- context_lines: args.context_lines,
703
- file_pattern: args.file_pattern,
704
- max_results: args.max_results,
705
- group_by_file: args.group_by_file,
706
- auto_group: args.auto_group,
707
- ranked: args.ranked,
708
- }),
836
+ handler: async (args) => {
837
+ const result = await searchText(args.repo, args.query, {
838
+ regex: args.regex,
839
+ context_lines: args.context_lines,
840
+ file_pattern: args.file_pattern,
841
+ max_results: args.max_results,
842
+ group_by_file: args.group_by_file,
843
+ auto_group: args.auto_group,
844
+ ranked: args.ranked,
845
+ });
846
+ // Zero-result hint: 41% of search_text calls return nothing in telemetry.
847
+ // Suggest the most likely fix based on query shape so the agent doesn't
848
+ // burn 2-3 follow-up turns guessing.
849
+ const isEmpty = (Array.isArray(result) && result.length === 0)
850
+ || (typeof result === "string" && result.length === 0);
851
+ if (isEmpty) {
852
+ const q = args.query;
853
+ const fp = args.file_pattern;
854
+ const looksLikeSymbol = /::|->|\.[a-z][a-zA-Z0-9_]*\(/.test(q)
855
+ || /^(class|function|def|fn|interface|type)\s+\w/.test(q)
856
+ || (/^[A-Z][a-zA-Z0-9_]+$|^[a-z][a-zA-Z0-9_]+$/.test(q.trim()) && !q.includes(" "));
857
+ const hints = ["No matches."];
858
+ if (looksLikeSymbol)
859
+ hints.push("Query looks like a symbol — try search_symbols(query=...) instead.");
860
+ if (fp)
861
+ hints.push(`Try without file_pattern="${fp}" to widen scope.`);
862
+ if (args.regex === true)
863
+ hints.push("Try regex=false (literal) — escapes may be off.");
864
+ if (!fp && !looksLikeSymbol)
865
+ hints.push("Try a shorter substring, or add file_pattern= to scope.");
866
+ return { matches: [], hint: hints.join(" ") };
867
+ }
868
+ return result;
869
+ },
709
870
  },
710
871
  // --- Outline ---
711
872
  {
@@ -793,10 +954,19 @@ const TOOL_DEFINITIONS = [
793
954
  const opts = {};
794
955
  if (args.include_related != null)
795
956
  opts.include_related = args.include_related;
796
- const result = await getSymbol(args.repo, args.symbol_id, opts);
957
+ const symbolId = args.symbol_id;
958
+ const result = await getSymbol(args.repo, symbolId, opts);
797
959
  if (!result) {
960
+ // Telemetry: 24% of get_symbol calls return null (hallucinated IDs).
961
+ // Suggest closest matches by name so the agent doesn't burn turns guessing.
962
+ const { findSimilarSymbols } = await import("./tools/symbol-tools.js");
963
+ const similar = await findSimilarSymbols(args.repo, symbolId, 3);
964
+ if (similar.length > 0) {
965
+ const suggestions = similar.map((s) => ` ${s.id} (${s.kind} ${s.name} @ ${s.file}:${s.start_line})`).join("\n");
966
+ return `Symbol "${symbolId}" not found. Did you mean:\n${suggestions}`;
967
+ }
798
968
  const hint = await checkTextStubHint(args.repo, "get_symbol", true);
799
- return hint ?? null;
969
+ return hint ?? `Symbol "${symbolId}" not found. Use search_symbols(query=...) to discover available IDs.`;
800
970
  }
801
971
  let text = await formatSymbolCompact(result.symbol);
802
972
  if (result.related && result.related.length > 0) {
@@ -818,10 +988,31 @@ const TOOL_DEFINITIONS = [
818
988
  ]).describe("Array of symbol identifiers. Can be passed as JSON string."),
819
989
  })),
820
990
  handler: async (args) => {
821
- const syms = await getSymbols(args.repo, args.symbol_ids);
991
+ const ids = args.symbol_ids;
992
+ const syms = await getSymbols(args.repo, ids);
822
993
  const output = await formatSymbolsCompact(syms);
994
+ // Surface fuzzy suggestions for missing IDs (telemetry: 26% zero rate).
995
+ let suggestions = "";
996
+ if (syms.length < ids.length) {
997
+ const foundIds = new Set(syms.map((s) => s.id));
998
+ const missing = ids.filter((id) => !foundIds.has(id));
999
+ if (missing.length > 0) {
1000
+ const { findSimilarSymbols } = await import("./tools/symbol-tools.js");
1001
+ const lines = [];
1002
+ for (const m of missing.slice(0, 5)) {
1003
+ const sims = await findSimilarSymbols(args.repo, m, 2);
1004
+ if (sims.length > 0) {
1005
+ lines.push(` ${m} → ${sims.map((s) => s.id).join(", ")}`);
1006
+ }
1007
+ else {
1008
+ lines.push(` ${m} → no similar symbols`);
1009
+ }
1010
+ }
1011
+ suggestions = `\n\n--- not found (${missing.length}) — suggestions ---\n${lines.join("\n")}`;
1012
+ }
1013
+ }
823
1014
  const hint = await checkTextStubHint(args.repo, "get_symbols", syms.length === 0);
824
- return hint ? hint + output : output;
1015
+ return (hint ? hint + output : output) + suggestions;
825
1016
  },
826
1017
  },
827
1018
  {
@@ -1584,6 +1775,9 @@ const TOOL_DEFINITIONS = [
1584
1775
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1585
1776
  focus: z.string().optional().describe("Scope to directory (e.g., 'src/tools')"),
1586
1777
  output_dir: z.string().optional().describe("Output directory (default: {repo_root}/.codesift/wiki)"),
1778
+ journal_mode: z.enum(["skip", "refresh-overview", "append", "full"]).optional().default("skip").describe("Journal integration mode (default: skip)"),
1779
+ journal_since_ref: z.string().optional().describe("git-relative ref for journal_mode=append (e.g., '2 weeks ago' or ISO date)"),
1780
+ journal_bulk_fill: z.boolean().optional().describe("Bulk-fill all phases when journal_mode=full"),
1587
1781
  })),
1588
1782
  handler: async (args) => {
1589
1783
  const opts = {};
@@ -1591,10 +1785,39 @@ const TOOL_DEFINITIONS = [
1591
1785
  opts.focus = args.focus;
1592
1786
  if (args.output_dir !== undefined)
1593
1787
  opts.output_dir = args.output_dir;
1788
+ if (args.journal_mode !== undefined)
1789
+ opts.journal_mode = args.journal_mode;
1790
+ if (args.journal_since_ref !== undefined)
1791
+ opts.journal_since_ref = args.journal_since_ref;
1792
+ if (args.journal_bulk_fill !== undefined)
1793
+ opts.journal_bulk_fill = args.journal_bulk_fill;
1594
1794
  const result = await generateWiki(args.repo, opts);
1595
1795
  return JSON.stringify(result, null, 2);
1596
1796
  },
1597
1797
  },
1798
+ {
1799
+ name: "journal_append",
1800
+ category: "reporting",
1801
+ searchHint: "journal append phases git commits since wiki journal",
1802
+ description: "Append new journal phases for commits since the given git ref. Dispatches to runJournalAppend.",
1803
+ schema: lazySchema(() => ({
1804
+ since: z.string().describe("git-relative string like '2 weeks ago' or ISO date"),
1805
+ max_cost_usd: z.number().optional().default(2.0).describe("Maximum LLM cost cap in USD (default: 2.0)"),
1806
+ dry_run: z.boolean().optional().default(false).describe("Plan phases without writing files (default: false)"),
1807
+ })),
1808
+ handler: async (args) => {
1809
+ const { runJournalAppend } = await import("./tools/journal-generator.js");
1810
+ const opts = {
1811
+ cwd: process.cwd(),
1812
+ outputDir: ".codesift/wiki",
1813
+ since: args.since,
1814
+ };
1815
+ if (args.dry_run !== undefined)
1816
+ opts.dryRun = args.dry_run;
1817
+ const r = await runJournalAppend(opts);
1818
+ return JSON.stringify(r, null, 2);
1819
+ },
1820
+ },
1598
1821
  // --- Conversations ---
1599
1822
  {
1600
1823
  name: "index_conversations",
@@ -1651,6 +1874,85 @@ const TOOL_DEFINITIONS = [
1651
1874
  return formatConversations(result);
1652
1875
  },
1653
1876
  },
1877
+ // --- Monorepo Workspaces (Tasks 8-11 of monorepo workspace intelligence plan) ---
1878
+ {
1879
+ name: "list_workspaces",
1880
+ category: "analysis",
1881
+ searchHint: "monorepo workspace list packages turbo pnpm yarn npm",
1882
+ description: "List workspace packages for a JS/TS monorepo (Turbo / pnpm / yarn / npm / Nx). Returns shape-stable empty result on flat repos.",
1883
+ schema: lazySchema(() => ({
1884
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1885
+ })),
1886
+ handler: async (args) => {
1887
+ const { listWorkspacesHandler } = await import("./tools/workspace-tools.js");
1888
+ return listWorkspacesHandler(args.repo ? { repo: args.repo } : {});
1889
+ },
1890
+ },
1891
+ {
1892
+ name: "workspace_graph",
1893
+ category: "analysis",
1894
+ searchHint: "monorepo workspace dependency graph turbo nx mermaid dot",
1895
+ description: "Build the workspace-to-workspace dependency DAG of a monorepo. Output formats: json (default), mermaid, dot.",
1896
+ schema: lazySchema(() => ({
1897
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1898
+ format: z.enum(["json", "mermaid", "dot"]).optional().describe("Output format (default: json)"),
1899
+ })),
1900
+ handler: async (args) => {
1901
+ const { workspaceGraphHandler } = await import("./tools/workspace-tools.js");
1902
+ const opts = {};
1903
+ if (args.repo)
1904
+ opts.repo = args.repo;
1905
+ if (args.format)
1906
+ opts.format = args.format;
1907
+ return workspaceGraphHandler(opts);
1908
+ },
1909
+ },
1910
+ {
1911
+ name: "affected_workspaces",
1912
+ category: "analysis",
1913
+ searchHint: "monorepo affected workspaces git diff impact transitive turbo nx",
1914
+ description: "Compute affected workspaces for a git diff. File changes -> containing workspace -> reverse-dep walk. Lockfile-only commits surface separately and never fan out.",
1915
+ schema: lazySchema(() => ({
1916
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1917
+ since: z.string().describe("Git ref to diff against (e.g. HEAD~1, main, <sha>)"),
1918
+ include_transitive: zBool().describe("Include transitive reverse-deps (default: true)"),
1919
+ })),
1920
+ handler: async (args) => {
1921
+ const { affectedWorkspacesHandler } = await import("./tools/workspace-tools.js");
1922
+ const opts = {
1923
+ since: args.since,
1924
+ };
1925
+ if (args.repo)
1926
+ opts.repo = args.repo;
1927
+ if (args.include_transitive !== undefined)
1928
+ opts.include_transitive = args.include_transitive;
1929
+ return affectedWorkspacesHandler(opts);
1930
+ },
1931
+ },
1932
+ {
1933
+ name: "workspace_boundaries",
1934
+ category: "analysis",
1935
+ searchHint: "monorepo boundary rules workspace import violations enforce",
1936
+ description: "Enforce workspace-level import boundaries. Walks ALL cross-workspace import edges (relative + bare/tsconfig-alias) and reports rule violations.",
1937
+ schema: lazySchema(() => ({
1938
+ repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
1939
+ rules: z
1940
+ .array(z.object({
1941
+ from_workspace: z.string().describe("Workspace name OR glob (e.g. 'apps/*')"),
1942
+ cannot_import_workspaces: z.array(z.string()).describe("Names, globs, or negation entries"),
1943
+ }))
1944
+ .describe("Workspace boundary rules"),
1945
+ })),
1946
+ handler: async (args) => {
1947
+ const { workspaceBoundariesHandler } = await import("./tools/workspace-tools.js");
1948
+ const opts = {
1949
+ rules: args.rules,
1950
+ };
1951
+ if (args.repo)
1952
+ opts.repo = args.repo;
1953
+ return workspaceBoundariesHandler(opts);
1954
+ },
1955
+ },
1654
1956
  // --- Security ---
1655
1957
  {
1656
1958
  name: "scan_secrets",
@@ -2582,6 +2884,16 @@ const TOOL_DEFINITIONS = [
2582
2884
  handler: async (args) => {
2583
2885
  const result = await indexStatus(args.repo);
2584
2886
  if (!result.indexed) {
2887
+ // Stale: index file exists but extractor_version drifted. Distinct
2888
+ // from "never indexed" — agents seeing "STALE" know that re-running
2889
+ // index_folder will fix it without wondering whether earlier indexing
2890
+ // attempts silently failed.
2891
+ if (result.stale) {
2892
+ return (`index_status: STALE — extractor_version_mismatch ` +
2893
+ `(${result.stale.language}: indexed at ${result.stale.actual_version}, ` +
2894
+ `current ${result.stale.expected_version}). ` +
2895
+ `Run index_folder to refresh.`);
2896
+ }
2585
2897
  // If no repo specified, list available repos so the agent can pick one
2586
2898
  if (!args.repo) {
2587
2899
  const { listAllRepos } = await import("./tools/index-tools.js");
@@ -2753,15 +3065,28 @@ const TOOL_DEFINITIONS = [
2753
3065
  {
2754
3066
  name: "nest_audit",
2755
3067
  category: "nestjs",
2756
- searchHint: "nestjs audit analysis comprehensive module di guard route lifecycle pattern graphql websocket schedule typeorm microservice hook onModuleInit onApplicationBootstrap shutdown dependency graph circular import boundary injection provider constructor inject cycle interceptor pipe filter middleware chain security endpoint api map inventory list all params resolver query mutation subscription apollo gateway subscribemessage socketio realtime event cron interval timeout scheduled job task onevent listener entity relation onetomany manytoone database schema messagepattern eventpattern kafka rabbitmq nats transport request pipeline handler execution flow visualization bull bullmq queue processor process background worker scope transient singleton performance escalation swagger openapi documentation apiproperty apioperation apiresponse contract extract",
2757
- description: "One-call NestJS architecture audit: modules, DI, guards, routes, lifecycle, patterns, GraphQL, WebSocket, schedule, TypeORM, microservices.",
3068
+ searchHint: "nestjs audit analysis comprehensive module di guard route lifecycle pattern graphql websocket schedule typeorm microservice hook onModuleInit onApplicationBootstrap shutdown dependency graph circular import boundary injection provider constructor inject cycle interceptor pipe filter middleware chain security endpoint api map inventory list all params resolver query mutation subscription apollo gateway subscribemessage socketio realtime event cron interval timeout scheduled job task onevent listener entity relation onetomany manytoone database schema messagepattern eventpattern kafka rabbitmq nats transport request pipeline handler execution flow visualization bull bullmq queue processor process background worker scope transient singleton performance escalation swagger openapi documentation apiproperty apioperation apiresponse contract extract workspace monorepo",
3069
+ description: "One-call NestJS architecture audit: modules, DI, guards, routes, lifecycle, patterns, GraphQL, WebSocket, schedule, TypeORM, microservices. Pass workspace=<name|path> in monorepos to scope to a single workspace.",
2758
3070
  schema: lazySchema(() => ({
2759
3071
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3072
+ workspace: z.string().optional().describe("Monorepo workspace name or path. Scopes the audit to that workspace's files."),
2760
3073
  checks: z.string().optional().describe("Comma-separated checks (default: all). Options: modules,routes,di,guards,lifecycle,patterns,graphql,websocket,schedule,typeorm,microservice"),
2761
3074
  })),
2762
3075
  handler: async (args) => {
3076
+ const { resolveWorkspaceScope } = await import("./tools/workspace-scope-helper.js");
2763
3077
  const checks = args.checks?.split(",").map((s) => s.trim()).filter(Boolean);
2764
- return nestAudit(args.repo ?? "", checks ? { checks } : undefined);
3078
+ const scope = await resolveWorkspaceScope(args.repo ?? "", args.workspace, "nestjs");
3079
+ if ("error" in scope) {
3080
+ return { error: scope.error, input: scope.input, available: scope.available };
3081
+ }
3082
+ const opts = {};
3083
+ if (checks)
3084
+ opts.checks = checks;
3085
+ if (scope.rootPaths.length > 0) {
3086
+ // Pass first matched workspace path through the existing file_pattern-style hook
3087
+ opts.file_pattern = `${scope.rootPaths[0]}/**`;
3088
+ }
3089
+ return nestAudit(args.repo ?? "", opts);
2765
3090
  },
2766
3091
  },
2767
3092
  // --- Agent config audit ---
@@ -3100,18 +3425,27 @@ const TOOL_DEFINITIONS = [
3100
3425
  {
3101
3426
  name: "astro_audit",
3102
3427
  category: "analysis",
3103
- searchHint: "astro meta audit full health check score gates recommendations islands hydration routes config actions content migration patterns",
3104
- description: "One-call Astro project health check: runs all 7 Astro tools + 13 Astro patterns in parallel, returns unified {score, gates, sections, recommendations}. Mirrors react_quickstart pattern.",
3428
+ searchHint: "astro meta audit full health check score gates recommendations islands hydration routes config actions content migration patterns workspace monorepo",
3429
+ description: "One-call Astro project health check: runs all 7 Astro tools + 13 Astro patterns in parallel, returns unified {score, gates, sections, recommendations}. Pass workspace=<name|path> in monorepos to scope to a single workspace.",
3105
3430
  schema: lazySchema(() => ({
3106
3431
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3432
+ workspace: z.string().optional().describe("Monorepo workspace name or path. Scopes the audit to that workspace."),
3107
3433
  skip: z.array(z.string()).optional().describe("Sections to skip: config, hydration, routes, actions, content, migration, patterns"),
3108
3434
  })),
3109
3435
  handler: async (args) => {
3436
+ const { resolveWorkspaceScope } = await import("./tools/workspace-scope-helper.js");
3437
+ const scope = await resolveWorkspaceScope(args.repo ?? "", args.workspace, "astro");
3438
+ if ("error" in scope) {
3439
+ return { error: scope.error, input: scope.input, available: scope.available };
3440
+ }
3110
3441
  const opts = {};
3111
3442
  if (args.repo != null)
3112
3443
  opts.repo = args.repo;
3113
3444
  if (args.skip != null)
3114
3445
  opts.skip = args.skip;
3446
+ if (scope.rootPaths.length > 0) {
3447
+ opts.file_pattern = `${scope.rootPaths[0]}/**`;
3448
+ }
3115
3449
  return await astroAudit(opts);
3116
3450
  },
3117
3451
  },
@@ -3141,16 +3475,29 @@ const TOOL_DEFINITIONS = [
3141
3475
  {
3142
3476
  name: "analyze_hono_app",
3143
3477
  category: "analysis",
3144
- searchHint: "hono overview analyze app routes middleware runtime env bindings rpc",
3145
- description: "Complete Hono application overview: routes grouped by method/scope, middleware map, context vars, OpenAPI status, RPC exports (flags Issue #3869 slow pattern), runtime, env bindings. One call for full project analysis.",
3478
+ searchHint: "hono overview analyze app routes middleware runtime env bindings rpc workspace monorepo",
3479
+ description: "Complete Hono application overview: routes grouped by method/scope, middleware map, context vars, OpenAPI status, RPC exports (flags Issue #3869 slow pattern), runtime, env bindings. Pass workspace=<name|path> in monorepos to scope to a single workspace.",
3146
3480
  schema: lazySchema(() => ({
3147
3481
  repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
3148
3482
  entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
3483
+ workspace: z.string().optional().describe("Monorepo workspace name or path (e.g. '@org/api' or 'apps/api'). Scopes Hono entry resolution to that workspace."),
3149
3484
  force_refresh: z.boolean().optional().describe("Clear cache and rebuild"),
3150
3485
  })),
3151
3486
  handler: async (args) => {
3152
3487
  const { analyzeHonoApp } = await import("./tools/hono-analyze-app.js");
3153
- return await analyzeHonoApp(args.repo, args.entry_file, args.force_refresh);
3488
+ const { resolveWorkspaceScope } = await import("./tools/workspace-scope-helper.js");
3489
+ const repo = args.repo;
3490
+ const scope = await resolveWorkspaceScope(repo, args.workspace, "hono");
3491
+ if ("error" in scope) {
3492
+ return { error: scope.error, input: scope.input, available: scope.available };
3493
+ }
3494
+ // If workspace scoping resolved to a path, prefer it as entry_file root hint.
3495
+ let entry = args.entry_file;
3496
+ if (!entry && scope.rootPaths.length === 1) {
3497
+ // Hono's entry resolver searches src/index.ts under the path provided
3498
+ entry = scope.rootPaths[0];
3499
+ }
3500
+ return await analyzeHonoApp(repo, entry, args.force_refresh);
3154
3501
  },
3155
3502
  },
3156
3503
  {