@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.
@@ -0,0 +1,1274 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
5
+ import { join, relative, resolve, dirname } from 'node:path';
6
+
7
+ const SDK_PACKAGE_NAMES = /* @__PURE__ */ new Set([
8
+ "@happyvertical/ai",
9
+ "@happyvertical/cache",
10
+ "@happyvertical/documents",
11
+ "@happyvertical/email",
12
+ "@happyvertical/encryption",
13
+ "@happyvertical/files",
14
+ "@happyvertical/geo",
15
+ "@happyvertical/images",
16
+ "@happyvertical/jobs",
17
+ "@happyvertical/json",
18
+ "@happyvertical/logger",
19
+ "@happyvertical/messages",
20
+ "@happyvertical/ocr",
21
+ "@happyvertical/pdf",
22
+ "@happyvertical/projects",
23
+ "@happyvertical/repos",
24
+ "@happyvertical/secrets",
25
+ "@happyvertical/spider",
26
+ "@happyvertical/sql",
27
+ "@happyvertical/utils"
28
+ ]);
29
+ const RELATIONSHIP_FIELD_TYPES = /* @__PURE__ */ new Set([
30
+ "foreignKey",
31
+ "crossPackageRef",
32
+ "oneToMany",
33
+ "manyToMany"
34
+ ]);
35
+ const WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
36
+ ".git",
37
+ ".svelte-kit",
38
+ ".turbo",
39
+ "coverage",
40
+ "dist",
41
+ "node_modules"
42
+ ]);
43
+ const STALE_PATTERNS = [
44
+ {
45
+ code: "stale-have-namespace",
46
+ pattern: new RegExp(`@${"have"}/`),
47
+ message: "Stale HappyVertical legacy namespace reference found"
48
+ },
49
+ {
50
+ code: "stale-smrt-core-namespace",
51
+ pattern: new RegExp(`@${"smrt"}/core`),
52
+ message: "Stale SMRT core namespace reference found"
53
+ },
54
+ {
55
+ code: "stale-field-helper-import",
56
+ pattern: /@happyvertical\/smrt-core\/fields/,
57
+ message: "Stale field-helper import path found"
58
+ },
59
+ {
60
+ code: "stale-docs-codex-command",
61
+ pattern: new RegExp(
62
+ `docs:${"codex"}|docs-${"codex"}|\\.${"codex"}|${"codex"}-command`,
63
+ "i"
64
+ ),
65
+ message: "Stale Codex-specific downstream-doc reference found"
66
+ }
67
+ ];
68
+ async function buildKnowledgeIndex(options = {}) {
69
+ const rootDir = findProjectRoot(options.rootDir ?? process.cwd());
70
+ const includeDocs = options.includeDocs ?? true;
71
+ const packageDirs = discoverProjectPackageDirs(rootDir);
72
+ const packages = packageDirs.map(
73
+ (dir) => readKnowledgePackage(rootDir, dir, includeDocs)
74
+ );
75
+ packages.push(
76
+ ...discoverInstalledSdkPackages(rootDir, packageDirs, includeDocs)
77
+ );
78
+ const uniquePackages = dedupePackages(packages);
79
+ const scopedPackages = filterKnowledgePackages(uniquePackages, options);
80
+ const smrtPackages = scopedPackages.filter((pkg) => pkg.kind === "smrt");
81
+ const sdkPackages = scopedPackages.filter((pkg) => pkg.kind === "sdk");
82
+ return {
83
+ schemaVersion: 1,
84
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
85
+ rootDir,
86
+ packages: scopedPackages,
87
+ smrtPackages,
88
+ sdkPackages,
89
+ relationshipsV2: summarizeRelationshipsV2(scopedPackages)
90
+ };
91
+ }
92
+ async function checkKnowledgeFreshness(options = {}) {
93
+ const index = await buildKnowledgeIndex(options);
94
+ return checkKnowledgeFreshnessFromIndex(index, options);
95
+ }
96
+ async function checkKnowledgeFreshnessFromIndex(index, options = {}) {
97
+ const issues = [];
98
+ const changedFiles = options.changed ? getChangedFiles(index.rootDir) : void 0;
99
+ for (const pkg of index.packages.filter((item) => item.kind !== "sdk")) {
100
+ const packageJsonPath = join(pkg.directory, "package.json");
101
+ if (!pkg.hasAgentsMd) {
102
+ issues.push({
103
+ severity: "error",
104
+ code: "missing-agents-md",
105
+ message: "Workspace package is missing canonical AGENTS.md",
106
+ file: relative(index.rootDir, join(pkg.directory, "AGENTS.md")),
107
+ packageName: pkg.name
108
+ });
109
+ }
110
+ if (!pkg.hasClaudeMd) {
111
+ issues.push({
112
+ severity: "error",
113
+ code: "missing-claude-shim",
114
+ message: "Workspace package is missing CLAUDE.md compatibility shim",
115
+ file: relative(index.rootDir, join(pkg.directory, "CLAUDE.md")),
116
+ packageName: pkg.name
117
+ });
118
+ } else if (!pkg.hasClaudeShim) {
119
+ issues.push({
120
+ severity: "error",
121
+ code: "claude-not-shim",
122
+ message: "CLAUDE.md must contain only @AGENTS.md",
123
+ file: relative(index.rootDir, join(pkg.directory, "CLAUDE.md")),
124
+ packageName: pkg.name
125
+ });
126
+ }
127
+ for (const entry of pkg.files) {
128
+ if (entry === "dist" || entry.startsWith("dist/") || entry.includes("*")) {
129
+ continue;
130
+ }
131
+ const entryPath = join(pkg.directory, entry);
132
+ if (!existsSync(entryPath)) {
133
+ issues.push({
134
+ severity: "error",
135
+ code: "package-files-entry-missing",
136
+ message: `package.json files entry "${entry}" does not exist`,
137
+ file: relative(index.rootDir, packageJsonPath),
138
+ packageName: pkg.name
139
+ });
140
+ }
141
+ }
142
+ if (!pkg.files.includes("AGENTS.md")) {
143
+ issues.push({
144
+ severity: "error",
145
+ code: "package-files-missing-agents",
146
+ message: "package.json files allowlist must include AGENTS.md",
147
+ file: relative(index.rootDir, packageJsonPath),
148
+ packageName: pkg.name
149
+ });
150
+ }
151
+ if (!pkg.files.includes("CLAUDE.md")) {
152
+ issues.push({
153
+ severity: "error",
154
+ code: "package-files-missing-claude-shim",
155
+ message: "package.json files allowlist must include CLAUDE.md shim",
156
+ file: relative(index.rootDir, packageJsonPath),
157
+ packageName: pkg.name
158
+ });
159
+ }
160
+ }
161
+ for (const pkg of index.packages) {
162
+ issues.push(...checkDomainKnowledgeArtifact(index.rootDir, pkg));
163
+ }
164
+ issues.push(...findStalePatternIssues(index.rootDir, changedFiles));
165
+ const effectiveIssues = issues.map(
166
+ (issue) => issue.code.startsWith("stale-") ? {
167
+ ...issue,
168
+ severity: options.strict ? "error" : "warning"
169
+ } : issue
170
+ );
171
+ const errorCount = effectiveIssues.filter(
172
+ (i) => i.severity === "error"
173
+ ).length;
174
+ const warningCount = effectiveIssues.filter(
175
+ (i) => i.severity === "warning"
176
+ ).length;
177
+ return {
178
+ ok: errorCount === 0,
179
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
180
+ rootDir: index.rootDir,
181
+ issueCount: effectiveIssues.length,
182
+ errorCount,
183
+ warningCount,
184
+ issues: effectiveIssues
185
+ };
186
+ }
187
+ function checkDomainKnowledgeArtifact(rootDir, pkg) {
188
+ const issues = [];
189
+ if (pkg.exportKeys.includes("./smrt-knowledge.json") && !pkg.hasDomainKnowledge) {
190
+ issues.push({
191
+ severity: "error",
192
+ code: "missing-domain-knowledge",
193
+ message: "package exports ./smrt-knowledge.json but no domain knowledge artifact was found",
194
+ file: relative(rootDir, join(pkg.directory, "package.json")),
195
+ packageName: pkg.name
196
+ });
197
+ }
198
+ if (!pkg.domainKnowledge || !pkg.domainKnowledgePath) {
199
+ return issues;
200
+ }
201
+ const hashes = pkg.domainKnowledge.sourceHashes ?? {};
202
+ const checks = [
203
+ {
204
+ key: "packageJson",
205
+ filePath: join(pkg.directory, "package.json"),
206
+ kind: "raw",
207
+ label: "package.json"
208
+ },
209
+ {
210
+ key: "agents",
211
+ filePath: existsSync(join(pkg.directory, "AGENTS.md")) ? join(pkg.directory, "AGENTS.md") : void 0,
212
+ kind: "raw",
213
+ label: "AGENTS.md"
214
+ },
215
+ {
216
+ key: "manifest",
217
+ filePath: domainSourceManifestPath(rootDir, pkg),
218
+ kind: "json",
219
+ label: "manifest"
220
+ }
221
+ ];
222
+ for (const check of checks) {
223
+ const expected = hashes[check.key];
224
+ if (!expected) continue;
225
+ if (!check.filePath || !existsSync(check.filePath)) {
226
+ issues.push({
227
+ severity: "error",
228
+ code: "domain-knowledge-source-missing",
229
+ message: `${check.label} source for smrt-knowledge.json is missing`,
230
+ file: pkg.domainKnowledgePath,
231
+ packageName: pkg.name
232
+ });
233
+ continue;
234
+ }
235
+ const actual = check.kind === "json" ? hashJsonFile(check.filePath) : hashFile(check.filePath);
236
+ if (actual !== expected) {
237
+ issues.push({
238
+ severity: "error",
239
+ code: "stale-domain-knowledge",
240
+ message: `${check.label} changed since smrt-knowledge.json was generated`,
241
+ file: pkg.domainKnowledgePath,
242
+ packageName: pkg.name
243
+ });
244
+ }
245
+ }
246
+ return issues;
247
+ }
248
+ function domainSourceManifestPath(rootDir, pkg) {
249
+ if (pkg.domainKnowledge?.sourceManifestPath) {
250
+ return join(pkg.directory, pkg.domainKnowledge.sourceManifestPath);
251
+ }
252
+ if (pkg.manifestPath) {
253
+ return join(rootDir, pkg.manifestPath);
254
+ }
255
+ return void 0;
256
+ }
257
+ async function diffKnowledgeIndex(options = {}) {
258
+ const index = await buildKnowledgeIndex({ rootDir: options.rootDir });
259
+ const base = options.base ?? "HEAD";
260
+ const changedFiles = getChangedFiles(index.rootDir, base);
261
+ const packageQuery = options.packageName ?? options.package;
262
+ const selected = selectPackagesForFiles(index, changedFiles).filter((pkg) => scopeAllowsPackage(pkg, options.scope)).filter(
263
+ (pkg) => packageQuery ? packageMatches(pkg, packageQuery.toLowerCase()) : true
264
+ );
265
+ const changedPackages = selected.map((pkg) => pkg.name);
266
+ return { base, changedFiles, changedPackages, index };
267
+ }
268
+ async function buildReviewContext(options = {}) {
269
+ const index = await buildKnowledgeIndex({ rootDir: options.rootDir });
270
+ const changedFiles = options.changedFiles ?? getChangedFiles(index.rootDir);
271
+ const selectedPackages = selectPackages(index, {
272
+ changedFiles,
273
+ text: [options.focus, options.documentation].filter(Boolean).join("\n"),
274
+ scope: options.scope,
275
+ packageName: options.packageName ?? options.package
276
+ });
277
+ const selectedSdkPackages = selectSdkPackages(
278
+ index,
279
+ selectedPackages,
280
+ [
281
+ options.focus,
282
+ options.documentation,
283
+ options.packageName ?? options.package
284
+ ],
285
+ {
286
+ scope: options.scope,
287
+ packageName: options.packageName ?? options.package
288
+ }
289
+ );
290
+ const deterministicFindings = findStalePatternIssues(
291
+ index.rootDir,
292
+ changedFiles.length > 0 ? changedFiles : void 0
293
+ ).concat(buildReviewFindings(index, changedFiles, selectedPackages));
294
+ return {
295
+ selectedPackages,
296
+ selectedSdkPackages,
297
+ deterministicFindings,
298
+ promptBundle: buildPromptBundle({
299
+ title: "SMRT code review",
300
+ task: "Review the changed SMRT code. Prioritize correctness, relationships-v2 invariants, tenancy, SDK usage, prompt/data safety, and stale documentation.",
301
+ index,
302
+ packages: selectedPackages,
303
+ sdkPackages: selectedSdkPackages,
304
+ sourceFiles: changedFiles,
305
+ extraContext: options.focus
306
+ })
307
+ };
308
+ }
309
+ async function smrtReview(options = {}) {
310
+ const context = await buildReviewContext(options);
311
+ const mode = options.mode ?? "both";
312
+ return {
313
+ mode,
314
+ selectedPackages: context.selectedPackages,
315
+ selectedSdkPackages: context.selectedSdkPackages,
316
+ ...mode !== "prompt-bundle" ? { deterministicFindings: context.deterministicFindings } : {},
317
+ ...mode !== "findings" ? { promptBundle: context.promptBundle } : {}
318
+ };
319
+ }
320
+ async function buildArchitectureContext(options = {}) {
321
+ const index = await buildKnowledgeIndex({ rootDir: options.rootDir });
322
+ const text = [options.idea, options.documentation, options.focus].filter(Boolean).join("\n");
323
+ const selectedPackages = selectPackages(index, {
324
+ text,
325
+ scope: options.scope,
326
+ packageName: options.packageName ?? options.package
327
+ });
328
+ const selectedSdkPackages = selectSdkPackages(
329
+ index,
330
+ selectedPackages,
331
+ [text, options.packageName ?? options.package],
332
+ {
333
+ scope: options.scope,
334
+ packageName: options.packageName ?? options.package
335
+ }
336
+ );
337
+ return {
338
+ selectedPackages,
339
+ selectedSdkPackages,
340
+ promptBundle: buildPromptBundle({
341
+ title: "SMRT architecture planning",
342
+ task: "Suggest the SMRT packages, HappyVertical SDK packages, object model, integration points, risks, and implementation slices for this project idea.",
343
+ index,
344
+ packages: selectedPackages,
345
+ sdkPackages: selectedSdkPackages,
346
+ sourceFiles: [],
347
+ extraContext: text
348
+ })
349
+ };
350
+ }
351
+ async function smrtArchitecture(options = {}) {
352
+ const context = await buildArchitectureContext(options);
353
+ const ideaText = [options.idea, options.documentation, options.focus].filter(Boolean).join("\n");
354
+ return {
355
+ ...context,
356
+ recommendations: buildArchitectureRecommendations(context, ideaText)
357
+ };
358
+ }
359
+ function renderKnowledgeIndexMarkdown(index) {
360
+ const lines = [
361
+ "# SMRT Knowledge Index",
362
+ "",
363
+ `Generated: ${index.generatedAt}`,
364
+ `Root: ${index.rootDir}`,
365
+ "",
366
+ "## Summary",
367
+ "",
368
+ `- SMRT packages: ${index.smrtPackages.length}`,
369
+ `- SDK packages: ${index.sdkPackages.length}`,
370
+ `- foreignKey fields: ${index.relationshipsV2.foreignKeyFields}`,
371
+ `- crossPackageRef fields: ${index.relationshipsV2.crossPackageRefFields}`,
372
+ `- junction collections: ${index.relationshipsV2.junctionCollections}`,
373
+ `- hierarchical objects: ${index.relationshipsV2.hierarchicalObjects}`,
374
+ `- polymorphic associations: ${index.relationshipsV2.polymorphicAssociations}`,
375
+ `- UUID columns: ${index.relationshipsV2.uuidColumns}`,
376
+ "",
377
+ "## Packages",
378
+ ""
379
+ ];
380
+ for (const pkg of index.packages) {
381
+ lines.push(`### ${pkg.name}`);
382
+ lines.push("");
383
+ lines.push(`- kind: ${pkg.kind}`);
384
+ lines.push(`- version: ${pkg.version}`);
385
+ lines.push(`- objects: ${pkg.objects.length}`);
386
+ lines.push(`- SMRT deps: ${pkg.smrtDependencies.join(", ") || "(none)"}`);
387
+ lines.push(`- SDK deps: ${pkg.sdkDependencies.join(", ") || "(none)"}`);
388
+ lines.push(`- exports: ${pkg.exportKeys.join(", ") || "(none)"}`);
389
+ lines.push(`- MCP tools: ${pkg.mcpTools.length}`);
390
+ lines.push(
391
+ `- domain knowledge: ${pkg.domainKnowledgePath ?? "(manifest fallback)"}`
392
+ );
393
+ lines.push(
394
+ `- docs: ${pkg.docSource ?? "(none)"}${pkg.hasClaudeShim ? " + CLAUDE.md shim" : ""}`
395
+ );
396
+ if (pkg.relationshipFeatures.length > 0) {
397
+ lines.push(`- relationships-v2: ${pkg.relationshipFeatures.join(", ")}`);
398
+ }
399
+ lines.push("");
400
+ }
401
+ return lines.join("\n");
402
+ }
403
+ function renderFreshnessResult(result) {
404
+ const lines = [
405
+ result.ok ? "✓ SMRT knowledge is fresh" : "✗ SMRT knowledge has issues",
406
+ "",
407
+ `Root: ${result.rootDir}`,
408
+ `Errors: ${result.errorCount}`,
409
+ `Warnings: ${result.warningCount}`,
410
+ ""
411
+ ];
412
+ for (const issue of result.issues) {
413
+ const location = issue.file ? ` (${issue.file})` : "";
414
+ lines.push(
415
+ `- [${issue.severity.toUpperCase()}] ${issue.code}: ${issue.message}${location}`
416
+ );
417
+ }
418
+ return lines.join("\n");
419
+ }
420
+ function findProjectRoot(startDir) {
421
+ let current = resolve(startDir);
422
+ for (; ; ) {
423
+ if (existsSync(join(current, "pnpm-workspace.yaml")) && existsSync(join(current, "packages"))) {
424
+ return current;
425
+ }
426
+ const parent = dirname(current);
427
+ if (parent === current) return resolve(startDir);
428
+ current = parent;
429
+ }
430
+ }
431
+ function discoverWorkspacePackageDirs(rootDir) {
432
+ const packagesDir = join(rootDir, "packages");
433
+ if (!existsSync(packagesDir)) return [];
434
+ return readdirSync(packagesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join(packagesDir, entry.name)).filter((dir) => existsSync(join(dir, "package.json"))).sort();
435
+ }
436
+ function discoverProjectPackageDirs(rootDir) {
437
+ const workspaceDirs = discoverWorkspacePackageDirs(rootDir);
438
+ if (existsSync(join(rootDir, "package.json")) && hasLocalDomainArtifact(rootDir)) {
439
+ return [rootDir, ...workspaceDirs.filter((dir) => dir !== rootDir)];
440
+ }
441
+ return workspaceDirs;
442
+ }
443
+ function hasLocalDomainArtifact(rootDir) {
444
+ return [
445
+ join(rootDir, ".smrt", "smrt-knowledge.json"),
446
+ join(rootDir, ".smrt", "manifest.json"),
447
+ join(rootDir, "dist", "smrt-knowledge.json"),
448
+ join(rootDir, "dist", "manifest.json"),
449
+ join(rootDir, "src", "manifest", "smrt-knowledge.json"),
450
+ join(rootDir, "src", "manifest", "manifest.json")
451
+ ].some((path) => existsSync(path));
452
+ }
453
+ function discoverInstalledSdkPackages(rootDir, packageDirs, includeDocs) {
454
+ const scopeDirs = [
455
+ join(rootDir, "node_modules", "@happyvertical"),
456
+ ...packageDirs.map((dir) => join(dir, "node_modules", "@happyvertical"))
457
+ ];
458
+ return scopeDirs.filter(
459
+ (scopeDir, index, all) => existsSync(scopeDir) && all.indexOf(scopeDir) === index
460
+ ).flatMap(
461
+ (scopeDir) => readdirSync(scopeDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => {
462
+ const entryPath = join(scopeDir, entry.name);
463
+ try {
464
+ return lstatSync(entryPath).isSymbolicLink() ? realpathSync(entryPath) : entryPath;
465
+ } catch {
466
+ return entryPath;
467
+ }
468
+ })
469
+ ).filter((dir) => {
470
+ const pkg = readJson(join(dir, "package.json"));
471
+ return typeof pkg?.name === "string" && SDK_PACKAGE_NAMES.has(pkg.name) && !pkg.name.startsWith("@happyvertical/smrt-");
472
+ }).map((dir) => readKnowledgePackage(rootDir, dir, includeDocs));
473
+ }
474
+ function filterKnowledgePackages(packages, options) {
475
+ const packageQuery = options.packageName ?? options.package;
476
+ return packages.filter((pkg) => {
477
+ if (packageQuery && !packageMatches(pkg, packageQuery.toLowerCase())) {
478
+ return false;
479
+ }
480
+ switch (options.scope) {
481
+ case "local":
482
+ return pkg.kind !== "sdk" && !pkg.relativeDirectory.includes("node_modules");
483
+ case "package":
484
+ return pkg.kind !== "sdk";
485
+ case "sdk":
486
+ return pkg.kind === "sdk";
487
+ case "project":
488
+ case void 0:
489
+ return true;
490
+ default:
491
+ return true;
492
+ }
493
+ });
494
+ }
495
+ function readKnowledgePackage(rootDir, directory, includeDocs) {
496
+ const packageJson = readJson(join(directory, "package.json")) ?? {};
497
+ const dependencies = objectRecord(packageJson.dependencies);
498
+ const devDependencies = objectRecord(packageJson.devDependencies);
499
+ const peerDependencies = objectRecord(packageJson.peerDependencies);
500
+ const allDeps = { ...dependencies, ...devDependencies, ...peerDependencies };
501
+ const name = String(packageJson.name ?? directory);
502
+ const agentsPath = join(directory, "AGENTS.md");
503
+ const claudePath = join(directory, "CLAUDE.md");
504
+ const hasAgentsMd = existsSync(agentsPath);
505
+ const hasClaudeMd = existsSync(claudePath);
506
+ const claudeContent = hasClaudeMd ? readFileSync(claudePath, "utf8") : "";
507
+ const agentsContent = hasAgentsMd ? readFileSync(agentsPath, "utf8") : "";
508
+ const fallbackClaudeDoc = hasClaudeMd && claudeContent.trim() !== "@AGENTS.md" ? claudeContent : "";
509
+ const domainKnowledge = readDomainKnowledge(directory);
510
+ const docSource = hasAgentsMd ? "AGENTS.md" : fallbackClaudeDoc ? "CLAUDE.md" : null;
511
+ const manifest = readManifest(directory);
512
+ const objects = domainKnowledge ? readDomainKnowledgeObjects(domainKnowledge.content) : manifest ? readManifestObjects(manifest.content) : [];
513
+ const prompts = domainKnowledge ? readDomainKnowledgePrompts(domainKnowledge.content, directory, rootDir) : readPrompts(directory, rootDir);
514
+ const smrtDependencies = Object.keys(allDeps).filter((dep) => dep.startsWith("@happyvertical/smrt-")).sort();
515
+ const sdkDependencies = Object.keys(allDeps).filter((dep) => SDK_PACKAGE_NAMES.has(dep)).sort();
516
+ return {
517
+ name,
518
+ version: String(packageJson.version ?? "0.0.0"),
519
+ kind: packageKind(name),
520
+ directory,
521
+ relativeDirectory: relative(rootDir, directory),
522
+ files: Array.isArray(packageJson.files) ? packageJson.files : [],
523
+ exportKeys: exportKeys(packageJson.exports),
524
+ dependencies,
525
+ devDependencies,
526
+ peerDependencies,
527
+ smrtDependencies,
528
+ sdkDependencies,
529
+ hasAgentsMd,
530
+ hasClaudeMd,
531
+ hasClaudeShim: claudeContent.trim() === "@AGENTS.md",
532
+ docSource,
533
+ agentDoc: includeDocs ? domainKnowledge?.content.agentDoc || (hasAgentsMd ? agentsContent : fallbackClaudeDoc || void 0) : void 0,
534
+ hasDomainKnowledge: Boolean(domainKnowledge),
535
+ domainKnowledgePath: domainKnowledge?.path ? relative(rootDir, domainKnowledge.path) : void 0,
536
+ domainKnowledge: domainKnowledge?.content,
537
+ manifestPath: manifest?.path ? relative(rootDir, manifest.path) : void 0,
538
+ manifestVersion: typeof manifest?.content.version === "string" ? manifest.content.version : void 0,
539
+ objects,
540
+ prompts,
541
+ mcpTools: domainKnowledge ? domainMcpTools(domainKnowledge.content) : mcpTools(objects),
542
+ relationshipFeatures: relationshipFeatures(objects)
543
+ };
544
+ }
545
+ function readDomainKnowledge(directory) {
546
+ for (const path of [
547
+ join(directory, ".smrt", "smrt-knowledge.json"),
548
+ join(directory, "dist", "smrt-knowledge.json"),
549
+ join(directory, "src", "manifest", "smrt-knowledge.json")
550
+ ]) {
551
+ const content = readJson(path);
552
+ if (isDomainKnowledgeManifest(content)) {
553
+ return { path, content };
554
+ }
555
+ }
556
+ return null;
557
+ }
558
+ function isDomainKnowledgeManifest(value) {
559
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
560
+ return false;
561
+ }
562
+ const record = value;
563
+ return record.schemaVersion === 1 && Array.isArray(record.objects);
564
+ }
565
+ function readManifest(directory) {
566
+ for (const path of [
567
+ join(directory, "src", "manifest", "manifest.json"),
568
+ join(directory, ".smrt", "manifest.json"),
569
+ join(directory, "dist", "manifest.json")
570
+ ]) {
571
+ const content = readJson(path);
572
+ if (content) return { path, content };
573
+ }
574
+ return null;
575
+ }
576
+ function readManifestObjects(manifest) {
577
+ const objects = objectRecord(manifest.objects);
578
+ return Object.values(objects).map((raw) => {
579
+ const item = raw;
580
+ const fields = objectRecord(item.fields);
581
+ const schemaColumns = objectRecord(item.schema?.columns);
582
+ const decoratorConfig = item.decoratorConfig;
583
+ const knowledgeFields = Object.entries(fields).map(([fieldName, field]) => {
584
+ const fieldInfo = field;
585
+ const columnName = camelToSnake(fieldName);
586
+ const column = schemaColumns[columnName];
587
+ return {
588
+ name: fieldName,
589
+ type: String(fieldInfo.type ?? "unknown"),
590
+ required: typeof fieldInfo.required === "boolean" ? fieldInfo.required : void 0,
591
+ related: typeof fieldInfo.related === "string" ? fieldInfo.related : void 0,
592
+ columnType: typeof column?.type === "string" ? column.type : void 0
593
+ };
594
+ });
595
+ return {
596
+ className: String(item.className ?? item.name ?? "Unknown"),
597
+ qualifiedName: typeof item.qualifiedName === "string" ? item.qualifiedName : void 0,
598
+ filePath: typeof item.filePath === "string" ? item.filePath : void 0,
599
+ extends: typeof item.extends === "string" ? item.extends : void 0,
600
+ collection: typeof item.collection === "string" ? item.collection : void 0,
601
+ mcpOperations: mcpOperations(item.decoratorConfig?.mcp),
602
+ tableName: typeof decoratorConfig?.tableName === "string" ? decoratorConfig.tableName : typeof item.schema?.tableName === "string" ? item.schema.tableName : void 0,
603
+ idColumnType: typeof schemaColumns.id?.type === "string" ? schemaColumns.id.type : void 0,
604
+ fields: knowledgeFields,
605
+ relationships: knowledgeFields.filter(
606
+ (field) => RELATIONSHIP_FIELD_TYPES.has(field.type)
607
+ ),
608
+ methods: Object.keys(objectRecord(item.methods)).sort()
609
+ };
610
+ });
611
+ }
612
+ function readDomainKnowledgeObjects(manifest) {
613
+ return manifest.objects.map((object) => ({
614
+ className: object.name,
615
+ qualifiedName: object.qualifiedName,
616
+ extends: object.extends,
617
+ collection: object.collection,
618
+ mcpOperations: object.surfaces.filter((surface) => surface.kind === "mcp").map((surface) => surface.operation).sort(),
619
+ tableName: object.tableName,
620
+ idColumnType: object.fields.find((field) => field.name === "id")?.columnType,
621
+ fields: object.fields.map((field) => ({
622
+ name: field.name,
623
+ type: field.type,
624
+ required: field.required,
625
+ related: field.related,
626
+ columnType: field.columnType
627
+ })),
628
+ relationships: object.relationships.map((field) => ({
629
+ name: field.name,
630
+ type: field.type,
631
+ required: field.required,
632
+ related: field.related,
633
+ columnType: field.columnType
634
+ })),
635
+ methods: object.methods
636
+ }));
637
+ }
638
+ function readDomainKnowledgePrompts(manifest, directory, rootDir) {
639
+ return manifest.prompts.map((prompt) => ({
640
+ filePath: relative(rootDir, join(directory, prompt.filePath)),
641
+ key: prompt.key
642
+ }));
643
+ }
644
+ function readPrompts(directory, rootDir) {
645
+ const srcDir = join(directory, "src");
646
+ if (!existsSync(srcDir)) return [];
647
+ const prompts = [];
648
+ for (const filePath of walkFiles(srcDir)) {
649
+ if (!filePath.endsWith(".ts")) continue;
650
+ const content = readFileSync(filePath, "utf8");
651
+ if (!content.includes("definePrompt")) continue;
652
+ const keyMatch = content.match(/definePrompt\s*\(\s*['"`]([^'"`]+)['"`]/);
653
+ prompts.push({
654
+ filePath: relative(rootDir, filePath),
655
+ key: keyMatch?.[1]
656
+ });
657
+ }
658
+ return prompts;
659
+ }
660
+ function walkFiles(dir) {
661
+ const files = [];
662
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
663
+ if (WALK_SKIP_DIRS.has(entry.name)) continue;
664
+ const fullPath = join(dir, entry.name);
665
+ if (entry.isDirectory()) {
666
+ files.push(...walkFiles(fullPath));
667
+ } else if (entry.isFile()) {
668
+ files.push(fullPath);
669
+ }
670
+ }
671
+ return files;
672
+ }
673
+ function packageKind(name) {
674
+ if (name.startsWith("@happyvertical/smrt-")) return "smrt";
675
+ if (SDK_PACKAGE_NAMES.has(name)) return "sdk";
676
+ return "workspace";
677
+ }
678
+ function exportKeys(exportsField) {
679
+ if (typeof exportsField === "string") return ["."];
680
+ if (typeof exportsField !== "object" || exportsField === null || Array.isArray(exportsField)) {
681
+ return [];
682
+ }
683
+ return Object.keys(exportsField).sort();
684
+ }
685
+ function mcpOperations(config) {
686
+ if (config === false) return [];
687
+ const defaultOperations = ["list", "get", "create", "update", "delete"];
688
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
689
+ return defaultOperations;
690
+ }
691
+ const record = config;
692
+ const include = Array.isArray(record.include) ? record.include.filter((item) => typeof item === "string") : defaultOperations;
693
+ const exclude = new Set(
694
+ Array.isArray(record.exclude) ? record.exclude.filter(
695
+ (item) => typeof item === "string"
696
+ ) : []
697
+ );
698
+ return include.filter((operation) => !exclude.has(operation));
699
+ }
700
+ function mcpTools(objects) {
701
+ return objects.flatMap((object) => {
702
+ const collection = object.collection ?? object.className.toLowerCase();
703
+ return object.mcpOperations.map((operation) => ({
704
+ name: `${operation}_${collection}`,
705
+ sourceObject: object.qualifiedName ?? object.className,
706
+ operation
707
+ }));
708
+ }).sort((a, b) => a.name.localeCompare(b.name));
709
+ }
710
+ function domainMcpTools(manifest) {
711
+ return manifest.surfaces.filter((surface) => surface.kind === "mcp").map((surface) => ({
712
+ name: surface.name,
713
+ sourceObject: surface.objectName ?? "",
714
+ operation: surface.operation
715
+ })).sort((a, b) => a.name.localeCompare(b.name));
716
+ }
717
+ function summarizeRelationshipsV2(packages) {
718
+ const objects = packages.flatMap((pkg) => pkg.objects);
719
+ const fields = objects.flatMap((object) => object.fields);
720
+ return {
721
+ foreignKeyFields: fields.filter((field) => field.type === "foreignKey").length,
722
+ crossPackageRefFields: fields.filter(
723
+ (field) => field.type === "crossPackageRef"
724
+ ).length,
725
+ junctionCollections: objects.filter(
726
+ (object) => object.extends === "SmrtJunction"
727
+ ).length,
728
+ hierarchicalObjects: objects.filter(
729
+ (object) => object.extends === "SmrtHierarchical"
730
+ ).length,
731
+ polymorphicAssociations: objects.filter(
732
+ (object) => object.fields.some(
733
+ (field) => field.name === "metaType" || field.name === "metaId"
734
+ )
735
+ ).length,
736
+ uuidColumns: fields.filter((field) => field.columnType === "UUID").length
737
+ };
738
+ }
739
+ function relationshipFeatures(objects) {
740
+ const features = /* @__PURE__ */ new Set();
741
+ if (objects.some(
742
+ (object) => object.fields.some((field) => field.type === "foreignKey")
743
+ )) {
744
+ features.add("foreignKey");
745
+ }
746
+ if (objects.some(
747
+ (object) => object.fields.some((field) => field.type === "crossPackageRef")
748
+ )) {
749
+ features.add("crossPackageRef");
750
+ }
751
+ if (objects.some((object) => object.extends === "SmrtJunction")) {
752
+ features.add("SmrtJunction");
753
+ }
754
+ if (objects.some((object) => object.extends === "SmrtHierarchical")) {
755
+ features.add("SmrtHierarchical");
756
+ }
757
+ if (objects.some(
758
+ (object) => object.fields.some(
759
+ (field) => field.name === "metaType" || field.name === "metaId"
760
+ )
761
+ )) {
762
+ features.add("SmrtPolymorphicAssociation");
763
+ }
764
+ if (objects.some(
765
+ (object) => object.fields.some((field) => field.columnType === "UUID")
766
+ )) {
767
+ features.add("uuidColumns");
768
+ }
769
+ return [...features].sort();
770
+ }
771
+ function findStalePatternIssues(rootDir, changedFiles) {
772
+ const candidates = changedFiles && changedFiles.length > 0 ? changedFiles.map((file) => join(rootDir, file)) : walkFiles(rootDir);
773
+ const issues = [];
774
+ for (const file of candidates.filter(
775
+ (candidate) => shouldScanStalePatternFile(rootDir, candidate)
776
+ )) {
777
+ if (!existsSync(file) || lstatSync(file).isDirectory()) continue;
778
+ const rel = relative(rootDir, file);
779
+ const content = readFileSync(file, "utf8");
780
+ for (const stale of STALE_PATTERNS) {
781
+ if (!stale.pattern.test(content)) continue;
782
+ issues.push({
783
+ severity: "warning",
784
+ code: stale.code,
785
+ message: stale.message,
786
+ file: rel
787
+ });
788
+ }
789
+ }
790
+ return issues;
791
+ }
792
+ function shouldScanStalePatternFile(rootDir, filePath) {
793
+ const rel = relative(rootDir, filePath).replaceAll("\\", "/");
794
+ if (!rel || rel.startsWith("..")) return false;
795
+ const parts = rel.split("/");
796
+ if (parts.includes("node_modules") || parts.includes("dist")) return false;
797
+ if (rel === "AGENTS.md" || rel === "README.md") return true;
798
+ if (rel.endsWith("/AGENTS.md") || rel.endsWith("/README.md")) return true;
799
+ if (!rel.startsWith("docs/content/") || !rel.endsWith(".md")) return false;
800
+ return !(rel.startsWith("docs/content/api/") || rel.startsWith("docs/content/rfcs/") || rel.startsWith("docs/content/architecture/"));
801
+ }
802
+ function buildReviewFindings(index, changedFiles, selectedPackages) {
803
+ const issues = [];
804
+ for (const pkg of selectedPackages) {
805
+ const changedPackageFiles = changedFiles.filter(
806
+ (file) => file === pkg.relativeDirectory || file.startsWith(`${pkg.relativeDirectory}/`)
807
+ );
808
+ if (!pkg.agentDoc && pkg.kind === "smrt") {
809
+ issues.push({
810
+ severity: "warning",
811
+ code: "missing-package-expertise",
812
+ message: "Selected SMRT package has no authored AGENTS.md expertise",
813
+ file: pkg.relativeDirectory,
814
+ packageName: pkg.name
815
+ });
816
+ }
817
+ if (changedPackageFiles.some((file) => file.endsWith(".ts")) && pkg.relationshipFeatures.length > 0) {
818
+ issues.push({
819
+ severity: "warning",
820
+ code: "relationship-sensitive-review",
821
+ message: `Package uses relationships-v2 features: ${pkg.relationshipFeatures.join(", ")}`,
822
+ file: changedPackageFiles.find((file) => file.endsWith(".ts")),
823
+ packageName: pkg.name
824
+ });
825
+ }
826
+ if (changedPackageFiles.some((file) => file.endsWith(".ts")) && pkg.mcpTools.length > 0) {
827
+ issues.push({
828
+ severity: "warning",
829
+ code: "mcp-surface-review",
830
+ message: `Package exposes ${pkg.mcpTools.length} generated MCP tool(s); check public tool compatibility`,
831
+ file: changedPackageFiles.find((file) => file.endsWith(".ts")),
832
+ packageName: pkg.name
833
+ });
834
+ }
835
+ const manifestFile = changedPackageFiles.find(
836
+ (file) => isPackageManifestFile(pkg, file)
837
+ );
838
+ if (manifestFile) {
839
+ issues.push({
840
+ severity: "warning",
841
+ code: "package-manifest-review",
842
+ message: "package.json changed; verify exports, files, dependencies, AGENTS.md, and CLAUDE.md shim packaging",
843
+ file: manifestFile,
844
+ packageName: pkg.name
845
+ });
846
+ }
847
+ const publicEntrypointFile = changedPackageFiles.find(
848
+ (file) => isPublicEntrypointFile(pkg, file)
849
+ );
850
+ if (publicEntrypointFile) {
851
+ issues.push({
852
+ severity: "warning",
853
+ code: "public-entrypoint-review",
854
+ message: "Public package entrypoint changed; check exports, generated surfaces, and downstream docs",
855
+ file: publicEntrypointFile,
856
+ packageName: pkg.name
857
+ });
858
+ }
859
+ const agentDocFile = changedPackageFiles.find(
860
+ (file) => isPackageAgentDocFile(pkg, file)
861
+ );
862
+ if (agentDocFile) {
863
+ issues.push({
864
+ severity: "warning",
865
+ code: "agent-expertise-review",
866
+ message: "AGENTS.md changed; validate authored expertise against generated objects, relationships, exports, and SDK dependencies",
867
+ file: agentDocFile,
868
+ packageName: pkg.name
869
+ });
870
+ }
871
+ }
872
+ for (const file of changedFiles) {
873
+ if (!file.startsWith("packages/")) continue;
874
+ if (selectPackagesForFiles(index, [file]).length > 0) continue;
875
+ issues.push({
876
+ severity: "warning",
877
+ code: "changed-file-without-package-expert",
878
+ message: "Changed file is under packages/ but did not map to a SMRT package",
879
+ file
880
+ });
881
+ }
882
+ return issues;
883
+ }
884
+ function isPackageManifestFile(pkg, file) {
885
+ return file === `${pkg.relativeDirectory}/package.json`;
886
+ }
887
+ function isPackageAgentDocFile(pkg, file) {
888
+ return file === `${pkg.relativeDirectory}/AGENTS.md`;
889
+ }
890
+ function isPublicEntrypointFile(pkg, file) {
891
+ const sourcePrefix = `${pkg.relativeDirectory}/src/`;
892
+ if (!file.startsWith(sourcePrefix)) return false;
893
+ const sourcePath = file.slice(sourcePrefix.length);
894
+ return sourcePath === "index.ts" || sourcePath === "index.tsx" || sourcePath === "index.js" || sourcePath.startsWith("api/") || sourcePath.startsWith("cli/") || sourcePath.startsWith("mcp/") || sourcePath.startsWith("tools/");
895
+ }
896
+ function buildArchitectureRecommendations(context, ideaText) {
897
+ const smrtPackages = context.selectedPackages.map((pkg) => pkg.name);
898
+ const sdkPackages = context.selectedSdkPackages.map((pkg) => pkg.name);
899
+ const objectModelSketch = buildObjectModelSketch(context.selectedPackages);
900
+ const risks = buildArchitectureRisks(context.selectedPackages, ideaText);
901
+ const questions = buildArchitectureQuestions(
902
+ context.selectedPackages,
903
+ ideaText
904
+ );
905
+ return {
906
+ smrtPackages,
907
+ sdkPackages,
908
+ objectModelSketch,
909
+ risks,
910
+ questions,
911
+ notes: [
912
+ "Use SMRT packages for domain/runtime models and generated REST, CLI, MCP, and AI-operation surfaces.",
913
+ "Use HappyVertical SDK packages for AI, SQL, files, logging, secrets, and external capability adapters.",
914
+ "Run smrt dev:knowledge-check after applying model-assisted architecture or review updates."
915
+ ]
916
+ };
917
+ }
918
+ function buildObjectModelSketch(packages) {
919
+ const lines = packages.flatMap((pkg) => {
920
+ const objects = pkg.objects.filter((object) => object.extends !== "SmrtCollection").slice(0, 6).map((object) => object.className);
921
+ if (objects.length === 0) {
922
+ return [
923
+ `${pkg.name}: use package services or templates; no manifest objects indexed.`
924
+ ];
925
+ }
926
+ return [`${pkg.name}: start from ${objects.join(", ")}.`];
927
+ });
928
+ return lines.length > 0 ? lines : [
929
+ "Define the core domain as SmrtObject classes with explicit relationships and generated surfaces."
930
+ ];
931
+ }
932
+ function buildArchitectureRisks(packages, ideaText) {
933
+ const risks = /* @__PURE__ */ new Set();
934
+ const names = new Set(packages.map((pkg) => pkg.name));
935
+ const relationshipFeatures2 = new Set(
936
+ packages.flatMap((pkg) => pkg.relationshipFeatures)
937
+ );
938
+ const lowerText = ideaText.toLowerCase();
939
+ if (relationshipFeatures2.has("crossPackageRef")) {
940
+ risks.add(
941
+ "Cross-package references should stay as plain string ids and validate target package ownership at workflow boundaries."
942
+ );
943
+ }
944
+ if (relationshipFeatures2.has("SmrtJunction")) {
945
+ risks.add(
946
+ "Junction models need explicit conflictColumns so generated upserts stay deterministic."
947
+ );
948
+ }
949
+ if (relationshipFeatures2.has("SmrtHierarchical")) {
950
+ risks.add(
951
+ "Hierarchical models need cycle prevention and tenant-aware child loading paths."
952
+ );
953
+ }
954
+ if (names.has("@happyvertical/smrt-tenancy") || lowerText.includes("tenant")) {
955
+ risks.add(
956
+ "Tenant-scoped models need nullable tenantId semantics and tenant-guarded loadRelated usage."
957
+ );
958
+ }
959
+ if (names.has("@happyvertical/smrt-assets")) {
960
+ risks.add(
961
+ "Asset ownership should use package-owned join tables; reserve asset_associations for generic/provenance links."
962
+ );
963
+ }
964
+ if (names.has("@happyvertical/smrt-secrets")) {
965
+ risks.add(
966
+ "Secrets must use envelope encryption and avoid exposing decrypted values through generated tools."
967
+ );
968
+ }
969
+ if (names.has("@happyvertical/smrt-jobs") || lowerText.includes("schedule")) {
970
+ risks.add(
971
+ "Background work should use jobs/schedules rather than request-time side effects."
972
+ );
973
+ }
974
+ risks.add(
975
+ "Generated architecture should be rechecked with smrt dev:knowledge-check after docs or expertise edits."
976
+ );
977
+ return [...risks];
978
+ }
979
+ function buildArchitectureQuestions(packages, ideaText) {
980
+ const questions = /* @__PURE__ */ new Set();
981
+ const names = new Set(packages.map((pkg) => pkg.name));
982
+ const lowerText = ideaText.toLowerCase();
983
+ questions.add(
984
+ "Which generated surfaces are required first: REST, CLI, MCP, AI operations, or Svelte UI?"
985
+ );
986
+ if (names.has("@happyvertical/smrt-tenancy") || lowerText.includes("tenant")) {
987
+ questions.add(
988
+ "Which objects are global catalogs and which are tenant-scoped records?"
989
+ );
990
+ }
991
+ if (names.has("@happyvertical/smrt-assets")) {
992
+ questions.add("Which package owns each asset relationship join table?");
993
+ }
994
+ if (names.has("@happyvertical/smrt-profiles")) {
995
+ questions.add("Which identities own or administer the primary records?");
996
+ }
997
+ if (names.has("@happyvertical/smrt-content")) {
998
+ questions.add(
999
+ "Which content snapshots need citation-time reference pinning?"
1000
+ );
1001
+ }
1002
+ if (names.has("@happyvertical/smrt-social")) {
1003
+ questions.add(
1004
+ "Which social providers need OAuth, scheduling, and post-state reconciliation?"
1005
+ );
1006
+ }
1007
+ return [...questions];
1008
+ }
1009
+ function selectPackagesForFiles(index, changedFiles) {
1010
+ const selected = /* @__PURE__ */ new Set();
1011
+ for (const file of changedFiles) {
1012
+ for (const pkg of domainPackages(index)) {
1013
+ if (file === pkg.relativeDirectory || file.startsWith(`${pkg.relativeDirectory}/`)) {
1014
+ selected.add(pkg);
1015
+ }
1016
+ }
1017
+ }
1018
+ return [...selected].sort((a, b) => a.name.localeCompare(b.name));
1019
+ }
1020
+ function selectPackages(index, options) {
1021
+ const selected = /* @__PURE__ */ new Set();
1022
+ const packageName = options.packageName?.toLowerCase();
1023
+ if (packageName) {
1024
+ for (const pkg of domainPackages(index)) {
1025
+ if (packageMatches(pkg, packageName)) {
1026
+ selected.add(pkg);
1027
+ }
1028
+ }
1029
+ }
1030
+ for (const pkg of selectPackagesForFiles(index, options.changedFiles ?? [])) {
1031
+ if (scopeAllowsPackage(pkg, options.scope)) selected.add(pkg);
1032
+ }
1033
+ const text = (options.text ?? "").toLowerCase();
1034
+ if (text) {
1035
+ for (const pkg of domainPackages(index)) {
1036
+ if (!scopeAllowsPackage(pkg, options.scope)) continue;
1037
+ const packageKey = pkg.name.replace("@happyvertical/smrt-", "");
1038
+ if (includesToken(text, packageKey) || text.includes(pkg.name.toLowerCase()) || pkg.objects.some(
1039
+ (object) => includesToken(text, object.className.toLowerCase())
1040
+ )) {
1041
+ selected.add(pkg);
1042
+ }
1043
+ }
1044
+ }
1045
+ if (selected.size === 0 && options.scope === "local") {
1046
+ for (const pkg of domainPackages(index).filter(
1047
+ (item) => scopeAllowsPackage(item, "local")
1048
+ )) {
1049
+ selected.add(pkg);
1050
+ }
1051
+ }
1052
+ if (selected.size === 0 && options.scope !== "sdk") {
1053
+ for (const name of [
1054
+ "@happyvertical/smrt-core",
1055
+ "@happyvertical/smrt-config",
1056
+ "@happyvertical/smrt-cli",
1057
+ "@happyvertical/smrt-scanner",
1058
+ "@happyvertical/smrt-dev-mcp"
1059
+ ]) {
1060
+ const pkg = index.smrtPackages.find((item) => item.name === name);
1061
+ if (pkg) selected.add(pkg);
1062
+ }
1063
+ }
1064
+ return [...selected].sort((a, b) => a.name.localeCompare(b.name));
1065
+ }
1066
+ function selectSdkPackages(index, selectedPackages, texts, options = {}) {
1067
+ const selected = /* @__PURE__ */ new Set();
1068
+ const sdkNames = new Set(
1069
+ selectedPackages.flatMap((pkg) => pkg.sdkDependencies)
1070
+ );
1071
+ const text = texts.filter(Boolean).join("\n").toLowerCase();
1072
+ const packageName = options.packageName?.toLowerCase();
1073
+ for (const sdk of index.sdkPackages) {
1074
+ const shortName = sdk.name.replace("@happyvertical/", "");
1075
+ if (options.scope === "sdk" || sdkNames.has(sdk.name) || packageName && packageMatches(sdk, packageName) || text.includes(sdk.name.toLowerCase()) || includesToken(text, shortName)) {
1076
+ selected.add(sdk);
1077
+ }
1078
+ }
1079
+ if (selected.size === 0) {
1080
+ for (const name of [
1081
+ "@happyvertical/ai",
1082
+ "@happyvertical/sql",
1083
+ "@happyvertical/files",
1084
+ "@happyvertical/utils"
1085
+ ]) {
1086
+ const sdk = index.sdkPackages.find((item) => item.name === name);
1087
+ if (sdk) selected.add(sdk);
1088
+ }
1089
+ }
1090
+ return [...selected].sort((a, b) => a.name.localeCompare(b.name));
1091
+ }
1092
+ function domainPackages(index) {
1093
+ return index.packages.filter((pkg) => pkg.kind !== "sdk");
1094
+ }
1095
+ function scopeAllowsPackage(pkg, scope) {
1096
+ switch (scope) {
1097
+ case "sdk":
1098
+ return false;
1099
+ case "local":
1100
+ return !pkg.relativeDirectory.includes("node_modules");
1101
+ case "package":
1102
+ case "project":
1103
+ case void 0:
1104
+ return true;
1105
+ }
1106
+ }
1107
+ function packageMatches(pkg, query) {
1108
+ const normalized = query.toLowerCase();
1109
+ const shortName = pkg.name.replace("@happyvertical/smrt-", "").replace("@happyvertical/", "");
1110
+ return pkg.name.toLowerCase() === normalized || pkg.name.toLowerCase().includes(normalized) || shortName.toLowerCase() === normalized || pkg.relativeDirectory.toLowerCase().endsWith(`/${normalized}`);
1111
+ }
1112
+ function buildPromptBundle(options) {
1113
+ const contextMarkdown = [
1114
+ `# ${options.title}`,
1115
+ "",
1116
+ `Baseline root: ${options.index.rootDir}`,
1117
+ "",
1118
+ "## Task",
1119
+ "",
1120
+ options.task,
1121
+ "",
1122
+ "## Relationships-v2 Summary",
1123
+ "",
1124
+ JSON.stringify(options.index.relationshipsV2, null, 2),
1125
+ "",
1126
+ "## Selected SMRT Packages",
1127
+ "",
1128
+ ...options.packages.map(renderPackageContext),
1129
+ "",
1130
+ "## Selected SDK Packages",
1131
+ "",
1132
+ ...options.sdkPackages.map(renderPackageContext),
1133
+ "",
1134
+ options.extraContext ? `## Extra Context
1135
+
1136
+ ${options.extraContext}
1137
+ ` : ""
1138
+ ];
1139
+ return {
1140
+ title: options.title,
1141
+ instructions: "Use the supplied SMRT knowledge context as source material. Return concrete findings or architecture guidance with package names and source references. Do not assume model-provider access.",
1142
+ contextMarkdown: contextMarkdown.join("\n"),
1143
+ selectedPackages: options.packages.map((pkg) => pkg.name),
1144
+ selectedSdkPackages: options.sdkPackages.map((pkg) => pkg.name),
1145
+ sourceFiles: options.sourceFiles
1146
+ };
1147
+ }
1148
+ function renderPackageContext(pkg) {
1149
+ const lines = [
1150
+ `### ${pkg.name}`,
1151
+ "",
1152
+ `- version: ${pkg.version}`,
1153
+ `- kind: ${pkg.kind}`,
1154
+ `- directory: ${pkg.relativeDirectory}`,
1155
+ `- domain knowledge: ${pkg.domainKnowledgePath ?? "(manifest fallback)"}`,
1156
+ `- docs: ${pkg.docSource ?? "(none)"}`,
1157
+ `- relationship features: ${pkg.relationshipFeatures.join(", ") || "(none)"}`,
1158
+ `- SDK deps: ${pkg.sdkDependencies.join(", ") || "(none)"}`,
1159
+ `- exports: ${pkg.exportKeys.join(", ") || "(none)"}`,
1160
+ `- MCP tools: ${pkg.mcpTools.slice(0, 20).map((tool) => tool.name).join(", ") || "(none)"}`,
1161
+ `- objects: ${pkg.objects.slice(0, 20).map((object) => object.qualifiedName ?? object.className).join(", ")}`
1162
+ ];
1163
+ if (pkg.objects.length > 20) {
1164
+ lines.push(`- object count: ${pkg.objects.length}`);
1165
+ }
1166
+ if (pkg.agentDoc) {
1167
+ lines.push("", pkg.agentDoc.trim());
1168
+ }
1169
+ lines.push("");
1170
+ return lines.join("\n");
1171
+ }
1172
+ function getChangedFiles(rootDir, base) {
1173
+ if (base) {
1174
+ try {
1175
+ const output = execFileSync(
1176
+ "git",
1177
+ ["diff", "--name-only", `${base}...HEAD`],
1178
+ {
1179
+ cwd: rootDir,
1180
+ encoding: "utf8",
1181
+ stdio: ["ignore", "pipe", "ignore"]
1182
+ }
1183
+ );
1184
+ const files = output.split("\n").map((line) => line.trim()).filter(Boolean);
1185
+ if (files.length > 0) return files;
1186
+ } catch {
1187
+ }
1188
+ }
1189
+ try {
1190
+ const output = execFileSync("git", ["diff", "--name-only"], {
1191
+ cwd: rootDir,
1192
+ encoding: "utf8",
1193
+ stdio: ["ignore", "pipe", "ignore"]
1194
+ });
1195
+ const cachedOutput = execFileSync(
1196
+ "git",
1197
+ ["diff", "--cached", "--name-only"],
1198
+ {
1199
+ cwd: rootDir,
1200
+ encoding: "utf8",
1201
+ stdio: ["ignore", "pipe", "ignore"]
1202
+ }
1203
+ );
1204
+ return uniqueStrings(
1205
+ [output, cachedOutput].flatMap(
1206
+ (value) => value.split("\n").map((line) => line.trim()).filter(Boolean)
1207
+ )
1208
+ );
1209
+ } catch {
1210
+ return [];
1211
+ }
1212
+ }
1213
+ function dedupePackages(packages) {
1214
+ const byName = /* @__PURE__ */ new Map();
1215
+ for (const pkg of packages) {
1216
+ const current = byName.get(pkg.name);
1217
+ if (!current || current.kind === "sdk") {
1218
+ byName.set(pkg.name, pkg);
1219
+ }
1220
+ }
1221
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
1222
+ }
1223
+ function includesToken(text, token) {
1224
+ const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1225
+ return new RegExp(`\\b${escaped}\\b`, "i").test(text);
1226
+ }
1227
+ function readJson(path) {
1228
+ if (!existsSync(path)) return null;
1229
+ try {
1230
+ return JSON.parse(readFileSync(path, "utf8"));
1231
+ } catch {
1232
+ return null;
1233
+ }
1234
+ }
1235
+ function hashFile(path) {
1236
+ return createHash("sha256").update(readFileSync(path, "utf8")).digest("hex");
1237
+ }
1238
+ function hashJsonFile(path) {
1239
+ const content = readJson(path);
1240
+ const hashContent = content ? stableJson(normalizeJsonForHash(content)) : readFileSync(path, "utf8");
1241
+ return createHash("sha256").update(hashContent).digest("hex");
1242
+ }
1243
+ function objectRecord(value) {
1244
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
1245
+ }
1246
+ function camelToSnake(value) {
1247
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
1248
+ }
1249
+ function uniqueStrings(values) {
1250
+ return [...new Set(values)].sort();
1251
+ }
1252
+ function normalizeJsonForHash(value) {
1253
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1254
+ return value;
1255
+ }
1256
+ const normalized = { ...value };
1257
+ delete normalized.timestamp;
1258
+ return normalized;
1259
+ }
1260
+ function stableJson(value) {
1261
+ return JSON.stringify(sortJson(value), null, 2);
1262
+ }
1263
+ function sortJson(value) {
1264
+ if (Array.isArray(value)) return value.map(sortJson);
1265
+ if (value && typeof value === "object") {
1266
+ return Object.fromEntries(
1267
+ Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, sortJson(entry)])
1268
+ );
1269
+ }
1270
+ return value;
1271
+ }
1272
+
1273
+ export { buildKnowledgeIndex as a, buildArchitectureContext as b, buildReviewContext as c, checkKnowledgeFreshness as d, checkKnowledgeFreshnessFromIndex as e, diffKnowledgeIndex as f, renderKnowledgeIndexMarkdown as g, smrtReview as h, renderFreshnessResult as r, smrtArchitecture as s };
1274
+ //# sourceMappingURL=index-DF0HSB_8.js.map