@happyvertical/smrt-dev-mcp 0.40.63 → 0.40.65

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.
@@ -4,6 +4,680 @@ import { execFileSync } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
5
5
  import { MODULE_DOC_HASH_PREFIX, readAgentModuleDocs } from "@happyvertical/smrt-core/knowledge";
6
6
  import { ManifestAdapter, OxcScanner } from "@happyvertical/smrt-scanner";
7
+ //#region src/tool-catalog.ts
8
+ var REVIEW_SKILL_NAME = "smrt-code-review";
9
+ var JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
10
+ function compareToolNames(left, right) {
11
+ return left < right ? -1 : left > right ? 1 : 0;
12
+ }
13
+ /**
14
+ * Stable success/error envelope for development tools. `data` preserves each
15
+ * tool's existing payload exactly; coverage/diagnostics are promoted only when
16
+ * the underlying result actually reports them.
17
+ */
18
+ var DEV_MCP_OUTPUT_SCHEMA = {
19
+ $schema: JSON_SCHEMA_2020_12,
20
+ type: "object",
21
+ additionalProperties: false,
22
+ required: [
23
+ "ok",
24
+ "coverage",
25
+ "diagnostics",
26
+ "data"
27
+ ],
28
+ properties: {
29
+ ok: { type: "boolean" },
30
+ coverage: {
31
+ type: ["object", "null"],
32
+ additionalProperties: true
33
+ },
34
+ diagnostics: {
35
+ type: "array",
36
+ items: {
37
+ type: "object",
38
+ additionalProperties: true
39
+ }
40
+ },
41
+ data: {}
42
+ }
43
+ };
44
+ var TOOLS = [
45
+ {
46
+ name: "generate-smrt-class",
47
+ description: "Generate a complete SMRT class with @smrt() decorator",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ className: {
52
+ type: "string",
53
+ description: "Name of the class (PascalCase)"
54
+ },
55
+ properties: {
56
+ type: "array",
57
+ description: "Array of property definitions",
58
+ items: {
59
+ type: "object",
60
+ properties: {
61
+ name: { type: "string" },
62
+ type: {
63
+ type: "string",
64
+ enum: [
65
+ "text",
66
+ "integer",
67
+ "decimal",
68
+ "boolean",
69
+ "datetime",
70
+ "json"
71
+ ]
72
+ },
73
+ required: { type: "boolean" },
74
+ nullable: { type: "boolean" },
75
+ description: { type: "string" },
76
+ defaultValue: { oneOf: [
77
+ { type: "string" },
78
+ { type: "number" },
79
+ { type: "boolean" },
80
+ { type: "object" },
81
+ { type: "null" }
82
+ ] }
83
+ },
84
+ required: ["name", "type"]
85
+ }
86
+ },
87
+ baseClass: {
88
+ type: "string",
89
+ enum: ["SmrtObject", "SmrtCollection"],
90
+ default: "SmrtObject"
91
+ },
92
+ template: {
93
+ type: "string",
94
+ enum: [
95
+ "basic",
96
+ "global-catalog",
97
+ "optional-catalog",
98
+ "tenant-project-object",
99
+ "tenant-event-log-object",
100
+ "cross-package-reference"
101
+ ],
102
+ default: "basic"
103
+ },
104
+ tableName: { type: "string" },
105
+ conflictColumns: {
106
+ type: "array",
107
+ items: { type: "string" }
108
+ },
109
+ tenantScoped: { oneOf: [{ type: "boolean" }, {
110
+ type: "object",
111
+ properties: {
112
+ mode: {
113
+ type: "string",
114
+ enum: ["required", "optional"]
115
+ },
116
+ field: { type: "string" },
117
+ autoFilter: { type: "boolean" },
118
+ autoPopulate: { type: "boolean" },
119
+ allowSuperAdminBypass: { type: "boolean" }
120
+ }
121
+ }] },
122
+ includeTenantIdField: { type: "boolean" },
123
+ relationships: {
124
+ type: "array",
125
+ items: {
126
+ type: "object",
127
+ properties: {
128
+ name: { type: "string" },
129
+ type: {
130
+ type: "string",
131
+ enum: [
132
+ "foreignKey",
133
+ "crossPackageRef",
134
+ "oneToMany",
135
+ "manyToMany"
136
+ ]
137
+ },
138
+ related: { type: "string" },
139
+ required: { type: "boolean" },
140
+ nullable: { type: "boolean" },
141
+ description: { type: "string" },
142
+ validate: { type: "boolean" },
143
+ foreignKey: { type: "string" },
144
+ through: { type: "string" },
145
+ sourceKey: { type: "string" },
146
+ targetKey: { type: "string" }
147
+ },
148
+ required: [
149
+ "name",
150
+ "type",
151
+ "related"
152
+ ]
153
+ }
154
+ },
155
+ includeCompanionSnippets: {
156
+ type: "boolean",
157
+ default: false
158
+ },
159
+ includeApiConfig: {
160
+ type: "boolean",
161
+ default: true
162
+ },
163
+ includeMcpConfig: {
164
+ type: "boolean",
165
+ default: true
166
+ },
167
+ includeCliConfig: {
168
+ type: "boolean",
169
+ default: true
170
+ }
171
+ },
172
+ required: ["className", "properties"]
173
+ }
174
+ },
175
+ {
176
+ name: "introspect-project",
177
+ description: "Scan a directory for SMRT objects. Returns a compact summary by default; pass detail: \"full\" for field, schema, and method details.",
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ directory: {
182
+ type: "string",
183
+ description: "Project directory (default: cwd)"
184
+ },
185
+ manifestPath: {
186
+ type: "string",
187
+ description: "Optional manifest path. Defaults to .smrt/manifest.json, dist/manifest.json, then source scanning."
188
+ },
189
+ detail: {
190
+ type: "string",
191
+ enum: ["summary", "full"],
192
+ default: "summary",
193
+ description: "summary: one compact record per object. full: complete field/schema/method detail (large)."
194
+ },
195
+ maxChars: {
196
+ type: "number",
197
+ 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."
198
+ },
199
+ includeFields: {
200
+ type: "boolean",
201
+ description: "Include field details (detail: \"full\" only)"
202
+ },
203
+ includeRelationships: {
204
+ type: "boolean",
205
+ description: "Analyze relationships (detail: \"full\" only)"
206
+ },
207
+ includeMethods: {
208
+ type: "boolean",
209
+ description: "Include public method details (detail: \"full\" only)"
210
+ }
211
+ }
212
+ }
213
+ },
214
+ {
215
+ name: "review-smrt-project",
216
+ description: "Advisory ecosystem alignment review for downstream SMRT projects",
217
+ inputSchema: {
218
+ type: "object",
219
+ properties: {
220
+ directory: {
221
+ type: "string",
222
+ description: "Project directory (default: cwd)"
223
+ },
224
+ rootDir: {
225
+ type: "string",
226
+ description: "Compatibility alias for directory"
227
+ },
228
+ includeSourceEvidence: {
229
+ type: "boolean",
230
+ description: "Include file and line evidence in findings",
231
+ default: true
232
+ },
233
+ maxFindings: {
234
+ type: "number",
235
+ description: "Optional maximum number of findings to return"
236
+ }
237
+ }
238
+ }
239
+ },
240
+ {
241
+ name: "reflect-knowledge",
242
+ description: "Report deterministic SMRT + HappyVertical SDK knowledge coverage and freshness",
243
+ inputSchema: {
244
+ type: "object",
245
+ properties: { rootDir: {
246
+ type: "string",
247
+ description: "Project root directory (default: cwd)"
248
+ } }
249
+ }
250
+ },
251
+ {
252
+ name: "reflect-domain-knowledge",
253
+ description: "Report domain-scoped SMRT knowledge artifacts, SDK packages, and freshness",
254
+ inputSchema: {
255
+ type: "object",
256
+ properties: {
257
+ rootDir: { type: "string" },
258
+ scope: {
259
+ type: "string",
260
+ enum: [
261
+ "project",
262
+ "local",
263
+ "package",
264
+ "sdk",
265
+ "installed"
266
+ ],
267
+ default: "project"
268
+ },
269
+ package: { type: "string" }
270
+ }
271
+ }
272
+ },
273
+ {
274
+ name: "check-knowledge-freshness",
275
+ description: "Run deterministic freshness checks for SMRT agent knowledge",
276
+ inputSchema: {
277
+ type: "object",
278
+ properties: {
279
+ rootDir: { type: "string" },
280
+ changed: {
281
+ type: "boolean",
282
+ description: "Limit stale-pattern checks to changed files"
283
+ },
284
+ strict: {
285
+ type: "boolean",
286
+ description: "Treat stale-pattern findings as errors"
287
+ }
288
+ }
289
+ }
290
+ },
291
+ {
292
+ name: "check-domain-knowledge",
293
+ description: "Run deterministic freshness checks for domain knowledge artifacts",
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: {
297
+ rootDir: { type: "string" },
298
+ changed: { type: "boolean" },
299
+ strict: { type: "boolean" },
300
+ scope: {
301
+ type: "string",
302
+ enum: [
303
+ "project",
304
+ "local",
305
+ "package",
306
+ "sdk",
307
+ "installed"
308
+ ],
309
+ default: "project"
310
+ },
311
+ package: { type: "string" }
312
+ }
313
+ }
314
+ },
315
+ {
316
+ name: "build-review-context",
317
+ description: "Build model-ready SMRT review context from changed files and optional focus text",
318
+ inputSchema: {
319
+ type: "object",
320
+ properties: {
321
+ rootDir: { type: "string" },
322
+ changedFiles: {
323
+ type: "array",
324
+ items: { type: "string" }
325
+ },
326
+ focus: { type: "string" },
327
+ documentation: { type: "string" },
328
+ detail: {
329
+ type: "string",
330
+ enum: ["summary", "full"],
331
+ default: "summary",
332
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
333
+ }
334
+ }
335
+ }
336
+ },
337
+ {
338
+ name: "build-domain-review-context",
339
+ description: "Build domain-scoped model-ready SMRT review context and prompt bundle",
340
+ inputSchema: {
341
+ type: "object",
342
+ properties: {
343
+ rootDir: { type: "string" },
344
+ changedFiles: {
345
+ type: "array",
346
+ items: { type: "string" }
347
+ },
348
+ focus: { type: "string" },
349
+ documentation: { type: "string" },
350
+ scope: {
351
+ type: "string",
352
+ enum: [
353
+ "project",
354
+ "local",
355
+ "package",
356
+ "sdk",
357
+ "installed"
358
+ ],
359
+ default: "project"
360
+ },
361
+ package: { type: "string" },
362
+ detail: {
363
+ type: "string",
364
+ enum: ["summary", "full"],
365
+ default: "summary",
366
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
367
+ }
368
+ }
369
+ }
370
+ },
371
+ {
372
+ name: "smrt-review",
373
+ description: "Return deterministic review findings and/or a reusable model prompt bundle. For a formal downstream review, first call get-agent-skill with { \"name\": \"smrt-code-review\" } or load the smrt-code-review MCP prompt/resource.",
374
+ inputSchema: {
375
+ type: "object",
376
+ properties: {
377
+ rootDir: { type: "string" },
378
+ changedFiles: {
379
+ type: "array",
380
+ items: { type: "string" }
381
+ },
382
+ focus: { type: "string" },
383
+ documentation: { type: "string" },
384
+ mode: {
385
+ type: "string",
386
+ enum: [
387
+ "findings",
388
+ "prompt-bundle",
389
+ "both"
390
+ ],
391
+ default: "both"
392
+ },
393
+ detail: {
394
+ type: "string",
395
+ enum: ["summary", "full"],
396
+ default: "summary",
397
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
398
+ }
399
+ }
400
+ }
401
+ },
402
+ {
403
+ name: "build-architecture-context",
404
+ description: "Build model-ready SMRT architecture context from an idea or documentation",
405
+ inputSchema: {
406
+ type: "object",
407
+ properties: {
408
+ rootDir: { type: "string" },
409
+ idea: { type: "string" },
410
+ documentation: { type: "string" },
411
+ focus: { type: "string" },
412
+ detail: {
413
+ type: "string",
414
+ enum: ["summary", "full"],
415
+ default: "summary",
416
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
417
+ }
418
+ }
419
+ }
420
+ },
421
+ {
422
+ name: "build-domain-architecture-context",
423
+ description: "Build domain-scoped model-ready SMRT architecture context and prompt bundle",
424
+ inputSchema: {
425
+ type: "object",
426
+ properties: {
427
+ rootDir: { type: "string" },
428
+ idea: { type: "string" },
429
+ documentation: { type: "string" },
430
+ focus: { type: "string" },
431
+ scope: {
432
+ type: "string",
433
+ enum: [
434
+ "project",
435
+ "local",
436
+ "package",
437
+ "sdk",
438
+ "installed"
439
+ ],
440
+ default: "project"
441
+ },
442
+ package: { type: "string" },
443
+ detail: {
444
+ type: "string",
445
+ enum: ["summary", "full"],
446
+ default: "summary",
447
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
448
+ }
449
+ }
450
+ }
451
+ },
452
+ {
453
+ name: "smrt-architecture",
454
+ description: "Suggest SMRT and HappyVertical SDK packages and return an architecture prompt bundle",
455
+ inputSchema: {
456
+ type: "object",
457
+ properties: {
458
+ rootDir: { type: "string" },
459
+ idea: { type: "string" },
460
+ documentation: { type: "string" },
461
+ focus: { type: "string" },
462
+ detail: {
463
+ type: "string",
464
+ enum: ["summary", "full"],
465
+ default: "summary",
466
+ description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
467
+ }
468
+ }
469
+ }
470
+ },
471
+ {
472
+ name: "list-agent-skills",
473
+ description: "List bundled harness-agnostic agent skills shipped with smrt-dev-mcp",
474
+ inputSchema: {
475
+ type: "object",
476
+ properties: {}
477
+ }
478
+ },
479
+ {
480
+ name: "get-agent-skill",
481
+ description: "Return a bundled harness-agnostic agent skill as Markdown plus optional references",
482
+ inputSchema: {
483
+ type: "object",
484
+ properties: {
485
+ name: {
486
+ type: "string",
487
+ enum: [REVIEW_SKILL_NAME],
488
+ description: "Bundled agent skill name"
489
+ },
490
+ includeReferences: {
491
+ type: "boolean",
492
+ default: true,
493
+ description: "Include referenced files with the skill bundle"
494
+ }
495
+ },
496
+ required: ["name"]
497
+ }
498
+ }
499
+ ].map((tool) => ({
500
+ ...tool,
501
+ inputSchema: {
502
+ ...tool.inputSchema,
503
+ $schema: JSON_SCHEMA_2020_12
504
+ },
505
+ outputSchema: DEV_MCP_OUTPUT_SCHEMA
506
+ })).sort((left, right) => compareToolNames(left.name, right.name));
507
+ //#endregion
508
+ //#region src/knowledge/mcp-docs.ts
509
+ var PACKAGE_NAME = "@happyvertical/smrt-dev-mcp";
510
+ /**
511
+ * Compare the authored development-MCP catalog to the actual exported tools.
512
+ * The parser intentionally checks only stable structural Markdown: tool names
513
+ * plus top-level input-property names, types, enums, and requiredness.
514
+ * Descriptive prose and prose-embedded defaults remain authored.
515
+ */
516
+ function checkMcpToolDocumentation(rootDir, packageDir, tools) {
517
+ const readmePath = join(packageDir, "README.md");
518
+ const agentsPath = join(packageDir, "AGENTS.md");
519
+ const issues = [];
520
+ if (!existsSync(readmePath)) {
521
+ issues.push(issue(rootDir, readmePath, "mcp-readme-missing", "MCP package README.md is missing"));
522
+ return issues;
523
+ }
524
+ const expectedTools = [...tools].sort((left, right) => compareNames(left.name, right.name));
525
+ const { sections: readmeSections, names: documentedToolNames } = readToolSections(readFileSync(readmePath, "utf8"));
526
+ const expectedToolNames = expectedTools.map((tool) => tool.name);
527
+ const readmeCatalogDiff = setDifference(expectedToolNames, documentedToolNames);
528
+ if (readmeCatalogDiff) issues.push(issue(rootDir, readmePath, "mcp-readme-tool-drift", `README tool catalog differs from exported TOOLS (${readmeCatalogDiff})`));
529
+ for (const tool of expectedTools) {
530
+ const section = readmeSections.get(tool.name);
531
+ if (section === void 0) continue;
532
+ const { parameters: documentedParameters, names: documentedParameterNames } = readParameterTable(section);
533
+ const expectedProperties = Object.keys(tool.inputSchema.properties ?? {}).sort(compareNames);
534
+ const propertyDiff = setDifference(expectedProperties, documentedParameterNames);
535
+ const required = new Set(tool.inputSchema.required ?? []);
536
+ const requiredness = expectedProperties.filter((name) => documentedParameters.has(name) && documentedParameters.get(name)?.required !== required.has(name)).map((name) => `${name} expected ${required.has(name) ? "Yes" : "No"} but documented ${documentedParameters.get(name)?.required ? "Yes" : "No"}`);
537
+ const inputProperties = tool.inputSchema.properties ?? {};
538
+ const types = expectedProperties.filter((name) => documentedParameters.has(name)).flatMap((name) => {
539
+ const expected = formatSchemaType(inputProperties[name]);
540
+ const documented = documentedParameters.get(name)?.type;
541
+ return documented === expected ? [] : [`${name} expected ${expected} but documented ${documented}`];
542
+ });
543
+ const details = [
544
+ propertyDiff,
545
+ requiredness.length > 0 ? `requiredness: ${requiredness.join(", ")}` : void 0,
546
+ types.length > 0 ? `types: ${types.join(", ")}` : void 0
547
+ ].filter((value) => Boolean(value));
548
+ if (details.length > 0) issues.push(issue(rootDir, readmePath, "mcp-readme-schema-drift", `README parameters for "${tool.name}" differ from its exported input schema (${details.join("; ")})`));
549
+ }
550
+ if (!existsSync(agentsPath)) return issues;
551
+ const agentsCatalogDiff = setDifference(expectedToolNames, readAgentToolNames(readFileSync(agentsPath, "utf8")).sort(compareNames));
552
+ if (agentsCatalogDiff) issues.push(issue(rootDir, agentsPath, "mcp-agents-tool-drift", `AGENTS.md tool catalog differs from exported TOOLS (${agentsCatalogDiff})`));
553
+ return issues;
554
+ }
555
+ function issue(rootDir, path, code, message) {
556
+ return {
557
+ severity: "error",
558
+ code,
559
+ message,
560
+ file: relative(rootDir, path),
561
+ packageName: PACKAGE_NAME
562
+ };
563
+ }
564
+ function readToolSections(markdown) {
565
+ const availableTools = sectionAfterHeading(markdown, "Available Tools");
566
+ if (!availableTools) return {
567
+ sections: /* @__PURE__ */ new Map(),
568
+ names: []
569
+ };
570
+ const headings = [...availableTools.matchAll(/^### `([^`]+)`\s*$/gm)];
571
+ return {
572
+ sections: new Map(headings.map((heading, index) => {
573
+ const start = (heading.index ?? 0) + heading[0].length;
574
+ const end = headings[index + 1]?.index ?? availableTools.length;
575
+ return [heading[1], availableTools.slice(start, end)];
576
+ })),
577
+ names: headings.map((heading) => heading[1])
578
+ };
579
+ }
580
+ function readParameterTable(section) {
581
+ const parameters = /* @__PURE__ */ new Map();
582
+ const names = [];
583
+ for (const line of section.split("\n")) {
584
+ if (!line.trimStart().startsWith("|")) continue;
585
+ const cells = splitMarkdownRow(line);
586
+ const name = /^`([^`]+)`$/.exec(cells[0]?.trim() ?? "")?.[1];
587
+ const required = cells[2]?.trim();
588
+ if (!name || required !== "Yes" && required !== "No") continue;
589
+ names.push(name);
590
+ parameters.set(name, {
591
+ name,
592
+ required: required === "Yes",
593
+ type: normalizeDocumentedType(cells[1] ?? "")
594
+ });
595
+ }
596
+ return {
597
+ parameters,
598
+ names
599
+ };
600
+ }
601
+ function normalizeDocumentedType(value) {
602
+ const trimmed = value.trim();
603
+ return (trimmed.startsWith("`") && trimmed.endsWith("`") ? trimmed.slice(1, -1) : trimmed).replaceAll("\\|", "|").replace(/\s*\|\s*/g, " | ").trim();
604
+ }
605
+ function formatSchemaType(value) {
606
+ if (!isRecord(value)) return "unknown";
607
+ if (Array.isArray(value.oneOf)) return unique(value.oneOf.map(formatSchemaType)).join(" | ");
608
+ if (Array.isArray(value.enum) && value.enum.length > 0) return value.enum.map(formatEnumValue).join(" | ");
609
+ if (value.type === "array") {
610
+ const itemType = formatSchemaType(value.items);
611
+ return itemType.includes(" | ") ? `(${itemType})[]` : `${itemType}[]`;
612
+ }
613
+ if (Array.isArray(value.type)) return unique(value.type.map(String)).join(" | ");
614
+ return typeof value.type === "string" ? value.type : "unknown";
615
+ }
616
+ function formatEnumValue(value) {
617
+ if (typeof value === "string") return `'${value.replaceAll("'", "\\'")}'`;
618
+ return String(value);
619
+ }
620
+ function isRecord(value) {
621
+ return value !== null && typeof value === "object" && !Array.isArray(value);
622
+ }
623
+ function unique(values) {
624
+ return [...new Set(values)];
625
+ }
626
+ function splitMarkdownRow(row) {
627
+ const content = row.trim().replace(/^\|/, "").replace(/\|$/, "");
628
+ const cells = [];
629
+ let cell = "";
630
+ for (let index = 0; index < content.length; index += 1) {
631
+ const character = content[index];
632
+ if (character === "|" && content[index - 1] !== "\\") {
633
+ cells.push(cell);
634
+ cell = "";
635
+ } else cell += character;
636
+ }
637
+ cells.push(cell);
638
+ return cells;
639
+ }
640
+ function readAgentToolNames(markdown) {
641
+ const toolsSection = sectionAfterHeading(markdown, "Tools");
642
+ if (!toolsSection) return [];
643
+ return [...toolsSection.matchAll(/^\|\s*`([^`]+)`\s*\|/gm)].map((match) => match[1]);
644
+ }
645
+ function sectionAfterHeading(markdown, heading) {
646
+ const marker = new RegExp(`^## ${heading}\\s*$`, "m").exec(markdown);
647
+ if (!marker) return "";
648
+ const start = (marker.index ?? 0) + marker[0].length;
649
+ const remainder = markdown.slice(start);
650
+ const nextHeading = /^##\s+/m.exec(remainder);
651
+ return remainder.slice(0, nextHeading?.index ?? remainder.length);
652
+ }
653
+ function setDifference(expected, actual) {
654
+ const expectedSet = new Set(expected);
655
+ const actualSet = new Set(actual);
656
+ const missing = expected.filter((value) => !actualSet.has(value)).sort(compareNames);
657
+ const extra = unique(actual.filter((value) => !expectedSet.has(value))).sort(compareNames);
658
+ const expectedDuplicates = duplicateNames(expected);
659
+ const actualDuplicates = duplicateNames(actual);
660
+ const parts = [
661
+ missing.length > 0 ? `missing: ${missing.join(", ")}` : void 0,
662
+ extra.length > 0 ? `extra: ${extra.join(", ")}` : void 0,
663
+ expectedDuplicates.length > 0 ? `exported duplicates: ${expectedDuplicates.join(", ")}` : void 0,
664
+ actualDuplicates.length > 0 ? `duplicates: ${actualDuplicates.join(", ")}` : void 0
665
+ ].filter((value) => Boolean(value));
666
+ return parts.length > 0 ? parts.join("; ") : void 0;
667
+ }
668
+ function duplicateNames(values) {
669
+ const seen = /* @__PURE__ */ new Set();
670
+ const duplicates = /* @__PURE__ */ new Set();
671
+ for (const value of values) {
672
+ if (seen.has(value)) duplicates.add(value);
673
+ seen.add(value);
674
+ }
675
+ return [...duplicates].sort(compareNames);
676
+ }
677
+ function compareNames(left, right) {
678
+ return left < right ? -1 : left > right ? 1 : 0;
679
+ }
680
+ //#endregion
7
681
  //#region src/knowledge/index.ts
8
682
  var SDK_PACKAGE_NAMES = /* @__PURE__ */ new Set([
9
683
  "@happyvertical/ai",
@@ -332,6 +1006,8 @@ async function checkKnowledgeFreshnessFromIndex(index, options = {}) {
332
1006
  });
333
1007
  }
334
1008
  for (const pkg of authoredPackages) issues.push(...checkDomainKnowledgeArtifact(index.rootDir, pkg));
1009
+ const devMcpPackage = authoredPackages.find((pkg) => pkg.name === "@happyvertical/smrt-dev-mcp");
1010
+ if (devMcpPackage) issues.push(...checkMcpToolDocumentation(index.rootDir, devMcpPackage.directory, TOOLS));
335
1011
  issues.push(...findStalePatternIssues(index.rootDir, changedFiles));
336
1012
  const effectiveIssues = issues.map((issue) => issue.code.startsWith("stale-") ? {
337
1013
  ...issue,
@@ -1926,6 +2602,6 @@ function sortJson(value) {
1926
2602
  return value;
1927
2603
  }
1928
2604
  //#endregion
1929
- export { checkKnowledgeFreshnessFromIndex as a, renderFreshnessResult as c, smrtReview as d, checkKnowledgeFreshness as i, renderKnowledgeIndexMarkdown as l, buildKnowledgeIndex as n, diffKnowledgeIndex as o, buildReviewContext as r, packageDocPaths as s, buildArchitectureContext as t, smrtArchitecture as u };
2605
+ export { checkKnowledgeFreshnessFromIndex as a, renderFreshnessResult as c, smrtReview as d, REVIEW_SKILL_NAME as f, checkKnowledgeFreshness as i, renderKnowledgeIndexMarkdown as l, buildKnowledgeIndex as n, diffKnowledgeIndex as o, TOOLS as p, buildReviewContext as r, packageDocPaths as s, buildArchitectureContext as t, smrtArchitecture as u };
1930
2606
 
1931
- //# sourceMappingURL=knowledge-CCX7oQzQ.js.map
2607
+ //# sourceMappingURL=knowledge-A7-gIjW4.js.map