@agentxm/client-core 0.3.2 → 0.4.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.
Files changed (142) hide show
  1. package/LICENSE +1 -1
  2. package/dist/src/unstable/agents/detection.d.ts.map +1 -1
  3. package/dist/src/unstable/agents/detection.js +2 -1
  4. package/dist/src/unstable/agents/detection.js.map +1 -1
  5. package/dist/src/unstable/branding.d.ts +2 -0
  6. package/dist/src/unstable/branding.d.ts.map +1 -0
  7. package/dist/src/unstable/branding.js +11 -0
  8. package/dist/src/unstable/branding.js.map +1 -0
  9. package/dist/src/unstable/cli-renderer/ansi-chrome.d.ts.map +1 -1
  10. package/dist/src/unstable/cli-renderer/ansi-chrome.js +4 -1
  11. package/dist/src/unstable/cli-renderer/ansi-chrome.js.map +1 -1
  12. package/dist/src/unstable/extensions/index.d.ts +1 -0
  13. package/dist/src/unstable/extensions/index.d.ts.map +1 -1
  14. package/dist/src/unstable/extensions/index.js +1 -0
  15. package/dist/src/unstable/extensions/index.js.map +1 -1
  16. package/dist/src/unstable/extensions/universal-skills-dir.d.ts +54 -0
  17. package/dist/src/unstable/extensions/universal-skills-dir.d.ts.map +1 -0
  18. package/dist/src/unstable/extensions/universal-skills-dir.js +68 -0
  19. package/dist/src/unstable/extensions/universal-skills-dir.js.map +1 -0
  20. package/dist/src/unstable/lint/catalog/pack/manifest-keys-recognized.js +1 -1
  21. package/dist/src/unstable/lint/catalog/pack/manifest-keys-recognized.js.map +1 -1
  22. package/dist/src/unstable/lint/catalog/pack/manifest-present.d.ts.map +1 -1
  23. package/dist/src/unstable/lint/catalog/pack/manifest-present.js +2 -5
  24. package/dist/src/unstable/lint/catalog/pack/manifest-present.js.map +1 -1
  25. package/dist/src/unstable/lint/catalog/pack/manifest-schema-valid.js +1 -1
  26. package/dist/src/unstable/lint/catalog/pack/manifest-schema-valid.js.map +1 -1
  27. package/dist/src/unstable/lint/catalog/shared/schema-rule.d.ts.map +1 -1
  28. package/dist/src/unstable/lint/catalog/shared/schema-rule.js +12 -5
  29. package/dist/src/unstable/lint/catalog/shared/schema-rule.js.map +1 -1
  30. package/dist/src/unstable/lint/catalog/skill/frontmatter-parseable.js +8 -11
  31. package/dist/src/unstable/lint/catalog/skill/frontmatter-parseable.js.map +1 -1
  32. package/dist/src/unstable/lint/catalog/skill/manifest-keys-recognized.js +1 -1
  33. package/dist/src/unstable/lint/catalog/skill/manifest-keys-recognized.js.map +1 -1
  34. package/dist/src/unstable/lint/catalog/skill/manifest-present.d.ts.map +1 -1
  35. package/dist/src/unstable/lint/catalog/skill/manifest-present.js +2 -5
  36. package/dist/src/unstable/lint/catalog/skill/manifest-present.js.map +1 -1
  37. package/dist/src/unstable/lint/catalog/skill/manifest-schema-valid.js +1 -1
  38. package/dist/src/unstable/lint/catalog/skill/manifest-schema-valid.js.map +1 -1
  39. package/dist/src/unstable/lint/catalog/skill/skill-md-present.js +2 -3
  40. package/dist/src/unstable/lint/catalog/skill/skill-md-present.js.map +1 -1
  41. package/dist/src/unstable/lint/catalog/workspace/agents-detected-declared.d.ts.map +1 -1
  42. package/dist/src/unstable/lint/catalog/workspace/agents-detected-declared.js +5 -15
  43. package/dist/src/unstable/lint/catalog/workspace/agents-detected-declared.js.map +1 -1
  44. package/dist/src/unstable/lint/catalog/workspace/agents-recognized.d.ts.map +1 -1
  45. package/dist/src/unstable/lint/catalog/workspace/agents-recognized.js +4 -15
  46. package/dist/src/unstable/lint/catalog/workspace/agents-recognized.js.map +1 -1
  47. package/dist/src/unstable/lint/catalog/workspace/helpers/decode.d.ts +14 -0
  48. package/dist/src/unstable/lint/catalog/workspace/helpers/decode.d.ts.map +1 -0
  49. package/dist/src/unstable/lint/catalog/workspace/helpers/decode.js +28 -0
  50. package/dist/src/unstable/lint/catalog/workspace/helpers/decode.js.map +1 -0
  51. package/dist/src/unstable/lint/catalog/workspace/helpers/finding.d.ts +10 -0
  52. package/dist/src/unstable/lint/catalog/workspace/helpers/finding.d.ts.map +1 -0
  53. package/dist/src/unstable/lint/catalog/workspace/helpers/finding.js +11 -0
  54. package/dist/src/unstable/lint/catalog/workspace/helpers/finding.js.map +1 -0
  55. package/dist/src/unstable/lint/catalog/workspace/helpers/retained-skills.d.ts +24 -0
  56. package/dist/src/unstable/lint/catalog/workspace/helpers/retained-skills.d.ts.map +1 -0
  57. package/dist/src/unstable/lint/catalog/workspace/helpers/retained-skills.js +41 -0
  58. package/dist/src/unstable/lint/catalog/workspace/helpers/retained-skills.js.map +1 -0
  59. package/dist/src/unstable/lint/catalog/workspace/helpers/source-categorize.d.ts +16 -0
  60. package/dist/src/unstable/lint/catalog/workspace/helpers/source-categorize.d.ts.map +1 -0
  61. package/dist/src/unstable/lint/catalog/workspace/helpers/source-categorize.js +37 -0
  62. package/dist/src/unstable/lint/catalog/workspace/helpers/source-categorize.js.map +1 -0
  63. package/dist/src/unstable/lint/catalog/workspace/initialized.d.ts +1 -1
  64. package/dist/src/unstable/lint/catalog/workspace/initialized.js +4 -6
  65. package/dist/src/unstable/lint/catalog/workspace/initialized.js.map +1 -1
  66. package/dist/src/unstable/lint/catalog/workspace/lockfile-valid.d.ts.map +1 -1
  67. package/dist/src/unstable/lint/catalog/workspace/lockfile-valid.js +8 -21
  68. package/dist/src/unstable/lint/catalog/workspace/lockfile-valid.js.map +1 -1
  69. package/dist/src/unstable/lint/catalog/workspace/packs-declarations-valid.d.ts.map +1 -1
  70. package/dist/src/unstable/lint/catalog/workspace/packs-declarations-valid.js +12 -49
  71. package/dist/src/unstable/lint/catalog/workspace/packs-declarations-valid.js.map +1 -1
  72. package/dist/src/unstable/lint/catalog/workspace/packs-dependencies-resolved.d.ts.map +1 -1
  73. package/dist/src/unstable/lint/catalog/workspace/packs-dependencies-resolved.js +23 -17
  74. package/dist/src/unstable/lint/catalog/workspace/packs-dependencies-resolved.js.map +1 -1
  75. package/dist/src/unstable/lint/catalog/workspace/packs-members-retained.d.ts.map +1 -1
  76. package/dist/src/unstable/lint/catalog/workspace/packs-members-retained.js +24 -29
  77. package/dist/src/unstable/lint/catalog/workspace/packs-members-retained.js.map +1 -1
  78. package/dist/src/unstable/lint/catalog/workspace/settings-schema-valid.d.ts.map +1 -1
  79. package/dist/src/unstable/lint/catalog/workspace/settings-schema-valid.js +3 -6
  80. package/dist/src/unstable/lint/catalog/workspace/settings-schema-valid.js.map +1 -1
  81. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-correct.d.ts +3 -1
  82. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-correct.d.ts.map +1 -1
  83. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-correct.js +142 -55
  84. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-correct.js.map +1 -1
  85. package/dist/src/unstable/lint/catalog/workspace/skills-declarations-valid.d.ts +3 -2
  86. package/dist/src/unstable/lint/catalog/workspace/skills-declarations-valid.d.ts.map +1 -1
  87. package/dist/src/unstable/lint/catalog/workspace/skills-declarations-valid.js +13 -56
  88. package/dist/src/unstable/lint/catalog/workspace/skills-declarations-valid.js.map +1 -1
  89. package/dist/src/unstable/lint/catalog/workspace/skills-integrity-valid.d.ts +3 -2
  90. package/dist/src/unstable/lint/catalog/workspace/skills-integrity-valid.d.ts.map +1 -1
  91. package/dist/src/unstable/lint/catalog/workspace/skills-integrity-valid.js +71 -54
  92. package/dist/src/unstable/lint/catalog/workspace/skills-integrity-valid.js.map +1 -1
  93. package/dist/src/unstable/lint/catalog/workspace/skills-lockfile-aligned.d.ts.map +1 -1
  94. package/dist/src/unstable/lint/catalog/workspace/skills-lockfile-aligned.js +76 -150
  95. package/dist/src/unstable/lint/catalog/workspace/skills-lockfile-aligned.js.map +1 -1
  96. package/dist/src/unstable/lint/catalog/workspace/skills-managed.d.ts +15 -0
  97. package/dist/src/unstable/lint/catalog/workspace/skills-managed.d.ts.map +1 -0
  98. package/dist/src/unstable/lint/catalog/workspace/skills-managed.js +102 -0
  99. package/dist/src/unstable/lint/catalog/workspace/skills-managed.js.map +1 -0
  100. package/dist/src/unstable/lint/catalog/workspace-accessor/platform.d.ts.map +1 -1
  101. package/dist/src/unstable/lint/catalog/workspace-accessor/platform.js +2 -1
  102. package/dist/src/unstable/lint/catalog/workspace-accessor/platform.js.map +1 -1
  103. package/dist/src/unstable/lint/catalog/workspace.d.ts +9 -5
  104. package/dist/src/unstable/lint/catalog/workspace.d.ts.map +1 -1
  105. package/dist/src/unstable/lint/catalog/workspace.js +20 -11
  106. package/dist/src/unstable/lint/catalog/workspace.js.map +1 -1
  107. package/dist/src/unstable/lint/cli.d.ts +45 -27
  108. package/dist/src/unstable/lint/cli.d.ts.map +1 -1
  109. package/dist/src/unstable/lint/cli.js +819 -59
  110. package/dist/src/unstable/lint/cli.js.map +1 -1
  111. package/dist/src/unstable/lint/index.d.ts +1 -1
  112. package/dist/src/unstable/lint/index.d.ts.map +1 -1
  113. package/dist/src/unstable/lint/index.js +1 -1
  114. package/dist/src/unstable/lint/index.js.map +1 -1
  115. package/dist/src/unstable/lint/issues-to-findings.d.ts +5 -5
  116. package/dist/src/unstable/lint/issues-to-findings.js +78 -15
  117. package/dist/src/unstable/lint/issues-to-findings.js.map +1 -1
  118. package/dist/src/unstable/lint/rule.d.ts +0 -8
  119. package/dist/src/unstable/lint/rule.d.ts.map +1 -1
  120. package/dist/src/unstable/skills/operations/install.d.ts.map +1 -1
  121. package/dist/src/unstable/skills/operations/install.js +2 -0
  122. package/dist/src/unstable/skills/operations/install.js.map +1 -1
  123. package/dist/src/unstable/workspace/classifier-records.d.ts.map +1 -1
  124. package/dist/src/unstable/workspace/classifier-records.js +1 -0
  125. package/dist/src/unstable/workspace/classifier-records.js.map +1 -1
  126. package/dist/src/unstable/workspace/classifier.d.ts +13 -2
  127. package/dist/src/unstable/workspace/classifier.d.ts.map +1 -1
  128. package/dist/src/unstable/workspace/classifier.js +14 -2
  129. package/dist/src/unstable/workspace/classifier.js.map +1 -1
  130. package/dist/src/unstable/workspace/initialization.js +1 -1
  131. package/dist/src/unstable/workspace/initialization.js.map +1 -1
  132. package/dist/src/unstable/workspace/service.d.ts.map +1 -1
  133. package/dist/src/unstable/workspace/service.js +30 -9
  134. package/dist/src/unstable/workspace/service.js.map +1 -1
  135. package/dist/src/unstable/workspace/taxonomy-types.d.ts +1 -0
  136. package/dist/src/unstable/workspace/taxonomy-types.d.ts.map +1 -1
  137. package/package.json +7 -3
  138. package/site-content/{INSTALL.md → install.md} +5 -5
  139. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-clean.d.ts +0 -30
  140. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-clean.d.ts.map +0 -1
  141. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-clean.js +0 -232
  142. package/dist/src/unstable/lint/catalog/workspace/skills-artifacts-clean.js.map +0 -1
