@happyvertical/smrt-dev-mcp 0.40.62 → 0.40.64
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/AGENTS.md +35 -1
- package/README.md +42 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -500
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index.d.ts +36 -3
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/mcp-docs.d.ts +16 -0
- package/dist/knowledge/mcp-docs.d.ts.map +1 -0
- package/dist/{knowledge-DUUhTM5u.js → knowledge-A7-gIjW4.js} +799 -31
- package/dist/knowledge-A7-gIjW4.js.map +1 -0
- package/dist/knowledge.js +1 -1
- package/dist/tool-catalog.d.ts +4 -0
- package/dist/tool-catalog.d.ts.map +1 -0
- package/package.json +4 -4
- package/dist/knowledge-DUUhTM5u.js.map +0 -1
|
@@ -4,6 +4,680 @@ import { execFileSync } from "node:child_process";
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { MODULE_DOC_HASH_PREFIX, readAgentModuleDocs } from "@happyvertical/smrt-core/knowledge";
|
|
6
6
|
import { ManifestAdapter, OxcScanner } from "@happyvertical/smrt-scanner";
|
|
7
|
+
//#region src/tool-catalog.ts
|
|
8
|
+
var REVIEW_SKILL_NAME = "smrt-code-review";
|
|
9
|
+
var JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
10
|
+
function compareToolNames(left, right) {
|
|
11
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Stable success/error envelope for development tools. `data` preserves each
|
|
15
|
+
* tool's existing payload exactly; coverage/diagnostics are promoted only when
|
|
16
|
+
* the underlying result actually reports them.
|
|
17
|
+
*/
|
|
18
|
+
var DEV_MCP_OUTPUT_SCHEMA = {
|
|
19
|
+
$schema: JSON_SCHEMA_2020_12,
|
|
20
|
+
type: "object",
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
required: [
|
|
23
|
+
"ok",
|
|
24
|
+
"coverage",
|
|
25
|
+
"diagnostics",
|
|
26
|
+
"data"
|
|
27
|
+
],
|
|
28
|
+
properties: {
|
|
29
|
+
ok: { type: "boolean" },
|
|
30
|
+
coverage: {
|
|
31
|
+
type: ["object", "null"],
|
|
32
|
+
additionalProperties: true
|
|
33
|
+
},
|
|
34
|
+
diagnostics: {
|
|
35
|
+
type: "array",
|
|
36
|
+
items: {
|
|
37
|
+
type: "object",
|
|
38
|
+
additionalProperties: true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
data: {}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var TOOLS = [
|
|
45
|
+
{
|
|
46
|
+
name: "generate-smrt-class",
|
|
47
|
+
description: "Generate a complete SMRT class with @smrt() decorator",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
className: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Name of the class (PascalCase)"
|
|
54
|
+
},
|
|
55
|
+
properties: {
|
|
56
|
+
type: "array",
|
|
57
|
+
description: "Array of property definitions",
|
|
58
|
+
items: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
name: { type: "string" },
|
|
62
|
+
type: {
|
|
63
|
+
type: "string",
|
|
64
|
+
enum: [
|
|
65
|
+
"text",
|
|
66
|
+
"integer",
|
|
67
|
+
"decimal",
|
|
68
|
+
"boolean",
|
|
69
|
+
"datetime",
|
|
70
|
+
"json"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
required: { type: "boolean" },
|
|
74
|
+
nullable: { type: "boolean" },
|
|
75
|
+
description: { type: "string" },
|
|
76
|
+
defaultValue: { oneOf: [
|
|
77
|
+
{ type: "string" },
|
|
78
|
+
{ type: "number" },
|
|
79
|
+
{ type: "boolean" },
|
|
80
|
+
{ type: "object" },
|
|
81
|
+
{ type: "null" }
|
|
82
|
+
] }
|
|
83
|
+
},
|
|
84
|
+
required: ["name", "type"]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
baseClass: {
|
|
88
|
+
type: "string",
|
|
89
|
+
enum: ["SmrtObject", "SmrtCollection"],
|
|
90
|
+
default: "SmrtObject"
|
|
91
|
+
},
|
|
92
|
+
template: {
|
|
93
|
+
type: "string",
|
|
94
|
+
enum: [
|
|
95
|
+
"basic",
|
|
96
|
+
"global-catalog",
|
|
97
|
+
"optional-catalog",
|
|
98
|
+
"tenant-project-object",
|
|
99
|
+
"tenant-event-log-object",
|
|
100
|
+
"cross-package-reference"
|
|
101
|
+
],
|
|
102
|
+
default: "basic"
|
|
103
|
+
},
|
|
104
|
+
tableName: { type: "string" },
|
|
105
|
+
conflictColumns: {
|
|
106
|
+
type: "array",
|
|
107
|
+
items: { type: "string" }
|
|
108
|
+
},
|
|
109
|
+
tenantScoped: { oneOf: [{ type: "boolean" }, {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
mode: {
|
|
113
|
+
type: "string",
|
|
114
|
+
enum: ["required", "optional"]
|
|
115
|
+
},
|
|
116
|
+
field: { type: "string" },
|
|
117
|
+
autoFilter: { type: "boolean" },
|
|
118
|
+
autoPopulate: { type: "boolean" },
|
|
119
|
+
allowSuperAdminBypass: { type: "boolean" }
|
|
120
|
+
}
|
|
121
|
+
}] },
|
|
122
|
+
includeTenantIdField: { type: "boolean" },
|
|
123
|
+
relationships: {
|
|
124
|
+
type: "array",
|
|
125
|
+
items: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
name: { type: "string" },
|
|
129
|
+
type: {
|
|
130
|
+
type: "string",
|
|
131
|
+
enum: [
|
|
132
|
+
"foreignKey",
|
|
133
|
+
"crossPackageRef",
|
|
134
|
+
"oneToMany",
|
|
135
|
+
"manyToMany"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
related: { type: "string" },
|
|
139
|
+
required: { type: "boolean" },
|
|
140
|
+
nullable: { type: "boolean" },
|
|
141
|
+
description: { type: "string" },
|
|
142
|
+
validate: { type: "boolean" },
|
|
143
|
+
foreignKey: { type: "string" },
|
|
144
|
+
through: { type: "string" },
|
|
145
|
+
sourceKey: { type: "string" },
|
|
146
|
+
targetKey: { type: "string" }
|
|
147
|
+
},
|
|
148
|
+
required: [
|
|
149
|
+
"name",
|
|
150
|
+
"type",
|
|
151
|
+
"related"
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
includeCompanionSnippets: {
|
|
156
|
+
type: "boolean",
|
|
157
|
+
default: false
|
|
158
|
+
},
|
|
159
|
+
includeApiConfig: {
|
|
160
|
+
type: "boolean",
|
|
161
|
+
default: true
|
|
162
|
+
},
|
|
163
|
+
includeMcpConfig: {
|
|
164
|
+
type: "boolean",
|
|
165
|
+
default: true
|
|
166
|
+
},
|
|
167
|
+
includeCliConfig: {
|
|
168
|
+
type: "boolean",
|
|
169
|
+
default: true
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
required: ["className", "properties"]
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "introspect-project",
|
|
177
|
+
description: "Scan a directory for SMRT objects. Returns a compact summary by default; pass detail: \"full\" for field, schema, and method details.",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
directory: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "Project directory (default: cwd)"
|
|
184
|
+
},
|
|
185
|
+
manifestPath: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Optional manifest path. Defaults to .smrt/manifest.json, dist/manifest.json, then source scanning."
|
|
188
|
+
},
|
|
189
|
+
detail: {
|
|
190
|
+
type: "string",
|
|
191
|
+
enum: ["summary", "full"],
|
|
192
|
+
default: "summary",
|
|
193
|
+
description: "summary: one compact record per object. full: complete field/schema/method detail (large)."
|
|
194
|
+
},
|
|
195
|
+
maxChars: {
|
|
196
|
+
type: "number",
|
|
197
|
+
description: "Character budget for the `objects` payload. Objects past the budget are omitted and reported under `truncated`. Project metadata and diagnostics are always returned in full, so the serialized response is somewhat larger than this budget."
|
|
198
|
+
},
|
|
199
|
+
includeFields: {
|
|
200
|
+
type: "boolean",
|
|
201
|
+
description: "Include field details (detail: \"full\" only)"
|
|
202
|
+
},
|
|
203
|
+
includeRelationships: {
|
|
204
|
+
type: "boolean",
|
|
205
|
+
description: "Analyze relationships (detail: \"full\" only)"
|
|
206
|
+
},
|
|
207
|
+
includeMethods: {
|
|
208
|
+
type: "boolean",
|
|
209
|
+
description: "Include public method details (detail: \"full\" only)"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "review-smrt-project",
|
|
216
|
+
description: "Advisory ecosystem alignment review for downstream SMRT projects",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
directory: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Project directory (default: cwd)"
|
|
223
|
+
},
|
|
224
|
+
rootDir: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "Compatibility alias for directory"
|
|
227
|
+
},
|
|
228
|
+
includeSourceEvidence: {
|
|
229
|
+
type: "boolean",
|
|
230
|
+
description: "Include file and line evidence in findings",
|
|
231
|
+
default: true
|
|
232
|
+
},
|
|
233
|
+
maxFindings: {
|
|
234
|
+
type: "number",
|
|
235
|
+
description: "Optional maximum number of findings to return"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "reflect-knowledge",
|
|
242
|
+
description: "Report deterministic SMRT + HappyVertical SDK knowledge coverage and freshness",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: { rootDir: {
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "Project root directory (default: cwd)"
|
|
248
|
+
} }
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "reflect-domain-knowledge",
|
|
253
|
+
description: "Report domain-scoped SMRT knowledge artifacts, SDK packages, and freshness",
|
|
254
|
+
inputSchema: {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
rootDir: { type: "string" },
|
|
258
|
+
scope: {
|
|
259
|
+
type: "string",
|
|
260
|
+
enum: [
|
|
261
|
+
"project",
|
|
262
|
+
"local",
|
|
263
|
+
"package",
|
|
264
|
+
"sdk",
|
|
265
|
+
"installed"
|
|
266
|
+
],
|
|
267
|
+
default: "project"
|
|
268
|
+
},
|
|
269
|
+
package: { type: "string" }
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "check-knowledge-freshness",
|
|
275
|
+
description: "Run deterministic freshness checks for SMRT agent knowledge",
|
|
276
|
+
inputSchema: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
rootDir: { type: "string" },
|
|
280
|
+
changed: {
|
|
281
|
+
type: "boolean",
|
|
282
|
+
description: "Limit stale-pattern checks to changed files"
|
|
283
|
+
},
|
|
284
|
+
strict: {
|
|
285
|
+
type: "boolean",
|
|
286
|
+
description: "Treat stale-pattern findings as errors"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "check-domain-knowledge",
|
|
293
|
+
description: "Run deterministic freshness checks for domain knowledge artifacts",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {
|
|
297
|
+
rootDir: { type: "string" },
|
|
298
|
+
changed: { type: "boolean" },
|
|
299
|
+
strict: { type: "boolean" },
|
|
300
|
+
scope: {
|
|
301
|
+
type: "string",
|
|
302
|
+
enum: [
|
|
303
|
+
"project",
|
|
304
|
+
"local",
|
|
305
|
+
"package",
|
|
306
|
+
"sdk",
|
|
307
|
+
"installed"
|
|
308
|
+
],
|
|
309
|
+
default: "project"
|
|
310
|
+
},
|
|
311
|
+
package: { type: "string" }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: "build-review-context",
|
|
317
|
+
description: "Build model-ready SMRT review context from changed files and optional focus text",
|
|
318
|
+
inputSchema: {
|
|
319
|
+
type: "object",
|
|
320
|
+
properties: {
|
|
321
|
+
rootDir: { type: "string" },
|
|
322
|
+
changedFiles: {
|
|
323
|
+
type: "array",
|
|
324
|
+
items: { type: "string" }
|
|
325
|
+
},
|
|
326
|
+
focus: { type: "string" },
|
|
327
|
+
documentation: { type: "string" },
|
|
328
|
+
detail: {
|
|
329
|
+
type: "string",
|
|
330
|
+
enum: ["summary", "full"],
|
|
331
|
+
default: "summary",
|
|
332
|
+
description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "build-domain-review-context",
|
|
339
|
+
description: "Build domain-scoped model-ready SMRT review context and prompt bundle",
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
rootDir: { type: "string" },
|
|
344
|
+
changedFiles: {
|
|
345
|
+
type: "array",
|
|
346
|
+
items: { type: "string" }
|
|
347
|
+
},
|
|
348
|
+
focus: { type: "string" },
|
|
349
|
+
documentation: { type: "string" },
|
|
350
|
+
scope: {
|
|
351
|
+
type: "string",
|
|
352
|
+
enum: [
|
|
353
|
+
"project",
|
|
354
|
+
"local",
|
|
355
|
+
"package",
|
|
356
|
+
"sdk",
|
|
357
|
+
"installed"
|
|
358
|
+
],
|
|
359
|
+
default: "project"
|
|
360
|
+
},
|
|
361
|
+
package: { type: "string" },
|
|
362
|
+
detail: {
|
|
363
|
+
type: "string",
|
|
364
|
+
enum: ["summary", "full"],
|
|
365
|
+
default: "summary",
|
|
366
|
+
description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "smrt-review",
|
|
373
|
+
description: "Return deterministic review findings and/or a reusable model prompt bundle. For a formal downstream review, first call get-agent-skill with { \"name\": \"smrt-code-review\" } or load the smrt-code-review MCP prompt/resource.",
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: "object",
|
|
376
|
+
properties: {
|
|
377
|
+
rootDir: { type: "string" },
|
|
378
|
+
changedFiles: {
|
|
379
|
+
type: "array",
|
|
380
|
+
items: { type: "string" }
|
|
381
|
+
},
|
|
382
|
+
focus: { type: "string" },
|
|
383
|
+
documentation: { type: "string" },
|
|
384
|
+
mode: {
|
|
385
|
+
type: "string",
|
|
386
|
+
enum: [
|
|
387
|
+
"findings",
|
|
388
|
+
"prompt-bundle",
|
|
389
|
+
"both"
|
|
390
|
+
],
|
|
391
|
+
default: "both"
|
|
392
|
+
},
|
|
393
|
+
detail: {
|
|
394
|
+
type: "string",
|
|
395
|
+
enum: ["summary", "full"],
|
|
396
|
+
default: "summary",
|
|
397
|
+
description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: "build-architecture-context",
|
|
404
|
+
description: "Build model-ready SMRT architecture context from an idea or documentation",
|
|
405
|
+
inputSchema: {
|
|
406
|
+
type: "object",
|
|
407
|
+
properties: {
|
|
408
|
+
rootDir: { type: "string" },
|
|
409
|
+
idea: { type: "string" },
|
|
410
|
+
documentation: { type: "string" },
|
|
411
|
+
focus: { type: "string" },
|
|
412
|
+
detail: {
|
|
413
|
+
type: "string",
|
|
414
|
+
enum: ["summary", "full"],
|
|
415
|
+
default: "summary",
|
|
416
|
+
description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: "build-domain-architecture-context",
|
|
423
|
+
description: "Build domain-scoped model-ready SMRT architecture context and prompt bundle",
|
|
424
|
+
inputSchema: {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
rootDir: { type: "string" },
|
|
428
|
+
idea: { type: "string" },
|
|
429
|
+
documentation: { type: "string" },
|
|
430
|
+
focus: { type: "string" },
|
|
431
|
+
scope: {
|
|
432
|
+
type: "string",
|
|
433
|
+
enum: [
|
|
434
|
+
"project",
|
|
435
|
+
"local",
|
|
436
|
+
"package",
|
|
437
|
+
"sdk",
|
|
438
|
+
"installed"
|
|
439
|
+
],
|
|
440
|
+
default: "project"
|
|
441
|
+
},
|
|
442
|
+
package: { type: "string" },
|
|
443
|
+
detail: {
|
|
444
|
+
type: "string",
|
|
445
|
+
enum: ["summary", "full"],
|
|
446
|
+
default: "summary",
|
|
447
|
+
description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: "smrt-architecture",
|
|
454
|
+
description: "Suggest SMRT and HappyVertical SDK packages and return an architecture prompt bundle",
|
|
455
|
+
inputSchema: {
|
|
456
|
+
type: "object",
|
|
457
|
+
properties: {
|
|
458
|
+
rootDir: { type: "string" },
|
|
459
|
+
idea: { type: "string" },
|
|
460
|
+
documentation: { type: "string" },
|
|
461
|
+
focus: { type: "string" },
|
|
462
|
+
detail: {
|
|
463
|
+
type: "string",
|
|
464
|
+
enum: ["summary", "full"],
|
|
465
|
+
default: "summary",
|
|
466
|
+
description: "summary: authored package docs are listed by path to stay inside tool-result budgets. full: embed them."
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "list-agent-skills",
|
|
473
|
+
description: "List bundled harness-agnostic agent skills shipped with smrt-dev-mcp",
|
|
474
|
+
inputSchema: {
|
|
475
|
+
type: "object",
|
|
476
|
+
properties: {}
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "get-agent-skill",
|
|
481
|
+
description: "Return a bundled harness-agnostic agent skill as Markdown plus optional references",
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: {
|
|
485
|
+
name: {
|
|
486
|
+
type: "string",
|
|
487
|
+
enum: [REVIEW_SKILL_NAME],
|
|
488
|
+
description: "Bundled agent skill name"
|
|
489
|
+
},
|
|
490
|
+
includeReferences: {
|
|
491
|
+
type: "boolean",
|
|
492
|
+
default: true,
|
|
493
|
+
description: "Include referenced files with the skill bundle"
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
required: ["name"]
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
].map((tool) => ({
|
|
500
|
+
...tool,
|
|
501
|
+
inputSchema: {
|
|
502
|
+
...tool.inputSchema,
|
|
503
|
+
$schema: JSON_SCHEMA_2020_12
|
|
504
|
+
},
|
|
505
|
+
outputSchema: DEV_MCP_OUTPUT_SCHEMA
|
|
506
|
+
})).sort((left, right) => compareToolNames(left.name, right.name));
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/knowledge/mcp-docs.ts
|
|
509
|
+
var PACKAGE_NAME = "@happyvertical/smrt-dev-mcp";
|
|
510
|
+
/**
|
|
511
|
+
* Compare the authored development-MCP catalog to the actual exported tools.
|
|
512
|
+
* The parser intentionally checks only stable structural Markdown: tool names
|
|
513
|
+
* plus top-level input-property names, types, enums, and requiredness.
|
|
514
|
+
* Descriptive prose and prose-embedded defaults remain authored.
|
|
515
|
+
*/
|
|
516
|
+
function checkMcpToolDocumentation(rootDir, packageDir, tools) {
|
|
517
|
+
const readmePath = join(packageDir, "README.md");
|
|
518
|
+
const agentsPath = join(packageDir, "AGENTS.md");
|
|
519
|
+
const issues = [];
|
|
520
|
+
if (!existsSync(readmePath)) {
|
|
521
|
+
issues.push(issue(rootDir, readmePath, "mcp-readme-missing", "MCP package README.md is missing"));
|
|
522
|
+
return issues;
|
|
523
|
+
}
|
|
524
|
+
const expectedTools = [...tools].sort((left, right) => compareNames(left.name, right.name));
|
|
525
|
+
const { sections: readmeSections, names: documentedToolNames } = readToolSections(readFileSync(readmePath, "utf8"));
|
|
526
|
+
const expectedToolNames = expectedTools.map((tool) => tool.name);
|
|
527
|
+
const readmeCatalogDiff = setDifference(expectedToolNames, documentedToolNames);
|
|
528
|
+
if (readmeCatalogDiff) issues.push(issue(rootDir, readmePath, "mcp-readme-tool-drift", `README tool catalog differs from exported TOOLS (${readmeCatalogDiff})`));
|
|
529
|
+
for (const tool of expectedTools) {
|
|
530
|
+
const section = readmeSections.get(tool.name);
|
|
531
|
+
if (section === void 0) continue;
|
|
532
|
+
const { parameters: documentedParameters, names: documentedParameterNames } = readParameterTable(section);
|
|
533
|
+
const expectedProperties = Object.keys(tool.inputSchema.properties ?? {}).sort(compareNames);
|
|
534
|
+
const propertyDiff = setDifference(expectedProperties, documentedParameterNames);
|
|
535
|
+
const required = new Set(tool.inputSchema.required ?? []);
|
|
536
|
+
const requiredness = expectedProperties.filter((name) => documentedParameters.has(name) && documentedParameters.get(name)?.required !== required.has(name)).map((name) => `${name} expected ${required.has(name) ? "Yes" : "No"} but documented ${documentedParameters.get(name)?.required ? "Yes" : "No"}`);
|
|
537
|
+
const inputProperties = tool.inputSchema.properties ?? {};
|
|
538
|
+
const types = expectedProperties.filter((name) => documentedParameters.has(name)).flatMap((name) => {
|
|
539
|
+
const expected = formatSchemaType(inputProperties[name]);
|
|
540
|
+
const documented = documentedParameters.get(name)?.type;
|
|
541
|
+
return documented === expected ? [] : [`${name} expected ${expected} but documented ${documented}`];
|
|
542
|
+
});
|
|
543
|
+
const details = [
|
|
544
|
+
propertyDiff,
|
|
545
|
+
requiredness.length > 0 ? `requiredness: ${requiredness.join(", ")}` : void 0,
|
|
546
|
+
types.length > 0 ? `types: ${types.join(", ")}` : void 0
|
|
547
|
+
].filter((value) => Boolean(value));
|
|
548
|
+
if (details.length > 0) issues.push(issue(rootDir, readmePath, "mcp-readme-schema-drift", `README parameters for "${tool.name}" differ from its exported input schema (${details.join("; ")})`));
|
|
549
|
+
}
|
|
550
|
+
if (!existsSync(agentsPath)) return issues;
|
|
551
|
+
const agentsCatalogDiff = setDifference(expectedToolNames, readAgentToolNames(readFileSync(agentsPath, "utf8")).sort(compareNames));
|
|
552
|
+
if (agentsCatalogDiff) issues.push(issue(rootDir, agentsPath, "mcp-agents-tool-drift", `AGENTS.md tool catalog differs from exported TOOLS (${agentsCatalogDiff})`));
|
|
553
|
+
return issues;
|
|
554
|
+
}
|
|
555
|
+
function issue(rootDir, path, code, message) {
|
|
556
|
+
return {
|
|
557
|
+
severity: "error",
|
|
558
|
+
code,
|
|
559
|
+
message,
|
|
560
|
+
file: relative(rootDir, path),
|
|
561
|
+
packageName: PACKAGE_NAME
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function readToolSections(markdown) {
|
|
565
|
+
const availableTools = sectionAfterHeading(markdown, "Available Tools");
|
|
566
|
+
if (!availableTools) return {
|
|
567
|
+
sections: /* @__PURE__ */ new Map(),
|
|
568
|
+
names: []
|
|
569
|
+
};
|
|
570
|
+
const headings = [...availableTools.matchAll(/^### `([^`]+)`\s*$/gm)];
|
|
571
|
+
return {
|
|
572
|
+
sections: new Map(headings.map((heading, index) => {
|
|
573
|
+
const start = (heading.index ?? 0) + heading[0].length;
|
|
574
|
+
const end = headings[index + 1]?.index ?? availableTools.length;
|
|
575
|
+
return [heading[1], availableTools.slice(start, end)];
|
|
576
|
+
})),
|
|
577
|
+
names: headings.map((heading) => heading[1])
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function readParameterTable(section) {
|
|
581
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
582
|
+
const names = [];
|
|
583
|
+
for (const line of section.split("\n")) {
|
|
584
|
+
if (!line.trimStart().startsWith("|")) continue;
|
|
585
|
+
const cells = splitMarkdownRow(line);
|
|
586
|
+
const name = /^`([^`]+)`$/.exec(cells[0]?.trim() ?? "")?.[1];
|
|
587
|
+
const required = cells[2]?.trim();
|
|
588
|
+
if (!name || required !== "Yes" && required !== "No") continue;
|
|
589
|
+
names.push(name);
|
|
590
|
+
parameters.set(name, {
|
|
591
|
+
name,
|
|
592
|
+
required: required === "Yes",
|
|
593
|
+
type: normalizeDocumentedType(cells[1] ?? "")
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
parameters,
|
|
598
|
+
names
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function normalizeDocumentedType(value) {
|
|
602
|
+
const trimmed = value.trim();
|
|
603
|
+
return (trimmed.startsWith("`") && trimmed.endsWith("`") ? trimmed.slice(1, -1) : trimmed).replaceAll("\\|", "|").replace(/\s*\|\s*/g, " | ").trim();
|
|
604
|
+
}
|
|
605
|
+
function formatSchemaType(value) {
|
|
606
|
+
if (!isRecord(value)) return "unknown";
|
|
607
|
+
if (Array.isArray(value.oneOf)) return unique(value.oneOf.map(formatSchemaType)).join(" | ");
|
|
608
|
+
if (Array.isArray(value.enum) && value.enum.length > 0) return value.enum.map(formatEnumValue).join(" | ");
|
|
609
|
+
if (value.type === "array") {
|
|
610
|
+
const itemType = formatSchemaType(value.items);
|
|
611
|
+
return itemType.includes(" | ") ? `(${itemType})[]` : `${itemType}[]`;
|
|
612
|
+
}
|
|
613
|
+
if (Array.isArray(value.type)) return unique(value.type.map(String)).join(" | ");
|
|
614
|
+
return typeof value.type === "string" ? value.type : "unknown";
|
|
615
|
+
}
|
|
616
|
+
function formatEnumValue(value) {
|
|
617
|
+
if (typeof value === "string") return `'${value.replaceAll("'", "\\'")}'`;
|
|
618
|
+
return String(value);
|
|
619
|
+
}
|
|
620
|
+
function isRecord(value) {
|
|
621
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
622
|
+
}
|
|
623
|
+
function unique(values) {
|
|
624
|
+
return [...new Set(values)];
|
|
625
|
+
}
|
|
626
|
+
function splitMarkdownRow(row) {
|
|
627
|
+
const content = row.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
628
|
+
const cells = [];
|
|
629
|
+
let cell = "";
|
|
630
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
631
|
+
const character = content[index];
|
|
632
|
+
if (character === "|" && content[index - 1] !== "\\") {
|
|
633
|
+
cells.push(cell);
|
|
634
|
+
cell = "";
|
|
635
|
+
} else cell += character;
|
|
636
|
+
}
|
|
637
|
+
cells.push(cell);
|
|
638
|
+
return cells;
|
|
639
|
+
}
|
|
640
|
+
function readAgentToolNames(markdown) {
|
|
641
|
+
const toolsSection = sectionAfterHeading(markdown, "Tools");
|
|
642
|
+
if (!toolsSection) return [];
|
|
643
|
+
return [...toolsSection.matchAll(/^\|\s*`([^`]+)`\s*\|/gm)].map((match) => match[1]);
|
|
644
|
+
}
|
|
645
|
+
function sectionAfterHeading(markdown, heading) {
|
|
646
|
+
const marker = new RegExp(`^## ${heading}\\s*$`, "m").exec(markdown);
|
|
647
|
+
if (!marker) return "";
|
|
648
|
+
const start = (marker.index ?? 0) + marker[0].length;
|
|
649
|
+
const remainder = markdown.slice(start);
|
|
650
|
+
const nextHeading = /^##\s+/m.exec(remainder);
|
|
651
|
+
return remainder.slice(0, nextHeading?.index ?? remainder.length);
|
|
652
|
+
}
|
|
653
|
+
function setDifference(expected, actual) {
|
|
654
|
+
const expectedSet = new Set(expected);
|
|
655
|
+
const actualSet = new Set(actual);
|
|
656
|
+
const missing = expected.filter((value) => !actualSet.has(value)).sort(compareNames);
|
|
657
|
+
const extra = unique(actual.filter((value) => !expectedSet.has(value))).sort(compareNames);
|
|
658
|
+
const expectedDuplicates = duplicateNames(expected);
|
|
659
|
+
const actualDuplicates = duplicateNames(actual);
|
|
660
|
+
const parts = [
|
|
661
|
+
missing.length > 0 ? `missing: ${missing.join(", ")}` : void 0,
|
|
662
|
+
extra.length > 0 ? `extra: ${extra.join(", ")}` : void 0,
|
|
663
|
+
expectedDuplicates.length > 0 ? `exported duplicates: ${expectedDuplicates.join(", ")}` : void 0,
|
|
664
|
+
actualDuplicates.length > 0 ? `duplicates: ${actualDuplicates.join(", ")}` : void 0
|
|
665
|
+
].filter((value) => Boolean(value));
|
|
666
|
+
return parts.length > 0 ? parts.join("; ") : void 0;
|
|
667
|
+
}
|
|
668
|
+
function duplicateNames(values) {
|
|
669
|
+
const seen = /* @__PURE__ */ new Set();
|
|
670
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
671
|
+
for (const value of values) {
|
|
672
|
+
if (seen.has(value)) duplicates.add(value);
|
|
673
|
+
seen.add(value);
|
|
674
|
+
}
|
|
675
|
+
return [...duplicates].sort(compareNames);
|
|
676
|
+
}
|
|
677
|
+
function compareNames(left, right) {
|
|
678
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
679
|
+
}
|
|
680
|
+
//#endregion
|
|
7
681
|
//#region src/knowledge/index.ts
|
|
8
682
|
var SDK_PACKAGE_NAMES = /* @__PURE__ */ new Set([
|
|
9
683
|
"@happyvertical/ai",
|
|
@@ -117,7 +791,7 @@ async function buildKnowledgeIndex(options = {}) {
|
|
|
117
791
|
packages.push(readKnowledgePackage(rootDir, dir, includeDocs));
|
|
118
792
|
}
|
|
119
793
|
await applyScannerFallbacks(packages, packageDirs.filter((dir) => resolve(dir) !== resolve(rootDir)).map((dir) => `${relative(rootDir, dir).replaceAll("\\", "/")}/**`));
|
|
120
|
-
packages.push(...
|
|
794
|
+
packages.push(...discoverInstalledPackages(rootDir, packageDirs, includeDocs));
|
|
121
795
|
const uniquePackages = dedupePackages(packages);
|
|
122
796
|
const scopedPackages = filterKnowledgePackages(uniquePackages, options);
|
|
123
797
|
const smrtPackages = scopedPackages.filter((pkg) => pkg.kind === "smrt");
|
|
@@ -130,24 +804,26 @@ async function buildKnowledgeIndex(options = {}) {
|
|
|
130
804
|
packages: uniquePackages
|
|
131
805
|
});
|
|
132
806
|
return {
|
|
133
|
-
schemaVersion:
|
|
807
|
+
schemaVersion: 3,
|
|
134
808
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
135
809
|
rootDir,
|
|
136
810
|
packages: scopedPackages,
|
|
137
811
|
smrtPackages,
|
|
138
812
|
sdkPackages,
|
|
813
|
+
installedPackages: scopedPackages.filter((pkg) => pkg.isInstalledDependency),
|
|
139
814
|
relationshipsV2: summarizeRelationshipsV2(scopedPackages),
|
|
140
815
|
coverage,
|
|
141
816
|
diagnostics: buildIndexDiagnostics(rootDir, uniquePackages, coverage, discoveryDiagnostics)
|
|
142
817
|
};
|
|
143
818
|
}
|
|
144
819
|
function buildCoverage(options) {
|
|
820
|
+
const authored = options.packages.filter((pkg) => !pkg.isInstalledDependency);
|
|
145
821
|
return {
|
|
146
822
|
workspaceGlobs: options.globs,
|
|
147
823
|
workspaceGlobSource: options.globSource,
|
|
148
824
|
packageDirs: options.packageDirs.map((dir) => relative(options.rootDir, dir) || "."),
|
|
149
|
-
packagesWithObjects:
|
|
150
|
-
packagesWithoutObjects:
|
|
825
|
+
packagesWithObjects: authored.filter((pkg) => pkg.objects.length > 0).map((pkg) => `${pkg.name} (${pkg.objects.length}, ${pkg.objectSource})`),
|
|
826
|
+
packagesWithoutObjects: authored.filter((pkg) => pkg.objects.length === 0).map((pkg) => ({
|
|
151
827
|
name: pkg.name,
|
|
152
828
|
reason: pkg.objectSourceReason ?? pkg.objectSource,
|
|
153
829
|
checkedPaths: pkg.checkedObjectPaths,
|
|
@@ -171,7 +847,25 @@ function remedyForReason(pkg) {
|
|
|
171
847
|
*/
|
|
172
848
|
function buildIndexDiagnostics(rootDir, packages, coverage, discoveryDiagnostics = []) {
|
|
173
849
|
const diagnostics = [...discoveryDiagnostics];
|
|
174
|
-
|
|
850
|
+
const authoredPackages = packages.filter((pkg) => !pkg.isInstalledDependency);
|
|
851
|
+
const authoredObjects = authoredPackages.reduce((total, pkg) => total + pkg.objects.length, 0);
|
|
852
|
+
const installedObjects = packages.reduce((total, pkg) => total + (pkg.isInstalledDependency ? pkg.objects.length : 0), 0);
|
|
853
|
+
if (authoredObjects === 0 && installedObjects > 0) diagnostics.push({
|
|
854
|
+
severity: "warning",
|
|
855
|
+
code: "no-authored-smrt-objects",
|
|
856
|
+
message: [
|
|
857
|
+
`No SMRT objects were discovered in the project's own packages under ${rootDir},`,
|
|
858
|
+
`though ${installedObjects} were read from installed dependencies.`,
|
|
859
|
+
"Expected for an application that only consumes the framework;",
|
|
860
|
+
"a discovery failure if this workspace is supposed to declare @smrt() classes."
|
|
861
|
+
].join(" "),
|
|
862
|
+
remedy: [
|
|
863
|
+
"If this workspace authors SMRT objects, confirm rootDir is the workspace root,",
|
|
864
|
+
"confirm the workspace globs cover the directories that hold @smrt() classes,",
|
|
865
|
+
"and run `pnpm build` in the owning package to emit .smrt/manifest.json."
|
|
866
|
+
].join(" ")
|
|
867
|
+
});
|
|
868
|
+
if (authoredObjects === 0 && installedObjects === 0) diagnostics.push({
|
|
175
869
|
severity: "error",
|
|
176
870
|
code: "no-smrt-objects-discovered",
|
|
177
871
|
message: [
|
|
@@ -180,7 +874,7 @@ function buildIndexDiagnostics(rootDir, packages, coverage, discoveryDiagnostics
|
|
|
180
874
|
`Package directories checked (${coverage.packageDirs.length}): ${coverage.packageDirs.join(", ") || "(none)"}.`,
|
|
181
875
|
"Treat this as a discovery failure, not as evidence that the project has no SMRT model."
|
|
182
876
|
].join(" "),
|
|
183
|
-
checkedPaths: [...new Set(
|
|
877
|
+
checkedPaths: [...new Set(authoredPackages.flatMap((pkg) => pkg.checkedObjectPaths))],
|
|
184
878
|
remedy: [
|
|
185
879
|
"Confirm rootDir is the workspace root;",
|
|
186
880
|
"confirm pnpm-workspace.yaml `packages:` covers the directories that hold @smrt() classes (for example apps/*);",
|
|
@@ -250,10 +944,11 @@ async function checkKnowledgeFreshness(options = {}) {
|
|
|
250
944
|
async function checkKnowledgeFreshnessFromIndex(index, options = {}) {
|
|
251
945
|
const issues = [];
|
|
252
946
|
const changedFiles = options.changed ? getChangedFiles(index.rootDir) : void 0;
|
|
253
|
-
const
|
|
254
|
-
const
|
|
947
|
+
const authoredPackages = index.packages.filter((item) => !item.isInstalledDependency);
|
|
948
|
+
const hasMemberPackages = authoredPackages.some((item) => item.kind !== "sdk" && !item.isWorkspaceRoot);
|
|
949
|
+
const memberDirectories = authoredPackages.filter((item) => item.kind !== "sdk" && !item.isWorkspaceRoot && item.relativeDirectory).map((item) => item.relativeDirectory);
|
|
255
950
|
const isNestedMember = (pkg) => Boolean(pkg.relativeDirectory) && memberDirectories.some((directory) => directory !== pkg.relativeDirectory && pkg.relativeDirectory.startsWith(`${directory}/`));
|
|
256
|
-
for (const pkg of
|
|
951
|
+
for (const pkg of authoredPackages.filter((item) => item.kind !== "sdk" && !(item.isWorkspaceRoot && hasMemberPackages))) {
|
|
257
952
|
const packageJsonPath = join(pkg.directory, "package.json");
|
|
258
953
|
const nested = isNestedMember(pkg);
|
|
259
954
|
if (!pkg.hasAgentsMd && !nested) issues.push({
|
|
@@ -310,7 +1005,9 @@ async function checkKnowledgeFreshnessFromIndex(index, options = {}) {
|
|
|
310
1005
|
packageName: pkg.name
|
|
311
1006
|
});
|
|
312
1007
|
}
|
|
313
|
-
for (const pkg of
|
|
1008
|
+
for (const pkg of authoredPackages) issues.push(...checkDomainKnowledgeArtifact(index.rootDir, pkg));
|
|
1009
|
+
const devMcpPackage = authoredPackages.find((pkg) => pkg.name === "@happyvertical/smrt-dev-mcp");
|
|
1010
|
+
if (devMcpPackage) issues.push(...checkMcpToolDocumentation(index.rootDir, devMcpPackage.directory, TOOLS));
|
|
314
1011
|
issues.push(...findStalePatternIssues(index.rootDir, changedFiles));
|
|
315
1012
|
const effectiveIssues = issues.map((issue) => issue.code.startsWith("stale-") ? {
|
|
316
1013
|
...issue,
|
|
@@ -517,6 +1214,7 @@ function renderKnowledgeIndexMarkdown(index) {
|
|
|
517
1214
|
"",
|
|
518
1215
|
`- SMRT packages: ${index.smrtPackages.length}`,
|
|
519
1216
|
`- SDK packages: ${index.sdkPackages.length}`,
|
|
1217
|
+
`- Installed dependencies: ${index.installedPackages.length}`,
|
|
520
1218
|
`- foreignKey fields: ${index.relationshipsV2.foreignKeyFields}`,
|
|
521
1219
|
`- crossPackageRef fields: ${index.relationshipsV2.crossPackageRefFields}`,
|
|
522
1220
|
`- junction collections: ${index.relationshipsV2.junctionCollections}`,
|
|
@@ -540,6 +1238,10 @@ function renderKnowledgeIndexMarkdown(index) {
|
|
|
540
1238
|
lines.push(`- MCP tools: ${pkg.mcpTools.length}`);
|
|
541
1239
|
lines.push(`- domain knowledge: ${pkg.domainKnowledgePath ?? "(manifest fallback)"}`);
|
|
542
1240
|
lines.push(`- docs: ${pkg.docSource ?? "(none)"}${pkg.hasClaudeShim ? " + CLAUDE.md shim" : ""}`);
|
|
1241
|
+
if (pkg.isInstalledDependency) {
|
|
1242
|
+
lines.push("- source: installed dependency");
|
|
1243
|
+
lines.push(`- AGENTS.md sha256: ${pkg.agentDocSha256 ?? "(none)"}`);
|
|
1244
|
+
}
|
|
543
1245
|
if (pkg.moduleDocs.length > 0) lines.push(`- module docs: ${pkg.moduleDocs.map((doc) => doc.path).join(", ")}`);
|
|
544
1246
|
if (pkg.relationshipFeatures.length > 0) lines.push(`- relationships-v2: ${pkg.relationshipFeatures.join(", ")}`);
|
|
545
1247
|
lines.push("");
|
|
@@ -817,34 +1519,85 @@ function discoverProjectPackageDirs(rootDir) {
|
|
|
817
1519
|
diagnostics: expansion.diagnostics
|
|
818
1520
|
};
|
|
819
1521
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1522
|
+
/**
|
|
1523
|
+
* Installed `@happyvertical/*` packages, resolved once each (#2275).
|
|
1524
|
+
*
|
|
1525
|
+
* Reads the `@happyvertical` scope directory of the project and of every
|
|
1526
|
+
* workspace package — one `readdir` per scope directory, resolving each entry's
|
|
1527
|
+
* real path — instead of descending through `node_modules`. Under pnpm those
|
|
1528
|
+
* entries are symlinks into a store whose entries link back out to each other,
|
|
1529
|
+
* so a descent revisits the same package once per path that reaches it. The
|
|
1530
|
+
* realpath is the dedupe key only: the same store entry reached from three
|
|
1531
|
+
* scope directories is read once, but it is read through its `node_modules`
|
|
1532
|
+
* path, because a realpath is the wrong thing to report. On a host where any
|
|
1533
|
+
* ancestor of the root is a symlink (`/var` on macOS, a symlinked worktree),
|
|
1534
|
+
* `relative(rootDir, realpath)` escapes the root entirely.
|
|
1535
|
+
*
|
|
1536
|
+
* Workspace packages linked into a sibling's `node_modules` are skipped here.
|
|
1537
|
+
* They are authored source that happens to be reachable through a link, and
|
|
1538
|
+
* calling them installed would exempt them from the freshness gate.
|
|
1539
|
+
*
|
|
1540
|
+
* Both SMRT and SDK packages are returned. A consumer app authors neither, and
|
|
1541
|
+
* its `@happyvertical/smrt-*` dependencies are exactly the surface it needs to
|
|
1542
|
+
* audit; excluding them is why the index could see nothing in a consumer app.
|
|
1543
|
+
*/
|
|
1544
|
+
function discoverInstalledPackages(rootDir, packageDirs, includeDocs) {
|
|
1545
|
+
const scopeDirs = [join(rootDir, "node_modules", "@happyvertical"), ...packageDirs.map((dir) => join(dir, "node_modules", "@happyvertical"))];
|
|
1546
|
+
const workspaceRealPaths = new Set([rootDir, ...packageDirs].flatMap((dir) => {
|
|
1547
|
+
try {
|
|
1548
|
+
return [realpathSync(dir)];
|
|
1549
|
+
} catch {
|
|
1550
|
+
return [];
|
|
1551
|
+
}
|
|
1552
|
+
}));
|
|
1553
|
+
const byRealPath = /* @__PURE__ */ new Map();
|
|
1554
|
+
for (const scopeDir of new Set(scopeDirs)) {
|
|
1555
|
+
if (!existsSync(scopeDir)) continue;
|
|
1556
|
+
let entries;
|
|
823
1557
|
try {
|
|
824
|
-
|
|
1558
|
+
entries = readdirSync(scopeDir, { withFileTypes: true });
|
|
825
1559
|
} catch {
|
|
826
|
-
|
|
1560
|
+
continue;
|
|
827
1561
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1562
|
+
for (const entry of entries) {
|
|
1563
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
1564
|
+
const entryPath = join(scopeDir, entry.name);
|
|
1565
|
+
let resolved;
|
|
1566
|
+
try {
|
|
1567
|
+
resolved = realpathSync(entryPath);
|
|
1568
|
+
} catch {
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
if (workspaceRealPaths.has(resolved)) continue;
|
|
1572
|
+
if (byRealPath.has(resolved)) continue;
|
|
1573
|
+
byRealPath.set(resolved, entryPath);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
const packages = [];
|
|
1577
|
+
for (const directory of byRealPath.values()) {
|
|
1578
|
+
const name = objectRecord(readJson(join(directory, "package.json"))).name;
|
|
1579
|
+
if (typeof name !== "string") continue;
|
|
1580
|
+
if (!SDK_PACKAGE_NAMES.has(name) && !name.startsWith("@happyvertical/smrt-")) continue;
|
|
1581
|
+
packages.push(readKnowledgePackage(rootDir, directory, includeDocs, { installed: true }));
|
|
1582
|
+
}
|
|
1583
|
+
return packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
832
1584
|
}
|
|
833
1585
|
function filterKnowledgePackages(packages, options) {
|
|
834
1586
|
const packageQuery = options.packageName ?? options.package;
|
|
835
1587
|
return packages.filter((pkg) => {
|
|
836
1588
|
if (packageQuery && !packageMatches(pkg, packageQuery.toLowerCase())) return false;
|
|
837
1589
|
switch (options.scope) {
|
|
838
|
-
case "local": return pkg.kind !== "sdk" && !pkg.relativeDirectory.includes("node_modules");
|
|
839
|
-
case "package": return pkg.kind !== "sdk";
|
|
1590
|
+
case "local": return pkg.kind !== "sdk" && !pkg.isInstalledDependency && !pkg.relativeDirectory.includes("node_modules");
|
|
1591
|
+
case "package": return pkg.kind !== "sdk" && !pkg.isInstalledDependency;
|
|
840
1592
|
case "sdk": return pkg.kind === "sdk";
|
|
1593
|
+
case "installed": return pkg.isInstalledDependency;
|
|
841
1594
|
case "project":
|
|
842
1595
|
case void 0: return true;
|
|
843
1596
|
default: return true;
|
|
844
1597
|
}
|
|
845
1598
|
});
|
|
846
1599
|
}
|
|
847
|
-
function readKnowledgePackage(rootDir, directory, includeDocs) {
|
|
1600
|
+
function readKnowledgePackage(rootDir, directory, includeDocs, options = {}) {
|
|
848
1601
|
const packageJson = objectRecord(readJson(join(directory, "package.json")));
|
|
849
1602
|
const dependencies = stringRecord(packageJson.dependencies);
|
|
850
1603
|
const devDependencies = stringRecord(packageJson.devDependencies);
|
|
@@ -894,6 +1647,7 @@ function readKnowledgePackage(rootDir, directory, includeDocs) {
|
|
|
894
1647
|
hasClaudeShim: claudeContent.trim() === "@AGENTS.md",
|
|
895
1648
|
docSource,
|
|
896
1649
|
agentDoc: includeDocs ? domainKnowledge?.content.agentDoc || (hasAgentsMd ? agentsContent : fallbackClaudeDoc || void 0) : void 0,
|
|
1650
|
+
agentDocSha256: hasAgentsMd ? createHash("sha256").update(agentsContent).digest("hex") : void 0,
|
|
897
1651
|
moduleDocs: includeDocs ? domainKnowledge?.content.moduleDocs ?? readAgentModuleDocs(directory, agentsContent || void 0) : [],
|
|
898
1652
|
hasDomainKnowledge: Boolean(domainKnowledge),
|
|
899
1653
|
domainKnowledgePath: domainKnowledge?.path ? relative(rootDir, domainKnowledge.path) : void 0,
|
|
@@ -906,6 +1660,7 @@ function readKnowledgePackage(rootDir, directory, includeDocs) {
|
|
|
906
1660
|
relationshipFeatures: relationshipFeatures(objects),
|
|
907
1661
|
isWorkspaceRoot: resolve(directory) === resolve(rootDir),
|
|
908
1662
|
isPrivate: packageJson.private === true,
|
|
1663
|
+
isInstalledDependency: options.installed === true,
|
|
909
1664
|
objectSource: resolvedObjects.source,
|
|
910
1665
|
objectSourceReason: resolvedObjects.reason,
|
|
911
1666
|
checkedObjectPaths: resolvedObjects.checkedPaths
|
|
@@ -1547,7 +2302,7 @@ function selectPackages(index, options) {
|
|
|
1547
2302
|
const selected = /* @__PURE__ */ new Set();
|
|
1548
2303
|
const packageName = options.packageName?.toLowerCase();
|
|
1549
2304
|
if (packageName) {
|
|
1550
|
-
for (const pkg of domainPackages(index)) if (packageMatches(pkg, packageName)) selected.add(pkg);
|
|
2305
|
+
for (const pkg of domainPackages(index)) if (scopeAllowsPackage(pkg, options.scope) && packageMatches(pkg, packageName)) selected.add(pkg);
|
|
1551
2306
|
}
|
|
1552
2307
|
for (const pkg of selectPackagesForFiles(index, options.changedFiles ?? [])) if (scopeAllowsPackage(pkg, options.scope)) selected.add(pkg);
|
|
1553
2308
|
const text = (options.text ?? "").toLowerCase();
|
|
@@ -1565,7 +2320,7 @@ function selectPackages(index, options) {
|
|
|
1565
2320
|
"@happyvertical/smrt-dev-mcp"
|
|
1566
2321
|
]) {
|
|
1567
2322
|
const pkg = index.smrtPackages.find((item) => item.name === name);
|
|
1568
|
-
if (pkg) selected.add(pkg);
|
|
2323
|
+
if (pkg && scopeAllowsPackage(pkg, options.scope)) selected.add(pkg);
|
|
1569
2324
|
}
|
|
1570
2325
|
if (selected.size === 0) {
|
|
1571
2326
|
const contributing = domainPackages(index).filter((pkg) => pkg.objects.length > 0 && scopeAllowsPackage(pkg, options.scope) && !pkg.relativeDirectory.includes("node_modules")).sort((a, b) => b.objects.length - a.objects.length).slice(0, MAX_FALLBACK_PACKAGES);
|
|
@@ -1580,28 +2335,41 @@ function selectSdkPackages(index, selectedPackages, texts, options = {}) {
|
|
|
1580
2335
|
const text = texts.filter(Boolean).join("\n").toLowerCase();
|
|
1581
2336
|
const packageName = options.packageName?.toLowerCase();
|
|
1582
2337
|
for (const sdk of index.sdkPackages) {
|
|
2338
|
+
const selectedDependency = sdkNames.has(sdk.name);
|
|
2339
|
+
if (!scopeAllowsSdkPackage(sdk, options.scope, selectedDependency)) continue;
|
|
1583
2340
|
const shortName = sdk.name.replace("@happyvertical/", "");
|
|
1584
|
-
if (options.scope === "sdk" ||
|
|
2341
|
+
if (options.scope === "sdk" || selectedDependency || packageName && packageMatches(sdk, packageName) || text.includes(sdk.name.toLowerCase()) || includesToken(text, shortName)) selected.add(sdk);
|
|
1585
2342
|
}
|
|
1586
|
-
if (selected.size === 0) for (const name of [
|
|
2343
|
+
if (selected.size === 0 && !(packageName && selectedPackages.length === 0)) for (const name of [
|
|
1587
2344
|
"@happyvertical/ai",
|
|
1588
2345
|
"@happyvertical/sql",
|
|
1589
2346
|
"@happyvertical/files",
|
|
1590
2347
|
"@happyvertical/utils"
|
|
1591
2348
|
]) {
|
|
1592
|
-
const sdk = index.sdkPackages.find((item) => item.name === name);
|
|
2349
|
+
const sdk = index.sdkPackages.find((item) => item.name === name && scopeAllowsSdkPackage(item, options.scope, false));
|
|
1593
2350
|
if (sdk) selected.add(sdk);
|
|
1594
2351
|
}
|
|
1595
2352
|
return [...selected].sort((a, b) => a.name.localeCompare(b.name));
|
|
1596
2353
|
}
|
|
2354
|
+
function scopeAllowsSdkPackage(pkg, scope, selectedDependency) {
|
|
2355
|
+
switch (scope) {
|
|
2356
|
+
case "installed": return pkg.isInstalledDependency;
|
|
2357
|
+
case "local":
|
|
2358
|
+
case "package": return !pkg.isInstalledDependency || selectedDependency;
|
|
2359
|
+
case "sdk":
|
|
2360
|
+
case "project":
|
|
2361
|
+
case void 0: return true;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
1597
2364
|
function domainPackages(index) {
|
|
1598
2365
|
return index.packages.filter((pkg) => pkg.kind !== "sdk");
|
|
1599
2366
|
}
|
|
1600
2367
|
function scopeAllowsPackage(pkg, scope) {
|
|
1601
2368
|
switch (scope) {
|
|
1602
2369
|
case "sdk": return false;
|
|
1603
|
-
case "
|
|
1604
|
-
case "
|
|
2370
|
+
case "installed": return pkg.isInstalledDependency;
|
|
2371
|
+
case "local": return !pkg.isInstalledDependency && !pkg.relativeDirectory.includes("node_modules");
|
|
2372
|
+
case "package": return !pkg.isInstalledDependency;
|
|
1605
2373
|
case "project":
|
|
1606
2374
|
case void 0: return true;
|
|
1607
2375
|
}
|
|
@@ -1834,6 +2602,6 @@ function sortJson(value) {
|
|
|
1834
2602
|
return value;
|
|
1835
2603
|
}
|
|
1836
2604
|
//#endregion
|
|
1837
|
-
export { checkKnowledgeFreshnessFromIndex as a, renderFreshnessResult as c, smrtReview as d, checkKnowledgeFreshness as i, renderKnowledgeIndexMarkdown as l, buildKnowledgeIndex as n, diffKnowledgeIndex as o, buildReviewContext as r, packageDocPaths as s, buildArchitectureContext as t, smrtArchitecture as u };
|
|
2605
|
+
export { checkKnowledgeFreshnessFromIndex as a, renderFreshnessResult as c, smrtReview as d, REVIEW_SKILL_NAME as f, checkKnowledgeFreshness as i, renderKnowledgeIndexMarkdown as l, buildKnowledgeIndex as n, diffKnowledgeIndex as o, TOOLS as p, buildReviewContext as r, packageDocPaths as s, buildArchitectureContext as t, smrtArchitecture as u };
|
|
1838
2606
|
|
|
1839
|
-
//# sourceMappingURL=knowledge-
|
|
2607
|
+
//# sourceMappingURL=knowledge-A7-gIjW4.js.map
|