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