@@ -222,83 +222,844 @@ export const detectPublishGateDrift = (config) => {
222
222
  visitCatalog(packRules);
223
223
  return weakened;
224
224
  };
225
- /**
226
- * Render a finding-first human text report.
227
- *
228
- * Output shape (one line per entry, grouped first by context type then by
229
- * severity within each group):
230
- *
231
- * DRIFT: The registry will still block publish on these rules:
232
- * - skill/manifest-schema-valid
233
- *
234
- * WORKSPACE
235
- * [error] workspace/lockfile-valid axm-lock.yaml is missing. ./axm-lock.yaml
236
- * [warning] workspace/... ...
237
- *
238
- * SKILLS
239
- * [error] ...
240
- *
241
- * Summary: 2 errors, 1 warning.
242
- * Applied 3 fixes; 1 warning surfaced from applyPlan.
243
- *
244
- * @experimental This API is unstable and may change without notice.
245
- */
246
- export const renderFindingsText = (args) => {
247
- const lines = [];
248
- const { summary, fixSummary } = args;
249
- if (summary.driftBanner.length > 0) {
250
- lines.push("DRIFT: The registry will still block publish on these rules:");
251
- for (const id of summary.driftBanner) {
252
- lines.push(` - ${id}`);
225
+ const defaultHumanReporter = "grouped";
226
+ const compareRenderedFindings = (left, right) => {
227
+ const byPath = left.path.localeCompare(right.path);
228
+ if (byPath !== 0) {
229
+ return byPath;
230
+ }
231
+ const bySeverity = severityOrder(left.finding.severity) - severityOrder(right.finding.severity);
232
+ if (bySeverity !== 0) {
233
+ return bySeverity;
234
+ }
235
+ const byRuleId = left.finding.ruleId.localeCompare(right.finding.ruleId);
236
+ if (byRuleId !== 0) {
237
+ return byRuleId;
238
+ }
239
+ return left.finding.message.localeCompare(right.finding.message);
240
+ };
241
+ const pluralize = (n, singular, plural) => n === 1 ? singular : plural;
242
+ const splitSentences = (message) => {
243
+ const out = [];
244
+ let remaining = message.trim();
245
+ while (remaining.length > 0) {
246
+ const match = /^(.+?\.(?=\s+[A-Z`]))\s+(.+)$/.exec(remaining);
247
+ if (match === null) {
248
+ out.push(remaining);
249
+ break;
253
250
  }
254
- lines.push("");
255
- }
256
- const groups = [
257
- { key: "workspace", label: "WORKSPACE" },
258
- { key: "skill", label: "SKILLS" },
259
- { key: "pack", label: "PACKS" },
260
- ];
261
- for (const group of groups) {
262
- const inGroup = summary.findings.filter((f) => f.group === group.key);
263
- if (inGroup.length === 0) {
251
+ const head = match[1];
252
+ const tail = match[2];
253
+ if (head === undefined || tail === undefined) {
254
+ out.push(remaining);
255
+ break;
256
+ }
257
+ out.push(head);
258
+ remaining = tail;
259
+ }
260
+ return out;
261
+ };
262
+ const splitDetailClause = (message) => {
263
+ const marker = " Detail: ";
264
+ const index = message.indexOf(marker);
265
+ if (index === -1) {
266
+ return {
267
+ lead: message,
268
+ detail: undefined,
269
+ trailing: [],
270
+ };
271
+ }
272
+ const lead = message.slice(0, index);
273
+ const rest = message.slice(index + marker.length);
274
+ const sentences = splitSentences(rest);
275
+ const detail = sentences[0];
276
+ return {
277
+ lead,
278
+ detail,
279
+ trailing: sentences.slice(1),
280
+ };
281
+ };
282
+ const parseFindingMessage = (message) => {
283
+ const detailSplit = splitDetailClause(message);
284
+ const leadSentences = splitSentences(detailSplit.lead);
285
+ const title = leadSentences[0] ?? message.trim();
286
+ const details = detailSplit.detail === undefined ? [] : [detailSplit.detail];
287
+ return {
288
+ title,
289
+ details,
290
+ helps: [...leadSentences.slice(1), ...detailSplit.trailing],
291
+ };
292
+ };
293
+ const dirnamePosix = (path) => {
294
+ if (path === "." || path === "..") {
295
+ return path;
296
+ }
297
+ const index = path.lastIndexOf("/");
298
+ if (index <= 0) {
299
+ return path;
300
+ }
301
+ return path.slice(0, index);
302
+ };
303
+ const groupDisplayPath = (entry) => {
304
+ switch (entry.finding.ruleId) {
305
+ case "workspace/skills-managed":
306
+ return dirnamePosix(entry.path);
307
+ default:
308
+ return entry.path;
309
+ }
310
+ };
311
+ const bucketForFinding = (entry, parsed) => {
312
+ switch (entry.finding.ruleId) {
313
+ case "workspace/lockfile-valid":
314
+ if (parsed.title.startsWith("Lockfile is missing required field `")) {
315
+ return "missing-required-field";
316
+ }
317
+ if (parsed.title.startsWith("The lockfile is not valid YAML.")) {
318
+ return "invalid-yaml";
319
+ }
320
+ return "validation";
321
+ case "workspace/skills-managed":
322
+ if (parsed.title.includes("but it is not managed by this workspace.")) {
323
+ return "unmanaged";
324
+ }
325
+ return "managed";
326
+ case "workspace/skills-artifacts-correct":
327
+ return "artifact-state";
328
+ case "workspace/skills-lockfile-aligned":
329
+ if (parsed.title.includes("missing from the lockfile.")) {
330
+ return "missing";
331
+ }
332
+ if (parsed.title.includes("listed in the lockfile but not in settings.skills.")) {
333
+ return "orphan";
334
+ }
335
+ if (parsed.title.includes("lockfile version does not match the declared version.")) {
336
+ return "version";
337
+ }
338
+ return "alignment";
339
+ case "workspace/skills-integrity-valid":
340
+ return "integrity";
341
+ default:
342
+ return entry.finding.ruleId;
343
+ }
344
+ };
345
+ const parseHumanFinding = (entry) => {
346
+ const parsed = parseFindingMessage(entry.finding.message);
347
+ return {
348
+ path: groupDisplayPath(entry),
349
+ bucket: bucketForFinding(entry, parsed),
350
+ severity: entry.finding.severity,
351
+ ruleId: entry.finding.ruleId,
352
+ title: parsed.title,
353
+ details: parsed.details,
354
+ helps: parsed.helps,
355
+ fixable: entry.finding.kind === "autofixable",
356
+ };
357
+ };
358
+ const matchSingleQuoted = (message) => {
359
+ const match = /'([^']+)'/.exec(message);
360
+ return match?.[1];
361
+ };
362
+ const uniqueStrings = (values) => {
363
+ const out = [];
364
+ const seen = new Set();
365
+ for (const value of values) {
366
+ if (seen.has(value)) {
264
367
  continue;
265
368
  }
266
- const sorted = [...inGroup].sort((a, b) => severityOrder(a.finding.severity) - severityOrder(b.finding.severity));
267
- lines.push(group.label);
268
- for (const entry of sorted) {
269
- lines.push(` [${entry.finding.severity}] ${entry.finding.ruleId} ${entry.finding.message} ${entry.path}`);
369
+ seen.add(value);
370
+ out.push(value);
371
+ }
372
+ return out;
373
+ };
374
+ const sortStrings = (values) => [...values].sort();
375
+ const uniquePaths = (findings) => uniqueStrings(findings.map((finding) => finding.path));
376
+ const mergedRuleHelps = (findings, autofixHelp) => {
377
+ if (findings.every((finding) => finding.fixable)) {
378
+ return [autofixHelp];
379
+ }
380
+ const helps = uniqueStrings(findings.flatMap((finding) => finding.helps));
381
+ return helps.length > 0
382
+ ? helps
383
+ : findings.some((finding) => finding.fixable)
384
+ ? [autofixHelp]
385
+ : [];
386
+ };
387
+ const summarizeSkillByDetail = (finding) => {
388
+ const name = matchSingleQuoted(finding.title);
389
+ const detail = finding.details[0];
390
+ if (name === undefined) {
391
+ return detail ?? finding.title;
392
+ }
393
+ if (detail === undefined) {
394
+ return name;
395
+ }
396
+ return `${name}: ${detail}`;
397
+ };
398
+ const compressDetails = (details, limit = 10) => {
399
+ if (details.length <= limit) {
400
+ return details;
401
+ }
402
+ const remaining = details.length - limit;
403
+ return [...details.slice(0, limit), `... and ${remaining} more`];
404
+ };
405
+ const previewList = (values, limit = 3) => {
406
+ if (values.length === 0) {
407
+ return "";
408
+ }
409
+ if (values.length <= limit) {
410
+ return values.join(", ");
411
+ }
412
+ const remaining = values.length - limit;
413
+ return `${values.slice(0, limit).join(", ")}, ... and ${remaining} more`;
414
+ };
415
+ const groupFindingsByPath = (findings, extract) => {
416
+ const grouped = new Map();
417
+ for (const finding of findings) {
418
+ const current = grouped.get(finding.path);
419
+ const value = extract(finding);
420
+ if (current === undefined) {
421
+ grouped.set(finding.path, [value]);
422
+ }
423
+ else {
424
+ current.push(value);
425
+ }
426
+ }
427
+ return [...grouped.entries()].sort(([left], [right]) => left.localeCompare(right));
428
+ };
429
+ const coalesceFullDiagnostic = (findings) => {
430
+ const [first] = findings;
431
+ if (first === undefined) {
432
+ return {
433
+ severity: "info",
434
+ ruleId: "",
435
+ title: "",
436
+ details: [],
437
+ helps: [],
438
+ fixable: false,
439
+ paths: [],
440
+ };
441
+ }
442
+ if (findings.length === 1) {
443
+ return {
444
+ severity: first.severity,
445
+ ruleId: first.ruleId,
446
+ title: first.title,
447
+ details: first.details,
448
+ helps: first.helps,
449
+ fixable: first.fixable,
450
+ paths: [first.path],
451
+ };
452
+ }
453
+ const allHelps = uniqueStrings(findings.flatMap((finding) => finding.helps));
454
+ const paths = uniquePaths(findings);
455
+ switch (`${first.ruleId}:${first.bucket}`) {
456
+ case "workspace/lockfile-valid:missing-required-field": {
457
+ const fields = findings.flatMap((finding) => {
458
+ const match = /Lockfile is missing required field `([^`]+)`\./.exec(finding.title);
459
+ return match?.[1] === undefined ? [] : [match[1]];
460
+ });
461
+ return {
462
+ severity: first.severity,
463
+ ruleId: first.ruleId,
464
+ title: "Lockfile is missing required fields.",
465
+ details: compressDetails(fields),
466
+ helps: [
467
+ "Regenerate `.axm/axm-lock.yaml` from `.axm/settings.json` by reinstalling the declared extensions.",
468
+ ],
469
+ fixable: false,
470
+ paths,
471
+ };
472
+ }
473
+ case "workspace/skills-managed:unmanaged": {
474
+ const names = findings.flatMap((finding) => {
475
+ const name = matchSingleQuoted(finding.title);
476
+ return name === undefined ? [] : [name];
477
+ });
478
+ return {
479
+ severity: first.severity,
480
+ ruleId: first.ruleId,
481
+ title: `${names.length} ${pluralize(names.length, "skill is", "skills are")} present here but not managed by this workspace.`,
482
+ details: compressDetails(names),
483
+ helps: [
484
+ "To keep them: run `axm skills install <source>` for each skill you want axm to manage.",
485
+ "To remove them: run `axm prune` or `axm skills prune <name>`.",
486
+ ],
487
+ fixable: false,
488
+ paths,
489
+ };
490
+ }
491
+ case "workspace/skills-artifacts-correct:enabled-missing":
492
+ case "workspace/skills-artifacts-correct:disabled-present":
493
+ case "workspace/skills-artifacts-correct:inconsistent":
494
+ case "workspace/skills-artifacts-correct:artifact-state":
495
+ return {
496
+ severity: first.severity,
497
+ ruleId: first.ruleId,
498
+ title: `${findings.length} ${pluralize(findings.length, "skill is", "skills are")} inconsistent across the declared agents.`,
499
+ details: compressDetails(findings.map(summarizeSkillByDetail)),
500
+ helps: mergedRuleHelps(findings, "Run `axm lint --fix` to reconcile the declared agent artifacts."),
501
+ fixable: findings.some((finding) => finding.fixable),
502
+ paths,
503
+ };
504
+ case "workspace/skills-lockfile-aligned:missing":
505
+ case "workspace/skills-lockfile-aligned:orphan":
506
+ case "workspace/skills-lockfile-aligned:version":
507
+ case "workspace/skills-lockfile-aligned:alignment":
508
+ return {
509
+ severity: first.severity,
510
+ ruleId: first.ruleId,
511
+ title: "Skill declarations and lockfile entries are out of sync.",
512
+ details: compressDetails(findings.map(summarizeSkillByDetail)),
513
+ helps: ["Run `axm lint --fix` to reconcile settings.skills with the lockfile."],
514
+ fixable: true,
515
+ paths,
516
+ };
517
+ case "workspace/skills-integrity-valid:integrity":
518
+ return {
519
+ severity: first.severity,
520
+ ruleId: first.ruleId,
521
+ title: "Installed skill sources do not match their lockfile entries.",
522
+ details: compressDetails(findings.map(summarizeSkillByDetail)),
523
+ helps: mergedRuleHelps(findings, "Run `axm lint --fix` to reinstall the affected skills."),
524
+ fixable: findings.some((finding) => finding.fixable),
525
+ paths,
526
+ };
527
+ default:
528
+ return {
529
+ severity: first.severity,
530
+ ruleId: first.ruleId,
531
+ title: `${findings.length} findings reported for this rule.`,
532
+ details: compressDetails(findings.map((finding) => finding.details.length === 0
533
+ ? finding.title
534
+ : `${finding.title}: ${finding.details.join("; ")}`)),
535
+ helps: allHelps,
536
+ fixable: findings.some((finding) => finding.fixable),
537
+ paths,
538
+ };
539
+ }
540
+ };
541
+ const coalesceGroupedDiagnostic = (findings) => {
542
+ const [first] = findings;
543
+ if (first === undefined) {
544
+ return {
545
+ severity: "info",
546
+ ruleId: "",
547
+ title: "",
548
+ details: [],
549
+ helps: [],
550
+ fixable: false,
551
+ paths: [],
552
+ };
553
+ }
554
+ if (findings.length === 1) {
555
+ return {
556
+ severity: first.severity,
557
+ ruleId: first.ruleId,
558
+ title: first.title,
559
+ details: first.details,
560
+ helps: first.helps,
561
+ fixable: first.fixable,
562
+ paths: [first.path],
563
+ };
564
+ }
565
+ const paths = uniquePaths(findings);
566
+ const allHelps = uniqueStrings(findings.flatMap((finding) => finding.helps));
567
+ switch (`${first.ruleId}:${first.bucket}`) {
568
+ case "workspace/lockfile-valid:missing-required-field": {
569
+ const fields = sortStrings(uniqueStrings(findings.flatMap((finding) => {
570
+ const match = /Lockfile is missing required field `([^`]+)`\./.exec(finding.title);
571
+ return match?.[1] === undefined ? [] : [match[1]];
572
+ })));
573
+ return {
574
+ severity: first.severity,
575
+ ruleId: first.ruleId,
576
+ title: "Lockfile is missing fields required by the current schema.",
577
+ details: [`Missing fields include: ${previewList(fields, 4)}`],
578
+ helps: [
579
+ "Fix: Regenerate `.axm/axm-lock.yaml` from `.axm/settings.json` by reinstalling the declared extensions.",
580
+ ],
581
+ fixable: false,
582
+ paths,
583
+ };
584
+ }
585
+ case "workspace/skills-managed:unmanaged": {
586
+ const perPath = groupFindingsByPath(findings, (finding) => matchSingleQuoted(finding.title) ?? finding.title);
587
+ return {
588
+ severity: first.severity,
589
+ ruleId: first.ruleId,
590
+ title: `Unmanaged skills are present in ${paths.length} ${pluralize(paths.length, "skill directory", "skill directories")}.`,
591
+ details: compressDetails(perPath.map(([path, names]) => {
592
+ const sorted = sortStrings(uniqueStrings(names));
593
+ return `${path}: ${sorted.length} unmanaged ${pluralize(sorted.length, "skill", "skills")} (${previewList(sorted, 3)})`;
594
+ }), 8),
595
+ helps: [
596
+ "To keep them: run `axm skills install <source>` for each skill you want axm to manage.",
597
+ "To remove them: run `axm prune` or `axm skills prune <name>`.",
598
+ ],
599
+ fixable: false,
600
+ paths,
601
+ };
270
602
  }
271
- lines.push("");
603
+ case "workspace/skills-artifacts-correct:enabled-missing":
604
+ case "workspace/skills-artifacts-correct:disabled-present":
605
+ case "workspace/skills-artifacts-correct:inconsistent":
606
+ case "workspace/skills-artifacts-correct:artifact-state":
607
+ return {
608
+ severity: first.severity,
609
+ ruleId: first.ruleId,
610
+ title: `${findings.length} ${pluralize(findings.length, "skill is", "skills are")} inconsistent across the declared agents.`,
611
+ details: compressDetails(findings.map(summarizeSkillByDetail)),
612
+ helps: mergedRuleHelps(findings, "Run `axm lint --fix` to reconcile the declared agent artifacts."),
613
+ fixable: findings.some((finding) => finding.fixable),
614
+ paths,
615
+ };
616
+ case "workspace/skills-lockfile-aligned:missing":
617
+ case "workspace/skills-lockfile-aligned:orphan":
618
+ case "workspace/skills-lockfile-aligned:version":
619
+ case "workspace/skills-lockfile-aligned:alignment":
620
+ return {
621
+ severity: first.severity,
622
+ ruleId: first.ruleId,
623
+ title: "`settings.skills` and the lockfile are out of sync.",
624
+ details: compressDetails(findings.map(summarizeSkillByDetail)),
625
+ helps: ["Run `axm lint --fix` to reconcile `settings.skills` with the lockfile."],
626
+ fixable: true,
627
+ paths,
628
+ };
629
+ case "workspace/skills-integrity-valid:integrity":
630
+ return {
631
+ severity: first.severity,
632
+ ruleId: first.ruleId,
633
+ title: "Installed skill sources do not match their lockfile entries.",
634
+ details: compressDetails(findings.map(summarizeSkillByDetail)),
635
+ helps: mergedRuleHelps(findings, "Run `axm lint --fix` to reinstall the affected skills."),
636
+ fixable: findings.some((finding) => finding.fixable),
637
+ paths,
638
+ };
639
+ default:
640
+ return {
641
+ severity: first.severity,
642
+ ruleId: first.ruleId,
643
+ title: findings.length === 1
644
+ ? first.title
645
+ : `${findings.length} related findings were reported.`,
646
+ details: compressDetails(findings.map((finding) => {
647
+ const detail = finding.details.length === 0
648
+ ? finding.title
649
+ : `${finding.title}: ${finding.details.join("; ")}`;
650
+ return paths.length === 1 ? detail : `${finding.path}: ${detail}`;
651
+ })),
652
+ helps: allHelps,
653
+ fixable: findings.some((finding) => finding.fixable),
654
+ paths,
655
+ };
656
+ }
657
+ };
658
+ const joinList = (values) => {
659
+ if (values.length === 0) {
660
+ return "";
661
+ }
662
+ if (values.length === 1) {
663
+ return values[0] ?? "";
272
664
  }
273
- if (summary.findings.length === 0 && summary.driftBanner.length === 0) {
274
- lines.push("No findings.");
665
+ if (values.length === 2) {
666
+ const first = values[0] ?? "";
667
+ const second = values[1] ?? "";
668
+ return `${first} and ${second}`;
669
+ }
670
+ const head = values.slice(0, -1).join(", ");
671
+ const tail = values[values.length - 1] ?? "";
672
+ return `${head}, and ${tail}`;
673
+ };
674
+ const formatFullOverviewSentence = (args) => {
675
+ const parts = [];
676
+ if (args.counts.errors > 0) {
677
+ parts.push(`${args.counts.errors} ${pluralize(args.counts.errors, "error", "errors")}`);
678
+ }
679
+ if (args.counts.warnings > 0) {
680
+ parts.push(`${args.counts.warnings} ${pluralize(args.counts.warnings, "warning", "warnings")}`);
681
+ }
682
+ if (args.counts.infos > 0) {
683
+ parts.push(`${args.counts.infos} ${pluralize(args.counts.infos, "info", "infos")}`);
684
+ }
685
+ const locations = `${args.locationCount} ${pluralize(args.locationCount, "location", "locations")}`;
686
+ const base = `Found ${joinList(parts)} in ${locations}.`;
687
+ if (args.fixableCount === 0) {
688
+ return base;
689
+ }
690
+ return `${base} ${args.fixableCount} ${pluralize(args.fixableCount, "finding can", "findings can")} be auto-fixed.`;
691
+ };
692
+ const formatGroupedOverviewSentence = (args) => {
693
+ const parts = [`${args.diagnosticCount} ${pluralize(args.diagnosticCount, "issue", "issues")}.`];
694
+ if (args.fixableCount > 0) {
695
+ parts.push(`${args.fixableCount} ${pluralize(args.fixableCount, "can", "can")} be fixed automatically.`);
696
+ }
697
+ const manualCount = args.diagnosticCount - args.fixableCount;
698
+ if (manualCount > 0) {
699
+ parts.push(`${manualCount} ${pluralize(manualCount, "needs", "need")} manual attention.`);
700
+ }
701
+ return parts.join(" ");
702
+ };
703
+ const buildFullDiagnostics = (parsed) => {
704
+ const pathOrder = [];
705
+ const byPath = new Map();
706
+ for (const entry of parsed) {
707
+ const current = byPath.get(entry.path);
708
+ if (current === undefined) {
709
+ byPath.set(entry.path, [entry]);
710
+ pathOrder.push(entry.path);
711
+ }
712
+ else {
713
+ current.push(entry);
714
+ }
715
+ }
716
+ const blocks = [];
717
+ pathOrder.forEach((path, index) => {
718
+ const pathEntries = byPath.get(path);
719
+ if (pathEntries === undefined) {
720
+ return;
721
+ }
722
+ const groups = new Map();
723
+ const groupOrder = [];
724
+ for (const entry of pathEntries) {
725
+ const key = `${severityOrder(entry.severity)}:${entry.ruleId}:${entry.bucket}`;
726
+ const current = groups.get(key);
727
+ if (current === undefined) {
728
+ groups.set(key, [entry]);
729
+ groupOrder.push(key);
730
+ }
731
+ else {
732
+ current.push(entry);
733
+ }
734
+ }
735
+ blocks.push({
736
+ kind: "pathGroup",
737
+ path,
738
+ diagnostics: groupOrder.flatMap((key) => {
739
+ const grouped = groups.get(key);
740
+ return grouped === undefined ? [] : [coalesceFullDiagnostic(grouped)];
741
+ }),
742
+ });
743
+ if (index < pathOrder.length - 1) {
744
+ blocks.push({ kind: "blank" });
745
+ }
746
+ });
747
+ return blocks;
748
+ };
749
+ const groupedBucketKey = (entry) => {
750
+ switch (entry.ruleId) {
751
+ case "workspace/skills-managed":
752
+ return `${entry.ruleId}:${entry.bucket}`;
753
+ default:
754
+ return `${entry.path}:${entry.ruleId}:${entry.bucket}`;
755
+ }
756
+ };
757
+ const buildGroupedDiagnostics = (parsed) => {
758
+ const groups = new Map();
759
+ const order = [];
760
+ for (const entry of parsed) {
761
+ const key = groupedBucketKey(entry);
762
+ const current = groups.get(key);
763
+ if (current === undefined) {
764
+ groups.set(key, [entry]);
765
+ order.push(key);
766
+ }
767
+ else {
768
+ current.push(entry);
769
+ }
770
+ }
771
+ return order.flatMap((key) => {
772
+ const grouped = groups.get(key);
773
+ return grouped === undefined ? [] : [coalesceGroupedDiagnostic(grouped)];
774
+ });
775
+ };
776
+ const appendDiagnosticSection = (blocks, title, diagnostics, note) => {
777
+ if (diagnostics.length === 0) {
778
+ return;
779
+ }
780
+ if (blocks.length > 0) {
781
+ blocks.push({ kind: "blank" });
782
+ }
783
+ blocks.push(note === undefined ? { kind: "section", title } : { kind: "section", title, note });
784
+ diagnostics.forEach((diagnostic, index) => {
785
+ blocks.push({ kind: "diagnostic", diagnostic });
786
+ if (index < diagnostics.length - 1) {
787
+ blocks.push({ kind: "blank" });
788
+ }
789
+ });
790
+ };
791
+ const stripFixHelps = (diagnostic) => {
792
+ const filtered = diagnostic.helps.filter((h) => !h.includes("axm lint --fix"));
793
+ return filtered.length === diagnostic.helps.length
794
+ ? diagnostic
795
+ : { ...diagnostic, helps: filtered };
796
+ };
797
+ const buildSectionedDiagnostics = (args) => {
798
+ const fixable = args.diagnostics.filter((diagnostic) => diagnostic.fixable);
799
+ const manual = args.diagnostics.filter((diagnostic) => diagnostic.severity === "error" && !diagnostic.fixable);
800
+ const warnings = args.diagnostics.filter((diagnostic) => diagnostic.severity === "warning");
801
+ const infos = args.diagnostics.filter((diagnostic) => diagnostic.severity === "info");
802
+ appendDiagnosticSection(args.blocks, "Auto-fixable", fixable.map(stripFixHelps), "run `axm lint --fix`");
803
+ appendDiagnosticSection(args.blocks, "Requires manual attention", manual);
804
+ appendDiagnosticSection(args.blocks, "Warnings", warnings);
805
+ appendDiagnosticSection(args.blocks, "Information", infos);
806
+ };
807
+ const makeSummaryDiagnostic = (diagnostic) => ({
808
+ ...diagnostic,
809
+ details: diagnostic.paths.length === 1
810
+ ? ["1 affected location"]
811
+ : [
812
+ `${diagnostic.paths.length} affected ${pluralize(diagnostic.paths.length, "location", "locations")}`,
813
+ ],
814
+ helps: [],
815
+ });
816
+ const toFullLintHumanBlocks = (args) => {
817
+ const blocks = [];
818
+ const { summary, fixSummary } = args;
819
+ if (summary.findings.length === 0) {
820
+ blocks.push(summary.driftBanner.length === 0
821
+ ? { kind: "empty", message: "No findings." }
822
+ : { kind: "empty", message: "No local findings." });
275
823
  }
276
824
  else {
277
- lines.push(formatCountsSentence(summary.counts));
825
+ const parsed = [...summary.findings].sort(compareRenderedFindings).map(parseHumanFinding);
826
+ const locationCount = uniqueStrings(parsed.map((finding) => finding.path)).length;
827
+ const fixableCount = summary.findings.filter((finding) => finding.finding.kind === "autofixable").length;
828
+ blocks.push({
829
+ kind: "overview",
830
+ message: formatFullOverviewSentence({
831
+ counts: summary.counts,
832
+ locationCount,
833
+ fixableCount,
834
+ }),
835
+ counts: summary.counts,
836
+ notes: fixableCount > 0 && fixSummary === undefined
837
+ ? ["Next step: Run `axm lint --fix` for the auto-fixable findings."]
838
+ : [],
839
+ });
840
+ if (summary.driftBanner.length > 0) {
841
+ blocks.push({ kind: "blank" });
842
+ blocks.push({
843
+ kind: "driftBanner",
844
+ title: "The registry will still block publish on these rules:",
845
+ ruleIds: summary.driftBanner,
846
+ });
847
+ }
848
+ const diagnostics = buildFullDiagnostics(parsed);
849
+ if (diagnostics.length > 0) {
850
+ blocks.push({ kind: "blank" });
851
+ blocks.push(...diagnostics);
852
+ }
853
+ }
854
+ if (summary.findings.length === 0 && summary.driftBanner.length > 0) {
855
+ blocks.push({ kind: "blank" });
856
+ blocks.push({
857
+ kind: "driftBanner",
858
+ title: "The registry will still block publish on these rules:",
859
+ ruleIds: summary.driftBanner,
860
+ });
278
861
  }
279
862
  if (fixSummary !== undefined) {
280
- lines.push(formatFixSummary(fixSummary));
281
- for (const warning of fixSummary.warnings) {
282
- lines.push(` warning: ${warning}`);
863
+ blocks.push({ kind: "blank" });
864
+ blocks.push({
865
+ kind: "fixSummary",
866
+ message: formatFixSummary(fixSummary),
867
+ summary: fixSummary,
868
+ });
869
+ }
870
+ return blocks;
871
+ };
872
+ const toGroupedLintHumanBlocks = (args) => {
873
+ const blocks = [];
874
+ const { summary, fixSummary } = args;
875
+ if (summary.findings.length === 0) {
876
+ blocks.push(summary.driftBanner.length === 0
877
+ ? { kind: "empty", message: "No findings." }
878
+ : { kind: "empty", message: "No local findings." });
879
+ }
880
+ else {
881
+ const parsed = [...summary.findings].sort(compareRenderedFindings).map(parseHumanFinding);
882
+ const diagnostics = buildGroupedDiagnostics(parsed);
883
+ const fixableCount = diagnostics.filter((diagnostic) => diagnostic.fixable).length;
884
+ blocks.push({
885
+ kind: "overview",
886
+ message: formatGroupedOverviewSentence({
887
+ diagnosticCount: diagnostics.length,
888
+ fixableCount,
889
+ }),
890
+ counts: summary.counts,
891
+ notes: [],
892
+ });
893
+ if (summary.driftBanner.length > 0) {
894
+ blocks.push({ kind: "blank" });
895
+ blocks.push({
896
+ kind: "driftBanner",
897
+ title: "The registry will still block publish on these rules:",
898
+ ruleIds: summary.driftBanner,
899
+ });
283
900
  }
901
+ buildSectionedDiagnostics({ blocks, diagnostics });
284
902
  }
285
- return lines;
903
+ if (summary.findings.length === 0 && summary.driftBanner.length > 0) {
904
+ blocks.push({ kind: "blank" });
905
+ blocks.push({
906
+ kind: "driftBanner",
907
+ title: "The registry will still block publish on these rules:",
908
+ ruleIds: summary.driftBanner,
909
+ });
910
+ }
911
+ if (fixSummary !== undefined) {
912
+ blocks.push({ kind: "blank" });
913
+ blocks.push({
914
+ kind: "fixSummary",
915
+ message: formatFixSummary(fixSummary),
916
+ summary: fixSummary,
917
+ });
918
+ }
919
+ if (summary.findings.length > 0) {
920
+ blocks.push({ kind: "blank" });
921
+ blocks.push({
922
+ kind: "footer",
923
+ message: "More output: `axm lint --details` | `axm lint --json`",
924
+ });
925
+ }
926
+ return blocks;
286
927
  };
287
- const formatCountsSentence = (counts) => {
288
- const parts = [];
289
- parts.push(`${counts.errors} ${pluralize(counts.errors, "error", "errors")}`);
290
- parts.push(`${counts.warnings} ${pluralize(counts.warnings, "warning", "warnings")}`);
291
- if (counts.infos > 0) {
292
- parts.push(`${counts.infos} ${pluralize(counts.infos, "info", "infos")}`);
928
+ const toSummaryLintHumanBlocks = (args) => {
929
+ const blocks = [];
930
+ const { summary, fixSummary } = args;
931
+ if (summary.findings.length === 0) {
932
+ blocks.push(summary.driftBanner.length === 0
933
+ ? { kind: "empty", message: "No findings." }
934
+ : { kind: "empty", message: "No local findings." });
935
+ }
936
+ else {
937
+ const parsed = [...summary.findings].sort(compareRenderedFindings).map(parseHumanFinding);
938
+ const diagnostics = buildGroupedDiagnostics(parsed).map(makeSummaryDiagnostic);
939
+ const fixableCount = diagnostics.filter((diagnostic) => diagnostic.fixable).length;
940
+ blocks.push({
941
+ kind: "overview",
942
+ message: formatGroupedOverviewSentence({
943
+ diagnosticCount: diagnostics.length,
944
+ fixableCount,
945
+ }),
946
+ counts: summary.counts,
947
+ notes: fixableCount > 0 && fixSummary === undefined
948
+ ? ["Run `axm lint --fix` to apply available fixes."]
949
+ : [],
950
+ });
951
+ if (summary.driftBanner.length > 0) {
952
+ blocks.push({ kind: "blank" });
953
+ blocks.push({
954
+ kind: "driftBanner",
955
+ title: "The registry will still block publish on these rules:",
956
+ ruleIds: summary.driftBanner,
957
+ });
958
+ }
959
+ buildSectionedDiagnostics({ blocks, diagnostics });
960
+ }
961
+ if (summary.findings.length === 0 && summary.driftBanner.length > 0) {
962
+ blocks.push({ kind: "blank" });
963
+ blocks.push({
964
+ kind: "driftBanner",
965
+ title: "The registry will still block publish on these rules:",
966
+ ruleIds: summary.driftBanner,
967
+ });
293
968
  }
294
- return `Summary: ${parts.join(", ")}.`;
969
+ if (fixSummary !== undefined) {
970
+ blocks.push({ kind: "blank" });
971
+ blocks.push({
972
+ kind: "fixSummary",
973
+ message: formatFixSummary(fixSummary),
974
+ summary: fixSummary,
975
+ });
976
+ }
977
+ return blocks;
978
+ };
979
+ export const toLintHumanBlocks = (args) => {
980
+ switch (args.reporter ?? defaultHumanReporter) {
981
+ case "full":
982
+ return toFullLintHumanBlocks(args);
983
+ case "summary":
984
+ return toSummaryLintHumanBlocks(args);
985
+ case "grouped":
986
+ return toGroupedLintHumanBlocks(args);
987
+ }
988
+ };
989
+ export const renderFindingsText = (args) => {
990
+ const lines = [];
991
+ for (const block of toLintHumanBlocks(args)) {
992
+ switch (block.kind) {
993
+ case "overview":
994
+ lines.push(block.message);
995
+ for (const note of block.notes) {
996
+ lines.push(note);
997
+ }
998
+ break;
999
+ case "driftBanner":
1000
+ lines.push(`DRIFT: ${block.title}`);
1001
+ for (const id of block.ruleIds) {
1002
+ lines.push(` - ${id}`);
1003
+ }
1004
+ break;
1005
+ case "section": {
1006
+ const label = block.note !== undefined ? `${block.title} (${block.note})` : block.title;
1007
+ lines.push(label);
1008
+ break;
1009
+ }
1010
+ case "diagnostic": {
1011
+ const location = block.diagnostic.paths.length === 1
1012
+ ? (block.diagnostic.paths[0] ?? "")
1013
+ : block.diagnostic.paths.length > 1
1014
+ ? `(${block.diagnostic.paths.length} locations)`
1015
+ : "(workspace)";
1016
+ lines.push(` [${block.diagnostic.severity}] ${location}`);
1017
+ lines.push(` rule: ${block.diagnostic.ruleId}${block.diagnostic.fixable ? " (auto-fixable)" : ""}`);
1018
+ lines.push(` ${block.diagnostic.title}`);
1019
+ for (const detail of block.diagnostic.details) {
1020
+ lines.push(` - ${detail}`);
1021
+ }
1022
+ for (const help of block.diagnostic.helps) {
1023
+ lines.push(` ${help}`);
1024
+ }
1025
+ break;
1026
+ }
1027
+ case "pathGroup":
1028
+ lines.push(block.path);
1029
+ for (const diagnostic of block.diagnostics) {
1030
+ lines.push(` [${diagnostic.severity}] ${diagnostic.ruleId}${diagnostic.fixable ? " (auto-fixable)" : ""}: ${diagnostic.title}`);
1031
+ for (const detail of diagnostic.details) {
1032
+ lines.push(` - ${detail}`);
1033
+ }
1034
+ for (const help of diagnostic.helps) {
1035
+ lines.push(` ${help}`);
1036
+ }
1037
+ }
1038
+ break;
1039
+ case "blank":
1040
+ lines.push("");
1041
+ break;
1042
+ case "empty":
1043
+ lines.push(block.message);
1044
+ break;
1045
+ case "footer":
1046
+ lines.push(block.message);
1047
+ break;
1048
+ case "fixSummary":
1049
+ lines.push(block.message);
1050
+ for (const warning of block.summary.warnings) {
1051
+ lines.push(` warning: ${warning}`);
1052
+ }
1053
+ break;
1054
+ }
1055
+ }
1056
+ return lines;
295
1057
  };
296
1058
  const formatFixSummary = (fix) => {
297
1059
  const appliedLabel = pluralize(fix.applied, "fix", "fixes");
298
1060
  const warningsLabel = pluralize(fix.warnings.length, "warning", "warnings");
299
1061
  return `Applied ${fix.applied} ${appliedLabel}; ${fix.warnings.length} ${warningsLabel}, ${fix.failed} failed.`;
300
1062
  };
301
- const pluralize = (n, singular, plural) => n === 1 ? singular : plural;
302
1063
  const toJsonFinding = (entry) => {
303
1064
  const base = {
304
1065
  group: entry.group,
@@ -308,7 +1069,6 @@ const toJsonFinding = (entry) => {
308
1069
  message: entry.finding.message,
309
1070
  displayRoot: entry.displayRoot,
310
1071
  path: entry.path,
311
- suggestions: [...entry.finding.suggestions],
312
1072
  };
313
1073
  if (entry.finding.location === undefined) {
314
1074
  return base;