@happyvertical/smrt-dev-mcp 0.40.37 → 0.40.38

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
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { a as checkKnowledgeFreshnessFromIndex, i as checkKnowledgeFreshness, l as smrtArchitecture, n as buildKnowledgeIndex, r as buildReviewContext, t as buildArchitectureContext, u as smrtReview } from "./knowledge-BPbgCtVJ.js";
2
+ import { a as checkKnowledgeFreshnessFromIndex, d as smrtReview, i as checkKnowledgeFreshness, n as buildKnowledgeIndex, r as buildReviewContext, s as packageDocPaths, t as buildArchitectureContext, u as smrtArchitecture } from "./knowledge-DUUhTM5u.js";
3
3
  import { existsSync, readFileSync, realpathSync } from "node:fs";
4
4
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
  import { CallToolRequestSchema, ErrorCode, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
+ import { ManifestAdapter, OxcScanner } from "@happyvertical/smrt-scanner";
9
10
  import { access, readFile, readdir } from "node:fs/promises";
10
11
  import { ManifestGenerator } from "@happyvertical/smrt-core/scanner";
11
- import { ManifestAdapter, OxcScanner } from "@happyvertical/smrt-scanner";
12
12
  //#region src/agent-skills.ts
13
13
  var AGENT_SKILLS = [{
14
14
  name: "smrt-code-review",
@@ -255,8 +255,20 @@ var RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
255
255
  "oneToMany",
256
256
  "manyToMany"
257
257
  ]);
258
+ /**
259
+ * Response budget in characters. Generous enough that a 42-object summary never
260
+ * truncates, while still capping a runaway payload instead of emitting one.
261
+ */
262
+ var DEFAULT_MAX_CHARS = 5e4;
263
+ var DEFAULT_MCP_OPERATIONS = [
264
+ "list",
265
+ "get",
266
+ "create",
267
+ "update",
268
+ "delete"
269
+ ];
258
270
  async function introspectProject(args) {
259
- const { directory = process.cwd(), includeFields = true, includeRelationships = true, includeMethods = true } = args;
271
+ const { directory = process.cwd(), includeFields = true, includeRelationships = true, includeMethods = true, detail = "summary", maxChars = DEFAULT_MAX_CHARS } = args;
260
272
  const projectPath = resolve(directory);
261
273
  if (!await pathExists$1(projectPath)) return JSON.stringify({
262
274
  projectPath,
@@ -271,7 +283,13 @@ async function introspectProject(args) {
271
283
  const packageMetadata = await readPackageMetadata(projectPath);
272
284
  const tenantScopes = await scanTenantScopes(projectPath);
273
285
  const manifestResult = await loadManifestArtifact(projectPath, args.manifestPath) ?? await scanSourceManifest(projectPath, packageMetadata);
274
- const objects = Object.entries(manifestResult.manifest.objects ?? {}).map(([manifestKey, object]) => formatObject({
286
+ const entries = Object.entries(manifestResult.manifest.objects ?? {});
287
+ const objects = detail === "summary" ? entries.map(([manifestKey, object]) => summarizeObject({
288
+ manifestKey,
289
+ object,
290
+ projectPath,
291
+ tenantScope: tenantScopes.get(object.className)
292
+ })) : entries.map(([manifestKey, object]) => formatObject({
275
293
  manifestKey,
276
294
  object,
277
295
  projectPath,
@@ -279,21 +297,77 @@ async function introspectProject(args) {
279
297
  includeRelationships,
280
298
  includeMethods,
281
299
  tenantScope: tenantScopes.get(object.className)
282
- })).sort((left, right) => left.className.localeCompare(right.className));
300
+ }));
301
+ objects.sort((left, right) => left.className.localeCompare(right.className));
302
+ const { kept, omitted } = applyObjectBudget(objects, maxChars);
283
303
  const output = {
284
304
  projectPath,
285
305
  manifestSource: manifestResult.source,
286
306
  manifestPath: "path" in manifestResult ? relative(projectPath, manifestResult.path) : void 0,
287
307
  packageName: manifestResult.manifest.packageName ?? packageMetadata.name ?? void 0,
288
308
  packageVersion: manifestResult.manifest.packageVersion ?? packageMetadata.version ?? void 0,
309
+ detail,
289
310
  objectCount: objects.length,
290
311
  scannedFileCount: manifestResult.scannedFileCount,
291
312
  parseTimeMs: manifestResult.parseTimeMs,
292
- objects,
313
+ ...omitted > 0 ? { truncated: {
314
+ returnedObjectCount: kept.length,
315
+ omittedObjectCount: omitted,
316
+ budgetChars: maxChars,
317
+ guidance: "The object list hit its character budget; metadata and diagnostics are still complete. Narrow the scan with `directory` (a single package), keep `detail: \"summary\"`, or raise `maxChars` deliberately. Object names are sorted alphabetically, so omitted objects are the alphabetical tail."
318
+ } } : {},
319
+ objects: kept,
293
320
  diagnostics: manifestResult.diagnostics
294
321
  };
295
322
  return JSON.stringify(output, null, 2);
296
323
  }
324
+ /**
325
+ * Trims the object list to the character budget, always keeping at least one so
326
+ * a caller sees the shape rather than an empty list.
327
+ */
328
+ function applyObjectBudget(objects, maxChars) {
329
+ const kept = [];
330
+ let used = 0;
331
+ for (const object of objects) {
332
+ const size = JSON.stringify(object, null, 2).length + 2;
333
+ if (used + size > maxChars && kept.length > 0) break;
334
+ used += size;
335
+ kept.push(object);
336
+ }
337
+ return {
338
+ kept,
339
+ omitted: objects.length - kept.length
340
+ };
341
+ }
342
+ function summarizeObject({ manifestKey, object, projectPath, tenantScope }) {
343
+ const fields = Object.entries(object.fields ?? {});
344
+ const decoratorConfig = object.decoratorConfig ?? {};
345
+ return compactObject({
346
+ manifestKey,
347
+ className: object.className,
348
+ qualifiedName: object.qualifiedName,
349
+ filePath: sanitizePath$1(projectPath, object.filePath),
350
+ extends: object.extends,
351
+ collection: object.collection,
352
+ tableName: object.schema?.tableName ?? stringFromConfig(decoratorConfig.tableName) ?? object.collection,
353
+ tenantScope: tenantScope ?? normalizeTenantScopedConfig(decoratorConfig.tenantScoped),
354
+ fieldCount: fields.length,
355
+ relationships: fields.filter(([, field]) => RELATIONSHIP_TYPES.has(field.type)).map(([name, field]) => `${name} -> ${field.related ?? ""} (${field.type})`).join(", ") || void 0,
356
+ mcpOperations: mcpOperationsFromConfig(decoratorConfig.mcp)
357
+ });
358
+ }
359
+ /**
360
+ * An omitted `mcp` config means full CRUD, not a closed surface — the same rule
361
+ * the knowledge index applies.
362
+ */
363
+ function mcpOperationsFromConfig(config) {
364
+ if (config === false) return [];
365
+ if (typeof config !== "object" || config === null || Array.isArray(config)) return [...DEFAULT_MCP_OPERATIONS];
366
+ const record = config;
367
+ const include = Array.isArray(record.include) ? record.include.filter((item) => typeof item === "string") : DEFAULT_MCP_OPERATIONS;
368
+ const exclude = new Set(Array.isArray(record.exclude) ? record.exclude.filter((item) => typeof item === "string") : []);
369
+ return include.filter((operation) => !exclude.has(operation));
370
+ }
297
371
  async function loadManifestArtifact(projectPath, manifestPath) {
298
372
  const candidates = manifestPath ? [resolve(projectPath, manifestPath)] : DEFAULT_MANIFEST_PATHS.map((candidate) => join(projectPath, candidate));
299
373
  const diagnostics = [];
@@ -1127,7 +1201,7 @@ var TOOLS = [
1127
1201
  },
1128
1202
  {
1129
1203
  name: "introspect-project",
1130
- description: "Scan current directory for SMRT objects",
1204
+ description: "Scan a directory for SMRT objects. Returns a compact summary by default; pass detail: \"full\" for field, schema, and method details.",
1131
1205
  inputSchema: {
1132
1206
  type: "object",
1133
1207
  properties: {
@@ -1139,17 +1213,27 @@ var TOOLS = [
1139
1213
  type: "string",
1140
1214
  description: "Optional manifest path. Defaults to .smrt/manifest.json, dist/manifest.json, then source scanning."
1141
1215
  },
1216
+ detail: {
1217
+ type: "string",
1218
+ enum: ["summary", "full"],
1219
+ default: "summary",
1220
+ description: "summary: one compact record per object. full: complete field/schema/method detail (large)."
1221
+ },
1222
+ maxChars: {
1223
+ type: "number",
1224
+ description: "Character budget for the `objects` payload. Objects past the budget are omitted and reported under `truncated`. Project metadata and diagnostics are always returned in full, so the serialized response is somewhat larger than this budget."
1225
+ },
1142
1226
  includeFields: {
1143
1227
  type: "boolean",
1144
- description: "Include field details"
1228
+ description: "Include field details (detail: \"full\" only)"
1145
1229
  },
1146
1230
  includeRelationships: {
1147
1231
  type: "boolean",
1148
- description: "Analyze relationships"
1232
+ description: "Analyze relationships (detail: \"full\" only)"
1149
1233
  },
1150
1234
  includeMethods: {
1151
1235
  type: "boolean",
1152
- description: "Include public method details"
1236
+ description: "Include public method details (detail: \"full\" only)"
1153
1237
  }
1154
1238
  }
1155
1239
  }
@@ -1265,7 +1349,13 @@ var TOOLS = [
1265
1349
  items: { type: "string" }
1266
1350
  },
1267
1351
  focus: { type: "string" },
1268
- documentation: { type: "string" }
1352
+ documentation: { type: "string" },
1353
+ detail: {
1354
+ type: "string",
1355
+ enum: ["summary", "full"],
1356
+ default: "summary",
1357
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
1358
+ }
1269
1359
  }
1270
1360
  }
1271
1361
  },
@@ -1292,7 +1382,13 @@ var TOOLS = [
1292
1382
  ],
1293
1383
  default: "project"
1294
1384
  },
1295
- package: { type: "string" }
1385
+ package: { type: "string" },
1386
+ detail: {
1387
+ type: "string",
1388
+ enum: ["summary", "full"],
1389
+ default: "summary",
1390
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
1391
+ }
1296
1392
  }
1297
1393
  }
1298
1394
  },
