@skill-map/cli 0.39.0 → 0.40.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/dist/cli/tutorial/sm-master/references/tour-authoring.md +0 -4
- package/dist/cli/tutorial/sm-tutorial/SKILL.md +279 -26
- package/dist/cli.js +732 -549
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +28 -13
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +44 -34
- package/dist/kernel/index.js +28 -13
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/{chunk-VZIYRREA.js → chunk-2W6BCESO.js} +30 -29
- package/dist/ui/{chunk-NHI5UPNK.js → chunk-3FPS5OIY.js} +1 -1
- package/dist/ui/chunk-4KQGLZV2.js +123 -0
- package/dist/ui/chunk-ASCTZSBZ.js +107 -0
- package/dist/ui/chunk-GSTYLDCL.js +135 -0
- package/dist/ui/chunk-MHEKCBIR.js +317 -0
- package/dist/ui/chunk-PFA2CVPN.js +61 -0
- package/dist/ui/chunk-PURF7B6R.js +411 -0
- package/dist/ui/chunk-UFCOGNZX.js +1 -0
- package/dist/ui/chunk-WQ42JTZB.js +90 -0
- package/dist/ui/chunk-XEWWXGNE.js +1 -0
- package/dist/ui/chunk-YXFKPYK3.js +965 -0
- package/dist/ui/index.html +1 -1
- package/dist/ui/main-WLCA4YLM.js +3 -0
- package/package.json +2 -2
- package/dist/ui/chunk-AALYQ3RG.js +0 -965
- package/dist/ui/chunk-JECPBFFX.js +0 -135
- package/dist/ui/chunk-LGLLRAJ6.js +0 -61
- package/dist/ui/chunk-PVVKVGJ6.js +0 -500
- package/dist/ui/chunk-SKA7ZFUX.js +0 -1
- package/dist/ui/chunk-VDMXHCXR.js +0 -1
- package/dist/ui/chunk-XRDZZC5F.js +0 -123
- package/dist/ui/chunk-YPO2DTMO.js +0 -317
- package/dist/ui/chunk-ZYIKNMFV.js +0 -107
- package/dist/ui/main-TYWMNAII.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -441,7 +441,6 @@ var claudeProvider = {
|
|
|
441
441
|
id: "claude",
|
|
442
442
|
pluginId: CLAUDE_PLUGIN_ID,
|
|
443
443
|
kind: "provider",
|
|
444
|
-
version: "1.0.0",
|
|
445
444
|
description: "Classifies files under `.claude/{agents,commands,skills}` as Claude Code agents, commands, and skills.",
|
|
446
445
|
// Vendor provider: Claude Code only reads its own `.claude/` territory
|
|
447
446
|
// and ignores `.codex/` / Antigravity layouts at runtime. Gating the
|
|
@@ -726,7 +725,6 @@ var atDirectiveExtractor = {
|
|
|
726
725
|
id: ID,
|
|
727
726
|
pluginId: CLAUDE_PLUGIN_ID,
|
|
728
727
|
kind: "extractor",
|
|
729
|
-
version: "1.0.0",
|
|
730
728
|
description: "Detects `@<token>` directives in a node's body using Claude Code rules. A bare handle (e.g. `@team`) becomes a `mentions` link; a file-flavoured token (e.g. `@docs/api.md`, `@./readme.md`) becomes a `references` link.",
|
|
731
729
|
scope: "body",
|
|
732
730
|
precondition: { provider: ["claude"] },
|
|
@@ -815,7 +813,6 @@ var slashCommandExtractor = {
|
|
|
815
813
|
id: ID2,
|
|
816
814
|
pluginId: CLAUDE_PLUGIN_ID,
|
|
817
815
|
kind: "extractor",
|
|
818
|
-
version: "1.0.0",
|
|
819
816
|
description: "Turns `/command` invocations in a node's body into arrows that point at the resolved slash command or skill, using Claude Code routing rules.",
|
|
820
817
|
scope: "body",
|
|
821
818
|
precondition: { provider: ["claude"] },
|
|
@@ -865,7 +862,6 @@ var antigravityProvider = {
|
|
|
865
862
|
id: "antigravity",
|
|
866
863
|
pluginId: ANTIGRAVITY_PLUGIN_ID,
|
|
867
864
|
kind: "provider",
|
|
868
|
-
version: "1.0.0",
|
|
869
865
|
description: "Declares the Google Antigravity runtime and its reserved built-in names.",
|
|
870
866
|
// Vendor provider: marked gated for the day Antigravity grows its own
|
|
871
867
|
// on-disk kind beyond the open standard. Today `kinds: {}` and
|
|
@@ -1014,7 +1010,6 @@ var openaiProvider = {
|
|
|
1014
1010
|
id: "openai",
|
|
1015
1011
|
pluginId: OPENAI_PLUGIN_ID,
|
|
1016
1012
|
kind: "provider",
|
|
1017
|
-
version: "1.0.0",
|
|
1018
1013
|
description: "Classifies files under `.codex/agents/*.toml` as OpenAI Codex CLI sub-agents.",
|
|
1019
1014
|
// Vendor provider: Codex CLI only reads its own `.codex/` territory.
|
|
1020
1015
|
// Gating the classifier behind the active lens keeps the walker from
|
|
@@ -1072,7 +1067,6 @@ var agentSkillsProvider = {
|
|
|
1072
1067
|
id: "agent-skills",
|
|
1073
1068
|
pluginId: AGENT_SKILLS_PLUGIN_ID,
|
|
1074
1069
|
kind: "provider",
|
|
1075
|
-
version: "1.0.0",
|
|
1076
1070
|
description: "Classifies files under `.agents/skills/<name>/SKILL.md` as Agent Skills.",
|
|
1077
1071
|
read: { extensions: [".md"], parser: "frontmatter-yaml" },
|
|
1078
1072
|
kinds: {
|
|
@@ -1120,7 +1114,6 @@ var coreMarkdownProvider = {
|
|
|
1120
1114
|
id: "markdown",
|
|
1121
1115
|
pluginId: CORE_PLUGIN_ID,
|
|
1122
1116
|
kind: "provider",
|
|
1123
|
-
version: "1.0.0",
|
|
1124
1117
|
description: "Universal `.md` fallback. Claims any markdown file that no vendor-specific provider has classified.",
|
|
1125
1118
|
read: { extensions: [".md"], parser: "frontmatter-yaml" },
|
|
1126
1119
|
// Per spec § A.6, defaultRefreshAction values MUST be qualified
|
|
@@ -1156,10 +1149,14 @@ var coreMarkdownProvider = {
|
|
|
1156
1149
|
},
|
|
1157
1150
|
// No `resolution`: `core/markdown` is the universal fallback Provider,
|
|
1158
1151
|
// it does not declare an invocation surface of its own. Mentions /
|
|
1159
|
-
// slashes sourced from markdown bodies still
|
|
1160
|
-
//
|
|
1161
|
-
//
|
|
1162
|
-
// the
|
|
1152
|
+
// slashes sourced from markdown bodies are still resolved by the
|
|
1153
|
+
// post-walk transform, the lookup keys on the ACTIVE PROVIDER LENS
|
|
1154
|
+
// (per `spec/architecture.md` §Provider · resolution rules), mirroring
|
|
1155
|
+
// the extractor gate that authorised the emission in the first place.
|
|
1156
|
+
// Leaving this field absent therefore has no resolver-side impact
|
|
1157
|
+
// under any lens that DOES declare a resolution map; it would only
|
|
1158
|
+
// matter the day `markdown` itself becomes a lens (which is not on
|
|
1159
|
+
// the roadmap, the format is provider-agnostic by design).
|
|
1163
1160
|
classify() {
|
|
1164
1161
|
return "markdown";
|
|
1165
1162
|
}
|
|
@@ -1171,7 +1168,6 @@ var annotationsExtractor = {
|
|
|
1171
1168
|
id: ID3,
|
|
1172
1169
|
pluginId: CORE_PLUGIN_ID,
|
|
1173
1170
|
kind: "extractor",
|
|
1174
|
-
version: "1.0.0",
|
|
1175
1171
|
description: "Turns the `supersedes` and `supersededBy` entries from a node's `.sm` sidecar into arrows between nodes in the graph.",
|
|
1176
1172
|
scope: "frontmatter",
|
|
1177
1173
|
extract(ctx) {
|
|
@@ -1233,7 +1229,6 @@ var externalUrlCounterExtractor = {
|
|
|
1233
1229
|
id: ID4,
|
|
1234
1230
|
pluginId: CORE_PLUGIN_ID,
|
|
1235
1231
|
kind: "extractor",
|
|
1236
|
-
version: "1.0.0",
|
|
1237
1232
|
description: "Counts the distinct external URLs in a node's body and shows the count on the card.",
|
|
1238
1233
|
scope: "body",
|
|
1239
1234
|
/**
|
|
@@ -1322,7 +1317,6 @@ var markdownLinkExtractor = {
|
|
|
1322
1317
|
id: ID5,
|
|
1323
1318
|
pluginId: CORE_PLUGIN_ID,
|
|
1324
1319
|
kind: "extractor",
|
|
1325
|
-
version: "1.0.0",
|
|
1326
1320
|
description: "Turns markdown links (`[text](path)`) in a node's body into arrows between nodes in the graph.",
|
|
1327
1321
|
scope: "body",
|
|
1328
1322
|
extract(ctx) {
|
|
@@ -1384,7 +1378,6 @@ var mcpToolsExtractor = {
|
|
|
1384
1378
|
id: ID6,
|
|
1385
1379
|
pluginId: CORE_PLUGIN_ID,
|
|
1386
1380
|
kind: "extractor",
|
|
1387
|
-
version: "1.0.0",
|
|
1388
1381
|
description: "Turns `tools: [mcp__<server>__<tool>]` entries in a node's frontmatter into an MCP node per unique server and an arrow from the source to each one.",
|
|
1389
1382
|
scope: "frontmatter",
|
|
1390
1383
|
extract(ctx) {
|
|
@@ -1446,7 +1439,6 @@ var toolsCounterExtractor = {
|
|
|
1446
1439
|
id: ID7,
|
|
1447
1440
|
pluginId: CORE_PLUGIN_ID,
|
|
1448
1441
|
kind: "extractor",
|
|
1449
|
-
version: "1.0.0",
|
|
1450
1442
|
description: "Counts the tools an agent declares in its frontmatter and shows the count on the agent card.",
|
|
1451
1443
|
scope: "frontmatter",
|
|
1452
1444
|
precondition: { kind: ["claude/agent"] },
|
|
@@ -1513,34 +1505,15 @@ var annotationFieldUnknownAnalyzer = {
|
|
|
1513
1505
|
id: ID8,
|
|
1514
1506
|
pluginId: CORE_PLUGIN_ID,
|
|
1515
1507
|
kind: "analyzer",
|
|
1516
|
-
version: "1.0.0",
|
|
1517
1508
|
description: "Flags typos or unrecognized keys in sidecars (`.sm`).",
|
|
1518
1509
|
mode: "deterministic",
|
|
1519
|
-
ui:
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
// below stays outlined for the quieter counter pairing.
|
|
1527
|
-
icon: "fa-solid fa-triangle-exclamation",
|
|
1528
|
-
emitWhenEmpty: false
|
|
1529
|
-
},
|
|
1530
|
-
// Footer chip on the card, `_counter` shape but rendered icon-only
|
|
1531
|
-
// (the analyzer emits `value: 0` so NodeCounter hides the number
|
|
1532
|
-
// and only the glyph shows). PrimeIcons `pi-question-circle` so the
|
|
1533
|
-
// visual weight matches `annotation-stale`'s `pi-clock` chip
|
|
1534
|
-
// sitting next to it on the same footer row. `emitWhenEmpty: true`
|
|
1535
|
-
// is required: with `value: 0` the slot treats the payload as
|
|
1536
|
-
// empty, so the manifest has to opt in to keep the emission.
|
|
1537
|
-
chip: {
|
|
1538
|
-
slot: "card.footer.right",
|
|
1539
|
-
icon: "pi-question-circle",
|
|
1540
|
-
emitWhenEmpty: true,
|
|
1541
|
-
priority: 30
|
|
1542
|
-
}
|
|
1543
|
-
},
|
|
1510
|
+
// No `ui` declaration: the per-node icon-only chip used to surface
|
|
1511
|
+
// "this sidecar declares unknown keys" on `card.footer.right`, but
|
|
1512
|
+
// its severity (`warn`) is now folded into the aggregate counters
|
|
1513
|
+
// emitted by `core/issue-counter`. The detection logic stays, the
|
|
1514
|
+
// findings still ship as `Issue` records (visible in the inspector,
|
|
1515
|
+
// `sm check`, etc.).
|
|
1516
|
+
ui: {},
|
|
1544
1517
|
// Analyzer body iterates every sidecar root and classifies each
|
|
1545
1518
|
// key against three buckets (catalog / plugin namespace / unknown
|
|
1546
1519
|
// root). The per-key branching IS the classification table; factoring
|
|
@@ -1623,19 +1596,7 @@ var annotationFieldUnknownAnalyzer = {
|
|
|
1623
1596
|
bump2(node.path);
|
|
1624
1597
|
}
|
|
1625
1598
|
}
|
|
1626
|
-
|
|
1627
|
-
const tooltip = count === 1 ? ANNOTATION_FIELD_UNKNOWN_TEXTS.alertTooltipSingle : tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.alertTooltipMany, { count });
|
|
1628
|
-
ctx.emitContribution(nodePath, "alert", {
|
|
1629
|
-
icon: "fa-solid fa-triangle-exclamation",
|
|
1630
|
-
severity: "warn",
|
|
1631
|
-
tooltip
|
|
1632
|
-
});
|
|
1633
|
-
ctx.emitContribution(nodePath, "chip", {
|
|
1634
|
-
value: 0,
|
|
1635
|
-
severity: "warn",
|
|
1636
|
-
tooltip
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1599
|
+
void perNode;
|
|
1639
1600
|
return issues;
|
|
1640
1601
|
}
|
|
1641
1602
|
};
|
|
@@ -1694,7 +1655,6 @@ var annotationOrphanAnalyzer = {
|
|
|
1694
1655
|
id: ID9,
|
|
1695
1656
|
pluginId: CORE_PLUGIN_ID,
|
|
1696
1657
|
kind: "analyzer",
|
|
1697
|
-
version: "1.0.0",
|
|
1698
1658
|
description: "Flags sidecars (`.sm`) whose `.md` file no longer exists.",
|
|
1699
1659
|
mode: "deterministic",
|
|
1700
1660
|
evaluate(ctx) {
|
|
@@ -1746,7 +1706,6 @@ var annotationStaleAnalyzer = {
|
|
|
1746
1706
|
id: ID10,
|
|
1747
1707
|
pluginId: CORE_PLUGIN_ID,
|
|
1748
1708
|
kind: "analyzer",
|
|
1749
|
-
version: "1.0.0",
|
|
1750
1709
|
description: "Marks sidecars (`.sm`) that are out of date with their `.md`.",
|
|
1751
1710
|
mode: "deterministic",
|
|
1752
1711
|
// The natural fix is to bump the node: refreshes `for` hashes,
|
|
@@ -1767,7 +1726,10 @@ var annotationStaleAnalyzer = {
|
|
|
1767
1726
|
slot: "card.footer.right",
|
|
1768
1727
|
icon: "pi-clock",
|
|
1769
1728
|
emitWhenEmpty: true,
|
|
1770
|
-
|
|
1729
|
+
// First in the footer-right cluster: drift is the operator's
|
|
1730
|
+
// entry point for "this node disagrees with its sidecar",
|
|
1731
|
+
// followed by stability, then the severity counters.
|
|
1732
|
+
priority: 10
|
|
1771
1733
|
}
|
|
1772
1734
|
},
|
|
1773
1735
|
evaluate(ctx) {
|
|
@@ -1810,7 +1772,6 @@ var contributionOrphanAnalyzer = {
|
|
|
1810
1772
|
id: ID11,
|
|
1811
1773
|
pluginId: CORE_PLUGIN_ID,
|
|
1812
1774
|
kind: "analyzer",
|
|
1813
|
-
version: "0.0.0",
|
|
1814
1775
|
description: "Warns about plugin data referencing nodes renamed or deleted in the latest scan.",
|
|
1815
1776
|
mode: "deterministic",
|
|
1816
1777
|
evaluate(_ctx) {
|
|
@@ -1818,6 +1779,89 @@ var contributionOrphanAnalyzer = {
|
|
|
1818
1779
|
}
|
|
1819
1780
|
};
|
|
1820
1781
|
|
|
1782
|
+
// plugins/core/analyzers/issue-counter/text.ts
|
|
1783
|
+
var ISSUE_COUNTER_TEXTS = {
|
|
1784
|
+
errorTooltipSingle: "1 error",
|
|
1785
|
+
errorTooltipMany: "{{count}} errors",
|
|
1786
|
+
warnTooltipSingle: "1 warning",
|
|
1787
|
+
warnTooltipMany: "{{count}} warnings"
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
// plugins/core/analyzers/issue-counter/index.ts
|
|
1791
|
+
var ID12 = "issue-counter";
|
|
1792
|
+
function countByTier(issues) {
|
|
1793
|
+
const errors = /* @__PURE__ */ new Map();
|
|
1794
|
+
const warns = /* @__PURE__ */ new Map();
|
|
1795
|
+
for (const issue of issues) {
|
|
1796
|
+
const bucket = issue.severity === "error" ? errors : issue.severity === "warn" ? warns : null;
|
|
1797
|
+
if (!bucket) continue;
|
|
1798
|
+
for (const nodeId of issue.nodeIds) {
|
|
1799
|
+
bucket.set(nodeId, (bucket.get(nodeId) ?? 0) + 1);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
return { errors, warns };
|
|
1803
|
+
}
|
|
1804
|
+
function emitTierChips(ctx, contributionId, severity, counts, singleTooltip, manyTooltip) {
|
|
1805
|
+
for (const [nodePath, count] of counts) {
|
|
1806
|
+
const capped = Math.min(count, 99);
|
|
1807
|
+
ctx.emitContribution(nodePath, contributionId, {
|
|
1808
|
+
value: capped,
|
|
1809
|
+
severity,
|
|
1810
|
+
tooltip: count === 1 ? singleTooltip : tx(manyTooltip, { count })
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
var issueCounterAnalyzer = {
|
|
1815
|
+
id: ID12,
|
|
1816
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1817
|
+
kind: "analyzer",
|
|
1818
|
+
description: "Emits one aggregate severity chip per node (error + warn counts) from the live issue accumulator.",
|
|
1819
|
+
mode: "deterministic",
|
|
1820
|
+
phase: "aggregate",
|
|
1821
|
+
ui: {
|
|
1822
|
+
// Third in the footer-right cluster, after the drift chip
|
|
1823
|
+
// (priority 10) and the stability badge (priority 20). The warn
|
|
1824
|
+
// counter sits before the error counter so the operator reads
|
|
1825
|
+
// "advisory → blocking" left-to-right.
|
|
1826
|
+
warnCount: {
|
|
1827
|
+
slot: "card.footer.right",
|
|
1828
|
+
icon: "pi-exclamation-triangle",
|
|
1829
|
+
emitWhenEmpty: false,
|
|
1830
|
+
priority: 30
|
|
1831
|
+
},
|
|
1832
|
+
// Last in the cluster, the red chip pins to the right edge so the
|
|
1833
|
+
// most severe signal anchors the row's reading position.
|
|
1834
|
+
errorCount: {
|
|
1835
|
+
slot: "card.footer.right",
|
|
1836
|
+
icon: "pi-times-circle",
|
|
1837
|
+
emitWhenEmpty: false,
|
|
1838
|
+
priority: 40
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
evaluate(ctx) {
|
|
1842
|
+
const accumulator = ctx.accumulatedIssues ?? [];
|
|
1843
|
+
if (accumulator.length === 0) return [];
|
|
1844
|
+
const { errors, warns } = countByTier(accumulator);
|
|
1845
|
+
emitTierChips(
|
|
1846
|
+
ctx,
|
|
1847
|
+
"errorCount",
|
|
1848
|
+
"danger",
|
|
1849
|
+
errors,
|
|
1850
|
+
ISSUE_COUNTER_TEXTS.errorTooltipSingle,
|
|
1851
|
+
ISSUE_COUNTER_TEXTS.errorTooltipMany
|
|
1852
|
+
);
|
|
1853
|
+
emitTierChips(
|
|
1854
|
+
ctx,
|
|
1855
|
+
"warnCount",
|
|
1856
|
+
"warn",
|
|
1857
|
+
warns,
|
|
1858
|
+
ISSUE_COUNTER_TEXTS.warnTooltipSingle,
|
|
1859
|
+
ISSUE_COUNTER_TEXTS.warnTooltipMany
|
|
1860
|
+
);
|
|
1861
|
+
return [];
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1821
1865
|
// plugins/core/analyzers/job-file-orphan/text.ts
|
|
1822
1866
|
var JOB_FILE_ORPHAN_TEXTS = {
|
|
1823
1867
|
/**
|
|
@@ -1828,12 +1872,11 @@ var JOB_FILE_ORPHAN_TEXTS = {
|
|
|
1828
1872
|
};
|
|
1829
1873
|
|
|
1830
1874
|
// plugins/core/analyzers/job-file-orphan/index.ts
|
|
1831
|
-
var
|
|
1875
|
+
var ID13 = "job-file-orphan";
|
|
1832
1876
|
var jobFileOrphanAnalyzer = {
|
|
1833
|
-
id:
|
|
1877
|
+
id: ID13,
|
|
1834
1878
|
pluginId: CORE_PLUGIN_ID,
|
|
1835
1879
|
kind: "analyzer",
|
|
1836
|
-
version: "1.0.0",
|
|
1837
1880
|
description: "Flags leftover job result files (no live job references them). Clean up via `sm job prune --orphan-files`.",
|
|
1838
1881
|
mode: "deterministic",
|
|
1839
1882
|
evaluate(ctx) {
|
|
@@ -1842,7 +1885,7 @@ var jobFileOrphanAnalyzer = {
|
|
|
1842
1885
|
const issues = [];
|
|
1843
1886
|
for (const filePath of orphans) {
|
|
1844
1887
|
issues.push({
|
|
1845
|
-
analyzerId:
|
|
1888
|
+
analyzerId: ID13,
|
|
1846
1889
|
severity: "warn",
|
|
1847
1890
|
nodeIds: [filePath],
|
|
1848
1891
|
message: tx(JOB_FILE_ORPHAN_TEXTS.message, { filePath }),
|
|
@@ -1860,12 +1903,11 @@ var LINK_CONFLICT_TEXTS = {
|
|
|
1860
1903
|
};
|
|
1861
1904
|
|
|
1862
1905
|
// plugins/core/analyzers/link-conflict/index.ts
|
|
1863
|
-
var
|
|
1906
|
+
var ID14 = "link-conflict";
|
|
1864
1907
|
var linkConflictAnalyzer = {
|
|
1865
|
-
id:
|
|
1908
|
+
id: ID14,
|
|
1866
1909
|
pluginId: "core",
|
|
1867
1910
|
kind: "analyzer",
|
|
1868
|
-
version: "1.0.0",
|
|
1869
1911
|
description: "Flags conflicting arrow meanings between extractors (e.g. `references` vs `invokes`).",
|
|
1870
1912
|
mode: "deterministic",
|
|
1871
1913
|
// Bucket links by (source, target), then per-bucket detect distinct
|
|
@@ -1910,7 +1952,7 @@ var linkConflictAnalyzer = {
|
|
|
1910
1952
|
const [source, target] = key.split("\0");
|
|
1911
1953
|
const kindList = variants.map((v) => v.kind).join(" / ");
|
|
1912
1954
|
issues.push({
|
|
1913
|
-
analyzerId:
|
|
1955
|
+
analyzerId: ID14,
|
|
1914
1956
|
severity: "warn",
|
|
1915
1957
|
nodeIds: [source, target],
|
|
1916
1958
|
message: tx(LINK_CONFLICT_TEXTS.message, {
|
|
@@ -1977,12 +2019,11 @@ function resolveLinkTargetToPath(link, nameIndex) {
|
|
|
1977
2019
|
}
|
|
1978
2020
|
|
|
1979
2021
|
// plugins/core/analyzers/link-counter/index.ts
|
|
1980
|
-
var
|
|
2022
|
+
var ID15 = "link-counter";
|
|
1981
2023
|
var linkCounterAnalyzer = {
|
|
1982
|
-
id:
|
|
2024
|
+
id: ID15,
|
|
1983
2025
|
pluginId: CORE_PLUGIN_ID,
|
|
1984
2026
|
kind: "analyzer",
|
|
1985
|
-
version: "1.0.0",
|
|
1986
2027
|
description: "Counts incoming and outgoing links per node.",
|
|
1987
2028
|
mode: "deterministic",
|
|
1988
2029
|
ui: {
|
|
@@ -2057,12 +2098,11 @@ var LINK_SELF_LOOP_TEXTS = {
|
|
|
2057
2098
|
};
|
|
2058
2099
|
|
|
2059
2100
|
// plugins/core/analyzers/link-self-loop/index.ts
|
|
2060
|
-
var
|
|
2101
|
+
var ID16 = "link-self-loop";
|
|
2061
2102
|
var linkSelfLoopAnalyzer = {
|
|
2062
|
-
id:
|
|
2103
|
+
id: ID16,
|
|
2063
2104
|
pluginId: CORE_PLUGIN_ID,
|
|
2064
2105
|
kind: "analyzer",
|
|
2065
|
-
version: "1.0.0",
|
|
2066
2106
|
description: "Flags links whose source is also their own resolved target (e.g. a body heading like `# /deploy` inside the file that defines `/deploy`).",
|
|
2067
2107
|
mode: "deterministic",
|
|
2068
2108
|
evaluate(ctx) {
|
|
@@ -2071,7 +2111,7 @@ var linkSelfLoopAnalyzer = {
|
|
|
2071
2111
|
for (const link of ctx.links) {
|
|
2072
2112
|
if (!isSelfLoop(link)) continue;
|
|
2073
2113
|
issues.push({
|
|
2074
|
-
analyzerId:
|
|
2114
|
+
analyzerId: ID16,
|
|
2075
2115
|
severity: "warn",
|
|
2076
2116
|
nodeIds: [link.source],
|
|
2077
2117
|
message: tx(LINK_SELF_LOOP_TEXTS.message, {
|
|
@@ -2175,10 +2215,9 @@ function resolveByName(link, indexes, ctx) {
|
|
|
2175
2215
|
const winner = candidates.find((c) => allowedKinds.includes(c.kind));
|
|
2176
2216
|
return winner ? winner.path : "none";
|
|
2177
2217
|
}
|
|
2178
|
-
function lookupAllowedKinds(link,
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
return ctx.providerResolution.get(sourceNode.provider)?.[link.kind];
|
|
2218
|
+
function lookupAllowedKinds(link, _indexes, ctx) {
|
|
2219
|
+
if (ctx.activeProvider === null) return void 0;
|
|
2220
|
+
return ctx.providerResolution.get(ctx.activeProvider)?.[link.kind];
|
|
2182
2221
|
}
|
|
2183
2222
|
function stripTriggerSigil(normalized) {
|
|
2184
2223
|
if (!normalized) return null;
|
|
@@ -2221,12 +2260,11 @@ var NAME_RESERVED_TEXTS = {
|
|
|
2221
2260
|
};
|
|
2222
2261
|
|
|
2223
2262
|
// plugins/core/analyzers/name-reserved/index.ts
|
|
2224
|
-
var
|
|
2263
|
+
var ID17 = "name-reserved";
|
|
2225
2264
|
var nameReservedAnalyzer = {
|
|
2226
|
-
id:
|
|
2265
|
+
id: ID17,
|
|
2227
2266
|
pluginId: CORE_PLUGIN_ID,
|
|
2228
2267
|
kind: "analyzer",
|
|
2229
|
-
version: "1.0.0",
|
|
2230
2268
|
description: "Flags two kinds of reserved-name collision: a file whose name shadows a built-in command of the active runtime, and a link that resolves to one of those reserved names.",
|
|
2231
2269
|
mode: "deterministic",
|
|
2232
2270
|
// eslint-disable-next-line complexity
|
|
@@ -2240,7 +2278,7 @@ var nameReservedAnalyzer = {
|
|
|
2240
2278
|
const node = byPath3.get(path);
|
|
2241
2279
|
if (!node) continue;
|
|
2242
2280
|
issues.push({
|
|
2243
|
-
analyzerId:
|
|
2281
|
+
analyzerId: ID17,
|
|
2244
2282
|
severity: "warn",
|
|
2245
2283
|
nodeIds: [node.path],
|
|
2246
2284
|
message: tx(NAME_RESERVED_TEXTS.message, {
|
|
@@ -2256,7 +2294,7 @@ var nameReservedAnalyzer = {
|
|
|
2256
2294
|
const reservedNode = findReservedNodeForLink(link, reserved, byPath3);
|
|
2257
2295
|
if (!reservedNode) continue;
|
|
2258
2296
|
issues.push({
|
|
2259
|
-
analyzerId:
|
|
2297
|
+
analyzerId: ID17,
|
|
2260
2298
|
severity: "warn",
|
|
2261
2299
|
nodeIds: [link.source],
|
|
2262
2300
|
message: tx(NAME_RESERVED_TEXTS.linkMessage, {
|
|
@@ -2317,30 +2355,32 @@ function normaliseId(raw) {
|
|
|
2317
2355
|
}
|
|
2318
2356
|
|
|
2319
2357
|
// plugins/core/analyzers/node-stability/index.ts
|
|
2320
|
-
var
|
|
2358
|
+
var ID18 = "node-stability";
|
|
2321
2359
|
var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
|
|
2322
2360
|
var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
|
|
2323
2361
|
var nodeStabilityAnalyzer = {
|
|
2324
|
-
id:
|
|
2362
|
+
id: ID18,
|
|
2325
2363
|
pluginId: CORE_PLUGIN_ID,
|
|
2326
2364
|
kind: "analyzer",
|
|
2327
|
-
version: "1.0.0",
|
|
2328
2365
|
description: "Reports a node's stability stage (`experimental`, `deprecated`) on the card.",
|
|
2329
2366
|
mode: "deterministic",
|
|
2330
2367
|
ui: {
|
|
2368
|
+
// Second in the footer-right cluster, after the drift chip and
|
|
2369
|
+
// before the severity counters. Stability is a state badge, not a
|
|
2370
|
+
// count, so its priority sits between the two semantic zones.
|
|
2331
2371
|
experimental: {
|
|
2332
2372
|
slot: "card.footer.right",
|
|
2333
2373
|
icon: "fa-solid fa-flask",
|
|
2334
2374
|
label: "experimental",
|
|
2335
2375
|
emitWhenEmpty: false,
|
|
2336
|
-
priority:
|
|
2376
|
+
priority: 20
|
|
2337
2377
|
},
|
|
2338
2378
|
deprecated: {
|
|
2339
2379
|
slot: "card.footer.right",
|
|
2340
2380
|
icon: "pi-ban",
|
|
2341
2381
|
label: "deprecated",
|
|
2342
2382
|
emitWhenEmpty: false,
|
|
2343
|
-
priority:
|
|
2383
|
+
priority: 20
|
|
2344
2384
|
}
|
|
2345
2385
|
},
|
|
2346
2386
|
evaluate(ctx) {
|
|
@@ -2353,7 +2393,7 @@ var nodeStabilityAnalyzer = {
|
|
|
2353
2393
|
tooltip: EXPERIMENTAL_TOOLTIP
|
|
2354
2394
|
});
|
|
2355
2395
|
issues.push({
|
|
2356
|
-
analyzerId:
|
|
2396
|
+
analyzerId: ID18,
|
|
2357
2397
|
severity: "info",
|
|
2358
2398
|
nodeIds: [node.path],
|
|
2359
2399
|
message: `Node '${node.path}' is marked experimental: API may change.`,
|
|
@@ -2366,7 +2406,7 @@ var nodeStabilityAnalyzer = {
|
|
|
2366
2406
|
severity: "warn"
|
|
2367
2407
|
});
|
|
2368
2408
|
issues.push({
|
|
2369
|
-
analyzerId:
|
|
2409
|
+
analyzerId: ID18,
|
|
2370
2410
|
severity: "warn",
|
|
2371
2411
|
nodeIds: [node.path],
|
|
2372
2412
|
message: `Node '${node.path}' is marked deprecated: avoid in new code.`,
|
|
@@ -2400,12 +2440,11 @@ var NODE_SUPERSEDED_TEXTS = {
|
|
|
2400
2440
|
};
|
|
2401
2441
|
|
|
2402
2442
|
// plugins/core/analyzers/node-superseded/index.ts
|
|
2403
|
-
var
|
|
2443
|
+
var ID19 = "node-superseded";
|
|
2404
2444
|
var nodeSupersededAnalyzer = {
|
|
2405
|
-
id:
|
|
2445
|
+
id: ID19,
|
|
2406
2446
|
pluginId: CORE_PLUGIN_ID,
|
|
2407
2447
|
kind: "analyzer",
|
|
2408
|
-
version: "1.0.0",
|
|
2409
2448
|
description: "Marks nodes replaced by a newer one via `supersededBy`.",
|
|
2410
2449
|
mode: "deterministic",
|
|
2411
2450
|
evaluate(ctx) {
|
|
@@ -2414,7 +2453,7 @@ var nodeSupersededAnalyzer = {
|
|
|
2414
2453
|
const supersededBy = pickSupersededBy(node);
|
|
2415
2454
|
if (supersededBy === null) continue;
|
|
2416
2455
|
issues.push({
|
|
2417
|
-
analyzerId:
|
|
2456
|
+
analyzerId: ID19,
|
|
2418
2457
|
severity: "info",
|
|
2419
2458
|
nodeIds: [node.path],
|
|
2420
2459
|
message: tx(NODE_SUPERSEDED_TEXTS.message, {
|
|
@@ -2457,66 +2496,34 @@ var REFERENCE_BROKEN_TEXTS = {
|
|
|
2457
2496
|
};
|
|
2458
2497
|
|
|
2459
2498
|
// plugins/core/analyzers/reference-broken/index.ts
|
|
2460
|
-
var
|
|
2499
|
+
var ID20 = "reference-broken";
|
|
2461
2500
|
var referenceBrokenAnalyzer = {
|
|
2462
|
-
id:
|
|
2501
|
+
id: ID20,
|
|
2463
2502
|
pluginId: CORE_PLUGIN_ID,
|
|
2464
2503
|
kind: "analyzer",
|
|
2465
|
-
version: "1.0.0",
|
|
2466
2504
|
description: "Flags arrows pointing at a node not part of the current scan.",
|
|
2467
2505
|
mode: "deterministic",
|
|
2468
|
-
ui:
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
// (`fa-regular`) so the corner alert (filled, attention-grabbing)
|
|
2479
|
-
// and the footer chip (quieter, paired with a number) read as two
|
|
2480
|
-
// beats of the same signal rather than two identical glyphs.
|
|
2481
|
-
chip: {
|
|
2482
|
-
slot: "card.footer.right",
|
|
2483
|
-
icon: "fa-regular fa-circle-xmark",
|
|
2484
|
-
emitWhenEmpty: false,
|
|
2485
|
-
priority: 40
|
|
2486
|
-
}
|
|
2487
|
-
},
|
|
2488
|
-
// The resolver, the reference-paths escape hatch, the per-source
|
|
2489
|
-
// aggregation, and the dual-slot emit (with single/plural tooltip and
|
|
2490
|
-
// optional count) all live in one flow because they share the per-link
|
|
2491
|
-
// loop. Splitting them would re-walk `ctx.links` three times.
|
|
2492
|
-
// eslint-disable-next-line complexity
|
|
2506
|
+
// No `ui` declaration: this analyzer used to emit a per-finding
|
|
2507
|
+
// counter chip on `card.footer.right`, but that chip duplicated the
|
|
2508
|
+
// aggregate severity counters now owned by `core/issue-counter`. The
|
|
2509
|
+
// detection logic stays intact, only the chip emission is gone.
|
|
2510
|
+
ui: {},
|
|
2511
|
+
// The resolver, the reference-paths escape hatch, and the hint
|
|
2512
|
+
// index all share the per-link loop, splitting would re-walk
|
|
2513
|
+
// `ctx.links` once per concern. The per-source aggregation that
|
|
2514
|
+
// historically lived alongside (driving the now-retired chip
|
|
2515
|
+
// emission) moved into `core/issue-counter`.
|
|
2493
2516
|
evaluate(ctx) {
|
|
2494
2517
|
const byPath3 = new Set(ctx.nodes.map((n) => n.path));
|
|
2495
2518
|
const byNormalizedName = indexByNormalizedName(ctx.nodes);
|
|
2496
2519
|
const byBasenameWithoutName = indexByBasenameWithoutName(ctx.nodes);
|
|
2497
2520
|
const refIndex = ctx.referenceablePaths && ctx.referenceablePaths.size > 0 && ctx.cwd ? { paths: ctx.referenceablePaths, cwd: ctx.cwd } : null;
|
|
2498
2521
|
const issues = [];
|
|
2499
|
-
const perNode = /* @__PURE__ */ new Map();
|
|
2500
2522
|
for (const link of ctx.links) {
|
|
2501
2523
|
if (isResolved(link, byPath3, byNormalizedName)) continue;
|
|
2502
2524
|
if (refIndex && resolvesViaReferencePaths(link, refIndex)) continue;
|
|
2503
2525
|
const candidates = findHintCandidates(link, byBasenameWithoutName);
|
|
2504
2526
|
issues.push(buildIssue(link, candidates));
|
|
2505
|
-
perNode.set(link.source, (perNode.get(link.source) ?? 0) + 1);
|
|
2506
|
-
}
|
|
2507
|
-
for (const [nodePath, count] of perNode) {
|
|
2508
|
-
const tooltip = count === 1 ? REFERENCE_BROKEN_TEXTS.alertTooltipSingle : tx(REFERENCE_BROKEN_TEXTS.alertTooltipMany, { count });
|
|
2509
|
-
const capped = Math.min(count, 99);
|
|
2510
|
-
ctx.emitContribution(nodePath, "alert", {
|
|
2511
|
-
icon: "fa-solid fa-circle-xmark",
|
|
2512
|
-
severity: "danger",
|
|
2513
|
-
tooltip
|
|
2514
|
-
});
|
|
2515
|
-
ctx.emitContribution(nodePath, "chip", {
|
|
2516
|
-
value: capped,
|
|
2517
|
-
severity: "danger",
|
|
2518
|
-
tooltip
|
|
2519
|
-
});
|
|
2520
2527
|
}
|
|
2521
2528
|
return issues;
|
|
2522
2529
|
}
|
|
@@ -2528,8 +2535,14 @@ function buildIssue(link, hintCandidates = []) {
|
|
|
2528
2535
|
trigger: link.trigger?.normalizedTrigger ?? null
|
|
2529
2536
|
};
|
|
2530
2537
|
const issue = {
|
|
2531
|
-
analyzerId:
|
|
2532
|
-
|
|
2538
|
+
analyzerId: ID20,
|
|
2539
|
+
// `error`, not `warn`: a link whose target is not in the scan is a
|
|
2540
|
+
// structural defect the operator must notice, and the card chip
|
|
2541
|
+
// paints `danger` (red) to match. Per the chip-vs-issue policy in
|
|
2542
|
+
// `context/view-slots.md`, a `danger` chip MUST be backed by an
|
|
2543
|
+
// `error` Issue so the visual signal lines up with the exit code
|
|
2544
|
+
// and the global error count on the card.
|
|
2545
|
+
severity: "error",
|
|
2533
2546
|
nodeIds: [link.source],
|
|
2534
2547
|
message: tx(REFERENCE_BROKEN_TEXTS.message, {
|
|
2535
2548
|
kind: link.kind,
|
|
@@ -2638,12 +2651,11 @@ var REFERENCE_REDUNDANT_TEXTS = {
|
|
|
2638
2651
|
};
|
|
2639
2652
|
|
|
2640
2653
|
// plugins/core/analyzers/reference-redundant/index.ts
|
|
2641
|
-
var
|
|
2654
|
+
var ID21 = "reference-redundant";
|
|
2642
2655
|
var referenceRedundantAnalyzer = {
|
|
2643
|
-
id:
|
|
2656
|
+
id: ID21,
|
|
2644
2657
|
pluginId: CORE_PLUGIN_ID,
|
|
2645
2658
|
kind: "analyzer",
|
|
2646
|
-
version: "1.0.0",
|
|
2647
2659
|
description: "Flags when one node references the same target through two or more different links (e.g. a markdown link plus a `references:` entry).",
|
|
2648
2660
|
mode: "deterministic",
|
|
2649
2661
|
evaluate(ctx) {
|
|
@@ -2667,7 +2679,7 @@ var referenceRedundantAnalyzer = {
|
|
|
2667
2679
|
const [source, resolvedTarget] = key.split("\0");
|
|
2668
2680
|
const flat = flattenOccurrences(links);
|
|
2669
2681
|
issues.push({
|
|
2670
|
-
analyzerId:
|
|
2682
|
+
analyzerId: ID21,
|
|
2671
2683
|
severity: "warn",
|
|
2672
2684
|
nodeIds: [source],
|
|
2673
2685
|
message: tx(REFERENCE_REDUNDANT_TEXTS.message, {
|
|
@@ -2989,35 +3001,25 @@ var SCHEMA_VIOLATION_TEXTS = {
|
|
|
2989
3001
|
};
|
|
2990
3002
|
|
|
2991
3003
|
// plugins/core/analyzers/schema-violation/index.ts
|
|
2992
|
-
var
|
|
3004
|
+
var ID22 = "schema-violation";
|
|
2993
3005
|
var schemaViolationAnalyzer = {
|
|
2994
|
-
id:
|
|
3006
|
+
id: ID22,
|
|
2995
3007
|
pluginId: CORE_PLUGIN_ID,
|
|
2996
3008
|
kind: "analyzer",
|
|
2997
|
-
version: "1.0.0",
|
|
2998
3009
|
description: "Flags nodes or links that violate the project schemas.",
|
|
2999
3010
|
mode: "deterministic",
|
|
3000
|
-
ui:
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
// Outlined (vs the filled corner alert) per the broken-ref
|
|
3013
|
-
// pattern: two beats of the same signal.
|
|
3014
|
-
chip: {
|
|
3015
|
-
slot: "card.footer.right",
|
|
3016
|
-
icon: "fa-regular fa-triangle-exclamation",
|
|
3017
|
-
emitWhenEmpty: false,
|
|
3018
|
-
priority: 35
|
|
3019
|
-
}
|
|
3020
|
-
},
|
|
3011
|
+
// No `ui` declaration: the per-node failure-count chip used to live
|
|
3012
|
+
// on `card.footer.right`, but its information is now folded into the
|
|
3013
|
+
// aggregate severity counters emitted by `core/issue-counter`. The
|
|
3014
|
+
// findings still emit as `Issue` records, so `sm check` / inspector
|
|
3015
|
+
// unchanged.
|
|
3016
|
+
ui: {},
|
|
3017
|
+
// Pre-existing complexity: validates every node + every link
|
|
3018
|
+
// against multiple schemas with per-severity aggregation. The
|
|
3019
|
+
// branching mirrors the schema catalog and splitting scatters the
|
|
3020
|
+
// validation contract. Tracked as tech-debt; surfaced when an
|
|
3021
|
+
// unrelated change touched the lint cache.
|
|
3022
|
+
// eslint-disable-next-line complexity
|
|
3021
3023
|
evaluate(ctx) {
|
|
3022
3024
|
const validators = loadSchemaValidators();
|
|
3023
3025
|
const findings = [];
|
|
@@ -3027,26 +3029,24 @@ var schemaViolationAnalyzer = {
|
|
|
3027
3029
|
collectNodeFindings(validators, node, findings);
|
|
3028
3030
|
collectFrontmatterBaseFindings(node, findings);
|
|
3029
3031
|
if (findings.length > before) {
|
|
3030
|
-
|
|
3032
|
+
let worst = "warn";
|
|
3033
|
+
for (let i = before; i < findings.length; i++) {
|
|
3034
|
+
if (findings[i].severity === "error") {
|
|
3035
|
+
worst = "danger";
|
|
3036
|
+
break;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
const prev = perNode.get(node.path);
|
|
3040
|
+
perNode.set(node.path, {
|
|
3041
|
+
count: (prev?.count ?? 0) + (findings.length - before),
|
|
3042
|
+
worst: prev?.worst === "danger" ? "danger" : worst
|
|
3043
|
+
});
|
|
3031
3044
|
}
|
|
3032
3045
|
}
|
|
3033
3046
|
for (const link of ctx.links) {
|
|
3034
3047
|
collectLinkFindings(validators, link, findings);
|
|
3035
3048
|
}
|
|
3036
|
-
|
|
3037
|
-
const tooltip = count === 1 ? SCHEMA_VIOLATION_TEXTS.alertTooltipSingle : tx(SCHEMA_VIOLATION_TEXTS.alertTooltipMany, { count });
|
|
3038
|
-
const capped = Math.min(count, 99);
|
|
3039
|
-
ctx.emitContribution(nodePath, "alert", {
|
|
3040
|
-
icon: "fa-solid fa-triangle-exclamation",
|
|
3041
|
-
severity: "danger",
|
|
3042
|
-
tooltip
|
|
3043
|
-
});
|
|
3044
|
-
ctx.emitContribution(nodePath, "chip", {
|
|
3045
|
-
value: capped,
|
|
3046
|
-
severity: "danger",
|
|
3047
|
-
tooltip
|
|
3048
|
-
});
|
|
3049
|
-
}
|
|
3049
|
+
void perNode;
|
|
3050
3050
|
return findings;
|
|
3051
3051
|
}
|
|
3052
3052
|
};
|
|
@@ -3054,7 +3054,7 @@ function collectNodeFindings(v, node, out) {
|
|
|
3054
3054
|
const result = v.validate("node", toNodeForSchema(node));
|
|
3055
3055
|
if (result.ok) return;
|
|
3056
3056
|
out.push({
|
|
3057
|
-
analyzerId:
|
|
3057
|
+
analyzerId: ID22,
|
|
3058
3058
|
severity: "error",
|
|
3059
3059
|
nodeIds: [node.path],
|
|
3060
3060
|
message: tx(SCHEMA_VIOLATION_TEXTS.nodeFailure, {
|
|
@@ -3073,7 +3073,7 @@ function collectFrontmatterBaseFindings(node, out) {
|
|
|
3073
3073
|
if (isMissingStringField(fm, "description")) missing.push("description");
|
|
3074
3074
|
if (missing.length === 0) return;
|
|
3075
3075
|
out.push({
|
|
3076
|
-
analyzerId:
|
|
3076
|
+
analyzerId: ID22,
|
|
3077
3077
|
// `warn` (not `error`) so the default `sm scan` exit code stays
|
|
3078
3078
|
// 0 even when nodes are missing frontmatter base fields. Strict
|
|
3079
3079
|
// mode (`sm scan --strict`) still escalates to exit 1. Matches
|
|
@@ -3095,7 +3095,7 @@ function collectLinkFindings(v, link, out) {
|
|
|
3095
3095
|
const result = v.validate("link", toLinkForSchema(link));
|
|
3096
3096
|
if (result.ok) return;
|
|
3097
3097
|
out.push({
|
|
3098
|
-
analyzerId:
|
|
3098
|
+
analyzerId: ID22,
|
|
3099
3099
|
severity: "error",
|
|
3100
3100
|
nodeIds: [link.source],
|
|
3101
3101
|
message: tx(SCHEMA_VIOLATION_TEXTS.linkFailure, {
|
|
@@ -3170,12 +3170,11 @@ var SIGNAL_COLLISION_TEXTS = {
|
|
|
3170
3170
|
};
|
|
3171
3171
|
|
|
3172
3172
|
// plugins/core/analyzers/signal-collision/index.ts
|
|
3173
|
-
var
|
|
3173
|
+
var ID23 = "signal-collision";
|
|
3174
3174
|
var signalCollisionAnalyzer = {
|
|
3175
|
-
id:
|
|
3175
|
+
id: ID23,
|
|
3176
3176
|
pluginId: CORE_PLUGIN_ID,
|
|
3177
3177
|
kind: "analyzer",
|
|
3178
|
-
version: "1.0.0",
|
|
3179
3178
|
description: "Reports when two extractors fight over the same span of body text, or when a candidate link is dropped (extractor disabled, confidence too low) before it reaches the graph.",
|
|
3180
3179
|
mode: "deterministic",
|
|
3181
3180
|
evaluate(ctx) {
|
|
@@ -3198,7 +3197,7 @@ function makeIssue(signal) {
|
|
|
3198
3197
|
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3199
3198
|
const winnerRange = `${winner.range.start}-${winner.range.end}`;
|
|
3200
3199
|
return {
|
|
3201
|
-
analyzerId:
|
|
3200
|
+
analyzerId: ID23,
|
|
3202
3201
|
severity: "warn",
|
|
3203
3202
|
nodeIds: [signal.source],
|
|
3204
3203
|
message: tx(SIGNAL_COLLISION_TEXTS.message, {
|
|
@@ -3231,7 +3230,7 @@ function makeIssue(signal) {
|
|
|
3231
3230
|
if (resolution.extractorDisabled) {
|
|
3232
3231
|
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3233
3232
|
return {
|
|
3234
|
-
analyzerId:
|
|
3233
|
+
analyzerId: ID23,
|
|
3235
3234
|
severity: "warn",
|
|
3236
3235
|
nodeIds: [signal.source],
|
|
3237
3236
|
message: tx(SIGNAL_COLLISION_TEXTS.messageExtractorDisabled, {
|
|
@@ -3250,7 +3249,7 @@ function makeIssue(signal) {
|
|
|
3250
3249
|
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3251
3250
|
const topCandidate = signal.candidates[0];
|
|
3252
3251
|
return {
|
|
3253
|
-
analyzerId:
|
|
3252
|
+
analyzerId: ID23,
|
|
3254
3253
|
severity: "warn",
|
|
3255
3254
|
nodeIds: [signal.source],
|
|
3256
3255
|
message: tx(SIGNAL_COLLISION_TEXTS.messageBelowFloor, {
|
|
@@ -3295,18 +3294,17 @@ var TRIGGER_COLLISION_TEXTS = {
|
|
|
3295
3294
|
};
|
|
3296
3295
|
|
|
3297
3296
|
// plugins/core/analyzers/trigger-collision/index.ts
|
|
3298
|
-
var
|
|
3297
|
+
var ID24 = "trigger-collision";
|
|
3299
3298
|
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
3300
3299
|
"command",
|
|
3301
3300
|
"skill",
|
|
3302
3301
|
"agent"
|
|
3303
3302
|
]);
|
|
3304
3303
|
var triggerCollisionAnalyzer = {
|
|
3305
|
-
id:
|
|
3304
|
+
id: ID24,
|
|
3306
3305
|
pluginId: CORE_PLUGIN_ID,
|
|
3307
3306
|
kind: "analyzer",
|
|
3308
3307
|
mode: "deterministic",
|
|
3309
|
-
version: "1.0.0",
|
|
3310
3308
|
description: "Flags two or more nodes that claim the same `/command` or `@agent` name.",
|
|
3311
3309
|
// Two claim-collection passes (advertisement + invocation) feeding
|
|
3312
3310
|
// the bucket map. Per-bucket analysis lives in `analyzeTriggerBucket`.
|
|
@@ -3401,7 +3399,7 @@ function analyzeTriggerBucket(normalized, claims) {
|
|
|
3401
3399
|
part: parts[0]
|
|
3402
3400
|
});
|
|
3403
3401
|
return {
|
|
3404
|
-
analyzerId:
|
|
3402
|
+
analyzerId: ID24,
|
|
3405
3403
|
severity: "error",
|
|
3406
3404
|
nodeIds,
|
|
3407
3405
|
message,
|
|
@@ -3441,14 +3439,13 @@ var ASCII_FORMATTER_TEXTS = {
|
|
|
3441
3439
|
};
|
|
3442
3440
|
|
|
3443
3441
|
// plugins/core/formatters/ascii/index.ts
|
|
3444
|
-
var
|
|
3442
|
+
var ID25 = "ascii";
|
|
3445
3443
|
var KIND_ORDER = ["agent", "command", "skill", "markdown"];
|
|
3446
3444
|
var asciiFormatter = {
|
|
3447
|
-
id:
|
|
3445
|
+
id: ID25,
|
|
3448
3446
|
pluginId: CORE_PLUGIN_ID,
|
|
3449
3447
|
kind: "formatter",
|
|
3450
|
-
formatId:
|
|
3451
|
-
version: "1.0.0",
|
|
3448
|
+
formatId: ID25,
|
|
3452
3449
|
description: "Renders the scan as plain text in three sections: nodes (grouped by kind), arrows, and issues. Used by `sm scan --format ascii`.",
|
|
3453
3450
|
// ASCII tree formatter, header + per-kind sections + per-issue
|
|
3454
3451
|
// section. Each section iterates and renders; splitting per section
|
|
@@ -3542,14 +3539,13 @@ function renderSection(out, kind, group) {
|
|
|
3542
3539
|
}
|
|
3543
3540
|
|
|
3544
3541
|
// plugins/core/formatters/json/index.ts
|
|
3545
|
-
var
|
|
3542
|
+
var ID26 = "json";
|
|
3546
3543
|
var jsonFormatter = {
|
|
3547
|
-
id:
|
|
3544
|
+
id: ID26,
|
|
3548
3545
|
pluginId: CORE_PLUGIN_ID,
|
|
3549
3546
|
kind: "formatter",
|
|
3550
|
-
version: "1.0.0",
|
|
3551
3547
|
description: "Renders the persisted scan as JSON (conforms to `scan-result.schema.json`). Used by `sm graph --format json` and `GET /api/graph?format=json`.",
|
|
3552
|
-
formatId:
|
|
3548
|
+
formatId: ID26,
|
|
3553
3549
|
format(ctx) {
|
|
3554
3550
|
if (ctx.scanResult !== void 0) {
|
|
3555
3551
|
return JSON.stringify(ctx.scanResult);
|
|
@@ -3688,12 +3684,11 @@ function resolveSpecRoot2() {
|
|
|
3688
3684
|
}
|
|
3689
3685
|
|
|
3690
3686
|
// plugins/core/actions/node-bump/index.ts
|
|
3691
|
-
var
|
|
3687
|
+
var ID27 = "node-bump";
|
|
3692
3688
|
var nodeBumpAction = {
|
|
3693
|
-
id:
|
|
3689
|
+
id: ID27,
|
|
3694
3690
|
pluginId: CORE_PLUGIN_ID,
|
|
3695
3691
|
kind: "action",
|
|
3696
|
-
version: "1.0.0",
|
|
3697
3692
|
description: "Marks a node as updated: bumps `annotations.version`, refreshes sidecar hashes, and records the timestamp.",
|
|
3698
3693
|
mode: "deterministic",
|
|
3699
3694
|
// The runtime contract uses generic <TInput, TReport>; bump narrows
|
|
@@ -3749,12 +3744,11 @@ function pickCurrentVersion(overlay) {
|
|
|
3749
3744
|
}
|
|
3750
3745
|
|
|
3751
3746
|
// plugins/core/actions/node-supersede/index.ts
|
|
3752
|
-
var
|
|
3747
|
+
var ID28 = "node-supersede";
|
|
3753
3748
|
var nodeSupersedeAction = {
|
|
3754
|
-
id:
|
|
3749
|
+
id: ID28,
|
|
3755
3750
|
pluginId: CORE_PLUGIN_ID,
|
|
3756
3751
|
kind: "action",
|
|
3757
|
-
version: "0.0.0",
|
|
3758
3752
|
description: "Declares the current node as superseded by another (writes `supersededBy` to the sidecar).",
|
|
3759
3753
|
mode: "deterministic",
|
|
3760
3754
|
invoke(_input, _ctx) {
|
|
@@ -3861,7 +3855,7 @@ var UPDATE_CHECK_TEXTS = {
|
|
|
3861
3855
|
// package.json
|
|
3862
3856
|
var package_default = {
|
|
3863
3857
|
name: "@skill-map/cli",
|
|
3864
|
-
version: "0.
|
|
3858
|
+
version: "0.40.0",
|
|
3865
3859
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
3866
3860
|
license: "MIT",
|
|
3867
3861
|
type: "module",
|
|
@@ -4278,7 +4272,6 @@ var updateCheckHook = {
|
|
|
4278
4272
|
id: "update-check",
|
|
4279
4273
|
pluginId: CORE_PLUGIN_ID,
|
|
4280
4274
|
kind: "hook",
|
|
4281
|
-
version: "1.0.0",
|
|
4282
4275
|
description: "Checks daily for a newer `skill-map` version on npm. Shows an `update available` banner when one is found.",
|
|
4283
4276
|
triggers: ["boot"],
|
|
4284
4277
|
async on(ctx) {
|
|
@@ -4294,43 +4287,43 @@ var updateCheckHook = {
|
|
|
4294
4287
|
};
|
|
4295
4288
|
|
|
4296
4289
|
// plugins/built-ins.ts
|
|
4297
|
-
var claudeProvider2 = { ...claudeProvider, pluginId: "claude" };
|
|
4298
|
-
var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude" };
|
|
4299
|
-
var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude" };
|
|
4300
|
-
var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity" };
|
|
4301
|
-
var openaiProvider2 = { ...openaiProvider, pluginId: "openai" };
|
|
4302
|
-
var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills" };
|
|
4303
|
-
var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core" };
|
|
4304
|
-
var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core" };
|
|
4305
|
-
var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core" };
|
|
4306
|
-
var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core" };
|
|
4307
|
-
var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core" };
|
|
4308
|
-
var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core" };
|
|
4309
|
-
var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core" };
|
|
4310
|
-
var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core" };
|
|
4311
|
-
var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core" };
|
|
4312
|
-
var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core" };
|
|
4313
|
-
var
|
|
4314
|
-
var
|
|
4315
|
-
var
|
|
4316
|
-
var
|
|
4317
|
-
var
|
|
4318
|
-
var
|
|
4319
|
-
var
|
|
4320
|
-
var
|
|
4321
|
-
var
|
|
4322
|
-
var
|
|
4323
|
-
var
|
|
4324
|
-
var
|
|
4325
|
-
var
|
|
4326
|
-
var
|
|
4327
|
-
var
|
|
4328
|
-
var
|
|
4329
|
-
var
|
|
4290
|
+
var claudeProvider2 = { ...claudeProvider, pluginId: "claude", version: "0.40.0" };
|
|
4291
|
+
var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude", version: "0.40.0" };
|
|
4292
|
+
var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude", version: "0.40.0" };
|
|
4293
|
+
var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity", version: "0.40.0" };
|
|
4294
|
+
var openaiProvider2 = { ...openaiProvider, pluginId: "openai", version: "0.40.0" };
|
|
4295
|
+
var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills", version: "0.40.0" };
|
|
4296
|
+
var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core", version: "0.40.0" };
|
|
4297
|
+
var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core", version: "0.40.0" };
|
|
4298
|
+
var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core", version: "0.40.0" };
|
|
4299
|
+
var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core", version: "0.40.0" };
|
|
4300
|
+
var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core", version: "0.40.0" };
|
|
4301
|
+
var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core", version: "0.40.0" };
|
|
4302
|
+
var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4303
|
+
var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4304
|
+
var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4305
|
+
var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4306
|
+
var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4307
|
+
var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4308
|
+
var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4309
|
+
var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4310
|
+
var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4311
|
+
var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4312
|
+
var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4313
|
+
var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4314
|
+
var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4315
|
+
var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4316
|
+
var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4317
|
+
var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4318
|
+
var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: "0.40.0" };
|
|
4319
|
+
var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: "0.40.0" };
|
|
4320
|
+
var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: "0.40.0" };
|
|
4321
|
+
var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: "0.40.0" };
|
|
4322
|
+
var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: "0.40.0" };
|
|
4323
|
+
var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: "0.40.0" };
|
|
4330
4324
|
var builtInBundles = [
|
|
4331
4325
|
{
|
|
4332
4326
|
id: "claude",
|
|
4333
|
-
granularity: "bundle",
|
|
4334
4327
|
description: "Claude Code platform integration. Classifies files under `.claude/{agents,commands,skills}` and detects Claude-flavored slash commands and at-directives in their bodies.",
|
|
4335
4328
|
extensions: [
|
|
4336
4329
|
claudeProvider2,
|
|
@@ -4340,7 +4333,6 @@ var builtInBundles = [
|
|
|
4340
4333
|
},
|
|
4341
4334
|
{
|
|
4342
4335
|
id: "antigravity",
|
|
4343
|
-
granularity: "bundle",
|
|
4344
4336
|
description: "Google Antigravity CLI platform integration (replaces the retired Gemini CLI). Antigravity adopted the open-standard `.agents/` layout, so skills are classified by the neutral `agent-skills` provider; this plugin contributes the Antigravity runtime identity and a seed list of reserved built-in names.",
|
|
4345
4337
|
extensions: [
|
|
4346
4338
|
antigravityProvider2
|
|
@@ -4348,7 +4340,6 @@ var builtInBundles = [
|
|
|
4348
4340
|
},
|
|
4349
4341
|
{
|
|
4350
4342
|
id: "openai",
|
|
4351
|
-
granularity: "bundle",
|
|
4352
4343
|
description: "OpenAI Codex CLI platform integration. Classifies TOML sub-agent definitions under `.codex/agents/*.toml`.",
|
|
4353
4344
|
extensions: [
|
|
4354
4345
|
openaiProvider2
|
|
@@ -4356,7 +4347,6 @@ var builtInBundles = [
|
|
|
4356
4347
|
},
|
|
4357
4348
|
{
|
|
4358
4349
|
id: "agent-skills",
|
|
4359
|
-
granularity: "bundle",
|
|
4360
4350
|
description: "Open-standard Agent Skills layout. Classifies skills under the vendor-neutral path `.agents/skills/<name>/SKILL.md` (adopted by Anthropic, OpenAI, Google). See agentskills.io.",
|
|
4361
4351
|
extensions: [
|
|
4362
4352
|
agentSkillsProvider2
|
|
@@ -4364,7 +4354,6 @@ var builtInBundles = [
|
|
|
4364
4354
|
},
|
|
4365
4355
|
{
|
|
4366
4356
|
id: "core",
|
|
4367
|
-
granularity: "extension",
|
|
4368
4357
|
description: "Core extensions shared across providers: parsers, extractors, analyzers, actions, hooks, formatters, and the universal `.md` fallback provider.",
|
|
4369
4358
|
extensions: [
|
|
4370
4359
|
coreMarkdownProvider2,
|
|
@@ -4377,6 +4366,7 @@ var builtInBundles = [
|
|
|
4377
4366
|
annotationOrphanAnalyzer2,
|
|
4378
4367
|
annotationStaleAnalyzer2,
|
|
4379
4368
|
contributionOrphanAnalyzer2,
|
|
4369
|
+
issueCounterAnalyzer2,
|
|
4380
4370
|
jobFileOrphanAnalyzer2,
|
|
4381
4371
|
linkConflictAnalyzer2,
|
|
4382
4372
|
linkCounterAnalyzer2,
|
|
@@ -8933,7 +8923,16 @@ var CHECK_TEXTS = {
|
|
|
8933
8923
|
tipLine: "\nTip: `sm refresh <node>` to revalidate a file after fixes.\n",
|
|
8934
8924
|
// --- prob stub advisory ---------------------------------------------------
|
|
8935
8925
|
probStubAdvisory: "sm check --include-prob: probabilistic Analyzer dispatch requires the job subsystem (Step 10). Stub: skipped {{count}} probabilistic analyzer(s): {{analyzerIds}}. Deterministic analyzers ran as usual; full dispatch lands when the job subsystem ships.\n",
|
|
8936
|
-
probStubAdvisoryAsync: "sm check --include-prob --async: probabilistic Analyzer dispatch requires the job subsystem (Step 10). Stub: skipped {{count}} probabilistic analyzer(s): {{analyzerIds}}. The --async flag is reserved for future encoding (returns job ids without waiting once jobs land); today it is a no-op. Deterministic analyzers ran as usual.\n"
|
|
8926
|
+
probStubAdvisoryAsync: "sm check --include-prob --async: probabilistic Analyzer dispatch requires the job subsystem (Step 10). Stub: skipped {{count}} probabilistic analyzer(s): {{analyzerIds}}. The --async flag is reserved for future encoding (returns job ids without waiting once jobs land); today it is a no-op. Deterministic analyzers ran as usual.\n",
|
|
8927
|
+
/**
|
|
8928
|
+
* Emitted on stderr when `--analyzers` lists one or more ids the
|
|
8929
|
+
* loaded analyzer registry does not know. The user almost always
|
|
8930
|
+
* mistyped (e.g. `broken-ref` instead of `reference-broken`); listing
|
|
8931
|
+
* the valid ids inline lets them fix the call without a second round
|
|
8932
|
+
* trip through `sm plugins list`. Exit code is `ExitCode.Error` (2):
|
|
8933
|
+
* bad usage, not "no issues found".
|
|
8934
|
+
*/
|
|
8935
|
+
unknownAnalyzerIds: "sm check: unknown analyzer id(s) in --analyzers: {{unknown}}.\nValid ids (qualified or short form accepted):\n{{known}}\n"
|
|
8937
8936
|
};
|
|
8938
8937
|
|
|
8939
8938
|
// cli/util/conformance-env.ts
|
|
@@ -9474,20 +9473,17 @@ var PluginLoader = class {
|
|
|
9474
9473
|
* into a helper would scatter the return-on-failure pattern without
|
|
9475
9474
|
* making the orchestration clearer.
|
|
9476
9475
|
*/
|
|
9477
|
-
// eslint-disable-next-line complexity
|
|
9478
9476
|
async loadOne(pluginPath) {
|
|
9479
9477
|
const pluginId = pathId(pluginPath);
|
|
9480
9478
|
const manifestResult = this.#parseAndValidateManifest(pluginPath, pluginId);
|
|
9481
9479
|
if (!manifestResult.ok) return manifestResult.failure;
|
|
9482
9480
|
const manifest = manifestResult.manifest;
|
|
9483
|
-
const granularity = manifest.granularity ?? "extension";
|
|
9484
9481
|
if (this.#options.resolveEnabled && !this.#options.resolveEnabled(pluginId)) {
|
|
9485
9482
|
return {
|
|
9486
9483
|
path: pluginPath,
|
|
9487
9484
|
id: pluginId,
|
|
9488
9485
|
status: "disabled",
|
|
9489
9486
|
manifest,
|
|
9490
|
-
granularity,
|
|
9491
9487
|
reason: PLUGIN_LOADER_TEXTS.disabledByConfig
|
|
9492
9488
|
};
|
|
9493
9489
|
}
|
|
@@ -9509,7 +9505,6 @@ var PluginLoader = class {
|
|
|
9509
9505
|
id: pluginId,
|
|
9510
9506
|
status: "enabled",
|
|
9511
9507
|
manifest,
|
|
9512
|
-
granularity,
|
|
9513
9508
|
extensions: loaded,
|
|
9514
9509
|
...storageSchemasResult.schemas ? { storageSchemas: storageSchemasResult.schemas } : {}
|
|
9515
9510
|
};
|
|
@@ -9567,7 +9562,6 @@ var PluginLoader = class {
|
|
|
9567
9562
|
id: pluginId,
|
|
9568
9563
|
status: "incompatible-spec",
|
|
9569
9564
|
manifest,
|
|
9570
|
-
granularity: manifest.granularity ?? "extension",
|
|
9571
9565
|
reason: tx(PLUGIN_LOADER_TEXTS.incompatibleSpec, {
|
|
9572
9566
|
installedSpecVersion: this.#options.specVersion,
|
|
9573
9567
|
specCompat: manifest.specCompat
|
|
@@ -9886,25 +9880,9 @@ function isBuiltInExtensionEnabled(bundle, ext, resolveEnabled) {
|
|
|
9886
9880
|
return isBundleEntryEnabled(bundle, ext.id, resolveEnabled);
|
|
9887
9881
|
}
|
|
9888
9882
|
function isBundleEntryEnabled(bundle, extId, resolveEnabled) {
|
|
9889
|
-
if (bundle.granularity === "bundle") {
|
|
9890
|
-
if (!resolveEnabled(bundle.id)) return false;
|
|
9891
|
-
return resolveEnabled(qualifiedExtensionId(bundle.id, extId));
|
|
9892
|
-
}
|
|
9893
9883
|
return resolveEnabled(qualifiedExtensionId(bundle.id, extId));
|
|
9894
9884
|
}
|
|
9895
|
-
function
|
|
9896
|
-
const out = /* @__PURE__ */ new Map();
|
|
9897
|
-
for (const plugin of discovered) {
|
|
9898
|
-
out.set(plugin.id, plugin.granularity ?? "bundle");
|
|
9899
|
-
}
|
|
9900
|
-
return out;
|
|
9901
|
-
}
|
|
9902
|
-
function isPluginExtensionEnabled(ext, granularityMap, resolveEnabled) {
|
|
9903
|
-
const granularity = granularityMap.get(ext.pluginId) ?? "bundle";
|
|
9904
|
-
if (granularity === "bundle") {
|
|
9905
|
-
if (!resolveEnabled(ext.pluginId)) return false;
|
|
9906
|
-
return resolveEnabled(qualifiedExtensionId(ext.pluginId, ext.id));
|
|
9907
|
-
}
|
|
9885
|
+
function isPluginExtensionEnabled(ext, resolveEnabled) {
|
|
9908
9886
|
return resolveEnabled(qualifiedExtensionId(ext.pluginId, ext.id));
|
|
9909
9887
|
}
|
|
9910
9888
|
async function buildEnabledResolver(ctx) {
|
|
@@ -10423,7 +10401,6 @@ function filterBuiltInManifests(manifests, resolveEnabled) {
|
|
|
10423
10401
|
// core/runtime/plugin-runtime/composer.ts
|
|
10424
10402
|
function composeScanExtensions(opts) {
|
|
10425
10403
|
const resolveEnabled = opts.resolveEnabled ?? opts.pluginRuntime.resolveEnabled;
|
|
10426
|
-
const granularityMap = buildGranularityMap(opts.pluginRuntime.discovered);
|
|
10427
10404
|
const providers = [];
|
|
10428
10405
|
const extractors = [];
|
|
10429
10406
|
const analyzers = [];
|
|
@@ -10435,16 +10412,16 @@ function composeScanExtensions(opts) {
|
|
|
10435
10412
|
);
|
|
10436
10413
|
}
|
|
10437
10414
|
for (const ext of opts.pluginRuntime.extensions.providers) {
|
|
10438
|
-
if (isPluginExtensionEnabled(ext,
|
|
10415
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) providers.push(ext);
|
|
10439
10416
|
}
|
|
10440
10417
|
for (const ext of opts.pluginRuntime.extensions.extractors) {
|
|
10441
|
-
if (isPluginExtensionEnabled(ext,
|
|
10418
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) extractors.push(ext);
|
|
10442
10419
|
}
|
|
10443
10420
|
for (const ext of opts.pluginRuntime.extensions.analyzers) {
|
|
10444
|
-
if (isPluginExtensionEnabled(ext,
|
|
10421
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) analyzers.push(ext);
|
|
10445
10422
|
}
|
|
10446
10423
|
for (const ext of opts.pluginRuntime.extensions.hooks) {
|
|
10447
|
-
if (isPluginExtensionEnabled(ext,
|
|
10424
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) hooks.push(ext);
|
|
10448
10425
|
}
|
|
10449
10426
|
const finalProviders = opts.killSwitches?.providers === true ? [] : providers;
|
|
10450
10427
|
const finalExtractors = opts.killSwitches?.extractors === true ? [] : extractors;
|
|
@@ -10491,7 +10468,6 @@ function accumulateBuiltInScanExtensions(buckets, resolveEnabled) {
|
|
|
10491
10468
|
function composeFormatters(opts) {
|
|
10492
10469
|
const noBuiltIns = opts.noBuiltIns ?? false;
|
|
10493
10470
|
const resolveEnabled = opts.resolveEnabled ?? opts.pluginRuntime.resolveEnabled;
|
|
10494
|
-
const granularityMap = buildGranularityMap(opts.pluginRuntime.discovered);
|
|
10495
10471
|
const out = [];
|
|
10496
10472
|
if (!noBuiltIns) {
|
|
10497
10473
|
for (const bundle of builtInBundles) {
|
|
@@ -10503,32 +10479,24 @@ function composeFormatters(opts) {
|
|
|
10503
10479
|
}
|
|
10504
10480
|
}
|
|
10505
10481
|
for (const ext of opts.pluginRuntime.extensions.formatters) {
|
|
10506
|
-
if (isPluginExtensionEnabled(ext,
|
|
10482
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) out.push(ext);
|
|
10507
10483
|
}
|
|
10508
10484
|
return out;
|
|
10509
10485
|
}
|
|
10510
10486
|
function registerEnabledExtensions(kernel, pluginRuntime, options = {}) {
|
|
10511
10487
|
const noBuiltIns = options.noBuiltIns === true;
|
|
10512
10488
|
const resolveEnabled = options.resolveEnabled ?? pluginRuntime.resolveEnabled;
|
|
10513
|
-
const granularityMap = buildGranularityMap(pluginRuntime.discovered);
|
|
10514
10489
|
if (!noBuiltIns) {
|
|
10515
10490
|
const enabledBuiltIns = filterBuiltInManifests(listBuiltIns(), resolveEnabled);
|
|
10516
10491
|
for (const manifest of enabledBuiltIns) kernel.registry.register(manifest);
|
|
10517
10492
|
}
|
|
10518
10493
|
for (const manifest of pluginRuntime.manifests) {
|
|
10519
|
-
if (!isPluginExtensionEnabled(manifest,
|
|
10494
|
+
if (!isPluginExtensionEnabled(manifest, resolveEnabled)) continue;
|
|
10520
10495
|
kernel.registry.register(manifest);
|
|
10521
10496
|
}
|
|
10522
10497
|
if (kernel.setRegisteredAnnotationKeys) {
|
|
10523
10498
|
const filteredAnnotations = pluginRuntime.annotationContributions.filter(
|
|
10524
|
-
(entry) => (
|
|
10525
|
-
// Annotation contributions live at plugin-id granularity (the
|
|
10526
|
-
// catalog row carries `pluginId`, not `extensionId`), so the
|
|
10527
|
-
// bundle-level toggle gates the entire row. Extension
|
|
10528
|
-
// granularity falls through to the manifest-level filter above
|
|
10529
|
-
// this surface is bundle-scoped by design.
|
|
10530
|
-
resolveEnabled(entry.pluginId)
|
|
10531
|
-
)
|
|
10499
|
+
(entry) => resolveEnabled(entry.pluginId)
|
|
10532
10500
|
);
|
|
10533
10501
|
kernel.setRegisteredAnnotationKeys(filteredAnnotations);
|
|
10534
10502
|
}
|
|
@@ -10536,7 +10504,6 @@ function registerEnabledExtensions(kernel, pluginRuntime, options = {}) {
|
|
|
10536
10504
|
const userContribs = pluginRuntime.viewContributions.filter(
|
|
10537
10505
|
(entry) => isPluginExtensionEnabled(
|
|
10538
10506
|
{ pluginId: entry.pluginId, id: entry.extensionId },
|
|
10539
|
-
granularityMap,
|
|
10540
10507
|
resolveEnabled
|
|
10541
10508
|
)
|
|
10542
10509
|
);
|
|
@@ -10602,22 +10569,8 @@ var CheckCommand = class extends SmCommand {
|
|
|
10602
10569
|
const exit = requireDbOrExit(dbPath, this.context.stderr);
|
|
10603
10570
|
if (exit !== null) return exit;
|
|
10604
10571
|
const analyzerFilter = parseAnalyzersFlag(this.analyzers);
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
noPlugins: this.noPlugins,
|
|
10608
|
-
analyzerFilter,
|
|
10609
|
-
printer: this.printer
|
|
10610
|
-
});
|
|
10611
|
-
if (probAnalyzerIds.length > 0) {
|
|
10612
|
-
const template = this.async ? CHECK_TEXTS.probStubAdvisoryAsync : CHECK_TEXTS.probStubAdvisory;
|
|
10613
|
-
this.printer.info(
|
|
10614
|
-
tx(template, {
|
|
10615
|
-
count: probAnalyzerIds.length,
|
|
10616
|
-
analyzerIds: probAnalyzerIds.join(", ")
|
|
10617
|
-
})
|
|
10618
|
-
);
|
|
10619
|
-
}
|
|
10620
|
-
}
|
|
10572
|
+
const preflight = await this.#preflightAnalyzerCatalog(analyzerFilter);
|
|
10573
|
+
if (preflight.exit !== null) return preflight.exit;
|
|
10621
10574
|
return withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
|
|
10622
10575
|
let issues = await adapter.issues.listAll();
|
|
10623
10576
|
if (this.node !== void 0) {
|
|
@@ -10640,6 +10593,53 @@ var CheckCommand = class extends SmCommand {
|
|
|
10640
10593
|
return issues.some((i) => i.severity === "error") ? ExitCode.Issues : ExitCode.Ok;
|
|
10641
10594
|
});
|
|
10642
10595
|
}
|
|
10596
|
+
/**
|
|
10597
|
+
* Either an explicit `--analyzers` list or `--include-prob` forces a
|
|
10598
|
+
* load of the live Analyzer catalog: the first needs it to validate
|
|
10599
|
+
* the user-supplied ids against the registry, the second to enumerate
|
|
10600
|
+
* registered probabilistic analyzers. Sharing a single load keeps
|
|
10601
|
+
* `sm check` from paying for two passes when both flags are present.
|
|
10602
|
+
*
|
|
10603
|
+
* Returns `{ exit: <code> }` to short-circuit `run()` when the
|
|
10604
|
+
* validation rejects an unknown id (the only path that aborts before
|
|
10605
|
+
* the DB read). Successful runs return `{ exit: null }`.
|
|
10606
|
+
*/
|
|
10607
|
+
async #preflightAnalyzerCatalog(analyzerFilter) {
|
|
10608
|
+
const needsCatalog = analyzerFilter !== void 0 || this.includeProb;
|
|
10609
|
+
if (!needsCatalog) return { exit: null };
|
|
10610
|
+
const analyzers = await loadAnalyzerCatalog({
|
|
10611
|
+
noPlugins: this.noPlugins,
|
|
10612
|
+
printer: this.printer
|
|
10613
|
+
});
|
|
10614
|
+
if (analyzerFilter !== void 0) {
|
|
10615
|
+
const validation = validateAnalyzerFilter(analyzerFilter, analyzers);
|
|
10616
|
+
if (validation !== null) {
|
|
10617
|
+
this.printer.error(validation);
|
|
10618
|
+
return { exit: ExitCode.Error };
|
|
10619
|
+
}
|
|
10620
|
+
}
|
|
10621
|
+
if (this.includeProb) {
|
|
10622
|
+
this.#emitProbAdvisory(analyzers, analyzerFilter);
|
|
10623
|
+
}
|
|
10624
|
+
return { exit: null };
|
|
10625
|
+
}
|
|
10626
|
+
/**
|
|
10627
|
+
* Walk the loaded catalog for probabilistic analyzers honouring the
|
|
10628
|
+
* `--analyzers` filter and, when any survive, emit the stub stderr
|
|
10629
|
+
* advisory naming them. Extracted so `run()` does not carry the
|
|
10630
|
+
* branching for the `--include-prob` / `--async` advisory shapes.
|
|
10631
|
+
*/
|
|
10632
|
+
#emitProbAdvisory(analyzers, analyzerFilter) {
|
|
10633
|
+
const probAnalyzerIds = detectProbAnalyzerIds(analyzers, analyzerFilter);
|
|
10634
|
+
if (probAnalyzerIds.length === 0) return;
|
|
10635
|
+
const template = this.async ? CHECK_TEXTS.probStubAdvisoryAsync : CHECK_TEXTS.probStubAdvisory;
|
|
10636
|
+
this.printer.info(
|
|
10637
|
+
tx(template, {
|
|
10638
|
+
count: probAnalyzerIds.length,
|
|
10639
|
+
analyzerIds: probAnalyzerIds.join(", ")
|
|
10640
|
+
})
|
|
10641
|
+
);
|
|
10642
|
+
}
|
|
10643
10643
|
};
|
|
10644
10644
|
function parseAnalyzersFlag(raw) {
|
|
10645
10645
|
if (raw === void 0) return void 0;
|
|
@@ -10647,7 +10647,7 @@ function parseAnalyzersFlag(raw) {
|
|
|
10647
10647
|
if (ids.length === 0) return void 0;
|
|
10648
10648
|
return ids;
|
|
10649
10649
|
}
|
|
10650
|
-
async function
|
|
10650
|
+
async function loadAnalyzerCatalog(opts) {
|
|
10651
10651
|
const pluginRuntime = opts.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime();
|
|
10652
10652
|
pluginRuntime.emitWarnings(opts.printer);
|
|
10653
10653
|
const composed = composeScanExtensions({
|
|
@@ -10655,12 +10655,30 @@ async function detectProbAnalyzerIds(opts) {
|
|
|
10655
10655
|
pluginRuntime,
|
|
10656
10656
|
killSwitches: readConformanceKillSwitches()
|
|
10657
10657
|
});
|
|
10658
|
-
|
|
10658
|
+
return composed?.analyzers ?? [];
|
|
10659
|
+
}
|
|
10660
|
+
function validateAnalyzerFilter(filter, analyzers) {
|
|
10661
|
+
const knownQualified = /* @__PURE__ */ new Set();
|
|
10662
|
+
const knownShort = /* @__PURE__ */ new Set();
|
|
10663
|
+
for (const analyzer of analyzers) {
|
|
10664
|
+
const qualified = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
10665
|
+
knownQualified.add(qualified);
|
|
10666
|
+
knownShort.add(analyzer.id);
|
|
10667
|
+
}
|
|
10668
|
+
const unknown = filter.filter((id) => !knownQualified.has(id) && !knownShort.has(id));
|
|
10669
|
+
if (unknown.length === 0) return null;
|
|
10670
|
+
const knownList = [...knownQualified].sort().map((id) => ` ${id}`).join("\n");
|
|
10671
|
+
return tx(CHECK_TEXTS.unknownAnalyzerIds, {
|
|
10672
|
+
unknown: unknown.join(", "),
|
|
10673
|
+
known: knownList
|
|
10674
|
+
});
|
|
10675
|
+
}
|
|
10676
|
+
function detectProbAnalyzerIds(analyzers, analyzerFilter) {
|
|
10659
10677
|
const probIds = [];
|
|
10660
10678
|
for (const analyzer of analyzers) {
|
|
10661
10679
|
if (analyzer.mode !== "probabilistic") continue;
|
|
10662
10680
|
const qualified = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
10663
|
-
if (
|
|
10681
|
+
if (analyzerFilter && !matchesAnalyzerFilter(qualified, analyzerFilter)) continue;
|
|
10664
10682
|
probIds.push(qualified);
|
|
10665
10683
|
}
|
|
10666
10684
|
probIds.sort();
|
|
@@ -14733,8 +14751,8 @@ function isExternalUrlLink(link) {
|
|
|
14733
14751
|
}
|
|
14734
14752
|
|
|
14735
14753
|
// kernel/orchestrator/analyzers.ts
|
|
14736
|
-
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals) {
|
|
14737
|
-
const issues = [];
|
|
14754
|
+
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals, seedIssues = []) {
|
|
14755
|
+
const issues = [...seedIssues];
|
|
14738
14756
|
const contributions = [];
|
|
14739
14757
|
const validators = loadSchemaValidators();
|
|
14740
14758
|
void registeredActionIds;
|
|
@@ -14742,7 +14760,8 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
14742
14760
|
relativePath: o.relativePath,
|
|
14743
14761
|
expectedMdPath: o.expectedMdPath
|
|
14744
14762
|
}));
|
|
14745
|
-
|
|
14763
|
+
const scheduled = orderAnalyzersByPhase(analyzers);
|
|
14764
|
+
for (const analyzer of scheduled) {
|
|
14746
14765
|
const qualifiedId2 = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
14747
14766
|
const declaredContributions = readDeclaredContributions(analyzer);
|
|
14748
14767
|
const emitContribution = (nodePath, contributionId, payload) => {
|
|
@@ -14795,6 +14814,11 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
14795
14814
|
annotationContributions,
|
|
14796
14815
|
viewContributions,
|
|
14797
14816
|
orphanJobFiles,
|
|
14817
|
+
// `issues` is the live accumulator, mutated by `issues.push(...)`
|
|
14818
|
+
// below as each analyzer's emission lands. Late-phase analyzers
|
|
14819
|
+
// (`core/issue-counter`) read it to compute cross-analyzer
|
|
14820
|
+
// aggregates. Treat as read-only on the analyzer side.
|
|
14821
|
+
accumulatedIssues: issues,
|
|
14798
14822
|
...referenceablePaths ? { referenceablePaths } : {},
|
|
14799
14823
|
...cwd ? { cwd } : {},
|
|
14800
14824
|
...reservedNodePaths ? { reservedNodePaths } : {},
|
|
@@ -14811,6 +14835,12 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
14811
14835
|
}
|
|
14812
14836
|
return { issues, contributions };
|
|
14813
14837
|
}
|
|
14838
|
+
function orderAnalyzersByPhase(analyzers) {
|
|
14839
|
+
return analyzers.slice().sort((a, b) => phaseRank(a) - phaseRank(b));
|
|
14840
|
+
}
|
|
14841
|
+
function phaseRank(a) {
|
|
14842
|
+
return a.phase === "aggregate" ? 1 : 0;
|
|
14843
|
+
}
|
|
14814
14844
|
function validateIssue(analyzer, issue, emitter) {
|
|
14815
14845
|
const severity = issue.severity;
|
|
14816
14846
|
if (severity !== "error" && severity !== "warn" && severity !== "info") {
|
|
@@ -15955,7 +15985,7 @@ async function runScanInternal(_kernel, options) {
|
|
|
15955
15985
|
else walked.internalLinks.push(link);
|
|
15956
15986
|
}
|
|
15957
15987
|
walked.signals = resolved.resolvedSignals;
|
|
15958
|
-
const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes);
|
|
15988
|
+
const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes, activeProviderId);
|
|
15959
15989
|
walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
|
|
15960
15990
|
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
15961
15991
|
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
@@ -15978,11 +16008,15 @@ async function runScanInternal(_kernel, options) {
|
|
|
15978
16008
|
emitter,
|
|
15979
16009
|
hookDispatcher,
|
|
15980
16010
|
postWalkCtx.reservedNodePaths,
|
|
15981
|
-
walked.signals
|
|
16011
|
+
walked.signals,
|
|
16012
|
+
// Seed the accumulator with orchestrator-emitted frontmatter
|
|
16013
|
+
// issues so the aggregate phase (`core/issue-counter`) counts
|
|
16014
|
+
// them on the per-node chip. The seeds are echoed back on
|
|
16015
|
+
// `analyzerResult.issues`, no explicit push is needed below.
|
|
16016
|
+
walked.frontmatterIssues
|
|
15982
16017
|
);
|
|
15983
16018
|
mergeAnalyzerEmissions(walked, analyzerResult, exts.analyzers);
|
|
15984
16019
|
const issues = analyzerResult.issues;
|
|
15985
|
-
for (const issue of walked.frontmatterIssues) issues.push(issue);
|
|
15986
16020
|
const silenced = options.ignoreFilter ? (path) => options.ignoreFilter.ignores(path) : void 0;
|
|
15987
16021
|
const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues, silenced) : [];
|
|
15988
16022
|
const stats = buildScanStats(walked, issues, start);
|
|
@@ -15991,14 +16025,14 @@ async function runScanInternal(_kernel, options) {
|
|
|
15991
16025
|
await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
|
|
15992
16026
|
return buildScanReturn(walked, issues, renameOps, stats, options, setup);
|
|
15993
16027
|
}
|
|
15994
|
-
function buildPostWalkTransformCtx(providers, nodes) {
|
|
16028
|
+
function buildPostWalkTransformCtx(providers, nodes, activeProvider) {
|
|
15995
16029
|
const { kindRegistry, providerResolution, reservedNamesByProviderKind } = buildProviderIndexes(providers);
|
|
15996
16030
|
const reservedNodePaths = buildReservedNodePaths(
|
|
15997
16031
|
nodes,
|
|
15998
16032
|
kindRegistry,
|
|
15999
16033
|
reservedNamesByProviderKind
|
|
16000
16034
|
);
|
|
16001
|
-
return { kindRegistry, providerResolution, reservedNodePaths };
|
|
16035
|
+
return { kindRegistry, providerResolution, activeProvider, reservedNodePaths };
|
|
16002
16036
|
}
|
|
16003
16037
|
function buildProviderIndexes(providers) {
|
|
16004
16038
|
const kindRegistry = /* @__PURE__ */ new Map();
|
|
@@ -16450,9 +16484,18 @@ var SCAN_RUNNER_TEXTS = {
|
|
|
16450
16484
|
* markers (`.claude/`, `.codex/`, `AGENTS.md`, `.cursor/`) anywhere
|
|
16451
16485
|
* under cwd or the effective scan roots. Plain-markdown projects
|
|
16452
16486
|
* keep scanning fine; provider-specific extractors silently no-op
|
|
16453
|
-
* for this scan.
|
|
16487
|
+
* for this scan. Follows `context/cli-output-style.md` §3.1b
|
|
16488
|
+
* (two-line block, glyph + dim hint):
|
|
16489
|
+
* - line 1: `{{glyph}}` (yellow `⚠`) + headline naming the
|
|
16490
|
+
* missing markers,
|
|
16491
|
+
* - line 2 (indent 3): `{{hint}}`, dim, names the consequence
|
|
16492
|
+
* and the actionable next step.
|
|
16493
|
+
* Both the full block AND the bare hint are catalog-side so the
|
|
16494
|
+
* caller can wrap the hint in `ansi.dim(...)` without splitting
|
|
16495
|
+
* the template manually.
|
|
16454
16496
|
*/
|
|
16455
|
-
activeProviderNoMarkerWarning: "No provider markers detected (.claude/, .codex/, AGENTS.md, .cursor/)
|
|
16497
|
+
activeProviderNoMarkerWarning: "{{glyph}} No provider markers detected (.claude/, .codex/, AGENTS.md, .cursor/).\n {{hint}}\n",
|
|
16498
|
+
activeProviderNoMarkerWarningHint: "Scanning as universal markdown only; provider-specific link types (e.g. claude @-directives, /-commands) will not appear. Set `activeProvider` in .skill-map/settings.json or install a provider plugin to enable them.",
|
|
16456
16499
|
/**
|
|
16457
16500
|
* Active-provider bootstrap: filesystem auto-detect found exactly
|
|
16458
16501
|
* one marker and persisted the detected id to project settings.
|
|
@@ -16605,7 +16648,14 @@ async function bootstrapActiveProvider(opts) {
|
|
|
16605
16648
|
}
|
|
16606
16649
|
const detected = aggregateDetected(opts.cwd, opts.effectiveRoots, fromCwd.detected);
|
|
16607
16650
|
if (detected.length === 0) {
|
|
16608
|
-
opts.
|
|
16651
|
+
const warnGlyph = opts.style?.warnGlyph ?? "\u26A0";
|
|
16652
|
+
const dim = opts.style?.dim ?? ((s) => s);
|
|
16653
|
+
opts.printer.warn(
|
|
16654
|
+
tx(SCAN_RUNNER_TEXTS.activeProviderNoMarkerWarning, {
|
|
16655
|
+
glyph: warnGlyph,
|
|
16656
|
+
hint: dim(SCAN_RUNNER_TEXTS.activeProviderNoMarkerWarningHint)
|
|
16657
|
+
})
|
|
16658
|
+
);
|
|
16609
16659
|
return { kind: "ok", activeProvider: null, source: "none" };
|
|
16610
16660
|
}
|
|
16611
16661
|
if (detected.length === 1) {
|
|
@@ -18814,21 +18864,6 @@ import { Command as Command22, Option as Option21 } from "clipanion";
|
|
|
18814
18864
|
// cli/i18n/plugins.texts.ts
|
|
18815
18865
|
var PLUGINS_TEXTS = {
|
|
18816
18866
|
// --- enable / disable error guidance --------------------------------
|
|
18817
|
-
// Spec § A.7, granularity validation. The CLI rejects mismatched ids
|
|
18818
|
-
// up front (instead of silently writing a config_plugins row that the
|
|
18819
|
-
// runtime would later ignore) so the user learns the model immediately.
|
|
18820
|
-
/**
|
|
18821
|
-
* Granularity-mismatch errors share a structured shape:
|
|
18822
|
-
* ✕ <headline>
|
|
18823
|
-
* <fix-line>
|
|
18824
|
-
* <hint-line>
|
|
18825
|
-
* Glyph + indent + dim hint applied at the call site so all four
|
|
18826
|
-
* "wrong shape" advisories read the same way.
|
|
18827
|
-
*/
|
|
18828
|
-
granularityBundleRejectsQualified: "{{glyph}} '{{bundleId}}' has granularity=bundle.\n Use `sm plugins {{verb}} {{bundleId}}` to {{verb}} the whole bundle.\n {{hint}}\n",
|
|
18829
|
-
granularityBundleRejectsQualifiedHint: "Individual extensions inside a bundle-granularity plugin cannot be toggled.",
|
|
18830
|
-
granularityExtensionRejectsBundleId: "{{glyph}} '{{bundleId}}' has granularity=extension.\n Use `sm plugins {{verb}} {{bundleId}}/<ext-id>` to {{verb}} a single extension.\n {{hint}}\n",
|
|
18831
|
-
granularityExtensionRejectsBundleIdHint: "Run `sm plugins list` for the per-extension qualified ids.",
|
|
18832
18867
|
pluginNotFound: "{{glyph}} Plugin not found: {{id}}\n {{hint}}\n",
|
|
18833
18868
|
pluginNotFoundHint: "Run `sm plugins list` for discovered ids and the qualified extension ids.",
|
|
18834
18869
|
pluginLocked: '{{glyph}} Plugin "{{id}}" is locked by the host and cannot be toggled.\n {{hint}}\n',
|
|
@@ -18859,8 +18894,14 @@ var PLUGINS_TEXTS = {
|
|
|
18859
18894
|
// --- list verb -------------------------------------------------------
|
|
18860
18895
|
listEmpty: "No plugins discovered.\n",
|
|
18861
18896
|
// --- doctor verb -----------------------------------------------------
|
|
18862
|
-
/**
|
|
18863
|
-
|
|
18897
|
+
/**
|
|
18898
|
+
* One-line summary that opens the human doctor output. `enabled` is
|
|
18899
|
+
* the count of enabled extensions across every bundle (every
|
|
18900
|
+
* extension is independently toggle-able by its qualified id); the
|
|
18901
|
+
* value matches the row count rendered by `sm plugins list` once
|
|
18902
|
+
* disabled extensions are filtered out.
|
|
18903
|
+
*/
|
|
18904
|
+
doctorSummary: "plugins doctor: {{enabled}} enabled extension{{enabledPlural}} \xB7 {{issues}} issue{{issuesPlural}} \xB7 {{warnings}} warning{{warningsPlural}}\n\n",
|
|
18864
18905
|
/** Source breakdown row (built-in vs user). Indented 4 to match the status rows. */
|
|
18865
18906
|
doctorSourceRow: " {{label}} {{count}}\n",
|
|
18866
18907
|
/** Status breakdown table heading. */
|
|
@@ -18894,8 +18935,35 @@ var PLUGINS_TEXTS = {
|
|
|
18894
18935
|
toggleNeitherIdNorAllHint: "Examples: `sm plugins {{verb}} <id1> <id2>` (explicit set), `sm plugins {{verb}} --all` (every discovered plugin).",
|
|
18895
18936
|
toggleResolveError: "{{error}}",
|
|
18896
18937
|
toggleAppliedSingle: "{{verbPast}}: {{id}}\n",
|
|
18897
|
-
toggleAppliedManyHeader: "{{verbPast}}: {{count}}
|
|
18938
|
+
toggleAppliedManyHeader: "{{verbPast}}: {{count}} extension(s)\n",
|
|
18898
18939
|
toggleAppliedManyRow: " - {{id}}\n",
|
|
18940
|
+
/**
|
|
18941
|
+
* Macro expansion summary printed on stderr before the confirm
|
|
18942
|
+
* prompt (or before the `--yes` rejection). The block lists every
|
|
18943
|
+
* qualified extension id the bare bundle id resolves to, so the
|
|
18944
|
+
* user sees the exact set that would flip.
|
|
18945
|
+
*/
|
|
18946
|
+
bundleMacroHeader: "sm plugins {{verb}} {{bundleId}}: this will affect {{count}} extensions:\n",
|
|
18947
|
+
bundleMacroRow: " - {{id}}\n",
|
|
18948
|
+
/**
|
|
18949
|
+
* Interactive prompt rendered to a TTY by the macro path. The
|
|
18950
|
+
* `confirm` helper appends the `[y/N]` suffix from UTIL_TEXTS.
|
|
18951
|
+
*/
|
|
18952
|
+
bundleMacroConfirmPrompt: "Apply this {{verb}} to every listed extension?",
|
|
18953
|
+
/**
|
|
18954
|
+
* Stderr advisory when a TTY user answers no to the macro prompt.
|
|
18955
|
+
* The verb exits non-zero (ExitCode.Error) so callers can detect
|
|
18956
|
+
* the cancellation.
|
|
18957
|
+
*/
|
|
18958
|
+
bundleMacroCancelled: "Cancelled.\n",
|
|
18959
|
+
/**
|
|
18960
|
+
* Non-TTY rejection path: pipes / CI cannot prompt, so the verb
|
|
18961
|
+
* refuses unless `--yes` is set. The body lines come from
|
|
18962
|
+
* `bundleMacroHeader` / `bundleMacroRow` above; this template adds
|
|
18963
|
+
* the directed re-run hint.
|
|
18964
|
+
*/
|
|
18965
|
+
bundleMacroRequiresYes: "{{glyph}} Refusing to {{verb}} multiple extensions without confirmation.\n {{hint}}\n",
|
|
18966
|
+
bundleMacroRequiresYesHint: "Re-run with --yes to apply, or pass a qualified id `<bundle>/<extension>` for a single extension.",
|
|
18899
18967
|
// --- list / show renderers ------------------------------------------
|
|
18900
18968
|
rowStatusOk: "ok",
|
|
18901
18969
|
rowStatusOff: "off",
|
|
@@ -18941,17 +19009,15 @@ var PLUGINS_TEXTS = {
|
|
|
18941
19009
|
/** Extensions block heading, separated from the header by a blank line. */
|
|
18942
19010
|
detailExtensionsBlock: "\n",
|
|
18943
19011
|
/**
|
|
18944
|
-
* Extension row
|
|
18945
|
-
*
|
|
18946
|
-
* for {{kind}} and {{name}} is computed at render
|
|
18947
|
-
* align inside the block.
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
* Extension row WITHOUT per-extension glyph (granularity=bundle).
|
|
18952
|
-
* The bundle is the only toggle; per-extension status is implicit.
|
|
19012
|
+
* Extension row inside the bundle detail. Every extension is
|
|
19013
|
+
* independently toggle-able, so every row carries its own glyph
|
|
19014
|
+
* (✓ / ✕). Padding for {{kind}} and {{name}} is computed at render
|
|
19015
|
+
* time so columns align inside the block. `{{versionSuffix}}` is
|
|
19016
|
+
* either ` v<x.y.z>` (user plugins) or empty (built-in bundles,
|
|
19017
|
+
* which inherit the CLI version and do not maintain per-extension
|
|
19018
|
+
* versions of their own).
|
|
18953
19019
|
*/
|
|
18954
|
-
|
|
19020
|
+
detailExtensionRowGlyph: " {{glyph}} {{kind}} {{name}}{{versionSuffix}}\n",
|
|
18955
19021
|
detailVersionUnknown: "?",
|
|
18956
19022
|
detailCompatUnknown: "?",
|
|
18957
19023
|
/**
|
|
@@ -19052,26 +19118,23 @@ async function loadAll(opts) {
|
|
|
19052
19118
|
}
|
|
19053
19119
|
function builtInRows(resolveEnabled) {
|
|
19054
19120
|
return sortBundlesForPresentation(builtInBundles).map((bundle) => {
|
|
19055
|
-
const
|
|
19056
|
-
const extensions = bundle.extensions.map((ext) => extensionRowFromBuiltIn(ext, bundle, bundleEnabled, resolveEnabled));
|
|
19121
|
+
const extensions = bundle.extensions.map((ext) => extensionRowFromBuiltIn(ext, bundle, resolveEnabled));
|
|
19057
19122
|
const manifestSummary = bundle.extensions.map((ext) => `${ext.kind}:${qualifiedExtensionId(bundle.id, ext.id)}@${ext.version}`).join(", ");
|
|
19058
19123
|
return {
|
|
19059
19124
|
id: bundle.id,
|
|
19060
|
-
|
|
19061
|
-
enabled: bundleEnabled,
|
|
19125
|
+
enabled: extensions.some((e) => e.enabled),
|
|
19062
19126
|
description: bundle.description,
|
|
19063
19127
|
extensions,
|
|
19064
19128
|
manifestSummary
|
|
19065
19129
|
};
|
|
19066
19130
|
});
|
|
19067
19131
|
}
|
|
19068
|
-
function extensionRowFromBuiltIn(ext, bundle,
|
|
19069
|
-
const qualifiedEnabled = resolveEnabled(qualifiedExtensionId(bundle.id, ext.id));
|
|
19132
|
+
function extensionRowFromBuiltIn(ext, bundle, resolveEnabled) {
|
|
19070
19133
|
const row = {
|
|
19071
19134
|
id: ext.id,
|
|
19072
19135
|
kind: ext.kind,
|
|
19073
19136
|
version: ext.version,
|
|
19074
|
-
enabled: bundle.
|
|
19137
|
+
enabled: resolveEnabled(qualifiedExtensionId(bundle.id, ext.id)),
|
|
19075
19138
|
description: ext.description ?? ""
|
|
19076
19139
|
};
|
|
19077
19140
|
if (ext.entry !== void 0) row.entry = ext.entry;
|
|
@@ -19125,14 +19188,14 @@ var PluginsListCommand = class extends SmCommand {
|
|
|
19125
19188
|
return ExitCode.Ok;
|
|
19126
19189
|
}
|
|
19127
19190
|
const ansi = this.ansiFor("stdout");
|
|
19128
|
-
this.printer.data(renderListHuman(builtIns2, plugins, ansi));
|
|
19191
|
+
this.printer.data(renderListHuman(builtIns2, plugins, resolveEnabled, ansi));
|
|
19129
19192
|
return ExitCode.Ok;
|
|
19130
19193
|
}
|
|
19131
19194
|
};
|
|
19132
|
-
function renderListHuman(builtIns2, plugins, ansi) {
|
|
19195
|
+
function renderListHuman(builtIns2, plugins, resolveEnabled, ansi) {
|
|
19133
19196
|
const rows = [
|
|
19134
19197
|
...builtIns2.map(builtInToListRow),
|
|
19135
|
-
...plugins.map(pluginToListRow)
|
|
19198
|
+
...plugins.map((p) => pluginToListRow(p, resolveEnabled))
|
|
19136
19199
|
];
|
|
19137
19200
|
const idWidth = Math.max(...rows.map((r) => r.id.length));
|
|
19138
19201
|
const countWidth = Math.max(
|
|
@@ -19163,9 +19226,9 @@ function renderListHuman(builtIns2, plugins, ansi) {
|
|
|
19163
19226
|
return lines.join("\n") + "\n" + PLUGINS_TEXTS.listTipShow;
|
|
19164
19227
|
}
|
|
19165
19228
|
function builtInToListRow(b) {
|
|
19166
|
-
const names = b.
|
|
19229
|
+
const names = b.extensions.map(
|
|
19167
19230
|
(e) => e.enabled ? e.id : `${PLUGINS_TEXTS.rowGlyphOff} ${e.id}`
|
|
19168
|
-
)
|
|
19231
|
+
);
|
|
19169
19232
|
return {
|
|
19170
19233
|
id: b.id,
|
|
19171
19234
|
enabled: b.enabled,
|
|
@@ -19173,9 +19236,14 @@ function builtInToListRow(b) {
|
|
|
19173
19236
|
names
|
|
19174
19237
|
};
|
|
19175
19238
|
}
|
|
19176
|
-
function pluginToListRow(p) {
|
|
19177
|
-
const
|
|
19178
|
-
const
|
|
19239
|
+
function pluginToListRow(p, resolveEnabled) {
|
|
19240
|
+
const isLoaded = p.status === "enabled";
|
|
19241
|
+
const extensions = p.extensions ?? [];
|
|
19242
|
+
const enabled = isLoaded ? extensions.length === 0 || extensions.some((e) => resolveEnabled(qualifiedExtensionId(p.id, e.id))) : false;
|
|
19243
|
+
const names = extensions.map((e) => {
|
|
19244
|
+
const safeId = sanitizeForTerminal(e.id);
|
|
19245
|
+
return resolveEnabled(qualifiedExtensionId(p.id, e.id)) ? safeId : `${PLUGINS_TEXTS.rowGlyphOff} ${safeId}`;
|
|
19246
|
+
});
|
|
19179
19247
|
const reason = p.status === "enabled" ? void 0 : sanitizeForTerminal(p.reason ?? "") || void 0;
|
|
19180
19248
|
return {
|
|
19181
19249
|
id: sanitizeForTerminal(p.id),
|
|
@@ -19213,11 +19281,10 @@ var PluginsShowCommand = class extends SmCommand {
|
|
|
19213
19281
|
Accepts a bundle / plugin id (\`core\`, \`claude\`, \`my-plugin\`)
|
|
19214
19282
|
or a qualified extension id (\`core/<ext-id>\`,
|
|
19215
19283
|
\`<plugin>/<ext-id>\`). When given a qualified id, validates the
|
|
19216
|
-
extension exists and renders
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
\`sm plugins
|
|
19220
|
-
cleanly here too.
|
|
19284
|
+
extension exists and renders a single-extension detail block.
|
|
19285
|
+
The bare form renders the parent bundle's detail with per-extension
|
|
19286
|
+
status. The same id shapes \`sm plugins enable\` and
|
|
19287
|
+
\`sm plugins disable\` accept resolve cleanly here too.
|
|
19221
19288
|
`
|
|
19222
19289
|
});
|
|
19223
19290
|
id = Option22.String({ required: true });
|
|
@@ -19348,16 +19415,13 @@ function sortExtensionsCanonical(exts) {
|
|
|
19348
19415
|
});
|
|
19349
19416
|
}
|
|
19350
19417
|
function renderBuiltInDetail(b, ansi) {
|
|
19351
|
-
const
|
|
19352
|
-
const glyph = enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
19418
|
+
const glyph = b.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
19353
19419
|
const count = b.extensions.length;
|
|
19354
|
-
const qualify = b.granularity === "extension";
|
|
19355
19420
|
const sorted = sortExtensionsCanonical(b.extensions);
|
|
19356
19421
|
const items = sorted.map((ext) => ({
|
|
19357
|
-
glyph:
|
|
19422
|
+
glyph: ext.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff),
|
|
19358
19423
|
kind: ext.kind,
|
|
19359
|
-
name:
|
|
19360
|
-
version: ext.version
|
|
19424
|
+
name: `${b.id}/${ext.id}`
|
|
19361
19425
|
}));
|
|
19362
19426
|
return tx(PLUGINS_TEXTS.detailHeaderBuiltIn, {
|
|
19363
19427
|
glyph,
|
|
@@ -19425,15 +19489,18 @@ function renderPluginDetailFields(match) {
|
|
|
19425
19489
|
function collectPluginExtensionItems(match, ansi) {
|
|
19426
19490
|
const enabled = match.status === "enabled";
|
|
19427
19491
|
if (!enabled || !match.extensions) return [];
|
|
19428
|
-
const qualify = match.granularity === "extension";
|
|
19429
19492
|
const safeBundleId = sanitizeForTerminal(match.id);
|
|
19430
19493
|
const sorted = sortExtensionsCanonical(match.extensions);
|
|
19431
19494
|
return sorted.map((ext) => {
|
|
19432
19495
|
const safeExtId = sanitizeForTerminal(ext.id);
|
|
19433
19496
|
return {
|
|
19434
|
-
|
|
19497
|
+
// User plugins surfaced via `loadAll` already filter on the
|
|
19498
|
+
// resolver, so a reachable extension on this surface is enabled
|
|
19499
|
+
// by construction. The disabled path goes through the bundle
|
|
19500
|
+
// status header above (✕ on the row).
|
|
19501
|
+
glyph: ansi.green(PLUGINS_TEXTS.rowGlyphOk),
|
|
19435
19502
|
kind: sanitizeForTerminal(ext.kind),
|
|
19436
|
-
name:
|
|
19503
|
+
name: `${safeBundleId}/${safeExtId}`,
|
|
19437
19504
|
version: sanitizeForTerminal(ext.version)
|
|
19438
19505
|
};
|
|
19439
19506
|
});
|
|
@@ -19441,29 +19508,21 @@ function collectPluginExtensionItems(match, ansi) {
|
|
|
19441
19508
|
function renderExtensionItems(items) {
|
|
19442
19509
|
if (items.length === 0) return "";
|
|
19443
19510
|
const kindWidth = Math.max(...items.map((i) => i.kind.length));
|
|
19444
|
-
const
|
|
19511
|
+
const anyVersion = items.some((i) => i.version !== void 0);
|
|
19512
|
+
const nameWidth = anyVersion ? Math.max(...items.map((i) => i.name.length)) : 0;
|
|
19445
19513
|
const out = [];
|
|
19446
19514
|
for (const item of items) {
|
|
19447
19515
|
const kind = item.kind.padEnd(kindWidth);
|
|
19448
|
-
const name = item.name.padEnd(nameWidth);
|
|
19449
|
-
|
|
19450
|
-
|
|
19451
|
-
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
|
|
19457
|
-
|
|
19458
|
-
} else {
|
|
19459
|
-
out.push(
|
|
19460
|
-
tx(PLUGINS_TEXTS.detailExtensionRowBare, {
|
|
19461
|
-
kind,
|
|
19462
|
-
name,
|
|
19463
|
-
version: item.version
|
|
19464
|
-
})
|
|
19465
|
-
);
|
|
19466
|
-
}
|
|
19516
|
+
const name = anyVersion ? item.name.padEnd(nameWidth) : item.name;
|
|
19517
|
+
const versionSuffix = item.version ? ` v${item.version}` : "";
|
|
19518
|
+
out.push(
|
|
19519
|
+
tx(PLUGINS_TEXTS.detailExtensionRowGlyph, {
|
|
19520
|
+
glyph: item.glyph,
|
|
19521
|
+
kind,
|
|
19522
|
+
name,
|
|
19523
|
+
versionSuffix
|
|
19524
|
+
})
|
|
19525
|
+
);
|
|
19467
19526
|
}
|
|
19468
19527
|
return out.join("");
|
|
19469
19528
|
}
|
|
@@ -19474,7 +19533,7 @@ function renderBuiltInExtensionDetail(bundleId, ext, ansi) {
|
|
|
19474
19533
|
qualifiedId: sanitizeForTerminal(`${bundleId}/${ext.id}`),
|
|
19475
19534
|
source: ansi.dim(PLUGINS_TEXTS.sourceBuiltIn)
|
|
19476
19535
|
});
|
|
19477
|
-
const meta = { kind: ext.kind
|
|
19536
|
+
const meta = { kind: ext.kind };
|
|
19478
19537
|
if (ext.description) meta.description = ext.description;
|
|
19479
19538
|
if (ext.entry !== void 0) meta.entry = ext.entry;
|
|
19480
19539
|
return header + "\n" + renderExtensionFields(meta);
|
|
@@ -19513,7 +19572,12 @@ function readInstanceMeta(instance) {
|
|
|
19513
19572
|
function renderExtensionFields(meta) {
|
|
19514
19573
|
const fields = [];
|
|
19515
19574
|
fields.push({ label: PLUGINS_TEXTS.detailFieldKind, value: sanitizeForTerminal(meta.kind) });
|
|
19516
|
-
|
|
19575
|
+
if (meta.version) {
|
|
19576
|
+
fields.push({
|
|
19577
|
+
label: PLUGINS_TEXTS.detailFieldVersion,
|
|
19578
|
+
value: sanitizeForTerminal(meta.version)
|
|
19579
|
+
});
|
|
19580
|
+
}
|
|
19517
19581
|
if (meta.stability) {
|
|
19518
19582
|
fields.push({
|
|
19519
19583
|
label: PLUGINS_TEXTS.detailFieldStability,
|
|
@@ -19567,7 +19631,7 @@ var PluginsDoctorCommand = class extends SmCommand {
|
|
|
19567
19631
|
const plugins = await loadAll({ pluginDir: this.pluginDir });
|
|
19568
19632
|
const resolveEnabled = await buildResolver();
|
|
19569
19633
|
const builtIns2 = builtInRows(resolveEnabled);
|
|
19570
|
-
const counts = countByStatus(builtIns2, plugins);
|
|
19634
|
+
const counts = countByStatus(builtIns2, plugins, resolveEnabled);
|
|
19571
19635
|
const knownKinds = collectKnownKinds(plugins);
|
|
19572
19636
|
const applicableKindWarnings = collectApplicableKindWarnings(plugins, knownKinds);
|
|
19573
19637
|
const unknownSlotWarnings = collectUnknownSlotWarnings(plugins, KNOWN_SLOT_NAMES);
|
|
@@ -19602,6 +19666,7 @@ var PluginsDoctorCommand = class extends SmCommand {
|
|
|
19602
19666
|
this.printer.data(
|
|
19603
19667
|
tx(PLUGINS_TEXTS.doctorSummary, {
|
|
19604
19668
|
enabled,
|
|
19669
|
+
enabledPlural: enabled === 1 ? "" : "s",
|
|
19605
19670
|
issues: badCount,
|
|
19606
19671
|
issuesPlural: badCount === 1 ? "" : "s",
|
|
19607
19672
|
warnings,
|
|
@@ -19699,7 +19764,7 @@ var PluginsDoctorCommand = class extends SmCommand {
|
|
|
19699
19764
|
}
|
|
19700
19765
|
}
|
|
19701
19766
|
};
|
|
19702
|
-
function countByStatus(builtIns2, plugins) {
|
|
19767
|
+
function countByStatus(builtIns2, plugins, resolveEnabled) {
|
|
19703
19768
|
const counts = {
|
|
19704
19769
|
enabled: 0,
|
|
19705
19770
|
disabled: 0,
|
|
@@ -19710,15 +19775,20 @@ function countByStatus(builtIns2, plugins) {
|
|
|
19710
19775
|
"id-collision": 0
|
|
19711
19776
|
};
|
|
19712
19777
|
for (const b of builtIns2) {
|
|
19713
|
-
|
|
19714
|
-
counts[
|
|
19715
|
-
}
|
|
19716
|
-
|
|
19717
|
-
|
|
19718
|
-
|
|
19778
|
+
for (const ext of b.extensions) {
|
|
19779
|
+
counts[ext.enabled ? "enabled" : "disabled"]++;
|
|
19780
|
+
}
|
|
19781
|
+
}
|
|
19782
|
+
for (const p of plugins) {
|
|
19783
|
+
if (p.status !== "enabled" || !p.extensions) {
|
|
19784
|
+
counts[p.status]++;
|
|
19785
|
+
continue;
|
|
19786
|
+
}
|
|
19787
|
+
for (const ext of p.extensions) {
|
|
19788
|
+
const enabled = resolveEnabled(`${p.id}/${ext.id}`);
|
|
19789
|
+
counts[enabled ? "enabled" : "disabled"]++;
|
|
19719
19790
|
}
|
|
19720
19791
|
}
|
|
19721
|
-
for (const p of plugins) counts[p.status]++;
|
|
19722
19792
|
return counts;
|
|
19723
19793
|
}
|
|
19724
19794
|
function forEachProviderInstance(plugins, callback) {
|
|
@@ -19911,6 +19981,9 @@ function buildDoctorJsonEnvelope(args2) {
|
|
|
19911
19981
|
import { Command as Command25, Option as Option24 } from "clipanion";
|
|
19912
19982
|
var TogglePluginsBase = class extends SmCommand {
|
|
19913
19983
|
all = Option24.Boolean("--all", false);
|
|
19984
|
+
yes = Option24.Boolean("--yes,-y", false, {
|
|
19985
|
+
description: "Skip the interactive confirm when a bare bundle id (or --all) fans the toggle out across multiple extensions."
|
|
19986
|
+
});
|
|
19914
19987
|
ids = Option24.Rest({ name: "ids" });
|
|
19915
19988
|
async toggle(enabled) {
|
|
19916
19989
|
const verb = enabled ? "enable" : "disable";
|
|
@@ -19919,14 +19992,17 @@ var TogglePluginsBase = class extends SmCommand {
|
|
|
19919
19992
|
if (argError !== null) return argError;
|
|
19920
19993
|
const plugins = await loadAll({ pluginDir: void 0 });
|
|
19921
19994
|
const catalogue = bundleCatalogue(plugins);
|
|
19922
|
-
const targetsResult = this.#pickTargets(catalogue,
|
|
19995
|
+
const targetsResult = this.#pickTargets(catalogue, stderrAnsi);
|
|
19923
19996
|
if (typeof targetsResult === "number") return targetsResult;
|
|
19924
19997
|
let targets = targetsResult;
|
|
19998
|
+
const macroOk = await this.#confirmMacroIfNeeded(targets, verb, stderrAnsi);
|
|
19999
|
+
if (!macroOk) return ExitCode.Error;
|
|
19925
20000
|
const lockError = this.#applyLockGate(targets, stderrAnsi);
|
|
19926
20001
|
if (typeof lockError === "number") return lockError;
|
|
19927
20002
|
targets = lockError;
|
|
19928
|
-
|
|
19929
|
-
this.#
|
|
20003
|
+
const keys = expandToKeys(targets);
|
|
20004
|
+
await this.#persistKeys(keys, enabled);
|
|
20005
|
+
this.#renderSuccess(keys, enabled);
|
|
19930
20006
|
return ExitCode.Ok;
|
|
19931
20007
|
}
|
|
19932
20008
|
/**
|
|
@@ -19959,87 +20035,166 @@ var TogglePluginsBase = class extends SmCommand {
|
|
|
19959
20035
|
/**
|
|
19960
20036
|
* Resolve `<id>...` against the catalogue or fan out via `--all`.
|
|
19961
20037
|
* Returns the target list on success, or the exit code on a
|
|
19962
|
-
* directed-error path (unknown id,
|
|
20038
|
+
* directed-error path (unknown id, malformed qualified id).
|
|
19963
20039
|
*
|
|
19964
|
-
*
|
|
19965
|
-
*
|
|
19966
|
-
*
|
|
19967
|
-
*
|
|
19968
|
-
*
|
|
19969
|
-
* so `--all` skips them here too and the real "disable every core
|
|
19970
|
-
* extension" intent is served by `--no-built-ins` on `sm scan`.
|
|
19971
|
-
*
|
|
19972
|
-
* Variadic mode is all-or-nothing: the first bad id aborts the
|
|
19973
|
-
* batch before any DB write, so the user never lands in a partial
|
|
19974
|
-
* state. Repeated ids in the same call are deduped.
|
|
20040
|
+
* Repeated ids in the same call are deduped at the target level
|
|
20041
|
+
* (`origin === 'bare'` and `origin === 'qualified'` rows stay
|
|
20042
|
+
* distinct so the macro-confirm path can address each correctly).
|
|
20043
|
+
* The first unknown id aborts the batch before any DB write so the
|
|
20044
|
+
* user never lands in a partial state.
|
|
19975
20045
|
*/
|
|
19976
|
-
#pickTargets(catalogue,
|
|
20046
|
+
#pickTargets(catalogue, ansi) {
|
|
19977
20047
|
if (this.all) {
|
|
19978
|
-
return catalogue.
|
|
20048
|
+
return catalogue.map((b) => ({
|
|
20049
|
+
origin: "bare",
|
|
20050
|
+
bundleId: b.id,
|
|
20051
|
+
keys: b.extensionIds.map((extId) => qualifiedExtensionId(b.id, extId))
|
|
20052
|
+
}));
|
|
19979
20053
|
}
|
|
19980
|
-
const
|
|
20054
|
+
const out = [];
|
|
20055
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19981
20056
|
for (const rawId of this.ids) {
|
|
19982
|
-
const resolved = resolveToggleTarget(rawId, catalogue,
|
|
20057
|
+
const resolved = resolveToggleTarget(rawId, catalogue, ansi);
|
|
19983
20058
|
if ("error" in resolved) {
|
|
19984
20059
|
this.printer.error(tx(PLUGINS_TEXTS.toggleResolveError, { error: resolved.error }));
|
|
19985
20060
|
return ExitCode.NotFound;
|
|
19986
20061
|
}
|
|
19987
|
-
keys.
|
|
20062
|
+
const novelKeys = resolved.keys.filter((k) => !seen.has(k));
|
|
20063
|
+
if (novelKeys.length === 0) continue;
|
|
20064
|
+
for (const k of novelKeys) seen.add(k);
|
|
20065
|
+
out.push({ ...resolved, keys: novelKeys });
|
|
19988
20066
|
}
|
|
19989
|
-
return
|
|
20067
|
+
return out;
|
|
20068
|
+
}
|
|
20069
|
+
/**
|
|
20070
|
+
* Macro gate: when the request would fan a single user input out
|
|
20071
|
+
* across more than one extension (either `--all` or a bare bundle
|
|
20072
|
+
* id whose bundle holds ≥2 extensions), confirm the intent.
|
|
20073
|
+
*
|
|
20074
|
+
* Resolution order:
|
|
20075
|
+
* 1. `--yes` flag: skip the prompt entirely.
|
|
20076
|
+
* 2. TTY stdin: render the list + ask interactively (`[y/N]`).
|
|
20077
|
+
* 3. Non-TTY (CI / pipe / agent harness): refuse with a directed
|
|
20078
|
+
* message that names the extensions and points at `--yes`.
|
|
20079
|
+
*
|
|
20080
|
+
* Returns `true` when the verb should proceed, `false` when it
|
|
20081
|
+
* should abort. Single-extension targets (bare bundle id mapping to
|
|
20082
|
+
* one child, or qualified ids) skip the gate uniformly.
|
|
20083
|
+
*/
|
|
20084
|
+
// Cyclomatic count comes from the three-stage gate (--yes shortcut,
|
|
20085
|
+
// TTY interactive path, non-TTY rejection) folded over the targets
|
|
20086
|
+
// loop. Splitting them scatters the contract without making the
|
|
20087
|
+
// algorithm clearer.
|
|
20088
|
+
// eslint-disable-next-line complexity
|
|
20089
|
+
async #confirmMacroIfNeeded(targets, verb, ansi) {
|
|
20090
|
+
const macroTargets = targets.filter((t) => requiresMacroConfirm(t));
|
|
20091
|
+
if (macroTargets.length === 0) return true;
|
|
20092
|
+
if (this.yes) return true;
|
|
20093
|
+
const isTty = Boolean(this.context.stdin && "isTTY" in this.context.stdin && this.context.stdin.isTTY);
|
|
20094
|
+
for (const target of macroTargets) {
|
|
20095
|
+
const bundleLabel = target.origin === "bare" ? target.bundleId ?? "--all" : "--all";
|
|
20096
|
+
this.printer.info(
|
|
20097
|
+
tx(PLUGINS_TEXTS.bundleMacroHeader, {
|
|
20098
|
+
verb,
|
|
20099
|
+
bundleId: sanitizeForTerminal(bundleLabel),
|
|
20100
|
+
count: target.keys.length
|
|
20101
|
+
})
|
|
20102
|
+
);
|
|
20103
|
+
for (const key of target.keys) {
|
|
20104
|
+
this.printer.info(tx(PLUGINS_TEXTS.bundleMacroRow, { id: sanitizeForTerminal(key) }));
|
|
20105
|
+
}
|
|
20106
|
+
}
|
|
20107
|
+
if (!isTty) {
|
|
20108
|
+
this.printer.error(
|
|
20109
|
+
tx(PLUGINS_TEXTS.bundleMacroRequiresYes, {
|
|
20110
|
+
glyph: ansi.red("\u2715"),
|
|
20111
|
+
verb,
|
|
20112
|
+
hint: ansi.dim(PLUGINS_TEXTS.bundleMacroRequiresYesHint)
|
|
20113
|
+
})
|
|
20114
|
+
);
|
|
20115
|
+
return false;
|
|
20116
|
+
}
|
|
20117
|
+
const ok = await confirm(
|
|
20118
|
+
tx(PLUGINS_TEXTS.bundleMacroConfirmPrompt, { verb }),
|
|
20119
|
+
{ stdin: this.context.stdin, stderr: this.context.stderr }
|
|
20120
|
+
);
|
|
20121
|
+
if (!ok) {
|
|
20122
|
+
this.printer.info(PLUGINS_TEXTS.bundleMacroCancelled);
|
|
20123
|
+
}
|
|
20124
|
+
return ok;
|
|
19990
20125
|
}
|
|
19991
20126
|
/**
|
|
19992
20127
|
* Host lock, see `src/kernel/config/locked-plugins.ts`. Bulk modes
|
|
19993
|
-
* (`--all
|
|
19994
|
-
*
|
|
19995
|
-
*
|
|
19996
|
-
* intended target was
|
|
20128
|
+
* (`--all`, an explicit batch of >1 targets, or a macro expansion
|
|
20129
|
+
* with >1 keys) silently skip locked extensions so the user can
|
|
20130
|
+
* still toggle the rest. Single-extension mode surfaces a directed
|
|
20131
|
+
* exit-5 message so the user knows their one intended target was
|
|
20132
|
+
* refused.
|
|
19997
20133
|
*/
|
|
19998
20134
|
#applyLockGate(targets, ansi) {
|
|
19999
|
-
|
|
20000
|
-
const
|
|
20001
|
-
if (
|
|
20135
|
+
const totalKeys = targets.reduce((acc, t) => acc + t.keys.length, 0);
|
|
20136
|
+
const bulk = this.all || this.ids.length > 1 || totalKeys > 1;
|
|
20137
|
+
if (bulk) {
|
|
20138
|
+
return targets.map((t) => ({ ...t, keys: t.keys.filter((k) => !isPluginLocked(k)) }));
|
|
20139
|
+
}
|
|
20140
|
+
const onlyKey = targets[0]?.keys[0];
|
|
20141
|
+
if (!onlyKey || !isPluginLocked(onlyKey)) return targets;
|
|
20002
20142
|
this.printer.error(
|
|
20003
20143
|
tx(PLUGINS_TEXTS.pluginLocked, {
|
|
20004
20144
|
glyph: ansi.red("\u2715"),
|
|
20005
|
-
id: sanitizeForTerminal(
|
|
20145
|
+
id: sanitizeForTerminal(onlyKey),
|
|
20006
20146
|
hint: ansi.dim(PLUGINS_TEXTS.pluginLockedHint)
|
|
20007
20147
|
})
|
|
20008
20148
|
);
|
|
20009
20149
|
return ExitCode.NotFound;
|
|
20010
20150
|
}
|
|
20011
20151
|
/**
|
|
20012
|
-
* Persist
|
|
20013
|
-
* the plugin's `scan_contributions` rows immediately (matches
|
|
20014
|
-
* BFF route, see `server/routes/plugins.ts:applyChangeToAdapter`).
|
|
20015
|
-
*
|
|
20016
|
-
*
|
|
20017
|
-
* how the catalog sweep groups rows.
|
|
20152
|
+
* Persist every qualified id in `config_plugins`. On disable, also
|
|
20153
|
+
* purge the plugin's `scan_contributions` rows immediately (matches
|
|
20154
|
+
* the BFF route, see `server/routes/plugins.ts:applyChangeToAdapter`).
|
|
20155
|
+
* Every key is `<bundle>/<ext>` shape so the contribution purge can
|
|
20156
|
+
* split into `(pluginId, extensionId)` cleanly.
|
|
20018
20157
|
*/
|
|
20019
|
-
async #
|
|
20158
|
+
async #persistKeys(keys, enabled) {
|
|
20020
20159
|
const ctx = defaultRuntimeContext();
|
|
20021
20160
|
const dbPath = resolveDbPath({ db: void 0, cwd: ctx.cwd });
|
|
20022
20161
|
await withSqlite({ databasePath: dbPath, autoBackup: false }, async (adapter) => {
|
|
20023
|
-
for (const id of
|
|
20162
|
+
for (const id of keys) {
|
|
20024
20163
|
await adapter.pluginConfig.set(id, enabled);
|
|
20025
20164
|
if (!enabled) await purgeContributionsFor(adapter, id);
|
|
20026
20165
|
}
|
|
20027
20166
|
});
|
|
20028
20167
|
}
|
|
20029
|
-
#renderSuccess(
|
|
20168
|
+
#renderSuccess(keys, enabled) {
|
|
20030
20169
|
const verbPast = enabled ? "enabled" : "disabled";
|
|
20031
|
-
if (
|
|
20032
|
-
this.printer.data(tx(PLUGINS_TEXTS.toggleAppliedSingle, { verbPast, id:
|
|
20170
|
+
if (keys.length === 1) {
|
|
20171
|
+
this.printer.data(tx(PLUGINS_TEXTS.toggleAppliedSingle, { verbPast, id: keys[0] }));
|
|
20033
20172
|
} else {
|
|
20034
20173
|
this.printer.data(
|
|
20035
|
-
tx(PLUGINS_TEXTS.toggleAppliedManyHeader, { verbPast, count:
|
|
20174
|
+
tx(PLUGINS_TEXTS.toggleAppliedManyHeader, { verbPast, count: keys.length })
|
|
20036
20175
|
);
|
|
20037
|
-
for (const id of
|
|
20176
|
+
for (const id of keys) {
|
|
20038
20177
|
this.printer.data(tx(PLUGINS_TEXTS.toggleAppliedManyRow, { id }));
|
|
20039
20178
|
}
|
|
20040
20179
|
}
|
|
20041
20180
|
}
|
|
20042
20181
|
};
|
|
20182
|
+
function requiresMacroConfirm(target) {
|
|
20183
|
+
if (target.origin !== "bare") return false;
|
|
20184
|
+
return target.keys.length >= 2;
|
|
20185
|
+
}
|
|
20186
|
+
function expandToKeys(targets) {
|
|
20187
|
+
const out = [];
|
|
20188
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20189
|
+
for (const t of targets) {
|
|
20190
|
+
for (const k of t.keys) {
|
|
20191
|
+
if (seen.has(k)) continue;
|
|
20192
|
+
seen.add(k);
|
|
20193
|
+
out.push(k);
|
|
20194
|
+
}
|
|
20195
|
+
}
|
|
20196
|
+
return out;
|
|
20197
|
+
}
|
|
20043
20198
|
async function purgeContributionsFor(adapter, id) {
|
|
20044
20199
|
const slash = id.indexOf("/");
|
|
20045
20200
|
if (slash < 0) {
|
|
@@ -20052,25 +20207,24 @@ var PluginsEnableCommand = class extends TogglePluginsBase {
|
|
|
20052
20207
|
static paths = [["plugins", "enable"]];
|
|
20053
20208
|
static usage = Command25.Usage({
|
|
20054
20209
|
category: "Plugins",
|
|
20055
|
-
description: "Enable one or more
|
|
20210
|
+
description: "Enable one or more extensions (or --all). Persists in config_plugins.",
|
|
20056
20211
|
details: `
|
|
20057
|
-
Writes a row to config_plugins with enabled=1 per
|
|
20058
|
-
precedence over the team-shared baseline at
|
|
20212
|
+
Writes a row to config_plugins with enabled=1 per qualified
|
|
20213
|
+
extension id. Takes precedence over the team-shared baseline at
|
|
20059
20214
|
settings.json#/plugins/<id>/enabled. Use sm plugins disable to
|
|
20060
20215
|
flip; sm config reset plugins.<id>.enabled drops the settings.json
|
|
20061
20216
|
baseline.
|
|
20062
20217
|
|
|
20063
|
-
Accepts
|
|
20064
|
-
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
|
|
20218
|
+
Accepts qualified ids (\`claude/at-directive\`) and bare bundle
|
|
20219
|
+
ids (\`claude\`, which fans the toggle out across every extension
|
|
20220
|
+
inside the bundle). Multi-extension bundles need --yes (or an
|
|
20221
|
+
interactive TTY confirm) to avoid flipping 27 core extensions by
|
|
20222
|
+
accident. Single-extension bundles (openai, agent-skills,
|
|
20223
|
+
antigravity) apply without prompting.
|
|
20068
20224
|
|
|
20069
|
-
|
|
20070
|
-
|
|
20071
|
-
|
|
20072
|
-
only qualified ids '<bundle>/<ext-id>'. Mismatches are rejected
|
|
20073
|
-
with directed guidance.
|
|
20225
|
+
Batches are all-or-nothing: a single unknown id aborts before
|
|
20226
|
+
any write. Repeated ids are deduped. Locked extensions inside a
|
|
20227
|
+
batch are silently skipped.
|
|
20074
20228
|
`
|
|
20075
20229
|
});
|
|
20076
20230
|
async run() {
|
|
@@ -20081,24 +20235,23 @@ var PluginsDisableCommand = class extends TogglePluginsBase {
|
|
|
20081
20235
|
static paths = [["plugins", "disable"]];
|
|
20082
20236
|
static usage = Command25.Usage({
|
|
20083
20237
|
category: "Plugins",
|
|
20084
|
-
description: "Disable one or more
|
|
20238
|
+
description: "Disable one or more extensions (or --all). Persists in config_plugins; does not delete files.",
|
|
20085
20239
|
details: `
|
|
20086
|
-
Writes a row to config_plugins with enabled=0 per
|
|
20087
|
-
still surfaces the plugin in
|
|
20088
|
-
status=disabled;
|
|
20089
|
-
|
|
20090
|
-
|
|
20091
|
-
Accepts
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
|
|
20240
|
+
Writes a row to config_plugins with enabled=0 per qualified
|
|
20241
|
+
extension id. Discovery still surfaces the plugin in
|
|
20242
|
+
sm plugins list, but with status=disabled; the kernel will not
|
|
20243
|
+
run any of its disabled extensions.
|
|
20244
|
+
|
|
20245
|
+
Accepts qualified ids (\`core/markdown-link\`) and bare bundle
|
|
20246
|
+
ids (\`core\`, which fans the toggle out across every extension
|
|
20247
|
+
inside the bundle). Multi-extension bundles need --yes (or an
|
|
20248
|
+
interactive TTY confirm) to avoid flipping 27 core extensions by
|
|
20249
|
+
accident. Single-extension bundles (openai, agent-skills,
|
|
20250
|
+
antigravity) apply without prompting.
|
|
20251
|
+
|
|
20252
|
+
Batches are all-or-nothing: a single unknown id aborts before
|
|
20253
|
+
any write. Repeated ids are deduped. Locked extensions inside a
|
|
20095
20254
|
batch are silently skipped.
|
|
20096
|
-
|
|
20097
|
-
Granularity: a bundle-granularity plugin (default for user plugins,
|
|
20098
|
-
and the built-in 'claude' bundle) accepts only the bundle id. An
|
|
20099
|
-
extension-granularity plugin (the built-in 'core' bundle) accepts
|
|
20100
|
-
only qualified ids '<bundle>/<ext-id>'. Mismatches are rejected
|
|
20101
|
-
with directed guidance.
|
|
20102
20255
|
`
|
|
20103
20256
|
});
|
|
20104
20257
|
async run() {
|
|
@@ -20110,23 +20263,21 @@ function bundleCatalogue(plugins) {
|
|
|
20110
20263
|
for (const bundle of builtInBundles) {
|
|
20111
20264
|
out.push({
|
|
20112
20265
|
id: bundle.id,
|
|
20113
|
-
granularity: bundle.granularity,
|
|
20114
20266
|
extensionIds: bundle.extensions.map((e) => e.id)
|
|
20115
20267
|
});
|
|
20116
20268
|
}
|
|
20117
20269
|
for (const p of plugins) {
|
|
20118
20270
|
out.push({
|
|
20119
20271
|
id: p.id,
|
|
20120
|
-
granularity: p.granularity ?? "bundle",
|
|
20121
20272
|
extensionIds: p.extensions?.map((e) => e.id) ?? []
|
|
20122
20273
|
});
|
|
20123
20274
|
}
|
|
20124
20275
|
return out;
|
|
20125
20276
|
}
|
|
20126
|
-
function resolveToggleTarget(id, catalogue,
|
|
20127
|
-
return id.includes("/") ? resolveQualifiedToggle(id, catalogue,
|
|
20277
|
+
function resolveToggleTarget(id, catalogue, ansi) {
|
|
20278
|
+
return id.includes("/") ? resolveQualifiedToggle(id, catalogue, ansi) : resolveBareToggle(id, catalogue);
|
|
20128
20279
|
}
|
|
20129
|
-
function resolveQualifiedToggle(id, catalogue,
|
|
20280
|
+
function resolveQualifiedToggle(id, catalogue, ansi) {
|
|
20130
20281
|
const errGlyph = ansi.red("\u2715");
|
|
20131
20282
|
const [bundleId, extId, ...rest] = id.split("/");
|
|
20132
20283
|
if (!bundleId || !extId || rest.length > 0) {
|
|
@@ -20148,17 +20299,6 @@ function resolveQualifiedToggle(id, catalogue, verb, ansi) {
|
|
|
20148
20299
|
})
|
|
20149
20300
|
};
|
|
20150
20301
|
}
|
|
20151
|
-
if (bundle.granularity === "bundle") {
|
|
20152
|
-
return {
|
|
20153
|
-
error: tx(PLUGINS_TEXTS.granularityBundleRejectsQualified, {
|
|
20154
|
-
glyph: errGlyph,
|
|
20155
|
-
bundleId: sanitizeForTerminal(bundleId),
|
|
20156
|
-
extId: sanitizeForTerminal(extId),
|
|
20157
|
-
verb,
|
|
20158
|
-
hint: ansi.dim(PLUGINS_TEXTS.granularityBundleRejectsQualifiedHint)
|
|
20159
|
-
})
|
|
20160
|
-
};
|
|
20161
|
-
}
|
|
20162
20302
|
if (!bundle.extensionIds.includes(extId)) {
|
|
20163
20303
|
return {
|
|
20164
20304
|
error: tx(PLUGINS_TEXTS.qualifiedIdNotFound, {
|
|
@@ -20170,31 +20310,27 @@ function resolveQualifiedToggle(id, catalogue, verb, ansi) {
|
|
|
20170
20310
|
})
|
|
20171
20311
|
};
|
|
20172
20312
|
}
|
|
20173
|
-
return {
|
|
20313
|
+
return {
|
|
20314
|
+
origin: "qualified",
|
|
20315
|
+
keys: [qualifiedExtensionId(bundleId, extId)]
|
|
20316
|
+
};
|
|
20174
20317
|
}
|
|
20175
|
-
function resolveBareToggle(id, catalogue
|
|
20176
|
-
const errGlyph = ansi.red("\u2715");
|
|
20318
|
+
function resolveBareToggle(id, catalogue) {
|
|
20177
20319
|
const bundle = catalogue.find((b) => b.id === id);
|
|
20178
20320
|
if (!bundle) {
|
|
20179
20321
|
return {
|
|
20180
20322
|
error: tx(PLUGINS_TEXTS.pluginNotFound, {
|
|
20181
|
-
glyph:
|
|
20323
|
+
glyph: "\u2715",
|
|
20182
20324
|
id: sanitizeForTerminal(id),
|
|
20183
|
-
hint:
|
|
20184
|
-
})
|
|
20185
|
-
};
|
|
20186
|
-
}
|
|
20187
|
-
if (bundle.granularity === "extension") {
|
|
20188
|
-
return {
|
|
20189
|
-
error: tx(PLUGINS_TEXTS.granularityExtensionRejectsBundleId, {
|
|
20190
|
-
glyph: errGlyph,
|
|
20191
|
-
bundleId: sanitizeForTerminal(id),
|
|
20192
|
-
verb,
|
|
20193
|
-
hint: ansi.dim(PLUGINS_TEXTS.granularityExtensionRejectsBundleIdHint)
|
|
20325
|
+
hint: PLUGINS_TEXTS.pluginNotFoundHint
|
|
20194
20326
|
})
|
|
20195
20327
|
};
|
|
20196
20328
|
}
|
|
20197
|
-
return {
|
|
20329
|
+
return {
|
|
20330
|
+
origin: "bare",
|
|
20331
|
+
bundleId: bundle.id,
|
|
20332
|
+
keys: bundle.extensionIds.map((extId) => qualifiedExtensionId(bundle.id, extId))
|
|
20333
|
+
};
|
|
20198
20334
|
}
|
|
20199
20335
|
|
|
20200
20336
|
// cli/commands/plugins/create.ts
|
|
@@ -20245,7 +20381,6 @@ var PluginsCreateCommand = class extends SmCommand {
|
|
|
20245
20381
|
version: "0.1.0",
|
|
20246
20382
|
specCompat: `^${specVersion}`,
|
|
20247
20383
|
catalogCompat: "^1.0.0",
|
|
20248
|
-
granularity: "bundle",
|
|
20249
20384
|
description: "Generated by `sm plugins create`. Edit to taste.",
|
|
20250
20385
|
settings: {
|
|
20251
20386
|
keywords: {
|
|
@@ -20361,7 +20496,7 @@ var VIEW_SLOTS_CATALOG = [
|
|
|
20361
20496
|
{ id: "card.subtitle.left", summary: "Single non-negative integer in the card subtitle row." },
|
|
20362
20497
|
{ id: "card.footer.left", summary: "Counter chip in the left footer of the card." },
|
|
20363
20498
|
{ id: "card.footer.right", summary: "Counter chip in the right footer of the card." },
|
|
20364
|
-
{ id: "graph.node.alert", summary:
|
|
20499
|
+
{ id: "graph.node.alert", summary: 'Reserved corner badge on the graph node, special-case signals only. No core analyzer emits here; routine "this node has a problem" findings belong in `card.footer.right`.' },
|
|
20365
20500
|
{ id: "inspector.header.badge.counter", summary: "Counter chip in the inspector header badge cluster." },
|
|
20366
20501
|
{ id: "inspector.header.badge.tag", summary: "Qualitative tag chip in the inspector header badge cluster." },
|
|
20367
20502
|
{ id: "inspector.body.panel.breakdown", summary: "Top-N labeled values rendered as a bar chart in the inspector body." },
|
|
@@ -21659,13 +21794,12 @@ var ScanCommand = class extends SmCommand {
|
|
|
21659
21794
|
const ansi = this.ansiFor("stdout");
|
|
21660
21795
|
const cwd = defaultRuntimeContext().cwd;
|
|
21661
21796
|
const hasErrors = exitCode2 === ExitCode.Issues;
|
|
21662
|
-
const
|
|
21663
|
-
const glyph = hasErrors ?
|
|
21797
|
+
const severityCounts = countBySeverity(result.issues);
|
|
21798
|
+
const glyph = hasErrors ? " " : ansi.green("\u2713");
|
|
21664
21799
|
const counts = formatScanCounts({
|
|
21665
21800
|
nodes: result.stats.nodesCount,
|
|
21666
21801
|
links: result.stats.linksCount,
|
|
21667
|
-
|
|
21668
|
-
hasErrors,
|
|
21802
|
+
severities: severityCounts,
|
|
21669
21803
|
ansi
|
|
21670
21804
|
});
|
|
21671
21805
|
const duration = ansi.dim(`in ${result.stats.durationMs}ms`);
|
|
@@ -21712,11 +21846,49 @@ var ScanCommand = class extends SmCommand {
|
|
|
21712
21846
|
return exitCode2;
|
|
21713
21847
|
}
|
|
21714
21848
|
};
|
|
21849
|
+
function countBySeverity(issues) {
|
|
21850
|
+
const buckets = {
|
|
21851
|
+
error: /* @__PURE__ */ new Set(),
|
|
21852
|
+
warn: /* @__PURE__ */ new Set(),
|
|
21853
|
+
info: /* @__PURE__ */ new Set()
|
|
21854
|
+
};
|
|
21855
|
+
for (const i of issues) {
|
|
21856
|
+
const tier = i.severity;
|
|
21857
|
+
const bucket = buckets[tier];
|
|
21858
|
+
if (!bucket) continue;
|
|
21859
|
+
fillSeverityBucket(bucket, i.nodeIds);
|
|
21860
|
+
}
|
|
21861
|
+
return { errors: buckets.error.size, warns: buckets.warn.size, info: buckets.info.size };
|
|
21862
|
+
}
|
|
21863
|
+
function fillSeverityBucket(bucket, nodeIds) {
|
|
21864
|
+
const ids = nodeIds ?? [];
|
|
21865
|
+
if (ids.length === 0) {
|
|
21866
|
+
bucket.add("");
|
|
21867
|
+
return;
|
|
21868
|
+
}
|
|
21869
|
+
for (const id of ids) bucket.add(id);
|
|
21870
|
+
}
|
|
21715
21871
|
function formatScanCounts(opts) {
|
|
21716
|
-
const { nodes, links,
|
|
21717
|
-
const
|
|
21718
|
-
|
|
21719
|
-
|
|
21872
|
+
const { nodes, links, severities, ansi } = opts;
|
|
21873
|
+
const parts = [
|
|
21874
|
+
`${nodes} ${plural(nodes, "node")}`,
|
|
21875
|
+
`${links} ${plural(links, "link")}`
|
|
21876
|
+
];
|
|
21877
|
+
const total = severities.errors + severities.warns + severities.info;
|
|
21878
|
+
if (total === 0) {
|
|
21879
|
+
parts.push(ansi.dim("0 issues"));
|
|
21880
|
+
} else {
|
|
21881
|
+
if (severities.errors > 0) {
|
|
21882
|
+
parts.push(ansi.red(`${severities.errors} ${plural(severities.errors, "error")}`));
|
|
21883
|
+
}
|
|
21884
|
+
if (severities.warns > 0) {
|
|
21885
|
+
parts.push(ansi.yellow(`${severities.warns} ${plural(severities.warns, "warning")}`));
|
|
21886
|
+
}
|
|
21887
|
+
if (severities.info > 0) {
|
|
21888
|
+
parts.push(ansi.dim(`${severities.info} info`));
|
|
21889
|
+
}
|
|
21890
|
+
}
|
|
21891
|
+
return parts.join(" \xB7 ");
|
|
21720
21892
|
}
|
|
21721
21893
|
function plural(count, word) {
|
|
21722
21894
|
return count === 1 ? word : `${word}s`;
|
|
@@ -22199,10 +22371,10 @@ var SERVER_TEXTS = {
|
|
|
22199
22371
|
pluginsBodyNotJson: "Request body must be valid JSON.",
|
|
22200
22372
|
pluginsBodyNotObject: "Request body must be a JSON object.",
|
|
22201
22373
|
pluginsEnabledRequired: "`enabled` is required and must be a boolean.",
|
|
22202
|
-
// 400,
|
|
22203
|
-
//
|
|
22204
|
-
|
|
22205
|
-
|
|
22374
|
+
// 400, cascade route rejects qualified ids: the bare-id PATCH is the
|
|
22375
|
+
// bundle macro endpoint. Anything containing `/` needs the dedicated
|
|
22376
|
+
// per-extension route below.
|
|
22377
|
+
pluginsCascadeRouteQualifiedRejected: 'Plugin id "{{id}}" contains "/"; toggle individual extensions via PATCH /api/plugins/<bundle>/extensions/<extensionId>.',
|
|
22206
22378
|
// 404, unknown plugin / extension.
|
|
22207
22379
|
pluginsUnknown: 'No plugin with id "{{id}}".',
|
|
22208
22380
|
pluginsExtensionUnknown: 'Plugin "{{bundleId}}" has no extension named "{{extensionId}}".',
|
|
@@ -23209,7 +23381,7 @@ function registerPluginsRoute(app, deps) {
|
|
|
23209
23381
|
const id = c.req.param("id");
|
|
23210
23382
|
if (id.includes("/")) {
|
|
23211
23383
|
throw new HTTPException8(400, {
|
|
23212
|
-
message: tx(SERVER_TEXTS.
|
|
23384
|
+
message: tx(SERVER_TEXTS.pluginsCascadeRouteQualifiedRejected, { id })
|
|
23213
23385
|
});
|
|
23214
23386
|
}
|
|
23215
23387
|
const handle = findHandle(id, deps);
|
|
@@ -23218,18 +23390,15 @@ function registerPluginsRoute(app, deps) {
|
|
|
23218
23390
|
message: tx(SERVER_TEXTS.pluginsUnknown, { id })
|
|
23219
23391
|
});
|
|
23220
23392
|
}
|
|
23221
|
-
if (granularityOf(handle) !== "bundle") {
|
|
23222
|
-
throw new HTTPException8(400, {
|
|
23223
|
-
message: tx(SERVER_TEXTS.pluginsGranularityExtensionExpected, { id })
|
|
23224
|
-
});
|
|
23225
|
-
}
|
|
23226
23393
|
if (isPluginLocked(id)) {
|
|
23227
23394
|
throw new HTTPException8(403, {
|
|
23228
23395
|
message: tx(SERVER_TEXTS.pluginsLocked, { id })
|
|
23229
23396
|
});
|
|
23230
23397
|
}
|
|
23231
23398
|
const body = await parsePatchBody(c.req.raw);
|
|
23232
|
-
|
|
23399
|
+
const childIds = bundleExtensionIds(handle).map((extId) => qualifiedExtensionId(id, extId));
|
|
23400
|
+
const writable = childIds.filter((q) => !isPluginLocked(q));
|
|
23401
|
+
return await persistManyAndProject(c, deps, writable, body.enabled);
|
|
23233
23402
|
});
|
|
23234
23403
|
app.patch("/api/plugins/:bundleId/extensions/:extensionId", async (c) => {
|
|
23235
23404
|
const bundleId = c.req.param("bundleId");
|
|
@@ -23278,7 +23447,6 @@ function listItems(deps, resolveEnabled) {
|
|
|
23278
23447
|
}
|
|
23279
23448
|
function buildBuiltInItems(resolveEnabled) {
|
|
23280
23449
|
return sortBundlesForPresentation(builtInBundles).map((bundle) => {
|
|
23281
|
-
const bundleEnabled = resolveEnabled(bundle.id);
|
|
23282
23450
|
const bundleLocked = isPluginLocked(bundle.id);
|
|
23283
23451
|
const extensions = bundle.extensions.map((ext) => {
|
|
23284
23452
|
const qualified = qualifiedExtensionId(bundle.id, ext.id);
|
|
@@ -23292,6 +23460,7 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
23292
23460
|
...extLocked ? { locked: true } : {}
|
|
23293
23461
|
};
|
|
23294
23462
|
});
|
|
23463
|
+
const bundleEnabled = extensions.some((e) => e.enabled);
|
|
23295
23464
|
return {
|
|
23296
23465
|
id: bundle.id,
|
|
23297
23466
|
version: firstVersion(bundle.extensions),
|
|
@@ -23299,7 +23468,6 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
23299
23468
|
status: bundleEnabled ? "enabled" : "disabled",
|
|
23300
23469
|
reason: null,
|
|
23301
23470
|
source: "built-in",
|
|
23302
|
-
granularity: bundle.granularity,
|
|
23303
23471
|
description: bundle.description,
|
|
23304
23472
|
...extensions.length > 0 ? { extensions } : {},
|
|
23305
23473
|
...bundleLocked ? { locked: true } : {}
|
|
@@ -23310,9 +23478,8 @@ function buildDiscoveredItems(discovered, deps, resolveEnabled) {
|
|
|
23310
23478
|
return discovered.map((plugin) => buildDiscoveredItem(plugin, deps, resolveEnabled));
|
|
23311
23479
|
}
|
|
23312
23480
|
function buildDiscoveredItem(plugin, deps, resolveEnabled) {
|
|
23313
|
-
const granularity = plugin.granularity ?? "bundle";
|
|
23314
23481
|
const bundleLocked = isPluginLocked(plugin.id);
|
|
23315
|
-
const extensions = projectExtensionRows(plugin,
|
|
23482
|
+
const extensions = projectExtensionRows(plugin, resolveEnabled, bundleLocked);
|
|
23316
23483
|
const optional = optionalDiscoveredFields(plugin, extensions);
|
|
23317
23484
|
return {
|
|
23318
23485
|
id: plugin.id,
|
|
@@ -23321,7 +23488,6 @@ function buildDiscoveredItem(plugin, deps, resolveEnabled) {
|
|
|
23321
23488
|
status: projectStatus(plugin, resolveEnabled),
|
|
23322
23489
|
reason: plugin.reason ?? null,
|
|
23323
23490
|
source: classifyPluginSource(plugin.path, deps),
|
|
23324
|
-
granularity,
|
|
23325
23491
|
...optional,
|
|
23326
23492
|
...bundleLocked ? { locked: true } : {},
|
|
23327
23493
|
...plugin.status === "disabled" ? { startsAsDisabled: true } : {}
|
|
@@ -23334,7 +23500,7 @@ function optionalDiscoveredFields(plugin, extensions) {
|
|
|
23334
23500
|
if (extensions) out.extensions = extensions;
|
|
23335
23501
|
return out;
|
|
23336
23502
|
}
|
|
23337
|
-
function projectExtensionRows(plugin,
|
|
23503
|
+
function projectExtensionRows(plugin, resolveEnabled, bundleLocked) {
|
|
23338
23504
|
if (!plugin.extensions || plugin.extensions.length === 0) return void 0;
|
|
23339
23505
|
return plugin.extensions.map((ext) => {
|
|
23340
23506
|
const description = readInstanceDescription(ext.instance);
|
|
@@ -23383,6 +23549,18 @@ async function persistAndProject(c, deps, configKey, enabled) {
|
|
|
23383
23549
|
);
|
|
23384
23550
|
return projectListResponse(c, deps, overrides);
|
|
23385
23551
|
}
|
|
23552
|
+
async function persistManyAndProject(c, deps, keys, enabled) {
|
|
23553
|
+
const overrides = await tryWithSqlite(
|
|
23554
|
+
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
23555
|
+
async (adapter) => {
|
|
23556
|
+
for (const key of keys) {
|
|
23557
|
+
await applyChangeToAdapter(adapter, key, enabled);
|
|
23558
|
+
}
|
|
23559
|
+
return await adapter.pluginConfig.loadOverrideMap();
|
|
23560
|
+
}
|
|
23561
|
+
);
|
|
23562
|
+
return projectListResponse(c, deps, overrides);
|
|
23563
|
+
}
|
|
23386
23564
|
async function applyChangeToAdapter(adapter, configKey, enabled) {
|
|
23387
23565
|
await adapter.pluginConfig.set(configKey, enabled);
|
|
23388
23566
|
if (enabled) return;
|
|
@@ -23426,13 +23604,6 @@ function validateBulkChange(change, deps) {
|
|
|
23426
23604
|
message: tx(SERVER_TEXTS.pluginsUnknown, { id: change.id })
|
|
23427
23605
|
};
|
|
23428
23606
|
}
|
|
23429
|
-
if (granularityOf(handle2) !== "bundle") {
|
|
23430
|
-
return {
|
|
23431
|
-
status: 400,
|
|
23432
|
-
code: "bad-query",
|
|
23433
|
-
message: tx(SERVER_TEXTS.pluginsGranularityExtensionExpected, { id: change.id })
|
|
23434
|
-
};
|
|
23435
|
-
}
|
|
23436
23607
|
if (isPluginLocked(change.id)) {
|
|
23437
23608
|
return {
|
|
23438
23609
|
status: 403,
|
|
@@ -23473,13 +23644,22 @@ async function persistBulkAndProject(c, deps, changes) {
|
|
|
23473
23644
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
23474
23645
|
async (adapter) => {
|
|
23475
23646
|
for (const change of changes) {
|
|
23476
|
-
|
|
23647
|
+
const writeKeys = expandBulkChangeKeys(change, deps);
|
|
23648
|
+
for (const key of writeKeys) {
|
|
23649
|
+
await applyChangeToAdapter(adapter, key, change.enabled);
|
|
23650
|
+
}
|
|
23477
23651
|
}
|
|
23478
23652
|
return await adapter.pluginConfig.loadOverrideMap();
|
|
23479
23653
|
}
|
|
23480
23654
|
);
|
|
23481
23655
|
return projectListResponse(c, deps, overrides);
|
|
23482
23656
|
}
|
|
23657
|
+
function expandBulkChangeKeys(change, deps) {
|
|
23658
|
+
if (change.id.includes("/")) return [change.id];
|
|
23659
|
+
const handle = findHandle(change.id, deps);
|
|
23660
|
+
if (!handle) return [];
|
|
23661
|
+
return bundleExtensionIds(handle).map((extId) => qualifiedExtensionId(change.id, extId)).filter((q) => !isPluginLocked(q));
|
|
23662
|
+
}
|
|
23483
23663
|
async function buildFreshResolver2(deps) {
|
|
23484
23664
|
return buildFreshResolver({
|
|
23485
23665
|
databasePath: deps.options.dbPath,
|
|
@@ -23497,8 +23677,11 @@ function findHandle(id, deps) {
|
|
|
23497
23677
|
if (discovered) return { kind: "discovered", plugin: discovered };
|
|
23498
23678
|
return null;
|
|
23499
23679
|
}
|
|
23500
|
-
function
|
|
23501
|
-
|
|
23680
|
+
function bundleExtensionIds(handle) {
|
|
23681
|
+
if (handle.kind === "built-in") {
|
|
23682
|
+
return handle.bundle.extensions.map((e) => e.id);
|
|
23683
|
+
}
|
|
23684
|
+
return (handle.plugin.extensions ?? []).map((e) => e.id);
|
|
23502
23685
|
}
|
|
23503
23686
|
function hasExtension(handle, extensionId) {
|
|
23504
23687
|
if (handle.kind === "built-in") {
|