renma 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -1
- package/README.md +2 -0
- package/dist/catalog.js +14 -0
- package/dist/catalog.js.map +1 -1
- package/dist/context-lens.js +23 -0
- package/dist/context-lens.js.map +1 -1
- package/dist/diagnostics-v2.d.ts +9 -0
- package/dist/diagnostics-v2.d.ts.map +1 -0
- package/dist/diagnostics-v2.js +763 -0
- package/dist/diagnostics-v2.js.map +1 -0
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +70 -0
- package/dist/rules.js.map +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +18 -5
- package/dist/scanner.js.map +1 -1
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
import { CONTEXT_LENS_DIAGNOSTIC_CODES } from "./context-lens.js";
|
|
2
|
+
import { DIAGNOSTIC_IDS } from "./diagnostic-ids.js";
|
|
3
|
+
const RESERVED_DETAIL_KEYS = new Set(["diagnosticId", "source"]);
|
|
4
|
+
/** Convert legacy diagnostics and findings into the LLM-actionable v2 shape. */
|
|
5
|
+
export function createDiagnosticsV2(input) {
|
|
6
|
+
return [
|
|
7
|
+
...input.findings.map(findingToDiagnosticV2),
|
|
8
|
+
...input.diagnostics.map(rawDiagnosticToDiagnosticV2),
|
|
9
|
+
].sort(compareDiagnosticsV2);
|
|
10
|
+
}
|
|
11
|
+
/** Build deterministic review bundles from LLM-actionable diagnostics. */
|
|
12
|
+
export function createReviewBundles(diagnostics) {
|
|
13
|
+
const groups = new Map();
|
|
14
|
+
for (const diagnostic of diagnostics) {
|
|
15
|
+
const seed = reviewBundleSeed(diagnostic);
|
|
16
|
+
groups.set(seed.key, {
|
|
17
|
+
seed,
|
|
18
|
+
diagnostics: [...(groups.get(seed.key)?.diagnostics ?? []), diagnostic],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return [...groups.values()]
|
|
22
|
+
.map(({ seed, diagnostics: groupedDiagnostics }) => reviewBundle(seed, groupedDiagnostics))
|
|
23
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
24
|
+
}
|
|
25
|
+
function findingToDiagnosticV2(finding, index) {
|
|
26
|
+
const code = finding.id;
|
|
27
|
+
const diagnosticId = diagnosticIdFor(code, locationFromEvidence(finding.evidence), index);
|
|
28
|
+
return compactDiagnostic({
|
|
29
|
+
version: 2,
|
|
30
|
+
code,
|
|
31
|
+
severity: severityFromFinding(finding),
|
|
32
|
+
message: finding.title,
|
|
33
|
+
location: locationFromEvidence(finding.evidence),
|
|
34
|
+
repairConstraints: repairConstraintsForFinding(finding),
|
|
35
|
+
verificationSteps: verificationStepsForFinding(finding),
|
|
36
|
+
llmHint: llmHintForFinding(finding),
|
|
37
|
+
details: diagnosticDetails("finding", diagnosticId, finding.details, {
|
|
38
|
+
findingSeverity: finding.severity,
|
|
39
|
+
category: finding.category,
|
|
40
|
+
confidence: finding.confidence,
|
|
41
|
+
riskClass: finding.riskClass,
|
|
42
|
+
remediation: finding.remediation,
|
|
43
|
+
whyItMatters: finding.whyItMatters,
|
|
44
|
+
legacyConstraints: finding.constraints,
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function rawDiagnosticToDiagnosticV2(diagnostic, index) {
|
|
49
|
+
const code = diagnostic.code ?? inferredDiagnosticCode(diagnostic);
|
|
50
|
+
const location = locationFromDiagnostic(diagnostic);
|
|
51
|
+
const diagnosticId = diagnosticIdFor(code, location, index);
|
|
52
|
+
return compactDiagnostic({
|
|
53
|
+
version: 2,
|
|
54
|
+
code,
|
|
55
|
+
severity: diagnostic.severity,
|
|
56
|
+
message: diagnostic.message,
|
|
57
|
+
location,
|
|
58
|
+
repairConstraints: repairConstraintsForDiagnostic(code, diagnostic),
|
|
59
|
+
verificationSteps: verificationStepsForDiagnostic(code, diagnostic),
|
|
60
|
+
llmHint: llmHintForDiagnostic(code, diagnostic),
|
|
61
|
+
details: diagnosticDetails("diagnostic", diagnosticId, diagnostic.details),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function repairConstraintsForFinding(finding) {
|
|
65
|
+
return uniqueConstraints([
|
|
66
|
+
...specificRepairConstraints(finding.id),
|
|
67
|
+
...(finding.repairConstraints ?? []),
|
|
68
|
+
...constraintTextsToRepairConstraints(finding.constraints ?? []),
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
function repairConstraintsForDiagnostic(code, diagnostic) {
|
|
72
|
+
return uniqueConstraints([
|
|
73
|
+
...specificRepairConstraints(code),
|
|
74
|
+
...(diagnostic.repairConstraints ?? []),
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
function specificRepairConstraints(code) {
|
|
78
|
+
if (code === DIAGNOSTIC_IDS.META_DUPLICATE_ASSET_ID ||
|
|
79
|
+
code === CONTEXT_LENS_DIAGNOSTIC_CODES.DUPLICATE_ID) {
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
kind: "must_preserve",
|
|
83
|
+
text: "Preserve existing references where possible and update only references affected by the chosen canonical id.",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
kind: "must_not_change",
|
|
87
|
+
text: "Do not rename every duplicate blindly; identify the canonical asset or ask for review when intent is ambiguous.",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
kind: "requires_human_decision",
|
|
91
|
+
text: "Choose whether duplicates represent the same source of truth, a deprecated copy, or distinct assets before renaming.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
kind: "allowed_change",
|
|
95
|
+
text: "Rename the non-canonical asset id and update declared references that pointed to it.",
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
if (code === DIAGNOSTIC_IDS.META_UNKNOWN_REFERENCE ||
|
|
100
|
+
code === DIAGNOSTIC_IDS.META_UNKNOWN_DEPENDENCY ||
|
|
101
|
+
code === CONTEXT_LENS_DIAGNOSTIC_CODES.TARGET_NOT_FOUND ||
|
|
102
|
+
code === DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_UNRESOLVED) {
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
kind: "must_not_change",
|
|
106
|
+
text: "Do not create a fake asset or dependency just to satisfy validation.",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
kind: "must_preserve",
|
|
110
|
+
text: "Preserve the source asset's intended relationship when correcting the target.",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
kind: "allowed_change",
|
|
114
|
+
text: "Correct the reference, add the missing asset only when clear source material exists, or remove the relationship if it is stale.",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
kind: "requires_human_decision",
|
|
118
|
+
text: "Ask for review when the intended target cannot be inferred from repository evidence.",
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
if (code === DIAGNOSTIC_IDS.MAINT_ORPHANED_CONTEXT_ASSET) {
|
|
123
|
+
return [
|
|
124
|
+
{
|
|
125
|
+
kind: "must_not_change",
|
|
126
|
+
text: "Do not delete the context asset automatically.",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
kind: "must_preserve",
|
|
130
|
+
text: "Preserve asset content and metadata until ownership and intended reuse are reviewed.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
kind: "allowed_change",
|
|
134
|
+
text: "Attach the asset to a relevant skill or context, deprecate/archive it after review, or document that it is intentionally standalone.",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
kind: "requires_human_decision",
|
|
138
|
+
text: "Confirm whether the asset is unused, newly staged, or missing declared references before removing or archiving it.",
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
if (code === DIAGNOSTIC_IDS.MAINT_ORPHANED_CONTEXT_LENS) {
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
kind: "must_not_change",
|
|
146
|
+
text: "Do not add runtime lens selection or prompt assembly to make the lens appear used.",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
kind: "must_preserve",
|
|
150
|
+
text: "Preserve the lens purpose and applies_to relationship while reviewing reachability.",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
kind: "allowed_change",
|
|
154
|
+
text: "Reference the lens from an appropriate skill, mark it inactive after review, or document why it is intentionally staged.",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
kind: "requires_human_decision",
|
|
158
|
+
text: "Confirm whether an unreferenced active lens is staged, stale, or missing skill metadata.",
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
if (code === DIAGNOSTIC_IDS.MAINT_REFERENCE_DEPRECATED_ASSET ||
|
|
163
|
+
code === DIAGNOSTIC_IDS.META_INACTIVE_DEPENDENCY ||
|
|
164
|
+
code === DIAGNOSTIC_IDS.MAINT_CONTEXT_LENS_APPLIES_TO_INACTIVE_CONTEXT ||
|
|
165
|
+
code === DIAGNOSTIC_IDS.MAINT_SKILL_REFERENCES_SUPERSEDED_ASSET ||
|
|
166
|
+
code === DIAGNOSTIC_IDS.MAINT_ASSET_REFERENCES_SUPERSEDED_ASSET) {
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
kind: "must_preserve",
|
|
170
|
+
text: "Preserve the source asset's intent and any compatibility guidance while retargeting references.",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
kind: "must_not_change",
|
|
174
|
+
text: "Do not remove deprecated or archived assets unless repository policy and human review allow it.",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
kind: "allowed_change",
|
|
178
|
+
text: "Retarget the reference to a reviewed replacement when superseded_by or canonical metadata provides one.",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
kind: "requires_human_decision",
|
|
182
|
+
text: "Keep the reference and document the reason when no reviewed replacement exists.",
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
if (code === DIAGNOSTIC_IDS.META_MISSING_ID ||
|
|
187
|
+
code === DIAGNOSTIC_IDS.META_MISSING_OWNER ||
|
|
188
|
+
code === CONTEXT_LENS_DIAGNOSTIC_CODES.MISSING_REQUIRED_FIELD) {
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
kind: "must_preserve",
|
|
192
|
+
text: "Preserve the asset body and existing metadata while adding the missing governance field.",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
kind: "allowed_change",
|
|
196
|
+
text: "Add the smallest metadata field needed to satisfy the declared schema.",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
kind: "requires_human_decision",
|
|
200
|
+
text: "Do not invent ownership, purpose, or usage boundaries when repository evidence is unclear.",
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
if (code.startsWith("SEC-")) {
|
|
205
|
+
return [
|
|
206
|
+
{
|
|
207
|
+
kind: "must_preserve",
|
|
208
|
+
text: "Preserve the intended workflow only where it can remain policy-compliant.",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
kind: "must_not_change",
|
|
212
|
+
text: "Do not weaken security policy, approval, redaction, destination, or secret-handling constraints to silence the finding.",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
kind: "risk",
|
|
216
|
+
text: "Treat security repairs as review-sensitive when network access, uploads, credentials, secrets, or destructive commands are involved.",
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
if (code === CONTEXT_LENS_DIAGNOSTIC_CODES.DEPRECATED_FIELD) {
|
|
221
|
+
return [
|
|
222
|
+
{
|
|
223
|
+
kind: "must_preserve",
|
|
224
|
+
text: "Preserve the deprecated field value when moving it to the replacement field.",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
kind: "allowed_change",
|
|
228
|
+
text: "Rename the deprecated field to the supported Context Lens field.",
|
|
229
|
+
},
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
if (code === CONTEXT_LENS_DIAGNOSTIC_CODES.PATH_NORMALIZATION_MISMATCH) {
|
|
233
|
+
return [
|
|
234
|
+
{
|
|
235
|
+
kind: "must_preserve",
|
|
236
|
+
text: "Preserve the target relationship while normalizing the path spelling.",
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
kind: "allowed_change",
|
|
240
|
+
text: "Replace the target path with the normalized repository-relative path reported by Renma.",
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
return [
|
|
245
|
+
{
|
|
246
|
+
kind: "must_preserve",
|
|
247
|
+
text: "Preserve existing repository semantics and reviewable evidence while making the smallest repair.",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
kind: "must_not_change",
|
|
251
|
+
text: "Do not introduce runtime context selection, prompt assembly, or hidden scan-time rewrites.",
|
|
252
|
+
},
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
function constraintTextsToRepairConstraints(constraints) {
|
|
256
|
+
return constraints.map((text) => ({
|
|
257
|
+
kind: repairConstraintKindForText(text),
|
|
258
|
+
text,
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
function repairConstraintKindForText(text) {
|
|
262
|
+
const normalized = text.toLowerCase();
|
|
263
|
+
if (/\b(do not|don't|must not|never)\b/.test(normalized)) {
|
|
264
|
+
return "must_not_change";
|
|
265
|
+
}
|
|
266
|
+
if (/\b(preserve|keep|retain|maintain)\b/.test(normalized)) {
|
|
267
|
+
return "must_preserve";
|
|
268
|
+
}
|
|
269
|
+
if (/\b(human|owner|review|approval|confirm|decide|ambiguous)\b/.test(normalized)) {
|
|
270
|
+
return "requires_human_decision";
|
|
271
|
+
}
|
|
272
|
+
if (/\b(risk|risky|danger|unsafe|secret|credential|destructive)\b/.test(normalized)) {
|
|
273
|
+
return "risk";
|
|
274
|
+
}
|
|
275
|
+
return "allowed_change";
|
|
276
|
+
}
|
|
277
|
+
function verificationStepsForFinding(finding) {
|
|
278
|
+
const steps = finding.verificationStepsV2 ?? [
|
|
279
|
+
...(finding.verificationSteps ?? []).map((step) => verificationStepFromText(step, finding.id)),
|
|
280
|
+
];
|
|
281
|
+
return steps.length > 0
|
|
282
|
+
? uniqueVerificationSteps(steps)
|
|
283
|
+
: defaultVerificationSteps(finding.id);
|
|
284
|
+
}
|
|
285
|
+
function verificationStepsForDiagnostic(code, diagnostic) {
|
|
286
|
+
if (diagnostic.verificationSteps && diagnostic.verificationSteps.length > 0) {
|
|
287
|
+
return uniqueVerificationSteps(diagnostic.verificationSteps);
|
|
288
|
+
}
|
|
289
|
+
return defaultVerificationSteps(code);
|
|
290
|
+
}
|
|
291
|
+
function defaultVerificationSteps(code) {
|
|
292
|
+
return [
|
|
293
|
+
{
|
|
294
|
+
text: "Run Renma scan again and confirm this diagnostic no longer appears.",
|
|
295
|
+
command: "renma scan",
|
|
296
|
+
expected: `No diagnostics with code ${code} are reported.`,
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
function verificationStepFromText(text, code) {
|
|
301
|
+
const normalized = text.toLowerCase();
|
|
302
|
+
if (normalized.startsWith("run renma scan")) {
|
|
303
|
+
return {
|
|
304
|
+
text,
|
|
305
|
+
command: "renma scan",
|
|
306
|
+
expected: `No diagnostics with code ${code} are reported.`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (normalized.startsWith("run renma catalog")) {
|
|
310
|
+
return {
|
|
311
|
+
text,
|
|
312
|
+
command: "renma catalog",
|
|
313
|
+
expected: "Catalog output resolves relevant assets and dependencies.",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (normalized.startsWith("run renma readiness")) {
|
|
317
|
+
return {
|
|
318
|
+
text,
|
|
319
|
+
command: "renma readiness",
|
|
320
|
+
expected: "Readiness checks reflect the repaired repository state.",
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (normalized.startsWith("run renma graph")) {
|
|
324
|
+
return {
|
|
325
|
+
text,
|
|
326
|
+
command: "renma graph",
|
|
327
|
+
expected: "Graph output shows the repaired relationships.",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (normalized.startsWith("run npm test")) {
|
|
331
|
+
return {
|
|
332
|
+
text,
|
|
333
|
+
command: "npm test",
|
|
334
|
+
expected: "The test suite passes.",
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return { text };
|
|
338
|
+
}
|
|
339
|
+
function llmHintForFinding(finding) {
|
|
340
|
+
if (finding.llmHint)
|
|
341
|
+
return finding.llmHint;
|
|
342
|
+
if (finding.id === DIAGNOSTIC_IDS.META_DUPLICATE_ASSET_ID) {
|
|
343
|
+
return "Rename only the non-canonical duplicate id, then update references that pointed to it.";
|
|
344
|
+
}
|
|
345
|
+
if (finding.id === DIAGNOSTIC_IDS.META_UNKNOWN_REFERENCE ||
|
|
346
|
+
finding.id === DIAGNOSTIC_IDS.META_UNKNOWN_DEPENDENCY) {
|
|
347
|
+
return "Check whether this reference is a typo before adding a new asset.";
|
|
348
|
+
}
|
|
349
|
+
if (finding.id === DIAGNOSTIC_IDS.MAINT_ORPHANED_CONTEXT_ASSET) {
|
|
350
|
+
return "Do not delete this orphaned asset automatically; first determine whether it is intentionally standalone.";
|
|
351
|
+
}
|
|
352
|
+
return "Use the evidence, repair constraints, and verification steps to make the smallest reviewable patch.";
|
|
353
|
+
}
|
|
354
|
+
function llmHintForDiagnostic(code, diagnostic) {
|
|
355
|
+
if (diagnostic.llmHint)
|
|
356
|
+
return diagnostic.llmHint;
|
|
357
|
+
if (code === CONTEXT_LENS_DIAGNOSTIC_CODES.TARGET_NOT_FOUND) {
|
|
358
|
+
return "Check whether the applies_to value is a typo before adding a new context asset.";
|
|
359
|
+
}
|
|
360
|
+
if (code === CONTEXT_LENS_DIAGNOSTIC_CODES.DUPLICATE_ID) {
|
|
361
|
+
return "Rename only the non-canonical lens id and update any skill references that pointed to it.";
|
|
362
|
+
}
|
|
363
|
+
if (code === "SUPPRESSION-EXPIRED") {
|
|
364
|
+
return "Review the finding again, then either fix it or renew the suppression with an explicit audit reason.";
|
|
365
|
+
}
|
|
366
|
+
return "Use the diagnostic evidence and keep the repair limited to the reported repository fact.";
|
|
367
|
+
}
|
|
368
|
+
function reviewBundleSeed(diagnostic) {
|
|
369
|
+
if (diagnostic.code === DIAGNOSTIC_IDS.META_DUPLICATE_ASSET_ID ||
|
|
370
|
+
diagnostic.code === CONTEXT_LENS_DIAGNOSTIC_CODES.DUPLICATE_ID) {
|
|
371
|
+
const label = extractedAssetId(diagnostic) ?? "unknown";
|
|
372
|
+
return {
|
|
373
|
+
key: `duplicate-id:${label}`,
|
|
374
|
+
kind: "duplicate-id",
|
|
375
|
+
label,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (diagnostic.code === DIAGNOSTIC_IDS.META_UNKNOWN_REFERENCE ||
|
|
379
|
+
diagnostic.code === DIAGNOSTIC_IDS.META_UNKNOWN_DEPENDENCY ||
|
|
380
|
+
diagnostic.code === CONTEXT_LENS_DIAGNOSTIC_CODES.TARGET_NOT_FOUND ||
|
|
381
|
+
diagnostic.code === DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_UNRESOLVED) {
|
|
382
|
+
const label = affectedSource(diagnostic);
|
|
383
|
+
return {
|
|
384
|
+
key: `unknown-reference:${label}`,
|
|
385
|
+
kind: "unknown-reference",
|
|
386
|
+
label,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (diagnostic.code === DIAGNOSTIC_IDS.MAINT_ORPHANED_CONTEXT_ASSET) {
|
|
390
|
+
return {
|
|
391
|
+
key: "orphaned-context-assets",
|
|
392
|
+
kind: "orphaned-context-assets",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (diagnostic.code === DIAGNOSTIC_IDS.MAINT_ORPHANED_CONTEXT_LENS) {
|
|
396
|
+
return {
|
|
397
|
+
key: "orphaned-context-lenses",
|
|
398
|
+
kind: "orphaned-context-lenses",
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (isReferenceOrDependencyCode(diagnostic.code)) {
|
|
402
|
+
const label = affectedSource(diagnostic);
|
|
403
|
+
return {
|
|
404
|
+
key: `dependency-review:${label}`,
|
|
405
|
+
kind: "dependency-review",
|
|
406
|
+
label,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const label = affectedSource(diagnostic);
|
|
410
|
+
return {
|
|
411
|
+
key: `code-review:${diagnostic.code}:${label}`,
|
|
412
|
+
kind: "code-review",
|
|
413
|
+
label,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function reviewBundle(seed, diagnostics) {
|
|
417
|
+
const sortedDiagnostics = [...diagnostics].sort(compareDiagnosticsV2);
|
|
418
|
+
const affectedFiles = stableUnique(sortedDiagnostics.flatMap((diagnostic) => filesForDiagnostic(diagnostic)));
|
|
419
|
+
const affectedAssets = stableUnique([
|
|
420
|
+
seed.label && seed.kind === "duplicate-id" ? seed.label : undefined,
|
|
421
|
+
...sortedDiagnostics.flatMap(extractAssets),
|
|
422
|
+
].filter((asset) => asset !== undefined));
|
|
423
|
+
const diagnosticCodes = stableUnique(sortedDiagnostics.map((diagnostic) => diagnostic.code));
|
|
424
|
+
const diagnosticIds = stableUnique(sortedDiagnostics
|
|
425
|
+
.map((diagnostic) => diagnostic.details?.diagnosticId)
|
|
426
|
+
.filter((id) => typeof id === "string"));
|
|
427
|
+
return compactBundle({
|
|
428
|
+
id: bundleId(seed.key),
|
|
429
|
+
title: bundleTitle(seed, sortedDiagnostics),
|
|
430
|
+
summary: bundleSummary(seed, sortedDiagnostics),
|
|
431
|
+
severity: aggregateSeverity(sortedDiagnostics),
|
|
432
|
+
diagnosticCodes,
|
|
433
|
+
diagnosticIds,
|
|
434
|
+
affectedAssets,
|
|
435
|
+
affectedFiles,
|
|
436
|
+
suggestedReviewOrder: reviewOrder(seed, affectedFiles, affectedAssets),
|
|
437
|
+
llmHint: bundleLlmHint(seed),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function bundleTitle(seed, diagnostics) {
|
|
441
|
+
if (seed.kind === "duplicate-id") {
|
|
442
|
+
return `Duplicate id review: ${seed.label}`;
|
|
443
|
+
}
|
|
444
|
+
if (seed.kind === "unknown-reference") {
|
|
445
|
+
return `Unresolved reference review: ${seed.label}`;
|
|
446
|
+
}
|
|
447
|
+
if (seed.kind === "orphaned-context-assets") {
|
|
448
|
+
return "Orphaned shared context assets";
|
|
449
|
+
}
|
|
450
|
+
if (seed.kind === "orphaned-context-lenses") {
|
|
451
|
+
return "Orphaned context lenses";
|
|
452
|
+
}
|
|
453
|
+
if (seed.kind === "dependency-review") {
|
|
454
|
+
return `Dependency/reference review: ${seed.label}`;
|
|
455
|
+
}
|
|
456
|
+
return `${diagnostics[0]?.code ?? "Diagnostic"} review`;
|
|
457
|
+
}
|
|
458
|
+
function bundleSummary(seed, diagnostics) {
|
|
459
|
+
const count = diagnostics.length;
|
|
460
|
+
if (seed.kind === "duplicate-id") {
|
|
461
|
+
return `${count} diagnostics report the same declared id and should be reviewed together before renaming or merging assets.`;
|
|
462
|
+
}
|
|
463
|
+
if (seed.kind === "unknown-reference") {
|
|
464
|
+
return `${count} unresolved reference diagnostics share a source and should be repaired as one dependency decision.`;
|
|
465
|
+
}
|
|
466
|
+
if (seed.kind === "orphaned-context-assets") {
|
|
467
|
+
return `${count} active shared context assets have no incoming declared references.`;
|
|
468
|
+
}
|
|
469
|
+
if (seed.kind === "orphaned-context-lenses") {
|
|
470
|
+
return `${count} active context lenses are not referenced by skills.`;
|
|
471
|
+
}
|
|
472
|
+
if (seed.kind === "dependency-review") {
|
|
473
|
+
return `${count} dependency or reference diagnostics affect the same source.`;
|
|
474
|
+
}
|
|
475
|
+
return `${count} diagnostics share the same code and source.`;
|
|
476
|
+
}
|
|
477
|
+
function bundleLlmHint(seed) {
|
|
478
|
+
if (seed.kind === "duplicate-id") {
|
|
479
|
+
return "Pick one canonical asset id before editing references; do not rename every duplicate in one blind pass.";
|
|
480
|
+
}
|
|
481
|
+
if (seed.kind === "unknown-reference") {
|
|
482
|
+
return "Look for typos, renamed files, or missing declared assets before adding anything new.";
|
|
483
|
+
}
|
|
484
|
+
if (seed.kind === "orphaned-context-assets") {
|
|
485
|
+
return "Review whether each context is intentionally standalone, missing a reference, or ready to deprecate; do not delete automatically.";
|
|
486
|
+
}
|
|
487
|
+
if (seed.kind === "orphaned-context-lenses") {
|
|
488
|
+
return "Connect intended lenses through skill metadata or mark stale lenses inactive after review; do not add runtime selection logic.";
|
|
489
|
+
}
|
|
490
|
+
if (seed.kind === "dependency-review") {
|
|
491
|
+
return "Repair related references in one patch so catalog and graph output remain consistent.";
|
|
492
|
+
}
|
|
493
|
+
return "Review these diagnostics together and make the smallest patch that satisfies their shared evidence.";
|
|
494
|
+
}
|
|
495
|
+
const REFERENCE_OR_DEPENDENCY_CODES = new Set([
|
|
496
|
+
DIAGNOSTIC_IDS.MAINT_REFERENCE_DEPRECATED_ASSET,
|
|
497
|
+
DIAGNOSTIC_IDS.META_INACTIVE_DEPENDENCY,
|
|
498
|
+
DIAGNOSTIC_IDS.MAINT_CONTEXT_LENS_APPLIES_TO_INACTIVE_CONTEXT,
|
|
499
|
+
DIAGNOSTIC_IDS.MAINT_SKILL_CONTEXT_REFERENCE_NOT_DECLARED,
|
|
500
|
+
DIAGNOSTIC_IDS.MAINT_SKILL_REFERENCES_SUPERSEDED_ASSET,
|
|
501
|
+
DIAGNOSTIC_IDS.MAINT_ASSET_REFERENCES_SUPERSEDED_ASSET,
|
|
502
|
+
DIAGNOSTIC_IDS.LAYOUT_CONTEXT_REFERENCE_NON_CANONICAL,
|
|
503
|
+
DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_NON_TOOLS,
|
|
504
|
+
DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_SKILL_SCRIPTS,
|
|
505
|
+
]);
|
|
506
|
+
function isReferenceOrDependencyCode(code) {
|
|
507
|
+
return REFERENCE_OR_DEPENDENCY_CODES.has(code);
|
|
508
|
+
}
|
|
509
|
+
function severityFromFinding(finding) {
|
|
510
|
+
return finding.severity === "critical" || finding.severity === "high"
|
|
511
|
+
? "error"
|
|
512
|
+
: "warning";
|
|
513
|
+
}
|
|
514
|
+
function aggregateSeverity(diagnostics) {
|
|
515
|
+
return diagnostics.reduce((current, diagnostic) => severityRank(diagnostic.severity) > severityRank(current)
|
|
516
|
+
? diagnostic.severity
|
|
517
|
+
: current, "info");
|
|
518
|
+
}
|
|
519
|
+
function severityRank(severity) {
|
|
520
|
+
if (severity === "error")
|
|
521
|
+
return 3;
|
|
522
|
+
if (severity === "warning")
|
|
523
|
+
return 2;
|
|
524
|
+
return 1;
|
|
525
|
+
}
|
|
526
|
+
function locationFromEvidence(evidence) {
|
|
527
|
+
return {
|
|
528
|
+
path: evidence.path,
|
|
529
|
+
startLine: evidence.startLine,
|
|
530
|
+
endLine: evidence.endLine,
|
|
531
|
+
snippet: evidence.snippet,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function locationFromDiagnostic(diagnostic) {
|
|
535
|
+
if (diagnostic.evidence)
|
|
536
|
+
return locationFromEvidence(diagnostic.evidence);
|
|
537
|
+
if (!diagnostic.path)
|
|
538
|
+
return undefined;
|
|
539
|
+
return { path: diagnostic.path };
|
|
540
|
+
}
|
|
541
|
+
function inferredDiagnosticCode(diagnostic) {
|
|
542
|
+
if (/Could not evaluate glob/i.test(diagnostic.message)) {
|
|
543
|
+
return "DISCOVERY-GLOB-EVALUATION-FAILED";
|
|
544
|
+
}
|
|
545
|
+
if (/Skipping symbolic link/i.test(diagnostic.message)) {
|
|
546
|
+
return "DISCOVERY-SYMLINK-SKIPPED";
|
|
547
|
+
}
|
|
548
|
+
if (/Skipping file larger than max_file_size_bytes/i.test(diagnostic.message)) {
|
|
549
|
+
return "DISCOVERY-FILE-TOO-LARGE";
|
|
550
|
+
}
|
|
551
|
+
if (/Could not read file/i.test(diagnostic.message)) {
|
|
552
|
+
return "DISCOVERY-FILE-READ-FAILED";
|
|
553
|
+
}
|
|
554
|
+
if (/Suppression for .+ expired/i.test(diagnostic.message)) {
|
|
555
|
+
return "SUPPRESSION-EXPIRED";
|
|
556
|
+
}
|
|
557
|
+
return "RENMA-DIAGNOSTIC";
|
|
558
|
+
}
|
|
559
|
+
function diagnosticIdFor(code, location, index) {
|
|
560
|
+
const pathPart = location?.path ?? "global";
|
|
561
|
+
const linePart = location?.startLine ?? 0;
|
|
562
|
+
return `${code}@${pathPart}:L${linePart}#${index}`;
|
|
563
|
+
}
|
|
564
|
+
function diagnosticDetails(source, diagnosticId, facts, compatibility = {}) {
|
|
565
|
+
const factRecord = facts ? compactRecord(facts) : {};
|
|
566
|
+
const safeFlatFacts = Object.fromEntries(Object.entries(factRecord).filter(([key]) => !RESERVED_DETAIL_KEYS.has(key)));
|
|
567
|
+
return compactRecord({
|
|
568
|
+
...safeFlatFacts,
|
|
569
|
+
...compatibility,
|
|
570
|
+
diagnosticId,
|
|
571
|
+
source,
|
|
572
|
+
facts: Object.keys(factRecord).length > 0 ? factRecord : undefined,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
function detailString(diagnostic, key) {
|
|
576
|
+
const factValue = detailFacts(diagnostic)?.[key];
|
|
577
|
+
if (typeof factValue === "string" && factValue.length > 0) {
|
|
578
|
+
return factValue;
|
|
579
|
+
}
|
|
580
|
+
if (RESERVED_DETAIL_KEYS.has(key))
|
|
581
|
+
return undefined;
|
|
582
|
+
const value = diagnostic.details?.[key];
|
|
583
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
584
|
+
}
|
|
585
|
+
function detailStringArray(diagnostic, key) {
|
|
586
|
+
const values = [detailFacts(diagnostic)?.[key], diagnostic.details?.[key]];
|
|
587
|
+
for (const value of values) {
|
|
588
|
+
if (!Array.isArray(value))
|
|
589
|
+
continue;
|
|
590
|
+
return value.filter((item) => typeof item === "string" && item.length > 0);
|
|
591
|
+
}
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
594
|
+
function detailFacts(diagnostic) {
|
|
595
|
+
const facts = diagnostic.details?.facts;
|
|
596
|
+
if (!facts || typeof facts !== "object" || Array.isArray(facts)) {
|
|
597
|
+
return undefined;
|
|
598
|
+
}
|
|
599
|
+
return facts;
|
|
600
|
+
}
|
|
601
|
+
function extractedAssetId(diagnostic) {
|
|
602
|
+
const structuredId = detailString(diagnostic, "assetId") ?? detailString(diagnostic, "lensId");
|
|
603
|
+
if (structuredId)
|
|
604
|
+
return structuredId;
|
|
605
|
+
const sources = [
|
|
606
|
+
diagnostic.message,
|
|
607
|
+
diagnostic.location?.snippet,
|
|
608
|
+
diagnostic.llmHint,
|
|
609
|
+
].filter((source) => source !== undefined);
|
|
610
|
+
for (const source of sources) {
|
|
611
|
+
const duplicateSnippet = source.match(/Duplicate asset id:\s*([^\n]+)/i);
|
|
612
|
+
if (duplicateSnippet?.[1])
|
|
613
|
+
return duplicateSnippet[1].trim();
|
|
614
|
+
const quotedId = source.match(/\bid\s+"([^"]+)"/i);
|
|
615
|
+
if (quotedId?.[1])
|
|
616
|
+
return quotedId[1].trim();
|
|
617
|
+
}
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
function affectedSource(diagnostic) {
|
|
621
|
+
const structuredSource = detailString(diagnostic, "sourcePath") ??
|
|
622
|
+
detailString(diagnostic, "source");
|
|
623
|
+
if (structuredSource)
|
|
624
|
+
return structuredSource;
|
|
625
|
+
const declaredBy = diagnostic.llmHint?.match(/declared by "([^"]+)"/i)?.[1];
|
|
626
|
+
if (declaredBy)
|
|
627
|
+
return declaredBy;
|
|
628
|
+
return diagnostic.location?.path ?? "global";
|
|
629
|
+
}
|
|
630
|
+
function filesForDiagnostic(diagnostic) {
|
|
631
|
+
return [
|
|
632
|
+
diagnostic.location?.path,
|
|
633
|
+
detailString(diagnostic, "sourcePath"),
|
|
634
|
+
detailString(diagnostic, "targetPath"),
|
|
635
|
+
...detailStringArray(diagnostic, "duplicatePaths"),
|
|
636
|
+
...(diagnostic.relatedLocations ?? []).map((location) => location.path),
|
|
637
|
+
].filter((pathValue) => pathValue !== undefined);
|
|
638
|
+
}
|
|
639
|
+
function extractAssets(diagnostic) {
|
|
640
|
+
const structured = stableUnique([
|
|
641
|
+
detailString(diagnostic, "assetId"),
|
|
642
|
+
detailString(diagnostic, "lensId"),
|
|
643
|
+
detailString(diagnostic, "source"),
|
|
644
|
+
detailString(diagnostic, "target"),
|
|
645
|
+
...detailStringArray(diagnostic, "replacementTargets"),
|
|
646
|
+
].filter((value) => value !== undefined));
|
|
647
|
+
if (structured.length > 0)
|
|
648
|
+
return structured;
|
|
649
|
+
const sources = [
|
|
650
|
+
diagnostic.message,
|
|
651
|
+
diagnostic.location?.snippet,
|
|
652
|
+
diagnostic.llmHint,
|
|
653
|
+
].filter((source) => source !== undefined);
|
|
654
|
+
const assets = new Set();
|
|
655
|
+
for (const source of sources) {
|
|
656
|
+
for (const match of source.matchAll(/"([^"]+)"/g)) {
|
|
657
|
+
const value = match[1]?.trim();
|
|
658
|
+
if (value && looksAssetLike(value))
|
|
659
|
+
assets.add(value);
|
|
660
|
+
}
|
|
661
|
+
const duplicateId = source.match(/Duplicate asset id:\s*([^\n]+)/i)?.[1];
|
|
662
|
+
if (duplicateId)
|
|
663
|
+
assets.add(duplicateId.trim());
|
|
664
|
+
}
|
|
665
|
+
return [...assets];
|
|
666
|
+
}
|
|
667
|
+
function looksAssetLike(value) {
|
|
668
|
+
return (value.includes("/") ||
|
|
669
|
+
value.includes(".") ||
|
|
670
|
+
value.startsWith("context") ||
|
|
671
|
+
value.startsWith("lens") ||
|
|
672
|
+
value.startsWith("skill"));
|
|
673
|
+
}
|
|
674
|
+
function reviewOrder(seed, affectedFiles, affectedAssets) {
|
|
675
|
+
if (seed.kind === "duplicate-id") {
|
|
676
|
+
return [
|
|
677
|
+
...affectedFiles.map((file) => `Inspect duplicate declaration in ${file}`),
|
|
678
|
+
"Choose canonical id before editing references.",
|
|
679
|
+
"Update references and rerun Renma scan.",
|
|
680
|
+
];
|
|
681
|
+
}
|
|
682
|
+
if (seed.kind === "unknown-reference" || seed.kind === "dependency-review") {
|
|
683
|
+
return [
|
|
684
|
+
...affectedFiles.map((file) => `Inspect declared references in ${file}`),
|
|
685
|
+
...affectedAssets.map((asset) => `Resolve intended target ${asset}`),
|
|
686
|
+
"Rerun Renma catalog or graph to verify relationships.",
|
|
687
|
+
];
|
|
688
|
+
}
|
|
689
|
+
return [
|
|
690
|
+
...affectedFiles.map((file) => `Inspect ${file}`),
|
|
691
|
+
"Apply the repair constraints before editing.",
|
|
692
|
+
"Rerun Renma scan.",
|
|
693
|
+
];
|
|
694
|
+
}
|
|
695
|
+
function compareDiagnosticsV2(a, b) {
|
|
696
|
+
const byCode = a.code.localeCompare(b.code);
|
|
697
|
+
if (byCode !== 0)
|
|
698
|
+
return byCode;
|
|
699
|
+
const byPath = (a.location?.path ?? "").localeCompare(b.location?.path ?? "");
|
|
700
|
+
if (byPath !== 0)
|
|
701
|
+
return byPath;
|
|
702
|
+
const byLine = (a.location?.startLine ?? 0) - (b.location?.startLine ?? 0);
|
|
703
|
+
if (byLine !== 0)
|
|
704
|
+
return byLine;
|
|
705
|
+
return a.message.localeCompare(b.message);
|
|
706
|
+
}
|
|
707
|
+
function uniqueConstraints(constraints) {
|
|
708
|
+
const seen = new Set();
|
|
709
|
+
const result = [];
|
|
710
|
+
for (const constraint of constraints) {
|
|
711
|
+
const key = `${constraint.kind}\u0000${constraint.text}`;
|
|
712
|
+
if (seen.has(key))
|
|
713
|
+
continue;
|
|
714
|
+
seen.add(key);
|
|
715
|
+
result.push(constraint);
|
|
716
|
+
}
|
|
717
|
+
return result;
|
|
718
|
+
}
|
|
719
|
+
function uniqueVerificationSteps(steps) {
|
|
720
|
+
const seen = new Set();
|
|
721
|
+
const result = [];
|
|
722
|
+
for (const step of steps) {
|
|
723
|
+
const key = `${step.text}\u0000${step.command ?? ""}\u0000${step.expected ?? ""}`;
|
|
724
|
+
if (seen.has(key))
|
|
725
|
+
continue;
|
|
726
|
+
seen.add(key);
|
|
727
|
+
result.push(step);
|
|
728
|
+
}
|
|
729
|
+
return result;
|
|
730
|
+
}
|
|
731
|
+
function stableUnique(values) {
|
|
732
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
733
|
+
}
|
|
734
|
+
function bundleId(value) {
|
|
735
|
+
return value
|
|
736
|
+
.toLowerCase()
|
|
737
|
+
.replace(/[^a-z0-9_.:/-]+/g, "-")
|
|
738
|
+
.replace(/^-+|-+$/g, "");
|
|
739
|
+
}
|
|
740
|
+
function compactDiagnostic(diagnostic) {
|
|
741
|
+
return compactRecord(diagnostic);
|
|
742
|
+
}
|
|
743
|
+
function compactBundle(bundle) {
|
|
744
|
+
return compactRecord({
|
|
745
|
+
...bundle,
|
|
746
|
+
diagnosticIds: bundle.diagnosticIds && bundle.diagnosticIds.length > 0
|
|
747
|
+
? bundle.diagnosticIds
|
|
748
|
+
: undefined,
|
|
749
|
+
affectedAssets: bundle.affectedAssets && bundle.affectedAssets.length > 0
|
|
750
|
+
? bundle.affectedAssets
|
|
751
|
+
: undefined,
|
|
752
|
+
affectedFiles: bundle.affectedFiles && bundle.affectedFiles.length > 0
|
|
753
|
+
? bundle.affectedFiles
|
|
754
|
+
: undefined,
|
|
755
|
+
suggestedReviewOrder: bundle.suggestedReviewOrder && bundle.suggestedReviewOrder.length > 0
|
|
756
|
+
? bundle.suggestedReviewOrder
|
|
757
|
+
: undefined,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
function compactRecord(record) {
|
|
761
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
762
|
+
}
|
|
763
|
+
//# sourceMappingURL=diagnostics-v2.js.map
|