@@ -1317,6 +1413,12 @@ var TOOLS = [
1317
1413
  "both"
1318
1414
  ],
1319
1415
  default: "both"
1416
+ },
1417
+ detail: {
1418
+ type: "string",
1419
+ enum: ["summary", "full"],
1420
+ default: "summary",
1421
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
1320
1422
  }
1321
1423
  }
1322
1424
  }
@@ -1330,7 +1432,13 @@ var TOOLS = [
1330
1432
  rootDir: { type: "string" },
1331
1433
  idea: { type: "string" },
1332
1434
  documentation: { type: "string" },
1333
- focus: { type: "string" }
1435
+ focus: { type: "string" },
1436
+ detail: {
1437
+ type: "string",
1438
+ enum: ["summary", "full"],
1439
+ default: "summary",
1440
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
1441
+ }
1334
1442
  }
1335
1443
  }
1336
1444
  },
@@ -1354,7 +1462,13 @@ var TOOLS = [
1354
1462
  ],
1355
1463
  default: "project"
1356
1464
  },
1357
- package: { type: "string" }
1465
+ package: { type: "string" },
1466
+ detail: {
1467
+ type: "string",
1468
+ enum: ["summary", "full"],
1469
+ default: "summary",
1470
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
1471
+ }
1358
1472
  }
