@happyvertical/smrt-dev-mcp 0.30.0

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 ADDED
@@ -0,0 +1,2039 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, existsSync, realpathSync } from 'node:fs';
3
+ import { join, dirname, resolve, relative, isAbsolute, sep } from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, McpError, ErrorCode, ListResourcesRequestSchema, ReadResourceRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
+ import { c as buildReviewContext, b as buildArchitectureContext, a as buildKnowledgeIndex, s as smrtArchitecture, h as smrtReview, d as checkKnowledgeFreshness, e as checkKnowledgeFreshnessFromIndex } from './index-DF0HSB_8.js';
9
+ import { access, readFile, readdir } from 'node:fs/promises';
10
+ import { ManifestGenerator } from '@happyvertical/smrt-core/scanner';
11
+ import { OxcScanner, ManifestAdapter } from '@happyvertical/smrt-scanner';
12
+
13
+ const AGENT_SKILLS = [
14
+ {
15
+ name: "smrt-code-review",
16
+ description: "Harness-agnostic downstream SMRT code review workflow using smrt-dev-mcp deterministic context and prompt bundles.",
17
+ path: "agent-skills/smrt-code-review/SKILL.md",
18
+ skillFile: "agent-skills/smrt-code-review/SKILL.md",
19
+ references: ["agent-skills/smrt-code-review/references/review-output.md"]
20
+ }
21
+ ];
22
+ function listAgentSkills() {
23
+ return AGENT_SKILLS.map(({ skillFile: _skillFile, ...skill }) => skill);
24
+ }
25
+ function getAgentSkill(options) {
26
+ const skill = AGENT_SKILLS.find((item) => item.name === options.name);
27
+ if (!skill) {
28
+ throw new Error(`Unknown agent skill: ${options.name}`);
29
+ }
30
+ return {
31
+ name: skill.name,
32
+ description: skill.description,
33
+ path: skill.path,
34
+ references: skill.references,
35
+ skillMarkdown: readPackageFile(skill.skillFile),
36
+ referenceFiles: options.includeReferences === false ? [] : skill.references.map((path) => ({
37
+ path,
38
+ content: readPackageFile(path)
39
+ }))
40
+ };
41
+ }
42
+ function readPackageFile(relativePath) {
43
+ const packageRoot = resolvePackageRoot();
44
+ return readFileSync(join(packageRoot, relativePath), "utf8");
45
+ }
46
+ function resolvePackageRoot() {
47
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
48
+ if (!existsSync(join(packageRoot, "package.json"))) {
49
+ throw new Error(
50
+ `Unable to resolve smrt-dev-mcp package root from ${import.meta.url}`
51
+ );
52
+ }
53
+ return packageRoot;
54
+ }
55
+
56
+ const TYPE_MAPPING = {
57
+ text: { tsType: "string", defaultValue: "''" },
58
+ integer: { tsType: "number", defaultValue: "0" },
59
+ decimal: { tsType: "number", defaultValue: "0.0" },
60
+ boolean: { tsType: "boolean", defaultValue: "false" },
61
+ datetime: { tsType: "Date", defaultValue: "new Date()" },
62
+ json: { tsType: "any", defaultValue: "{}" }
63
+ };
64
+ async function generateSmrtClass(args) {
65
+ const normalized = normalizeArgs(args);
66
+ const {
67
+ className,
68
+ properties,
69
+ relationships,
70
+ baseClass,
71
+ tableName,
72
+ conflictColumns,
73
+ tenantScoped,
74
+ includeTenantIdField,
75
+ includeApiConfig,
76
+ includeMcpConfig,
77
+ includeCliConfig,
78
+ includeCompanionSnippets
79
+ } = normalized;
80
+ const coreImports = /* @__PURE__ */ new Set([baseClass, "smrt"]);
81
+ if (needsFieldDecorator(properties)) coreImports.add("field");
82
+ for (const relationship of relationships) {
83
+ coreImports.add(relationship.type);
84
+ }
85
+ const imports = [
86
+ `import { ${Array.from(coreImports).join(", ")} } from '@happyvertical/smrt-core';`
87
+ ];
88
+ if (tenantScoped) {
89
+ const tenancyImports = ["TenantScoped"];
90
+ if (includeTenantIdField) tenancyImports.push("tenantId");
91
+ imports.push(
92
+ `import { ${tenancyImports.join(", ")} } from '@happyvertical/smrt-tenancy';`
93
+ );
94
+ }
95
+ const decoratorLines = [
96
+ ...tenantScoped ? [`@TenantScoped(${renderObjectLiteral({ ...tenantScoped })})`] : [],
97
+ renderSmrtDecorator({
98
+ includeApiConfig,
99
+ includeMcpConfig,
100
+ includeCliConfig,
101
+ tableName,
102
+ conflictColumns
103
+ })
104
+ ];
105
+ const classMembers = [
106
+ ...includeTenantIdField && tenantScoped ? [renderTenantIdField(tenantScoped)] : [],
107
+ ...properties.map(renderProperty),
108
+ ...relationships.map(renderRelationship)
109
+ ].filter(Boolean);
110
+ const companionSnippets = includeCompanionSnippets ? `
111
+ ${renderCompanionSnippets(className, Boolean(tenantScoped))}` : "";
112
+ return `${imports.join("\n")}
113
+
114
+ ${decoratorLines.join("\n")}
115
+ export class ${className} extends ${baseClass} {
116
+ ${classMembers.join("\n\n")}
117
+
118
+ constructor(options: any = {}) {
119
+ super(options);
120
+ Object.assign(this, options);
121
+ }
122
+ }
123
+ ${companionSnippets}`;
124
+ }
125
+ function normalizeArgs(args) {
126
+ const template = args.template ?? "basic";
127
+ const templateDefaults = defaultsForTemplate(template);
128
+ const tenantScoped = args.tenantScoped === true ? templateDefaults.tenantScoped ?? { mode: "required" } : args.tenantScoped === false ? void 0 : args.tenantScoped ?? templateDefaults.tenantScoped;
129
+ return {
130
+ className: args.className,
131
+ properties: args.properties,
132
+ baseClass: args.baseClass ?? "SmrtObject",
133
+ template,
134
+ tableName: args.tableName ?? templateDefaults.tableName,
135
+ conflictColumns: args.conflictColumns ?? templateDefaults.conflictColumns ?? [],
136
+ tenantScoped: normalizeTenantScoped(tenantScoped),
137
+ includeTenantIdField: args.includeTenantIdField ?? templateDefaults.includeTenantIdField ?? Boolean(tenantScoped),
138
+ relationships: args.relationships ?? [],
139
+ includeApiConfig: args.includeApiConfig ?? true,
140
+ includeMcpConfig: args.includeMcpConfig ?? true,
141
+ includeCliConfig: args.includeCliConfig ?? true,
142
+ includeCompanionSnippets: args.includeCompanionSnippets ?? false
143
+ };
144
+ }
145
+ function defaultsForTemplate(template) {
146
+ switch (template) {
147
+ case "optional-catalog":
148
+ return {
149
+ tenantScoped: { mode: "optional" },
150
+ includeTenantIdField: true,
151
+ conflictColumns: ["tenant_id", "slug"]
152
+ };
153
+ case "tenant-project-object":
154
+ return {
155
+ tenantScoped: { mode: "required" },
156
+ includeTenantIdField: true
157
+ };
158
+ case "tenant-event-log-object":
159
+ return {
160
+ tenantScoped: { mode: "optional" },
161
+ includeTenantIdField: true
162
+ };
163
+ case "global-catalog":
164
+ return { conflictColumns: ["slug"] };
165
+ case "cross-package-reference":
166
+ case "basic":
167
+ return {};
168
+ }
169
+ }
170
+ function normalizeTenantScoped(value) {
171
+ if (!value) return void 0;
172
+ return {
173
+ mode: typeof value === "object" ? value.mode ?? "required" : "required",
174
+ field: typeof value === "object" ? value.field ?? "tenantId" : "tenantId",
175
+ autoFilter: typeof value === "object" ? value.autoFilter : void 0,
176
+ autoPopulate: typeof value === "object" ? value.autoPopulate : void 0,
177
+ allowSuperAdminBypass: typeof value === "object" ? value.allowSuperAdminBypass : void 0
178
+ };
179
+ }
180
+ function renderSmrtDecorator(options) {
181
+ const decoratorConfig = {};
182
+ if (options.tableName) {
183
+ decoratorConfig.tableName = options.tableName;
184
+ }
185
+ if (options.conflictColumns.length > 0) {
186
+ decoratorConfig.conflictColumns = options.conflictColumns;
187
+ }
188
+ if (options.includeApiConfig) {
189
+ decoratorConfig.api = {
190
+ include: ["list", "get", "create", "update"],
191
+ exclude: ["delete"]
192
+ };
193
+ }
194
+ if (options.includeMcpConfig) {
195
+ decoratorConfig.mcp = {
196
+ include: ["list", "get"]
197
+ };
198
+ }
199
+ if (options.includeCliConfig) {
200
+ decoratorConfig.cli = true;
201
+ }
202
+ return Object.keys(decoratorConfig).length > 0 ? `@smrt(${JSON.stringify(decoratorConfig, null, 2)})` : "@smrt()";
203
+ }
204
+ function renderTenantIdField(tenantScoped) {
205
+ const nullable = tenantScoped.mode === "optional";
206
+ const field = tenantScoped.field ?? "tenantId";
207
+ return nullable ? ` @tenantId({ nullable: true })
208
+ ${field}: string | null = null;` : ` @tenantId()
209
+ ${field}: string = '';`;
210
+ }
211
+ function renderProperty(prop) {
212
+ const mapping = TYPE_MAPPING[prop.type];
213
+ const nullable = prop.nullable === true;
214
+ const tsType = nullable ? `${mapping.tsType} | null` : mapping.tsType;
215
+ const defaultValue = prop.defaultValue !== void 0 ? renderLiteral(prop.defaultValue) : nullable ? "null" : mapping.defaultValue;
216
+ const fieldOptions = compactObject$1({
217
+ required: prop.required,
218
+ nullable: prop.nullable,
219
+ description: prop.description
220
+ });
221
+ const jsdoc = prop.description ? ` /** ${prop.description} */
222
+ ` : "";
223
+ const decorator = Object.keys(fieldOptions).length > 0 ? ` @field(${JSON.stringify(fieldOptions)})
224
+ ` : "";
225
+ return `${jsdoc}${decorator} ${prop.name}: ${tsType} = ${defaultValue};`;
226
+ }
227
+ function renderRelationship(relationship) {
228
+ const options = compactObject$1({
229
+ required: relationship.required,
230
+ nullable: relationship.nullable,
231
+ description: relationship.description,
232
+ validate: relationship.validate,
233
+ foreignKey: relationship.foreignKey,
234
+ through: relationship.through,
235
+ sourceKey: relationship.sourceKey,
236
+ targetKey: relationship.targetKey
237
+ });
238
+ const args = [
239
+ renderLiteral(relationship.related),
240
+ ...Object.keys(options).length > 0 ? [JSON.stringify(options)] : []
241
+ ];
242
+ const decorator = `@${relationship.type}(${args.join(", ")})`;
243
+ const fieldType = relationship.type === "oneToMany" || relationship.type === "manyToMany" ? "unknown[]" : relationship.nullable ? "string | null" : "string";
244
+ const defaultValue = relationship.type === "oneToMany" || relationship.type === "manyToMany" ? "[]" : relationship.nullable ? "null" : "''";
245
+ return ` ${decorator}
246
+ ${relationship.name}: ${fieldType} = ${defaultValue};`;
247
+ }
248
+ function needsFieldDecorator(properties) {
249
+ return properties.some(
250
+ (property) => property.required !== void 0 || property.nullable !== void 0 || property.description !== void 0
251
+ );
252
+ }
253
+ function renderCompanionSnippets(className, usesTenantScoped) {
254
+ const dependencyNote = usesTenantScoped ? `
255
+ * - Ensure package.json declares "@happyvertical/smrt-tenancy".` : "";
256
+ return `/*
257
+ * Package wiring:
258
+ * - Export ${className} from the package entrypoint used by consumers.
259
+ * - Import this module from any package registration file that eagerly loads objects.${dependencyNote}
260
+ */`;
261
+ }
262
+ function renderObjectLiteral(value) {
263
+ return JSON.stringify(compactObject$1(value), null, 2);
264
+ }
265
+ function renderLiteral(value) {
266
+ if (typeof value === "string") return JSON.stringify(value);
267
+ if (typeof value === "number" || typeof value === "boolean") {
268
+ return String(value);
269
+ }
270
+ if (value === null) return "null";
271
+ return JSON.stringify(value, null, 2);
272
+ }
273
+ function compactObject$1(value) {
274
+ return Object.fromEntries(
275
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
276
+ );
277
+ }
278
+
279
+ const DEFAULT_MANIFEST_PATHS = [
280
+ ".smrt/manifest.json",
281
+ "dist/manifest.json",
282
+ "src/manifest/manifest.json"
283
+ ];
284
+ const SCAN_EXCLUDE = [
285
+ "**/node_modules/**",
286
+ "**/dist/**",
287
+ "**/build/**",
288
+ "**/.git/**",
289
+ "**/.smrt/**",
290
+ "**/*.d.ts",
291
+ "**/*.test.ts",
292
+ "**/*.spec.ts",
293
+ "**/__tests__/**"
294
+ ];
295
+ const RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
296
+ "foreignKey",
297
+ "crossPackageRef",
298
+ "oneToMany",
299
+ "manyToMany"
300
+ ]);
301
+ async function introspectProject(args) {
302
+ const {
303
+ directory = process.cwd(),
304
+ includeFields = true,
305
+ includeRelationships = true,
306
+ includeMethods = true
307
+ } = args;
308
+ const projectPath = resolve(directory);
309
+ const exists = await pathExists$1(projectPath);
310
+ if (!exists) {
311
+ return JSON.stringify(
312
+ {
313
+ projectPath,
314
+ manifestSource: "none",
315
+ objectCount: 0,
316
+ objects: [],
317
+ diagnostics: [
318
+ {
319
+ severity: "warning",
320
+ message: `Project directory does not exist: ${projectPath}`
321
+ }
322
+ ]
323
+ },
324
+ null,
325
+ 2
326
+ );
327
+ }
328
+ const packageMetadata = await readPackageMetadata(projectPath);
329
+ const tenantScopes = await scanTenantScopes(projectPath);
330
+ const manifestResult = await loadManifestArtifact(projectPath, args.manifestPath) ?? await scanSourceManifest(projectPath, packageMetadata);
331
+ const objects = Object.entries(manifestResult.manifest.objects ?? {}).map(
332
+ ([manifestKey, object]) => formatObject({
333
+ manifestKey,
334
+ object,
335
+ projectPath,
336
+ includeFields,
337
+ includeRelationships,
338
+ includeMethods,
339
+ tenantScope: tenantScopes.get(object.className)
340
+ })
341
+ ).sort((left, right) => left.className.localeCompare(right.className));
342
+ const output = {
343
+ projectPath,
344
+ manifestSource: manifestResult.source,
345
+ manifestPath: "path" in manifestResult ? relative(projectPath, manifestResult.path) : void 0,
346
+ packageName: manifestResult.manifest.packageName ?? packageMetadata.name ?? void 0,
347
+ packageVersion: manifestResult.manifest.packageVersion ?? packageMetadata.version ?? void 0,
348
+ objectCount: objects.length,
349
+ scannedFileCount: manifestResult.scannedFileCount,
350
+ parseTimeMs: manifestResult.parseTimeMs,
351
+ objects,
352
+ diagnostics: manifestResult.diagnostics
353
+ };
354
+ return JSON.stringify(output, null, 2);
355
+ }
356
+ async function loadManifestArtifact(projectPath, manifestPath) {
357
+ const candidates = manifestPath ? [resolve(projectPath, manifestPath)] : DEFAULT_MANIFEST_PATHS.map((candidate) => join(projectPath, candidate));
358
+ const diagnostics = [];
359
+ for (const candidate of candidates) {
360
+ if (!await pathExists$1(candidate)) {
361
+ continue;
362
+ }
363
+ try {
364
+ const parsed = JSON.parse(await readFile(candidate, "utf-8"));
365
+ if (isManifestLike(parsed)) {
366
+ return {
367
+ source: "manifest",
368
+ path: candidate,
369
+ manifest: parsed,
370
+ diagnostics
371
+ };
372
+ }
373
+ diagnostics.push({
374
+ severity: "warning",
375
+ filePath: candidate,
376
+ message: "Manifest artifact is present but does not contain objects."
377
+ });
378
+ } catch (error) {
379
+ diagnostics.push({
380
+ severity: "error",
381
+ filePath: candidate,
382
+ message: `Unable to parse manifest artifact: ${messageFromError(error)}`
383
+ });
384
+ }
385
+ }
386
+ return manifestPath ? {
387
+ source: "manifest",
388
+ path: resolve(projectPath, manifestPath),
389
+ manifest: { objects: {} },
390
+ diagnostics: [
391
+ ...diagnostics,
392
+ {
393
+ severity: "warning",
394
+ filePath: resolve(projectPath, manifestPath),
395
+ message: "Requested manifest artifact was not found."
396
+ }
397
+ ]
398
+ } : void 0;
399
+ }
400
+ async function scanSourceManifest(projectPath, packageMetadata) {
401
+ const scanner = new OxcScanner({
402
+ cwd: projectPath,
403
+ include: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
404
+ exclude: SCAN_EXCLUDE
405
+ });
406
+ const { results, resolved } = await scanner.scanAndResolve();
407
+ const adapter = new ManifestAdapter();
408
+ const manifest = adapter.toManifest(
409
+ resolved.filter((classDef) => classDef.hasSmartDecorator),
410
+ {
411
+ packageName: packageMetadata.name,
412
+ packageVersion: packageMetadata.version,
413
+ typeAliases: results.typeAliases
414
+ }
415
+ );
416
+ finalizeScannerManifest(manifest, packageMetadata);
417
+ return {
418
+ source: "scanner",
419
+ manifest,
420
+ diagnostics: results.errors.map(scanErrorToDiagnostic),
421
+ scannedFileCount: results.fileCount,
422
+ parseTimeMs: Math.round(results.totalParseTimeMs)
423
+ };
424
+ }
425
+ function finalizeScannerManifest(manifest, packageMetadata) {
426
+ const manifestGen = new ManifestGenerator();
427
+ const fullManifest = manifest;
428
+ withSuppressedConsoleLog(() => {
429
+ manifestGen.injectTenantScopedFields(fullManifest);
430
+ manifestGen.mergeInheritedFields(fullManifest);
431
+ manifestGen.generateValidationRules(fullManifest);
432
+ manifestGen.generateSchemas(fullManifest);
433
+ manifestGen.assertTenantScopedSchemaContract(fullManifest);
434
+ manifestGen.generateAgentManifests(
435
+ fullManifest,
436
+ packageMetadata.name,
437
+ packageMetadata.json
438
+ );
439
+ });
440
+ }
441
+ function withSuppressedConsoleLog(callback) {
442
+ const originalLog = console.log;
443
+ console.log = () => void 0;
444
+ try {
445
+ return callback();
446
+ } finally {
447
+ console.log = originalLog;
448
+ }
449
+ }
450
+ function formatObject({
451
+ manifestKey,
452
+ object,
453
+ projectPath,
454
+ includeFields,
455
+ includeRelationships,
456
+ includeMethods,
457
+ tenantScope
458
+ }) {
459
+ const fieldDetails = Object.entries(object.fields ?? {}).map(
460
+ ([name, field]) => ({
461
+ name,
462
+ type: field.type,
463
+ ...field.required !== void 0 ? { required: field.required } : {},
464
+ ...field.default !== void 0 ? { default: field.default } : {},
465
+ ...field.related ? { related: field.related } : {},
466
+ ...field.description ? { description: field.description } : {},
467
+ ...field._meta ? { meta: field._meta } : {},
468
+ ...field.transient !== void 0 ? { transient: field.transient } : {}
469
+ })
470
+ );
471
+ const relationshipDetails = fieldDetails.filter((field) => RELATIONSHIP_TYPES.has(field.type)).map((field) => ({
472
+ field: field.name,
473
+ relatedClass: field.related ?? "",
474
+ type: field.type,
475
+ ...field.meta ? { meta: field.meta } : {}
476
+ }));
477
+ const methodDetails = Object.entries(object.methods ?? {}).map(
478
+ ([name, method]) => ({
479
+ name: method.name ?? name,
480
+ isAsync: method.async === true,
481
+ isStatic: method.isStatic === true,
482
+ isPublic: method.isPublic !== false,
483
+ parameters: method.parameters ?? [],
484
+ returnType: method.returnType ?? "unknown",
485
+ ...method.description ? { description: method.description } : {}
486
+ })
487
+ );
488
+ const decoratorConfig = object.decoratorConfig ?? {};
489
+ const effectiveTenantScope = tenantScope ?? normalizeTenantScopedConfig(decoratorConfig.tenantScoped);
490
+ const schema = object.schema;
491
+ return compactObject({
492
+ manifestKey,
493
+ name: object.name,
494
+ className: object.className,
495
+ qualifiedName: object.qualifiedName,
496
+ filePath: sanitizePath$1(projectPath, object.filePath),
497
+ packageName: object.packageName,
498
+ packageVersion: object.packageVersion,
499
+ importPath: object.importPath,
500
+ modulePath: object.modulePath,
501
+ exportName: object.exportName,
502
+ collectionExportName: object.collectionExportName,
503
+ collection: object.collection,
504
+ extends: object.extends,
505
+ extendsTypeArg: object.extendsTypeArg,
506
+ tableName: schema?.tableName ?? stringFromConfig(decoratorConfig.tableName) ?? object.collection,
507
+ tableStrategy: stringFromConfig(decoratorConfig.tableStrategy) ?? "cti",
508
+ conflictColumns: arrayFromConfig(decoratorConfig.conflictColumns),
509
+ tenantScope: effectiveTenantScope,
510
+ decoratorConfig,
511
+ schema: schema ? {
512
+ tableName: schema.tableName,
513
+ columns: schema.columns,
514
+ indexes: schema.indexes ?? [],
515
+ version: schema.version
516
+ } : void 0,
517
+ indexes: schema?.indexes ?? [],
518
+ staticProperties: object.staticProperties,
519
+ validationRules: object.validationRules,
520
+ ...includeFields && {
521
+ fields: fieldDetails.map((field) => `${field.name}: ${field.type}`).join(", "),
522
+ fieldDetails
523
+ },
524
+ ...includeRelationships && relationshipDetails.length > 0 && {
525
+ relationships: relationshipDetails.map(
526
+ (relationship) => `${relationship.field} -> ${relationship.relatedClass} (${relationship.type})`
527
+ ).join(", "),
528
+ relationshipDetails
529
+ },
530
+ ...includeMethods && methodDetails.length > 0 && {
531
+ methods: methodDetails.map((method) => `${method.isAsync ? "async " : ""}${method.name}()`).join(", "),
532
+ methodDetails
533
+ }
534
+ });
535
+ }
536
+ async function scanTenantScopes(projectPath) {
537
+ const files = await listSourceFiles$1(projectPath);
538
+ const scopes = /* @__PURE__ */ new Map();
539
+ await Promise.all(
540
+ files.map(async (filePath) => {
541
+ const content = await readFile(filePath, "utf-8");
542
+ const classMatches = content.matchAll(/class\s+([A-Za-z_]\w*)\b/g);
543
+ for (const match of classMatches) {
544
+ const className = match[1];
545
+ if (!className || match.index === void 0) continue;
546
+ const prefix = content.slice(
547
+ Math.max(0, match.index - 800),
548
+ match.index
549
+ );
550
+ const tenantMatches = Array.from(
551
+ prefix.matchAll(/@TenantScoped\s*\(([\s\S]*?)\)/g)
552
+ );
553
+ const tenantMatch = tenantMatches.at(-1);
554
+ if (!tenantMatch) continue;
555
+ scopes.set(className, {
556
+ source: "TenantScoped",
557
+ ...parseTenantScopedOptions(tenantMatch[1])
558
+ });
559
+ }
560
+ })
561
+ );
562
+ return scopes;
563
+ }
564
+ async function listSourceFiles$1(projectPath) {
565
+ const files = [];
566
+ async function visit(dir) {
567
+ let entries;
568
+ try {
569
+ entries = await readdir(dir, { withFileTypes: true });
570
+ } catch {
571
+ return;
572
+ }
573
+ for (const entry of entries) {
574
+ const fullPath = join(dir, entry.name);
575
+ if (entry.isDirectory()) {
576
+ if ([
577
+ "node_modules",
578
+ "dist",
579
+ "build",
580
+ ".git",
581
+ ".smrt",
582
+ "__tests__"
583
+ ].includes(entry.name) || entry.name.startsWith(".")) {
584
+ continue;
585
+ }
586
+ await visit(fullPath);
587
+ continue;
588
+ }
589
+ if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name) && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".spec.ts")) {
590
+ files.push(fullPath);
591
+ }
592
+ }
593
+ }
594
+ await visit(projectPath);
595
+ return files;
596
+ }
597
+ function parseTenantScopedOptions(raw) {
598
+ const mode = raw.match(/mode\s*:\s*['"`](required|optional)['"`]/)?.[1];
599
+ const field = raw.match(/field\s*:\s*['"`]([A-Za-z_]\w*)['"`]/)?.[1];
600
+ const allowSuperAdminBypass = raw.match(
601
+ /allowSuperAdminBypass\s*:\s*(true|false)/
602
+ )?.[1];
603
+ const autoFilter = raw.match(/autoFilter\s*:\s*(true|false)/)?.[1];
604
+ const autoPopulate = raw.match(/autoPopulate\s*:\s*(true|false)/)?.[1];
605
+ return compactObject({
606
+ mode: mode ?? "required",
607
+ field: field ?? "tenantId",
608
+ autoFilter: autoFilter === void 0 ? void 0 : autoFilter === "true",
609
+ autoPopulate: autoPopulate === void 0 ? void 0 : autoPopulate === "true",
610
+ allowSuperAdminBypass: allowSuperAdminBypass === void 0 ? void 0 : allowSuperAdminBypass === "true"
611
+ });
612
+ }
613
+ function normalizeTenantScopedConfig(tenantScoped) {
614
+ if (!tenantScoped) return void 0;
615
+ const options = typeof tenantScoped === "object" && !Array.isArray(tenantScoped) ? tenantScoped : {};
616
+ return {
617
+ source: "smrt",
618
+ mode: options.mode ?? "required",
619
+ field: options.field ?? "tenantId",
620
+ autoFilter: options.autoFilter ?? true,
621
+ autoPopulate: options.autoPopulate ?? true,
622
+ allowSuperAdminBypass: options.allowSuperAdminBypass ?? false
623
+ };
624
+ }
625
+ async function readPackageMetadata(projectPath) {
626
+ const packageJsonPath = join(projectPath, "package.json");
627
+ if (!await pathExists$1(packageJsonPath)) return {};
628
+ try {
629
+ const json = JSON.parse(await readFile(packageJsonPath, "utf-8"));
630
+ return {
631
+ name: typeof json.name === "string" ? json.name : void 0,
632
+ version: typeof json.version === "string" ? json.version : void 0,
633
+ json
634
+ };
635
+ } catch {
636
+ return {};
637
+ }
638
+ }
639
+ async function pathExists$1(path) {
640
+ try {
641
+ await access(path);
642
+ return true;
643
+ } catch {
644
+ return false;
645
+ }
646
+ }
647
+ function isManifestLike(value) {
648
+ return !!value && typeof value === "object" && !!value.objects && typeof value.objects === "object";
649
+ }
650
+ function scanErrorToDiagnostic(error) {
651
+ return {
652
+ severity: error.severity,
653
+ message: error.message,
654
+ filePath: error.filePath,
655
+ line: error.line,
656
+ column: error.column
657
+ };
658
+ }
659
+ function sanitizePath$1(projectPath, filePath) {
660
+ const absolute = isAbsolute(filePath) ? filePath : resolve(projectPath, filePath);
661
+ const relativePath = relative(projectPath, absolute);
662
+ if (!relativePath.startsWith("..")) {
663
+ return relativePath || filePath;
664
+ }
665
+ return filePath;
666
+ }
667
+ function stringFromConfig(value) {
668
+ return typeof value === "string" ? value : void 0;
669
+ }
670
+ function arrayFromConfig(value) {
671
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : void 0;
672
+ }
673
+ function compactObject(value) {
674
+ return Object.fromEntries(
675
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
676
+ );
677
+ }
678
+ function messageFromError(error) {
679
+ return error instanceof Error ? error.message : String(error);
680
+ }
681
+
682
+ const SOURCE_EXTENSIONS = /\.(tsx?|jsx?|svelte)$/;
683
+ const SKIP_DIRS = /* @__PURE__ */ new Set([
684
+ "node_modules",
685
+ "dist",
686
+ "build",
687
+ ".git",
688
+ ".smrt",
689
+ ".svelte-kit",
690
+ "coverage"
691
+ ]);
692
+ const DEPENDENCY_SECTIONS = [
693
+ "dependencies",
694
+ "devDependencies",
695
+ "peerDependencies",
696
+ "optionalDependencies"
697
+ ];
698
+ const SMRT_PACKAGE_PREFIX = "@happyvertical/smrt-";
699
+ const HAPPYVERTICAL_PACKAGE_PREFIX = "@happyvertical/";
700
+ async function reviewSmrtProject(args) {
701
+ const projectPath = resolve(args.directory ?? args.rootDir ?? process.cwd());
702
+ if (!await pathExists(projectPath)) {
703
+ return JSON.stringify(
704
+ {
705
+ projectPath,
706
+ packageCount: 0,
707
+ packages: [],
708
+ findings: [],
709
+ summary: { high: 0, medium: 0, low: 0 },
710
+ diagnostics: [
711
+ {
712
+ severity: "warning",
713
+ message: `Project directory does not exist: ${projectPath}`
714
+ }
715
+ ]
716
+ },
717
+ null,
718
+ 2
719
+ );
720
+ }
721
+ const packageContexts = await buildPackageContexts(projectPath);
722
+ const sourceFiles = await listSourceFiles(projectPath, packageContexts);
723
+ await attachSourceFiles(sourceFiles, packageContexts, projectPath);
724
+ const findings = limitFindings(
725
+ [
726
+ ...findMissingHappyVerticalDependencies(packageContexts),
727
+ ...findCustomManifestGeneration(sourceFiles, projectPath),
728
+ ...findDirectStorageBypasses(sourceFiles, packageContexts, projectPath),
729
+ ...findCustomHttpShells(sourceFiles, packageContexts, projectPath),
730
+ ...findLocalAuthTenancy(sourceFiles, packageContexts, projectPath),
731
+ ...findUiShellDrift(packageContexts, projectPath),
732
+ ...findMissingManifestArtifacts(
733
+ sourceFiles,
734
+ packageContexts,
735
+ projectPath
736
+ )
737
+ ],
738
+ args.maxFindings
739
+ );
740
+ const packages = packageContexts.map(packageInventory).sort((left, right) => left.path.localeCompare(right.path));
741
+ const summary = summarizeFindings(findings);
742
+ return JSON.stringify(
743
+ {
744
+ projectPath,
745
+ packageCount: packages.length,
746
+ packages,
747
+ findings: args.includeSourceEvidence === false ? findings.map(({ evidence: _evidence, ...finding }) => finding) : findings,
748
+ summary,
749
+ referenceChecks: [
750
+ "Prefer SMRT scanner/runtime manifests over custom manifest builders.",
751
+ "Prefer SvelteKit plus @happyvertical/smrt-svelte for app shells.",
752
+ "Prefer @happyvertical/sql and @happyvertical/files/assets/content over direct durable node:fs storage.",
753
+ "Prefer smrt-users, smrt-tenancy, and profile/audit packages over local static auth seams."
754
+ ],
755
+ suggestedFollowUpIssues: findings.map((finding) => ({
756
+ title: finding.suggestedIssueTitle,
757
+ severity: finding.severity,
758
+ area: finding.area
759
+ }))
760
+ },
761
+ null,
762
+ 2
763
+ );
764
+ }
765
+ async function buildPackageContexts(projectPath) {
766
+ const packageJsonPaths = await listPackageJsonFiles(projectPath);
767
+ const contexts = await Promise.all(
768
+ packageJsonPaths.map(async (packageJsonPath) => {
769
+ const json = JSON.parse(
770
+ await readFile(packageJsonPath, "utf-8")
771
+ );
772
+ const directory = dirname(packageJsonPath);
773
+ return {
774
+ directory,
775
+ relativePath: relative(projectPath, directory) || ".",
776
+ packageJsonPath,
777
+ json,
778
+ dependencies: collectDependencies(json),
779
+ scripts: isRecord(json.scripts) ? json.scripts : {},
780
+ imports: /* @__PURE__ */ new Map(),
781
+ sourceFiles: []
782
+ };
783
+ })
784
+ );
785
+ if (contexts.length === 0) {
786
+ contexts.push({
787
+ directory: projectPath,
788
+ relativePath: ".",
789
+ packageJsonPath: join(projectPath, "package.json"),
790
+ json: {},
791
+ dependencies: /* @__PURE__ */ new Set(),
792
+ scripts: {},
793
+ imports: /* @__PURE__ */ new Map(),
794
+ sourceFiles: []
795
+ });
796
+ }
797
+ return contexts.sort(
798
+ (left, right) => left.directory.length - right.directory.length
799
+ );
800
+ }
801
+ async function listPackageJsonFiles(projectPath) {
802
+ const files = [];
803
+ await visit(projectPath, async (filePath, entryName) => {
804
+ if (entryName === "package.json") files.push(filePath);
805
+ });
806
+ return files;
807
+ }
808
+ async function listSourceFiles(projectPath, packages) {
809
+ const files = [];
810
+ await visit(projectPath, async (filePath, entryName) => {
811
+ if (!SOURCE_EXTENSIONS.test(entryName)) return;
812
+ if (entryName.endsWith(".d.ts") || entryName.endsWith(".test.ts") || entryName.endsWith(".spec.ts")) {
813
+ return;
814
+ }
815
+ const packageDir = findOwningPackage(filePath, packages).directory;
816
+ const content = await readFile(filePath, "utf-8");
817
+ files.push({ path: filePath, packageDir, content });
818
+ });
819
+ return files;
820
+ }
821
+ async function attachSourceFiles(sourceFiles, packages, projectPath) {
822
+ for (const sourceFile of sourceFiles) {
823
+ const owner = packages.find(
824
+ (pkg) => pkg.directory === sourceFile.packageDir
825
+ );
826
+ if (!owner) continue;
827
+ owner.sourceFiles.push(sourceFile);
828
+ for (const importedPackage of extractImports(sourceFile.content)) {
829
+ if (!importedPackage.startsWith(HAPPYVERTICAL_PACKAGE_PREFIX)) continue;
830
+ const evidence = {
831
+ filePath: relative(projectPath, sourceFile.path),
832
+ line: lineNumber(sourceFile.content, importedPackage),
833
+ detail: `Imports ${importedPackage}`
834
+ };
835
+ const existing = owner.imports.get(importedPackage) ?? [];
836
+ existing.push(evidence);
837
+ owner.imports.set(importedPackage, existing);
838
+ }
839
+ }
840
+ }
841
+ function findMissingHappyVerticalDependencies(packages) {
842
+ return packages.flatMap((pkg) => {
843
+ const ownName = typeof pkg.json.name === "string" ? pkg.json.name : void 0;
844
+ const missing = Array.from(pkg.imports.keys()).filter(
845
+ (importedPackage) => importedPackage !== ownName && !pkg.dependencies.has(importedPackage)
846
+ );
847
+ if (missing.length === 0) return [];
848
+ const hasSmrtMissing = missing.some(
849
+ (name) => name.startsWith(SMRT_PACKAGE_PREFIX)
850
+ );
851
+ return [
852
+ {
853
+ severity: hasSmrtMissing ? "high" : "medium",
854
+ area: "dependencies",
855
+ code: "missing-happyvertical-dependencies",
856
+ title: `${packageLabel(pkg)} imports HappyVertical packages that package.json does not declare`,
857
+ evidence: missing.flatMap((name) => pkg.imports.get(name) ?? []),
858
+ recommendation: "Declare every imported @happyvertical package in the owning package manifest so downstream installs and generated knowledge stay reproducible.",
859
+ suggestedIssueTitle: `Declare missing HappyVertical dependencies in ${packageLabel(pkg)}`
860
+ }
861
+ ];
862
+ });
863
+ }
864
+ function findCustomManifestGeneration(sourceFiles, projectPath) {
865
+ return sourceFiles.filter(
866
+ (file) => /manifest\.json/.test(file.content) && /objects\s*:/.test(file.content) && /\b(writeFile|writeFileSync)\b/.test(file.content) && !/@happyvertical\/smrt-scanner/.test(file.content)
867
+ ).map((file) => ({
868
+ severity: "high",
869
+ area: "manifest",
870
+ code: "custom-object-manifest-generation",
871
+ title: "Custom SMRT object manifest generation detected",
872
+ evidence: [
873
+ {
874
+ filePath: relative(projectPath, file.path),
875
+ line: lineNumber(file.content, "manifest.json"),
876
+ detail: "Writes a manifest.json object inventory outside the SMRT scanner/runtime path."
877
+ }
878
+ ],
879
+ recommendation: "Use the SMRT scanner/runtime manifest path so defaults, relationships, schemas, tenant fields, and cross-package references match framework behavior.",
880
+ suggestedIssueTitle: "Replace custom object manifest generation with SMRT scanner/runtime manifest generation"
881
+ }));
882
+ }
883
+ function findDirectStorageBypasses(sourceFiles, packages, projectPath) {
884
+ return sourceFiles.flatMap((file) => {
885
+ const owner = findOwningPackage(file.path, packages);
886
+ const usesFs = /from\s+['"](?:node:fs|node:fs\/promises|fs|fs\/promises)['"]|require\(['"](?:node:fs|node:fs\/promises|fs|fs\/promises)['"]\)/.test(
887
+ file.content
888
+ );
889
+ const writesDurableData = /\b(writeFile|appendFile|mkdir|rm|rename)\b/.test(file.content) && /(\.json|data|storage|persist|cache|db)/i.test(file.content);
890
+ const usesDirectSql = /from\s+['"](?:better-sqlite3|sqlite3|pg|mysql2?|knex|drizzle-orm)['"]/.test(
891
+ file.content
892
+ );
893
+ if ((!usesFs || !writesDurableData) && !usesDirectSql) return [];
894
+ const hasApprovedStorage = owner.dependencies.has("@happyvertical/sql") || owner.dependencies.has("@happyvertical/files") || owner.dependencies.has("@happyvertical/smrt-assets") || owner.dependencies.has("@happyvertical/smrt-content");
895
+ if (hasApprovedStorage) return [];
896
+ return [
897
+ {
898
+ severity: "medium",
899
+ area: "storage",
900
+ code: "direct-storage-bypass",
901
+ title: `${packageLabel(owner)} appears to bypass HappyVertical storage packages`,
902
+ evidence: [
903
+ {
904
+ filePath: relative(projectPath, file.path),
905
+ line: lineNumber(
906
+ file.content,
907
+ usesDirectSql ? "sqlite" : "writeFile"
908
+ ),
909
+ detail: usesDirectSql ? "Imports a direct SQL/storage library without @happyvertical/sql." : "Uses node:fs-style durable writes without @happyvertical/files/assets/content."
910
+ }
911
+ ],
912
+ recommendation: "Route durable data through @happyvertical/sql, @happyvertical/files, SMRT assets, or SMRT content unless this is explicitly build-only tooling.",
913
+ suggestedIssueTitle: `Review direct storage usage in ${packageLabel(owner)}`
914
+ }
915
+ ];
916
+ });
917
+ }
918
+ function findCustomHttpShells(sourceFiles, packages, projectPath) {
919
+ const evidenceByPackage = /* @__PURE__ */ new Map();
920
+ for (const pkg of packages) {
921
+ if (pkg.dependencies.has("@sveltejs/kit")) continue;
922
+ const routerDependencies = ["express", "fastify", "hono", "koa"].filter(
923
+ (dep) => pkg.dependencies.has(dep)
924
+ );
925
+ if (routerDependencies.length === 0) continue;
926
+ appendEvidence(evidenceByPackage, pkg, {
927
+ filePath: relative(projectPath, pkg.packageJsonPath),
928
+ detail: `Declares custom router package(s): ${routerDependencies.join(", ")}.`
929
+ });
930
+ }
931
+ for (const file of sourceFiles) {
932
+ const owner = findOwningPackage(file.path, packages);
933
+ if (owner.dependencies.has("@sveltejs/kit")) continue;
934
+ const usesNodeHttp = /from\s+['"](?:node:http|http|node:https|https)['"]|createServer\s*\(/.test(
935
+ file.content
936
+ );
937
+ if (!usesNodeHttp) continue;
938
+ appendEvidence(evidenceByPackage, owner, {
939
+ filePath: relative(projectPath, file.path),
940
+ line: lineNumber(file.content, "createServer"),
941
+ detail: "Custom HTTP routing found without the SvelteKit/SMRT app-shell dependency pattern."
942
+ });
943
+ }
944
+ return Array.from(evidenceByPackage.entries()).map(([owner, evidence]) => ({
945
+ severity: "medium",
946
+ area: "api-shell",
947
+ code: "custom-http-shell",
948
+ title: `${packageLabel(owner)} uses a custom HTTP shell`,
949
+ evidence,
950
+ recommendation: "Compare the app shell against the Anytown/Ergot SvelteKit + SMRT shell pattern before adding custom HTTP infrastructure.",
951
+ suggestedIssueTitle: `Align ${packageLabel(owner)} app shell with SMRT/SvelteKit conventions`
952
+ }));
953
+ }
954
+ function appendEvidence(evidenceByPackage, pkg, evidence) {
955
+ const existing = evidenceByPackage.get(pkg);
956
+ if (existing) {
957
+ existing.push(evidence);
958
+ return;
959
+ }
960
+ evidenceByPackage.set(pkg, [evidence]);
961
+ }
962
+ function findLocalAuthTenancy(sourceFiles, packages, projectPath) {
963
+ return sourceFiles.flatMap((file) => {
964
+ const owner = findOwningPackage(file.path, packages);
965
+ const pathSignal = /(auth|tenant|audit|session|rbac|user)/i.test(file.path);
966
+ const codeSignal = /\b(tenantId|tenant|role|permission|session|auditLog|apiKey)\b/.test(
967
+ file.content
968
+ );
969
+ if (!pathSignal || !codeSignal) return [];
970
+ const hasApprovedPackages = owner.dependencies.has("@happyvertical/smrt-users") || owner.dependencies.has("@happyvertical/smrt-tenancy") || owner.dependencies.has("@happyvertical/smrt-profiles");
971
+ if (hasApprovedPackages) return [];
972
+ return [
973
+ {
974
+ severity: "medium",
975
+ area: "auth-tenancy",
976
+ code: "local-auth-tenancy",
977
+ title: `${packageLabel(owner)} contains local auth/tenancy/audit logic`,
978
+ evidence: [
979
+ {
980
+ filePath: relative(projectPath, file.path),
981
+ line: lineNumber(file.content, "tenant"),
982
+ detail: "Auth, tenancy, session, role, or audit terminology appears without smrt-users/smrt-tenancy/smrt-profiles dependencies."
983
+ }
984
+ ],
985
+ recommendation: "Add explicit adapters to smrt-users, smrt-tenancy, and profile/audit models or document why this local implementation is intentionally isolated.",
986
+ suggestedIssueTitle: `Review auth and tenancy adapters in ${packageLabel(owner)}`
987
+ }
988
+ ];
989
+ });
990
+ }
991
+ function findUiShellDrift(packages, projectPath) {
992
+ return packages.flatMap((pkg) => {
993
+ const hasUiSource = pkg.sourceFiles.some(
994
+ (file) => file.path.endsWith(".svelte")
995
+ );
996
+ const likelyUiPackage = hasUiSource || /(?:web|ui|app|site|frontend)/i.test(packageLabel(pkg)) || pkg.dependencies.has("svelte") || pkg.dependencies.has("vite");
997
+ if (!likelyUiPackage) return [];
998
+ if (pkg.dependencies.has("@happyvertical/smrt-svelte")) return [];
999
+ return [
1000
+ {
1001
+ severity: "low",
1002
+ area: "ui-shell",
1003
+ code: "missing-smrt-svelte-shell",
1004
+ title: `${packageLabel(pkg)} looks like UI work without @happyvertical/smrt-svelte`,
1005
+ evidence: [
1006
+ {
1007
+ filePath: relative(projectPath, pkg.packageJsonPath),
1008
+ detail: "UI-facing package does not declare @happyvertical/smrt-svelte."
1009
+ }
1010
+ ],
1011
+ recommendation: "Use @happyvertical/smrt-svelte and the SvelteKit shell pattern for downstream app UI unless this package is intentionally framework-agnostic.",
1012
+ suggestedIssueTitle: `Check SMRT Svelte shell alignment for ${packageLabel(pkg)}`
1013
+ }
1014
+ ];
1015
+ });
1016
+ }
1017
+ function findMissingManifestArtifacts(sourceFiles, packages, projectPath) {
1018
+ return packages.flatMap((pkg) => {
1019
+ const hasSmrtSource = pkg.sourceFiles.some(
1020
+ (file) => /@smrt\s*\(/.test(file.content)
1021
+ );
1022
+ if (!hasSmrtSource) return [];
1023
+ const hasManifest = sourceFiles.some(
1024
+ (file) => file.packageDir === pkg.directory && /@happyvertical\/smrt-scanner/.test(file.content)
1025
+ ) || pathExistsSyncHint(join(pkg.directory, ".smrt", "manifest.json")) || pathExistsSyncHint(join(pkg.directory, "dist", "manifest.json"));
1026
+ if (hasManifest) return [];
1027
+ return [
1028
+ {
1029
+ severity: "low",
1030
+ area: "manifest",
1031
+ code: "missing-generated-manifest-artifact",
1032
+ title: `${packageLabel(pkg)} has @smrt objects but no generated manifest artifact was found`,
1033
+ evidence: [
1034
+ {
1035
+ filePath: relative(projectPath, pkg.packageJsonPath),
1036
+ detail: "No .smrt/manifest.json or dist/manifest.json was visible during review."
1037
+ }
1038
+ ],
1039
+ recommendation: "Run the package build/test manifest generation path and verify it uses the SMRT scanner/runtime manifest pipeline.",
1040
+ suggestedIssueTitle: `Verify SMRT manifest generation for ${packageLabel(pkg)}`
1041
+ }
1042
+ ];
1043
+ });
1044
+ }
1045
+ async function visit(dir, onFile) {
1046
+ let entries;
1047
+ try {
1048
+ entries = await readdir(dir, { withFileTypes: true });
1049
+ } catch {
1050
+ return;
1051
+ }
1052
+ for (const entry of entries) {
1053
+ const fullPath = join(dir, entry.name);
1054
+ if (entry.isDirectory()) {
1055
+ if (SKIP_DIRS.has(entry.name)) continue;
1056
+ await visit(fullPath, onFile);
1057
+ continue;
1058
+ }
1059
+ if (entry.isFile()) await onFile(fullPath, entry.name);
1060
+ }
1061
+ }
1062
+ function collectDependencies(packageJson) {
1063
+ const dependencies = /* @__PURE__ */ new Set();
1064
+ for (const sectionName of DEPENDENCY_SECTIONS) {
1065
+ const section = packageJson[sectionName];
1066
+ if (!isRecord(section)) continue;
1067
+ for (const dependencyName of Object.keys(section)) {
1068
+ dependencies.add(dependencyName);
1069
+ }
1070
+ }
1071
+ return dependencies;
1072
+ }
1073
+ function extractImports(content) {
1074
+ const imports = /* @__PURE__ */ new Set();
1075
+ const patterns = [
1076
+ /(?:import|export)\s+(?:type\s+)?(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g,
1077
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1078
+ /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g
1079
+ ];
1080
+ for (const pattern of patterns) {
1081
+ for (const match of content.matchAll(pattern)) {
1082
+ const imported = normalizePackageImport(match[1]);
1083
+ if (imported) imports.add(imported);
1084
+ }
1085
+ }
1086
+ return Array.from(imports);
1087
+ }
1088
+ function normalizePackageImport(value) {
1089
+ if (!value || value.startsWith(".") || value.startsWith("/"))
1090
+ return void 0;
1091
+ if (value.startsWith("@")) {
1092
+ const [scope, name] = value.split("/");
1093
+ return scope && name ? `${scope}/${name}` : value;
1094
+ }
1095
+ return value.split("/")[0];
1096
+ }
1097
+ function findOwningPackage(filePath, packages) {
1098
+ const normalized = resolve(filePath);
1099
+ const matches = packages.filter(
1100
+ (pkg) => normalized === pkg.directory || normalized.startsWith(`${pkg.directory}${sep}`)
1101
+ ).sort((left, right) => right.directory.length - left.directory.length);
1102
+ return matches[0] ?? packages[0];
1103
+ }
1104
+ function packageInventory(pkg) {
1105
+ const declaredHappyVerticalDependencies = Array.from(pkg.dependencies).filter((dependency) => dependency.startsWith(HAPPYVERTICAL_PACKAGE_PREFIX)).sort();
1106
+ const importedHappyVerticalPackages = Array.from(pkg.imports.keys()).sort();
1107
+ const ownName = typeof pkg.json.name === "string" ? pkg.json.name : void 0;
1108
+ return {
1109
+ name: packageLabel(pkg),
1110
+ path: pkg.relativePath,
1111
+ private: typeof pkg.json.private === "boolean" ? pkg.json.private : void 0,
1112
+ scripts: Object.keys(pkg.scripts).sort(),
1113
+ declaredHappyVerticalDependencies,
1114
+ importedHappyVerticalPackages,
1115
+ missingHappyVerticalDependencies: importedHappyVerticalPackages.filter(
1116
+ (name) => name !== ownName && !pkg.dependencies.has(name)
1117
+ ),
1118
+ hasSvelteKit: pkg.dependencies.has("@sveltejs/kit"),
1119
+ hasSmrtSvelte: pkg.dependencies.has("@happyvertical/smrt-svelte")
1120
+ };
1121
+ }
1122
+ function summarizeFindings(findings) {
1123
+ return findings.reduce(
1124
+ (summary, finding) => {
1125
+ summary[finding.severity]++;
1126
+ return summary;
1127
+ },
1128
+ { high: 0, medium: 0, low: 0 }
1129
+ );
1130
+ }
1131
+ function limitFindings(findings, maxFindings) {
1132
+ const sorted = findings.sort(
1133
+ (left, right) => severityRank(left.severity) - severityRank(right.severity) || left.area.localeCompare(right.area) || left.title.localeCompare(right.title)
1134
+ );
1135
+ return maxFindings && maxFindings > 0 ? sorted.slice(0, maxFindings) : sorted;
1136
+ }
1137
+ function severityRank(severity) {
1138
+ return severity === "high" ? 0 : severity === "medium" ? 1 : 2;
1139
+ }
1140
+ function packageLabel(pkg) {
1141
+ return typeof pkg.json.name === "string" ? pkg.json.name : pkg.relativePath;
1142
+ }
1143
+ function lineNumber(content, needle) {
1144
+ const index = content.indexOf(needle);
1145
+ if (index < 0) return void 0;
1146
+ return content.slice(0, index).split("\n").length;
1147
+ }
1148
+ function isRecord(value) {
1149
+ return !!value && typeof value === "object" && !Array.isArray(value);
1150
+ }
1151
+ async function pathExists(path) {
1152
+ try {
1153
+ await access(path);
1154
+ return true;
1155
+ } catch {
1156
+ return false;
1157
+ }
1158
+ }
1159
+ function pathExistsSyncHint(path) {
1160
+ return existsSync(path);
1161
+ }
1162
+
1163
+ const SERVER_NAME = "smrt-dev-mcp";
1164
+ const SERVER_VERSION = readPackageVersion();
1165
+ const DEBUG = process.env.DEBUG === "true";
1166
+ const REVIEW_SKILL_NAME = "smrt-code-review";
1167
+ const REVIEW_SKILL_URI = `smrt-dev-mcp://agent-skills/${REVIEW_SKILL_NAME}`;
1168
+ const DOMAIN_CODE_REVIEW_PROMPT = "domain-code-review";
1169
+ const DOMAIN_ARCHITECTURE_PROMPT = "domain-architecture";
1170
+ const KNOWLEDGE_PROJECT_URI = "smrt://knowledge/project";
1171
+ const KNOWLEDGE_PACKAGE_PREFIX = "smrt://knowledge/package/";
1172
+ const TOOLS = [
1173
+ // Code Generation Tools
1174
+ {
1175
+ name: "generate-smrt-class",
1176
+ description: "Generate a complete SMRT class with @smrt() decorator",
1177
+ inputSchema: {
1178
+ type: "object",
1179
+ properties: {
1180
+ className: {
1181
+ type: "string",
1182
+ description: "Name of the class (PascalCase)"
1183
+ },
1184
+ properties: {
1185
+ type: "array",
1186
+ description: "Array of property definitions",
1187
+ items: {
1188
+ type: "object",
1189
+ properties: {
1190
+ name: { type: "string" },
1191
+ type: {
1192
+ type: "string",
1193
+ enum: [
1194
+ "text",
1195
+ "integer",
1196
+ "decimal",
1197
+ "boolean",
1198
+ "datetime",
1199
+ "json"
1200
+ ]
1201
+ },
1202
+ required: { type: "boolean" },
1203
+ nullable: { type: "boolean" },
1204
+ description: { type: "string" },
1205
+ defaultValue: {
1206
+ oneOf: [
1207
+ { type: "string" },
1208
+ { type: "number" },
1209
+ { type: "boolean" },
1210
+ { type: "object" },
1211
+ { type: "null" }
1212
+ ]
1213
+ }
1214
+ },
1215
+ required: ["name", "type"]
1216
+ }
1217
+ },
1218
+ baseClass: {
1219
+ type: "string",
1220
+ enum: ["SmrtObject", "SmrtCollection"],
1221
+ default: "SmrtObject"
1222
+ },
1223
+ template: {
1224
+ type: "string",
1225
+ enum: [
1226
+ "basic",
1227
+ "global-catalog",
1228
+ "optional-catalog",
1229
+ "tenant-project-object",
1230
+ "tenant-event-log-object",
1231
+ "cross-package-reference"
1232
+ ],
1233
+ default: "basic"
1234
+ },
1235
+ tableName: { type: "string" },
1236
+ conflictColumns: {
1237
+ type: "array",
1238
+ items: { type: "string" }
1239
+ },
1240
+ tenantScoped: {
1241
+ oneOf: [
1242
+ { type: "boolean" },
1243
+ {
1244
+ type: "object",
1245
+ properties: {
1246
+ mode: { type: "string", enum: ["required", "optional"] },
1247
+ field: { type: "string" },
1248
+ autoFilter: { type: "boolean" },
1249
+ autoPopulate: { type: "boolean" },
1250
+ allowSuperAdminBypass: { type: "boolean" }
1251
+ }
1252
+ }
1253
+ ]
1254
+ },
1255
+ includeTenantIdField: { type: "boolean" },
1256
+ relationships: {
1257
+ type: "array",
1258
+ items: {
1259
+ type: "object",
1260
+ properties: {
1261
+ name: { type: "string" },
1262
+ type: {
1263
+ type: "string",
1264
+ enum: [
1265
+ "foreignKey",
1266
+ "crossPackageRef",
1267
+ "oneToMany",
1268
+ "manyToMany"
1269
+ ]
1270
+ },
1271
+ related: { type: "string" },
1272
+ required: { type: "boolean" },
1273
+ nullable: { type: "boolean" },
1274
+ description: { type: "string" },
1275
+ validate: { type: "boolean" },
1276
+ foreignKey: { type: "string" },
1277
+ through: { type: "string" },
1278
+ sourceKey: { type: "string" },
1279
+ targetKey: { type: "string" }
1280
+ },
1281
+ required: ["name", "type", "related"]
1282
+ }
1283
+ },
1284
+ includeCompanionSnippets: { type: "boolean", default: false },
1285
+ includeApiConfig: { type: "boolean", default: true },
1286
+ includeMcpConfig: { type: "boolean", default: true },
1287
+ includeCliConfig: { type: "boolean", default: true }
1288
+ },
1289
+ required: ["className", "properties"]
1290
+ }
1291
+ },
1292
+ // Project Introspection Tools
1293
+ {
1294
+ name: "introspect-project",
1295
+ description: "Scan current directory for SMRT objects",
1296
+ inputSchema: {
1297
+ type: "object",
1298
+ properties: {
1299
+ directory: {
1300
+ type: "string",
1301
+ description: "Project directory (default: cwd)"
1302
+ },
1303
+ manifestPath: {
1304
+ type: "string",
1305
+ description: "Optional manifest path. Defaults to .smrt/manifest.json, dist/manifest.json, then source scanning."
1306
+ },
1307
+ includeFields: {
1308
+ type: "boolean",
1309
+ description: "Include field details"
1310
+ },
1311
+ includeRelationships: {
1312
+ type: "boolean",
1313
+ description: "Analyze relationships"
1314
+ },
1315
+ includeMethods: {
1316
+ type: "boolean",
1317
+ description: "Include public method details"
1318
+ }
1319
+ }
1320
+ }
1321
+ },
1322
+ {
1323
+ name: "review-smrt-project",
1324
+ description: "Advisory ecosystem alignment review for downstream SMRT projects",
1325
+ inputSchema: {
1326
+ type: "object",
1327
+ properties: {
1328
+ directory: {
1329
+ type: "string",
1330
+ description: "Project directory (default: cwd)"
1331
+ },
1332
+ rootDir: {
1333
+ type: "string",
1334
+ description: "Compatibility alias for directory"
1335
+ },
1336
+ includeSourceEvidence: {
1337
+ type: "boolean",
1338
+ description: "Include file and line evidence in findings",
1339
+ default: true
1340
+ },
1341
+ maxFindings: {
1342
+ type: "number",
1343
+ description: "Optional maximum number of findings to return"
1344
+ }
1345
+ }
1346
+ }
1347
+ },
1348
+ {
1349
+ name: "reflect-knowledge",
1350
+ description: "Report deterministic SMRT + HappyVertical SDK knowledge coverage and freshness",
1351
+ inputSchema: {
1352
+ type: "object",
1353
+ properties: {
1354
+ rootDir: {
1355
+ type: "string",
1356
+ description: "Project root directory (default: cwd)"
1357
+ }
1358
+ }
1359
+ }
1360
+ },
1361
+ {
1362
+ name: "reflect-domain-knowledge",
1363
+ description: "Report domain-scoped SMRT knowledge artifacts, SDK packages, and freshness",
1364
+ inputSchema: {
1365
+ type: "object",
1366
+ properties: {
1367
+ rootDir: { type: "string" },
1368
+ scope: {
1369
+ type: "string",
1370
+ enum: ["project", "local", "package", "sdk"],
1371
+ default: "project"
1372
+ },
1373
+ package: { type: "string" }
1374
+ }
1375
+ }
1376
+ },
1377
+ {
1378
+ name: "check-knowledge-freshness",
1379
+ description: "Run deterministic freshness checks for SMRT agent knowledge",
1380
+ inputSchema: {
1381
+ type: "object",
1382
+ properties: {
1383
+ rootDir: { type: "string" },
1384
+ changed: {
1385
+ type: "boolean",
1386
+ description: "Limit stale-pattern checks to changed files"
1387
+ },
1388
+ strict: {
1389
+ type: "boolean",
1390
+ description: "Treat stale-pattern findings as errors"
1391
+ }
1392
+ }
1393
+ }
1394
+ },
1395
+ {
1396
+ name: "check-domain-knowledge",
1397
+ description: "Run deterministic freshness checks for domain knowledge artifacts",
1398
+ inputSchema: {
1399
+ type: "object",
1400
+ properties: {
1401
+ rootDir: { type: "string" },
1402
+ changed: { type: "boolean" },
1403
+ strict: { type: "boolean" },
1404
+ scope: {
1405
+ type: "string",
1406
+ enum: ["project", "local", "package", "sdk"],
1407
+ default: "project"
1408
+ },
1409
+ package: { type: "string" }
1410
+ }
1411
+ }
1412
+ },
1413
+ {
1414
+ name: "build-review-context",
1415
+ description: "Build model-ready SMRT review context from changed files and optional focus text",
1416
+ inputSchema: {
1417
+ type: "object",
1418
+ properties: {
1419
+ rootDir: { type: "string" },
1420
+ changedFiles: { type: "array", items: { type: "string" } },
1421
+ focus: { type: "string" },
1422
+ documentation: { type: "string" }
1423
+ }
1424
+ }
1425
+ },
1426
+ {
1427
+ name: "build-domain-review-context",
1428
+ description: "Build domain-scoped model-ready SMRT review context and prompt bundle",
1429
+ inputSchema: {
1430
+ type: "object",
1431
+ properties: {
1432
+ rootDir: { type: "string" },
1433
+ changedFiles: { type: "array", items: { type: "string" } },
1434
+ focus: { type: "string" },
1435
+ documentation: { type: "string" },
1436
+ scope: {
1437
+ type: "string",
1438
+ enum: ["project", "local", "package", "sdk"],
1439
+ default: "project"
1440
+ },
1441
+ package: { type: "string" }
1442
+ }
1443
+ }
1444
+ },
1445
+ {
1446
+ name: "smrt-review",
1447
+ 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.',
1448
+ inputSchema: {
1449
+ type: "object",
1450
+ properties: {
1451
+ rootDir: { type: "string" },
1452
+ changedFiles: { type: "array", items: { type: "string" } },
1453
+ focus: { type: "string" },
1454
+ documentation: { type: "string" },
1455
+ mode: {
1456
+ type: "string",
1457
+ enum: ["findings", "prompt-bundle", "both"],
1458
+ default: "both"
1459
+ }
1460
+ }
1461
+ }
1462
+ },
1463
+ {
1464
+ name: "build-architecture-context",
1465
+ description: "Build model-ready SMRT architecture context from an idea or documentation",
1466
+ inputSchema: {
1467
+ type: "object",
1468
+ properties: {
1469
+ rootDir: { type: "string" },
1470
+ idea: { type: "string" },
1471
+ documentation: { type: "string" },
1472
+ focus: { type: "string" }
1473
+ }
1474
+ }
1475
+ },
1476
+ {
1477
+ name: "build-domain-architecture-context",
1478
+ description: "Build domain-scoped model-ready SMRT architecture context and prompt bundle",
1479
+ inputSchema: {
1480
+ type: "object",
1481
+ properties: {
1482
+ rootDir: { type: "string" },
1483
+ idea: { type: "string" },
1484
+ documentation: { type: "string" },
1485
+ focus: { type: "string" },
1486
+ scope: {
1487
+ type: "string",
1488
+ enum: ["project", "local", "package", "sdk"],
1489
+ default: "project"
1490
+ },
1491
+ package: { type: "string" }
1492
+ }
1493
+ }
1494
+ },
1495
+ {
1496
+ name: "smrt-architecture",
1497
+ description: "Suggest SMRT and HappyVertical SDK packages and return an architecture prompt bundle",
1498
+ inputSchema: {
1499
+ type: "object",
1500
+ properties: {
1501
+ rootDir: { type: "string" },
1502
+ idea: { type: "string" },
1503
+ documentation: { type: "string" },
1504
+ focus: { type: "string" }
1505
+ }
1506
+ }
1507
+ },
1508
+ {
1509
+ name: "list-agent-skills",
1510
+ description: "List bundled harness-agnostic agent skills shipped with smrt-dev-mcp",
1511
+ inputSchema: {
1512
+ type: "object",
1513
+ properties: {}
1514
+ }
1515
+ },
1516
+ {
1517
+ name: "get-agent-skill",
1518
+ description: "Return a bundled harness-agnostic agent skill as Markdown plus optional references",
1519
+ inputSchema: {
1520
+ type: "object",
1521
+ properties: {
1522
+ name: {
1523
+ type: "string",
1524
+ enum: [REVIEW_SKILL_NAME],
1525
+ description: "Bundled agent skill name"
1526
+ },
1527
+ includeReferences: {
1528
+ type: "boolean",
1529
+ default: true,
1530
+ description: "Include referenced files with the skill bundle"
1531
+ }
1532
+ },
1533
+ required: ["name"]
1534
+ }
1535
+ }
1536
+ ];
1537
+ async function main() {
1538
+ if (DEBUG) {
1539
+ console.error(`[${SERVER_NAME}] Starting server v${SERVER_VERSION}`);
1540
+ }
1541
+ const server = new Server(
1542
+ {
1543
+ name: SERVER_NAME,
1544
+ version: SERVER_VERSION
1545
+ },
1546
+ {
1547
+ capabilities: {
1548
+ prompts: {},
1549
+ resources: {},
1550
+ tools: {}
1551
+ }
1552
+ }
1553
+ );
1554
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1555
+ if (DEBUG) {
1556
+ console.error(`[${SERVER_NAME}] ListTools request`);
1557
+ }
1558
+ return { tools: TOOLS };
1559
+ });
1560
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
1561
+ return {
1562
+ prompts: [
1563
+ {
1564
+ name: REVIEW_SKILL_NAME,
1565
+ title: "SMRT Code Review",
1566
+ description: "Harness-agnostic downstream SMRT review procedure that uses smrt-dev-mcp deterministic context and prompt bundles."
1567
+ },
1568
+ {
1569
+ name: DOMAIN_CODE_REVIEW_PROMPT,
1570
+ title: "Domain Code Review",
1571
+ description: "Model-ready domain-scoped SMRT code review prompt bundle.",
1572
+ arguments: [
1573
+ {
1574
+ name: "rootDir",
1575
+ description: "Project root directory. Defaults to server cwd.",
1576
+ required: false
1577
+ },
1578
+ {
1579
+ name: "changedFiles",
1580
+ description: "Changed file paths as newline-separated, comma-separated, or JSON array text.",
1581
+ required: false
1582
+ },
1583
+ {
1584
+ name: "focus",
1585
+ description: "Review focus text.",
1586
+ required: false
1587
+ },
1588
+ {
1589
+ name: "documentation",
1590
+ description: "Additional documentation or notes.",
1591
+ required: false
1592
+ },
1593
+ {
1594
+ name: "scope",
1595
+ description: "Knowledge scope: project, local, package, or sdk.",
1596
+ required: false
1597
+ },
1598
+ {
1599
+ name: "package",
1600
+ description: "Package name or short package selector.",
1601
+ required: false
1602
+ }
1603
+ ]
1604
+ },
1605
+ {
1606
+ name: DOMAIN_ARCHITECTURE_PROMPT,
1607
+ title: "Domain Architecture",
1608
+ description: "Model-ready domain-scoped SMRT architecture planning prompt bundle.",
1609
+ arguments: [
1610
+ {
1611
+ name: "rootDir",
1612
+ description: "Project root directory. Defaults to server cwd.",
1613
+ required: false
1614
+ },
1615
+ {
1616
+ name: "idea",
1617
+ description: "Architecture idea or product concept.",
1618
+ required: false
1619
+ },
1620
+ {
1621
+ name: "documentation",
1622
+ description: "Additional documentation or notes.",
1623
+ required: false
1624
+ },
1625
+ {
1626
+ name: "focus",
1627
+ description: "Planning focus text.",
1628
+ required: false
1629
+ },
1630
+ {
1631
+ name: "scope",
1632
+ description: "Knowledge scope: project, local, package, or sdk.",
1633
+ required: false
1634
+ },
1635
+ {
1636
+ name: "package",
1637
+ description: "Package name or short package selector.",
1638
+ required: false
1639
+ }
1640
+ ]
1641
+ }
1642
+ ]
1643
+ };
1644
+ });
1645
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1646
+ const { name } = request.params;
1647
+ if (name === REVIEW_SKILL_NAME) {
1648
+ return {
1649
+ description: "Use this procedure when reviewing downstream SMRT projects.",
1650
+ messages: [
1651
+ {
1652
+ role: "user",
1653
+ content: {
1654
+ type: "text",
1655
+ text: renderAgentSkillMarkdown(REVIEW_SKILL_NAME)
1656
+ }
1657
+ }
1658
+ ]
1659
+ };
1660
+ }
1661
+ if (name === DOMAIN_CODE_REVIEW_PROMPT) {
1662
+ const context = await buildReviewContext(
1663
+ reviewPromptArguments(request.params.arguments)
1664
+ );
1665
+ return {
1666
+ description: "Review downstream SMRT code with domain knowledge.",
1667
+ messages: [
1668
+ {
1669
+ role: "user",
1670
+ content: {
1671
+ type: "text",
1672
+ text: context.promptBundle.contextMarkdown
1673
+ }
1674
+ }
1675
+ ]
1676
+ };
1677
+ }
1678
+ if (name === DOMAIN_ARCHITECTURE_PROMPT) {
1679
+ const context = await buildArchitectureContext(
1680
+ architecturePromptArguments(request.params.arguments)
1681
+ );
1682
+ return {
1683
+ description: "Plan a downstream SMRT project with domain knowledge.",
1684
+ messages: [
1685
+ {
1686
+ role: "user",
1687
+ content: {
1688
+ type: "text",
1689
+ text: context.promptBundle.contextMarkdown
1690
+ }
1691
+ }
1692
+ ]
1693
+ };
1694
+ }
1695
+ throw new McpError(ErrorCode.InvalidParams, `Unknown prompt: ${name}`);
1696
+ });
1697
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
1698
+ const index = await buildKnowledgeIndex();
1699
+ return {
1700
+ resources: [
1701
+ {
1702
+ uri: REVIEW_SKILL_URI,
1703
+ name: REVIEW_SKILL_NAME,
1704
+ title: "SMRT Code Review Skill",
1705
+ description: "Bundled Markdown skill for downstream SMRT code reviews.",
1706
+ mimeType: "text/markdown"
1707
+ },
1708
+ {
1709
+ uri: KNOWLEDGE_PROJECT_URI,
1710
+ name: "smrt-domain-knowledge-project",
1711
+ title: "SMRT Domain Knowledge Project Index",
1712
+ description: "Composed SMRT, downstream domain, and HappyVertical SDK knowledge index.",
1713
+ mimeType: "application/json"
1714
+ },
1715
+ ...index.packages.map((pkg) => ({
1716
+ uri: `${KNOWLEDGE_PACKAGE_PREFIX}${encodeURIComponent(pkg.name)}`,
1717
+ name: `smrt-domain-knowledge-${pkg.name}`,
1718
+ title: `SMRT Domain Knowledge: ${pkg.name}`,
1719
+ description: "Package-scoped SMRT domain knowledge, generated surfaces, and authored context.",
1720
+ mimeType: "application/json"
1721
+ }))
1722
+ ]
1723
+ };
1724
+ });
1725
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1726
+ const { uri } = request.params;
1727
+ if (uri === REVIEW_SKILL_URI) {
1728
+ return {
1729
+ contents: [
1730
+ {
1731
+ uri,
1732
+ mimeType: "text/markdown",
1733
+ text: renderAgentSkillMarkdown(REVIEW_SKILL_NAME)
1734
+ }
1735
+ ]
1736
+ };
1737
+ }
1738
+ if (uri === KNOWLEDGE_PROJECT_URI) {
1739
+ const index = await buildKnowledgeIndex();
1740
+ return {
1741
+ contents: [
1742
+ {
1743
+ uri,
1744
+ mimeType: "application/json",
1745
+ text: JSON.stringify(sanitizeKnowledgeIndex(index), null, 2)
1746
+ }
1747
+ ]
1748
+ };
1749
+ }
1750
+ if (uri.startsWith(KNOWLEDGE_PACKAGE_PREFIX)) {
1751
+ const packageName = decodeURIComponent(
1752
+ uri.slice(KNOWLEDGE_PACKAGE_PREFIX.length)
1753
+ );
1754
+ const index = await buildKnowledgeIndex();
1755
+ const pkg = index.packages.find((item) => item.name === packageName);
1756
+ if (!pkg) {
1757
+ throw new McpError(
1758
+ ErrorCode.InvalidParams,
1759
+ `Unknown knowledge package: ${packageName}`
1760
+ );
1761
+ }
1762
+ return {
1763
+ contents: [
1764
+ {
1765
+ uri,
1766
+ mimeType: "application/json",
1767
+ text: JSON.stringify(sanitizeKnowledgePackage(pkg), null, 2)
1768
+ }
1769
+ ]
1770
+ };
1771
+ }
1772
+ throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${uri}`);
1773
+ });
1774
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1775
+ const { name, arguments: args } = request.params;
1776
+ if (DEBUG) {
1777
+ console.error(`[${SERVER_NAME}] CallTool: ${name}`);
1778
+ console.error(
1779
+ `[${SERVER_NAME}] Arguments:`,
1780
+ JSON.stringify(args, null, 2)
1781
+ );
1782
+ }
1783
+ try {
1784
+ let result;
1785
+ switch (name) {
1786
+ case "generate-smrt-class":
1787
+ result = await generateSmrtClass(args);
1788
+ break;
1789
+ case "introspect-project":
1790
+ result = await introspectProject(args);
1791
+ break;
1792
+ case "review-smrt-project":
1793
+ result = await reviewSmrtProject(args);
1794
+ break;
1795
+ case "reflect-knowledge": {
1796
+ const index = await buildKnowledgeIndex(args);
1797
+ const freshness = await checkKnowledgeFreshnessFromIndex(
1798
+ index,
1799
+ args
1800
+ );
1801
+ result = JSON.stringify(
1802
+ {
1803
+ rootDir: index.rootDir,
1804
+ packageCount: index.packages.length,
1805
+ smrtPackageCount: index.smrtPackages.length,
1806
+ sdkPackageCount: index.sdkPackages.length,
1807
+ relationshipsV2: index.relationshipsV2,
1808
+ freshness
1809
+ },
1810
+ null,
1811
+ 2
1812
+ );
1813
+ break;
1814
+ }
1815
+ case "reflect-domain-knowledge": {
1816
+ const index = await buildKnowledgeIndex(args);
1817
+ const freshness = await checkKnowledgeFreshnessFromIndex(
1818
+ index,
1819
+ args
1820
+ );
1821
+ result = JSON.stringify(
1822
+ {
1823
+ rootDir: index.rootDir,
1824
+ packageCount: index.packages.length,
1825
+ smrtPackageCount: index.smrtPackages.length,
1826
+ sdkPackageCount: index.sdkPackages.length,
1827
+ domainKnowledgePackageCount: index.packages.filter(
1828
+ (pkg) => pkg.hasDomainKnowledge
1829
+ ).length,
1830
+ missingDomainKnowledgePackages: index.packages.filter(
1831
+ (pkg) => pkg.exportKeys.includes("./smrt-knowledge.json") && !pkg.hasDomainKnowledge
1832
+ ).map((pkg) => pkg.name),
1833
+ relationshipsV2: index.relationshipsV2,
1834
+ freshness
1835
+ },
1836
+ null,
1837
+ 2
1838
+ );
1839
+ break;
1840
+ }
1841
+ case "check-knowledge-freshness":
1842
+ result = JSON.stringify(
1843
+ await checkKnowledgeFreshness(args),
1844
+ null,
1845
+ 2
1846
+ );
1847
+ break;
1848
+ case "check-domain-knowledge":
1849
+ result = JSON.stringify(
1850
+ await checkKnowledgeFreshness(args),
1851
+ null,
1852
+ 2
1853
+ );
1854
+ break;
1855
+ case "build-review-context":
1856
+ result = JSON.stringify(
1857
+ await buildReviewContext(args),
1858
+ null,
1859
+ 2
1860
+ );
1861
+ break;
1862
+ case "build-domain-review-context":
1863
+ result = JSON.stringify(
1864
+ await buildReviewContext(args),
1865
+ null,
1866
+ 2
1867
+ );
1868
+ break;
1869
+ case "smrt-review":
1870
+ result = JSON.stringify(await smrtReview(args), null, 2);
1871
+ break;
1872
+ case "build-architecture-context":
1873
+ result = JSON.stringify(
1874
+ await buildArchitectureContext(args),
1875
+ null,
1876
+ 2
1877
+ );
1878
+ break;
1879
+ case "build-domain-architecture-context":
1880
+ result = JSON.stringify(
1881
+ await buildArchitectureContext(args),
1882
+ null,
1883
+ 2
1884
+ );
1885
+ break;
1886
+ case "smrt-architecture":
1887
+ result = JSON.stringify(await smrtArchitecture(args), null, 2);
1888
+ break;
1889
+ case "list-agent-skills":
1890
+ result = JSON.stringify({ skills: listAgentSkills() }, null, 2);
1891
+ break;
1892
+ case "get-agent-skill":
1893
+ result = JSON.stringify(await getAgentSkill(args), null, 2);
1894
+ break;
1895
+ default:
1896
+ throw new Error(`Unknown tool: ${name}`);
1897
+ }
1898
+ return {
1899
+ content: [
1900
+ {
1901
+ type: "text",
1902
+ text: result
1903
+ }
1904
+ ]
1905
+ };
1906
+ } catch (error) {
1907
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1908
+ console.error(`[${SERVER_NAME}] Error:`, error);
1909
+ return {
1910
+ content: [
1911
+ {
1912
+ type: "text",
1913
+ text: `Error executing tool ${name}: ${errorMessage}`
1914
+ }
1915
+ ],
1916
+ isError: true
1917
+ };
1918
+ }
1919
+ });
1920
+ const transport = new StdioServerTransport();
1921
+ await server.connect(transport);
1922
+ if (DEBUG) {
1923
+ console.error(`[${SERVER_NAME}] Server connected via stdio`);
1924
+ }
1925
+ process.on("SIGINT", async () => {
1926
+ if (DEBUG) {
1927
+ console.error(`[${SERVER_NAME}] Shutting down...`);
1928
+ }
1929
+ await server.close();
1930
+ process.exit(0);
1931
+ });
1932
+ process.on("SIGTERM", async () => {
1933
+ if (DEBUG) {
1934
+ console.error(`[${SERVER_NAME}] Shutting down...`);
1935
+ }
1936
+ await server.close();
1937
+ process.exit(0);
1938
+ });
1939
+ }
1940
+ function isEntrypoint() {
1941
+ const entry = process.argv[1];
1942
+ if (!entry) return false;
1943
+ try {
1944
+ return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
1945
+ } catch {
1946
+ return import.meta.url === pathToFileURL(entry).href;
1947
+ }
1948
+ }
1949
+ function readPackageVersion() {
1950
+ try {
1951
+ const packageRoot = dirname(fileURLToPath(import.meta.url));
1952
+ const packageJsonPath = join(packageRoot, "..", "package.json");
1953
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1954
+ return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
1955
+ } catch {
1956
+ return "0.0.0";
1957
+ }
1958
+ }
1959
+ function renderAgentSkillMarkdown(name) {
1960
+ const skill = getAgentSkill({ name, includeReferences: true });
1961
+ const references = skill.referenceFiles.map(
1962
+ (file) => `## Reference: ${file.path}
1963
+
1964
+ ${file.content.trim()}`
1965
+ );
1966
+ return [skill.skillMarkdown.trim(), ...references].join("\n\n");
1967
+ }
1968
+ function reviewPromptArguments(args) {
1969
+ return compactRecord({
1970
+ rootDir: args?.rootDir,
1971
+ changedFiles: parseStringList(args?.changedFiles),
1972
+ focus: args?.focus,
1973
+ documentation: args?.documentation,
1974
+ scope: args?.scope,
1975
+ package: args?.package
1976
+ });
1977
+ }
1978
+ function architecturePromptArguments(args) {
1979
+ return compactRecord({
1980
+ rootDir: args?.rootDir,
1981
+ idea: args?.idea,
1982
+ documentation: args?.documentation,
1983
+ focus: args?.focus,
1984
+ scope: args?.scope,
1985
+ package: args?.package
1986
+ });
1987
+ }
1988
+ function parseStringList(value) {
1989
+ if (!value?.trim()) return void 0;
1990
+ try {
1991
+ const parsed = JSON.parse(value);
1992
+ if (Array.isArray(parsed)) {
1993
+ return parsed.filter((item) => typeof item === "string");
1994
+ }
1995
+ } catch {
1996
+ }
1997
+ return value.split(/[\n,]/).map((item) => item.trim()).filter(Boolean);
1998
+ }
1999
+ function compactRecord(value) {
2000
+ return Object.fromEntries(
2001
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
2002
+ );
2003
+ }
2004
+ function sanitizeKnowledgeIndex(index) {
2005
+ const packages = index.packages.map((pkg) => sanitizeKnowledgePackage(pkg));
2006
+ return {
2007
+ ...index,
2008
+ rootDir: ".",
2009
+ packages,
2010
+ smrtPackages: packages.filter((pkg) => pkg.kind === "smrt"),
2011
+ sdkPackages: packages.filter((pkg) => pkg.kind === "sdk")
2012
+ };
2013
+ }
2014
+ function sanitizeKnowledgePackage(pkg) {
2015
+ const { directory: _directory, objects, ...rest } = pkg;
2016
+ return {
2017
+ ...rest,
2018
+ objects: objects.map((object) => ({
2019
+ ...object,
2020
+ filePath: sanitizePath(object.filePath)
2021
+ }))
2022
+ };
2023
+ }
2024
+ function sanitizePath(path) {
2025
+ if (!path) return path;
2026
+ if (path.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path)) {
2027
+ return "<absolute-path>";
2028
+ }
2029
+ return path;
2030
+ }
2031
+ if (isEntrypoint()) {
2032
+ main().catch((error) => {
2033
+ console.error(`[${SERVER_NAME}] Fatal error:`, error);
2034
+ process.exit(1);
2035
+ });
2036
+ }
2037
+
2038
+ export { SERVER_VERSION, TOOLS };
2039
+ //# sourceMappingURL=index.js.map