@skill-map/cli 0.54.0 → 0.56.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-tutorial/SKILL.md +22 -24
- package/dist/cli/tutorial/sm-tutorial/references/_core.md +8 -7
- package/dist/cli/tutorial/sm-tutorial/references/_manifest.yml +21 -42
- package/dist/cli/tutorial/sm-tutorial/references/fixtures.md +15 -7
- package/dist/cli/tutorial/sm-tutorial/references/part-authoring.md +2 -2
- package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +1 -1
- package/dist/cli/tutorial/sm-tutorial/references/part-connect-harness.md +9 -10
- package/dist/cli/tutorial/sm-tutorial/references/part-daily-loop.md +563 -0
- package/dist/cli/tutorial/sm-tutorial/references/part-mcp.md +5 -1
- package/dist/cli/tutorial/sm-tutorial/references/part-plugins.md +7 -7
- package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +24 -12
- package/dist/cli/tutorial/sm-tutorial/references/part-settings.md +2 -2
- package/dist/cli.js +1785 -1102
- package/dist/index.js +148 -14
- package/dist/kernel/index.d.ts +229 -97
- package/dist/kernel/index.js +148 -14
- package/dist/migrations/001_initial.sql +5 -0
- package/dist/ui/chunk-4ITL7E6U.js +1 -0
- package/dist/ui/chunk-DWBJCNC7.js +2 -0
- package/dist/ui/{chunk-CXTU4HQV.js → chunk-GHOVZAAV.js} +1 -1
- package/dist/ui/{chunk-GBKHMJ4B.js → chunk-H6O2DYVT.js} +13 -13
- package/dist/ui/chunk-HDKR6XHG.js +917 -0
- package/dist/ui/{chunk-GEI6INVH.js → chunk-JA4Z74I3.js} +1 -1
- package/dist/ui/chunk-RS3ANRT5.js +1 -0
- package/dist/ui/chunk-VUNP5KNI.js +3 -0
- package/dist/ui/chunk-W3Z3CZL4.js +1844 -0
- package/dist/ui/chunk-YHJL5LP3.js +913 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/{main-HP3MOLI2.js → main-PL3BEVQI.js} +3 -3
- package/dist/ui/{styles-4SNVM34O.css → styles-RHEEXRHQ.css} +1 -1
- package/migrations/001_initial.sql +5 -0
- package/package.json +2 -2
- package/dist/cli/tutorial/sm-tutorial/references/part-live-site.md +0 -155
- package/dist/cli/tutorial/sm-tutorial/references/part-maintain.md +0 -284
- package/dist/cli/tutorial/sm-tutorial/references/part-run-harness.md +0 -181
- package/dist/ui/chunk-4CXAL43H.js +0 -1
- package/dist/ui/chunk-BUNPMGDX.js +0 -2205
- package/dist/ui/chunk-DSNBKMYU.js +0 -2
- package/dist/ui/chunk-JXRIGHET.js +0 -552
- package/dist/ui/chunk-MVRQGDZJ.js +0 -123
- package/dist/ui/chunk-WFLPMCK4.js +0 -392
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/entry.ts
|
|
2
2
|
|
|
3
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="49fdc2af-2694-5cd0-ac19-33f8d1dffa9e")}catch(e){}}();
|
|
4
4
|
import { existsSync as existsSync33 } from "fs";
|
|
5
5
|
import { Builtins, Cli as Cli2 } from "clipanion";
|
|
6
6
|
|
|
@@ -197,6 +197,10 @@ function matchesFilter(hook, event) {
|
|
|
197
197
|
function buildHookContext(_hook, trigger, event) {
|
|
198
198
|
const data = event.data ?? {};
|
|
199
199
|
const ctx = {
|
|
200
|
+
// `settings` is always populated (possibly empty) so hooks can read
|
|
201
|
+
// `ctx.settings.<id>` without a presence check. The composer
|
|
202
|
+
// populated `resolvedSettings` on each composed hook.
|
|
203
|
+
settings: _hook.resolvedSettings ?? {},
|
|
200
204
|
event: {
|
|
201
205
|
type: trigger,
|
|
202
206
|
timestamp: event.timestamp,
|
|
@@ -246,7 +250,7 @@ function bucketByKind(kind, instance, bag) {
|
|
|
246
250
|
// package.json
|
|
247
251
|
var package_default = {
|
|
248
252
|
name: "@skill-map/cli",
|
|
249
|
-
version: "0.
|
|
253
|
+
version: "0.56.0",
|
|
250
254
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
251
255
|
license: "MIT",
|
|
252
256
|
type: "module",
|
|
@@ -567,6 +571,41 @@ var ANTIGRAVITY_PLUGIN_ID = "antigravity";
|
|
|
567
571
|
var AGENT_SKILLS_PLUGIN_ID = "agent-skills";
|
|
568
572
|
|
|
569
573
|
// plugins/claude/providers/claude/index.ts
|
|
574
|
+
var RESERVED_SLASH_NAMES = [
|
|
575
|
+
"help",
|
|
576
|
+
"clear",
|
|
577
|
+
"compact",
|
|
578
|
+
"cost",
|
|
579
|
+
"init",
|
|
580
|
+
"model",
|
|
581
|
+
"agents",
|
|
582
|
+
"login",
|
|
583
|
+
"logout",
|
|
584
|
+
"mcp",
|
|
585
|
+
"memory",
|
|
586
|
+
"config",
|
|
587
|
+
"doctor",
|
|
588
|
+
"permissions",
|
|
589
|
+
"add-dir",
|
|
590
|
+
"bug",
|
|
591
|
+
"pr-comments",
|
|
592
|
+
"release-notes",
|
|
593
|
+
"review",
|
|
594
|
+
"terminal-setup",
|
|
595
|
+
"vim",
|
|
596
|
+
"output-style",
|
|
597
|
+
"hooks",
|
|
598
|
+
"install-github-app",
|
|
599
|
+
"migrate-installer",
|
|
600
|
+
"upgrade",
|
|
601
|
+
"resume",
|
|
602
|
+
"exit",
|
|
603
|
+
"quit",
|
|
604
|
+
"security-review",
|
|
605
|
+
"statusline",
|
|
606
|
+
"usage",
|
|
607
|
+
"feedback"
|
|
608
|
+
];
|
|
570
609
|
var claudeProvider = {
|
|
571
610
|
id: "claude",
|
|
572
611
|
pluginId: CLAUDE_PLUGIN_ID,
|
|
@@ -718,53 +757,18 @@ var claudeProvider = {
|
|
|
718
757
|
// declaring one of these (e.g. `.claude/commands/help.md`) is
|
|
719
758
|
// silently shadowed: the runtime runs the built-in instead.
|
|
720
759
|
//
|
|
721
|
-
// - `command
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
// reflect
|
|
727
|
-
// - `agent`: built-in agents Anthropic ships with the CLI
|
|
760
|
+
// - `command` / `skill`: both reserve `RESERVED_SLASH_NAMES` (defined
|
|
761
|
+
// above). Per the resolution matrix they share the `/` invocation
|
|
762
|
+
// namespace, so a built-in slash command shadows a user node of
|
|
763
|
+
// EITHER kind that claims its name (a `skill` named `help` is as
|
|
764
|
+
// unreachable as a `command` named `help`). The catalog is API
|
|
765
|
+
// surface users rely on the analyzer to reflect.
|
|
766
|
+
// - `agent`: built-in agents Anthropic ships with the CLI, invoked
|
|
767
|
+
// through the `@` / Task namespace (separate from `/`). Smaller
|
|
728
768
|
// surface than commands today.
|
|
729
|
-
// - `skill`: no built-in skills today (skills are user-defined and
|
|
730
|
-
// discovered from disk); the key is omitted on purpose, defaulting
|
|
731
|
-
// to no reserved names for the kind.
|
|
732
769
|
reservedNames: {
|
|
733
|
-
command:
|
|
734
|
-
|
|
735
|
-
"clear",
|
|
736
|
-
"compact",
|
|
737
|
-
"cost",
|
|
738
|
-
"init",
|
|
739
|
-
"model",
|
|
740
|
-
"agents",
|
|
741
|
-
"login",
|
|
742
|
-
"logout",
|
|
743
|
-
"mcp",
|
|
744
|
-
"memory",
|
|
745
|
-
"config",
|
|
746
|
-
"doctor",
|
|
747
|
-
"permissions",
|
|
748
|
-
"add-dir",
|
|
749
|
-
"bug",
|
|
750
|
-
"pr-comments",
|
|
751
|
-
"release-notes",
|
|
752
|
-
"review",
|
|
753
|
-
"terminal-setup",
|
|
754
|
-
"vim",
|
|
755
|
-
"output-style",
|
|
756
|
-
"hooks",
|
|
757
|
-
"install-github-app",
|
|
758
|
-
"migrate-installer",
|
|
759
|
-
"upgrade",
|
|
760
|
-
"resume",
|
|
761
|
-
"exit",
|
|
762
|
-
"quit",
|
|
763
|
-
"security-review",
|
|
764
|
-
"statusline",
|
|
765
|
-
"usage",
|
|
766
|
-
"feedback"
|
|
767
|
-
],
|
|
770
|
+
command: RESERVED_SLASH_NAMES,
|
|
771
|
+
skill: RESERVED_SLASH_NAMES,
|
|
768
772
|
agent: [
|
|
769
773
|
"general-purpose",
|
|
770
774
|
"output-style-setup",
|
|
@@ -883,7 +887,7 @@ var atDirectiveExtractor = {
|
|
|
883
887
|
id: ID,
|
|
884
888
|
pluginId: CLAUDE_PLUGIN_ID,
|
|
885
889
|
kind: "extractor",
|
|
886
|
-
description: "Detects `@<token>` directives in a node's body using Claude Code rules.
|
|
890
|
+
description: "Detects `@<token>` directives in a node's body using Claude Code rules, choosing the link kind by token shape. Example: a bare handle `@team` becomes a `mentions` link, while a file-flavoured token `@docs/api.md` becomes a `references` link.",
|
|
887
891
|
scope: "body",
|
|
888
892
|
precondition: { provider: ["claude"] },
|
|
889
893
|
// eslint-disable-next-line complexity
|
|
@@ -971,7 +975,7 @@ var slashCommandExtractor = {
|
|
|
971
975
|
id: ID2,
|
|
972
976
|
pluginId: CLAUDE_PLUGIN_ID,
|
|
973
977
|
kind: "extractor",
|
|
974
|
-
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.",
|
|
978
|
+
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. Example: `/deploy` in the body draws an arrow to the `deploy` command.",
|
|
975
979
|
scope: "body",
|
|
976
980
|
precondition: { provider: ["claude"] },
|
|
977
981
|
extract(ctx) {
|
|
@@ -1029,7 +1033,7 @@ var toolsCounterExtractor = {
|
|
|
1029
1033
|
id: ID3,
|
|
1030
1034
|
pluginId: CLAUDE_PLUGIN_ID,
|
|
1031
1035
|
kind: "extractor",
|
|
1032
|
-
description: "Counts the tools an agent declares in its frontmatter and shows the count on the agent card.",
|
|
1036
|
+
description: "Counts the tools an agent declares in its frontmatter and shows the count on the agent card. Example: an agent with `tools: [Bash, Read, Grep]` shows a count of 3.",
|
|
1033
1037
|
scope: "frontmatter",
|
|
1034
1038
|
precondition: { kind: ["claude/agent"] },
|
|
1035
1039
|
ui: { count },
|
|
@@ -1444,7 +1448,7 @@ var annotationsExtractor = {
|
|
|
1444
1448
|
id: ID4,
|
|
1445
1449
|
pluginId: CORE_PLUGIN_ID,
|
|
1446
1450
|
kind: "extractor",
|
|
1447
|
-
description: "Turns the `supersedes` and `supersededBy` entries from a node's `.sm` sidecar into arrows between nodes in the graph.",
|
|
1451
|
+
description: "Turns the `supersedes` and `supersededBy` entries from a node's `.sm` sidecar into arrows between nodes in the graph. Example: `supersededBy: v1-skill.md` in a `.sm` sidecar draws an arrow to `v1-skill.md`.",
|
|
1448
1452
|
scope: "frontmatter",
|
|
1449
1453
|
extract(ctx) {
|
|
1450
1454
|
const sourcePath = ctx.node.path;
|
|
@@ -1500,12 +1504,12 @@ function stringArray(value) {
|
|
|
1500
1504
|
// plugins/core/extractors/backtick-path/index.ts
|
|
1501
1505
|
import { posix as pathPosix2 } from "path";
|
|
1502
1506
|
var ID5 = "backtick-path";
|
|
1503
|
-
var PATH_RE = /(?<![\w/:.-])(?:\.{1,2}\/)?[\w.-]
|
|
1507
|
+
var PATH_RE = /(?<![\w/:.-])(?:\.{1,2}\/)?[\w][\w.-]*(?:\/[\w.-]+)*\.md\b(?![\w/])/g;
|
|
1504
1508
|
var backtickPathExtractor = {
|
|
1505
1509
|
id: ID5,
|
|
1506
1510
|
pluginId: CORE_PLUGIN_ID,
|
|
1507
1511
|
kind: "extractor",
|
|
1508
|
-
description: "Turns relative .md paths written inside code spans and fenced blocks into arrows between nodes in the graph.",
|
|
1512
|
+
description: "Turns relative .md paths written inside code spans and fenced blocks into arrows between nodes in the graph. Example: a backticked `references/rules.md` path draws an arrow to that file.",
|
|
1509
1513
|
scope: "body",
|
|
1510
1514
|
extract(ctx) {
|
|
1511
1515
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1564,14 +1568,31 @@ var count2 = {
|
|
|
1564
1568
|
emitWhenEmpty: false,
|
|
1565
1569
|
priority: 30
|
|
1566
1570
|
};
|
|
1571
|
+
var SETTING_IGNORED_DOMAINS = "ignored-domains";
|
|
1572
|
+
var settings = {
|
|
1573
|
+
[SETTING_IGNORED_DOMAINS]: {
|
|
1574
|
+
type: "string-list",
|
|
1575
|
+
label: "Ignored domains",
|
|
1576
|
+
description: "Hostnames to exclude from the external-URL count (e.g. internal mirrors, link shorteners).",
|
|
1577
|
+
default: []
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1567
1580
|
var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
|
|
1568
1581
|
var TRAILING_PUNCT = /[.,;:!?]+$/;
|
|
1569
1582
|
var externalUrlCounterExtractor = {
|
|
1570
1583
|
id: ID6,
|
|
1571
1584
|
pluginId: CORE_PLUGIN_ID,
|
|
1572
1585
|
kind: "extractor",
|
|
1573
|
-
description: "Counts the distinct external URLs in a node's body and shows the count on the card.",
|
|
1586
|
+
description: "Counts the distinct external URLs in a node's body and shows the count on the card. Example: a body linking `https://example.com` and `https://docs.rs` shows a count of 2.",
|
|
1574
1587
|
scope: "body",
|
|
1588
|
+
/**
|
|
1589
|
+
* Operator-configurable hostnames to exclude from the count. A URL
|
|
1590
|
+
* whose normalized `hostname` is in this list is skipped entirely:
|
|
1591
|
+
* no Signal, no contribution increment. Default empty (count every
|
|
1592
|
+
* external URL). The operator sets it via
|
|
1593
|
+
* `sm plugins config core/external-url-counter ignored-domains '["…"]'`.
|
|
1594
|
+
*/
|
|
1595
|
+
settings,
|
|
1575
1596
|
/**
|
|
1576
1597
|
* Phase 6 / View contribution system, surface the distinct-URL
|
|
1577
1598
|
* count as a card-footer-left chip alongside the in/out link
|
|
@@ -1591,6 +1612,7 @@ var externalUrlCounterExtractor = {
|
|
|
1591
1612
|
ui: { count: count2 },
|
|
1592
1613
|
extract(ctx) {
|
|
1593
1614
|
const seen = /* @__PURE__ */ new Set();
|
|
1615
|
+
const ignoredDomains = readIgnoredDomains(ctx.settings[SETTING_IGNORED_DOMAINS]);
|
|
1594
1616
|
const body = stripCodeBlocks(ctx.body);
|
|
1595
1617
|
const lineStarts = computeLineStarts(body);
|
|
1596
1618
|
for (const match of body.matchAll(URL_RE)) {
|
|
@@ -1598,8 +1620,9 @@ var externalUrlCounterExtractor = {
|
|
|
1598
1620
|
if (original.length === 0) continue;
|
|
1599
1621
|
const normalized = normalizeUrl(original);
|
|
1600
1622
|
if (normalized === null) continue;
|
|
1601
|
-
if (
|
|
1602
|
-
seen.
|
|
1623
|
+
if (ignoredDomains.has(normalized.host)) continue;
|
|
1624
|
+
if (seen.has(normalized.href)) continue;
|
|
1625
|
+
seen.add(normalized.href);
|
|
1603
1626
|
const offset = match.index ?? 0;
|
|
1604
1627
|
const line = lineFor(lineStarts, offset);
|
|
1605
1628
|
ctx.emitSignal({
|
|
@@ -1611,12 +1634,12 @@ var externalUrlCounterExtractor = {
|
|
|
1611
1634
|
{
|
|
1612
1635
|
extractorId: ID6,
|
|
1613
1636
|
kind: "references",
|
|
1614
|
-
target: normalized,
|
|
1637
|
+
target: normalized.href,
|
|
1615
1638
|
confidence: 0.3,
|
|
1616
1639
|
rationale: "external URL pseudo-link, counted then dropped",
|
|
1617
1640
|
trigger: {
|
|
1618
1641
|
originalTrigger: original,
|
|
1619
|
-
normalizedTrigger: normalized
|
|
1642
|
+
normalizedTrigger: normalized.href
|
|
1620
1643
|
}
|
|
1621
1644
|
}
|
|
1622
1645
|
]
|
|
@@ -1630,12 +1653,20 @@ var externalUrlCounterExtractor = {
|
|
|
1630
1653
|
function stripTrailingPunctuation(raw) {
|
|
1631
1654
|
return raw.replace(TRAILING_PUNCT, "");
|
|
1632
1655
|
}
|
|
1656
|
+
function readIgnoredDomains(value) {
|
|
1657
|
+
if (!Array.isArray(value)) return /* @__PURE__ */ new Set();
|
|
1658
|
+
const out = /* @__PURE__ */ new Set();
|
|
1659
|
+
for (const entry of value) {
|
|
1660
|
+
if (typeof entry === "string" && entry.length > 0) out.add(entry.toLowerCase());
|
|
1661
|
+
}
|
|
1662
|
+
return out;
|
|
1663
|
+
}
|
|
1633
1664
|
function normalizeUrl(raw) {
|
|
1634
1665
|
try {
|
|
1635
1666
|
const url = new URL(raw);
|
|
1636
1667
|
url.hostname = url.hostname.toLowerCase();
|
|
1637
1668
|
url.hash = "";
|
|
1638
|
-
return url.href;
|
|
1669
|
+
return { href: url.href, host: url.hostname };
|
|
1639
1670
|
} catch {
|
|
1640
1671
|
return null;
|
|
1641
1672
|
}
|
|
@@ -1650,7 +1681,7 @@ var markdownLinkExtractor = {
|
|
|
1650
1681
|
id: ID7,
|
|
1651
1682
|
pluginId: CORE_PLUGIN_ID,
|
|
1652
1683
|
kind: "extractor",
|
|
1653
|
-
description: "Turns markdown links (`[text](path)`) in a node's body into arrows between nodes in the graph.",
|
|
1684
|
+
description: "Turns markdown links (`[text](path)`) in a node's body into arrows between nodes in the graph. Example: `[the guide](docs/guide.md)` draws an arrow to `docs/guide.md`.",
|
|
1654
1685
|
scope: "body",
|
|
1655
1686
|
extract(ctx) {
|
|
1656
1687
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1675,13 +1706,15 @@ var markdownLinkExtractor = {
|
|
|
1675
1706
|
extractorId: ID7,
|
|
1676
1707
|
kind: "references",
|
|
1677
1708
|
target: resolved,
|
|
1678
|
-
//
|
|
1679
|
-
//
|
|
1680
|
-
//
|
|
1681
|
-
//
|
|
1682
|
-
//
|
|
1683
|
-
//
|
|
1684
|
-
|
|
1709
|
+
// 0.95: the `[text](path)` syntax is unambiguous (the spec's
|
|
1710
|
+
// "unambiguous syntax" tier), but NOT 1.0. `1.0` is reserved
|
|
1711
|
+
// for structured input and is earned post-walk by the
|
|
1712
|
+
// confidence-lift transform when `target` resolves to a real
|
|
1713
|
+
// node; an unresolved (broken) target is downgraded to the
|
|
1714
|
+
// broken floor (0.5) by the same transform. Emitting 1.0 here
|
|
1715
|
+
// would short-circuit the lift and make a broken link
|
|
1716
|
+
// indistinguishable from a resolved one.
|
|
1717
|
+
confidence: 0.95,
|
|
1685
1718
|
rationale: "unambiguous markdown link syntax",
|
|
1686
1719
|
trigger: {
|
|
1687
1720
|
originalTrigger: original,
|
|
@@ -1711,7 +1744,7 @@ var mcpToolsExtractor = {
|
|
|
1711
1744
|
id: ID8,
|
|
1712
1745
|
pluginId: CORE_PLUGIN_ID,
|
|
1713
1746
|
kind: "extractor",
|
|
1714
|
-
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.",
|
|
1747
|
+
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. Example: `tools: [mcp__github__create_pr]` adds an `mcp://github` node and an arrow to it.",
|
|
1715
1748
|
// Claude-convention pattern only; per-vendor flavours and the
|
|
1716
1749
|
// config-side MCP declaration (Phase 5b) are still pending, so the
|
|
1717
1750
|
// extractor ships flagged as experimental in list / show / Settings.
|
|
@@ -2004,11 +2037,7 @@ var ANNOTATION_STALE_TEXTS = {
|
|
|
2004
2037
|
// a literal placeholder the operator substitutes.
|
|
2005
2038
|
bodyTooltip: "Sidecar drift since last bump:\n \u2022 body\nRun `sm bump <path>` to refresh.",
|
|
2006
2039
|
frontmatterTooltip: "Sidecar drift since last bump:\n \u2022 frontmatter\nRun `sm bump <path>` to refresh.",
|
|
2007
|
-
bothTooltip: "Sidecar drift since last bump:\n \u2022 body\n \u2022 frontmatter\nRun `sm bump <path>` to refresh."
|
|
2008
|
-
/** Label of the inspector action button that dispatches a bump. */
|
|
2009
|
-
bumpLabel: "Bump",
|
|
2010
|
-
/** Tooltip shown when the bump button is disabled (the node is fresh, no drift). */
|
|
2011
|
-
bumpDisabledReason: "No drift to bump."
|
|
2040
|
+
bothTooltip: "Sidecar drift since last bump:\n \u2022 body\n \u2022 frontmatter\nRun `sm bump <path>` to refresh."
|
|
2012
2041
|
};
|
|
2013
2042
|
|
|
2014
2043
|
// plugins/core/analyzers/annotation-stale/index.ts
|
|
@@ -2024,10 +2053,6 @@ var staleBadge = {
|
|
|
2024
2053
|
emitWhenEmpty: false,
|
|
2025
2054
|
priority: 20
|
|
2026
2055
|
};
|
|
2027
|
-
var bumpButton = {
|
|
2028
|
-
slot: "inspector.action.button",
|
|
2029
|
-
priority: 10
|
|
2030
|
-
};
|
|
2031
2056
|
var annotationStaleAnalyzer = {
|
|
2032
2057
|
id: ID11,
|
|
2033
2058
|
pluginId: CORE_PLUGIN_ID,
|
|
@@ -2036,15 +2061,13 @@ var annotationStaleAnalyzer = {
|
|
|
2036
2061
|
mode: "deterministic",
|
|
2037
2062
|
// The natural fix is to bump the node: refreshes the sidecar hashes,
|
|
2038
2063
|
// increments `annotations.version`, and stamps the audit block. The
|
|
2039
|
-
// inspector surfaces `core/node-bump`
|
|
2040
|
-
|
|
2064
|
+
// inspector surfaces that affordance via the `core/node-bump` action's
|
|
2065
|
+
// own scan-time `project()` self-projection, not from this analyzer.
|
|
2066
|
+
ui: { staleIcon, staleBadge },
|
|
2041
2067
|
evaluate(ctx) {
|
|
2042
2068
|
const issues = [];
|
|
2043
2069
|
for (const node of ctx.nodes) {
|
|
2044
2070
|
const status = staleStatus(node.sidecar);
|
|
2045
|
-
if (node.sidecar?.present === true) {
|
|
2046
|
-
emitBumpButton(ctx, node.path, status !== null);
|
|
2047
|
-
}
|
|
2048
2071
|
if (status === null) continue;
|
|
2049
2072
|
issues.push({
|
|
2050
2073
|
analyzerId: ID11,
|
|
@@ -2080,15 +2103,6 @@ function messageFor(status, path) {
|
|
|
2080
2103
|
return tx(ANNOTATION_STALE_TEXTS.bothDrift, { path });
|
|
2081
2104
|
}
|
|
2082
2105
|
}
|
|
2083
|
-
function emitBumpButton(ctx, nodePath, enabled) {
|
|
2084
|
-
ctx.emitContribution(nodePath, bumpButton, {
|
|
2085
|
-
actionId: "core/node-bump",
|
|
2086
|
-
label: ANNOTATION_STALE_TEXTS.bumpLabel,
|
|
2087
|
-
icon: "pi-arrow-up-right",
|
|
2088
|
-
enabled,
|
|
2089
|
-
...enabled ? {} : { disabledReason: ANNOTATION_STALE_TEXTS.bumpDisabledReason }
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
2106
|
function tooltipFor(status) {
|
|
2093
2107
|
switch (status) {
|
|
2094
2108
|
case "stale-body":
|
|
@@ -2239,7 +2253,7 @@ var ID15 = "link-conflict";
|
|
|
2239
2253
|
var NON_CONFLICTING_KINDS = /* @__PURE__ */ new Set(["points"]);
|
|
2240
2254
|
var linkConflictAnalyzer = {
|
|
2241
2255
|
id: ID15,
|
|
2242
|
-
pluginId:
|
|
2256
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2243
2257
|
kind: "analyzer",
|
|
2244
2258
|
description: "Flags conflicting arrow meanings between extractors (e.g. `references` vs `invokes`).",
|
|
2245
2259
|
mode: "deterministic",
|
|
@@ -2304,52 +2318,30 @@ function rankConfidence(c) {
|
|
|
2304
2318
|
return c;
|
|
2305
2319
|
}
|
|
2306
2320
|
|
|
2307
|
-
// kernel/util/
|
|
2308
|
-
function
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
return out;
|
|
2313
|
-
}
|
|
2314
|
-
function indexByCanonicalName(nodes, out) {
|
|
2315
|
-
for (const node of nodes) {
|
|
2316
|
-
const raw = canonicalName(node);
|
|
2317
|
-
if (raw === null) continue;
|
|
2318
|
-
const key = normalizeTrigger(raw);
|
|
2319
|
-
if (!out.has(key)) out.set(key, node.path);
|
|
2320
|
-
}
|
|
2321
|
+
// kernel/util/link-lines.ts
|
|
2322
|
+
function isSelfLoop(link) {
|
|
2323
|
+
if (link.source === link.target) return true;
|
|
2324
|
+
if (link.resolvedTarget && link.source === link.resolvedTarget) return true;
|
|
2325
|
+
return false;
|
|
2321
2326
|
}
|
|
2322
|
-
function
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
const
|
|
2326
|
-
if (
|
|
2327
|
-
const key = normalizeTrigger(derived);
|
|
2328
|
-
if (!out.has(key)) out.set(key, node.path);
|
|
2327
|
+
function linkLines(link) {
|
|
2328
|
+
const lines = /* @__PURE__ */ new Set();
|
|
2329
|
+
for (const occ of link.occurrences ?? []) {
|
|
2330
|
+
const line = occ.location?.line;
|
|
2331
|
+
if (typeof line === "number") lines.add(line);
|
|
2329
2332
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
2334
|
-
return raw;
|
|
2335
|
-
}
|
|
2336
|
-
function pathBasenameForLink(path) {
|
|
2337
|
-
const segments = path.split("/").filter((s) => s.length > 0);
|
|
2338
|
-
if (segments.length === 0) return path;
|
|
2339
|
-
const last = segments[segments.length - 1];
|
|
2340
|
-
if (last === "SKILL.md" && segments.length >= 2) {
|
|
2341
|
-
return segments[segments.length - 2];
|
|
2333
|
+
if (lines.size === 0) {
|
|
2334
|
+
const line = link.location?.line;
|
|
2335
|
+
if (typeof line === "number") lines.add(line);
|
|
2342
2336
|
}
|
|
2343
|
-
return
|
|
2337
|
+
return [...lines].sort((a, b) => a - b);
|
|
2344
2338
|
}
|
|
2345
|
-
function
|
|
2346
|
-
const
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
const resolved = nameIndex.get(normalized);
|
|
2352
|
-
return resolved ?? raw;
|
|
2339
|
+
function linkWhere(link, texts) {
|
|
2340
|
+
const lines = linkLines(link);
|
|
2341
|
+
if (lines.length === 0) return "";
|
|
2342
|
+
return tx(lines.length === 1 ? texts.single : texts.plural, {
|
|
2343
|
+
lines: lines.join(", ")
|
|
2344
|
+
});
|
|
2353
2345
|
}
|
|
2354
2346
|
|
|
2355
2347
|
// plugins/core/analyzers/link-counter/index.ts
|
|
@@ -2376,13 +2368,12 @@ var linkCounterAnalyzer = {
|
|
|
2376
2368
|
mode: "deterministic",
|
|
2377
2369
|
ui: { linksIn, linksOut },
|
|
2378
2370
|
evaluate(ctx) {
|
|
2379
|
-
const nameIndex = buildNameIndex(ctx.nodes);
|
|
2380
2371
|
const perTarget = /* @__PURE__ */ new Map();
|
|
2381
2372
|
const perSource = /* @__PURE__ */ new Map();
|
|
2382
2373
|
for (const link of ctx.links) {
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
bump(perTarget,
|
|
2374
|
+
if (isSelfLoop(link)) continue;
|
|
2375
|
+
const target = link.resolvedTarget ?? link.target;
|
|
2376
|
+
bump(perTarget, target, link.kind);
|
|
2386
2377
|
bump(perSource, link.source, link.kind);
|
|
2387
2378
|
}
|
|
2388
2379
|
for (const node of ctx.nodes) {
|
|
@@ -2416,27 +2407,6 @@ function formatBreakdown(byKind, direction) {
|
|
|
2416
2407
|
return [direction, ...lines].join("\n");
|
|
2417
2408
|
}
|
|
2418
2409
|
|
|
2419
|
-
// kernel/util/link-lines.ts
|
|
2420
|
-
function linkLines(link) {
|
|
2421
|
-
const lines = /* @__PURE__ */ new Set();
|
|
2422
|
-
for (const occ of link.occurrences ?? []) {
|
|
2423
|
-
const line = occ.location?.line;
|
|
2424
|
-
if (typeof line === "number") lines.add(line);
|
|
2425
|
-
}
|
|
2426
|
-
if (lines.size === 0) {
|
|
2427
|
-
const line = link.location?.line;
|
|
2428
|
-
if (typeof line === "number") lines.add(line);
|
|
2429
|
-
}
|
|
2430
|
-
return [...lines].sort((a, b) => a - b);
|
|
2431
|
-
}
|
|
2432
|
-
function linkWhere(link, texts) {
|
|
2433
|
-
const lines = linkLines(link);
|
|
2434
|
-
if (lines.length === 0) return "";
|
|
2435
|
-
return tx(lines.length === 1 ? texts.single : texts.plural, {
|
|
2436
|
-
lines: lines.join(", ")
|
|
2437
|
-
});
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
2410
|
// plugins/core/analyzers/link-self-loop/text.ts
|
|
2441
2411
|
var LINK_SELF_LOOP_TEXTS = {
|
|
2442
2412
|
/**
|
|
@@ -2482,22 +2452,13 @@ var linkSelfLoopAnalyzer = {
|
|
|
2482
2452
|
data: {
|
|
2483
2453
|
target: link.target,
|
|
2484
2454
|
resolvedTarget: link.resolvedTarget ?? link.target,
|
|
2485
|
-
kind: link.kind
|
|
2486
|
-
// Mark explicitly so UI / downstream consumers can read this
|
|
2487
|
-
// single field instead of re-computing the `source === target`
|
|
2488
|
-
// predicate themselves.
|
|
2489
|
-
selfLoop: true
|
|
2455
|
+
kind: link.kind
|
|
2490
2456
|
}
|
|
2491
2457
|
});
|
|
2492
2458
|
}
|
|
2493
2459
|
return issues;
|
|
2494
2460
|
}
|
|
2495
2461
|
};
|
|
2496
|
-
function isSelfLoop(link) {
|
|
2497
|
-
if (link.source === link.target) return true;
|
|
2498
|
-
if (link.resolvedTarget && link.source === link.resolvedTarget) return true;
|
|
2499
|
-
return false;
|
|
2500
|
-
}
|
|
2501
2462
|
|
|
2502
2463
|
// kernel/orchestrator/node-identifiers.ts
|
|
2503
2464
|
import { posix as pathPosix4 } from "path";
|
|
@@ -2539,16 +2500,34 @@ function readDirname(node) {
|
|
|
2539
2500
|
|
|
2540
2501
|
// kernel/orchestrator/lift-resolved-link-confidence.ts
|
|
2541
2502
|
var RESERVED_TARGET_CONFIDENCE = 0.1;
|
|
2503
|
+
var BROKEN_TARGET_CONFIDENCE = 0.5;
|
|
2542
2504
|
function liftResolvedLinkConfidence(links, nodes, ctx) {
|
|
2543
2505
|
if (!links.some((l) => l.confidence < 1)) return;
|
|
2544
2506
|
const indexes = buildIndexes(nodes, ctx);
|
|
2545
2507
|
for (const link of links) {
|
|
2546
|
-
if (link.confidence
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2508
|
+
if (link.confidence < 1) applyResolution(link, indexes, ctx);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
function collectBrokenLinks(links, nodes, ctx) {
|
|
2512
|
+
const broken = /* @__PURE__ */ new Set();
|
|
2513
|
+
if (links.length === 0) return broken;
|
|
2514
|
+
const indexes = buildIndexes(nodes, ctx);
|
|
2515
|
+
for (const link of links) {
|
|
2516
|
+
if (isGenuinelyBroken(link, indexes)) broken.add(link);
|
|
2517
|
+
}
|
|
2518
|
+
return broken;
|
|
2519
|
+
}
|
|
2520
|
+
function applyResolution(link, indexes, ctx) {
|
|
2521
|
+
const resolution = resolve2(link, indexes, ctx);
|
|
2522
|
+
if (resolution === "none") {
|
|
2523
|
+
if (isGenuinelyBroken(link, indexes)) {
|
|
2524
|
+
link.confidence = Math.min(link.confidence, BROKEN_TARGET_CONFIDENCE);
|
|
2525
|
+
}
|
|
2526
|
+
return;
|
|
2551
2527
|
}
|
|
2528
|
+
link.resolvedTarget = resolution;
|
|
2529
|
+
if (indexes.nodeByPath.get(resolution)?.virtual) return;
|
|
2530
|
+
link.confidence = ctx.reservedNodePaths.has(resolution) ? RESERVED_TARGET_CONFIDENCE : 1;
|
|
2552
2531
|
}
|
|
2553
2532
|
function buildIndexes(nodes, ctx) {
|
|
2554
2533
|
const byPath3 = /* @__PURE__ */ new Set();
|
|
@@ -2565,6 +2544,12 @@ function resolve2(link, indexes, ctx) {
|
|
|
2565
2544
|
if (indexes.byPath.has(link.target)) return link.target;
|
|
2566
2545
|
return resolveByName(link, indexes, ctx);
|
|
2567
2546
|
}
|
|
2547
|
+
function isGenuinelyBroken(link, indexes) {
|
|
2548
|
+
if (indexes.byPath.has(link.target)) return false;
|
|
2549
|
+
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
2550
|
+
if (stripped !== null && indexes.byName.has(stripped)) return false;
|
|
2551
|
+
return true;
|
|
2552
|
+
}
|
|
2568
2553
|
function resolveByName(link, indexes, ctx) {
|
|
2569
2554
|
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
2570
2555
|
if (stripped === null) return "none";
|
|
@@ -2608,7 +2593,7 @@ var NAME_RESERVED_TEXTS = {
|
|
|
2608
2593
|
* a runtime built-in. Same wording skill-map shipped before the
|
|
2609
2594
|
* source-side link finding landed.
|
|
2610
2595
|
*/
|
|
2611
|
-
message: "
|
|
2596
|
+
message: "Name collision: this {{kind}} name is already used by the {{provider}} runtime built-in, which shadows this file. Rename the file or its `frontmatter.name`.",
|
|
2612
2597
|
/**
|
|
2613
2598
|
* Source-side message: emitted on the node that AUTHORED a link
|
|
2614
2599
|
* whose target resolves to a reserved name. Explains WHY the link's
|
|
@@ -2616,7 +2601,7 @@ var NAME_RESERVED_TEXTS = {
|
|
|
2616
2601
|
* the kernel saw the target match a runtime built-in and downgraded
|
|
2617
2602
|
* the edge so the operator notices.
|
|
2618
2603
|
*/
|
|
2619
|
-
linkMessage: "{{target}}:\
|
|
2604
|
+
linkMessage: "{{target}}:\nName collision: resolves to a {{provider}} built-in ({{reservedKind}} `{{reservedPath}}`){{where}}; the built-in wins, so this edge drops to confidence {{confidence}}. Rename the target file or its `frontmatter.name`.",
|
|
2620
2605
|
/** Location suffix after the built-in parens, one detection site. */
|
|
2621
2606
|
whereSingle: " (line {{lines}})",
|
|
2622
2607
|
/** Location suffix after the built-in parens, several detection sites. */
|
|
@@ -2655,7 +2640,9 @@ var nameReservedAnalyzer = {
|
|
|
2655
2640
|
}
|
|
2656
2641
|
for (const link of ctx.links) {
|
|
2657
2642
|
if (link.confidence !== RESERVED_TARGET_CONFIDENCE) continue;
|
|
2658
|
-
const
|
|
2643
|
+
const reservedPath = link.resolvedTarget;
|
|
2644
|
+
if (!reservedPath || !reserved.has(reservedPath)) continue;
|
|
2645
|
+
const reservedNode = byPath3.get(reservedPath);
|
|
2659
2646
|
if (!reservedNode) continue;
|
|
2660
2647
|
issues.push({
|
|
2661
2648
|
analyzerId: ID18,
|
|
@@ -2688,41 +2675,6 @@ function linkWhereSuffix(link) {
|
|
|
2688
2675
|
plural: NAME_RESERVED_TEXTS.wherePlural
|
|
2689
2676
|
});
|
|
2690
2677
|
}
|
|
2691
|
-
function findReservedNodeForLink(link, reserved, byPath3) {
|
|
2692
|
-
if (reserved.has(link.target)) {
|
|
2693
|
-
const node = byPath3.get(link.target);
|
|
2694
|
-
if (node) return node;
|
|
2695
|
-
}
|
|
2696
|
-
const trigger = link.trigger?.normalizedTrigger;
|
|
2697
|
-
if (!trigger) return null;
|
|
2698
|
-
const stripped = trigger.replace(/^[/@]/, "").trim();
|
|
2699
|
-
if (stripped.length === 0) return null;
|
|
2700
|
-
for (const path of reserved) {
|
|
2701
|
-
const node = byPath3.get(path);
|
|
2702
|
-
if (!node) continue;
|
|
2703
|
-
if (matchesNodeIdentifier(node, stripped)) return node;
|
|
2704
|
-
}
|
|
2705
|
-
return null;
|
|
2706
|
-
}
|
|
2707
|
-
function matchesNodeIdentifier(node, stripped) {
|
|
2708
|
-
const candidates = [];
|
|
2709
|
-
const fmName = node.frontmatter?.["name"];
|
|
2710
|
-
if (typeof fmName === "string" && fmName.length > 0) candidates.push(normaliseId(fmName));
|
|
2711
|
-
const basename = node.path.split("/").pop() ?? "";
|
|
2712
|
-
if (basename) {
|
|
2713
|
-
const stem = basename.replace(/\.[^.]+$/, "");
|
|
2714
|
-
if (stem) candidates.push(normaliseId(stem));
|
|
2715
|
-
}
|
|
2716
|
-
const segs = node.path.split("/");
|
|
2717
|
-
if (segs.length >= 2) {
|
|
2718
|
-
const dirBase = segs[segs.length - 2];
|
|
2719
|
-
if (dirBase) candidates.push(normaliseId(dirBase));
|
|
2720
|
-
}
|
|
2721
|
-
return candidates.includes(stripped);
|
|
2722
|
-
}
|
|
2723
|
-
function normaliseId(raw) {
|
|
2724
|
-
return raw.normalize("NFD").replace(new RegExp("\\p{Mn}+", "gu"), "").toLowerCase().replace(/[-_\s]+/g, " ").replace(/ +/g, " ").trim();
|
|
2725
|
-
}
|
|
2726
2678
|
|
|
2727
2679
|
// plugins/core/analyzers/node-stability/text.ts
|
|
2728
2680
|
var NODE_STABILITY_TEXTS = {
|
|
@@ -2856,6 +2808,12 @@ var nodeSupersededAnalyzer = {
|
|
|
2856
2808
|
pluginId: CORE_PLUGIN_ID,
|
|
2857
2809
|
kind: "analyzer",
|
|
2858
2810
|
description: "Marks nodes replaced by a newer one via `supersededBy`.",
|
|
2811
|
+
// Part of the experimental supersession feature: ships disabled by
|
|
2812
|
+
// default alongside the declarer (`core/supersede` button +
|
|
2813
|
+
// `core/node-supersede` action). With the declarer off by default a
|
|
2814
|
+
// user rarely produces `supersededBy` data, so surfacing it stays
|
|
2815
|
+
// experimental too; the operator opts the whole family in together.
|
|
2816
|
+
stability: "experimental",
|
|
2859
2817
|
mode: "deterministic",
|
|
2860
2818
|
evaluate(ctx) {
|
|
2861
2819
|
const issues = [];
|
|
@@ -2887,7 +2845,7 @@ function pickSupersededBy(node) {
|
|
|
2887
2845
|
}
|
|
2888
2846
|
|
|
2889
2847
|
// plugins/core/analyzers/reference-broken/index.ts
|
|
2890
|
-
import {
|
|
2848
|
+
import { resolve as resolve3 } from "path";
|
|
2891
2849
|
|
|
2892
2850
|
// plugins/core/analyzers/reference-broken/text.ts
|
|
2893
2851
|
var REFERENCE_BROKEN_TEXTS = {
|
|
@@ -2918,13 +2876,7 @@ var REFERENCE_BROKEN_TEXTS = {
|
|
|
2918
2876
|
// Tooltips for the per-node view-contribution badges. Singular vs
|
|
2919
2877
|
// plural keeps the count grammar correct without a sub-template.
|
|
2920
2878
|
alertTooltipSingle: "This node has a broken reference. Open the inspector for details.",
|
|
2921
|
-
alertTooltipMany: "This node has {{count}} broken references. Open the inspector for details."
|
|
2922
|
-
// Fix-summary copy when the broken trigger has a same-named file on
|
|
2923
|
-
// disk that does not advertise `name:` in its frontmatter. Two
|
|
2924
|
-
// variants for single vs multiple candidates; same template family
|
|
2925
|
-
// as the alert tooltips above.
|
|
2926
|
-
hintSummarySingle: "Add `name: {{name}}` to the frontmatter of {{candidate}} so this reference resolves.",
|
|
2927
|
-
hintSummaryMany: "Add `name: {{name}}` to the frontmatter of one of these files so this reference resolves: {{candidates}}."
|
|
2879
|
+
alertTooltipMany: "This node has {{count}} broken references. Open the inspector for details."
|
|
2928
2880
|
};
|
|
2929
2881
|
|
|
2930
2882
|
// plugins/core/analyzers/reference-broken/index.ts
|
|
@@ -2940,33 +2892,31 @@ var referenceBrokenAnalyzer = {
|
|
|
2940
2892
|
// aggregate severity counters now owned by `core/issue-counter`. The
|
|
2941
2893
|
// detection logic stays intact, only the chip emission is gone.
|
|
2942
2894
|
ui: {},
|
|
2943
|
-
//
|
|
2944
|
-
//
|
|
2945
|
-
//
|
|
2946
|
-
//
|
|
2947
|
-
// emission) moved into
|
|
2895
|
+
// Pure projector of the orchestrator's genuinely-broken verdict
|
|
2896
|
+
// (`ctx.brokenLinks`, computed once from the same name index the
|
|
2897
|
+
// confidence lift uses), with the reference-paths escape hatch layered
|
|
2898
|
+
// on top. The per-source aggregation that historically lived alongside
|
|
2899
|
+
// (driving the now-retired chip emission) moved into
|
|
2900
|
+
// `core/issue-counter`.
|
|
2948
2901
|
evaluate(ctx) {
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
const
|
|
2952
|
-
const refIndex = ctx.referenceablePaths && ctx.referenceablePaths.size > 0 && ctx.cwd ? { paths: ctx.referenceablePaths, cwd: ctx.cwd } : null;
|
|
2902
|
+
const broken = ctx.brokenLinks;
|
|
2903
|
+
if (!broken || broken.size === 0) return [];
|
|
2904
|
+
const refIndex = buildReferenceIndex(ctx);
|
|
2953
2905
|
const issues = [];
|
|
2954
2906
|
for (const link of ctx.links) {
|
|
2955
|
-
if (
|
|
2907
|
+
if (!broken.has(link)) continue;
|
|
2956
2908
|
if (refIndex && resolvesViaReferencePaths(link, refIndex)) continue;
|
|
2957
|
-
|
|
2958
|
-
issues.push(buildIssue(link, candidates));
|
|
2909
|
+
issues.push(buildIssue(link));
|
|
2959
2910
|
}
|
|
2960
2911
|
return issues;
|
|
2961
2912
|
}
|
|
2962
2913
|
};
|
|
2963
|
-
function
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
const issue = {
|
|
2914
|
+
function buildReferenceIndex(ctx) {
|
|
2915
|
+
if (!ctx.referenceablePaths || ctx.referenceablePaths.size === 0 || !ctx.cwd) return null;
|
|
2916
|
+
return { paths: ctx.referenceablePaths, cwd: ctx.cwd };
|
|
2917
|
+
}
|
|
2918
|
+
function buildIssue(link) {
|
|
2919
|
+
return {
|
|
2970
2920
|
analyzerId: ID21,
|
|
2971
2921
|
// `error`, not `warn`: a link whose target is not in the scan is a
|
|
2972
2922
|
// structural defect the operator must notice, and the card chip
|
|
@@ -2984,86 +2934,17 @@ function buildIssue(link, hintCandidates = []) {
|
|
|
2984
2934
|
plural: REFERENCE_BROKEN_TEXTS.wherePlural
|
|
2985
2935
|
})
|
|
2986
2936
|
}),
|
|
2987
|
-
data
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
}
|
|
2992
|
-
function attachHint(issue, data, link, hintCandidates) {
|
|
2993
|
-
const suggestedName = (link.trigger?.normalizedTrigger ?? "").replace(/^[/@]/, "").trim();
|
|
2994
|
-
const candidatePaths = hintCandidates.map((n) => n.path);
|
|
2995
|
-
data["hint"] = {
|
|
2996
|
-
kind: "missing-frontmatter-name",
|
|
2997
|
-
suggestedName,
|
|
2998
|
-
candidates: candidatePaths
|
|
2999
|
-
};
|
|
3000
|
-
issue.fix = {
|
|
3001
|
-
summary: candidatePaths.length === 1 ? tx(REFERENCE_BROKEN_TEXTS.hintSummarySingle, {
|
|
3002
|
-
name: suggestedName,
|
|
3003
|
-
candidate: candidatePaths[0]
|
|
3004
|
-
}) : tx(REFERENCE_BROKEN_TEXTS.hintSummaryMany, {
|
|
3005
|
-
name: suggestedName,
|
|
3006
|
-
candidates: candidatePaths.join(", ")
|
|
3007
|
-
}),
|
|
3008
|
-
autofixable: false
|
|
2937
|
+
data: {
|
|
2938
|
+
target: link.target,
|
|
2939
|
+
kind: link.kind,
|
|
2940
|
+
trigger: link.trigger?.normalizedTrigger ?? null
|
|
2941
|
+
}
|
|
3009
2942
|
};
|
|
3010
2943
|
}
|
|
3011
2944
|
function resolvesViaReferencePaths(link, refIndex) {
|
|
3012
2945
|
if (!isPathStyleLink(link)) return false;
|
|
3013
2946
|
return refIndex.paths.has(resolve3(refIndex.cwd, link.target));
|
|
3014
2947
|
}
|
|
3015
|
-
function indexByNormalizedName(nodes) {
|
|
3016
|
-
const out = /* @__PURE__ */ new Map();
|
|
3017
|
-
for (const node of nodes) {
|
|
3018
|
-
const raw = node.frontmatter?.["name"];
|
|
3019
|
-
const name = typeof raw === "string" ? raw : "";
|
|
3020
|
-
if (!name) continue;
|
|
3021
|
-
const key = normalizeTrigger(name);
|
|
3022
|
-
const bucket = out.get(key) ?? [];
|
|
3023
|
-
bucket.push(node);
|
|
3024
|
-
out.set(key, bucket);
|
|
3025
|
-
}
|
|
3026
|
-
return out;
|
|
3027
|
-
}
|
|
3028
|
-
function basenameWithoutExt(path) {
|
|
3029
|
-
const base = pathPosix5.basename(path);
|
|
3030
|
-
const ext = pathPosix5.extname(base);
|
|
3031
|
-
return ext ? base.slice(0, -ext.length) : base;
|
|
3032
|
-
}
|
|
3033
|
-
function indexByBasenameWithoutName(nodes) {
|
|
3034
|
-
const out = /* @__PURE__ */ new Map();
|
|
3035
|
-
for (const node of nodes) {
|
|
3036
|
-
const raw = node.frontmatter?.["name"];
|
|
3037
|
-
const name = typeof raw === "string" ? raw : "";
|
|
3038
|
-
if (name) continue;
|
|
3039
|
-
const bare = basenameWithoutExt(node.path);
|
|
3040
|
-
if (!bare) continue;
|
|
3041
|
-
const key = normalizeTrigger(bare);
|
|
3042
|
-
if (!key) continue;
|
|
3043
|
-
const bucket = out.get(key) ?? [];
|
|
3044
|
-
bucket.push(node);
|
|
3045
|
-
out.set(key, bucket);
|
|
3046
|
-
}
|
|
3047
|
-
return out;
|
|
3048
|
-
}
|
|
3049
|
-
function findHintCandidates(link, idx) {
|
|
3050
|
-
const normalized = link.trigger?.normalizedTrigger;
|
|
3051
|
-
if (!normalized) return [];
|
|
3052
|
-
const sigil = normalized.charAt(0);
|
|
3053
|
-
if (sigil !== "/" && sigil !== "@") return [];
|
|
3054
|
-
const withoutSigil = normalized.slice(1).trim();
|
|
3055
|
-
if (!withoutSigil) return [];
|
|
3056
|
-
return idx.get(withoutSigil) ?? [];
|
|
3057
|
-
}
|
|
3058
|
-
function isResolved(link, byPath3, byNormalizedName) {
|
|
3059
|
-
const normalized = link.trigger?.normalizedTrigger;
|
|
3060
|
-
if (normalized) {
|
|
3061
|
-
const withoutSigil = normalized.replace(/^[/@]/, "").trim();
|
|
3062
|
-
if (byNormalizedName.has(withoutSigil)) return true;
|
|
3063
|
-
}
|
|
3064
|
-
if (byPath3.has(link.target)) return true;
|
|
3065
|
-
return false;
|
|
3066
|
-
}
|
|
3067
2948
|
function isPathStyleLink(link) {
|
|
3068
2949
|
const sigil = link.trigger?.normalizedTrigger?.charAt(0);
|
|
3069
2950
|
if (sigil === "/" || sigil === "@") return false;
|
|
@@ -3102,12 +2983,9 @@ var referenceRedundantAnalyzer = {
|
|
|
3102
2983
|
mode: "deterministic",
|
|
3103
2984
|
evaluate(ctx) {
|
|
3104
2985
|
if (ctx.links.length === 0) return [];
|
|
3105
|
-
const byPath3 = /* @__PURE__ */ new Map();
|
|
3106
|
-
for (const node of ctx.nodes) byPath3.set(node.path, node);
|
|
3107
|
-
const byName = buildNameIndex2(ctx.nodes);
|
|
3108
2986
|
const groups = /* @__PURE__ */ new Map();
|
|
3109
2987
|
for (const link of ctx.links) {
|
|
3110
|
-
const resolved =
|
|
2988
|
+
const resolved = link.resolvedTarget;
|
|
3111
2989
|
if (!resolved) continue;
|
|
3112
2990
|
const key = `${link.source}\0${resolved}`;
|
|
3113
2991
|
const bucket = groups.get(key);
|
|
@@ -3188,45 +3066,6 @@ function formatGroupedOccurrences(occurrences) {
|
|
|
3188
3066
|
})
|
|
3189
3067
|
).join(REFERENCE_REDUNDANT_TEXTS.occurrenceSeparator);
|
|
3190
3068
|
}
|
|
3191
|
-
function buildNameIndex2(nodes) {
|
|
3192
|
-
const out = /* @__PURE__ */ new Map();
|
|
3193
|
-
for (const node of nodes) {
|
|
3194
|
-
for (const candidate of collectIdentifiers(node)) {
|
|
3195
|
-
const normalised = normalizeTrigger(candidate);
|
|
3196
|
-
if (!normalised) continue;
|
|
3197
|
-
const bucket = out.get(normalised);
|
|
3198
|
-
if (bucket) bucket.push(node.path);
|
|
3199
|
-
else out.set(normalised, [node.path]);
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
return out;
|
|
3203
|
-
}
|
|
3204
|
-
function collectIdentifiers(node) {
|
|
3205
|
-
const out = [];
|
|
3206
|
-
const fmName = node.frontmatter?.["name"];
|
|
3207
|
-
if (typeof fmName === "string" && fmName.length > 0) out.push(fmName);
|
|
3208
|
-
const segs = node.path.split("/");
|
|
3209
|
-
const last = segs[segs.length - 1] ?? "";
|
|
3210
|
-
if (last) {
|
|
3211
|
-
const stem = last.replace(/\.[^.]+$/, "");
|
|
3212
|
-
if (stem) out.push(stem);
|
|
3213
|
-
}
|
|
3214
|
-
if (segs.length >= 2) {
|
|
3215
|
-
const dirBase = segs[segs.length - 2];
|
|
3216
|
-
if (dirBase) out.push(dirBase);
|
|
3217
|
-
}
|
|
3218
|
-
return out;
|
|
3219
|
-
}
|
|
3220
|
-
function resolveTargetPath(link, byPath3, byName) {
|
|
3221
|
-
if (byPath3.has(link.target)) return link.target;
|
|
3222
|
-
const trigger = link.trigger?.normalizedTrigger;
|
|
3223
|
-
if (!trigger) return null;
|
|
3224
|
-
const stripped = trigger.replace(/^[/@]/, "").trim();
|
|
3225
|
-
if (!stripped) return null;
|
|
3226
|
-
const candidates = byName.get(stripped);
|
|
3227
|
-
if (!candidates || candidates.length === 0) return null;
|
|
3228
|
-
return candidates[0] ?? null;
|
|
3229
|
-
}
|
|
3230
3069
|
|
|
3231
3070
|
// kernel/adapters/schema-validators.ts
|
|
3232
3071
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -3760,198 +3599,82 @@ function makeIssue(signal) {
|
|
|
3760
3599
|
return null;
|
|
3761
3600
|
}
|
|
3762
3601
|
|
|
3763
|
-
// plugins/core/analyzers/
|
|
3764
|
-
var
|
|
3765
|
-
/**
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
/**
|
|
3772
|
-
|
|
3602
|
+
// plugins/core/analyzers/trigger-collision/text.ts
|
|
3603
|
+
var TRIGGER_COLLISION_TEXTS = {
|
|
3604
|
+
/**
|
|
3605
|
+
* Top-level message when `analyzeTriggerBucket` accumulated exactly one
|
|
3606
|
+
* cause part. Used for the advertiser-ambiguous-only, invocation-
|
|
3607
|
+
* ambiguous-only, and cross-kind-only branches.
|
|
3608
|
+
*/
|
|
3609
|
+
messageOnePart: '"{{normalized}}":\nTrigger collision: {{part}}.',
|
|
3610
|
+
/**
|
|
3611
|
+
* Top-level message when `analyzeTriggerBucket` accumulated two cause
|
|
3612
|
+
* parts (advertiser-ambiguous AND invocation-ambiguous fire together).
|
|
3613
|
+
* The joiner lives inside the template so future locales can adapt it
|
|
3614
|
+
* (e.g. `'; y '` in Spanish) without touching the rule code.
|
|
3615
|
+
*/
|
|
3616
|
+
messageTwoParts: '"{{normalized}}":\nTrigger collision: {{first}}; and {{second}}.',
|
|
3617
|
+
/** `<n> advertisers: <list>` part, fires on the advertiser-ambiguous branch. */
|
|
3618
|
+
partAdvertisers: "{{count}} advertisers: {{paths}}",
|
|
3619
|
+
/** `<n> invocation forms: <list>` part, fires on the invocation-ambiguous branch. */
|
|
3620
|
+
partInvocations: "{{count}} invocation forms: {{forms}}",
|
|
3621
|
+
/** Singular cross-kind cause: `non-canonical invocation <form> against advertiser <path>`. */
|
|
3622
|
+
partNonCanonicalSingular: "non-canonical invocation {{forms}} against advertiser {{advertiser}}",
|
|
3623
|
+
/** Plural cross-kind cause: `non-canonical invocations <forms> against advertiser <path>`. */
|
|
3624
|
+
partNonCanonicalPlural: "non-canonical invocations {{forms}} against advertiser {{advertiser}}"
|
|
3773
3625
|
};
|
|
3774
3626
|
|
|
3775
|
-
// plugins/core/analyzers/
|
|
3776
|
-
var ID25 = "
|
|
3777
|
-
var
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3627
|
+
// plugins/core/analyzers/trigger-collision/index.ts
|
|
3628
|
+
var ID25 = "trigger-collision";
|
|
3629
|
+
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
3630
|
+
"command",
|
|
3631
|
+
"skill",
|
|
3632
|
+
"agent"
|
|
3633
|
+
]);
|
|
3634
|
+
var triggerCollisionAnalyzer = {
|
|
3782
3635
|
id: ID25,
|
|
3783
3636
|
pluginId: CORE_PLUGIN_ID,
|
|
3784
3637
|
kind: "analyzer",
|
|
3785
|
-
description: 'Projects the inspector "Supersede" button (declares a node replaced by another).',
|
|
3786
3638
|
mode: "deterministic",
|
|
3787
|
-
|
|
3639
|
+
description: "Flags two or more nodes that claim the same `/command` or `@agent` name.",
|
|
3640
|
+
// Two claim-collection passes (advertisement + invocation) feeding
|
|
3641
|
+
// the bucket map. Per-bucket analysis lives in `analyzeTriggerBucket`.
|
|
3642
|
+
// eslint-disable-next-line complexity
|
|
3788
3643
|
evaluate(ctx) {
|
|
3789
|
-
const
|
|
3644
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
3645
|
+
const push = (key, claim) => {
|
|
3646
|
+
const bucket = buckets.get(key) ?? [];
|
|
3647
|
+
bucket.push(claim);
|
|
3648
|
+
buckets.set(key, bucket);
|
|
3649
|
+
};
|
|
3790
3650
|
for (const node of ctx.nodes) {
|
|
3791
|
-
if (node.
|
|
3792
|
-
const
|
|
3793
|
-
|
|
3651
|
+
if (!ADVERTISING_KINDS.has(node.kind)) continue;
|
|
3652
|
+
const raw = node.frontmatter?.["name"];
|
|
3653
|
+
if (typeof raw !== "string" || raw.length === 0) continue;
|
|
3654
|
+
const normalized = `/${normalizeTrigger(raw)}`;
|
|
3655
|
+
if (normalized === "/") continue;
|
|
3656
|
+
push(normalized, {
|
|
3657
|
+
kind: "advertiser",
|
|
3658
|
+
token: node.path,
|
|
3659
|
+
nodeId: node.path,
|
|
3660
|
+
canonicalForm: `/${raw}`
|
|
3661
|
+
});
|
|
3794
3662
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
options
|
|
3811
|
-
}
|
|
3812
|
-
});
|
|
3813
|
-
}
|
|
3814
|
-
function resolveDisabledReason(node, optionCount) {
|
|
3815
|
-
if (alreadySuperseded(node)) return SUPERSEDE_TEXTS.supersedeDisabledReason;
|
|
3816
|
-
if (optionCount === 0) return SUPERSEDE_TEXTS.supersedeNoTargetsReason;
|
|
3817
|
-
return void 0;
|
|
3818
|
-
}
|
|
3819
|
-
function alreadySuperseded(node) {
|
|
3820
|
-
const sidecar = node.sidecar;
|
|
3821
|
-
if (!sidecar || sidecar.present !== true) return false;
|
|
3822
|
-
const ann = sidecar.annotations;
|
|
3823
|
-
if (!ann || typeof ann !== "object" || Array.isArray(ann)) return false;
|
|
3824
|
-
const value = ann["supersededBy"];
|
|
3825
|
-
return typeof value === "string" && value.length > 0;
|
|
3826
|
-
}
|
|
3827
|
-
|
|
3828
|
-
// plugins/core/analyzers/tags/text.ts
|
|
3829
|
-
var TAGS_TEXTS = {
|
|
3830
|
-
/** Label of the inspector action button that edits the node's tags. */
|
|
3831
|
-
editLabel: "Edit tags",
|
|
3832
|
-
/** Prompt label for the string-list tags input. */
|
|
3833
|
-
promptLabel: "Tags"
|
|
3834
|
-
};
|
|
3835
|
-
|
|
3836
|
-
// plugins/core/analyzers/tags/index.ts
|
|
3837
|
-
var ID26 = "tags";
|
|
3838
|
-
var setTagsButton = {
|
|
3839
|
-
slot: "inspector.action.button",
|
|
3840
|
-
priority: 15
|
|
3841
|
-
};
|
|
3842
|
-
var tagsAnalyzer = {
|
|
3843
|
-
id: ID26,
|
|
3844
|
-
pluginId: CORE_PLUGIN_ID,
|
|
3845
|
-
kind: "analyzer",
|
|
3846
|
-
description: `Projects the inspector "Edit tags" button (edits a node's taxonomy tags).`,
|
|
3847
|
-
mode: "deterministic",
|
|
3848
|
-
ui: { setTagsButton },
|
|
3849
|
-
evaluate(ctx) {
|
|
3850
|
-
for (const node of ctx.nodes) {
|
|
3851
|
-
if (node.sidecar?.present !== true) continue;
|
|
3852
|
-
emitSetTagsButton(ctx, node);
|
|
3853
|
-
}
|
|
3854
|
-
return [];
|
|
3855
|
-
}
|
|
3856
|
-
};
|
|
3857
|
-
function emitSetTagsButton(ctx, node) {
|
|
3858
|
-
ctx.emitContribution(node.path, setTagsButton, {
|
|
3859
|
-
actionId: "core/node-set-tags",
|
|
3860
|
-
label: TAGS_TEXTS.editLabel,
|
|
3861
|
-
icon: "pi-tags",
|
|
3862
|
-
enabled: true,
|
|
3863
|
-
prompt: {
|
|
3864
|
-
inputType: "string-list",
|
|
3865
|
-
paramKey: "tags",
|
|
3866
|
-
label: TAGS_TEXTS.promptLabel,
|
|
3867
|
-
defaultValue: currentTags(node)
|
|
3868
|
-
}
|
|
3869
|
-
});
|
|
3870
|
-
}
|
|
3871
|
-
function currentTags(node) {
|
|
3872
|
-
const ann = node.sidecar?.annotations;
|
|
3873
|
-
if (!ann || typeof ann !== "object" || Array.isArray(ann)) return [];
|
|
3874
|
-
const value = ann["tags"];
|
|
3875
|
-
if (!Array.isArray(value)) return [];
|
|
3876
|
-
return value.filter((t) => typeof t === "string");
|
|
3877
|
-
}
|
|
3878
|
-
|
|
3879
|
-
// plugins/core/analyzers/trigger-collision/text.ts
|
|
3880
|
-
var TRIGGER_COLLISION_TEXTS = {
|
|
3881
|
-
/**
|
|
3882
|
-
* Top-level message when `analyzeTriggerBucket` accumulated exactly one
|
|
3883
|
-
* cause part. Used for the advertiser-ambiguous-only, invocation-
|
|
3884
|
-
* ambiguous-only, and cross-kind-only branches.
|
|
3885
|
-
*/
|
|
3886
|
-
messageOnePart: '"{{normalized}}":\nTrigger collision: {{part}}.',
|
|
3887
|
-
/**
|
|
3888
|
-
* Top-level message when `analyzeTriggerBucket` accumulated two cause
|
|
3889
|
-
* parts (advertiser-ambiguous AND invocation-ambiguous fire together).
|
|
3890
|
-
* The joiner lives inside the template so future locales can adapt it
|
|
3891
|
-
* (e.g. `'; y '` in Spanish) without touching the rule code.
|
|
3892
|
-
*/
|
|
3893
|
-
messageTwoParts: '"{{normalized}}":\nTrigger collision: {{first}}; and {{second}}.',
|
|
3894
|
-
/** `<n> advertisers: <list>` part, fires on the advertiser-ambiguous branch. */
|
|
3895
|
-
partAdvertisers: "{{count}} advertisers: {{paths}}",
|
|
3896
|
-
/** `<n> invocation forms: <list>` part, fires on the invocation-ambiguous branch. */
|
|
3897
|
-
partInvocations: "{{count}} invocation forms: {{forms}}",
|
|
3898
|
-
/** Singular cross-kind cause: `non-canonical invocation <form> against advertiser <path>`. */
|
|
3899
|
-
partNonCanonicalSingular: "non-canonical invocation {{forms}} against advertiser {{advertiser}}",
|
|
3900
|
-
/** Plural cross-kind cause: `non-canonical invocations <forms> against advertiser <path>`. */
|
|
3901
|
-
partNonCanonicalPlural: "non-canonical invocations {{forms}} against advertiser {{advertiser}}"
|
|
3902
|
-
};
|
|
3903
|
-
|
|
3904
|
-
// plugins/core/analyzers/trigger-collision/index.ts
|
|
3905
|
-
var ID27 = "trigger-collision";
|
|
3906
|
-
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
3907
|
-
"command",
|
|
3908
|
-
"skill",
|
|
3909
|
-
"agent"
|
|
3910
|
-
]);
|
|
3911
|
-
var triggerCollisionAnalyzer = {
|
|
3912
|
-
id: ID27,
|
|
3913
|
-
pluginId: CORE_PLUGIN_ID,
|
|
3914
|
-
kind: "analyzer",
|
|
3915
|
-
mode: "deterministic",
|
|
3916
|
-
description: "Flags two or more nodes that claim the same `/command` or `@agent` name.",
|
|
3917
|
-
// Two claim-collection passes (advertisement + invocation) feeding
|
|
3918
|
-
// the bucket map. Per-bucket analysis lives in `analyzeTriggerBucket`.
|
|
3919
|
-
// eslint-disable-next-line complexity
|
|
3920
|
-
evaluate(ctx) {
|
|
3921
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
3922
|
-
const push = (key, claim) => {
|
|
3923
|
-
const bucket = buckets.get(key) ?? [];
|
|
3924
|
-
bucket.push(claim);
|
|
3925
|
-
buckets.set(key, bucket);
|
|
3926
|
-
};
|
|
3927
|
-
for (const node of ctx.nodes) {
|
|
3928
|
-
if (!ADVERTISING_KINDS.has(node.kind)) continue;
|
|
3929
|
-
const raw = node.frontmatter?.["name"];
|
|
3930
|
-
if (typeof raw !== "string" || raw.length === 0) continue;
|
|
3931
|
-
const normalized = `/${normalizeTrigger(raw)}`;
|
|
3932
|
-
if (normalized === "/") continue;
|
|
3933
|
-
push(normalized, {
|
|
3934
|
-
kind: "advertiser",
|
|
3935
|
-
token: node.path,
|
|
3936
|
-
nodeId: node.path,
|
|
3937
|
-
canonicalForm: `/${raw}`
|
|
3938
|
-
});
|
|
3939
|
-
}
|
|
3940
|
-
for (const link of ctx.links) {
|
|
3941
|
-
const normalized = link.trigger?.normalizedTrigger;
|
|
3942
|
-
if (!normalized) continue;
|
|
3943
|
-
push(normalized, {
|
|
3944
|
-
kind: "invocation",
|
|
3945
|
-
token: link.target,
|
|
3946
|
-
nodeId: link.source
|
|
3947
|
-
});
|
|
3948
|
-
}
|
|
3949
|
-
const issues = [];
|
|
3950
|
-
for (const [normalized, claims] of buckets) {
|
|
3951
|
-
const issue = analyzeTriggerBucket(normalized, claims);
|
|
3952
|
-
if (issue) issues.push(issue);
|
|
3953
|
-
}
|
|
3954
|
-
return issues;
|
|
3663
|
+
for (const link of ctx.links) {
|
|
3664
|
+
const normalized = link.trigger?.normalizedTrigger;
|
|
3665
|
+
if (!normalized) continue;
|
|
3666
|
+
push(normalized, {
|
|
3667
|
+
kind: "invocation",
|
|
3668
|
+
token: link.target,
|
|
3669
|
+
nodeId: link.source
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
const issues = [];
|
|
3673
|
+
for (const [normalized, claims] of buckets) {
|
|
3674
|
+
const issue = analyzeTriggerBucket(normalized, claims);
|
|
3675
|
+
if (issue) issues.push(issue);
|
|
3676
|
+
}
|
|
3677
|
+
return issues;
|
|
3955
3678
|
}
|
|
3956
3679
|
};
|
|
3957
3680
|
function analyzeTriggerBucket(normalized, claims) {
|
|
@@ -4007,7 +3730,7 @@ function analyzeTriggerBucket(normalized, claims) {
|
|
|
4007
3730
|
part: parts[0]
|
|
4008
3731
|
});
|
|
4009
3732
|
return {
|
|
4010
|
-
analyzerId:
|
|
3733
|
+
analyzerId: ID25,
|
|
4011
3734
|
severity: "error",
|
|
4012
3735
|
nodeIds,
|
|
4013
3736
|
message,
|
|
@@ -4047,13 +3770,13 @@ var ASCII_FORMATTER_TEXTS = {
|
|
|
4047
3770
|
};
|
|
4048
3771
|
|
|
4049
3772
|
// plugins/core/formatters/ascii/index.ts
|
|
4050
|
-
var
|
|
3773
|
+
var ID26 = "ascii";
|
|
4051
3774
|
var KIND_ORDER = ["agent", "command", "skill", "markdown"];
|
|
4052
3775
|
var asciiFormatter = {
|
|
4053
|
-
id:
|
|
3776
|
+
id: ID26,
|
|
4054
3777
|
pluginId: CORE_PLUGIN_ID,
|
|
4055
3778
|
kind: "formatter",
|
|
4056
|
-
formatId:
|
|
3779
|
+
formatId: ID26,
|
|
4057
3780
|
description: "Renders the scan as plain text in three sections: nodes (grouped by kind), arrows, and issues. Used by `sm scan --format ascii`.",
|
|
4058
3781
|
// ASCII tree formatter, header + per-kind sections + per-issue
|
|
4059
3782
|
// section. Each section iterates and renders; splitting per section
|
|
@@ -4147,13 +3870,13 @@ function renderSection(out, kind, group) {
|
|
|
4147
3870
|
}
|
|
4148
3871
|
|
|
4149
3872
|
// plugins/core/formatters/json/index.ts
|
|
4150
|
-
var
|
|
3873
|
+
var ID27 = "json";
|
|
4151
3874
|
var jsonFormatter = {
|
|
4152
|
-
id:
|
|
3875
|
+
id: ID27,
|
|
4153
3876
|
pluginId: CORE_PLUGIN_ID,
|
|
4154
3877
|
kind: "formatter",
|
|
4155
3878
|
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`.",
|
|
4156
|
-
formatId:
|
|
3879
|
+
formatId: ID27,
|
|
4157
3880
|
format(ctx) {
|
|
4158
3881
|
if (ctx.scanResult !== void 0) {
|
|
4159
3882
|
return JSON.stringify(ctx.scanResult);
|
|
@@ -4291,14 +4014,33 @@ function resolveSpecRoot2() {
|
|
|
4291
4014
|
}
|
|
4292
4015
|
}
|
|
4293
4016
|
|
|
4017
|
+
// plugins/core/actions/node-bump/text.ts
|
|
4018
|
+
var BUMP_TEXTS = {
|
|
4019
|
+
/** Label of the inspector action button that dispatches a bump. */
|
|
4020
|
+
bumpLabel: "Bump",
|
|
4021
|
+
/** Tooltip shown when the bump button is disabled (the node is fresh, no drift). */
|
|
4022
|
+
bumpDisabledReason: "No drift to bump."
|
|
4023
|
+
};
|
|
4024
|
+
|
|
4294
4025
|
// plugins/core/actions/node-bump/index.ts
|
|
4295
|
-
var
|
|
4026
|
+
var ID28 = "node-bump";
|
|
4027
|
+
var bumpButton = {
|
|
4028
|
+
slot: "inspector.action.button",
|
|
4029
|
+
priority: 10
|
|
4030
|
+
};
|
|
4296
4031
|
var nodeBumpAction = {
|
|
4297
|
-
id:
|
|
4032
|
+
id: ID28,
|
|
4298
4033
|
pluginId: CORE_PLUGIN_ID,
|
|
4299
4034
|
kind: "action",
|
|
4300
4035
|
description: "Marks a node as updated: bumps `annotations.version`, refreshes sidecar hashes, and records the timestamp.",
|
|
4301
4036
|
mode: "deterministic",
|
|
4037
|
+
ui: { bumpButton },
|
|
4038
|
+
project(ctx) {
|
|
4039
|
+
for (const node of ctx.nodes) {
|
|
4040
|
+
if (node.sidecar?.present !== true) continue;
|
|
4041
|
+
emitBumpButton(ctx, node.path, staleStatus2(node.sidecar) !== null);
|
|
4042
|
+
}
|
|
4043
|
+
},
|
|
4302
4044
|
// The runtime contract uses generic <TInput, TReport>; bump narrows
|
|
4303
4045
|
// both. The cast is the standard pattern for built-ins that want
|
|
4304
4046
|
// typed local I/O while staying compatible with the open generic.
|
|
@@ -4307,6 +4049,20 @@ var nodeBumpAction = {
|
|
|
4307
4049
|
return invokeBump(input, ctx);
|
|
4308
4050
|
}
|
|
4309
4051
|
};
|
|
4052
|
+
function emitBumpButton(ctx, nodePath, enabled) {
|
|
4053
|
+
ctx.emitContribution(nodePath, bumpButton, {
|
|
4054
|
+
actionId: "core/node-bump",
|
|
4055
|
+
label: BUMP_TEXTS.bumpLabel,
|
|
4056
|
+
icon: "pi-arrow-up-right",
|
|
4057
|
+
enabled,
|
|
4058
|
+
...enabled ? {} : { disabledReason: BUMP_TEXTS.bumpDisabledReason }
|
|
4059
|
+
});
|
|
4060
|
+
}
|
|
4061
|
+
function staleStatus2(overlay) {
|
|
4062
|
+
const status = overlay?.status;
|
|
4063
|
+
if (status === void 0 || status === null || status === "fresh") return null;
|
|
4064
|
+
return status;
|
|
4065
|
+
}
|
|
4310
4066
|
function invokeBump(input, ctx) {
|
|
4311
4067
|
const overlay = ctx.node.sidecar ?? null;
|
|
4312
4068
|
const isFresh = overlay?.present === true && overlay.status === "fresh";
|
|
@@ -4353,9 +4109,9 @@ function pickCurrentVersion(overlay) {
|
|
|
4353
4109
|
|
|
4354
4110
|
// plugins/core/actions/node-set-stability/index.ts
|
|
4355
4111
|
var STABILITY_VALUES = ["experimental", "stable", "deprecated"];
|
|
4356
|
-
var
|
|
4112
|
+
var ID29 = "node-set-stability";
|
|
4357
4113
|
var nodeSetStabilityAction = {
|
|
4358
|
-
id:
|
|
4114
|
+
id: ID29,
|
|
4359
4115
|
pluginId: CORE_PLUGIN_ID,
|
|
4360
4116
|
kind: "action",
|
|
4361
4117
|
description: "Sets the lifecycle stage of the current node (writes `stability` to the sidecar).",
|
|
@@ -4394,14 +4150,33 @@ function invokeSetStability(input, ctx) {
|
|
|
4394
4150
|
return { report, writes: [write] };
|
|
4395
4151
|
}
|
|
4396
4152
|
|
|
4153
|
+
// plugins/core/actions/node-set-tags/text.ts
|
|
4154
|
+
var TAGS_TEXTS = {
|
|
4155
|
+
/** Label of the inspector action button that edits the node's tags. */
|
|
4156
|
+
editLabel: "Edit tags",
|
|
4157
|
+
/** Prompt label for the string-list tags input. */
|
|
4158
|
+
promptLabel: "Tags"
|
|
4159
|
+
};
|
|
4160
|
+
|
|
4397
4161
|
// plugins/core/actions/node-set-tags/index.ts
|
|
4398
|
-
var
|
|
4162
|
+
var ID30 = "node-set-tags";
|
|
4163
|
+
var setTagsButton = {
|
|
4164
|
+
slot: "inspector.action.button",
|
|
4165
|
+
priority: 15
|
|
4166
|
+
};
|
|
4399
4167
|
var nodeSetTagsAction = {
|
|
4400
|
-
id:
|
|
4168
|
+
id: ID30,
|
|
4401
4169
|
pluginId: CORE_PLUGIN_ID,
|
|
4402
4170
|
kind: "action",
|
|
4403
4171
|
description: "Sets the taxonomy tags of the current node (writes `tags` to the sidecar; whole-array replace).",
|
|
4404
4172
|
mode: "deterministic",
|
|
4173
|
+
ui: { setTagsButton },
|
|
4174
|
+
project(ctx) {
|
|
4175
|
+
for (const node of ctx.nodes) {
|
|
4176
|
+
if (node.sidecar?.present !== true) continue;
|
|
4177
|
+
emitSetTagsButton(ctx, node);
|
|
4178
|
+
}
|
|
4179
|
+
},
|
|
4405
4180
|
// The runtime contract uses generic <TInput, TReport>; this narrows
|
|
4406
4181
|
// both. The cast is the standard pattern for built-ins that want
|
|
4407
4182
|
// typed local I/O while staying compatible with the open generic.
|
|
@@ -4410,6 +4185,27 @@ var nodeSetTagsAction = {
|
|
|
4410
4185
|
return invokeSetTags(input, ctx);
|
|
4411
4186
|
}
|
|
4412
4187
|
};
|
|
4188
|
+
function emitSetTagsButton(ctx, node) {
|
|
4189
|
+
ctx.emitContribution(node.path, setTagsButton, {
|
|
4190
|
+
actionId: "core/node-set-tags",
|
|
4191
|
+
label: TAGS_TEXTS.editLabel,
|
|
4192
|
+
icon: "pi-tags",
|
|
4193
|
+
enabled: true,
|
|
4194
|
+
prompt: {
|
|
4195
|
+
inputType: "string-list",
|
|
4196
|
+
paramKey: "tags",
|
|
4197
|
+
label: TAGS_TEXTS.promptLabel,
|
|
4198
|
+
defaultValue: currentTags(node)
|
|
4199
|
+
}
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
function currentTags(node) {
|
|
4203
|
+
const ann = node.sidecar?.annotations;
|
|
4204
|
+
if (!ann || typeof ann !== "object" || Array.isArray(ann)) return [];
|
|
4205
|
+
const value = ann["tags"];
|
|
4206
|
+
if (!Array.isArray(value)) return [];
|
|
4207
|
+
return value.filter((t) => typeof t === "string");
|
|
4208
|
+
}
|
|
4413
4209
|
function invokeSetTags(input, ctx) {
|
|
4414
4210
|
const tags = Array.isArray(input.tags) ? input.tags : [];
|
|
4415
4211
|
const timestamp = ctx.now().toISOString();
|
|
@@ -4432,23 +4228,82 @@ function invokeSetTags(input, ctx) {
|
|
|
4432
4228
|
const report = { ok: true, tags };
|
|
4433
4229
|
return { report, writes: [write] };
|
|
4434
4230
|
}
|
|
4435
|
-
|
|
4436
|
-
// plugins/core/actions/node-supersede/
|
|
4437
|
-
var
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4231
|
+
|
|
4232
|
+
// plugins/core/actions/node-supersede/text.ts
|
|
4233
|
+
var SUPERSEDE_TEXTS = {
|
|
4234
|
+
/** Label of the inspector action button that declares supersession. */
|
|
4235
|
+
supersedeLabel: "Supersede",
|
|
4236
|
+
/** Tooltip shown when the supersede button is disabled (already superseded). */
|
|
4237
|
+
supersedeDisabledReason: "Already superseded.",
|
|
4238
|
+
/** Tooltip shown when there is no other node to supersede this one. */
|
|
4239
|
+
supersedeNoTargetsReason: "No other node to supersede this one.",
|
|
4240
|
+
/** Prompt label for the target node-picker (enum-pick over the live node set). */
|
|
4241
|
+
supersedePromptLabel: "Superseded by"
|
|
4242
|
+
};
|
|
4243
|
+
|
|
4244
|
+
// plugins/core/actions/node-supersede/index.ts
|
|
4245
|
+
var ID31 = "node-supersede";
|
|
4246
|
+
var supersedeButton = {
|
|
4247
|
+
slot: "inspector.action.button",
|
|
4248
|
+
priority: 10
|
|
4249
|
+
};
|
|
4250
|
+
var nodeSupersedeAction = {
|
|
4251
|
+
id: ID31,
|
|
4252
|
+
pluginId: CORE_PLUGIN_ID,
|
|
4253
|
+
kind: "action",
|
|
4254
|
+
description: "Declares the current node as superseded by another (writes `supersededBy` to the sidecar).",
|
|
4255
|
+
// Ships disabled by default (the declarer feature is still settling its
|
|
4256
|
+
// node-picker UX). The button self-projection gates as a unit with the
|
|
4257
|
+
// invoke executor: an enabled button pointing at a disabled action
|
|
4258
|
+
// would error on click, so the whole action stays experimental.
|
|
4259
|
+
stability: "experimental",
|
|
4260
|
+
mode: "deterministic",
|
|
4261
|
+
ui: { supersedeButton },
|
|
4262
|
+
project(ctx) {
|
|
4263
|
+
const candidates = ctx.nodes.filter((n) => n.virtual !== true).map((n) => n.path);
|
|
4264
|
+
for (const node of ctx.nodes) {
|
|
4265
|
+
if (node.virtual === true) continue;
|
|
4266
|
+
const options = candidates.filter((p) => p !== node.path).map((p) => ({ value: p, label: p }));
|
|
4267
|
+
emitSupersedeButton(ctx, node, options);
|
|
4268
|
+
}
|
|
4269
|
+
},
|
|
4270
|
+
// The runtime contract uses generic <TInput, TReport>; supersede
|
|
4271
|
+
// narrows both. The cast is the standard pattern for built-ins that
|
|
4272
|
+
// want typed local I/O while staying compatible with the open generic.
|
|
4273
|
+
invoke(rawInput, ctx) {
|
|
4274
|
+
const input = rawInput ?? {};
|
|
4275
|
+
return invokeSupersede(input, ctx);
|
|
4276
|
+
}
|
|
4277
|
+
};
|
|
4278
|
+
function emitSupersedeButton(ctx, node, options) {
|
|
4279
|
+
const disabledReason = resolveDisabledReason(node, options.length);
|
|
4280
|
+
ctx.emitContribution(node.path, supersedeButton, {
|
|
4281
|
+
actionId: "core/node-supersede",
|
|
4282
|
+
label: SUPERSEDE_TEXTS.supersedeLabel,
|
|
4283
|
+
icon: "pi-arrow-right-arrow-left",
|
|
4284
|
+
enabled: disabledReason === void 0,
|
|
4285
|
+
...disabledReason === void 0 ? {} : { disabledReason },
|
|
4286
|
+
prompt: {
|
|
4287
|
+
inputType: "enum-pick",
|
|
4288
|
+
paramKey: "supersededBy",
|
|
4289
|
+
label: SUPERSEDE_TEXTS.supersedePromptLabel,
|
|
4290
|
+
options
|
|
4291
|
+
}
|
|
4292
|
+
});
|
|
4293
|
+
}
|
|
4294
|
+
function resolveDisabledReason(node, optionCount) {
|
|
4295
|
+
if (alreadySuperseded(node)) return SUPERSEDE_TEXTS.supersedeDisabledReason;
|
|
4296
|
+
if (optionCount === 0) return SUPERSEDE_TEXTS.supersedeNoTargetsReason;
|
|
4297
|
+
return void 0;
|
|
4298
|
+
}
|
|
4299
|
+
function alreadySuperseded(node) {
|
|
4300
|
+
const sidecar = node.sidecar;
|
|
4301
|
+
if (!sidecar || sidecar.present !== true) return false;
|
|
4302
|
+
const ann = sidecar.annotations;
|
|
4303
|
+
if (!ann || typeof ann !== "object" || Array.isArray(ann)) return false;
|
|
4304
|
+
const value = ann["supersededBy"];
|
|
4305
|
+
return typeof value === "string" && value.length > 0;
|
|
4306
|
+
}
|
|
4452
4307
|
function invokeSupersede(input, ctx) {
|
|
4453
4308
|
const supersededBy = input.supersededBy;
|
|
4454
4309
|
if (supersededBy === ctx.node.path) {
|
|
@@ -4769,11 +4624,11 @@ function validateOrDefault(parsed) {
|
|
|
4769
4624
|
if (!result.ok) return defaultSettings();
|
|
4770
4625
|
return result.data;
|
|
4771
4626
|
}
|
|
4772
|
-
function backfillSubObjects(
|
|
4627
|
+
function backfillSubObjects(settings2) {
|
|
4773
4628
|
return {
|
|
4774
|
-
...
|
|
4775
|
-
updateCheck:
|
|
4776
|
-
telemetry:
|
|
4629
|
+
...settings2,
|
|
4630
|
+
updateCheck: settings2.updateCheck ?? {},
|
|
4631
|
+
telemetry: settings2.telemetry ?? {}
|
|
4777
4632
|
};
|
|
4778
4633
|
}
|
|
4779
4634
|
function writeUserSettings(patch) {
|
|
@@ -4793,24 +4648,24 @@ function writeUserSettings(patch) {
|
|
|
4793
4648
|
}
|
|
4794
4649
|
}
|
|
4795
4650
|
function isUpdateCheckEnabled() {
|
|
4796
|
-
const
|
|
4797
|
-
return
|
|
4651
|
+
const settings2 = readUserSettings();
|
|
4652
|
+
return settings2.updateCheck?.enabled !== false;
|
|
4798
4653
|
}
|
|
4799
4654
|
function isErrorTelemetryEnabled() {
|
|
4800
|
-
const
|
|
4801
|
-
return
|
|
4655
|
+
const settings2 = readUserSettings();
|
|
4656
|
+
return settings2.telemetry?.errorsEnabled === true;
|
|
4802
4657
|
}
|
|
4803
4658
|
function isUsageCliTelemetryEnabled() {
|
|
4804
|
-
const
|
|
4805
|
-
return
|
|
4659
|
+
const settings2 = readUserSettings();
|
|
4660
|
+
return settings2.telemetry?.usageCliEnabled === true;
|
|
4806
4661
|
}
|
|
4807
4662
|
function isUsageUiTelemetryEnabled() {
|
|
4808
|
-
const
|
|
4809
|
-
return
|
|
4663
|
+
const settings2 = readUserSettings();
|
|
4664
|
+
return settings2.telemetry?.usageUiEnabled === true;
|
|
4810
4665
|
}
|
|
4811
4666
|
function readAnonymousId() {
|
|
4812
|
-
const
|
|
4813
|
-
return
|
|
4667
|
+
const settings2 = readUserSettings();
|
|
4668
|
+
return settings2.telemetry?.anonymousId ?? null;
|
|
4814
4669
|
}
|
|
4815
4670
|
function ensureAnonymousId(generate = () => randomUUID()) {
|
|
4816
4671
|
const existing = readAnonymousId();
|
|
@@ -4820,12 +4675,12 @@ function ensureAnonymousId(generate = () => randomUUID()) {
|
|
|
4820
4675
|
return id;
|
|
4821
4676
|
}
|
|
4822
4677
|
function hasTelemetryPromptBeenShown() {
|
|
4823
|
-
const
|
|
4824
|
-
return typeof
|
|
4678
|
+
const settings2 = readUserSettings();
|
|
4679
|
+
return typeof settings2.telemetry?.promptedAt === "number";
|
|
4825
4680
|
}
|
|
4826
4681
|
function hasSeenFirstRun() {
|
|
4827
|
-
const
|
|
4828
|
-
return typeof
|
|
4682
|
+
const settings2 = readUserSettings();
|
|
4683
|
+
return typeof settings2.telemetry?.firstRunAt === "number";
|
|
4829
4684
|
}
|
|
4830
4685
|
function mergeSettings(current, patch) {
|
|
4831
4686
|
const merged = {
|
|
@@ -4980,8 +4835,6 @@ var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", v
|
|
|
4980
4835
|
var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: VERSION };
|
|
4981
4836
|
var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: VERSION };
|
|
4982
4837
|
var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4983
|
-
var supersedeAnalyzer2 = { ...supersedeAnalyzer, pluginId: "core", version: VERSION };
|
|
4984
|
-
var tagsAnalyzer2 = { ...tagsAnalyzer, pluginId: "core", version: VERSION };
|
|
4985
4838
|
var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4986
4839
|
var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: VERSION };
|
|
4987
4840
|
var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: VERSION };
|
|
@@ -5048,8 +4901,6 @@ var builtInPlugins = [
|
|
|
5048
4901
|
referenceRedundantAnalyzer2,
|
|
5049
4902
|
schemaViolationAnalyzer2,
|
|
5050
4903
|
signalCollisionAnalyzer2,
|
|
5051
|
-
supersedeAnalyzer2,
|
|
5052
|
-
tagsAnalyzer2,
|
|
5053
4904
|
triggerCollisionAnalyzer2,
|
|
5054
4905
|
asciiFormatter2,
|
|
5055
4906
|
jsonFormatter2,
|
|
@@ -6239,6 +6090,10 @@ function removeConfigValue(key, opts) {
|
|
|
6239
6090
|
writeJsonAtomic(path, merged);
|
|
6240
6091
|
return true;
|
|
6241
6092
|
}
|
|
6093
|
+
function getValueSource(key, opts) {
|
|
6094
|
+
const loaded = loadConfigForScope(opts);
|
|
6095
|
+
return loaded.sources.get(key);
|
|
6096
|
+
}
|
|
6242
6097
|
function loadConfigForScope(opts) {
|
|
6243
6098
|
return loadConfig({
|
|
6244
6099
|
cwd: opts.cwd,
|
|
@@ -6458,7 +6313,7 @@ function resolveSpecRoot3() {
|
|
|
6458
6313
|
}
|
|
6459
6314
|
|
|
6460
6315
|
// cli/i18n/bump.texts.ts
|
|
6461
|
-
var
|
|
6316
|
+
var BUMP_TEXTS2 = {
|
|
6462
6317
|
// --- argument validation --------------------------------------------------
|
|
6463
6318
|
/**
|
|
6464
6319
|
* §3.1b two-line block. Mutex between the positional <node.path> and
|
|
@@ -8149,6 +8004,7 @@ function rowToNode(row) {
|
|
|
8149
8004
|
const parsed = JSON.parse(row.externalRefsJson);
|
|
8150
8005
|
if (Array.isArray(parsed) && parsed.length > 0) node.externalRefs = parsed;
|
|
8151
8006
|
}
|
|
8007
|
+
if (row.modifiedAtMs !== null) node.modifiedAtMs = row.modifiedAtMs;
|
|
8152
8008
|
return node;
|
|
8153
8009
|
}
|
|
8154
8010
|
function rowToLink(row) {
|
|
@@ -8665,7 +8521,10 @@ function nodeToRow(node, scannedAt) {
|
|
|
8665
8521
|
// the column stays sparse for nodes whose bodies have no http(s)
|
|
8666
8522
|
// URLs at all. Round-tripped by `rowToNode` on load.
|
|
8667
8523
|
externalRefsJson: node.externalRefs && node.externalRefs.length > 0 ? JSON.stringify(node.externalRefs) : null,
|
|
8668
|
-
scannedAt
|
|
8524
|
+
scannedAt,
|
|
8525
|
+
// File mtime (Unix ms) from the walker; NULL for virtual / derived
|
|
8526
|
+
// nodes that carry no backing file. Round-tripped by `rowToNode`.
|
|
8527
|
+
modifiedAtMs: node.modifiedAtMs ?? null
|
|
8669
8528
|
};
|
|
8670
8529
|
}
|
|
8671
8530
|
function projectAnnotationColumns(node) {
|
|
@@ -9666,10 +9525,10 @@ var BumpCommand = class extends SmCommand {
|
|
|
9666
9525
|
);
|
|
9667
9526
|
if (!persisted) {
|
|
9668
9527
|
this.printer.error(
|
|
9669
|
-
tx(
|
|
9528
|
+
tx(BUMP_TEXTS2.nodeNotFound, {
|
|
9670
9529
|
glyph: ansi.red("\u2715"),
|
|
9671
9530
|
nodePath: this.nodePath ?? "<pending>",
|
|
9672
|
-
hint: ansi.dim(
|
|
9531
|
+
hint: ansi.dim(BUMP_TEXTS2.nodeNotFoundHint)
|
|
9673
9532
|
})
|
|
9674
9533
|
);
|
|
9675
9534
|
return ExitCode.NotFound;
|
|
@@ -9689,27 +9548,27 @@ var BumpCommand = class extends SmCommand {
|
|
|
9689
9548
|
const errGlyph = ansi.red("\u2715");
|
|
9690
9549
|
if (this.pending && this.nodePath !== void 0) {
|
|
9691
9550
|
this.printer.error(
|
|
9692
|
-
tx(
|
|
9551
|
+
tx(BUMP_TEXTS2.nodeAndPendingMutex, {
|
|
9693
9552
|
glyph: errGlyph,
|
|
9694
|
-
hint: ansi.dim(
|
|
9553
|
+
hint: ansi.dim(BUMP_TEXTS2.nodeAndPendingMutexHint)
|
|
9695
9554
|
})
|
|
9696
9555
|
);
|
|
9697
9556
|
return ExitCode.Error;
|
|
9698
9557
|
}
|
|
9699
9558
|
if (!this.pending && this.nodePath === void 0) {
|
|
9700
9559
|
this.printer.error(
|
|
9701
|
-
tx(
|
|
9560
|
+
tx(BUMP_TEXTS2.noTargetSpecified, {
|
|
9702
9561
|
glyph: errGlyph,
|
|
9703
|
-
hint: ansi.dim(
|
|
9562
|
+
hint: ansi.dim(BUMP_TEXTS2.noTargetSpecifiedHint)
|
|
9704
9563
|
})
|
|
9705
9564
|
);
|
|
9706
9565
|
return ExitCode.Error;
|
|
9707
9566
|
}
|
|
9708
9567
|
if (this.staged && !this.pending) {
|
|
9709
9568
|
this.printer.error(
|
|
9710
|
-
tx(
|
|
9569
|
+
tx(BUMP_TEXTS2.stagedRequiresPending, {
|
|
9711
9570
|
glyph: errGlyph,
|
|
9712
|
-
hint: ansi.dim(
|
|
9571
|
+
hint: ansi.dim(BUMP_TEXTS2.stagedRequiresPendingHint)
|
|
9713
9572
|
})
|
|
9714
9573
|
);
|
|
9715
9574
|
return ExitCode.Error;
|
|
@@ -9768,10 +9627,10 @@ var BumpCommand = class extends SmCommand {
|
|
|
9768
9627
|
const node = nodes.find((n) => n.path === this.nodePath);
|
|
9769
9628
|
if (!node) {
|
|
9770
9629
|
this.printer.error(
|
|
9771
|
-
tx(
|
|
9630
|
+
tx(BUMP_TEXTS2.nodeNotFound, {
|
|
9772
9631
|
glyph: ansi.red("\u2715"),
|
|
9773
9632
|
nodePath: this.nodePath,
|
|
9774
|
-
hint: ansi.dim(
|
|
9633
|
+
hint: ansi.dim(BUMP_TEXTS2.nodeNotFoundHint)
|
|
9775
9634
|
})
|
|
9776
9635
|
);
|
|
9777
9636
|
return ExitCode.NotFound;
|
|
@@ -9793,9 +9652,9 @@ var BumpCommand = class extends SmCommand {
|
|
|
9793
9652
|
const errGlyph = ansi.red("\u2715");
|
|
9794
9653
|
if (item.status === "error") {
|
|
9795
9654
|
this.printer.error(
|
|
9796
|
-
tx(
|
|
9655
|
+
tx(BUMP_TEXTS2.bumpFailed, {
|
|
9797
9656
|
glyph: errGlyph,
|
|
9798
|
-
message: tx(
|
|
9657
|
+
message: tx(BUMP_TEXTS2.resolveAbsPathFailed, {
|
|
9799
9658
|
nodePath: node.path,
|
|
9800
9659
|
message: item.message
|
|
9801
9660
|
})
|
|
@@ -9805,10 +9664,10 @@ var BumpCommand = class extends SmCommand {
|
|
|
9805
9664
|
}
|
|
9806
9665
|
if (item.status === "refused") {
|
|
9807
9666
|
this.printer.error(
|
|
9808
|
-
tx(
|
|
9667
|
+
tx(BUMP_TEXTS2.refusedFresh, {
|
|
9809
9668
|
glyph: errGlyph,
|
|
9810
9669
|
nodePath: node.path,
|
|
9811
|
-
hint: ansi.dim(
|
|
9670
|
+
hint: ansi.dim(BUMP_TEXTS2.refusedFreshHint)
|
|
9812
9671
|
})
|
|
9813
9672
|
);
|
|
9814
9673
|
return ExitCode.Error;
|
|
@@ -9835,9 +9694,9 @@ var BumpCommand = class extends SmCommand {
|
|
|
9835
9694
|
if (applied.error !== void 0) {
|
|
9836
9695
|
if (applied.error instanceof EConsentRequiredError) throw applied.error;
|
|
9837
9696
|
this.printer.error(
|
|
9838
|
-
tx(
|
|
9697
|
+
tx(BUMP_TEXTS2.bumpFailed, {
|
|
9839
9698
|
glyph: ansi.red("\u2715"),
|
|
9840
|
-
message: tx(
|
|
9699
|
+
message: tx(BUMP_TEXTS2.storeFailedDetail, {
|
|
9841
9700
|
path: applied.sidecarPath ?? sidecarPathFor(item.absPath),
|
|
9842
9701
|
message: formatErrorMessage(applied.error)
|
|
9843
9702
|
})
|
|
@@ -9854,11 +9713,11 @@ var BumpCommand = class extends SmCommand {
|
|
|
9854
9713
|
const version = item.report.version ?? 1;
|
|
9855
9714
|
if (item.report.createdSidecar === true) {
|
|
9856
9715
|
this.printer.data(
|
|
9857
|
-
tx(
|
|
9716
|
+
tx(BUMP_TEXTS2.bumpedCreated, { glyph: okGlyph, sidecarPath, nodePath: node.path, version })
|
|
9858
9717
|
);
|
|
9859
9718
|
} else {
|
|
9860
9719
|
this.printer.data(
|
|
9861
|
-
tx(
|
|
9720
|
+
tx(BUMP_TEXTS2.bumped, { glyph: okGlyph, nodePath: node.path, version })
|
|
9862
9721
|
);
|
|
9863
9722
|
}
|
|
9864
9723
|
return ExitCode.Ok;
|
|
@@ -9870,7 +9729,7 @@ var BumpCommand = class extends SmCommand {
|
|
|
9870
9729
|
const stale = nodes.filter((n) => n.sidecar?.present === true && n.sidecar.status !== null && n.sidecar.status !== "fresh").sort((a, b) => a.path.localeCompare(b.path));
|
|
9871
9730
|
if (stale.length === 0) return this.#renderEmptyPending();
|
|
9872
9731
|
if (!this.json) {
|
|
9873
|
-
this.printer.info(tx(
|
|
9732
|
+
this.printer.info(tx(BUMP_TEXTS2.pendingBanner, { count: stale.length }));
|
|
9874
9733
|
}
|
|
9875
9734
|
const plan = computeBumpPlan(stale, { cwd, force: this.force });
|
|
9876
9735
|
const outcomes = await this.#executePending(plan, cwd, ansi);
|
|
@@ -9888,19 +9747,19 @@ var BumpCommand = class extends SmCommand {
|
|
|
9888
9747
|
const gitOk = ensureGitForStaged(cwd);
|
|
9889
9748
|
if (gitOk === "no-repo") {
|
|
9890
9749
|
this.printer.error(
|
|
9891
|
-
tx(
|
|
9750
|
+
tx(BUMP_TEXTS2.notInGitRepo, {
|
|
9892
9751
|
glyph: errGlyph,
|
|
9893
9752
|
cwd,
|
|
9894
|
-
hint: ansi.dim(
|
|
9753
|
+
hint: ansi.dim(BUMP_TEXTS2.notInGitRepoHint)
|
|
9895
9754
|
})
|
|
9896
9755
|
);
|
|
9897
9756
|
return ExitCode.NotFound;
|
|
9898
9757
|
}
|
|
9899
9758
|
if (gitOk === "no-binary") {
|
|
9900
9759
|
this.printer.error(
|
|
9901
|
-
tx(
|
|
9760
|
+
tx(BUMP_TEXTS2.gitBinaryMissing, {
|
|
9902
9761
|
glyph: errGlyph,
|
|
9903
|
-
hint: ansi.dim(
|
|
9762
|
+
hint: ansi.dim(BUMP_TEXTS2.gitBinaryMissingHint)
|
|
9904
9763
|
})
|
|
9905
9764
|
);
|
|
9906
9765
|
return ExitCode.Error;
|
|
@@ -9938,7 +9797,7 @@ var BumpCommand = class extends SmCommand {
|
|
|
9938
9797
|
return {
|
|
9939
9798
|
nodePath: item.nodePath,
|
|
9940
9799
|
status: "error",
|
|
9941
|
-
message: tx(
|
|
9800
|
+
message: tx(BUMP_TEXTS2.storeFailedDetail, {
|
|
9942
9801
|
path: applied.sidecarPath ?? sidecarPathFor(item.absPath),
|
|
9943
9802
|
message: formatErrorMessage(applied.error)
|
|
9944
9803
|
})
|
|
@@ -9958,11 +9817,11 @@ var BumpCommand = class extends SmCommand {
|
|
|
9958
9817
|
const addErr = stageSidecar(cwd, sidecarPath);
|
|
9959
9818
|
if (addErr === null || this.json) return;
|
|
9960
9819
|
this.printer.warn(
|
|
9961
|
-
tx(
|
|
9820
|
+
tx(BUMP_TEXTS2.gitAddFailed, {
|
|
9962
9821
|
glyph: ansi.yellow("\u26A0"),
|
|
9963
9822
|
path: sidecarPath,
|
|
9964
9823
|
message: addErr,
|
|
9965
|
-
hint: ansi.dim(tx(
|
|
9824
|
+
hint: ansi.dim(tx(BUMP_TEXTS2.gitAddFailedHint, { path: sidecarPath }))
|
|
9966
9825
|
})
|
|
9967
9826
|
);
|
|
9968
9827
|
}
|
|
@@ -9982,7 +9841,7 @@ var BumpCommand = class extends SmCommand {
|
|
|
9982
9841
|
this.printer.data(JSON.stringify(empty) + "\n");
|
|
9983
9842
|
return ExitCode.Ok;
|
|
9984
9843
|
}
|
|
9985
|
-
this.printer.data(
|
|
9844
|
+
this.printer.data(BUMP_TEXTS2.pendingNone);
|
|
9986
9845
|
return ExitCode.Ok;
|
|
9987
9846
|
}
|
|
9988
9847
|
// Complexity is from per-status rendering (4 status values) plus
|
|
@@ -10013,24 +9872,24 @@ var BumpCommand = class extends SmCommand {
|
|
|
10013
9872
|
for (const o of outcomes) {
|
|
10014
9873
|
if (o.status === "bumped") {
|
|
10015
9874
|
this.printer.data(
|
|
10016
|
-
tx(
|
|
9875
|
+
tx(BUMP_TEXTS2.bumpedItem, {
|
|
10017
9876
|
nodePath: o.nodePath,
|
|
10018
9877
|
version: o.version ?? 0,
|
|
10019
9878
|
createdSuffix: o.createdSidecar === true ? " (new sidecar)" : ""
|
|
10020
9879
|
})
|
|
10021
9880
|
);
|
|
10022
9881
|
} else if (o.status === "refused") {
|
|
10023
|
-
this.printer.data(tx(
|
|
9882
|
+
this.printer.data(tx(BUMP_TEXTS2.refusedItem, { nodePath: o.nodePath }));
|
|
10024
9883
|
} else if (o.status === "skipped") {
|
|
10025
9884
|
this.printer.data(
|
|
10026
|
-
tx(
|
|
9885
|
+
tx(BUMP_TEXTS2.skippedItem, {
|
|
10027
9886
|
nodePath: o.nodePath,
|
|
10028
9887
|
reason: o.reason ?? "unknown"
|
|
10029
9888
|
})
|
|
10030
9889
|
);
|
|
10031
9890
|
} else {
|
|
10032
9891
|
this.printer.data(
|
|
10033
|
-
tx(
|
|
9892
|
+
tx(BUMP_TEXTS2.errorItem, {
|
|
10034
9893
|
nodePath: o.nodePath,
|
|
10035
9894
|
message: o.message ?? ""
|
|
10036
9895
|
})
|
|
@@ -10038,7 +9897,7 @@ var BumpCommand = class extends SmCommand {
|
|
|
10038
9897
|
}
|
|
10039
9898
|
}
|
|
10040
9899
|
this.printer.info(
|
|
10041
|
-
tx(
|
|
9900
|
+
tx(BUMP_TEXTS2.pendingSummary, {
|
|
10042
9901
|
bumped: counts.bumped,
|
|
10043
9902
|
refused: counts.refused,
|
|
10044
9903
|
skipped: counts.skipped,
|
|
@@ -11091,29 +10950,39 @@ function isPluginLocked(idOrQualified) {
|
|
|
11091
10950
|
}
|
|
11092
10951
|
|
|
11093
10952
|
// kernel/config/plugin-resolver.ts
|
|
11094
|
-
|
|
10953
|
+
var SHIPS_DISABLED = /* @__PURE__ */ new Set([
|
|
10954
|
+
"experimental",
|
|
10955
|
+
"deprecated"
|
|
10956
|
+
]);
|
|
10957
|
+
function installedDefaultEnabled(stability) {
|
|
10958
|
+
return stability === void 0 || !SHIPS_DISABLED.has(stability);
|
|
10959
|
+
}
|
|
10960
|
+
function resolvePluginEnabled(pluginId, cfg, dbOverrides, installedDefault = true) {
|
|
11095
10961
|
if (isPluginLocked(pluginId)) return true;
|
|
11096
10962
|
if (dbOverrides.has(pluginId)) return dbOverrides.get(pluginId) === true;
|
|
11097
10963
|
const settingsEntry = cfg.plugins[pluginId];
|
|
11098
10964
|
if (settingsEntry?.enabled !== void 0) return settingsEntry.enabled;
|
|
11099
|
-
return
|
|
10965
|
+
return installedDefault;
|
|
11100
10966
|
}
|
|
11101
10967
|
function makeEnabledResolver(cfg, dbOverrides) {
|
|
11102
|
-
return (pluginId) => resolvePluginEnabled(pluginId, cfg, dbOverrides);
|
|
10968
|
+
return (pluginId, installedDefault) => resolvePluginEnabled(pluginId, cfg, dbOverrides, installedDefault);
|
|
11103
10969
|
}
|
|
11104
10970
|
|
|
11105
10971
|
// core/runtime/plugin-runtime/resolver.ts
|
|
11106
|
-
function defaultResolveEnabled(_id) {
|
|
11107
|
-
return
|
|
10972
|
+
function defaultResolveEnabled(_id, installedDefault = true) {
|
|
10973
|
+
return installedDefault;
|
|
11108
10974
|
}
|
|
11109
10975
|
function isBuiltInExtensionEnabled(plugin, ext, resolveEnabled) {
|
|
11110
|
-
return isPluginEntryEnabled(plugin, ext.id, resolveEnabled);
|
|
10976
|
+
return isPluginEntryEnabled(plugin, ext.id, resolveEnabled, ext.stability);
|
|
11111
10977
|
}
|
|
11112
|
-
function isPluginEntryEnabled(plugin, extId, resolveEnabled) {
|
|
11113
|
-
return resolveEnabled(qualifiedExtensionId(plugin.id, extId));
|
|
10978
|
+
function isPluginEntryEnabled(plugin, extId, resolveEnabled, stability) {
|
|
10979
|
+
return resolveEnabled(qualifiedExtensionId(plugin.id, extId), installedDefaultEnabled(stability));
|
|
11114
10980
|
}
|
|
11115
10981
|
function isPluginExtensionEnabled(ext, resolveEnabled) {
|
|
11116
|
-
return resolveEnabled(
|
|
10982
|
+
return resolveEnabled(
|
|
10983
|
+
qualifiedExtensionId(ext.pluginId, ext.id),
|
|
10984
|
+
installedDefaultEnabled(ext.stability)
|
|
10985
|
+
);
|
|
11117
10986
|
}
|
|
11118
10987
|
async function buildEnabledResolver(ctx) {
|
|
11119
10988
|
const { effective: cfg } = loadConfig({ ...ctx });
|
|
@@ -11311,11 +11180,11 @@ async function* walkContent(roots, options) {
|
|
|
11311
11180
|
const extensions = options.extensions;
|
|
11312
11181
|
const sizeLimit = buildSizeLimit(options);
|
|
11313
11182
|
for (const root of roots) {
|
|
11314
|
-
for await (const
|
|
11315
|
-
const relPath = relative2(root,
|
|
11183
|
+
for await (const entry of walkRoot(root, root, filter, extensions, sizeLimit)) {
|
|
11184
|
+
const relPath = relative2(root, entry.full).split(sep3).join("/");
|
|
11316
11185
|
let raw;
|
|
11317
11186
|
try {
|
|
11318
|
-
raw = await readFile(
|
|
11187
|
+
raw = await readFile(entry.full, "utf8");
|
|
11319
11188
|
} catch {
|
|
11320
11189
|
continue;
|
|
11321
11190
|
}
|
|
@@ -11325,6 +11194,9 @@ async function* walkContent(roots, options) {
|
|
|
11325
11194
|
body: parsed.body,
|
|
11326
11195
|
frontmatterRaw: parsed.frontmatterRaw,
|
|
11327
11196
|
frontmatter: parsed.frontmatter,
|
|
11197
|
+
// File mtime from the TOCTOU `lstat` (zero extra syscalls).
|
|
11198
|
+
// Threaded onto the persisted `Node` as `modifiedAtMs`.
|
|
11199
|
+
modifiedAtMs: entry.modifiedAtMs,
|
|
11328
11200
|
// Audit L1: forward parser diagnostics (e.g. malformed YAML)
|
|
11329
11201
|
// through the IRawNode surface so the orchestrator can
|
|
11330
11202
|
// convert them into warn-level kernel `Issue` rows. Omitted
|
|
@@ -11365,7 +11237,7 @@ async function* walkRoot(root, current, filter, extensions, sizeLimit) {
|
|
|
11365
11237
|
sizeLimit.onOversizedFile?.({ path: rel, bytes: s.size });
|
|
11366
11238
|
continue;
|
|
11367
11239
|
}
|
|
11368
|
-
yield full;
|
|
11240
|
+
yield { full, modifiedAtMs: Math.round(s.mtimeMs) };
|
|
11369
11241
|
} catch {
|
|
11370
11242
|
}
|
|
11371
11243
|
}
|
|
@@ -11447,8 +11319,8 @@ function bucketLoaded(loaded, runtime, pluginOrder) {
|
|
|
11447
11319
|
extractor: runtime.extensions.extractors,
|
|
11448
11320
|
analyzer: runtime.extensions.analyzers,
|
|
11449
11321
|
formatter: runtime.extensions.formatters,
|
|
11450
|
-
hook: runtime.extensions.hooks
|
|
11451
|
-
|
|
11322
|
+
hook: runtime.extensions.hooks,
|
|
11323
|
+
action: runtime.extensions.actions
|
|
11452
11324
|
});
|
|
11453
11325
|
runtime.manifests.push({
|
|
11454
11326
|
id: ext.id,
|
|
@@ -11563,7 +11435,7 @@ async function loadPluginRuntime(opts = {}) {
|
|
|
11563
11435
|
const loader = createPluginLoader(loaderOpts);
|
|
11564
11436
|
const discovered = await loader.discoverAndLoadAll();
|
|
11565
11437
|
const runtime = {
|
|
11566
|
-
extensions: { providers: [], extractors: [], analyzers: [], formatters: [], hooks: [] },
|
|
11438
|
+
extensions: { providers: [], extractors: [], analyzers: [], formatters: [], hooks: [], actions: [] },
|
|
11567
11439
|
annotationContributions: [],
|
|
11568
11440
|
viewContributions: [],
|
|
11569
11441
|
manifests: [],
|
|
@@ -11618,7 +11490,7 @@ function enforceRootExclusivity(catalog) {
|
|
|
11618
11490
|
}
|
|
11619
11491
|
function emptyPluginRuntime() {
|
|
11620
11492
|
const runtime = {
|
|
11621
|
-
extensions: { providers: [], extractors: [], analyzers: [], formatters: [], hooks: [] },
|
|
11493
|
+
extensions: { providers: [], extractors: [], analyzers: [], formatters: [], hooks: [], actions: [] },
|
|
11622
11494
|
annotationContributions: [],
|
|
11623
11495
|
viewContributions: [],
|
|
11624
11496
|
manifests: [],
|
|
@@ -11636,50 +11508,59 @@ function emptyPluginRuntime() {
|
|
|
11636
11508
|
function collectRegisteredContributionKeys(composed) {
|
|
11637
11509
|
const keys = /* @__PURE__ */ new Set();
|
|
11638
11510
|
if (!composed) return keys;
|
|
11639
|
-
for (const ext of [...composed.extractors, ...composed.analyzers]) {
|
|
11640
|
-
|
|
11641
|
-
if (typeof raw !== "object" || raw === null) continue;
|
|
11642
|
-
for (const [contributionId, value] of Object.entries(raw)) {
|
|
11643
|
-
if (typeof value !== "object" || value === null) continue;
|
|
11644
|
-
keys.add(`${ext.pluginId}/${ext.id}/${contributionId}`);
|
|
11645
|
-
}
|
|
11511
|
+
for (const ext of [...composed.extractors, ...composed.analyzers, ...composed.actions ?? []]) {
|
|
11512
|
+
addContributionKeysForExtension(ext, keys);
|
|
11646
11513
|
}
|
|
11647
11514
|
return keys;
|
|
11648
11515
|
}
|
|
11516
|
+
function addContributionKeysForExtension(ext, keys) {
|
|
11517
|
+
const raw = ext.ui;
|
|
11518
|
+
if (typeof raw !== "object" || raw === null) return;
|
|
11519
|
+
for (const [contributionId, value] of Object.entries(raw)) {
|
|
11520
|
+
if (typeof value !== "object" || value === null) continue;
|
|
11521
|
+
keys.add(`${ext.pluginId}/${ext.id}/${contributionId}`);
|
|
11522
|
+
}
|
|
11523
|
+
}
|
|
11649
11524
|
function filterBuiltInManifests(manifests, resolveEnabled) {
|
|
11650
11525
|
const pluginById = /* @__PURE__ */ new Map();
|
|
11651
11526
|
for (const plugin of builtInPlugins) pluginById.set(plugin.id, plugin);
|
|
11652
11527
|
return manifests.filter((m) => {
|
|
11653
11528
|
const plugin = pluginById.get(m.pluginId);
|
|
11654
11529
|
if (!plugin) return true;
|
|
11655
|
-
return isPluginEntryEnabled(plugin, m.id, resolveEnabled);
|
|
11530
|
+
return isPluginEntryEnabled(plugin, m.id, resolveEnabled, m.stability);
|
|
11656
11531
|
});
|
|
11657
11532
|
}
|
|
11658
11533
|
|
|
11659
11534
|
// core/runtime/plugin-runtime/composer.ts
|
|
11660
11535
|
function composeScanExtensions(opts) {
|
|
11661
11536
|
const resolveEnabled = opts.resolveEnabled ?? opts.pluginRuntime.resolveEnabled;
|
|
11537
|
+
const resolveSettings = opts.resolveSettings;
|
|
11662
11538
|
const providers = [];
|
|
11663
11539
|
const extractors = [];
|
|
11664
11540
|
const analyzers = [];
|
|
11665
11541
|
const hooks = [];
|
|
11542
|
+
const actions = [];
|
|
11666
11543
|
if (!opts.noBuiltIns) {
|
|
11667
11544
|
accumulateBuiltInScanExtensions(
|
|
11668
|
-
{ providers, extractors, analyzers, hooks },
|
|
11669
|
-
resolveEnabled
|
|
11545
|
+
{ providers, extractors, analyzers, hooks, actions },
|
|
11546
|
+
resolveEnabled,
|
|
11547
|
+
resolveSettings
|
|
11670
11548
|
);
|
|
11671
11549
|
}
|
|
11672
11550
|
for (const ext of opts.pluginRuntime.extensions.providers) {
|
|
11673
|
-
if (isPluginExtensionEnabled(ext, resolveEnabled)) providers.push(ext);
|
|
11551
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) providers.push(withResolvedSettings(ext, resolveSettings));
|
|
11674
11552
|
}
|
|
11675
11553
|
for (const ext of opts.pluginRuntime.extensions.extractors) {
|
|
11676
|
-
if (isPluginExtensionEnabled(ext, resolveEnabled)) extractors.push(ext);
|
|
11554
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) extractors.push(withResolvedSettings(ext, resolveSettings));
|
|
11677
11555
|
}
|
|
11678
11556
|
for (const ext of opts.pluginRuntime.extensions.analyzers) {
|
|
11679
|
-
if (isPluginExtensionEnabled(ext, resolveEnabled)) analyzers.push(ext);
|
|
11557
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) analyzers.push(withResolvedSettings(ext, resolveSettings));
|
|
11680
11558
|
}
|
|
11681
11559
|
for (const ext of opts.pluginRuntime.extensions.hooks) {
|
|
11682
|
-
if (isPluginExtensionEnabled(ext, resolveEnabled)) hooks.push(ext);
|
|
11560
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) hooks.push(withResolvedSettings(ext, resolveSettings));
|
|
11561
|
+
}
|
|
11562
|
+
for (const ext of opts.pluginRuntime.extensions.actions) {
|
|
11563
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) actions.push(ext);
|
|
11683
11564
|
}
|
|
11684
11565
|
const finalProviders = opts.killSwitches?.providers === true ? [] : providers;
|
|
11685
11566
|
const finalExtractors = opts.killSwitches?.extractors === true ? [] : extractors;
|
|
@@ -11691,27 +11572,33 @@ function composeScanExtensions(opts) {
|
|
|
11691
11572
|
providers: finalProviders,
|
|
11692
11573
|
extractors: finalExtractors,
|
|
11693
11574
|
analyzers: finalAnalyzers,
|
|
11694
|
-
hooks
|
|
11575
|
+
hooks,
|
|
11576
|
+
actions
|
|
11695
11577
|
};
|
|
11696
11578
|
}
|
|
11697
|
-
function
|
|
11579
|
+
function withResolvedSettings(ext, resolveSettings) {
|
|
11580
|
+
if (!resolveSettings) return ext;
|
|
11581
|
+
return { ...ext, resolvedSettings: resolveSettings(ext) };
|
|
11582
|
+
}
|
|
11583
|
+
function accumulateBuiltInScanExtensions(buckets, resolveEnabled, resolveSettings) {
|
|
11698
11584
|
for (const plugin of builtInPlugins) {
|
|
11699
11585
|
for (const ext of plugin.extensions) {
|
|
11700
11586
|
if (!isBuiltInExtensionEnabled(plugin, ext, resolveEnabled)) continue;
|
|
11701
11587
|
switch (ext.kind) {
|
|
11702
11588
|
case "provider":
|
|
11703
|
-
buckets.providers.push(ext);
|
|
11589
|
+
buckets.providers.push(withResolvedSettings(ext, resolveSettings));
|
|
11704
11590
|
break;
|
|
11705
11591
|
case "extractor":
|
|
11706
|
-
buckets.extractors.push(ext);
|
|
11592
|
+
buckets.extractors.push(withResolvedSettings(ext, resolveSettings));
|
|
11707
11593
|
break;
|
|
11708
11594
|
case "analyzer":
|
|
11709
|
-
buckets.analyzers.push(ext);
|
|
11595
|
+
buckets.analyzers.push(withResolvedSettings(ext, resolveSettings));
|
|
11710
11596
|
break;
|
|
11711
11597
|
case "hook":
|
|
11712
|
-
buckets.hooks.push(ext);
|
|
11598
|
+
buckets.hooks.push(withResolvedSettings(ext, resolveSettings));
|
|
11713
11599
|
break;
|
|
11714
11600
|
case "action":
|
|
11601
|
+
buckets.actions.push(ext);
|
|
11715
11602
|
break;
|
|
11716
11603
|
case "formatter":
|
|
11717
11604
|
break;
|
|
@@ -11726,18 +11613,19 @@ function accumulateBuiltInScanExtensions(buckets, resolveEnabled) {
|
|
|
11726
11613
|
function composeFormatters(opts) {
|
|
11727
11614
|
const noBuiltIns = opts.noBuiltIns ?? false;
|
|
11728
11615
|
const resolveEnabled = opts.resolveEnabled ?? opts.pluginRuntime.resolveEnabled;
|
|
11616
|
+
const resolveSettings = opts.resolveSettings;
|
|
11729
11617
|
const out = [];
|
|
11730
11618
|
if (!noBuiltIns) {
|
|
11731
11619
|
for (const plugin of builtInPlugins) {
|
|
11732
11620
|
for (const ext of plugin.extensions) {
|
|
11733
11621
|
if (ext.kind !== "formatter") continue;
|
|
11734
11622
|
if (!isBuiltInExtensionEnabled(plugin, ext, resolveEnabled)) continue;
|
|
11735
|
-
out.push(ext);
|
|
11623
|
+
out.push(withResolvedSettings(ext, resolveSettings));
|
|
11736
11624
|
}
|
|
11737
11625
|
}
|
|
11738
11626
|
}
|
|
11739
11627
|
for (const ext of opts.pluginRuntime.extensions.formatters) {
|
|
11740
|
-
if (isPluginExtensionEnabled(ext, resolveEnabled)) out.push(ext);
|
|
11628
|
+
if (isPluginExtensionEnabled(ext, resolveEnabled)) out.push(withResolvedSettings(ext, resolveSettings));
|
|
11741
11629
|
}
|
|
11742
11630
|
return out;
|
|
11743
11631
|
}
|
|
@@ -14924,6 +14812,9 @@ var GraphCommand = class extends SmCommand {
|
|
|
14924
14812
|
nodes: scan.nodes,
|
|
14925
14813
|
links: scan.links,
|
|
14926
14814
|
issues: scan.issues,
|
|
14815
|
+
// Resolved settings of the formatter (empty when the formatter
|
|
14816
|
+
// declares none, or when the composer did not populate them).
|
|
14817
|
+
settings: formatter.resolvedSettings ?? {},
|
|
14927
14818
|
// Pass the full persisted scan so format-specific renderers
|
|
14928
14819
|
// that mirror a `ScanResult` envelope (today: built-in `json`)
|
|
14929
14820
|
// can emit it verbatim without re-deriving fields like
|
|
@@ -15935,12 +15826,12 @@ function emitExtensionError(emitter, qualifiedId2, nodePath, data) {
|
|
|
15935
15826
|
}
|
|
15936
15827
|
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, emitSignal, emitNode, store) {
|
|
15937
15828
|
const scope = extractor.scope ?? "both";
|
|
15938
|
-
const
|
|
15829
|
+
const settings2 = extractor.resolvedSettings ?? {};
|
|
15939
15830
|
return {
|
|
15940
15831
|
node,
|
|
15941
15832
|
body: scope === "frontmatter" ? "" : body,
|
|
15942
15833
|
frontmatter: scope === "body" ? {} : frontmatter,
|
|
15943
|
-
settings,
|
|
15834
|
+
settings: settings2,
|
|
15944
15835
|
emitLink,
|
|
15945
15836
|
enrichNode,
|
|
15946
15837
|
emitContribution,
|
|
@@ -16163,8 +16054,83 @@ function isExternalUrlLink(link) {
|
|
|
16163
16054
|
return EXTERNAL_URL_SCHEME_RE.test(link.target);
|
|
16164
16055
|
}
|
|
16165
16056
|
|
|
16057
|
+
// kernel/orchestrator/action-projections.ts
|
|
16058
|
+
function runActionProjections(actions, nodes, links, emitter) {
|
|
16059
|
+
const contributions = [];
|
|
16060
|
+
const contributionErrors = [];
|
|
16061
|
+
const validators = loadSchemaValidators();
|
|
16062
|
+
for (const action of actions) {
|
|
16063
|
+
if (typeof action.project !== "function") continue;
|
|
16064
|
+
const qualifiedId2 = qualifiedExtensionId(action.pluginId, action.id);
|
|
16065
|
+
const declaredContributions = readDeclaredContributionRefs(action);
|
|
16066
|
+
const emitContribution = (nodePath, ref, payload) => {
|
|
16067
|
+
const declared = typeof ref === "object" && ref !== null ? declaredContributions.get(ref) : void 0;
|
|
16068
|
+
if (!declared) {
|
|
16069
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUndeclaredRef, {
|
|
16070
|
+
extractorId: qualifiedId2,
|
|
16071
|
+
nodePath
|
|
16072
|
+
});
|
|
16073
|
+
emitExtensionError(emitter, qualifiedId2, nodePath, {
|
|
16074
|
+
phase: "emitContribution",
|
|
16075
|
+
reason: "undeclared-contribution-ref",
|
|
16076
|
+
message
|
|
16077
|
+
});
|
|
16078
|
+
contributionErrors.push({
|
|
16079
|
+
pluginId: action.pluginId,
|
|
16080
|
+
extensionId: action.id,
|
|
16081
|
+
nodePath,
|
|
16082
|
+
reason: "undeclared-contribution-ref",
|
|
16083
|
+
message,
|
|
16084
|
+
emittedAt: Date.now()
|
|
16085
|
+
});
|
|
16086
|
+
return;
|
|
16087
|
+
}
|
|
16088
|
+
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
16089
|
+
if (!result.ok) {
|
|
16090
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
16091
|
+
extractorId: qualifiedId2,
|
|
16092
|
+
contributionId: declared.id,
|
|
16093
|
+
nodePath,
|
|
16094
|
+
slot: declared.slot,
|
|
16095
|
+
errors: result.errors
|
|
16096
|
+
});
|
|
16097
|
+
emitExtensionError(emitter, qualifiedId2, nodePath, {
|
|
16098
|
+
phase: "emitContribution",
|
|
16099
|
+
contributionId: declared.id,
|
|
16100
|
+
slot: declared.slot,
|
|
16101
|
+
reason: result.errors,
|
|
16102
|
+
message
|
|
16103
|
+
});
|
|
16104
|
+
contributionErrors.push({
|
|
16105
|
+
pluginId: action.pluginId,
|
|
16106
|
+
extensionId: action.id,
|
|
16107
|
+
nodePath,
|
|
16108
|
+
reason: result.errors,
|
|
16109
|
+
message,
|
|
16110
|
+
contributionId: declared.id,
|
|
16111
|
+
slot: declared.slot,
|
|
16112
|
+
emittedAt: Date.now()
|
|
16113
|
+
});
|
|
16114
|
+
return;
|
|
16115
|
+
}
|
|
16116
|
+
contributions.push({
|
|
16117
|
+
pluginId: action.pluginId,
|
|
16118
|
+
extensionId: action.id,
|
|
16119
|
+
nodePath,
|
|
16120
|
+
contributionId: declared.id,
|
|
16121
|
+
slot: declared.slot,
|
|
16122
|
+
payload,
|
|
16123
|
+
emittedAt: Date.now()
|
|
16124
|
+
});
|
|
16125
|
+
};
|
|
16126
|
+
const ctx = { nodes, links, emitContribution };
|
|
16127
|
+
action.project(ctx);
|
|
16128
|
+
}
|
|
16129
|
+
return { contributions, contributionErrors };
|
|
16130
|
+
}
|
|
16131
|
+
|
|
16166
16132
|
// kernel/orchestrator/analyzers.ts
|
|
16167
|
-
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals, seedIssues = []) {
|
|
16133
|
+
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, brokenLinks, signals, seedIssues = []) {
|
|
16168
16134
|
const issues = [...seedIssues];
|
|
16169
16135
|
const contributions = [];
|
|
16170
16136
|
const contributionErrors = [];
|
|
@@ -16241,6 +16207,10 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
16241
16207
|
const emitted = await analyzer.evaluate({
|
|
16242
16208
|
nodes,
|
|
16243
16209
|
links: internalLinks,
|
|
16210
|
+
// `settings` is always populated (possibly empty) so analyzers can
|
|
16211
|
+
// read `ctx.settings.<id>` without a presence check. The composer
|
|
16212
|
+
// populated `resolvedSettings` on each composed analyzer.
|
|
16213
|
+
settings: analyzer.resolvedSettings ?? {},
|
|
16244
16214
|
orphanSidecars: analyzerOrphans,
|
|
16245
16215
|
sidecarRoots,
|
|
16246
16216
|
annotationContributions,
|
|
@@ -16254,6 +16224,7 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
16254
16224
|
...referenceablePaths ? { referenceablePaths } : {},
|
|
16255
16225
|
...cwd ? { cwd } : {},
|
|
16256
16226
|
...reservedNodePaths ? { reservedNodePaths } : {},
|
|
16227
|
+
...brokenLinks ? { brokenLinks } : {},
|
|
16257
16228
|
...signals && signals.length > 0 ? { signals } : {},
|
|
16258
16229
|
emitContribution
|
|
16259
16230
|
});
|
|
@@ -16952,6 +16923,7 @@ function buildNode(args2) {
|
|
|
16952
16923
|
externalRefsCount: 0,
|
|
16953
16924
|
frontmatter: args2.frontmatter
|
|
16954
16925
|
};
|
|
16926
|
+
if (args2.modifiedAtMs !== void 0) node.modifiedAtMs = args2.modifiedAtMs;
|
|
16955
16927
|
if (args2.encoder) {
|
|
16956
16928
|
node.tokens = countTokens(args2.encoder, args2.frontmatterRaw, args2.body);
|
|
16957
16929
|
}
|
|
@@ -17067,7 +17039,10 @@ function buildFreshNodeAndValidateFrontmatter(opts) {
|
|
|
17067
17039
|
frontmatter: opts.raw.frontmatter,
|
|
17068
17040
|
bodyHash: opts.bodyHash,
|
|
17069
17041
|
frontmatterHash: opts.frontmatterHash,
|
|
17070
|
-
encoder: opts.encoder
|
|
17042
|
+
encoder: opts.encoder,
|
|
17043
|
+
// Thread the walker's mtime through; `buildNode` only attaches it
|
|
17044
|
+
// when present, so virtual / walk()-without-stat sources stay absent.
|
|
17045
|
+
modifiedAtMs: opts.raw.modifiedAtMs
|
|
17071
17046
|
});
|
|
17072
17047
|
const frontmatterIssues = [];
|
|
17073
17048
|
if (opts.raw.parseIssues && opts.raw.parseIssues.length > 0) {
|
|
@@ -17472,6 +17447,7 @@ async function runScanInternal(_kernel, options) {
|
|
|
17472
17447
|
walked.signals = resolved.resolvedSignals;
|
|
17473
17448
|
const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes, activeProviderId);
|
|
17474
17449
|
walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
|
|
17450
|
+
const brokenLinks = collectBrokenLinks(walked.internalLinks, walked.nodes, postWalkCtx);
|
|
17475
17451
|
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
17476
17452
|
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
17477
17453
|
await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
|
|
@@ -17493,6 +17469,7 @@ async function runScanInternal(_kernel, options) {
|
|
|
17493
17469
|
emitter,
|
|
17494
17470
|
hookDispatcher,
|
|
17495
17471
|
postWalkCtx.reservedNodePaths,
|
|
17472
|
+
brokenLinks,
|
|
17496
17473
|
walked.signals,
|
|
17497
17474
|
// Seed the accumulator with orchestrator-emitted frontmatter
|
|
17498
17475
|
// issues so the aggregate phase (`core/issue-counter`) counts
|
|
@@ -17501,6 +17478,13 @@ async function runScanInternal(_kernel, options) {
|
|
|
17501
17478
|
walked.frontmatterIssues
|
|
17502
17479
|
);
|
|
17503
17480
|
mergeAnalyzerEmissions(walked, analyzerResult, exts.analyzers);
|
|
17481
|
+
const projectionResult = runActionProjections(
|
|
17482
|
+
exts.actions ?? [],
|
|
17483
|
+
walked.nodes,
|
|
17484
|
+
walked.internalLinks,
|
|
17485
|
+
emitter
|
|
17486
|
+
);
|
|
17487
|
+
mergeActionProjections(walked, projectionResult, exts.actions);
|
|
17504
17488
|
const issues = analyzerResult.issues;
|
|
17505
17489
|
const silenced = options.ignoreFilter ? (path) => options.ignoreFilter.ignores(path) : void 0;
|
|
17506
17490
|
const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues, silenced) : [];
|
|
@@ -17609,6 +17593,16 @@ function mergeAnalyzerEmissions(walked, analyzerResult, analyzers) {
|
|
|
17609
17593
|
}
|
|
17610
17594
|
}
|
|
17611
17595
|
}
|
|
17596
|
+
function mergeActionProjections(walked, projectionResult, actions) {
|
|
17597
|
+
for (const c of projectionResult.contributions) walked.contributions.push(c);
|
|
17598
|
+
for (const e of projectionResult.contributionErrors) walked.contributionErrors.push(e);
|
|
17599
|
+
for (const action of actions ?? []) {
|
|
17600
|
+
if (action.ui === void 0 || typeof action.project !== "function") continue;
|
|
17601
|
+
for (const node of walked.nodes) {
|
|
17602
|
+
walked.freshlyRunTuples.add(`${action.pluginId}\0${action.id}\0${node.path}`);
|
|
17603
|
+
}
|
|
17604
|
+
}
|
|
17605
|
+
}
|
|
17612
17606
|
function buildScanStats(walked, issues, start) {
|
|
17613
17607
|
return {
|
|
17614
17608
|
// `filesSkipped` is "files walked but not classified by any
|
|
@@ -17920,6 +17914,159 @@ function findOrphanJobFiles(jobsDir, referencedPaths) {
|
|
|
17920
17914
|
return { orphanFilePaths: orphans, referencedCount: referencedPaths.size };
|
|
17921
17915
|
}
|
|
17922
17916
|
|
|
17917
|
+
// core/config/plugin-settings.ts
|
|
17918
|
+
var defaultWarn = (message) => log.warn(message);
|
|
17919
|
+
function resolveExtensionSettings(manifest, config, onWarn = defaultWarn) {
|
|
17920
|
+
const declarations = manifest.settings;
|
|
17921
|
+
if (!declarations || Object.keys(declarations).length === 0) return {};
|
|
17922
|
+
const overrides = readSettingsOverrides(config, manifest.pluginId, manifest.id);
|
|
17923
|
+
const resolved = {};
|
|
17924
|
+
for (const [settingId, declaration] of Object.entries(declarations)) {
|
|
17925
|
+
const outcome = resolveOneSetting(manifest, settingId, declaration, overrides, onWarn);
|
|
17926
|
+
if (outcome.hasValue) resolved[settingId] = outcome.value;
|
|
17927
|
+
}
|
|
17928
|
+
return resolved;
|
|
17929
|
+
}
|
|
17930
|
+
function resolveOneSetting(manifest, settingId, declaration, overrides, onWarn) {
|
|
17931
|
+
const fallback = declarationDefault(declaration);
|
|
17932
|
+
const toFallback = () => fallback !== void 0 ? { hasValue: true, value: fallback } : { hasValue: false };
|
|
17933
|
+
if (!Object.prototype.hasOwnProperty.call(overrides, settingId)) return toFallback();
|
|
17934
|
+
const candidate = overrides[settingId];
|
|
17935
|
+
const check = validateSettingValue(declaration, candidate);
|
|
17936
|
+
if (check.ok) return { hasValue: true, value: candidate };
|
|
17937
|
+
onWarn(
|
|
17938
|
+
`Setting '${settingId}' for extension '${manifest.pluginId}/${manifest.id}' is invalid (${check.reason}); falling back to the declared default.`
|
|
17939
|
+
);
|
|
17940
|
+
return toFallback();
|
|
17941
|
+
}
|
|
17942
|
+
function buildSettingsResolver(config, onWarn = defaultWarn) {
|
|
17943
|
+
return (ext) => resolveExtensionSettings(ext, config, onWarn);
|
|
17944
|
+
}
|
|
17945
|
+
function declarationDefault(declaration) {
|
|
17946
|
+
return "default" in declaration ? declaration.default : void 0;
|
|
17947
|
+
}
|
|
17948
|
+
function readSettingsOverrides(config, pluginId, extId) {
|
|
17949
|
+
const entry = config.plugins?.[pluginId];
|
|
17950
|
+
const ext = entry?.extensions?.[extId];
|
|
17951
|
+
const settings2 = ext?.settings;
|
|
17952
|
+
if (settings2 && typeof settings2 === "object" && !Array.isArray(settings2)) {
|
|
17953
|
+
return settings2;
|
|
17954
|
+
}
|
|
17955
|
+
return {};
|
|
17956
|
+
}
|
|
17957
|
+
var OK = { ok: true, reason: "" };
|
|
17958
|
+
function fail2(reason) {
|
|
17959
|
+
return { ok: false, reason };
|
|
17960
|
+
}
|
|
17961
|
+
function validateSettingValue(declaration, value) {
|
|
17962
|
+
switch (declaration.type) {
|
|
17963
|
+
case "string-list":
|
|
17964
|
+
return validateStringList(value, declaration.min, declaration.max, declaration.itemMaxLength);
|
|
17965
|
+
case "single-string":
|
|
17966
|
+
return validateSingleString(value, declaration.minLength, declaration.maxLength, declaration.pattern);
|
|
17967
|
+
case "boolean-flag":
|
|
17968
|
+
return typeof value === "boolean" ? OK : fail2("expected a boolean");
|
|
17969
|
+
case "integer":
|
|
17970
|
+
return validateInteger(value, declaration.min, declaration.max);
|
|
17971
|
+
case "number":
|
|
17972
|
+
return validateNumber(value, declaration.min, declaration.max);
|
|
17973
|
+
case "enum-pick":
|
|
17974
|
+
return validateEnumPick(value, declaration.options.map((o) => o.value));
|
|
17975
|
+
case "enum-multipick":
|
|
17976
|
+
return validateEnumMultipick(value, declaration.options.map((o) => o.value), declaration.min, declaration.max);
|
|
17977
|
+
case "path-glob":
|
|
17978
|
+
return validatePathGlob(value, declaration.multiple === true);
|
|
17979
|
+
case "regex":
|
|
17980
|
+
return validateRegex(value, declaration.flags);
|
|
17981
|
+
case "secret":
|
|
17982
|
+
return typeof value === "string" ? OK : fail2("expected a string");
|
|
17983
|
+
case "key-value-list":
|
|
17984
|
+
return validateKeyValueList(value, declaration.min, declaration.max);
|
|
17985
|
+
default: {
|
|
17986
|
+
const _exhaustive = declaration;
|
|
17987
|
+
return fail2(`unknown input-type: ${String(_exhaustive.type)}`);
|
|
17988
|
+
}
|
|
17989
|
+
}
|
|
17990
|
+
}
|
|
17991
|
+
function isStringArray(value) {
|
|
17992
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
17993
|
+
}
|
|
17994
|
+
function validateStringList(value, min, max, itemMaxLength) {
|
|
17995
|
+
if (!isStringArray(value)) return fail2("expected an array of strings");
|
|
17996
|
+
if (min !== void 0 && value.length < min) return fail2(`expected at least ${min} item(s)`);
|
|
17997
|
+
if (max !== void 0 && value.length > max) return fail2(`expected at most ${max} item(s)`);
|
|
17998
|
+
const cap = itemMaxLength ?? 256;
|
|
17999
|
+
if (value.some((item) => item.length > cap)) return fail2(`item exceeds ${cap} characters`);
|
|
18000
|
+
return OK;
|
|
18001
|
+
}
|
|
18002
|
+
function validateSingleString(value, minLength, maxLength, pattern) {
|
|
18003
|
+
if (typeof value !== "string") return fail2("expected a string");
|
|
18004
|
+
if (minLength !== void 0 && value.length < minLength) return fail2(`expected at least ${minLength} characters`);
|
|
18005
|
+
if (maxLength !== void 0 && value.length > maxLength) return fail2(`expected at most ${maxLength} characters`);
|
|
18006
|
+
return matchesPattern(value, pattern);
|
|
18007
|
+
}
|
|
18008
|
+
function matchesPattern(value, pattern) {
|
|
18009
|
+
if (pattern === void 0) return OK;
|
|
18010
|
+
let re;
|
|
18011
|
+
try {
|
|
18012
|
+
re = new RegExp(pattern);
|
|
18013
|
+
} catch {
|
|
18014
|
+
return OK;
|
|
18015
|
+
}
|
|
18016
|
+
return re.test(value) ? OK : fail2(`does not match pattern ${pattern}`);
|
|
18017
|
+
}
|
|
18018
|
+
function validateInteger(value, min, max) {
|
|
18019
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return fail2("expected an integer");
|
|
18020
|
+
if (!Number.isSafeInteger(value)) return fail2("integer out of safe range");
|
|
18021
|
+
if (min !== void 0 && value < min) return fail2(`expected >= ${min}`);
|
|
18022
|
+
if (max !== void 0 && value > max) return fail2(`expected <= ${max}`);
|
|
18023
|
+
return OK;
|
|
18024
|
+
}
|
|
18025
|
+
function validateNumber(value, min, max) {
|
|
18026
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fail2("expected a finite number");
|
|
18027
|
+
if (min !== void 0 && value < min) return fail2(`expected >= ${min}`);
|
|
18028
|
+
if (max !== void 0 && value > max) return fail2(`expected <= ${max}`);
|
|
18029
|
+
return OK;
|
|
18030
|
+
}
|
|
18031
|
+
function validateEnumPick(value, allowed) {
|
|
18032
|
+
if (typeof value !== "string") return fail2("expected a string");
|
|
18033
|
+
if (!allowed.includes(value)) return fail2(`expected one of: ${allowed.join(", ")}`);
|
|
18034
|
+
return OK;
|
|
18035
|
+
}
|
|
18036
|
+
function validateEnumMultipick(value, allowed, min, max) {
|
|
18037
|
+
if (!isStringArray(value)) return fail2("expected an array of strings");
|
|
18038
|
+
const allowedSet = new Set(allowed);
|
|
18039
|
+
if (value.some((v) => !allowedSet.has(v))) return fail2(`every entry must be one of: ${allowed.join(", ")}`);
|
|
18040
|
+
if (min !== void 0 && value.length < min) return fail2(`expected at least ${min} selection(s)`);
|
|
18041
|
+
if (max !== void 0 && value.length > max) return fail2(`expected at most ${max} selection(s)`);
|
|
18042
|
+
return OK;
|
|
18043
|
+
}
|
|
18044
|
+
function validatePathGlob(value, multiple) {
|
|
18045
|
+
if (multiple) {
|
|
18046
|
+
return isStringArray(value) ? OK : fail2("expected an array of glob strings");
|
|
18047
|
+
}
|
|
18048
|
+
return typeof value === "string" ? OK : fail2("expected a glob string");
|
|
18049
|
+
}
|
|
18050
|
+
function validateRegex(value, flags) {
|
|
18051
|
+
if (typeof value !== "string") return fail2("expected a string");
|
|
18052
|
+
try {
|
|
18053
|
+
new RegExp(value, flags ?? "");
|
|
18054
|
+
} catch (err) {
|
|
18055
|
+
return fail2(`is not a compilable regex (${err instanceof Error ? err.message : String(err)})`);
|
|
18056
|
+
}
|
|
18057
|
+
return OK;
|
|
18058
|
+
}
|
|
18059
|
+
function validateKeyValueList(value, min, max) {
|
|
18060
|
+
if (!Array.isArray(value)) return fail2("expected an array of { key, value } entries");
|
|
18061
|
+
const wellShaped = value.every(
|
|
18062
|
+
(entry) => entry !== null && typeof entry === "object" && typeof entry.key === "string" && typeof entry.value === "string"
|
|
18063
|
+
);
|
|
18064
|
+
if (!wellShaped) return fail2("every entry must be { key: string, value: string }");
|
|
18065
|
+
if (min !== void 0 && value.length < min) return fail2(`expected at least ${min} entr(y/ies)`);
|
|
18066
|
+
if (max !== void 0 && value.length > max) return fail2(`expected at most ${max} entr(y/ies)`);
|
|
18067
|
+
return OK;
|
|
18068
|
+
}
|
|
18069
|
+
|
|
17923
18070
|
// core/runtime/i18n/progress-emitter.texts.ts
|
|
17924
18071
|
var PROGRESS_EMITTER_TEXTS = {
|
|
17925
18072
|
/**
|
|
@@ -18420,10 +18567,10 @@ async function runScanForCommand(opts) {
|
|
|
18420
18567
|
const dbPath = resolveDbPath({ db: void 0, ...ctx });
|
|
18421
18568
|
const kernel = createKernel();
|
|
18422
18569
|
const pluginRuntime = await preparePluginRuntime(opts, opts.printer);
|
|
18423
|
-
const extensions = registerExtensions(kernel, pluginRuntime, opts);
|
|
18424
18570
|
const scanInputs = loadScanInputs(opts, ctx);
|
|
18425
18571
|
if ("kind" in scanInputs) return scanInputs;
|
|
18426
18572
|
const { cfg, ignoreFilter, strict, effectiveRoots } = scanInputs;
|
|
18573
|
+
const extensions = registerExtensions(kernel, pluginRuntime, opts, cfg);
|
|
18427
18574
|
let referenceablePaths;
|
|
18428
18575
|
if (cfg.scan.referencePaths.length > 0) {
|
|
18429
18576
|
const walk3 = walkReferencePaths(cfg.scan.referencePaths, ctx.cwd);
|
|
@@ -18519,10 +18666,11 @@ async function preparePluginRuntime(opts, printer) {
|
|
|
18519
18666
|
pluginRuntime.emitWarnings(printer);
|
|
18520
18667
|
return pluginRuntime;
|
|
18521
18668
|
}
|
|
18522
|
-
function registerExtensions(kernel, pluginRuntime, opts) {
|
|
18669
|
+
function registerExtensions(kernel, pluginRuntime, opts, cfg) {
|
|
18523
18670
|
const composeOpts = {
|
|
18524
18671
|
noBuiltIns: opts.noBuiltIns,
|
|
18525
|
-
pluginRuntime
|
|
18672
|
+
pluginRuntime,
|
|
18673
|
+
resolveSettings: buildSettingsResolver(cfg)
|
|
18526
18674
|
};
|
|
18527
18675
|
if (opts.killSwitches) composeOpts.killSwitches = opts.killSwitches;
|
|
18528
18676
|
if (opts.resolveEnabledOverride) composeOpts.resolveEnabled = opts.resolveEnabledOverride;
|
|
@@ -20066,7 +20214,7 @@ async function findActiveOrphanIssues(adapter, predicate) {
|
|
|
20066
20214
|
(issue) => ORPHAN_RULE_IDS.includes(issue.analyzerId) && predicate(issue)
|
|
20067
20215
|
);
|
|
20068
20216
|
}
|
|
20069
|
-
function
|
|
20217
|
+
function isStringArray2(v) {
|
|
20070
20218
|
return Array.isArray(v) && v.every((s) => typeof s === "string");
|
|
20071
20219
|
}
|
|
20072
20220
|
var OrphansCommand = class extends SmCommand {
|
|
@@ -20437,7 +20585,7 @@ var OrphansUndoRenameCommand = class extends SmCommand {
|
|
|
20437
20585
|
return { ok: false, exitCode: ExitCode.NotFound };
|
|
20438
20586
|
}
|
|
20439
20587
|
const dataCandidates = issue.data ? issue.data["candidates"] : void 0;
|
|
20440
|
-
if (!
|
|
20588
|
+
if (!isStringArray2(dataCandidates) || !dataCandidates.includes(this.from)) {
|
|
20441
20589
|
this.printer.error(
|
|
20442
20590
|
tx(ORPHANS_TEXTS.undoAmbiguousNotInCandidates, { glyph: errGlyph, from: this.from })
|
|
20443
20591
|
);
|
|
@@ -20513,6 +20661,17 @@ var PLUGINS_TEXTS = {
|
|
|
20513
20661
|
qualifiedIdNotFoundHint: "Run `sm plugins list` to see what each plugin ships.",
|
|
20514
20662
|
qualifiedIdUnknownPlugin: "{{glyph}} Qualified extension id references unknown plugin: {{pluginId}}\n {{hint}}\n",
|
|
20515
20663
|
qualifiedIdUnknownPluginHint: "Run `sm plugins list` for known plugin ids.",
|
|
20664
|
+
// --- verb-shape redirects (show is extension-only; list is plugin-only) ---
|
|
20665
|
+
// `sm plugins show` takes a qualified `<plugin>/<ext>` id and renders a
|
|
20666
|
+
// single extension. A bare plugin id is the wrong granularity, redirect
|
|
20667
|
+
// to `sm plugins list <id>`, which renders the whole plugin.
|
|
20668
|
+
showBareId: '{{glyph}} `sm plugins show` needs a qualified `<plugin>/<ext>` id; "{{id}}" is a plugin.\n {{hint}}\n',
|
|
20669
|
+
showBareIdHint: "Run `sm plugins list {{id}}` for the plugin and its extensions, then `sm plugins show {{id}}/<ext>` for one.",
|
|
20670
|
+
// `sm plugins list <id>` takes a bare plugin id. A qualified
|
|
20671
|
+
// `<plugin>/<ext>` id targets a single extension, redirect to
|
|
20672
|
+
// `sm plugins show`.
|
|
20673
|
+
listQualifiedId: "{{glyph}} `sm plugins list` takes a plugin id, not a qualified `<plugin>/<ext>` id: {{id}}\n {{hint}}\n",
|
|
20674
|
+
listQualifiedIdHint: "Run `sm plugins show {{id}}` for that extension, or `sm plugins list {{pluginId}}` for the whole plugin.",
|
|
20516
20675
|
// Spec § A.10, `applicableKinds` filter on Extractors. When an extractor
|
|
20517
20676
|
// declares a kind that no installed Provider emits, the load succeeds
|
|
20518
20677
|
// (the Provider may arrive later) but `sm plugins doctor` surfaces a
|
|
@@ -20653,7 +20812,7 @@ var PLUGINS_TEXTS = {
|
|
|
20653
20812
|
* (declared or defaulted) renders no tag.
|
|
20654
20813
|
*/
|
|
20655
20814
|
stabilityTag: " ({{stability}})",
|
|
20656
|
-
listTipShow: "\nTip: `sm plugins
|
|
20815
|
+
listTipShow: "\nTip: `sm plugins list <id>` for a plugin's extensions (kinds, versions, per-extension status), `sm plugins show <plugin>/<ext>` for one extension.\n",
|
|
20657
20816
|
/** Show command, built-in header (no version row, no path). */
|
|
20658
20817
|
detailHeaderBuiltIn: " {{glyph}} {{id}} {{source}} {{count}} extension{{plural}}\n",
|
|
20659
20818
|
/**
|
|
@@ -20805,7 +20964,7 @@ function extensionRowFromBuiltIn(ext, plugin, resolveEnabled) {
|
|
|
20805
20964
|
id: ext.id,
|
|
20806
20965
|
kind: ext.kind,
|
|
20807
20966
|
version: ext.version,
|
|
20808
|
-
enabled: resolveEnabled(qualifiedExtensionId(plugin.id, ext.id)),
|
|
20967
|
+
enabled: resolveEnabled(qualifiedExtensionId(plugin.id, ext.id), installedDefaultEnabled(ext.stability)),
|
|
20809
20968
|
description: ext.description ?? ""
|
|
20810
20969
|
};
|
|
20811
20970
|
if (ext.stability !== void 0) row.stability = ext.stability;
|
|
@@ -20822,6 +20981,43 @@ function omitModule(key, value) {
|
|
|
20822
20981
|
const tag = value[Symbol.toStringTag];
|
|
20823
20982
|
return tag === "Module" ? void 0 : value;
|
|
20824
20983
|
}
|
|
20984
|
+
function pluginCatalogue(plugins) {
|
|
20985
|
+
const out = [];
|
|
20986
|
+
for (const plugin of builtInPlugins) {
|
|
20987
|
+
out.push({ id: plugin.id, extensionIds: plugin.extensions.map((e) => e.id) });
|
|
20988
|
+
}
|
|
20989
|
+
for (const p of plugins) {
|
|
20990
|
+
out.push({ id: p.id, extensionIds: p.extensions?.map((e) => e.id) ?? [] });
|
|
20991
|
+
}
|
|
20992
|
+
return out;
|
|
20993
|
+
}
|
|
20994
|
+
function parseQualifiedExtensionId(id, catalogue) {
|
|
20995
|
+
const [pluginId, extId, ...rest] = id.split("/");
|
|
20996
|
+
if (!pluginId || !extId || rest.length > 0) return { ok: false, reason: "malformed" };
|
|
20997
|
+
const plugin = catalogue.find((p) => p.id === pluginId);
|
|
20998
|
+
if (!plugin) return { ok: false, reason: "unknown-plugin", pluginId };
|
|
20999
|
+
if (!plugin.extensionIds.includes(extId)) {
|
|
21000
|
+
return { ok: false, reason: "unknown-extension", pluginId, extId };
|
|
21001
|
+
}
|
|
21002
|
+
return { ok: true, pluginId, extId };
|
|
21003
|
+
}
|
|
21004
|
+
function renderQualifiedIdError(result, rawId, ansi) {
|
|
21005
|
+
const glyph = ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
21006
|
+
if (result.reason === "unknown-extension") {
|
|
21007
|
+
return tx(PLUGINS_TEXTS.qualifiedIdNotFound, {
|
|
21008
|
+
glyph,
|
|
21009
|
+
id: sanitizeForTerminal(rawId),
|
|
21010
|
+
pluginId: sanitizeForTerminal(result.pluginId ?? ""),
|
|
21011
|
+
extId: sanitizeForTerminal(result.extId ?? ""),
|
|
21012
|
+
hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdNotFoundHint)
|
|
21013
|
+
});
|
|
21014
|
+
}
|
|
21015
|
+
return tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
|
|
21016
|
+
glyph,
|
|
21017
|
+
pluginId: sanitizeForTerminal(result.reason === "unknown-plugin" ? result.pluginId ?? rawId : rawId),
|
|
21018
|
+
hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
|
|
21019
|
+
});
|
|
21020
|
+
}
|
|
20825
21021
|
function wrapText(text, maxWidth) {
|
|
20826
21022
|
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
20827
21023
|
if (words.length === 0) return [];
|
|
@@ -20845,14 +21041,24 @@ var PluginsListCommand = class extends SmCommand {
|
|
|
20845
21041
|
static paths = [["plugins", "list"]];
|
|
20846
21042
|
static usage = Command22.Usage({
|
|
20847
21043
|
category: "Plugins",
|
|
20848
|
-
description: "List discovered plugins
|
|
20849
|
-
details:
|
|
21044
|
+
description: "List discovered plugins, or one plugin's extensions.",
|
|
21045
|
+
details: `
|
|
21046
|
+
No id: scans <cwd>/.skill-map/plugins (or --plugin-dir <path>) and
|
|
21047
|
+
lists every plugin (built-in + user) with status, one row each.
|
|
21048
|
+
With a bare plugin id: renders that plugin's manifest and its
|
|
21049
|
+
extensions (kind / version / per-extension status). A qualified
|
|
21050
|
+
\`<plugin>/<ext>\` id is rejected with a redirect to \`sm plugins show\`.
|
|
21051
|
+
`
|
|
20850
21052
|
});
|
|
21053
|
+
id = Option21.String({ required: false });
|
|
20851
21054
|
pluginDir = Option21.String("--plugin-dir", { required: false });
|
|
20852
21055
|
async run() {
|
|
20853
21056
|
const plugins = await loadAll({ pluginDir: this.pluginDir });
|
|
20854
21057
|
const resolveEnabled = await buildResolver();
|
|
20855
21058
|
const builtIns2 = builtInRows(resolveEnabled);
|
|
21059
|
+
if (this.id !== void 0) {
|
|
21060
|
+
return this.renderPluginDetailById(this.id, builtIns2, plugins);
|
|
21061
|
+
}
|
|
20856
21062
|
if (this.json) {
|
|
20857
21063
|
this.printer.data(
|
|
20858
21064
|
JSON.stringify({ builtIns: builtIns2, plugins }, omitModule, 2) + "\n"
|
|
@@ -20864,134 +21070,45 @@ var PluginsListCommand = class extends SmCommand {
|
|
|
20864
21070
|
return ExitCode.Ok;
|
|
20865
21071
|
}
|
|
20866
21072
|
const ansi = this.ansiFor("stdout");
|
|
20867
|
-
this.printer.data(
|
|
21073
|
+
this.printer.data(renderIndexHuman(builtIns2, plugins, resolveEnabled, ansi));
|
|
20868
21074
|
return ExitCode.Ok;
|
|
20869
21075
|
}
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20876
|
-
|
|
20877
|
-
const countWidth = Math.max(
|
|
20878
|
-
...rows.map((r) => String(r.names.length).length)
|
|
20879
|
-
);
|
|
20880
|
-
const lines = [];
|
|
20881
|
-
for (const row of rows) {
|
|
20882
|
-
const glyph = row.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
20883
|
-
const idCol = row.id.padEnd(idWidth);
|
|
20884
|
-
const countCol = String(row.names.length).padStart(countWidth);
|
|
20885
|
-
lines.push(
|
|
20886
|
-
tx(PLUGINS_TEXTS.pluginRow, {
|
|
20887
|
-
glyph,
|
|
20888
|
-
id: idCol,
|
|
20889
|
-
count: ` ${countCol}`,
|
|
20890
|
-
source: ansi.dim(row.source)
|
|
20891
|
-
})
|
|
20892
|
-
);
|
|
20893
|
-
const indent = PLUGINS_TEXTS.pluginSubIndent;
|
|
20894
|
-
if (row.reason) {
|
|
20895
|
-
lines.push(`${indent}${ansi.dim(row.reason)}`);
|
|
20896
|
-
} else if (row.names.length > 0) {
|
|
20897
|
-
for (const wrapped of wrapNames(row.names, indent, 76)) {
|
|
20898
|
-
lines.push(`${indent}${ansi.dim(wrapped)}`);
|
|
20899
|
-
}
|
|
20900
|
-
}
|
|
20901
|
-
}
|
|
20902
|
-
return lines.join("\n") + "\n" + PLUGINS_TEXTS.listTipShow;
|
|
20903
|
-
}
|
|
20904
|
-
function builtInToListRow(b) {
|
|
20905
|
-
const names = b.extensions.map((e) => {
|
|
20906
|
-
const name = withStabilityTag(e.id, e.stability);
|
|
20907
|
-
return e.enabled ? name : `${PLUGINS_TEXTS.rowGlyphOff} ${name}`;
|
|
20908
|
-
});
|
|
20909
|
-
return {
|
|
20910
|
-
id: b.id,
|
|
20911
|
-
enabled: b.enabled,
|
|
20912
|
-
source: PLUGINS_TEXTS.sourceBuiltIn,
|
|
20913
|
-
names
|
|
20914
|
-
};
|
|
20915
|
-
}
|
|
20916
|
-
function pluginToListRow(p, resolveEnabled) {
|
|
20917
|
-
const isLoaded = p.status === "enabled";
|
|
20918
|
-
const extensions = p.extensions ?? [];
|
|
20919
|
-
const enabled = isLoaded ? extensions.length === 0 || extensions.some((e) => resolveEnabled(qualifiedExtensionId(p.id, e.id))) : false;
|
|
20920
|
-
const names = extensions.map((e) => {
|
|
20921
|
-
const safeId = withStabilityTag(sanitizeForTerminal(e.id), e.stability);
|
|
20922
|
-
return resolveEnabled(qualifiedExtensionId(p.id, e.id)) ? safeId : `${PLUGINS_TEXTS.rowGlyphOff} ${safeId}`;
|
|
20923
|
-
});
|
|
20924
|
-
const reason = p.status === "enabled" ? void 0 : sanitizeForTerminal(p.reason ?? "") || void 0;
|
|
20925
|
-
return {
|
|
20926
|
-
id: sanitizeForTerminal(p.id),
|
|
20927
|
-
enabled,
|
|
20928
|
-
source: PLUGINS_TEXTS.sourceUser,
|
|
20929
|
-
names,
|
|
20930
|
-
reason
|
|
20931
|
-
};
|
|
20932
|
-
}
|
|
20933
|
-
function wrapNames(names, indent, maxWidth) {
|
|
20934
|
-
const out = [];
|
|
20935
|
-
const sep8 = ", ";
|
|
20936
|
-
let current = "";
|
|
20937
|
-
for (const name of names) {
|
|
20938
|
-
const candidate = current === "" ? name : `${current}${sep8}${name}`;
|
|
20939
|
-
if (indent.length + candidate.length > maxWidth && current !== "") {
|
|
20940
|
-
out.push(`${current},`);
|
|
20941
|
-
current = name;
|
|
20942
|
-
} else {
|
|
20943
|
-
current = candidate;
|
|
20944
|
-
}
|
|
20945
|
-
}
|
|
20946
|
-
if (current !== "") out.push(current);
|
|
20947
|
-
return out;
|
|
20948
|
-
}
|
|
20949
|
-
|
|
20950
|
-
// cli/commands/plugins/show.ts
|
|
20951
|
-
import { Command as Command23, Option as Option22 } from "clipanion";
|
|
20952
|
-
var PluginsShowCommand = class extends SmCommand {
|
|
20953
|
-
static paths = [["plugins", "show"]];
|
|
20954
|
-
static usage = Command23.Usage({
|
|
20955
|
-
category: "Plugins",
|
|
20956
|
-
description: "Show a single plugin's manifest + loaded extensions.",
|
|
20957
|
-
details: `
|
|
20958
|
-
Accepts a plugin id (\`core\`, \`claude\`, \`my-plugin\`)
|
|
20959
|
-
or a qualified extension id (\`core/<ext-id>\`,
|
|
20960
|
-
\`<plugin>/<ext-id>\`). When given a qualified id, validates the
|
|
20961
|
-
extension exists and renders a single-extension detail block.
|
|
20962
|
-
The bare form renders the parent plugin's detail with per-extension
|
|
20963
|
-
status. The same id shapes \`sm plugins enable\` and
|
|
20964
|
-
\`sm plugins disable\` accept resolve cleanly here too.
|
|
20965
|
-
`
|
|
20966
|
-
});
|
|
20967
|
-
id = Option22.String({ required: true });
|
|
20968
|
-
pluginDir = Option22.String("--plugin-dir", { required: false });
|
|
20969
|
-
async run() {
|
|
20970
|
-
const plugins = await loadAll({ pluginDir: this.pluginDir });
|
|
20971
|
-
const resolveEnabled = await buildResolver();
|
|
20972
|
-
const builtIns2 = builtInRows(resolveEnabled);
|
|
21076
|
+
/**
|
|
21077
|
+
* `sm plugins list <id>`, render one plugin's full detail. A qualified
|
|
21078
|
+
* `<plugin>/<ext>` id is the wrong granularity for `list` (it targets a
|
|
21079
|
+
* single extension), redirect to `sm plugins show`. A bare id that
|
|
21080
|
+
* matches no plugin is a NotFound.
|
|
21081
|
+
*/
|
|
21082
|
+
renderPluginDetailById(id, builtIns2, plugins) {
|
|
20973
21083
|
const stderrAnsi = this.ansiFor("stderr");
|
|
20974
|
-
|
|
20975
|
-
|
|
20976
|
-
this.printer.error(
|
|
20977
|
-
|
|
21084
|
+
if (id.includes("/")) {
|
|
21085
|
+
const pluginId = id.split("/")[0] ?? id;
|
|
21086
|
+
this.printer.error(
|
|
21087
|
+
tx(PLUGINS_TEXTS.listQualifiedId, {
|
|
21088
|
+
glyph: stderrAnsi.red(PLUGINS_TEXTS.rowGlyphOff),
|
|
21089
|
+
id: sanitizeForTerminal(id),
|
|
21090
|
+
hint: stderrAnsi.dim(
|
|
21091
|
+
tx(PLUGINS_TEXTS.listQualifiedIdHint, {
|
|
21092
|
+
id: sanitizeForTerminal(id),
|
|
21093
|
+
pluginId: sanitizeForTerminal(pluginId)
|
|
21094
|
+
})
|
|
21095
|
+
)
|
|
21096
|
+
})
|
|
21097
|
+
);
|
|
21098
|
+
return ExitCode.Error;
|
|
20978
21099
|
}
|
|
20979
|
-
const
|
|
20980
|
-
const
|
|
20981
|
-
const match = plugins.find((p) => p.id === pluginId);
|
|
21100
|
+
const builtIn = builtIns2.find((b) => b.id === id);
|
|
21101
|
+
const match = plugins.find((p) => p.id === id);
|
|
20982
21102
|
if (!builtIn && !match) {
|
|
20983
21103
|
this.printer.error(
|
|
20984
21104
|
tx(PLUGINS_TEXTS.pluginNotFound, {
|
|
20985
|
-
glyph: stderrAnsi.red(
|
|
20986
|
-
id: sanitizeForTerminal(
|
|
21105
|
+
glyph: stderrAnsi.red(PLUGINS_TEXTS.rowGlyphOff),
|
|
21106
|
+
id: sanitizeForTerminal(id),
|
|
20987
21107
|
hint: stderrAnsi.dim(PLUGINS_TEXTS.pluginNotFoundHint)
|
|
20988
21108
|
})
|
|
20989
21109
|
);
|
|
20990
21110
|
return ExitCode.NotFound;
|
|
20991
21111
|
}
|
|
20992
|
-
if (extId !== void 0) {
|
|
20993
|
-
return this.renderExtensionDetail({ extId, pluginId, builtIn, match });
|
|
20994
|
-
}
|
|
20995
21112
|
if (this.json) {
|
|
20996
21113
|
const payload = builtIn ?? match;
|
|
20997
21114
|
this.printer.data(JSON.stringify(payload, omitModule, 2) + "\n");
|
|
@@ -21002,83 +21119,54 @@ var PluginsShowCommand = class extends SmCommand {
|
|
|
21002
21119
|
this.printer.data(text);
|
|
21003
21120
|
return ExitCode.Ok;
|
|
21004
21121
|
}
|
|
21005
|
-
|
|
21006
|
-
|
|
21007
|
-
|
|
21008
|
-
|
|
21009
|
-
|
|
21010
|
-
|
|
21011
|
-
|
|
21012
|
-
|
|
21013
|
-
|
|
21014
|
-
|
|
21015
|
-
const
|
|
21016
|
-
|
|
21017
|
-
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
|
|
21021
|
-
|
|
21022
|
-
|
|
21023
|
-
|
|
21024
|
-
|
|
21025
|
-
|
|
21026
|
-
|
|
21027
|
-
|
|
21028
|
-
if (this.json) {
|
|
21029
|
-
this.printer.data(JSON.stringify(userExt, omitModule, 2) + "\n");
|
|
21030
|
-
return ExitCode.Ok;
|
|
21122
|
+
};
|
|
21123
|
+
function renderIndexHuman(builtIns2, plugins, resolveEnabled, ansi) {
|
|
21124
|
+
const rows = [
|
|
21125
|
+
...builtIns2.map(builtInToIndexRow),
|
|
21126
|
+
...plugins.map((p) => pluginToIndexRow(p, resolveEnabled))
|
|
21127
|
+
];
|
|
21128
|
+
const idWidth = Math.max(...rows.map((r) => r.id.length));
|
|
21129
|
+
const countWidth = Math.max(...rows.map((r) => String(r.extCount).length));
|
|
21130
|
+
const lines = [];
|
|
21131
|
+
for (const row of rows) {
|
|
21132
|
+
const glyph = row.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
21133
|
+
const idCol = row.id.padEnd(idWidth);
|
|
21134
|
+
const countCol = String(row.extCount).padStart(countWidth);
|
|
21135
|
+
lines.push(
|
|
21136
|
+
tx(PLUGINS_TEXTS.pluginRow, {
|
|
21137
|
+
glyph,
|
|
21138
|
+
id: idCol,
|
|
21139
|
+
count: ` ${countCol}`,
|
|
21140
|
+
source: ansi.dim(row.source)
|
|
21141
|
+
})
|
|
21142
|
+
);
|
|
21143
|
+
if (row.reason) {
|
|
21144
|
+
lines.push(`${PLUGINS_TEXTS.pluginSubIndent}${ansi.dim(row.reason)}`);
|
|
21031
21145
|
}
|
|
21032
|
-
this.printer.data(renderUserExtensionDetail(pluginId, userExt, ansi));
|
|
21033
|
-
return ExitCode.Ok;
|
|
21034
21146
|
}
|
|
21035
|
-
|
|
21036
|
-
function resolveShowLookupId(id, builtIns2, plugins, ansi) {
|
|
21037
|
-
if (!id.includes("/")) return { pluginId: id };
|
|
21038
|
-
const parsed = parseQualifiedId(id);
|
|
21039
|
-
if ("error" in parsed) return { error: malformedQualifiedError(id, ansi) };
|
|
21040
|
-
const { pluginId, extId } = parsed;
|
|
21041
|
-
const knownExts = collectKnownExtensions(pluginId, builtIns2, plugins);
|
|
21042
|
-
if (knownExts === null) return { error: unknownPluginError(pluginId, ansi) };
|
|
21043
|
-
if (!knownExts.includes(extId)) {
|
|
21044
|
-
return { error: unknownExtensionError(id, pluginId, extId, ansi) };
|
|
21045
|
-
}
|
|
21046
|
-
return { pluginId, extId };
|
|
21047
|
-
}
|
|
21048
|
-
function parseQualifiedId(id) {
|
|
21049
|
-
const [pluginId, extId, ...rest] = id.split("/");
|
|
21050
|
-
if (!pluginId || !extId || rest.length > 0) return { error: true };
|
|
21051
|
-
return { pluginId, extId };
|
|
21052
|
-
}
|
|
21053
|
-
function collectKnownExtensions(pluginId, builtIns2, plugins) {
|
|
21054
|
-
const builtIn = builtIns2.find((b) => b.id === pluginId);
|
|
21055
|
-
if (builtIn) return builtIn.extensions.map((e) => e.id);
|
|
21056
|
-
const userPlugin = plugins.find((p) => p.id === pluginId);
|
|
21057
|
-
if (userPlugin) return userPlugin.extensions?.map((e) => e.id) ?? [];
|
|
21058
|
-
return null;
|
|
21059
|
-
}
|
|
21060
|
-
function malformedQualifiedError(id, ansi) {
|
|
21061
|
-
return tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
|
|
21062
|
-
glyph: ansi.red("\u2715"),
|
|
21063
|
-
pluginId: sanitizeForTerminal(id),
|
|
21064
|
-
hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
|
|
21065
|
-
});
|
|
21147
|
+
return lines.join("\n") + "\n" + PLUGINS_TEXTS.listTipShow;
|
|
21066
21148
|
}
|
|
21067
|
-
function
|
|
21068
|
-
return
|
|
21069
|
-
|
|
21070
|
-
|
|
21071
|
-
|
|
21072
|
-
|
|
21149
|
+
function builtInToIndexRow(b) {
|
|
21150
|
+
return {
|
|
21151
|
+
id: b.id,
|
|
21152
|
+
enabled: b.enabled,
|
|
21153
|
+
source: PLUGINS_TEXTS.sourceBuiltIn,
|
|
21154
|
+
extCount: b.extensions.length
|
|
21155
|
+
};
|
|
21073
21156
|
}
|
|
21074
|
-
function
|
|
21075
|
-
|
|
21076
|
-
|
|
21077
|
-
|
|
21078
|
-
|
|
21079
|
-
|
|
21080
|
-
|
|
21081
|
-
|
|
21157
|
+
function pluginToIndexRow(p, resolveEnabled) {
|
|
21158
|
+
const isLoaded = p.status === "enabled";
|
|
21159
|
+
const extensions = p.extensions ?? [];
|
|
21160
|
+
const extEnabled = (e) => resolveEnabled(qualifiedExtensionId(p.id, e.id), installedDefaultEnabled(e.stability));
|
|
21161
|
+
const enabled = isLoaded ? extensions.length === 0 || extensions.some((e) => extEnabled(e)) : false;
|
|
21162
|
+
const reason = p.status === "enabled" ? void 0 : sanitizeForTerminal(p.reason ?? "") || void 0;
|
|
21163
|
+
return {
|
|
21164
|
+
id: sanitizeForTerminal(p.id),
|
|
21165
|
+
enabled,
|
|
21166
|
+
source: PLUGINS_TEXTS.sourceUser,
|
|
21167
|
+
extCount: extensions.length,
|
|
21168
|
+
reason
|
|
21169
|
+
};
|
|
21082
21170
|
}
|
|
21083
21171
|
function kindIndex(kind) {
|
|
21084
21172
|
const idx = EXTENSION_KINDS.indexOf(kind);
|
|
@@ -21203,6 +21291,83 @@ function renderExtensionItems(items) {
|
|
|
21203
21291
|
}
|
|
21204
21292
|
return out.join("");
|
|
21205
21293
|
}
|
|
21294
|
+
|
|
21295
|
+
// cli/commands/plugins/show.ts
|
|
21296
|
+
import { Command as Command23, Option as Option22 } from "clipanion";
|
|
21297
|
+
var PluginsShowCommand = class extends SmCommand {
|
|
21298
|
+
static paths = [["plugins", "show"]];
|
|
21299
|
+
static usage = Command23.Usage({
|
|
21300
|
+
category: "Plugins",
|
|
21301
|
+
description: "Show a single extension's detail.",
|
|
21302
|
+
details: `
|
|
21303
|
+
Accepts a qualified extension id (\`core/<ext-id>\`,
|
|
21304
|
+
\`<plugin>/<ext-id>\`) and renders a single-extension detail block
|
|
21305
|
+
(Kind / Version / Stability / Description / Preconditions / Entry).
|
|
21306
|
+
A bare plugin id is rejected with a redirect to
|
|
21307
|
+
\`sm plugins list <id>\`, which renders the whole plugin. The same
|
|
21308
|
+
qualified id shape \`sm plugins enable\` and \`sm plugins disable\`
|
|
21309
|
+
accept resolves cleanly here too.
|
|
21310
|
+
`
|
|
21311
|
+
});
|
|
21312
|
+
id = Option22.String({ required: true });
|
|
21313
|
+
pluginDir = Option22.String("--plugin-dir", { required: false });
|
|
21314
|
+
async run() {
|
|
21315
|
+
const plugins = await loadAll({ pluginDir: this.pluginDir });
|
|
21316
|
+
const resolveEnabled = await buildResolver();
|
|
21317
|
+
const builtIns2 = builtInRows(resolveEnabled);
|
|
21318
|
+
const stderrAnsi = this.ansiFor("stderr");
|
|
21319
|
+
if (!this.id.includes("/")) {
|
|
21320
|
+
this.printer.error(
|
|
21321
|
+
tx(PLUGINS_TEXTS.showBareId, {
|
|
21322
|
+
glyph: stderrAnsi.red(PLUGINS_TEXTS.rowGlyphOff),
|
|
21323
|
+
id: sanitizeForTerminal(this.id),
|
|
21324
|
+
hint: stderrAnsi.dim(
|
|
21325
|
+
tx(PLUGINS_TEXTS.showBareIdHint, { id: sanitizeForTerminal(this.id) })
|
|
21326
|
+
)
|
|
21327
|
+
})
|
|
21328
|
+
);
|
|
21329
|
+
return ExitCode.Error;
|
|
21330
|
+
}
|
|
21331
|
+
const parsed = parseQualifiedExtensionId(this.id, pluginCatalogue(plugins));
|
|
21332
|
+
if (!parsed.ok) {
|
|
21333
|
+
this.printer.error(renderQualifiedIdError(parsed, this.id, stderrAnsi));
|
|
21334
|
+
return ExitCode.NotFound;
|
|
21335
|
+
}
|
|
21336
|
+
const { pluginId, extId } = parsed;
|
|
21337
|
+
const builtIn = builtIns2.find((b) => b.id === pluginId);
|
|
21338
|
+
const match = plugins.find((p) => p.id === pluginId);
|
|
21339
|
+
return this.renderExtensionDetail({ extId, pluginId, builtIn, match });
|
|
21340
|
+
}
|
|
21341
|
+
/**
|
|
21342
|
+
* Render the single-extension detail block. `--json` emits the single
|
|
21343
|
+
* extension row (no surrounding plugin envelope) so tooling can pipe
|
|
21344
|
+
* straight into `jq`; human mode renders a focused header plus a
|
|
21345
|
+
* Kind / Version / Stability / Description / Preconditions / Entry
|
|
21346
|
+
* field block.
|
|
21347
|
+
*/
|
|
21348
|
+
renderExtensionDetail(args2) {
|
|
21349
|
+
const { extId, pluginId, builtIn, match } = args2;
|
|
21350
|
+
const ansi = this.ansiFor("stdout");
|
|
21351
|
+
if (builtIn) {
|
|
21352
|
+
const ext = builtIn.extensions.find((e) => e.id === extId);
|
|
21353
|
+
if (!ext) return ExitCode.NotFound;
|
|
21354
|
+
if (this.json) {
|
|
21355
|
+
this.printer.data(JSON.stringify({ pluginId, ...ext }, omitModule, 2) + "\n");
|
|
21356
|
+
return ExitCode.Ok;
|
|
21357
|
+
}
|
|
21358
|
+
this.printer.data(renderBuiltInExtensionDetail(pluginId, ext, ansi));
|
|
21359
|
+
return ExitCode.Ok;
|
|
21360
|
+
}
|
|
21361
|
+
const userExt = match?.extensions?.find((e) => e.id === extId);
|
|
21362
|
+
if (!userExt) return ExitCode.NotFound;
|
|
21363
|
+
if (this.json) {
|
|
21364
|
+
this.printer.data(JSON.stringify(userExt, omitModule, 2) + "\n");
|
|
21365
|
+
return ExitCode.Ok;
|
|
21366
|
+
}
|
|
21367
|
+
this.printer.data(renderUserExtensionDetail(pluginId, userExt, ansi));
|
|
21368
|
+
return ExitCode.Ok;
|
|
21369
|
+
}
|
|
21370
|
+
};
|
|
21206
21371
|
function renderBuiltInExtensionDetail(pluginId, ext, ansi) {
|
|
21207
21372
|
const glyph = ext.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
21208
21373
|
const header = tx(PLUGINS_TEXTS.detailHeaderExtensionBuiltIn, {
|
|
@@ -22039,61 +22204,15 @@ var PluginsDisableCommand = class extends TogglePluginsBase {
|
|
|
22039
22204
|
return this.toggle(false);
|
|
22040
22205
|
}
|
|
22041
22206
|
};
|
|
22042
|
-
function pluginCatalogue(plugins) {
|
|
22043
|
-
const out = [];
|
|
22044
|
-
for (const plugin of builtInPlugins) {
|
|
22045
|
-
out.push({
|
|
22046
|
-
id: plugin.id,
|
|
22047
|
-
extensionIds: plugin.extensions.map((e) => e.id)
|
|
22048
|
-
});
|
|
22049
|
-
}
|
|
22050
|
-
for (const p of plugins) {
|
|
22051
|
-
out.push({
|
|
22052
|
-
id: p.id,
|
|
22053
|
-
extensionIds: p.extensions?.map((e) => e.id) ?? []
|
|
22054
|
-
});
|
|
22055
|
-
}
|
|
22056
|
-
return out;
|
|
22057
|
-
}
|
|
22058
22207
|
function resolveToggleTarget(id, catalogue, ansi) {
|
|
22059
22208
|
return id.includes("/") ? resolveQualifiedToggle(id, catalogue, ansi) : resolveBareToggle(id, catalogue);
|
|
22060
22209
|
}
|
|
22061
22210
|
function resolveQualifiedToggle(id, catalogue, ansi) {
|
|
22062
|
-
const
|
|
22063
|
-
|
|
22064
|
-
if (!pluginId || !extId || rest.length > 0) {
|
|
22065
|
-
return {
|
|
22066
|
-
error: tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
|
|
22067
|
-
glyph: errGlyph,
|
|
22068
|
-
pluginId: sanitizeForTerminal(id),
|
|
22069
|
-
hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
|
|
22070
|
-
})
|
|
22071
|
-
};
|
|
22072
|
-
}
|
|
22073
|
-
const plugin = catalogue.find((b) => b.id === pluginId);
|
|
22074
|
-
if (!plugin) {
|
|
22075
|
-
return {
|
|
22076
|
-
error: tx(PLUGINS_TEXTS.qualifiedIdUnknownPlugin, {
|
|
22077
|
-
glyph: errGlyph,
|
|
22078
|
-
pluginId: sanitizeForTerminal(pluginId),
|
|
22079
|
-
hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdUnknownPluginHint)
|
|
22080
|
-
})
|
|
22081
|
-
};
|
|
22082
|
-
}
|
|
22083
|
-
if (!plugin.extensionIds.includes(extId)) {
|
|
22084
|
-
return {
|
|
22085
|
-
error: tx(PLUGINS_TEXTS.qualifiedIdNotFound, {
|
|
22086
|
-
glyph: errGlyph,
|
|
22087
|
-
id: sanitizeForTerminal(id),
|
|
22088
|
-
pluginId: sanitizeForTerminal(pluginId),
|
|
22089
|
-
extId: sanitizeForTerminal(extId),
|
|
22090
|
-
hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdNotFoundHint)
|
|
22091
|
-
})
|
|
22092
|
-
};
|
|
22093
|
-
}
|
|
22211
|
+
const parsed = parseQualifiedExtensionId(id, catalogue);
|
|
22212
|
+
if (!parsed.ok) return { error: renderQualifiedIdError(parsed, id, ansi) };
|
|
22094
22213
|
return {
|
|
22095
22214
|
origin: "qualified",
|
|
22096
|
-
keys: [qualifiedExtensionId(pluginId, extId)]
|
|
22215
|
+
keys: [qualifiedExtensionId(parsed.pluginId, parsed.extId)]
|
|
22097
22216
|
};
|
|
22098
22217
|
}
|
|
22099
22218
|
function resolveBareToggle(id, catalogue) {
|
|
@@ -22473,7 +22592,7 @@ Generated by \`sm plugins create ${kind} ${pluginId}\`. Edit \`${mainFileRel}\`
|
|
|
22473
22592
|
|
|
22474
22593
|
## Verbs
|
|
22475
22594
|
|
|
22476
|
-
- \`sm plugins
|
|
22595
|
+
- \`sm plugins list ${pluginId}\`: manifest + extensions + load status
|
|
22477
22596
|
- \`sm plugins doctor\`: full plugin diagnostic
|
|
22478
22597
|
- \`sm scan\`: re-emit contributions / re-run analysis
|
|
22479
22598
|
|
|
@@ -22620,11 +22739,12 @@ var INPUT_TYPES_CATALOG = [
|
|
|
22620
22739
|
{ id: "single-string", summary: "Single text input." },
|
|
22621
22740
|
{ id: "boolean-flag", summary: "On/off toggle." },
|
|
22622
22741
|
{ id: "integer", summary: "Integer with optional bounds." },
|
|
22742
|
+
{ id: "number", summary: "Decimal number with optional bounds." },
|
|
22623
22743
|
{ id: "enum-pick", summary: "Pick one from a closed set." },
|
|
22624
22744
|
{ id: "enum-multipick", summary: "Pick zero or more from a closed set." },
|
|
22625
22745
|
{ id: "path-glob", summary: "Glob pattern (single or multiple)." },
|
|
22626
22746
|
{ id: "regex", summary: "ECMAScript regex pattern body." },
|
|
22627
|
-
{ id: "secret", summary: "Sensitive string (
|
|
22747
|
+
{ id: "secret", summary: "Sensitive string, forced into project-local storage (gitignored), not encrypted." },
|
|
22628
22748
|
{ id: "key-value-list", summary: "Editable mapping of strings to strings." }
|
|
22629
22749
|
];
|
|
22630
22750
|
|
|
@@ -22645,57 +22765,426 @@ var PluginsSlotsListCommand = class extends SmCommand {
|
|
|
22645
22765
|
2
|
|
22646
22766
|
) + "\n"
|
|
22647
22767
|
);
|
|
22648
|
-
return ExitCode.Ok;
|
|
22768
|
+
return ExitCode.Ok;
|
|
22769
|
+
}
|
|
22770
|
+
const ansi = this.ansiFor("stdout");
|
|
22771
|
+
const idWidth = Math.max(
|
|
22772
|
+
...VIEW_SLOTS_CATALOG.map((c) => c.id.length),
|
|
22773
|
+
...INPUT_TYPES_CATALOG.map((t) => t.id.length)
|
|
22774
|
+
);
|
|
22775
|
+
this.printer.data(
|
|
22776
|
+
tx(PLUGINS_TEXTS.slotsListHeaderViewSlots, { count: VIEW_SLOTS_CATALOG.length })
|
|
22777
|
+
);
|
|
22778
|
+
for (const c of VIEW_SLOTS_CATALOG) {
|
|
22779
|
+
this.printer.data(
|
|
22780
|
+
` ${c.id.padEnd(idWidth)} ${ansi.dim(c.summary)}
|
|
22781
|
+
`
|
|
22782
|
+
);
|
|
22783
|
+
}
|
|
22784
|
+
this.printer.data(
|
|
22785
|
+
tx(PLUGINS_TEXTS.slotsListHeaderInputTypes, { count: INPUT_TYPES_CATALOG.length })
|
|
22786
|
+
);
|
|
22787
|
+
for (const t of INPUT_TYPES_CATALOG) {
|
|
22788
|
+
this.printer.data(
|
|
22789
|
+
` ${t.id.padEnd(idWidth)} ${ansi.dim(t.summary)}
|
|
22790
|
+
`
|
|
22791
|
+
);
|
|
22792
|
+
}
|
|
22793
|
+
this.printer.data(
|
|
22794
|
+
tx(PLUGINS_TEXTS.slotsListTipFooter, {
|
|
22795
|
+
tip: ansi.dim(PLUGINS_TEXTS.slotsListTipText)
|
|
22796
|
+
})
|
|
22797
|
+
);
|
|
22798
|
+
return ExitCode.Ok;
|
|
22799
|
+
}
|
|
22800
|
+
};
|
|
22801
|
+
|
|
22802
|
+
// cli/commands/plugins/upgrade.ts
|
|
22803
|
+
import { Command as Command28, Option as Option26 } from "clipanion";
|
|
22804
|
+
var PluginsUpgradeCommand = class extends SmCommand {
|
|
22805
|
+
static paths = [["plugins", "upgrade"]];
|
|
22806
|
+
static usage = Command28.Usage({
|
|
22807
|
+
category: "Plugins",
|
|
22808
|
+
description: "Apply catalog migrations to plugin manifests.",
|
|
22809
|
+
details: "No migrations registered against catalog v1.0.0 yet; this verb is a no-op today. The structure exists so future slot renames / deprecations land without spec churn."
|
|
22810
|
+
});
|
|
22811
|
+
pluginId = Option26.String({ required: false, name: "plugin-id" });
|
|
22812
|
+
async run() {
|
|
22813
|
+
this.printer.data(
|
|
22814
|
+
"sm plugins upgrade: no migrations registered for catalog v1.0.0.\n All loaded plugins are catalog-current.\n Run `sm plugins doctor` to surface any incompatible-catalog status.\n"
|
|
22815
|
+
);
|
|
22816
|
+
return ExitCode.Ok;
|
|
22817
|
+
}
|
|
22818
|
+
};
|
|
22819
|
+
|
|
22820
|
+
// cli/commands/plugins/config.ts
|
|
22821
|
+
import { Command as Command29, Option as Option27 } from "clipanion";
|
|
22822
|
+
|
|
22823
|
+
// cli/i18n/plugins-config.texts.ts
|
|
22824
|
+
var PLUGINS_CONFIG_TEXTS = {
|
|
22825
|
+
// --- id-shape redirects ----------------------------------------------
|
|
22826
|
+
// `sm plugins config` operates on one extension. A bare plugin id is
|
|
22827
|
+
// the wrong granularity, redirect to `sm plugins list <id>`.
|
|
22828
|
+
bareId: '{{glyph}} `sm plugins config` needs a qualified `<plugin>/<ext>` id; "{{id}}" is a plugin.\n {{hint}}\n',
|
|
22829
|
+
bareIdHint: "Run `sm plugins list {{id}}` to see the extensions, then `sm plugins config {{id}}/<ext>`.",
|
|
22830
|
+
// --- no declared settings --------------------------------------------
|
|
22831
|
+
noSettings: '{{glyph}} Extension "{{id}}" declares no configurable settings.\n {{hint}}\n',
|
|
22832
|
+
noSettingsHint: "Run `sm plugins show {{id}}` to inspect the extension.",
|
|
22833
|
+
unknownSetting: '{{glyph}} Unknown setting "{{settingId}}" for extension "{{id}}".\n {{hint}}\n',
|
|
22834
|
+
unknownSettingHint: "Declared settings: {{declared}}.",
|
|
22835
|
+
// --- coercion / validation -------------------------------------------
|
|
22836
|
+
coerceFailed: '{{glyph}} Could not parse "{{value}}" as type {{type}} for setting "{{settingId}}".\n {{hint}}\n',
|
|
22837
|
+
coerceFailedHint: "{{detail}}",
|
|
22838
|
+
validationFailed: '{{glyph}} Invalid value for setting "{{settingId}}" ({{type}}): {{reason}}.\n',
|
|
22839
|
+
writeFailed: '{{glyph}} Failed to write setting "{{settingId}}": {{message}}\n',
|
|
22840
|
+
// --- table view (no settingId) ---------------------------------------
|
|
22841
|
+
/** Section header above the settings table. */
|
|
22842
|
+
tableHeader: " Settings for {{id}}\n",
|
|
22843
|
+
/** One table row: setting id, effective value, source layer tag. */
|
|
22844
|
+
tableRow: " {{settingId}} {{value}}{{sourceTag}}\n",
|
|
22845
|
+
/** Dim suffix showing which layer set the effective value. */
|
|
22846
|
+
tableSourceTag: " [{{source}}]",
|
|
22847
|
+
/** Redaction placeholder for `secret`-typed values in any output. */
|
|
22848
|
+
redacted: "<redacted>",
|
|
22849
|
+
// --- write / reset receipts ------------------------------------------
|
|
22850
|
+
setWritten: "{{glyph}} Set {{settingId}} = {{value}} for {{id}}{{wroteTag}}\n",
|
|
22851
|
+
setWroteTag: " (wrote {{path}})",
|
|
22852
|
+
resetRemoved: "{{glyph}} Cleared {{settingId}} for {{id}}; falls back to the declared default{{wroteTag}}\n",
|
|
22853
|
+
resetNoOverride: "{{glyph}} No override set for {{settingId}} on {{id}}; nothing to clear.\n",
|
|
22854
|
+
// --- re-scan footer ---------------------------------------------------
|
|
22855
|
+
rescanFooter: "{{hint}}\n",
|
|
22856
|
+
rescanFooterText: "Settings are read once per scan; run `sm scan` to apply."
|
|
22857
|
+
};
|
|
22858
|
+
|
|
22859
|
+
// cli/commands/plugins/config.ts
|
|
22860
|
+
var PluginsConfigCommand = class extends SmCommand {
|
|
22861
|
+
static paths = [["plugins", "config"]];
|
|
22862
|
+
static usage = Command29.Usage({
|
|
22863
|
+
category: "Plugins",
|
|
22864
|
+
description: "Read or write an extension's declared settings.",
|
|
22865
|
+
details: `
|
|
22866
|
+
Operates on a single extension by its qualified \`<plugin>/<ext>\`
|
|
22867
|
+
id. With no settingId it prints a table of each declared setting,
|
|
22868
|
+
its effective value, and the config layer that set it. With a
|
|
22869
|
+
settingId + value it coerces the shell string to the declared
|
|
22870
|
+
input-type, validates it, and writes
|
|
22871
|
+
\`plugins.<plugin>.extensions.<ext>.settings.<settingId>\` to
|
|
22872
|
+
settings.json (or settings.local.json for \`secret\` settings).
|
|
22873
|
+
\`--reset\` removes the override so the manifest default applies.
|
|
22874
|
+
Secret values are shown as <redacted>. Run \`sm scan\` to apply.
|
|
22875
|
+
`
|
|
22876
|
+
});
|
|
22877
|
+
id = Option27.String({ required: true });
|
|
22878
|
+
settingId = Option27.String({ required: false });
|
|
22879
|
+
value = Option27.String({ required: false });
|
|
22880
|
+
reset = Option27.Boolean("--reset", false, {
|
|
22881
|
+
description: "Remove the override for <settingId> so the manifest default applies."
|
|
22882
|
+
});
|
|
22883
|
+
pluginDir = Option27.String("--plugin-dir", { required: false });
|
|
22884
|
+
// Read-only when listing; the write / reset paths emit their own
|
|
22885
|
+
// receipt. `sm config` exempts the config family from "done in <…>";
|
|
22886
|
+
// mirror that here for the read path. The write path keeps the line.
|
|
22887
|
+
emitElapsed = true;
|
|
22888
|
+
// CLI orchestrator: each branch is one validation gate (bare id /
|
|
22889
|
+
// unknown extension / no settings / unknown setting) or a mode
|
|
22890
|
+
// dispatch (table vs set vs reset). Splitting per branch scatters the
|
|
22891
|
+
// gate from the value it gates.
|
|
22892
|
+
// eslint-disable-next-line complexity
|
|
22893
|
+
async run() {
|
|
22894
|
+
const ctx = defaultRuntimeContext();
|
|
22895
|
+
const stderrAnsi = this.ansiFor("stderr");
|
|
22896
|
+
if (!this.id.includes("/")) {
|
|
22897
|
+
this.printer.error(
|
|
22898
|
+
tx(PLUGINS_CONFIG_TEXTS.bareId, {
|
|
22899
|
+
glyph: stderrAnsi.red("\u2715"),
|
|
22900
|
+
id: sanitizeForTerminal(this.id),
|
|
22901
|
+
hint: stderrAnsi.dim(
|
|
22902
|
+
tx(PLUGINS_CONFIG_TEXTS.bareIdHint, { id: sanitizeForTerminal(this.id) })
|
|
22903
|
+
)
|
|
22904
|
+
})
|
|
22905
|
+
);
|
|
22906
|
+
return ExitCode.Error;
|
|
22907
|
+
}
|
|
22908
|
+
const plugins = await loadAll({ pluginDir: this.pluginDir });
|
|
22909
|
+
const parsed = parseQualifiedExtensionId(this.id, pluginCatalogue(plugins));
|
|
22910
|
+
if (!parsed.ok) {
|
|
22911
|
+
this.printer.error(renderQualifiedIdError(parsed, this.id, stderrAnsi));
|
|
22912
|
+
return ExitCode.NotFound;
|
|
22913
|
+
}
|
|
22914
|
+
const { pluginId, extId } = parsed;
|
|
22915
|
+
const declarations = resolveDeclaredSettings(pluginId, extId, plugins);
|
|
22916
|
+
if (!declarations || Object.keys(declarations).length === 0) {
|
|
22917
|
+
this.printer.error(
|
|
22918
|
+
tx(PLUGINS_CONFIG_TEXTS.noSettings, {
|
|
22919
|
+
glyph: stderrAnsi.red("\u2715"),
|
|
22920
|
+
id: sanitizeForTerminal(this.id),
|
|
22921
|
+
hint: stderrAnsi.dim(
|
|
22922
|
+
tx(PLUGINS_CONFIG_TEXTS.noSettingsHint, { id: sanitizeForTerminal(this.id) })
|
|
22923
|
+
)
|
|
22924
|
+
})
|
|
22925
|
+
);
|
|
22926
|
+
return ExitCode.NotFound;
|
|
22927
|
+
}
|
|
22928
|
+
if (this.settingId === void 0) {
|
|
22929
|
+
return this.renderTable(pluginId, extId, declarations, ctx.cwd);
|
|
22930
|
+
}
|
|
22931
|
+
const declaration = declarations[this.settingId];
|
|
22932
|
+
if (!declaration) {
|
|
22933
|
+
this.printer.error(
|
|
22934
|
+
tx(PLUGINS_CONFIG_TEXTS.unknownSetting, {
|
|
22935
|
+
glyph: stderrAnsi.red("\u2715"),
|
|
22936
|
+
settingId: sanitizeForTerminal(this.settingId),
|
|
22937
|
+
id: sanitizeForTerminal(this.id),
|
|
22938
|
+
hint: stderrAnsi.dim(
|
|
22939
|
+
tx(PLUGINS_CONFIG_TEXTS.unknownSettingHint, {
|
|
22940
|
+
declared: Object.keys(declarations).map((k) => `'${k}'`).join(", ")
|
|
22941
|
+
})
|
|
22942
|
+
)
|
|
22943
|
+
})
|
|
22944
|
+
);
|
|
22945
|
+
return ExitCode.NotFound;
|
|
22946
|
+
}
|
|
22947
|
+
if (this.reset) {
|
|
22948
|
+
return this.resetSetting(pluginId, extId, this.settingId, declaration, ctx.cwd);
|
|
22949
|
+
}
|
|
22950
|
+
if (this.value === void 0) {
|
|
22951
|
+
return this.renderTable(pluginId, extId, { [this.settingId]: declaration }, ctx.cwd);
|
|
22952
|
+
}
|
|
22953
|
+
return this.writeSetting(pluginId, extId, this.settingId, declaration, this.value, ctx.cwd);
|
|
22954
|
+
}
|
|
22955
|
+
/**
|
|
22956
|
+
* Render the settings table (human) or the resolved set (`--json`).
|
|
22957
|
+
* Effective values come from the kernel settings resolver so the table
|
|
22958
|
+
* shows exactly what `ctx.settings.<id>` would see at scan time
|
|
22959
|
+
* (default overlaid by the config override, validated). Secret values
|
|
22960
|
+
* are redacted.
|
|
22961
|
+
*/
|
|
22962
|
+
renderTable(pluginId, extId, declarations, cwd) {
|
|
22963
|
+
const { effective } = loadConfig({ cwd });
|
|
22964
|
+
const resolved = resolveExtensionSettings({ pluginId, id: extId, settings: declarations }, effective, () => {
|
|
22965
|
+
});
|
|
22966
|
+
if (this.json) {
|
|
22967
|
+
const payload = {};
|
|
22968
|
+
for (const [settingId, declaration] of Object.entries(declarations)) {
|
|
22969
|
+
payload[settingId] = declaration.type === "secret" ? PLUGINS_CONFIG_TEXTS.redacted : resolved[settingId] ?? null;
|
|
22970
|
+
}
|
|
22971
|
+
this.printer.data(JSON.stringify(payload) + "\n");
|
|
22972
|
+
return ExitCode.Ok;
|
|
22973
|
+
}
|
|
22974
|
+
const ansi = this.ansiFor("stdout");
|
|
22975
|
+
const lines = [tx(PLUGINS_CONFIG_TEXTS.tableHeader, { id: `${pluginId}/${extId}` })];
|
|
22976
|
+
const idWidth = Math.max(...Object.keys(declarations).map((k) => k.length));
|
|
22977
|
+
for (const [settingId, declaration] of Object.entries(declarations)) {
|
|
22978
|
+
const display = declaration.type === "secret" ? PLUGINS_CONFIG_TEXTS.redacted : formatValue2(resolved[settingId]);
|
|
22979
|
+
const dotKey = settingDotKey(pluginId, extId, settingId);
|
|
22980
|
+
const source = getValueSource(dotKey, { cwd });
|
|
22981
|
+
const sourceTag = source ? ansi.dim(tx(PLUGINS_CONFIG_TEXTS.tableSourceTag, { source: layerLabel(source) })) : "";
|
|
22982
|
+
lines.push(
|
|
22983
|
+
tx(PLUGINS_CONFIG_TEXTS.tableRow, {
|
|
22984
|
+
settingId: settingId.padEnd(idWidth),
|
|
22985
|
+
value: sanitizeForTerminal(display),
|
|
22986
|
+
sourceTag
|
|
22987
|
+
})
|
|
22988
|
+
);
|
|
22989
|
+
}
|
|
22990
|
+
this.printer.data(lines.join(""));
|
|
22991
|
+
return ExitCode.Ok;
|
|
22992
|
+
}
|
|
22993
|
+
writeSetting(pluginId, extId, settingId, declaration, raw, cwd) {
|
|
22994
|
+
const stderrAnsi = this.ansiFor("stderr");
|
|
22995
|
+
const errGlyph = stderrAnsi.red("\u2715");
|
|
22996
|
+
const coerced = coerceCliValue(declaration, raw);
|
|
22997
|
+
if (!coerced.ok) {
|
|
22998
|
+
this.printer.error(
|
|
22999
|
+
tx(PLUGINS_CONFIG_TEXTS.coerceFailed, {
|
|
23000
|
+
glyph: errGlyph,
|
|
23001
|
+
value: sanitizeForTerminal(raw),
|
|
23002
|
+
type: declaration.type,
|
|
23003
|
+
settingId: sanitizeForTerminal(settingId),
|
|
23004
|
+
hint: stderrAnsi.dim(tx(PLUGINS_CONFIG_TEXTS.coerceFailedHint, { detail: coerced.reason }))
|
|
23005
|
+
})
|
|
23006
|
+
);
|
|
23007
|
+
return ExitCode.Error;
|
|
23008
|
+
}
|
|
23009
|
+
let invalidReason = null;
|
|
23010
|
+
resolveExtensionSettings(
|
|
23011
|
+
{ pluginId, id: extId, settings: { [settingId]: declaration } },
|
|
23012
|
+
{ plugins: { [pluginId]: { extensions: { [extId]: { settings: { [settingId]: coerced.value } } } } } },
|
|
23013
|
+
(message) => {
|
|
23014
|
+
invalidReason = message;
|
|
23015
|
+
}
|
|
23016
|
+
);
|
|
23017
|
+
if (invalidReason !== null) {
|
|
23018
|
+
this.printer.error(
|
|
23019
|
+
tx(PLUGINS_CONFIG_TEXTS.validationFailed, {
|
|
23020
|
+
glyph: errGlyph,
|
|
23021
|
+
settingId: sanitizeForTerminal(settingId),
|
|
23022
|
+
type: declaration.type,
|
|
23023
|
+
reason: invalidReason
|
|
23024
|
+
})
|
|
23025
|
+
);
|
|
23026
|
+
return ExitCode.Error;
|
|
23027
|
+
}
|
|
23028
|
+
const target = declaration.type === "secret" ? "project-local" : "project";
|
|
23029
|
+
const dotKey = settingDotKey(pluginId, extId, settingId);
|
|
23030
|
+
try {
|
|
23031
|
+
writeConfigValue(dotKey, coerced.value, { target, cwd });
|
|
23032
|
+
} catch (err) {
|
|
23033
|
+
return this.renderWriteError(err, settingId);
|
|
22649
23034
|
}
|
|
22650
23035
|
const ansi = this.ansiFor("stdout");
|
|
22651
|
-
const
|
|
22652
|
-
|
|
22653
|
-
...INPUT_TYPES_CATALOG.map((t) => t.id.length)
|
|
22654
|
-
);
|
|
23036
|
+
const display = declaration.type === "secret" ? PLUGINS_CONFIG_TEXTS.redacted : formatValue2(coerced.value);
|
|
23037
|
+
const path = target === "project-local" ? defaultLocalSettingsPath(cwd) : defaultSettingsPath(cwd);
|
|
22655
23038
|
this.printer.data(
|
|
22656
|
-
tx(
|
|
23039
|
+
tx(PLUGINS_CONFIG_TEXTS.setWritten, {
|
|
23040
|
+
glyph: ansi.green("\u2713"),
|
|
23041
|
+
settingId,
|
|
23042
|
+
value: sanitizeForTerminal(display),
|
|
23043
|
+
id: `${pluginId}/${extId}`,
|
|
23044
|
+
wroteTag: ansi.dim(tx(PLUGINS_CONFIG_TEXTS.setWroteTag, { path: relativeIfBelow(path, cwd) }))
|
|
23045
|
+
})
|
|
22657
23046
|
);
|
|
22658
|
-
|
|
23047
|
+
this.printRescanFooter();
|
|
23048
|
+
return ExitCode.Ok;
|
|
23049
|
+
}
|
|
23050
|
+
resetSetting(pluginId, extId, settingId, declaration, cwd) {
|
|
23051
|
+
const target = declaration.type === "secret" ? "project-local" : "project";
|
|
23052
|
+
const dotKey = settingDotKey(pluginId, extId, settingId);
|
|
23053
|
+
const ansi = this.ansiFor("stdout");
|
|
23054
|
+
let removed;
|
|
23055
|
+
try {
|
|
23056
|
+
removed = removeConfigValue(dotKey, { target, cwd });
|
|
23057
|
+
} catch (err) {
|
|
23058
|
+
return this.renderWriteError(err, settingId);
|
|
23059
|
+
}
|
|
23060
|
+
if (!removed) {
|
|
22659
23061
|
this.printer.data(
|
|
22660
|
-
|
|
22661
|
-
|
|
23062
|
+
tx(PLUGINS_CONFIG_TEXTS.resetNoOverride, {
|
|
23063
|
+
glyph: ansi.green("\u2713"),
|
|
23064
|
+
settingId,
|
|
23065
|
+
id: `${pluginId}/${extId}`
|
|
23066
|
+
})
|
|
22662
23067
|
);
|
|
23068
|
+
return ExitCode.Ok;
|
|
22663
23069
|
}
|
|
23070
|
+
const path = target === "project-local" ? defaultLocalSettingsPath(cwd) : defaultSettingsPath(cwd);
|
|
22664
23071
|
this.printer.data(
|
|
22665
|
-
tx(
|
|
23072
|
+
tx(PLUGINS_CONFIG_TEXTS.resetRemoved, {
|
|
23073
|
+
glyph: ansi.green("\u2713"),
|
|
23074
|
+
settingId,
|
|
23075
|
+
id: `${pluginId}/${extId}`,
|
|
23076
|
+
wroteTag: ansi.dim(tx(PLUGINS_CONFIG_TEXTS.setWroteTag, { path: relativeIfBelow(path, cwd) }))
|
|
23077
|
+
})
|
|
22666
23078
|
);
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
23079
|
+
this.printRescanFooter();
|
|
23080
|
+
return ExitCode.Ok;
|
|
23081
|
+
}
|
|
23082
|
+
renderWriteError(err, settingId) {
|
|
23083
|
+
const ansi = this.ansiFor("stderr");
|
|
23084
|
+
const glyph = ansi.red("\u2715");
|
|
23085
|
+
if (err instanceof ForbiddenSegmentError) {
|
|
23086
|
+
this.printer.error(
|
|
23087
|
+
tx(PLUGINS_CONFIG_TEXTS.writeFailed, { glyph, settingId, message: err.message })
|
|
23088
|
+
);
|
|
23089
|
+
return ExitCode.Error;
|
|
23090
|
+
}
|
|
23091
|
+
if (err instanceof ProjectLocalOnlyKeyError) {
|
|
23092
|
+
this.printer.error(
|
|
23093
|
+
tx(PLUGINS_CONFIG_TEXTS.writeFailed, { glyph, settingId, message: err.message })
|
|
23094
|
+
);
|
|
23095
|
+
return ExitCode.Error;
|
|
23096
|
+
}
|
|
23097
|
+
if (err instanceof ConfigValidationError) {
|
|
23098
|
+
this.printer.error(
|
|
23099
|
+
tx(PLUGINS_CONFIG_TEXTS.writeFailed, { glyph, settingId, message: err.errors })
|
|
22671
23100
|
);
|
|
23101
|
+
return ExitCode.Error;
|
|
22672
23102
|
}
|
|
23103
|
+
throw err;
|
|
23104
|
+
}
|
|
23105
|
+
printRescanFooter() {
|
|
23106
|
+
if (this.json) return;
|
|
23107
|
+
const ansi = this.ansiFor("stdout");
|
|
22673
23108
|
this.printer.data(
|
|
22674
|
-
tx(
|
|
22675
|
-
|
|
23109
|
+
tx(PLUGINS_CONFIG_TEXTS.rescanFooter, {
|
|
23110
|
+
hint: ansi.dim(PLUGINS_CONFIG_TEXTS.rescanFooterText)
|
|
22676
23111
|
})
|
|
22677
23112
|
);
|
|
22678
|
-
return ExitCode.Ok;
|
|
22679
23113
|
}
|
|
22680
23114
|
};
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
return
|
|
23115
|
+
function resolveDeclaredSettings(pluginId, extId, plugins) {
|
|
23116
|
+
for (const plugin of builtInPlugins) {
|
|
23117
|
+
if (plugin.id !== pluginId) continue;
|
|
23118
|
+
for (const ext of plugin.extensions) {
|
|
23119
|
+
if (ext.id === extId) return ext.settings;
|
|
23120
|
+
}
|
|
23121
|
+
}
|
|
23122
|
+
const match = plugins.find((p) => p.id === pluginId);
|
|
23123
|
+
const userExt = match?.extensions?.find((e) => e.id === extId);
|
|
23124
|
+
return readInstanceSettings(userExt?.instance);
|
|
23125
|
+
}
|
|
23126
|
+
function readInstanceSettings(instance) {
|
|
23127
|
+
if (instance === null || typeof instance !== "object") return void 0;
|
|
23128
|
+
const settings2 = instance.settings;
|
|
23129
|
+
if (settings2 && typeof settings2 === "object" && !Array.isArray(settings2)) {
|
|
23130
|
+
return settings2;
|
|
23131
|
+
}
|
|
23132
|
+
return void 0;
|
|
23133
|
+
}
|
|
23134
|
+
function coerceCliValue(declaration, raw) {
|
|
23135
|
+
switch (declaration.type) {
|
|
23136
|
+
case "integer":
|
|
23137
|
+
case "number": {
|
|
23138
|
+
const n = Number(raw);
|
|
23139
|
+
if (raw.trim() === "" || Number.isNaN(n)) return { ok: false, reason: "expected a numeric value" };
|
|
23140
|
+
return { ok: true, value: n };
|
|
23141
|
+
}
|
|
23142
|
+
case "boolean-flag": {
|
|
23143
|
+
if (raw === "true") return { ok: true, value: true };
|
|
23144
|
+
if (raw === "false") return { ok: true, value: false };
|
|
23145
|
+
return { ok: false, reason: "expected `true` or `false`" };
|
|
23146
|
+
}
|
|
23147
|
+
case "string-list":
|
|
23148
|
+
case "enum-multipick":
|
|
23149
|
+
case "key-value-list":
|
|
23150
|
+
return parseJsonValue(raw);
|
|
23151
|
+
case "path-glob":
|
|
23152
|
+
return declaration.multiple === true ? parseJsonValue(raw) : { ok: true, value: raw };
|
|
23153
|
+
case "single-string":
|
|
23154
|
+
case "enum-pick":
|
|
23155
|
+
case "regex":
|
|
23156
|
+
case "secret":
|
|
23157
|
+
return { ok: true, value: raw };
|
|
23158
|
+
default: {
|
|
23159
|
+
const _exhaustive = declaration;
|
|
23160
|
+
return { ok: false, reason: `unknown input-type: ${String(_exhaustive.type)}` };
|
|
23161
|
+
}
|
|
23162
|
+
}
|
|
23163
|
+
}
|
|
23164
|
+
function parseJsonValue(raw) {
|
|
23165
|
+
try {
|
|
23166
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
23167
|
+
} catch {
|
|
23168
|
+
return { ok: false, reason: 'expected a JSON value (e.g. ["a","b"])' };
|
|
22697
23169
|
}
|
|
23170
|
+
}
|
|
23171
|
+
function settingDotKey(pluginId, extId, settingId) {
|
|
23172
|
+
return `plugins.${pluginId}.extensions.${extId}.settings.${settingId}`;
|
|
23173
|
+
}
|
|
23174
|
+
function formatValue2(value) {
|
|
23175
|
+
if (value === void 0 || value === null) return "null";
|
|
23176
|
+
if (Array.isArray(value) || typeof value === "object") return JSON.stringify(value);
|
|
23177
|
+
return String(value);
|
|
23178
|
+
}
|
|
23179
|
+
var LAYER_LABEL = {
|
|
23180
|
+
defaults: "default",
|
|
23181
|
+
project: "settings.json",
|
|
23182
|
+
"project-local": "settings.local.json",
|
|
23183
|
+
override: "override"
|
|
22698
23184
|
};
|
|
23185
|
+
function layerLabel(layer) {
|
|
23186
|
+
return LAYER_LABEL[layer];
|
|
23187
|
+
}
|
|
22699
23188
|
|
|
22700
23189
|
// cli/commands/plugins.ts
|
|
22701
23190
|
var PLUGIN_COMMANDS = [
|
|
@@ -22706,13 +23195,14 @@ var PLUGIN_COMMANDS = [
|
|
|
22706
23195
|
PluginsDisableCommand,
|
|
22707
23196
|
PluginsCreateCommand,
|
|
22708
23197
|
PluginsSlotsListCommand,
|
|
22709
|
-
PluginsUpgradeCommand
|
|
23198
|
+
PluginsUpgradeCommand,
|
|
23199
|
+
PluginsConfigCommand
|
|
22710
23200
|
];
|
|
22711
23201
|
|
|
22712
23202
|
// cli/commands/refresh.ts
|
|
22713
23203
|
import { readFile as readFile4 } from "fs/promises";
|
|
22714
23204
|
import { resolve as resolve34 } from "path";
|
|
22715
|
-
import { Command as
|
|
23205
|
+
import { Command as Command30, Option as Option28 } from "clipanion";
|
|
22716
23206
|
|
|
22717
23207
|
// cli/i18n/refresh.texts.ts
|
|
22718
23208
|
var REFRESH_TEXTS = {
|
|
@@ -22768,7 +23258,7 @@ var REFRESH_TEXTS = {
|
|
|
22768
23258
|
// cli/commands/refresh.ts
|
|
22769
23259
|
var RefreshCommand = class extends SmCommand {
|
|
22770
23260
|
static paths = [["refresh"]];
|
|
22771
|
-
static usage =
|
|
23261
|
+
static usage = Command30.Usage({
|
|
22772
23262
|
category: "Scan",
|
|
22773
23263
|
description: "Refresh enrichment rows: granular (single node) or batch (every stale row).",
|
|
22774
23264
|
details: `
|
|
@@ -22790,11 +23280,11 @@ var RefreshCommand = class extends SmCommand {
|
|
|
22790
23280
|
["Refresh every node with stale enrichments", "$0 refresh --stale"]
|
|
22791
23281
|
]
|
|
22792
23282
|
});
|
|
22793
|
-
nodePath =
|
|
22794
|
-
stale =
|
|
23283
|
+
nodePath = Option28.String({ name: "node", required: false });
|
|
23284
|
+
stale = Option28.Boolean("--stale", false, {
|
|
22795
23285
|
description: "Refresh every node carrying a stale enrichment row (no-op in this revision; reserved for future Action-prob enrichments)."
|
|
22796
23286
|
});
|
|
22797
|
-
noPlugins =
|
|
23287
|
+
noPlugins = Option28.Boolean("--no-plugins", false, {
|
|
22798
23288
|
description: "Skip drop-in plugin discovery; use only the built-in extractor set."
|
|
22799
23289
|
});
|
|
22800
23290
|
// The remaining cyclomatic count comes from CLI ergonomics that don't
|
|
@@ -22828,9 +23318,11 @@ var RefreshCommand = class extends SmCommand {
|
|
|
22828
23318
|
const pluginRuntime = this.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime();
|
|
22829
23319
|
pluginRuntime.emitWarnings(this.printer);
|
|
22830
23320
|
listBuiltIns();
|
|
23321
|
+
const refreshCfg = loadConfig({ cwd: ctx.cwd }).effective;
|
|
22831
23322
|
const composed = composeScanExtensions({
|
|
22832
23323
|
noBuiltIns: false,
|
|
22833
23324
|
pluginRuntime,
|
|
23325
|
+
resolveSettings: buildSettingsResolver(refreshCfg),
|
|
22834
23326
|
killSwitches: readConformanceKillSwitches()
|
|
22835
23327
|
});
|
|
22836
23328
|
const allExtractors = composed?.extractors ?? [];
|
|
@@ -23096,7 +23588,7 @@ var IntentionalFailCommand = class extends SmCommand {
|
|
|
23096
23588
|
};
|
|
23097
23589
|
|
|
23098
23590
|
// cli/commands/scan.ts
|
|
23099
|
-
import { Command as
|
|
23591
|
+
import { Command as Command32, Option as Option30 } from "clipanion";
|
|
23100
23592
|
|
|
23101
23593
|
// kernel/util/format-bytes.ts
|
|
23102
23594
|
var UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
|
|
@@ -23248,7 +23740,7 @@ var SCAN_TEXTS = {
|
|
|
23248
23740
|
};
|
|
23249
23741
|
|
|
23250
23742
|
// cli/commands/watch.ts
|
|
23251
|
-
import { Command as
|
|
23743
|
+
import { Command as Command31, Option as Option29 } from "clipanion";
|
|
23252
23744
|
|
|
23253
23745
|
// core/watcher/runtime.ts
|
|
23254
23746
|
import { dirname as dirname18 } from "path";
|
|
@@ -23370,7 +23862,8 @@ function createWatcherRuntime(opts) {
|
|
|
23370
23862
|
const composeOpts = {
|
|
23371
23863
|
noBuiltIns: opts.noBuiltIns,
|
|
23372
23864
|
pluginRuntime,
|
|
23373
|
-
resolveEnabled: resolveEnabledOverride
|
|
23865
|
+
resolveEnabled: resolveEnabledOverride,
|
|
23866
|
+
resolveSettings: buildSettingsResolver(cfg)
|
|
23374
23867
|
};
|
|
23375
23868
|
if (opts.killSwitches) composeOpts.killSwitches = opts.killSwitches;
|
|
23376
23869
|
const composed = composeScanExtensions(composeOpts);
|
|
@@ -23787,7 +24280,7 @@ async function runWatchLoop(opts) {
|
|
|
23787
24280
|
}
|
|
23788
24281
|
var WatchCommand = class extends SmCommand {
|
|
23789
24282
|
static paths = [["watch"]];
|
|
23790
|
-
static usage =
|
|
24283
|
+
static usage = Command31.Usage({
|
|
23791
24284
|
category: "Scan",
|
|
23792
24285
|
description: "Watch roots and run an incremental scan after each debounced batch of filesystem events.",
|
|
23793
24286
|
details: `
|
|
@@ -23811,21 +24304,21 @@ var WatchCommand = class extends SmCommand {
|
|
|
23811
24304
|
["Stream ScanResult per batch as ndjson", "$0 watch --json"]
|
|
23812
24305
|
]
|
|
23813
24306
|
});
|
|
23814
|
-
roots =
|
|
23815
|
-
noTokens =
|
|
24307
|
+
roots = Option29.Rest({ name: "roots" });
|
|
24308
|
+
noTokens = Option29.Boolean("--no-tokens", false, {
|
|
23816
24309
|
description: "Skip per-node token counts (cl100k_base BPE)."
|
|
23817
24310
|
});
|
|
23818
|
-
strict =
|
|
24311
|
+
strict = Option29.Boolean("--strict", false, {
|
|
23819
24312
|
description: "Promote frontmatter-validation findings from warn to error inside each batch. Does not change the watcher exit code."
|
|
23820
24313
|
});
|
|
23821
|
-
noPlugins =
|
|
24314
|
+
noPlugins = Option29.Boolean("--no-plugins", false, {
|
|
23822
24315
|
description: "Skip drop-in plugin discovery for the watcher session."
|
|
23823
24316
|
});
|
|
23824
|
-
maxConsecutiveFailures =
|
|
24317
|
+
maxConsecutiveFailures = Option29.String("--max-consecutive-failures", {
|
|
23825
24318
|
required: false,
|
|
23826
24319
|
description: "Shut down with exit 2 after N consecutive batch failures (default 5; 0 disables the breaker)."
|
|
23827
24320
|
});
|
|
23828
|
-
maxNodes =
|
|
24321
|
+
maxNodes = Option29.String("--max-nodes", {
|
|
23829
24322
|
required: false,
|
|
23830
24323
|
description: "Per-batch override of scan.maxNodes (default 256). Bidirectional: raises OR lowers the recommended cap on classified nodes. When a batch hits the cap, additional files are dropped and the UI surfaces the persistent oversized banner. Validation: integer >= 1."
|
|
23831
24324
|
});
|
|
@@ -23892,7 +24385,7 @@ function parseMaxNodesLimit(raw, stderr, noColor) {
|
|
|
23892
24385
|
// cli/commands/scan.ts
|
|
23893
24386
|
var ScanCommand = class extends SmCommand {
|
|
23894
24387
|
static paths = [["scan"]];
|
|
23895
|
-
static usage =
|
|
24388
|
+
static usage = Command32.Usage({
|
|
23896
24389
|
category: "Scan",
|
|
23897
24390
|
description: "Scan roots for markdown nodes, run extractors and analyzers.",
|
|
23898
24391
|
details: `
|
|
@@ -23927,35 +24420,35 @@ var ScanCommand = class extends SmCommand {
|
|
|
23927
24420
|
["What would the next incremental scan persist?", "$0 scan --changed -n --json"]
|
|
23928
24421
|
]
|
|
23929
24422
|
});
|
|
23930
|
-
roots =
|
|
23931
|
-
noBuiltIns =
|
|
24423
|
+
roots = Option30.Rest({ name: "roots" });
|
|
24424
|
+
noBuiltIns = Option30.Boolean("--no-built-ins", false, {
|
|
23932
24425
|
description: "Skip the built-in extension set. Yields a zero-filled ScanResult (kernel-empty-boot parity); skips DB persistence."
|
|
23933
24426
|
});
|
|
23934
|
-
noPlugins =
|
|
24427
|
+
noPlugins = Option30.Boolean("--no-plugins", false, {
|
|
23935
24428
|
description: "Skip drop-in plugin discovery. Only the built-in set runs. Combine with --no-built-ins for a fully empty pipeline."
|
|
23936
24429
|
});
|
|
23937
|
-
noTokens =
|
|
24430
|
+
noTokens = Option30.Boolean("--no-tokens", false, {
|
|
23938
24431
|
description: "Skip per-node token counts (cl100k_base BPE). Leaves node.tokens undefined; spec-valid since the field is optional."
|
|
23939
24432
|
});
|
|
23940
|
-
dryRun =
|
|
24433
|
+
dryRun = Option30.Boolean("-n,--dry-run", false, {
|
|
23941
24434
|
description: "Run the scan in memory and skip every DB write. Combined with --changed, still opens the DB read-side to load the prior snapshot."
|
|
23942
24435
|
});
|
|
23943
|
-
changed =
|
|
24436
|
+
changed = Option30.Boolean("--changed", false, {
|
|
23944
24437
|
description: "Incremental scan: reuse unchanged nodes from the persisted prior snapshot. Degrades to a full scan if no prior snapshot exists."
|
|
23945
24438
|
});
|
|
23946
|
-
allowEmpty =
|
|
24439
|
+
allowEmpty = Option30.Boolean("--allow-empty", false, {
|
|
23947
24440
|
description: "Allow a zero-result scan to wipe an already-populated DB (replace-all replace by zero rows). Off by default to avoid the typo-trap where an invalid root silently clears your data."
|
|
23948
24441
|
});
|
|
23949
|
-
strict =
|
|
24442
|
+
strict = Option30.Boolean("--strict", false, {
|
|
23950
24443
|
description: "Promote frontmatter-validation findings from warn to error (exit code 1 on any violation). Overrides scan.strict from config when both are set."
|
|
23951
24444
|
});
|
|
23952
|
-
watch =
|
|
24445
|
+
watch = Option30.Boolean("--watch", false, {
|
|
23953
24446
|
description: "Long-running mode: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`."
|
|
23954
24447
|
});
|
|
23955
|
-
yes =
|
|
24448
|
+
yes = Option30.Boolean("--yes", false, {
|
|
23956
24449
|
description: "Non-interactive mode. For ambiguous activeProvider auto-detect, multiple provider markers (.claude/, .codex/, AGENTS.md, .cursor/) under the scan tree exit non-zero instead of prompting; set the lens manually via `sm config set activeProvider <id>` and re-run. Also auto-confirms the pre-1.0 schema-drift rebuild (when the DB was written by a different skill-map major.minor it is deleted and regenerated) instead of prompting."
|
|
23957
24450
|
});
|
|
23958
|
-
maxNodes =
|
|
24451
|
+
maxNodes = Option30.String("--max-nodes", {
|
|
23959
24452
|
required: false,
|
|
23960
24453
|
description: "Per-invocation override of `scan.maxNodes` (default 256). Bidirectional: raises OR lowers the recommended cap on classified nodes. When the walker hits the cap, additional files are dropped and the scan is marked oversized in scan_meta (the UI raises a persistent banner pointing at the .skillmapignore editor in Settings \u2192 Project). Validation: integer >= 1."
|
|
23961
24454
|
});
|
|
@@ -24296,10 +24789,10 @@ function countNoun(count3, singular, plural) {
|
|
|
24296
24789
|
|
|
24297
24790
|
// cli/commands/scan-compare.ts
|
|
24298
24791
|
import { access, readFile as readFile5 } from "fs/promises";
|
|
24299
|
-
import { Command as
|
|
24792
|
+
import { Command as Command33, Option as Option31 } from "clipanion";
|
|
24300
24793
|
var ScanCompareCommand = class extends SmCommand {
|
|
24301
24794
|
static paths = [["scan", "compare-with"]];
|
|
24302
|
-
static usage =
|
|
24795
|
+
static usage = Command33.Usage({
|
|
24303
24796
|
category: "Scan",
|
|
24304
24797
|
description: "Run a fresh scan in memory and emit a delta against the saved ScanResult dump at <dump>. Read-only.",
|
|
24305
24798
|
details: `
|
|
@@ -24327,15 +24820,15 @@ var ScanCompareCommand = class extends SmCommand {
|
|
|
24327
24820
|
["JSON output for tooling", "$0 scan compare-with baseline.json --json"]
|
|
24328
24821
|
]
|
|
24329
24822
|
});
|
|
24330
|
-
dump =
|
|
24331
|
-
roots =
|
|
24332
|
-
noTokens =
|
|
24823
|
+
dump = Option31.String({ required: true });
|
|
24824
|
+
roots = Option31.Rest({ name: "roots" });
|
|
24825
|
+
noTokens = Option31.Boolean("--no-tokens", false, {
|
|
24333
24826
|
description: "Skip per-node token counts during the fresh scan."
|
|
24334
24827
|
});
|
|
24335
|
-
strict =
|
|
24828
|
+
strict = Option31.Boolean("--strict", false, {
|
|
24336
24829
|
description: "Promote layered-config warnings and frontmatter-validation findings from warn to error."
|
|
24337
24830
|
});
|
|
24338
|
-
noPlugins =
|
|
24831
|
+
noPlugins = Option31.Boolean("--no-plugins", false, {
|
|
24339
24832
|
description: "Skip drop-in plugin discovery."
|
|
24340
24833
|
});
|
|
24341
24834
|
// Cyclomatic count comes from CLI ergonomics: 3 distinct try/catch
|
|
@@ -24377,6 +24870,7 @@ var ScanCompareCommand = class extends SmCommand {
|
|
|
24377
24870
|
const composedExtensions = composeScanExtensions({
|
|
24378
24871
|
noBuiltIns: false,
|
|
24379
24872
|
pluginRuntime,
|
|
24873
|
+
resolveSettings: buildSettingsResolver(cfg),
|
|
24380
24874
|
killSwitches: readConformanceKillSwitches()
|
|
24381
24875
|
});
|
|
24382
24876
|
let current;
|
|
@@ -24540,7 +25034,7 @@ function renderDeltaIssues(issues) {
|
|
|
24540
25034
|
// cli/commands/serve.ts
|
|
24541
25035
|
import { spawn as spawn2 } from "child_process";
|
|
24542
25036
|
import { existsSync as existsSync31 } from "fs";
|
|
24543
|
-
import { Command as
|
|
25037
|
+
import { Command as Command34, Option as Option32 } from "clipanion";
|
|
24544
25038
|
|
|
24545
25039
|
// kernel/util/dev-mode.ts
|
|
24546
25040
|
import { sep as sep6 } from "path";
|
|
@@ -24801,8 +25295,22 @@ var SERVER_TEXTS = {
|
|
|
24801
25295
|
// The single-id variants above still apply for per-entry validation
|
|
24802
25296
|
// (unknown id, granularity mismatch, lock); these cover the
|
|
24803
25297
|
// body-shape level.
|
|
24804
|
-
pluginsChangesRequired: "Request body must include a `changes` array of `{ id, enabled }` entries.",
|
|
24805
|
-
pluginsChangeMalformed: "Each entry in `changes` must have a string `id`
|
|
25298
|
+
pluginsChangesRequired: "Request body must include a `changes` array of `{ id, enabled?, settings? }` entries.",
|
|
25299
|
+
pluginsChangeMalformed: "Each entry in `changes` must have a string `id` plus at least one of `enabled` (boolean) or `settings` (object).",
|
|
25300
|
+
// 400, a bulk change carries `settings` against a bare plugin id.
|
|
25301
|
+
// Settings are per-extension, so they require a qualified
|
|
25302
|
+
// `<plugin>/<ext>` id; a bare plugin id is the wrong granularity.
|
|
25303
|
+
pluginsSettingsRequireQualifiedId: 'Settings can only be written on a qualified `<plugin>/<ext>` id, not the bare plugin id "{{id}}".',
|
|
25304
|
+
// 400, a bulk change carries `settings` for an extension that declares
|
|
25305
|
+
// none in its manifest.
|
|
25306
|
+
pluginsSettingsNoneDeclared: 'Extension "{{pluginId}}/{{extensionId}}" declares no configurable settings.',
|
|
25307
|
+
// 400, a `settings` entry names a settingId the extension does not
|
|
25308
|
+
// declare, or its value fails the declared input-type's rules. The
|
|
25309
|
+
// `{{reason}}` is the resolver's per-type validation message.
|
|
25310
|
+
pluginsSettingsInvalid: 'Invalid setting "{{settingId}}" for "{{pluginId}}/{{extensionId}}": {{reason}}.',
|
|
25311
|
+
// 500, a settings write failed at persist time (AJV revalidation of
|
|
25312
|
+
// the merged config file rejected the result).
|
|
25313
|
+
pluginsSettingsPersistFailed: 'Failed to persist settings for "{{id}}": {{message}}.',
|
|
24806
25314
|
// ---- preferences route (routes/preferences.ts) --------------------------
|
|
24807
25315
|
//
|
|
24808
25316
|
// GET / PATCH /api/preferences. The PATCH body is shaped
|
|
@@ -25316,13 +25824,15 @@ function registerGraphRoute(app, deps) {
|
|
|
25316
25824
|
}
|
|
25317
25825
|
function renderGraphPayload(formatter, loaded) {
|
|
25318
25826
|
const scan = loaded ?? { nodes: [], links: [], issues: [] };
|
|
25827
|
+
const settings2 = formatter.resolvedSettings ?? {};
|
|
25319
25828
|
if (loaded === null) {
|
|
25320
|
-
return formatter.format({ nodes: scan.nodes, links: scan.links, issues: scan.issues });
|
|
25829
|
+
return formatter.format({ nodes: scan.nodes, links: scan.links, issues: scan.issues, settings: settings2 });
|
|
25321
25830
|
}
|
|
25322
25831
|
return formatter.format({
|
|
25323
25832
|
nodes: scan.nodes,
|
|
25324
25833
|
links: scan.links,
|
|
25325
25834
|
issues: scan.issues,
|
|
25835
|
+
settings: settings2,
|
|
25326
25836
|
scanResult: loaded
|
|
25327
25837
|
});
|
|
25328
25838
|
}
|
|
@@ -25782,6 +26292,72 @@ function normalizeArrayIndices(path) {
|
|
|
25782
26292
|
return path.replace(/\/\d+(?=\/|$)/g, "/*");
|
|
25783
26293
|
}
|
|
25784
26294
|
|
|
26295
|
+
// server/routes/plugins-settings.ts
|
|
26296
|
+
function readManifestSettings(manifestLike) {
|
|
26297
|
+
if (manifestLike === null || typeof manifestLike !== "object") return void 0;
|
|
26298
|
+
const settings2 = manifestLike.settings;
|
|
26299
|
+
if (settings2 && typeof settings2 === "object" && !Array.isArray(settings2)) {
|
|
26300
|
+
return settings2;
|
|
26301
|
+
}
|
|
26302
|
+
return void 0;
|
|
26303
|
+
}
|
|
26304
|
+
function projectExtensionSettings(pluginId, extId, declarations, config) {
|
|
26305
|
+
if (!declarations || Object.keys(declarations).length === 0) return {};
|
|
26306
|
+
const settings2 = Object.entries(declarations).map(
|
|
26307
|
+
([id, declaration]) => ({ ...declaration, id })
|
|
26308
|
+
);
|
|
26309
|
+
const ref = { pluginId, id: extId, settings: declarations };
|
|
26310
|
+
const resolved = resolveExtensionSettings(ref, config, () => {
|
|
26311
|
+
});
|
|
26312
|
+
const settingValues = {};
|
|
26313
|
+
const secretSet = [];
|
|
26314
|
+
for (const [id, declaration] of Object.entries(declarations)) {
|
|
26315
|
+
if (declaration.type === "secret") {
|
|
26316
|
+
if (Object.prototype.hasOwnProperty.call(resolved, id)) secretSet.push(id);
|
|
26317
|
+
continue;
|
|
26318
|
+
}
|
|
26319
|
+
if (Object.prototype.hasOwnProperty.call(resolved, id)) {
|
|
26320
|
+
settingValues[id] = resolved[id];
|
|
26321
|
+
}
|
|
26322
|
+
}
|
|
26323
|
+
return {
|
|
26324
|
+
settings: settings2,
|
|
26325
|
+
settingValues,
|
|
26326
|
+
...secretSet.length > 0 ? { secretSettingsSet: secretSet } : {}
|
|
26327
|
+
};
|
|
26328
|
+
}
|
|
26329
|
+
function validateSettingsPatch(pluginId, extId, declarations, patch) {
|
|
26330
|
+
for (const [settingId, value] of Object.entries(patch)) {
|
|
26331
|
+
const declaration = declarations?.[settingId];
|
|
26332
|
+
if (!declaration) {
|
|
26333
|
+
return {
|
|
26334
|
+
settingId,
|
|
26335
|
+
reason: `extension "${pluginId}/${extId}" declares no setting "${settingId}"`
|
|
26336
|
+
};
|
|
26337
|
+
}
|
|
26338
|
+
let invalidReason = null;
|
|
26339
|
+
resolveExtensionSettings(
|
|
26340
|
+
{ pluginId, id: extId, settings: { [settingId]: declaration } },
|
|
26341
|
+
{ plugins: { [pluginId]: { extensions: { [extId]: { settings: { [settingId]: value } } } } } },
|
|
26342
|
+
(message) => {
|
|
26343
|
+
invalidReason = message;
|
|
26344
|
+
}
|
|
26345
|
+
);
|
|
26346
|
+
if (invalidReason !== null) {
|
|
26347
|
+
return { settingId, reason: invalidReason };
|
|
26348
|
+
}
|
|
26349
|
+
}
|
|
26350
|
+
return null;
|
|
26351
|
+
}
|
|
26352
|
+
function persistSettingsPatch(pluginId, extId, declarations, patch, cwd) {
|
|
26353
|
+
for (const [settingId, value] of Object.entries(patch)) {
|
|
26354
|
+
const declaration = declarations?.[settingId];
|
|
26355
|
+
const target = declaration?.type === "secret" ? "project-local" : "project";
|
|
26356
|
+
const dotKey = `plugins.${pluginId}.extensions.${extId}.settings.${settingId}`;
|
|
26357
|
+
writeConfigValue(dotKey, value, { target, cwd });
|
|
26358
|
+
}
|
|
26359
|
+
}
|
|
26360
|
+
|
|
25785
26361
|
// server/routes/plugins.ts
|
|
25786
26362
|
var SINGLE_PATCH_BODY_SCHEMA = {
|
|
25787
26363
|
type: "object",
|
|
@@ -25811,10 +26387,18 @@ var BULK_PATCH_BODY_SCHEMA = {
|
|
|
25811
26387
|
items: {
|
|
25812
26388
|
type: "object",
|
|
25813
26389
|
additionalProperties: false,
|
|
25814
|
-
|
|
26390
|
+
// `id` is the only mandatory field; a change carries `enabled`,
|
|
26391
|
+
// `settings`, or both. `minProperties: 2` enforces at least one
|
|
26392
|
+
// of the two beyond `id` (an `{ id }`-only entry is a no-op the
|
|
26393
|
+
// client should not send). Per-setting type validation runs in
|
|
26394
|
+
// code against the manifest, so `settings` is a permissive
|
|
26395
|
+
// object here (the body schema only fixes its container shape).
|
|
26396
|
+
required: ["id"],
|
|
26397
|
+
minProperties: 2,
|
|
25815
26398
|
properties: {
|
|
25816
26399
|
id: { type: "string", minLength: 1 },
|
|
25817
|
-
enabled: { type: "boolean" }
|
|
26400
|
+
enabled: { type: "boolean" },
|
|
26401
|
+
settings: { type: "object" }
|
|
25818
26402
|
}
|
|
25819
26403
|
}
|
|
25820
26404
|
}
|
|
@@ -25826,7 +26410,13 @@ var parseBulkPatchBody = makeBodyValidator(BULK_PATCH_BODY_SCHEMA, {
|
|
|
25826
26410
|
invalid: SERVER_TEXTS.pluginsChangeMalformed,
|
|
25827
26411
|
mapping: {
|
|
25828
26412
|
"/changes:required": SERVER_TEXTS.pluginsChangesRequired,
|
|
25829
|
-
"/changes:type:array": SERVER_TEXTS.pluginsChangesRequired
|
|
26413
|
+
"/changes:type:array": SERVER_TEXTS.pluginsChangesRequired,
|
|
26414
|
+
// A change with neither `enabled` nor `settings` (just `id`) trips
|
|
26415
|
+
// `minProperties`; surface the same "malformed entry" message.
|
|
26416
|
+
"/changes/*:minProperties": SERVER_TEXTS.pluginsChangeMalformed,
|
|
26417
|
+
"/changes/*:type:object": SERVER_TEXTS.pluginsChangeMalformed,
|
|
26418
|
+
"/changes/*/settings:type:object": SERVER_TEXTS.pluginsChangeMalformed,
|
|
26419
|
+
"/changes/*/enabled:type:boolean": SERVER_TEXTS.pluginsChangeMalformed
|
|
25830
26420
|
}
|
|
25831
26421
|
});
|
|
25832
26422
|
function registerPluginsRoute(app, deps) {
|
|
@@ -25910,25 +26500,33 @@ function registerPluginsRoute(app, deps) {
|
|
|
25910
26500
|
});
|
|
25911
26501
|
}
|
|
25912
26502
|
function listItems(deps, resolveEnabled) {
|
|
26503
|
+
const config = deps.configService.effective();
|
|
25913
26504
|
return [
|
|
25914
|
-
...deps.options.noBuiltIns ? [] : buildBuiltInItems(resolveEnabled),
|
|
25915
|
-
...buildDiscoveredItems(deps.pluginRuntime.discovered, deps, resolveEnabled)
|
|
26505
|
+
...deps.options.noBuiltIns ? [] : buildBuiltInItems(resolveEnabled, config),
|
|
26506
|
+
...buildDiscoveredItems(deps.pluginRuntime.discovered, deps, resolveEnabled, config)
|
|
25916
26507
|
];
|
|
25917
26508
|
}
|
|
25918
|
-
function buildBuiltInItems(resolveEnabled) {
|
|
26509
|
+
function buildBuiltInItems(resolveEnabled, config) {
|
|
25919
26510
|
return sortPluginsForPresentation(builtInPlugins).map((plugin) => {
|
|
25920
26511
|
const pluginLocked = isPluginLocked(plugin.id);
|
|
25921
26512
|
const extensions = plugin.extensions.map((ext) => {
|
|
25922
26513
|
const qualified = qualifiedExtensionId(plugin.id, ext.id);
|
|
25923
26514
|
const extLocked = pluginLocked || isPluginLocked(qualified);
|
|
26515
|
+
const settings2 = projectExtensionSettings(
|
|
26516
|
+
plugin.id,
|
|
26517
|
+
ext.id,
|
|
26518
|
+
readManifestSettings(ext),
|
|
26519
|
+
config
|
|
26520
|
+
);
|
|
25924
26521
|
return {
|
|
25925
26522
|
id: ext.id,
|
|
25926
26523
|
kind: ext.kind,
|
|
25927
26524
|
version: ext.version,
|
|
25928
|
-
enabled: resolveEnabled(qualified),
|
|
26525
|
+
enabled: resolveEnabled(qualified, installedDefaultEnabled(ext.stability)),
|
|
25929
26526
|
...ext.description ? { description: ext.description } : {},
|
|
25930
26527
|
...ext.stability ? { stability: ext.stability } : {},
|
|
25931
|
-
...extLocked ? { locked: true } : {}
|
|
26528
|
+
...extLocked ? { locked: true } : {},
|
|
26529
|
+
...settings2
|
|
25932
26530
|
};
|
|
25933
26531
|
});
|
|
25934
26532
|
const pluginEnabled = extensions.some((e) => e.enabled);
|
|
@@ -25945,12 +26543,12 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
25945
26543
|
};
|
|
25946
26544
|
});
|
|
25947
26545
|
}
|
|
25948
|
-
function buildDiscoveredItems(discovered, deps, resolveEnabled) {
|
|
25949
|
-
return discovered.map((plugin) => buildDiscoveredItem(plugin, deps, resolveEnabled));
|
|
26546
|
+
function buildDiscoveredItems(discovered, deps, resolveEnabled, config) {
|
|
26547
|
+
return discovered.map((plugin) => buildDiscoveredItem(plugin, deps, resolveEnabled, config));
|
|
25950
26548
|
}
|
|
25951
|
-
function buildDiscoveredItem(plugin, deps, resolveEnabled) {
|
|
26549
|
+
function buildDiscoveredItem(plugin, deps, resolveEnabled, config) {
|
|
25952
26550
|
const pluginLocked = isPluginLocked(plugin.id);
|
|
25953
|
-
const extensions = projectExtensionRows(plugin, resolveEnabled, pluginLocked);
|
|
26551
|
+
const extensions = projectExtensionRows(plugin, resolveEnabled, pluginLocked, config);
|
|
25954
26552
|
const optional = optionalDiscoveredFields(plugin, extensions);
|
|
25955
26553
|
return {
|
|
25956
26554
|
id: plugin.id,
|
|
@@ -25971,20 +26569,27 @@ function optionalDiscoveredFields(plugin, extensions) {
|
|
|
25971
26569
|
if (extensions) out.extensions = extensions;
|
|
25972
26570
|
return out;
|
|
25973
26571
|
}
|
|
25974
|
-
function projectExtensionRows(plugin, resolveEnabled, pluginLocked) {
|
|
26572
|
+
function projectExtensionRows(plugin, resolveEnabled, pluginLocked, config) {
|
|
25975
26573
|
if (!plugin.extensions || plugin.extensions.length === 0) return void 0;
|
|
25976
26574
|
return plugin.extensions.map((ext) => {
|
|
25977
26575
|
const description = readInstanceDescription(ext.instance);
|
|
25978
26576
|
const qualified = qualifiedExtensionId(plugin.id, ext.id);
|
|
25979
26577
|
const extLocked = pluginLocked || isPluginLocked(qualified);
|
|
26578
|
+
const settings2 = projectExtensionSettings(
|
|
26579
|
+
plugin.id,
|
|
26580
|
+
ext.id,
|
|
26581
|
+
readManifestSettings(ext.instance),
|
|
26582
|
+
config
|
|
26583
|
+
);
|
|
25980
26584
|
return {
|
|
25981
26585
|
id: ext.id,
|
|
25982
26586
|
kind: ext.kind,
|
|
25983
26587
|
version: ext.version,
|
|
25984
|
-
enabled: resolveEnabled(qualified),
|
|
26588
|
+
enabled: resolveEnabled(qualified, installedDefaultEnabled(ext.stability)),
|
|
25985
26589
|
...description ? { description } : {},
|
|
25986
26590
|
...ext.stability ? { stability: ext.stability } : {},
|
|
25987
|
-
...extLocked ? { locked: true } : {}
|
|
26591
|
+
...extLocked ? { locked: true } : {},
|
|
26592
|
+
...settings2
|
|
25988
26593
|
};
|
|
25989
26594
|
});
|
|
25990
26595
|
}
|
|
@@ -26102,25 +26707,35 @@ function projectListResponse(c, deps, overrides) {
|
|
|
26102
26707
|
);
|
|
26103
26708
|
}
|
|
26104
26709
|
function validateBulkChange(change, deps) {
|
|
26105
|
-
|
|
26106
|
-
|
|
26107
|
-
|
|
26108
|
-
|
|
26109
|
-
|
|
26110
|
-
|
|
26111
|
-
|
|
26112
|
-
|
|
26113
|
-
|
|
26114
|
-
|
|
26115
|
-
|
|
26116
|
-
|
|
26117
|
-
|
|
26118
|
-
|
|
26119
|
-
|
|
26120
|
-
}
|
|
26121
|
-
}
|
|
26122
|
-
|
|
26710
|
+
return change.id.includes("/") ? validateQualifiedBulkChange(change, deps) : validateBareBulkChange(change, deps);
|
|
26711
|
+
}
|
|
26712
|
+
function validateBareBulkChange(change, deps) {
|
|
26713
|
+
if (change.settings !== void 0) {
|
|
26714
|
+
return {
|
|
26715
|
+
status: 400,
|
|
26716
|
+
code: "bad-query",
|
|
26717
|
+
message: tx(SERVER_TEXTS.pluginsSettingsRequireQualifiedId, { id: change.id })
|
|
26718
|
+
};
|
|
26719
|
+
}
|
|
26720
|
+
const handle = findHandle(change.id, deps);
|
|
26721
|
+
if (!handle) {
|
|
26722
|
+
return {
|
|
26723
|
+
status: 404,
|
|
26724
|
+
code: "not-found",
|
|
26725
|
+
message: tx(SERVER_TEXTS.pluginsUnknown, { id: change.id })
|
|
26726
|
+
};
|
|
26727
|
+
}
|
|
26728
|
+
if (isPluginLocked(change.id)) {
|
|
26729
|
+
return {
|
|
26730
|
+
status: 403,
|
|
26731
|
+
code: "locked",
|
|
26732
|
+
message: tx(SERVER_TEXTS.pluginsLocked, { id: change.id })
|
|
26733
|
+
};
|
|
26123
26734
|
}
|
|
26735
|
+
return null;
|
|
26736
|
+
}
|
|
26737
|
+
function validateQualifiedBulkChange(change, deps) {
|
|
26738
|
+
const slash = change.id.indexOf("/");
|
|
26124
26739
|
const pluginId = change.id.slice(0, slash);
|
|
26125
26740
|
const extensionId = change.id.slice(slash + 1);
|
|
26126
26741
|
const handle = findHandle(pluginId, deps);
|
|
@@ -26145,13 +26760,49 @@ function validateBulkChange(change, deps) {
|
|
|
26145
26760
|
message: tx(SERVER_TEXTS.pluginsExtensionLocked, { pluginId, extensionId })
|
|
26146
26761
|
};
|
|
26147
26762
|
}
|
|
26763
|
+
if (change.settings !== void 0) {
|
|
26764
|
+
return validateChangeSettings(handle, pluginId, extensionId, change.settings);
|
|
26765
|
+
}
|
|
26766
|
+
return null;
|
|
26767
|
+
}
|
|
26768
|
+
function validateChangeSettings(handle, pluginId, extensionId, patch) {
|
|
26769
|
+
const declarations = handleExtensionSettings(handle, extensionId);
|
|
26770
|
+
if (!declarations || Object.keys(declarations).length === 0) {
|
|
26771
|
+
return {
|
|
26772
|
+
status: 400,
|
|
26773
|
+
code: "bad-query",
|
|
26774
|
+
message: tx(SERVER_TEXTS.pluginsSettingsNoneDeclared, { pluginId, extensionId })
|
|
26775
|
+
};
|
|
26776
|
+
}
|
|
26777
|
+
const failure = validateSettingsPatch(pluginId, extensionId, declarations, patch);
|
|
26778
|
+
if (failure !== null) {
|
|
26779
|
+
return {
|
|
26780
|
+
status: 400,
|
|
26781
|
+
code: "bad-query",
|
|
26782
|
+
message: tx(SERVER_TEXTS.pluginsSettingsInvalid, {
|
|
26783
|
+
settingId: failure.settingId,
|
|
26784
|
+
pluginId,
|
|
26785
|
+
extensionId,
|
|
26786
|
+
reason: failure.reason
|
|
26787
|
+
})
|
|
26788
|
+
};
|
|
26789
|
+
}
|
|
26148
26790
|
return null;
|
|
26149
26791
|
}
|
|
26792
|
+
function handleExtensionSettings(handle, extensionId) {
|
|
26793
|
+
if (handle.kind === "built-in") {
|
|
26794
|
+
const ext2 = handle.plugin.extensions.find((e) => e.id === extensionId);
|
|
26795
|
+
return readManifestSettings(ext2);
|
|
26796
|
+
}
|
|
26797
|
+
const ext = (handle.plugin.extensions ?? []).find((e) => e.id === extensionId);
|
|
26798
|
+
return readManifestSettings(ext?.instance);
|
|
26799
|
+
}
|
|
26150
26800
|
async function persistBulkAndProject(c, deps, changes) {
|
|
26151
26801
|
const overrides = await tryWithSqlite(
|
|
26152
26802
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
26153
26803
|
async (adapter) => {
|
|
26154
26804
|
for (const change of changes) {
|
|
26805
|
+
if (change.enabled === void 0) continue;
|
|
26155
26806
|
const writeKeys = expandBulkChangeKeys(change, deps);
|
|
26156
26807
|
for (const key of writeKeys) {
|
|
26157
26808
|
await applyChangeToAdapter(adapter, key, change.enabled);
|
|
@@ -26160,8 +26811,40 @@ async function persistBulkAndProject(c, deps, changes) {
|
|
|
26160
26811
|
return await adapter.pluginConfig.loadOverrideMap();
|
|
26161
26812
|
}
|
|
26162
26813
|
);
|
|
26814
|
+
if (overrides === null) {
|
|
26815
|
+
throw new DbMissingError(
|
|
26816
|
+
tx(SERVER_TEXTS.pluginsDbMissing, { path: deps.options.dbPath })
|
|
26817
|
+
);
|
|
26818
|
+
}
|
|
26819
|
+
const settingsTouched = persistBulkSettings(deps, changes);
|
|
26820
|
+
if (settingsTouched) deps.configService.reload();
|
|
26163
26821
|
return projectListResponse(c, deps, overrides);
|
|
26164
26822
|
}
|
|
26823
|
+
function persistBulkSettings(deps, changes) {
|
|
26824
|
+
const cwd = deps.runtimeContext.cwd;
|
|
26825
|
+
let touched = false;
|
|
26826
|
+
for (const change of changes) {
|
|
26827
|
+
if (change.settings === void 0) continue;
|
|
26828
|
+
const slash = change.id.indexOf("/");
|
|
26829
|
+
if (slash < 0) continue;
|
|
26830
|
+
const pluginId = change.id.slice(0, slash);
|
|
26831
|
+
const extensionId = change.id.slice(slash + 1);
|
|
26832
|
+
const handle = findHandle(pluginId, deps);
|
|
26833
|
+
const declarations = handle ? handleExtensionSettings(handle, extensionId) : void 0;
|
|
26834
|
+
try {
|
|
26835
|
+
persistSettingsPatch(pluginId, extensionId, declarations, change.settings, cwd);
|
|
26836
|
+
} catch (err) {
|
|
26837
|
+
throw new HTTPException9(500, {
|
|
26838
|
+
message: tx(SERVER_TEXTS.pluginsSettingsPersistFailed, {
|
|
26839
|
+
id: change.id,
|
|
26840
|
+
message: err instanceof Error ? err.message : String(err)
|
|
26841
|
+
})
|
|
26842
|
+
});
|
|
26843
|
+
}
|
|
26844
|
+
if (Object.keys(change.settings).length > 0) touched = true;
|
|
26845
|
+
}
|
|
26846
|
+
return touched;
|
|
26847
|
+
}
|
|
26165
26848
|
function expandBulkChangeKeys(change, deps) {
|
|
26166
26849
|
if (change.id.includes("/")) return [change.id];
|
|
26167
26850
|
const handle = findHandle(change.id, deps);
|
|
@@ -28513,7 +29196,7 @@ function formatCwdPath(cwd) {
|
|
|
28513
29196
|
// cli/commands/serve.ts
|
|
28514
29197
|
var ServeCommand = class extends SmCommand {
|
|
28515
29198
|
static paths = [["serve"]];
|
|
28516
|
-
static usage =
|
|
29199
|
+
static usage = Command34.Usage({
|
|
28517
29200
|
category: "Setup",
|
|
28518
29201
|
description: "Start the Hono BFF (single-port: REST + WebSocket + SPA bundle).",
|
|
28519
29202
|
details: `
|
|
@@ -28537,18 +29220,18 @@ var ServeCommand = class extends SmCommand {
|
|
|
28537
29220
|
["Point at a pre-built UI bundle", "$0 serve --ui-dist ./ui/dist/browser"]
|
|
28538
29221
|
]
|
|
28539
29222
|
});
|
|
28540
|
-
port =
|
|
29223
|
+
port = Option32.String("--port", {
|
|
28541
29224
|
required: false,
|
|
28542
29225
|
description: "Listening port (default 4242). 0 = OS-assigned."
|
|
28543
29226
|
});
|
|
28544
|
-
host =
|
|
29227
|
+
host = Option32.String("--host", {
|
|
28545
29228
|
required: false,
|
|
28546
29229
|
description: "Listening host (default 127.0.0.1). Loopback-only enforced when --dev-cors is set."
|
|
28547
29230
|
});
|
|
28548
|
-
noBuiltIns =
|
|
29231
|
+
noBuiltIns = Option32.Boolean("--no-built-ins", false, {
|
|
28549
29232
|
description: "Skip built-in plugin registration (parity with sm scan --no-built-ins)."
|
|
28550
29233
|
});
|
|
28551
|
-
noPlugins =
|
|
29234
|
+
noPlugins = Option32.Boolean("--no-plugins", false, {
|
|
28552
29235
|
description: "Skip drop-in plugin discovery."
|
|
28553
29236
|
});
|
|
28554
29237
|
// `Option.Boolean('--open', true)`, Clipanion's parser auto-derives
|
|
@@ -28558,31 +29241,31 @@ var ServeCommand = class extends SmCommand {
|
|
|
28558
29241
|
// two registrations for the same flag and rejects the invocation
|
|
28559
29242
|
// with "Ambiguous Syntax Error". Same convention shipped by every
|
|
28560
29243
|
// other `--no-...` flag in the CLI tree.
|
|
28561
|
-
open =
|
|
29244
|
+
open = Option32.Boolean("--open", true, {
|
|
28562
29245
|
description: "Auto-open the SPA in the user's default browser after listen. --no-open opts out."
|
|
28563
29246
|
});
|
|
28564
|
-
devCors =
|
|
29247
|
+
devCors = Option32.Boolean("--dev-cors", false, {
|
|
28565
29248
|
description: "Enable permissive CORS for the Angular dev-server proxy workflow."
|
|
28566
29249
|
});
|
|
28567
29250
|
// `--ui-dist` is intentionally undocumented in the Usage block above
|
|
28568
29251
|
// (the demo build pipeline + tests rely on it; everyday users never
|
|
28569
29252
|
// need it). Clipanion still exposes it on the parser; the Usage
|
|
28570
29253
|
// omission is the "hidden" contract per the 14.1 brief.
|
|
28571
|
-
uiDist =
|
|
28572
|
-
noUi =
|
|
29254
|
+
uiDist = Option32.String("--ui-dist", { required: false, hidden: true });
|
|
29255
|
+
noUi = Option32.Boolean("--no-ui", false, {
|
|
28573
29256
|
description: "Don't serve the Angular UI bundle. Use this when running the BFF alongside `ui:dev` (Angular dev server with HMR). The root `/` then renders an inline placeholder pointing the user at the dev server."
|
|
28574
29257
|
});
|
|
28575
|
-
noWatcher =
|
|
29258
|
+
noWatcher = Option32.Boolean("--no-watcher", false, {
|
|
28576
29259
|
description: "Disable the chokidar-fed scan-and-broadcast loop. Use only for CI / read-only deployments."
|
|
28577
29260
|
});
|
|
28578
|
-
yes =
|
|
29261
|
+
yes = Option32.Boolean("--yes", false, {
|
|
28579
29262
|
description: "Skip the interactive prompt and rebuild the local cache when the on-disk DB has drifted (version skew or an inline schema change). Non-TTY invocations rebuild without asking regardless of this flag."
|
|
28580
29263
|
});
|
|
28581
29264
|
// `--watcher-debounce-ms` is undocumented sugar for advanced users
|
|
28582
29265
|
// who want to tighten / relax the watcher's batching window without
|
|
28583
29266
|
// editing settings.json. Hidden flag, the Usage block omits it.
|
|
28584
|
-
watcherDebounceMs =
|
|
28585
|
-
maxNodes =
|
|
29267
|
+
watcherDebounceMs = Option32.String("--watcher-debounce-ms", { required: false, hidden: true });
|
|
29268
|
+
maxNodes = Option32.String("--max-nodes", {
|
|
28586
29269
|
required: false,
|
|
28587
29270
|
description: "Per-invocation override of scan.maxNodes (default 256). Bidirectional: raises OR lowers the recommended cap on classified nodes. Applies to every scan the server runs (initial watcher pass, debounced batches, POST /api/scan, GET /api/scan?fresh=1). Same flag is honoured on the bare `sm` invocation, which routes to `sm serve`."
|
|
28588
29271
|
});
|
|
@@ -28926,7 +29609,7 @@ function tryOpenBrowser(url, stderr, warnGlyph) {
|
|
|
28926
29609
|
}
|
|
28927
29610
|
|
|
28928
29611
|
// cli/commands/show.ts
|
|
28929
|
-
import { Command as
|
|
29612
|
+
import { Command as Command35, Option as Option33 } from "clipanion";
|
|
28930
29613
|
|
|
28931
29614
|
// cli/i18n/show.texts.ts
|
|
28932
29615
|
var SHOW_TEXTS = {
|
|
@@ -28977,7 +29660,7 @@ var SHOW_TEXTS = {
|
|
|
28977
29660
|
// cli/commands/show.ts
|
|
28978
29661
|
var ShowCommand = class extends SmCommand {
|
|
28979
29662
|
static paths = [["show"]];
|
|
28980
|
-
static usage =
|
|
29663
|
+
static usage = Command35.Usage({
|
|
28981
29664
|
category: "Browse",
|
|
28982
29665
|
description: "Node detail: weight, frontmatter, links, issues.",
|
|
28983
29666
|
details: `
|
|
@@ -28993,7 +29676,7 @@ var ShowCommand = class extends SmCommand {
|
|
|
28993
29676
|
["Machine-readable detail", "$0 show .claude/agents/architect.md --json"]
|
|
28994
29677
|
]
|
|
28995
29678
|
});
|
|
28996
|
-
nodePath =
|
|
29679
|
+
nodePath = Option33.String({ required: true });
|
|
28997
29680
|
async run() {
|
|
28998
29681
|
const dbPath = resolveDbPath({ db: this.db, ...defaultRuntimeContext() });
|
|
28999
29682
|
const exit = requireDbOrExit(dbPath, this.context.stderr);
|
|
@@ -29235,7 +29918,7 @@ function rankConfidenceForGrouping(c) {
|
|
|
29235
29918
|
// cli/commands/sidecar.ts
|
|
29236
29919
|
import { unlink as unlink3 } from "fs/promises";
|
|
29237
29920
|
import { resolve as resolve38 } from "path";
|
|
29238
|
-
import { Command as
|
|
29921
|
+
import { Command as Command36, Option as Option34 } from "clipanion";
|
|
29239
29922
|
|
|
29240
29923
|
// cli/i18n/sidecar.texts.ts
|
|
29241
29924
|
var SIDECAR_TEXTS = {
|
|
@@ -29316,7 +29999,7 @@ async function runWithSidecarConsent(bag, ansi, dispatch) {
|
|
|
29316
29999
|
}
|
|
29317
30000
|
var SidecarRefreshCommand = class extends SmCommand {
|
|
29318
30001
|
static paths = [["sidecar", "refresh"]];
|
|
29319
|
-
static usage =
|
|
30002
|
+
static usage = Command36.Usage({
|
|
29320
30003
|
category: "Actions",
|
|
29321
30004
|
description: "Refresh a sidecar's `for.{bodyHash, frontmatterHash}` to match the live node. Does NOT bump the version.",
|
|
29322
30005
|
details: `
|
|
@@ -29333,8 +30016,8 @@ var SidecarRefreshCommand = class extends SmCommand {
|
|
|
29333
30016
|
["Refresh a node's sidecar hashes", "$0 sidecar refresh .claude/agents/architect.md"]
|
|
29334
30017
|
]
|
|
29335
30018
|
});
|
|
29336
|
-
nodePath =
|
|
29337
|
-
yes =
|
|
30019
|
+
nodePath = Option34.String({ required: true });
|
|
30020
|
+
yes = Option34.Boolean("--yes", false, {
|
|
29338
30021
|
description: "Confirm writing .sm sidecar files in this project (sets allowEditSmFiles=true on first run)."
|
|
29339
30022
|
});
|
|
29340
30023
|
async run() {
|
|
@@ -29456,7 +30139,7 @@ var SidecarRefreshCommand = class extends SmCommand {
|
|
|
29456
30139
|
};
|
|
29457
30140
|
var SidecarPruneCommand = class extends SmCommand {
|
|
29458
30141
|
static paths = [["sidecar", "prune"]];
|
|
29459
|
-
static usage =
|
|
30142
|
+
static usage = Command36.Usage({
|
|
29460
30143
|
category: "Actions",
|
|
29461
30144
|
description: "Delete orphan .sm files (sidecars whose accompanying .md no longer exists).",
|
|
29462
30145
|
details: `
|
|
@@ -29478,8 +30161,8 @@ var SidecarPruneCommand = class extends SmCommand {
|
|
|
29478
30161
|
["Delete every orphan .sm file (non-interactive)", "$0 sidecar prune --yes"]
|
|
29479
30162
|
]
|
|
29480
30163
|
});
|
|
29481
|
-
dryRun =
|
|
29482
|
-
yes =
|
|
30164
|
+
dryRun = Option34.Boolean("-n,--dry-run", false);
|
|
30165
|
+
yes = Option34.Boolean("--yes,--force", false, {
|
|
29483
30166
|
description: "Skip the interactive confirmation prompt. Required for non-interactive callers (CI, pre-commit hooks)."
|
|
29484
30167
|
});
|
|
29485
30168
|
// Complexity is from per-orphan handling, empty-set / dry-run /
|
|
@@ -29599,7 +30282,7 @@ var SidecarPruneCommand = class extends SmCommand {
|
|
|
29599
30282
|
};
|
|
29600
30283
|
var SidecarAnnotateCommand = class extends SmCommand {
|
|
29601
30284
|
static paths = [["sidecar", "annotate"]];
|
|
29602
|
-
static usage =
|
|
30285
|
+
static usage = Command36.Usage({
|
|
29603
30286
|
category: "Actions",
|
|
29604
30287
|
description: "Scaffold an empty `<basename>.sm` next to a node ready for editing.",
|
|
29605
30288
|
details: `
|
|
@@ -29617,9 +30300,9 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
29617
30300
|
["Overwrite an existing one", "$0 sidecar annotate .claude/agents/architect.md --force"]
|
|
29618
30301
|
]
|
|
29619
30302
|
});
|
|
29620
|
-
nodePath =
|
|
29621
|
-
force =
|
|
29622
|
-
yes =
|
|
30303
|
+
nodePath = Option34.String({ required: true });
|
|
30304
|
+
force = Option34.Boolean("--force", false);
|
|
30305
|
+
yes = Option34.Boolean("--yes", false, {
|
|
29623
30306
|
description: "Confirm writing .sm sidecar files in this project (sets allowEditSmFiles=true on first run)."
|
|
29624
30307
|
});
|
|
29625
30308
|
async run() {
|
|
@@ -29758,7 +30441,7 @@ var SIDECAR_COMMANDS = [
|
|
|
29758
30441
|
];
|
|
29759
30442
|
|
|
29760
30443
|
// cli/commands/stubs.ts
|
|
29761
|
-
import { Command as
|
|
30444
|
+
import { Command as Command37, Option as Option35 } from "clipanion";
|
|
29762
30445
|
|
|
29763
30446
|
// cli/i18n/stubs.texts.ts
|
|
29764
30447
|
var STUBS_TEXTS = {
|
|
@@ -29784,7 +30467,7 @@ var StubCommand = class extends SmCommand {
|
|
|
29784
30467
|
};
|
|
29785
30468
|
var DoctorCommand = class extends StubCommand {
|
|
29786
30469
|
static paths = [["doctor"]];
|
|
29787
|
-
static usage =
|
|
30470
|
+
static usage = Command37.Usage({
|
|
29788
30471
|
category: "Setup",
|
|
29789
30472
|
description: planned("Diagnostic report: DB integrity, pending migrations, orphan rows, plugin status, runner availability.")
|
|
29790
30473
|
});
|
|
@@ -29792,18 +30475,18 @@ var DoctorCommand = class extends StubCommand {
|
|
|
29792
30475
|
};
|
|
29793
30476
|
var FindingsCommand = class extends StubCommand {
|
|
29794
30477
|
static paths = [["findings"]];
|
|
29795
|
-
static usage =
|
|
30478
|
+
static usage = Command37.Usage({
|
|
29796
30479
|
category: "Browse",
|
|
29797
30480
|
description: planned("Probabilistic findings: injection, stale summaries, low confidence.")
|
|
29798
30481
|
});
|
|
29799
|
-
kind =
|
|
29800
|
-
since =
|
|
29801
|
-
threshold =
|
|
30482
|
+
kind = Option35.String("--kind", { required: false });
|
|
30483
|
+
since = Option35.String("--since", { required: false });
|
|
30484
|
+
threshold = Option35.String("--threshold", { required: false });
|
|
29802
30485
|
verbName = "findings";
|
|
29803
30486
|
};
|
|
29804
30487
|
var ActionsListCommand = class extends StubCommand {
|
|
29805
30488
|
static paths = [["actions", "list"]];
|
|
29806
|
-
static usage =
|
|
30489
|
+
static usage = Command37.Usage({
|
|
29807
30490
|
category: "Jobs",
|
|
29808
30491
|
description: planned("Registered action types (manifest view).")
|
|
29809
30492
|
});
|
|
@@ -29811,103 +30494,103 @@ var ActionsListCommand = class extends StubCommand {
|
|
|
29811
30494
|
};
|
|
29812
30495
|
var ActionsShowCommand = class extends StubCommand {
|
|
29813
30496
|
static paths = [["actions", "show"]];
|
|
29814
|
-
static usage =
|
|
30497
|
+
static usage = Command37.Usage({
|
|
29815
30498
|
category: "Jobs",
|
|
29816
30499
|
description: planned("Full action manifest, including preconditions and expected duration.")
|
|
29817
30500
|
});
|
|
29818
|
-
id =
|
|
30501
|
+
id = Option35.String({ required: true });
|
|
29819
30502
|
verbName = "actions show";
|
|
29820
30503
|
};
|
|
29821
30504
|
var JobSubmitCommand = class extends StubCommand {
|
|
29822
30505
|
static paths = [["job", "submit"]];
|
|
29823
|
-
static usage =
|
|
30506
|
+
static usage = Command37.Usage({
|
|
29824
30507
|
category: "Jobs",
|
|
29825
30508
|
description: planned("Enqueue a single job or fan out to every matching node (--all).")
|
|
29826
30509
|
});
|
|
29827
|
-
action =
|
|
29828
|
-
node =
|
|
29829
|
-
all =
|
|
30510
|
+
action = Option35.String({ required: true });
|
|
30511
|
+
node = Option35.String("-n", { required: false });
|
|
30512
|
+
all = Option35.Boolean("--all", false);
|
|
29830
30513
|
// CLI flag stays `--run`; field name is `runFlag` per the
|
|
29831
30514
|
// shadow-avoidance convention documented on `SmCommand`.
|
|
29832
|
-
runFlag =
|
|
29833
|
-
force =
|
|
29834
|
-
ttl =
|
|
29835
|
-
priority =
|
|
30515
|
+
runFlag = Option35.Boolean("--run", false);
|
|
30516
|
+
force = Option35.Boolean("--force", false);
|
|
30517
|
+
ttl = Option35.String("--ttl", { required: false });
|
|
30518
|
+
priority = Option35.String("--priority", { required: false });
|
|
29836
30519
|
verbName = "job submit";
|
|
29837
30520
|
};
|
|
29838
30521
|
var JobListCommand = class extends StubCommand {
|
|
29839
30522
|
static paths = [["job", "list"]];
|
|
29840
|
-
static usage =
|
|
29841
|
-
status =
|
|
29842
|
-
action =
|
|
29843
|
-
node =
|
|
30523
|
+
static usage = Command37.Usage({ category: "Jobs", description: planned("List jobs.") });
|
|
30524
|
+
status = Option35.String("--status", { required: false });
|
|
30525
|
+
action = Option35.String("--action", { required: false });
|
|
30526
|
+
node = Option35.String("--node", { required: false });
|
|
29844
30527
|
verbName = "job list";
|
|
29845
30528
|
};
|
|
29846
30529
|
var JobShowCommand = class extends StubCommand {
|
|
29847
30530
|
static paths = [["job", "show"]];
|
|
29848
|
-
static usage =
|
|
29849
|
-
id =
|
|
30531
|
+
static usage = Command37.Usage({ category: "Jobs", description: planned("Job detail: state, claim time, TTL, runner, content hash.") });
|
|
30532
|
+
id = Option35.String({ required: true });
|
|
29850
30533
|
verbName = "job show";
|
|
29851
30534
|
};
|
|
29852
30535
|
var JobPreviewCommand = class extends StubCommand {
|
|
29853
30536
|
static paths = [["job", "preview"]];
|
|
29854
|
-
static usage =
|
|
29855
|
-
id =
|
|
30537
|
+
static usage = Command37.Usage({ category: "Jobs", description: planned("Render the job MD file without executing.") });
|
|
30538
|
+
id = Option35.String({ required: true });
|
|
29856
30539
|
verbName = "job preview";
|
|
29857
30540
|
};
|
|
29858
30541
|
var JobClaimCommand = class extends StubCommand {
|
|
29859
30542
|
static paths = [["job", "claim"]];
|
|
29860
|
-
static usage =
|
|
30543
|
+
static usage = Command37.Usage({
|
|
29861
30544
|
category: "Jobs",
|
|
29862
30545
|
description: planned("Atomic primitive: return next queued job id, mark it running.")
|
|
29863
30546
|
});
|
|
29864
|
-
filter =
|
|
30547
|
+
filter = Option35.String("--filter", { required: false });
|
|
29865
30548
|
verbName = "job claim";
|
|
29866
30549
|
};
|
|
29867
30550
|
var JobRunCommand = class extends StubCommand {
|
|
29868
30551
|
static paths = [["job", "run"]];
|
|
29869
|
-
static usage =
|
|
30552
|
+
static usage = Command37.Usage({
|
|
29870
30553
|
category: "Jobs",
|
|
29871
30554
|
description: planned("Full CLI-runner loop: claim + spawn + record.")
|
|
29872
30555
|
});
|
|
29873
|
-
all =
|
|
29874
|
-
max =
|
|
30556
|
+
all = Option35.Boolean("--all", false);
|
|
30557
|
+
max = Option35.String("--max", { required: false });
|
|
29875
30558
|
verbName = "job run";
|
|
29876
30559
|
};
|
|
29877
30560
|
var JobStatusCommand = class extends StubCommand {
|
|
29878
30561
|
static paths = [["job", "status"]];
|
|
29879
|
-
static usage =
|
|
30562
|
+
static usage = Command37.Usage({
|
|
29880
30563
|
category: "Jobs",
|
|
29881
30564
|
description: planned("Counts (per status) or single-job status.")
|
|
29882
30565
|
});
|
|
29883
|
-
id =
|
|
30566
|
+
id = Option35.String({ required: false });
|
|
29884
30567
|
verbName = "job status";
|
|
29885
30568
|
};
|
|
29886
30569
|
var JobCancelCommand = class extends StubCommand {
|
|
29887
30570
|
static paths = [["job", "cancel"]];
|
|
29888
|
-
static usage =
|
|
30571
|
+
static usage = Command37.Usage({
|
|
29889
30572
|
category: "Jobs",
|
|
29890
30573
|
description: planned("Force a running job to failed with reason user-cancelled.")
|
|
29891
30574
|
});
|
|
29892
|
-
id =
|
|
29893
|
-
all =
|
|
30575
|
+
id = Option35.String({ required: false });
|
|
30576
|
+
all = Option35.Boolean("--all", false);
|
|
29894
30577
|
verbName = "job cancel";
|
|
29895
30578
|
};
|
|
29896
30579
|
var RecordCommand = class extends StubCommand {
|
|
29897
30580
|
static paths = [["record"]];
|
|
29898
|
-
static usage =
|
|
30581
|
+
static usage = Command37.Usage({
|
|
29899
30582
|
category: "Jobs",
|
|
29900
30583
|
description: planned("Close a running job with success or failure. Nonce is the sole credential.")
|
|
29901
30584
|
});
|
|
29902
|
-
id =
|
|
29903
|
-
nonce =
|
|
29904
|
-
status =
|
|
29905
|
-
report =
|
|
29906
|
-
tokensIn =
|
|
29907
|
-
tokensOut =
|
|
29908
|
-
durationMs =
|
|
29909
|
-
model =
|
|
29910
|
-
error =
|
|
30585
|
+
id = Option35.String("--id", { required: true });
|
|
30586
|
+
nonce = Option35.String("--nonce", { required: true });
|
|
30587
|
+
status = Option35.String("--status", { required: true });
|
|
30588
|
+
report = Option35.String("--report", { required: false });
|
|
30589
|
+
tokensIn = Option35.String("--tokens-in", { required: false });
|
|
30590
|
+
tokensOut = Option35.String("--tokens-out", { required: false });
|
|
30591
|
+
durationMs = Option35.String("--duration-ms", { required: false });
|
|
30592
|
+
model = Option35.String("--model", { required: false });
|
|
30593
|
+
error = Option35.String("--error", { required: false });
|
|
29911
30594
|
verbName = "record";
|
|
29912
30595
|
};
|
|
29913
30596
|
var STUB_COMMANDS = [
|
|
@@ -29931,7 +30614,7 @@ import { cpSync as cpSync2, existsSync as existsSync32, mkdirSync as mkdirSync6,
|
|
|
29931
30614
|
import { dirname as dirname20, join as join21, resolve as resolve39 } from "path";
|
|
29932
30615
|
import { createInterface as createInterface5 } from "readline";
|
|
29933
30616
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
29934
|
-
import { Command as
|
|
30617
|
+
import { Command as Command38, Option as Option36 } from "clipanion";
|
|
29935
30618
|
|
|
29936
30619
|
// cli/i18n/tutorial.texts.ts
|
|
29937
30620
|
var TUTORIAL_TEXTS = {
|
|
@@ -29993,7 +30676,7 @@ var TRIGGER_EN = "run the tutorial";
|
|
|
29993
30676
|
var TRIGGER_ES = "ejecuta el tutorial";
|
|
29994
30677
|
var TutorialCommand = class extends SmCommand {
|
|
29995
30678
|
static paths = [["tutorial"]];
|
|
29996
|
-
static usage =
|
|
30679
|
+
static usage = Command38.Usage({
|
|
29997
30680
|
category: "Setup",
|
|
29998
30681
|
description: "Materialize an interactive tester tutorial as a Claude Code skill folder under `<cwd>/.claude/skills/`.",
|
|
29999
30682
|
details: `
|
|
@@ -30016,15 +30699,15 @@ var TutorialCommand = class extends SmCommand {
|
|
|
30016
30699
|
// more. Accept one so a stale `sm tutorial master` lands on a friendly
|
|
30017
30700
|
// usage error (guarded in `run()`) instead of clipanion's generic
|
|
30018
30701
|
// "extraneous argument" message.
|
|
30019
|
-
legacyPositional =
|
|
30702
|
+
legacyPositional = Option36.String({ required: false });
|
|
30020
30703
|
// Named `forProvider`, NOT `for` (reserved word). The CLI surface stays
|
|
30021
30704
|
// `--for`; selects the destination Provider whose `scaffold.skillDir`
|
|
30022
30705
|
// the skill is materialised under, skipping the interactive prompt.
|
|
30023
|
-
forProvider =
|
|
30706
|
+
forProvider = Option36.String("--for", {
|
|
30024
30707
|
required: false,
|
|
30025
30708
|
description: "Destination provider id (e.g. claude, agent-skills). Skips the prompt."
|
|
30026
30709
|
});
|
|
30027
|
-
force =
|
|
30710
|
+
force = Option36.Boolean("--force", false, {
|
|
30028
30711
|
description: "Overwrite an existing target directory without prompting."
|
|
30029
30712
|
});
|
|
30030
30713
|
async run() {
|
|
@@ -30255,7 +30938,7 @@ function resolveSkillSourceDir() {
|
|
|
30255
30938
|
}
|
|
30256
30939
|
|
|
30257
30940
|
// cli/commands/version.ts
|
|
30258
|
-
import { Command as
|
|
30941
|
+
import { Command as Command39 } from "clipanion";
|
|
30259
30942
|
|
|
30260
30943
|
// cli/i18n/version.texts.ts
|
|
30261
30944
|
var VERSION_TEXTS = {
|
|
@@ -30270,7 +30953,7 @@ var VERSION_TEXTS = {
|
|
|
30270
30953
|
// cli/commands/version.ts
|
|
30271
30954
|
var VersionCommand = class extends SmCommand {
|
|
30272
30955
|
static paths = [["version"]];
|
|
30273
|
-
static usage =
|
|
30956
|
+
static usage = Command39.Usage({
|
|
30274
30957
|
category: "Introspection",
|
|
30275
30958
|
description: "Print the CLI / kernel / spec / runtime / db-schema version matrix."
|
|
30276
30959
|
});
|
|
@@ -30469,4 +31152,4 @@ function resolveBareDefault() {
|
|
|
30469
31152
|
process.exit(ExitCode.Error);
|
|
30470
31153
|
}
|
|
30471
31154
|
//# sourceMappingURL=cli.js.map
|
|
30472
|
-
//# debugId=
|
|
31155
|
+
//# debugId=49fdc2af-2694-5cd0-ac19-33f8d1dffa9e
|