1359
1473
  }
1360
1474
  },
@@ -1367,7 +1481,13 @@ var TOOLS = [
1367
1481
  rootDir: { type: "string" },
1368
1482
  idea: { type: "string" },
1369
1483
  documentation: { type: "string" },
1370
- focus: { type: "string" }
1484
+ focus: { type: "string" },
1485
+ detail: {
1486
+ type: "string",
1487
+ enum: ["summary", "full"],
1488
+ default: "summary",
1489
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
1490
+ }
1371
1491
  }
1372
1492
  }
1373
1493
  },
@@ -1611,6 +1731,8 @@ async function main() {
1611
1731
  smrtPackageCount: index.smrtPackages.length,
1612
1732
  sdkPackageCount: index.sdkPackages.length,
1613
1733
  relationshipsV2: index.relationshipsV2,
1734
+ coverage: index.coverage,
1735
+ diagnostics: index.diagnostics,
1614
1736
  freshness
1615
1737
  }, null, 2);
1616
1738
  break;
@@ -1626,6 +1748,8 @@ async function main() {
1626
1748
  domainKnowledgePackageCount: index.packages.filter((pkg) => pkg.hasDomainKnowledge).length,
1627
1749
  missingDomainKnowledgePackages: index.packages.filter((pkg) => pkg.exportKeys.includes("./smrt-knowledge.json") && !pkg.hasDomainKnowledge).map((pkg) => pkg.name),
1628
1750
  relationshipsV2: index.relationshipsV2,
1751
+ coverage: index.coverage,
1752
+ diagnostics: index.diagnostics,
1629
1753
  freshness
1630
1754
  }, null, 2);
1631
1755
  break;
@@ -1637,22 +1761,18 @@ async function main() {
1637
1761
  result = JSON.stringify(await checkKnowledgeFreshness(args), null, 2);
1638
1762
  break;
1639
1763
  case "build-review-context":
1640
- result = JSON.stringify(await buildReviewContext(args), null, 2);
1641
- break;
1642
1764
  case "build-domain-review-context":
1643
- result = JSON.stringify(await buildReviewContext(args), null, 2);
1765
+ result = JSON.stringify(compactContextResult(await buildReviewContext(args), detailArg(args)), null, 2);
1644
1766
  break;
1645
1767
  case "smrt-review":
1646
- result = JSON.stringify(await smrtReview(args), null, 2);
1768
+ result = JSON.stringify(compactContextResult(await smrtReview(args), detailArg(args)), null, 2);
1647
1769
  break;
1648
1770
  case "build-architecture-context":
1649
- result = JSON.stringify(await buildArchitectureContext(args), null, 2);
1650
- break;
1651
1771
  case "build-domain-architecture-context":
1652
- result = JSON.stringify(await buildArchitectureContext(args), null, 2);
1772
+ result = JSON.stringify(compactContextResult(await buildArchitectureContext(args), detailArg(args)), null, 2);
1653
1773
  break;
1654
1774
  case "smrt-architecture":
1655
- result = JSON.stringify(await smrtArchitecture(args), null, 2);
1775
+ result = JSON.stringify(compactContextResult(await smrtArchitecture(args), detailArg(args)), null, 2);
1656
1776
  break;
1657
1777
  case "list-agent-skills":
1658
1778
  result = JSON.stringify({ skills: listAgentSkills() }, null, 2);
@@ -1759,10 +1879,56 @@ function sanitizeKnowledgeIndex(index) {
1759
1879
  sdkPackages: packages.filter((pkg) => pkg.kind === "sdk")
1760
1880
  };
1761
1881
  }
1882
+ /**
1883
+ * Compact projection of a context result for MCP callers (#2143).
1884
+ *
1885
+ * `selectedPackages` carries each package's whole authored `agentDoc`, its
1886
+ * module-doc contents, and its full domain manifest. A three-package downstream
1887
+ * product serialized to 329,003 characters that way, which is what made these
1888
+ * tools unusable as planning aids. `detail: "full"` returns the whole shape.
1889
+ */
1890
+ function compactContextResult(result, detail) {
1891
+ const source = result;
1892
+ if (detail === "full") return source;
1893
+ const compact = {
1894
+ ...source,
1895
+ detail: "summary"
1896
+ };
1897
+ for (const key of ["selectedPackages", "selectedSdkPackages"]) {
1898
+ const packages = source[key];
1899
+ if (!Array.isArray(packages)) continue;
1900
+ compact[key] = packages.map(compactKnowledgePackage);
1901
+ }
1902
+ return compact;
1903
+ }
1904
+ function compactKnowledgePackage(pkg) {
1905
+ return {
1906
+ name: pkg.name,
1907
+ version: pkg.version,
1908
+ kind: pkg.kind,
1909
+ directory: pkg.relativeDirectory,
1910
+ objectSource: pkg.objectSource,
1911
+ ...pkg.objectSourceReason ? { objectSourceReason: pkg.objectSourceReason } : {},
1912
+ docs: packageDocPaths(pkg),
1913
+ domainKnowledgePath: pkg.domainKnowledgePath,
1914
+ relationshipFeatures: pkg.relationshipFeatures,
1915
+ smrtDependencies: pkg.smrtDependencies,
1916
+ sdkDependencies: pkg.sdkDependencies,
1917
+ exportKeys: pkg.exportKeys,
1918
+ objectCount: pkg.objects.length,
1919
+ objects: pkg.objects.map((object) => object.tableName ? `${object.qualifiedName ?? object.className} (${object.tableName})` : object.qualifiedName ?? object.className),
1920
+ mcpToolCount: pkg.mcpTools.length
1921
+ };
1922
+ }
1923
+ function detailArg(args) {
1924
+ const detail = args?.detail;
1925
+ return typeof detail === "string" ? detail : void 0;
1926
+ }
1762
1927
  function sanitizeKnowledgePackage(pkg) {
1763
- const { directory: _directory, objects, ...rest } = pkg;
1928
+ const { directory: _directory, objects, checkedObjectPaths, ...rest } = pkg;
1764
1929
  return {
1765
1930
  ...rest,
1931
+ checkedObjectPaths: checkedObjectPaths.map((path) => sanitizePath(path) ?? path),
1766
1932
  objects: objects.map((object) => ({
1767
1933
  ...object,
1768
1934
  filePath: sanitizePath(object.filePath)