@skill-map/cli 0.37.0 → 0.39.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 +37 -7
- package/dist/cli.js +1587 -1392
- package/dist/cli.js.map +1 -1
- package/dist/conformance/index.d.ts +4 -4
- package/dist/conformance/index.js +48 -2
- package/dist/conformance/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -13
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +38 -29
- package/dist/kernel/index.js +14 -13
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/{chunk-LTQTJU54.js → chunk-AALYQ3RG.js} +13 -13
- package/dist/ui/chunk-CR3AANNX.js +3 -0
- package/dist/ui/{chunk-5CFY2K3Y.js → chunk-JECPBFFX.js} +1 -1
- package/dist/ui/{chunk-S2NIJM7Z.js → chunk-LGLLRAJ6.js} +2 -2
- package/dist/ui/{chunk-UK5YFHL3.js → chunk-NHI5UPNK.js} +5 -5
- package/dist/ui/{chunk-YZ7KCL3G.js → chunk-PVVKVGJ6.js} +1 -1
- package/dist/ui/{chunk-2QZDJSJN.js → chunk-SKA7ZFUX.js} +1 -1
- package/dist/ui/chunk-VDMXHCXR.js +1 -0
- package/dist/ui/{chunk-GX2V3PSZ.js → chunk-VZIYRREA.js} +3 -3
- package/dist/ui/chunk-XRDZZC5F.js +123 -0
- package/dist/ui/chunk-YPO2DTMO.js +317 -0
- package/dist/ui/{chunk-UMCC32EJ.js → chunk-ZYIKNMFV.js} +1 -1
- package/dist/ui/index.html +6 -9
- package/dist/ui/main-TYWMNAII.js +2 -0
- package/package.json +3 -3
- package/dist/ui/chunk-5JBW2LUN.js +0 -2
- package/dist/ui/chunk-DGXHJVQN.js +0 -317
- package/dist/ui/chunk-O2N4CRRW.js +0 -123
- package/dist/ui/chunk-VFYNKTSB.js +0 -1
- package/dist/ui/main-YSELA4XD.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// cli/entry.ts
|
|
2
|
-
import { existsSync as
|
|
2
|
+
import { existsSync as existsSync30 } from "fs";
|
|
3
3
|
import { Builtins, Cli as Cli2 } from "clipanion";
|
|
4
4
|
|
|
5
5
|
// kernel/adapters/in-memory-progress.ts
|
|
@@ -61,7 +61,7 @@ var DuplicateExtensionError = class extends Error {
|
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
var Registry = class {
|
|
64
|
-
/** kind → qualifiedId →
|
|
64
|
+
/** kind → qualifiedId → IExtension. */
|
|
65
65
|
#byKind;
|
|
66
66
|
constructor() {
|
|
67
67
|
this.#byKind = new Map(
|
|
@@ -429,13 +429,20 @@ var command_schema_default = {
|
|
|
429
429
|
properties: {}
|
|
430
430
|
};
|
|
431
431
|
|
|
432
|
+
// plugins/ids.ts
|
|
433
|
+
var CORE_PLUGIN_ID = "core";
|
|
434
|
+
var CLAUDE_PLUGIN_ID = "claude";
|
|
435
|
+
var OPENAI_PLUGIN_ID = "openai";
|
|
436
|
+
var ANTIGRAVITY_PLUGIN_ID = "antigravity";
|
|
437
|
+
var AGENT_SKILLS_PLUGIN_ID = "agent-skills";
|
|
438
|
+
|
|
432
439
|
// plugins/claude/providers/claude/index.ts
|
|
433
440
|
var claudeProvider = {
|
|
434
441
|
id: "claude",
|
|
435
|
-
pluginId:
|
|
442
|
+
pluginId: CLAUDE_PLUGIN_ID,
|
|
436
443
|
kind: "provider",
|
|
437
444
|
version: "1.0.0",
|
|
438
|
-
description: "
|
|
445
|
+
description: "Classifies files under `.claude/{agents,commands,skills}` as Claude Code agents, commands, and skills.",
|
|
439
446
|
// Vendor provider: Claude Code only reads its own `.claude/` territory
|
|
440
447
|
// and ignores `.codex/` / Antigravity layouts at runtime. Gating the
|
|
441
448
|
// classifier behind the active lens prevents the walker from inventing
|
|
@@ -657,9 +664,9 @@ function stripFences(input) {
|
|
|
657
664
|
}
|
|
658
665
|
continue;
|
|
659
666
|
}
|
|
660
|
-
const
|
|
661
|
-
if (
|
|
662
|
-
openFence =
|
|
667
|
+
const open2 = FENCE_RE.exec(line);
|
|
668
|
+
if (open2?.groups) {
|
|
669
|
+
openFence = open2.groups["fence"];
|
|
663
670
|
out.push(blank(line));
|
|
664
671
|
continue;
|
|
665
672
|
}
|
|
@@ -717,10 +724,10 @@ var AT_RE = /(?:^|[^A-Za-z0-9_@])(@(?:\.{1,2}\/|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-
|
|
|
717
724
|
var FILE_EXT_RE = /\.(md|mdx|js|jsx|ts|tsx|json|yml|yaml|toml|txt|html|css|scss|less|py|rb|go|rs|java|c|cpp|h|hpp|sh|sql|svg|png|jpg|jpeg|gif|webp|pdf)$/i;
|
|
718
725
|
var atDirectiveExtractor = {
|
|
719
726
|
id: ID,
|
|
720
|
-
pluginId:
|
|
727
|
+
pluginId: CLAUDE_PLUGIN_ID,
|
|
721
728
|
kind: "extractor",
|
|
722
729
|
version: "1.0.0",
|
|
723
|
-
description: "Detects `@<token>` directives in a node's body using Claude Code
|
|
730
|
+
description: "Detects `@<token>` directives in a node's body using Claude Code rules. A bare handle (e.g. `@team`) becomes a `mentions` link; a file-flavoured token (e.g. `@docs/api.md`, `@./readme.md`) becomes a `references` link.",
|
|
724
731
|
scope: "body",
|
|
725
732
|
precondition: { provider: ["claude"] },
|
|
726
733
|
// eslint-disable-next-line complexity
|
|
@@ -801,15 +808,15 @@ function resolveSourceRelative(sourceDir, bare) {
|
|
|
801
808
|
return pathPosix.normalize(joined);
|
|
802
809
|
}
|
|
803
810
|
|
|
804
|
-
// plugins/claude/extractors/slash/index.ts
|
|
805
|
-
var ID2 = "slash";
|
|
811
|
+
// plugins/claude/extractors/slash-command/index.ts
|
|
812
|
+
var ID2 = "slash-command";
|
|
806
813
|
var SLASH_RE = /(?<![A-Za-z0-9_/.:?#=&])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
|
|
807
|
-
var
|
|
814
|
+
var slashCommandExtractor = {
|
|
808
815
|
id: ID2,
|
|
809
|
-
pluginId:
|
|
816
|
+
pluginId: CLAUDE_PLUGIN_ID,
|
|
810
817
|
kind: "extractor",
|
|
811
818
|
version: "1.0.0",
|
|
812
|
-
description: "
|
|
819
|
+
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.",
|
|
813
820
|
scope: "body",
|
|
814
821
|
precondition: { provider: ["claude"] },
|
|
815
822
|
extract(ctx) {
|
|
@@ -856,10 +863,10 @@ var slashExtractor = {
|
|
|
856
863
|
// plugins/antigravity/providers/antigravity/index.ts
|
|
857
864
|
var antigravityProvider = {
|
|
858
865
|
id: "antigravity",
|
|
859
|
-
pluginId:
|
|
866
|
+
pluginId: ANTIGRAVITY_PLUGIN_ID,
|
|
860
867
|
kind: "provider",
|
|
861
868
|
version: "1.0.0",
|
|
862
|
-
description: "
|
|
869
|
+
description: "Declares the Google Antigravity runtime and its reserved built-in names.",
|
|
863
870
|
// Vendor provider: marked gated for the day Antigravity grows its own
|
|
864
871
|
// on-disk kind beyond the open standard. Today `kinds: {}` and
|
|
865
872
|
// `classify` returns `null` for every path, so the flag is inert; the
|
|
@@ -889,7 +896,7 @@ var antigravityProvider = {
|
|
|
889
896
|
// Gemini CLI's. We mirror the full 38-verb Gemini CLI catalog (plus its
|
|
890
897
|
// four documented aliases: `dir`, `?`, `exit`, `bashes`) so a user file
|
|
891
898
|
// that names a skill / command `help`, `clear`, `mcp`, etc. is flagged
|
|
892
|
-
// immediately by `core/reserved
|
|
899
|
+
// immediately by `core/name-reserved` once the lens activates the catalog.
|
|
893
900
|
//
|
|
894
901
|
// The catalog is INACTIVE today: the analyzer keys on `node.provider`
|
|
895
902
|
// and this Provider's `classify()` returns `null` for every path, so
|
|
@@ -1005,10 +1012,10 @@ var agent_schema_default2 = {
|
|
|
1005
1012
|
// plugins/openai/providers/openai/index.ts
|
|
1006
1013
|
var openaiProvider = {
|
|
1007
1014
|
id: "openai",
|
|
1008
|
-
pluginId:
|
|
1015
|
+
pluginId: OPENAI_PLUGIN_ID,
|
|
1009
1016
|
kind: "provider",
|
|
1010
1017
|
version: "1.0.0",
|
|
1011
|
-
description: "
|
|
1018
|
+
description: "Classifies files under `.codex/agents/*.toml` as OpenAI Codex CLI sub-agents.",
|
|
1012
1019
|
// Vendor provider: Codex CLI only reads its own `.codex/` territory.
|
|
1013
1020
|
// Gating the classifier behind the active lens keeps the walker from
|
|
1014
1021
|
// claiming Codex agents under a `claude` (or any other) lens, where
|
|
@@ -1063,10 +1070,10 @@ var skill_schema_default2 = {
|
|
|
1063
1070
|
// plugins/agent-skills/providers/agent-skills/index.ts
|
|
1064
1071
|
var agentSkillsProvider = {
|
|
1065
1072
|
id: "agent-skills",
|
|
1066
|
-
pluginId:
|
|
1073
|
+
pluginId: AGENT_SKILLS_PLUGIN_ID,
|
|
1067
1074
|
kind: "provider",
|
|
1068
1075
|
version: "1.0.0",
|
|
1069
|
-
description: "
|
|
1076
|
+
description: "Classifies files under `.agents/skills/<name>/SKILL.md` as Agent Skills.",
|
|
1070
1077
|
read: { extensions: [".md"], parser: "frontmatter-yaml" },
|
|
1071
1078
|
kinds: {
|
|
1072
1079
|
skill: {
|
|
@@ -1111,10 +1118,10 @@ var markdown_schema_default = {
|
|
|
1111
1118
|
// plugins/core/providers/core-markdown/index.ts
|
|
1112
1119
|
var coreMarkdownProvider = {
|
|
1113
1120
|
id: "markdown",
|
|
1114
|
-
pluginId:
|
|
1121
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1115
1122
|
kind: "provider",
|
|
1116
1123
|
version: "1.0.0",
|
|
1117
|
-
description: "Universal `.md` fallback. Claims any markdown file no vendor-specific
|
|
1124
|
+
description: "Universal `.md` fallback. Claims any markdown file that no vendor-specific provider has classified.",
|
|
1118
1125
|
read: { extensions: [".md"], parser: "frontmatter-yaml" },
|
|
1119
1126
|
// Per spec § A.6, defaultRefreshAction values MUST be qualified
|
|
1120
1127
|
// action ids. The summarize-markdown action is not yet implemented
|
|
@@ -1162,10 +1169,10 @@ var coreMarkdownProvider = {
|
|
|
1162
1169
|
var ID3 = "annotations";
|
|
1163
1170
|
var annotationsExtractor = {
|
|
1164
1171
|
id: ID3,
|
|
1165
|
-
pluginId:
|
|
1172
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1166
1173
|
kind: "extractor",
|
|
1167
1174
|
version: "1.0.0",
|
|
1168
|
-
description: "Turns the `supersedes` and `supersededBy` entries
|
|
1175
|
+
description: "Turns the `supersedes` and `supersededBy` entries from a node's `.sm` sidecar into arrows between nodes in the graph.",
|
|
1169
1176
|
scope: "frontmatter",
|
|
1170
1177
|
extract(ctx) {
|
|
1171
1178
|
const sourcePath = ctx.node.path;
|
|
@@ -1224,10 +1231,10 @@ var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
|
|
|
1224
1231
|
var TRAILING_PUNCT = /[.,;:!?]+$/;
|
|
1225
1232
|
var externalUrlCounterExtractor = {
|
|
1226
1233
|
id: ID4,
|
|
1227
|
-
pluginId:
|
|
1234
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1228
1235
|
kind: "extractor",
|
|
1229
1236
|
version: "1.0.0",
|
|
1230
|
-
description: "Counts the distinct external URLs in a node's body and shows the
|
|
1237
|
+
description: "Counts the distinct external URLs in a node's body and shows the count on the card.",
|
|
1231
1238
|
scope: "body",
|
|
1232
1239
|
/**
|
|
1233
1240
|
* Phase 6 / View contribution system, surface the distinct-URL
|
|
@@ -1313,10 +1320,10 @@ var LINK_RE = /(?<!!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
|
1313
1320
|
var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
|
|
1314
1321
|
var markdownLinkExtractor = {
|
|
1315
1322
|
id: ID5,
|
|
1316
|
-
pluginId:
|
|
1323
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1317
1324
|
kind: "extractor",
|
|
1318
1325
|
version: "1.0.0",
|
|
1319
|
-
description: "
|
|
1326
|
+
description: "Turns markdown links (`[text](path)`) in a node's body into arrows between nodes in the graph.",
|
|
1320
1327
|
scope: "body",
|
|
1321
1328
|
extract(ctx) {
|
|
1322
1329
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1345,7 +1352,7 @@ var markdownLinkExtractor = {
|
|
|
1345
1352
|
// explicitly designates an out-link via the brackets +
|
|
1346
1353
|
// parentheses pair; there is no inference left to discount.
|
|
1347
1354
|
// Whether the path resolves to a real node is a separate
|
|
1348
|
-
// concern (the `core/broken
|
|
1355
|
+
// concern (the `core/reference-broken` analyzer flags unresolved
|
|
1349
1356
|
// targets), not a confidence question.
|
|
1350
1357
|
confidence: 1,
|
|
1351
1358
|
rationale: "unambiguous markdown link syntax",
|
|
@@ -1375,10 +1382,10 @@ var ID6 = "mcp-tools";
|
|
|
1375
1382
|
var MCP_PATTERN = /^mcp__([a-z0-9][a-z0-9_-]*)__[a-z0-9_-]+$/i;
|
|
1376
1383
|
var mcpToolsExtractor = {
|
|
1377
1384
|
id: ID6,
|
|
1378
|
-
pluginId:
|
|
1385
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1379
1386
|
kind: "extractor",
|
|
1380
1387
|
version: "1.0.0",
|
|
1381
|
-
description: "
|
|
1388
|
+
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.",
|
|
1382
1389
|
scope: "frontmatter",
|
|
1383
1390
|
extract(ctx) {
|
|
1384
1391
|
const raw = ctx.frontmatter["tools"];
|
|
@@ -1432,15 +1439,15 @@ function collectMcpServers(tools) {
|
|
|
1432
1439
|
return out;
|
|
1433
1440
|
}
|
|
1434
1441
|
|
|
1435
|
-
// plugins/core/extractors/tools-
|
|
1436
|
-
var ID7 = "tools-
|
|
1442
|
+
// plugins/core/extractors/tools-counter/index.ts
|
|
1443
|
+
var ID7 = "tools-counter";
|
|
1437
1444
|
var TOOLTIP_MAX = 255;
|
|
1438
|
-
var
|
|
1445
|
+
var toolsCounterExtractor = {
|
|
1439
1446
|
id: ID7,
|
|
1440
|
-
pluginId:
|
|
1447
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1441
1448
|
kind: "extractor",
|
|
1442
1449
|
version: "1.0.0",
|
|
1443
|
-
description: "Counts the tools an agent declares in its frontmatter and shows the
|
|
1450
|
+
description: "Counts the tools an agent declares in its frontmatter and shows the count on the agent card.",
|
|
1444
1451
|
scope: "frontmatter",
|
|
1445
1452
|
precondition: { kind: ["claude/agent"] },
|
|
1446
1453
|
ui: {
|
|
@@ -1472,6 +1479,209 @@ function buildTooltip(names) {
|
|
|
1472
1479
|
return `${joined.slice(0, TOOLTIP_MAX - 1)}\u2026`;
|
|
1473
1480
|
}
|
|
1474
1481
|
|
|
1482
|
+
// plugins/core/analyzers/annotation-field-unknown/index.ts
|
|
1483
|
+
import { readFileSync } from "fs";
|
|
1484
|
+
import { dirname, resolve } from "path";
|
|
1485
|
+
import { createRequire } from "module";
|
|
1486
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
1487
|
+
|
|
1488
|
+
// kernel/util/ajv-interop.ts
|
|
1489
|
+
import addFormatsModule from "ajv-formats";
|
|
1490
|
+
var addFormats = addFormatsModule.default ?? addFormatsModule;
|
|
1491
|
+
function applyAjvFormats(ajv) {
|
|
1492
|
+
addFormats(ajv);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// plugins/core/analyzers/annotation-field-unknown/text.ts
|
|
1496
|
+
var ANNOTATION_FIELD_UNKNOWN_TEXTS = {
|
|
1497
|
+
/** Key inside `annotations:` is not in the curated catalog. */
|
|
1498
|
+
unknownAnnotationKey: "{{path}}: sidecar annotations contain unknown key '{{key}}' (not in annotations.schema.json catalog).",
|
|
1499
|
+
/** Top-level key is neither reserved, nor a registered plugin namespace, nor a registered root key. */
|
|
1500
|
+
unknownRootKey: "{{path}}: sidecar declares unknown top-level key '{{key}}'; not a reserved block, not a registered plugin namespace, not a registered root contribution.",
|
|
1501
|
+
/** Value under a registered plugin namespace fails the contributed schema. */
|
|
1502
|
+
pluginNamespaceInvalid: "{{path}}: sidecar block '{{pluginId}}.{{key}}' fails the schema contributed by plugin '{{pluginId}}': {{errors}}.",
|
|
1503
|
+
// Tooltips for the per-node view-contribution badges. Singular vs
|
|
1504
|
+
// plural keeps the count grammar correct without a sub-template.
|
|
1505
|
+
alertTooltipSingle: "This node has 1 unknown field in its sidecar. Open the inspector for details.",
|
|
1506
|
+
alertTooltipMany: "This node has {{count}} unknown fields in its sidecar. Open the inspector for details."
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
// plugins/core/analyzers/annotation-field-unknown/index.ts
|
|
1510
|
+
var ID8 = "annotation-field-unknown";
|
|
1511
|
+
var RESERVED_ROOT_BLOCKS = /* @__PURE__ */ new Set(["identity", "annotations", "settings", "audit"]);
|
|
1512
|
+
var annotationFieldUnknownAnalyzer = {
|
|
1513
|
+
id: ID8,
|
|
1514
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1515
|
+
kind: "analyzer",
|
|
1516
|
+
version: "1.0.0",
|
|
1517
|
+
description: "Flags typos or unrecognized keys in sidecars (`.sm`).",
|
|
1518
|
+
mode: "deterministic",
|
|
1519
|
+
ui: {
|
|
1520
|
+
// Corner badge on the graph card; count omitted when there is a
|
|
1521
|
+
// single unknown field (avoids a noisy "icon + 1" chip).
|
|
1522
|
+
alert: {
|
|
1523
|
+
slot: "graph.node.alert",
|
|
1524
|
+
// Filled warning triangle on the corner, matches the broken-ref
|
|
1525
|
+
// alert's "attention-grabbing solid" pattern; the footer chip
|
|
1526
|
+
// below stays outlined for the quieter counter pairing.
|
|
1527
|
+
icon: "fa-solid fa-triangle-exclamation",
|
|
1528
|
+
emitWhenEmpty: false
|
|
1529
|
+
},
|
|
1530
|
+
// Footer chip on the card, `_counter` shape but rendered icon-only
|
|
1531
|
+
// (the analyzer emits `value: 0` so NodeCounter hides the number
|
|
1532
|
+
// and only the glyph shows). PrimeIcons `pi-question-circle` so the
|
|
1533
|
+
// visual weight matches `annotation-stale`'s `pi-clock` chip
|
|
1534
|
+
// sitting next to it on the same footer row. `emitWhenEmpty: true`
|
|
1535
|
+
// is required: with `value: 0` the slot treats the payload as
|
|
1536
|
+
// empty, so the manifest has to opt in to keep the emission.
|
|
1537
|
+
chip: {
|
|
1538
|
+
slot: "card.footer.right",
|
|
1539
|
+
icon: "pi-question-circle",
|
|
1540
|
+
emitWhenEmpty: true,
|
|
1541
|
+
priority: 30
|
|
1542
|
+
}
|
|
1543
|
+
},
|
|
1544
|
+
// Analyzer body iterates every sidecar root and classifies each
|
|
1545
|
+
// key against three buckets (catalog / plugin namespace / unknown
|
|
1546
|
+
// root). The per-key branching IS the classification table; factoring
|
|
1547
|
+
// it out would rebuild the discriminator elsewhere. Per
|
|
1548
|
+
// `context/lint.md` category 7 (recursive type-discriminator walkers).
|
|
1549
|
+
// eslint-disable-next-line complexity
|
|
1550
|
+
evaluate(ctx) {
|
|
1551
|
+
const sidecarRoots = ctx.sidecarRoots;
|
|
1552
|
+
if (!sidecarRoots || sidecarRoots.size === 0) return [];
|
|
1553
|
+
const knownAnnotationKeys = getKnownAnnotationKeys();
|
|
1554
|
+
const contributions = ctx.annotationContributions ?? [];
|
|
1555
|
+
const namespacedByPlugin = indexNamespacedContributions(contributions);
|
|
1556
|
+
const rootKeys = indexRootContributions(contributions);
|
|
1557
|
+
const knownPluginIds = collectPluginIds(contributions);
|
|
1558
|
+
const issues = [];
|
|
1559
|
+
const perNode = /* @__PURE__ */ new Map();
|
|
1560
|
+
const bump2 = (nodePath) => {
|
|
1561
|
+
perNode.set(nodePath, (perNode.get(nodePath) ?? 0) + 1);
|
|
1562
|
+
};
|
|
1563
|
+
for (const node of ctx.nodes) {
|
|
1564
|
+
const root = sidecarRoots.get(node.path);
|
|
1565
|
+
if (!root) continue;
|
|
1566
|
+
const annotations = root["annotations"];
|
|
1567
|
+
if (annotations !== void 0 && annotations !== null && typeof annotations === "object" && !Array.isArray(annotations)) {
|
|
1568
|
+
for (const key of Object.keys(annotations)) {
|
|
1569
|
+
if (!knownAnnotationKeys.has(key)) {
|
|
1570
|
+
issues.push({
|
|
1571
|
+
analyzerId: ID8,
|
|
1572
|
+
severity: "warn",
|
|
1573
|
+
nodeIds: [node.path],
|
|
1574
|
+
message: tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.unknownAnnotationKey, {
|
|
1575
|
+
path: node.path,
|
|
1576
|
+
key
|
|
1577
|
+
}),
|
|
1578
|
+
data: { surface: "annotations", key }
|
|
1579
|
+
});
|
|
1580
|
+
bump2(node.path);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
for (const key of Object.keys(root)) {
|
|
1585
|
+
if (RESERVED_ROOT_BLOCKS.has(key)) continue;
|
|
1586
|
+
if (rootKeys.has(key)) continue;
|
|
1587
|
+
if (knownPluginIds.has(key)) {
|
|
1588
|
+
const block = root[key];
|
|
1589
|
+
if (block === null || typeof block !== "object" || Array.isArray(block)) continue;
|
|
1590
|
+
const contribsForPlugin = namespacedByPlugin.get(key);
|
|
1591
|
+
if (!contribsForPlugin) continue;
|
|
1592
|
+
for (const [contribKey, validator] of contribsForPlugin) {
|
|
1593
|
+
const value = block[contribKey];
|
|
1594
|
+
if (value === void 0) continue;
|
|
1595
|
+
if (validator(value)) continue;
|
|
1596
|
+
const errors = (validator.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
|
|
1597
|
+
issues.push({
|
|
1598
|
+
analyzerId: ID8,
|
|
1599
|
+
severity: "warn",
|
|
1600
|
+
nodeIds: [node.path],
|
|
1601
|
+
message: tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.pluginNamespaceInvalid, {
|
|
1602
|
+
path: node.path,
|
|
1603
|
+
pluginId: key,
|
|
1604
|
+
key: contribKey,
|
|
1605
|
+
errors
|
|
1606
|
+
}),
|
|
1607
|
+
data: { surface: "plugin-namespace", pluginId: key, key: contribKey }
|
|
1608
|
+
});
|
|
1609
|
+
bump2(node.path);
|
|
1610
|
+
}
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
issues.push({
|
|
1614
|
+
analyzerId: ID8,
|
|
1615
|
+
severity: "warn",
|
|
1616
|
+
nodeIds: [node.path],
|
|
1617
|
+
message: tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.unknownRootKey, {
|
|
1618
|
+
path: node.path,
|
|
1619
|
+
key
|
|
1620
|
+
}),
|
|
1621
|
+
data: { surface: "root", key }
|
|
1622
|
+
});
|
|
1623
|
+
bump2(node.path);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
for (const [nodePath, count] of perNode) {
|
|
1627
|
+
const tooltip = count === 1 ? ANNOTATION_FIELD_UNKNOWN_TEXTS.alertTooltipSingle : tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.alertTooltipMany, { count });
|
|
1628
|
+
ctx.emitContribution(nodePath, "alert", {
|
|
1629
|
+
icon: "fa-solid fa-triangle-exclamation",
|
|
1630
|
+
severity: "warn",
|
|
1631
|
+
tooltip
|
|
1632
|
+
});
|
|
1633
|
+
ctx.emitContribution(nodePath, "chip", {
|
|
1634
|
+
value: 0,
|
|
1635
|
+
severity: "warn",
|
|
1636
|
+
tooltip
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
return issues;
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
var cachedKnownKeys = null;
|
|
1643
|
+
function getKnownAnnotationKeys() {
|
|
1644
|
+
if (cachedKnownKeys) return cachedKnownKeys;
|
|
1645
|
+
const require2 = createRequire(import.meta.url);
|
|
1646
|
+
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
1647
|
+
const specRoot = dirname(indexPath);
|
|
1648
|
+
const schema = JSON.parse(
|
|
1649
|
+
readFileSync(resolve(specRoot, "schemas/annotations.schema.json"), "utf8")
|
|
1650
|
+
);
|
|
1651
|
+
cachedKnownKeys = new Set(Object.keys(schema.properties ?? {}));
|
|
1652
|
+
return cachedKnownKeys;
|
|
1653
|
+
}
|
|
1654
|
+
function indexNamespacedContributions(contributions) {
|
|
1655
|
+
const out = /* @__PURE__ */ new Map();
|
|
1656
|
+
const ajv = new Ajv2020({ strict: false, allErrors: true, allowUnionTypes: true });
|
|
1657
|
+
applyAjvFormats(ajv);
|
|
1658
|
+
for (const entry of contributions) {
|
|
1659
|
+
if (entry.location !== "namespaced") continue;
|
|
1660
|
+
let bucket = out.get(entry.pluginId);
|
|
1661
|
+
if (!bucket) {
|
|
1662
|
+
bucket = /* @__PURE__ */ new Map();
|
|
1663
|
+
out.set(entry.pluginId, bucket);
|
|
1664
|
+
}
|
|
1665
|
+
try {
|
|
1666
|
+
bucket.set(entry.key, ajv.compile(entry.schema));
|
|
1667
|
+
} catch {
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return out;
|
|
1671
|
+
}
|
|
1672
|
+
function indexRootContributions(contributions) {
|
|
1673
|
+
const out = /* @__PURE__ */ new Set();
|
|
1674
|
+
for (const entry of contributions) {
|
|
1675
|
+
if (entry.location === "root") out.add(entry.key);
|
|
1676
|
+
}
|
|
1677
|
+
return out;
|
|
1678
|
+
}
|
|
1679
|
+
function collectPluginIds(contributions) {
|
|
1680
|
+
const out = /* @__PURE__ */ new Set();
|
|
1681
|
+
for (const entry of contributions) out.add(entry.pluginId);
|
|
1682
|
+
return out;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1475
1685
|
// plugins/core/analyzers/annotation-orphan/text.ts
|
|
1476
1686
|
var ANNOTATION_ORPHAN_TEXTS = {
|
|
1477
1687
|
/** Sidecar `<path>.sm` has no matching `<path>.md`. */
|
|
@@ -1479,13 +1689,13 @@ var ANNOTATION_ORPHAN_TEXTS = {
|
|
|
1479
1689
|
};
|
|
1480
1690
|
|
|
1481
1691
|
// plugins/core/analyzers/annotation-orphan/index.ts
|
|
1482
|
-
var
|
|
1692
|
+
var ID9 = "annotation-orphan";
|
|
1483
1693
|
var annotationOrphanAnalyzer = {
|
|
1484
|
-
id:
|
|
1485
|
-
pluginId:
|
|
1694
|
+
id: ID9,
|
|
1695
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1486
1696
|
kind: "analyzer",
|
|
1487
1697
|
version: "1.0.0",
|
|
1488
|
-
description: "
|
|
1698
|
+
description: "Flags sidecars (`.sm`) whose `.md` file no longer exists.",
|
|
1489
1699
|
mode: "deterministic",
|
|
1490
1700
|
evaluate(ctx) {
|
|
1491
1701
|
const orphans = ctx.orphanSidecars;
|
|
@@ -1494,7 +1704,7 @@ var annotationOrphanAnalyzer = {
|
|
|
1494
1704
|
for (const orphan of orphans) {
|
|
1495
1705
|
const expectedMdRelative = orphan.relativePath.endsWith(".sm") ? `${orphan.relativePath.slice(0, -".sm".length)}.md` : `${orphan.relativePath}.md`;
|
|
1496
1706
|
issues.push({
|
|
1497
|
-
analyzerId:
|
|
1707
|
+
analyzerId: ID9,
|
|
1498
1708
|
severity: "warn",
|
|
1499
1709
|
nodeIds: [expectedMdRelative],
|
|
1500
1710
|
message: tx(ANNOTATION_ORPHAN_TEXTS.message, {
|
|
@@ -1531,17 +1741,17 @@ var ANNOTATION_STALE_TEXTS = {
|
|
|
1531
1741
|
};
|
|
1532
1742
|
|
|
1533
1743
|
// plugins/core/analyzers/annotation-stale/index.ts
|
|
1534
|
-
var
|
|
1744
|
+
var ID10 = "annotation-stale";
|
|
1535
1745
|
var annotationStaleAnalyzer = {
|
|
1536
|
-
id:
|
|
1537
|
-
pluginId:
|
|
1746
|
+
id: ID10,
|
|
1747
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1538
1748
|
kind: "analyzer",
|
|
1539
1749
|
version: "1.0.0",
|
|
1540
|
-
description: "
|
|
1750
|
+
description: "Marks sidecars (`.sm`) that are out of date with their `.md`.",
|
|
1541
1751
|
mode: "deterministic",
|
|
1542
1752
|
// The natural fix is to bump the node: refreshes `for` hashes,
|
|
1543
1753
|
// increments `annotations.version`, and stamps the audit block. The
|
|
1544
|
-
// UI surfaces `core/bump` in the node inspector under "Recommended
|
|
1754
|
+
// UI surfaces `core/node-bump` in the node inspector under "Recommended
|
|
1545
1755
|
// for issues" whenever this analyzer fires.
|
|
1546
1756
|
ui: {
|
|
1547
1757
|
// A `pi-clock` chip in the footer-right cluster so the operator
|
|
@@ -1568,7 +1778,7 @@ var annotationStaleAnalyzer = {
|
|
|
1568
1778
|
if (status === "fresh") continue;
|
|
1569
1779
|
const message = status === "stale-body" ? tx(ANNOTATION_STALE_TEXTS.bodyDrift, { path: node.path }) : status === "stale-frontmatter" ? tx(ANNOTATION_STALE_TEXTS.frontmatterDrift, { path: node.path }) : tx(ANNOTATION_STALE_TEXTS.bothDrift, { path: node.path });
|
|
1570
1780
|
issues.push({
|
|
1571
|
-
analyzerId:
|
|
1781
|
+
analyzerId: ID10,
|
|
1572
1782
|
severity: "warn",
|
|
1573
1783
|
nodeIds: [node.path],
|
|
1574
1784
|
message,
|
|
@@ -1594,221 +1804,37 @@ function tooltipFor(status) {
|
|
|
1594
1804
|
}
|
|
1595
1805
|
}
|
|
1596
1806
|
|
|
1597
|
-
// plugins/core/analyzers/
|
|
1598
|
-
|
|
1807
|
+
// plugins/core/analyzers/contribution-orphan/index.ts
|
|
1808
|
+
var ID11 = "contribution-orphan";
|
|
1809
|
+
var contributionOrphanAnalyzer = {
|
|
1810
|
+
id: ID11,
|
|
1811
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1812
|
+
kind: "analyzer",
|
|
1813
|
+
version: "0.0.0",
|
|
1814
|
+
description: "Warns about plugin data referencing nodes renamed or deleted in the latest scan.",
|
|
1815
|
+
mode: "deterministic",
|
|
1816
|
+
evaluate(_ctx) {
|
|
1817
|
+
return [];
|
|
1818
|
+
}
|
|
1819
|
+
};
|
|
1599
1820
|
|
|
1600
|
-
// plugins/core/analyzers/
|
|
1601
|
-
var
|
|
1602
|
-
/**
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
alertTooltipMany: "This node has {{count}} broken references. Open the inspector for details.",
|
|
1608
|
-
// Fix-summary copy when the broken trigger has a same-named file on
|
|
1609
|
-
// disk that does not advertise `name:` in its frontmatter. Two
|
|
1610
|
-
// variants for single vs multiple candidates; same template family
|
|
1611
|
-
// as the alert tooltips above.
|
|
1612
|
-
hintSummarySingle: "Add `name: {{name}}` to the frontmatter of {{candidate}} so this reference resolves.",
|
|
1613
|
-
hintSummaryMany: "Add `name: {{name}}` to the frontmatter of one of these files so this reference resolves: {{candidates}}."
|
|
1821
|
+
// plugins/core/analyzers/job-file-orphan/text.ts
|
|
1822
|
+
var JOB_FILE_ORPHAN_TEXTS = {
|
|
1823
|
+
/**
|
|
1824
|
+
* `<path>.md` lives under `.skill-map/jobs/` but no `state_jobs.filePath`
|
|
1825
|
+
* row references it. Run `sm job prune --orphan-files` to remove.
|
|
1826
|
+
*/
|
|
1827
|
+
message: "Orphan job file: {{filePath}} is not referenced by any state_jobs row. Run `sm job prune --orphan-files` to remove it."
|
|
1614
1828
|
};
|
|
1615
1829
|
|
|
1616
|
-
// plugins/core/analyzers/
|
|
1617
|
-
var
|
|
1618
|
-
var
|
|
1619
|
-
id:
|
|
1620
|
-
pluginId:
|
|
1830
|
+
// plugins/core/analyzers/job-file-orphan/index.ts
|
|
1831
|
+
var ID12 = "job-file-orphan";
|
|
1832
|
+
var jobFileOrphanAnalyzer = {
|
|
1833
|
+
id: ID12,
|
|
1834
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1621
1835
|
kind: "analyzer",
|
|
1622
1836
|
version: "1.0.0",
|
|
1623
|
-
description: "
|
|
1624
|
-
mode: "deterministic",
|
|
1625
|
-
ui: {
|
|
1626
|
-
// Corner badge on the graph card; count omitted when there is a
|
|
1627
|
-
// single broken ref (avoids a noisy "icon + 1" chip).
|
|
1628
|
-
alert: {
|
|
1629
|
-
slot: "graph.node.alert",
|
|
1630
|
-
icon: "fa-solid fa-circle-xmark",
|
|
1631
|
-
emitWhenEmpty: false
|
|
1632
|
-
},
|
|
1633
|
-
// Footer chip on the card. `_counter` shape, `value` always shows,
|
|
1634
|
-
// so the operator sees "how many" at a glance. Renders OUTLINED
|
|
1635
|
-
// (`fa-regular`) so the corner alert (filled, attention-grabbing)
|
|
1636
|
-
// and the footer chip (quieter, paired with a number) read as two
|
|
1637
|
-
// beats of the same signal rather than two identical glyphs.
|
|
1638
|
-
chip: {
|
|
1639
|
-
slot: "card.footer.right",
|
|
1640
|
-
icon: "fa-regular fa-circle-xmark",
|
|
1641
|
-
emitWhenEmpty: false,
|
|
1642
|
-
priority: 40
|
|
1643
|
-
}
|
|
1644
|
-
},
|
|
1645
|
-
// The resolver, the reference-paths escape hatch, the per-source
|
|
1646
|
-
// aggregation, and the dual-slot emit (with single/plural tooltip and
|
|
1647
|
-
// optional count) all live in one flow because they share the per-link
|
|
1648
|
-
// loop. Splitting them would re-walk `ctx.links` three times.
|
|
1649
|
-
// eslint-disable-next-line complexity
|
|
1650
|
-
evaluate(ctx) {
|
|
1651
|
-
const byPath3 = new Set(ctx.nodes.map((n) => n.path));
|
|
1652
|
-
const byNormalizedName = indexByNormalizedName(ctx.nodes);
|
|
1653
|
-
const byBasenameWithoutName = indexByBasenameWithoutName(ctx.nodes);
|
|
1654
|
-
const refIndex = ctx.referenceablePaths && ctx.referenceablePaths.size > 0 && ctx.cwd ? { paths: ctx.referenceablePaths, cwd: ctx.cwd } : null;
|
|
1655
|
-
const issues = [];
|
|
1656
|
-
const perNode = /* @__PURE__ */ new Map();
|
|
1657
|
-
for (const link of ctx.links) {
|
|
1658
|
-
if (isResolved(link, byPath3, byNormalizedName)) continue;
|
|
1659
|
-
if (refIndex && resolvesViaReferencePaths(link, refIndex)) continue;
|
|
1660
|
-
const candidates = findHintCandidates(link, byBasenameWithoutName);
|
|
1661
|
-
issues.push(buildIssue(link, candidates));
|
|
1662
|
-
perNode.set(link.source, (perNode.get(link.source) ?? 0) + 1);
|
|
1663
|
-
}
|
|
1664
|
-
for (const [nodePath, count] of perNode) {
|
|
1665
|
-
const tooltip = count === 1 ? BROKEN_REF_TEXTS.alertTooltipSingle : tx(BROKEN_REF_TEXTS.alertTooltipMany, { count });
|
|
1666
|
-
const capped = Math.min(count, 99);
|
|
1667
|
-
ctx.emitContribution(nodePath, "alert", {
|
|
1668
|
-
icon: "fa-solid fa-circle-xmark",
|
|
1669
|
-
severity: "danger",
|
|
1670
|
-
tooltip
|
|
1671
|
-
});
|
|
1672
|
-
ctx.emitContribution(nodePath, "chip", {
|
|
1673
|
-
value: capped,
|
|
1674
|
-
severity: "danger",
|
|
1675
|
-
tooltip
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
return issues;
|
|
1679
|
-
}
|
|
1680
|
-
};
|
|
1681
|
-
function buildIssue(link, hintCandidates = []) {
|
|
1682
|
-
const data = {
|
|
1683
|
-
target: link.target,
|
|
1684
|
-
kind: link.kind,
|
|
1685
|
-
trigger: link.trigger?.normalizedTrigger ?? null
|
|
1686
|
-
};
|
|
1687
|
-
const issue = {
|
|
1688
|
-
analyzerId: ID10,
|
|
1689
|
-
severity: "warn",
|
|
1690
|
-
nodeIds: [link.source],
|
|
1691
|
-
message: tx(BROKEN_REF_TEXTS.message, {
|
|
1692
|
-
kind: link.kind,
|
|
1693
|
-
source: link.source,
|
|
1694
|
-
target: link.target
|
|
1695
|
-
}),
|
|
1696
|
-
data
|
|
1697
|
-
};
|
|
1698
|
-
if (hintCandidates.length > 0) {
|
|
1699
|
-
const suggestedName = (link.trigger?.normalizedTrigger ?? "").replace(/^[/@]/, "").trim();
|
|
1700
|
-
const candidatePaths = hintCandidates.map((n) => n.path);
|
|
1701
|
-
data["hint"] = {
|
|
1702
|
-
kind: "missing-frontmatter-name",
|
|
1703
|
-
suggestedName,
|
|
1704
|
-
candidates: candidatePaths
|
|
1705
|
-
};
|
|
1706
|
-
issue.fix = {
|
|
1707
|
-
summary: candidatePaths.length === 1 ? tx(BROKEN_REF_TEXTS.hintSummarySingle, {
|
|
1708
|
-
name: suggestedName,
|
|
1709
|
-
candidate: candidatePaths[0]
|
|
1710
|
-
}) : tx(BROKEN_REF_TEXTS.hintSummaryMany, {
|
|
1711
|
-
name: suggestedName,
|
|
1712
|
-
candidates: candidatePaths.join(", ")
|
|
1713
|
-
}),
|
|
1714
|
-
autofixable: false
|
|
1715
|
-
};
|
|
1716
|
-
}
|
|
1717
|
-
return issue;
|
|
1718
|
-
}
|
|
1719
|
-
function resolvesViaReferencePaths(link, refIndex) {
|
|
1720
|
-
if (!isPathStyleLink(link)) return false;
|
|
1721
|
-
return refIndex.paths.has(resolve(refIndex.cwd, link.target));
|
|
1722
|
-
}
|
|
1723
|
-
function indexByNormalizedName(nodes) {
|
|
1724
|
-
const out = /* @__PURE__ */ new Map();
|
|
1725
|
-
for (const node of nodes) {
|
|
1726
|
-
const raw = node.frontmatter?.["name"];
|
|
1727
|
-
const name = typeof raw === "string" ? raw : "";
|
|
1728
|
-
if (!name) continue;
|
|
1729
|
-
const key = normalizeTrigger(name);
|
|
1730
|
-
const bucket = out.get(key) ?? [];
|
|
1731
|
-
bucket.push(node);
|
|
1732
|
-
out.set(key, bucket);
|
|
1733
|
-
}
|
|
1734
|
-
return out;
|
|
1735
|
-
}
|
|
1736
|
-
function basenameWithoutExt(path) {
|
|
1737
|
-
const base = pathPosix3.basename(path);
|
|
1738
|
-
const ext = pathPosix3.extname(base);
|
|
1739
|
-
return ext ? base.slice(0, -ext.length) : base;
|
|
1740
|
-
}
|
|
1741
|
-
function indexByBasenameWithoutName(nodes) {
|
|
1742
|
-
const out = /* @__PURE__ */ new Map();
|
|
1743
|
-
for (const node of nodes) {
|
|
1744
|
-
const raw = node.frontmatter?.["name"];
|
|
1745
|
-
const name = typeof raw === "string" ? raw : "";
|
|
1746
|
-
if (name) continue;
|
|
1747
|
-
const bare = basenameWithoutExt(node.path);
|
|
1748
|
-
if (!bare) continue;
|
|
1749
|
-
const key = normalizeTrigger(bare);
|
|
1750
|
-
if (!key) continue;
|
|
1751
|
-
const bucket = out.get(key) ?? [];
|
|
1752
|
-
bucket.push(node);
|
|
1753
|
-
out.set(key, bucket);
|
|
1754
|
-
}
|
|
1755
|
-
return out;
|
|
1756
|
-
}
|
|
1757
|
-
function findHintCandidates(link, idx) {
|
|
1758
|
-
const normalized = link.trigger?.normalizedTrigger;
|
|
1759
|
-
if (!normalized) return [];
|
|
1760
|
-
const sigil = normalized.charAt(0);
|
|
1761
|
-
if (sigil !== "/" && sigil !== "@") return [];
|
|
1762
|
-
const withoutSigil = normalized.slice(1).trim();
|
|
1763
|
-
if (!withoutSigil) return [];
|
|
1764
|
-
return idx.get(withoutSigil) ?? [];
|
|
1765
|
-
}
|
|
1766
|
-
function isResolved(link, byPath3, byNormalizedName) {
|
|
1767
|
-
const normalized = link.trigger?.normalizedTrigger;
|
|
1768
|
-
if (normalized) {
|
|
1769
|
-
const withoutSigil = normalized.replace(/^[/@]/, "").trim();
|
|
1770
|
-
if (byNormalizedName.has(withoutSigil)) return true;
|
|
1771
|
-
}
|
|
1772
|
-
if (byPath3.has(link.target)) return true;
|
|
1773
|
-
return false;
|
|
1774
|
-
}
|
|
1775
|
-
function isPathStyleLink(link) {
|
|
1776
|
-
const sigil = link.trigger?.normalizedTrigger?.charAt(0);
|
|
1777
|
-
if (sigil === "/" || sigil === "@") return false;
|
|
1778
|
-
return true;
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
// plugins/core/analyzers/contribution-orphan/index.ts
|
|
1782
|
-
var ID11 = "contribution-orphan";
|
|
1783
|
-
var contributionOrphanAnalyzer = {
|
|
1784
|
-
id: ID11,
|
|
1785
|
-
pluginId: "core",
|
|
1786
|
-
kind: "analyzer",
|
|
1787
|
-
version: "0.0.0",
|
|
1788
|
-
description: "Detects and warns about plugin data referencing nodes renamed or deleted in the latest scan.",
|
|
1789
|
-
mode: "deterministic",
|
|
1790
|
-
evaluate(_ctx) {
|
|
1791
|
-
return [];
|
|
1792
|
-
}
|
|
1793
|
-
};
|
|
1794
|
-
|
|
1795
|
-
// plugins/core/analyzers/job-orphan-file/text.ts
|
|
1796
|
-
var JOB_ORPHAN_FILE_TEXTS = {
|
|
1797
|
-
/**
|
|
1798
|
-
* `<path>.md` lives under `.skill-map/jobs/` but no `state_jobs.filePath`
|
|
1799
|
-
* row references it. Run `sm job prune --orphan-files` to remove.
|
|
1800
|
-
*/
|
|
1801
|
-
message: "Orphan job file: {{filePath}} is not referenced by any state_jobs row. Run `sm job prune --orphan-files` to remove it."
|
|
1802
|
-
};
|
|
1803
|
-
|
|
1804
|
-
// plugins/core/analyzers/job-orphan-file/index.ts
|
|
1805
|
-
var ID12 = "job-orphan-file";
|
|
1806
|
-
var jobOrphanFileAnalyzer = {
|
|
1807
|
-
id: ID12,
|
|
1808
|
-
pluginId: "core",
|
|
1809
|
-
kind: "analyzer",
|
|
1810
|
-
version: "1.0.0",
|
|
1811
|
-
description: "Detects and flags leftover job result files (no live job references them). Cleanup via `sm job prune --orphan-files`.",
|
|
1837
|
+
description: "Flags leftover job result files (no live job references them). Clean up via `sm job prune --orphan-files`.",
|
|
1812
1838
|
mode: "deterministic",
|
|
1813
1839
|
evaluate(ctx) {
|
|
1814
1840
|
const orphans = ctx.orphanJobFiles;
|
|
@@ -1819,7 +1845,7 @@ var jobOrphanFileAnalyzer = {
|
|
|
1819
1845
|
analyzerId: ID12,
|
|
1820
1846
|
severity: "warn",
|
|
1821
1847
|
nodeIds: [filePath],
|
|
1822
|
-
message: tx(
|
|
1848
|
+
message: tx(JOB_FILE_ORPHAN_TEXTS.message, { filePath }),
|
|
1823
1849
|
data: { filePath }
|
|
1824
1850
|
});
|
|
1825
1851
|
}
|
|
@@ -1840,7 +1866,7 @@ var linkConflictAnalyzer = {
|
|
|
1840
1866
|
pluginId: "core",
|
|
1841
1867
|
kind: "analyzer",
|
|
1842
1868
|
version: "1.0.0",
|
|
1843
|
-
description:
|
|
1869
|
+
description: "Flags conflicting arrow meanings between extractors (e.g. `references` vs `invokes`).",
|
|
1844
1870
|
mode: "deterministic",
|
|
1845
1871
|
// Bucket links by (source, target), then per-bucket detect distinct
|
|
1846
1872
|
// kinds. The branching is intrinsic to the per-bucket conflict
|
|
@@ -1950,11 +1976,11 @@ function resolveLinkTargetToPath(link, nameIndex) {
|
|
|
1950
1976
|
return resolved ?? raw;
|
|
1951
1977
|
}
|
|
1952
1978
|
|
|
1953
|
-
// plugins/core/analyzers/link-
|
|
1954
|
-
var ID14 = "link-
|
|
1955
|
-
var
|
|
1979
|
+
// plugins/core/analyzers/link-counter/index.ts
|
|
1980
|
+
var ID14 = "link-counter";
|
|
1981
|
+
var linkCounterAnalyzer = {
|
|
1956
1982
|
id: ID14,
|
|
1957
|
-
pluginId:
|
|
1983
|
+
pluginId: CORE_PLUGIN_ID,
|
|
1958
1984
|
kind: "analyzer",
|
|
1959
1985
|
version: "1.0.0",
|
|
1960
1986
|
description: "Counts incoming and outgoing links per node.",
|
|
@@ -2017,163 +2043,73 @@ function formatBreakdown(byKind, direction) {
|
|
|
2017
2043
|
return [direction, ...lines].join("\n");
|
|
2018
2044
|
}
|
|
2019
2045
|
|
|
2020
|
-
// plugins/core/analyzers/
|
|
2021
|
-
var
|
|
2046
|
+
// plugins/core/analyzers/link-self-loop/text.ts
|
|
2047
|
+
var LINK_SELF_LOOP_TEXTS = {
|
|
2022
2048
|
/**
|
|
2023
|
-
*
|
|
2024
|
-
*
|
|
2025
|
-
*
|
|
2049
|
+
* Per-edge warn: a node body references itself via the slash /
|
|
2050
|
+
* at-directive / markdown-link surface (most commonly because the
|
|
2051
|
+
* file's heading IS the invocation token, e.g. `# /deploy` inside
|
|
2052
|
+
* `commands/deploy.md`). The link is structurally valid but rarely
|
|
2053
|
+
* the operator's intent; UI consumers MAY hide it by default and
|
|
2054
|
+
* surface a count.
|
|
2026
2055
|
*/
|
|
2027
|
-
message: "{{source}} references
|
|
2028
|
-
/** Inline separator between occurrences in the message. */
|
|
2029
|
-
occurrenceSeparator: ", ",
|
|
2030
|
-
/** Per-occurrence formatting (trigger + line). */
|
|
2031
|
-
occurrence: "`{{trigger}}` ({{kind}}, line {{line}})",
|
|
2032
|
-
/** Per-occurrence formatting when the extractor did not record a line. */
|
|
2033
|
-
occurrenceUnknownLine: "`{{trigger}}` ({{kind}}, unknown line)"
|
|
2056
|
+
message: "{{source}} references itself via `{{trigger}}` ({{kind}}). Self-loops typically come from the file's own heading or label and are noise rather than intent. Either remove the in-body token or treat this finding as expected and acknowledged."
|
|
2034
2057
|
};
|
|
2035
2058
|
|
|
2036
|
-
// plugins/core/analyzers/
|
|
2037
|
-
var ID15 = "
|
|
2038
|
-
var
|
|
2059
|
+
// plugins/core/analyzers/link-self-loop/index.ts
|
|
2060
|
+
var ID15 = "link-self-loop";
|
|
2061
|
+
var linkSelfLoopAnalyzer = {
|
|
2039
2062
|
id: ID15,
|
|
2040
|
-
pluginId:
|
|
2063
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2041
2064
|
kind: "analyzer",
|
|
2042
2065
|
version: "1.0.0",
|
|
2043
|
-
description: "Flags
|
|
2066
|
+
description: "Flags links whose source is also their own resolved target (e.g. a body heading like `# /deploy` inside the file that defines `/deploy`).",
|
|
2044
2067
|
mode: "deterministic",
|
|
2045
2068
|
evaluate(ctx) {
|
|
2046
2069
|
if (ctx.links.length === 0) return [];
|
|
2047
|
-
const byPath3 = /* @__PURE__ */ new Map();
|
|
2048
|
-
for (const node of ctx.nodes) byPath3.set(node.path, node);
|
|
2049
|
-
const byName = buildNameIndex2(ctx.nodes);
|
|
2050
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2051
|
-
for (const link of ctx.links) {
|
|
2052
|
-
const resolved = resolveTargetPath(link, byPath3, byName);
|
|
2053
|
-
if (!resolved) continue;
|
|
2054
|
-
const key = `${link.source}\0${resolved}`;
|
|
2055
|
-
const bucket = groups.get(key);
|
|
2056
|
-
if (bucket) bucket.push(link);
|
|
2057
|
-
else groups.set(key, [link]);
|
|
2058
|
-
}
|
|
2059
2070
|
const issues = [];
|
|
2060
|
-
for (const
|
|
2061
|
-
|
|
2062
|
-
if (totalOccurrences < 2) continue;
|
|
2063
|
-
const [source, resolvedTarget] = key.split("\0");
|
|
2064
|
-
const flat = flattenOccurrences(links);
|
|
2071
|
+
for (const link of ctx.links) {
|
|
2072
|
+
if (!isSelfLoop(link)) continue;
|
|
2065
2073
|
issues.push({
|
|
2066
2074
|
analyzerId: ID15,
|
|
2067
2075
|
severity: "warn",
|
|
2068
|
-
nodeIds: [source],
|
|
2069
|
-
message: tx(
|
|
2070
|
-
source,
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
occurrences: flat.map(formatOccurrence).join(REDUNDANT_TARGET_REFERENCE_TEXTS.occurrenceSeparator)
|
|
2076
|
+
nodeIds: [link.source],
|
|
2077
|
+
message: tx(LINK_SELF_LOOP_TEXTS.message, {
|
|
2078
|
+
source: link.source,
|
|
2079
|
+
trigger: link.trigger?.originalTrigger ?? link.target,
|
|
2080
|
+
kind: link.kind
|
|
2074
2081
|
}),
|
|
2075
2082
|
data: {
|
|
2076
|
-
target:
|
|
2077
|
-
resolvedTarget,
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
}))
|
|
2083
|
+
target: link.target,
|
|
2084
|
+
resolvedTarget: link.resolvedTarget ?? link.target,
|
|
2085
|
+
kind: link.kind,
|
|
2086
|
+
// Mark explicitly so UI / downstream consumers can read this
|
|
2087
|
+
// single field instead of re-computing the `source === target`
|
|
2088
|
+
// predicate themselves.
|
|
2089
|
+
selfLoop: true
|
|
2084
2090
|
}
|
|
2085
2091
|
});
|
|
2086
2092
|
}
|
|
2087
2093
|
return issues;
|
|
2088
2094
|
}
|
|
2089
2095
|
};
|
|
2090
|
-
function
|
|
2096
|
+
function isSelfLoop(link) {
|
|
2097
|
+
if (link.source === link.target) return true;
|
|
2098
|
+
if (link.resolvedTarget && link.source === link.resolvedTarget) return true;
|
|
2099
|
+
return false;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// kernel/orchestrator/node-identifiers.ts
|
|
2103
|
+
import { posix as pathPosix3 } from "path";
|
|
2104
|
+
function deriveNodeIdentifiers(node, kindDescriptor) {
|
|
2105
|
+
const sources = kindDescriptor?.identifiers;
|
|
2106
|
+
if (!sources || sources.length === 0) return [];
|
|
2091
2107
|
const out = [];
|
|
2092
|
-
for (const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
originalTrigger: occ.originalTrigger,
|
|
2098
|
-
extractor: occ.extractor,
|
|
2099
|
-
line: occ.location?.line ?? null
|
|
2100
|
-
});
|
|
2101
|
-
}
|
|
2102
|
-
continue;
|
|
2103
|
-
}
|
|
2104
|
-
const trigger = link.trigger?.originalTrigger ?? link.target;
|
|
2105
|
-
out.push({
|
|
2106
|
-
kind: link.kind,
|
|
2107
|
-
originalTrigger: trigger,
|
|
2108
|
-
extractor: link.sources[0] ?? "unknown",
|
|
2109
|
-
line: link.location?.line ?? null
|
|
2110
|
-
});
|
|
2111
|
-
}
|
|
2112
|
-
out.sort((a, b) => {
|
|
2113
|
-
const la = a.line ?? Number.MAX_SAFE_INTEGER;
|
|
2114
|
-
const lb = b.line ?? Number.MAX_SAFE_INTEGER;
|
|
2115
|
-
if (la !== lb) return la - lb;
|
|
2116
|
-
return a.originalTrigger.localeCompare(b.originalTrigger);
|
|
2117
|
-
});
|
|
2118
|
-
return out;
|
|
2119
|
-
}
|
|
2120
|
-
function formatOccurrence(occ) {
|
|
2121
|
-
if (occ.line === null) {
|
|
2122
|
-
return tx(REDUNDANT_TARGET_REFERENCE_TEXTS.occurrenceUnknownLine, { trigger: occ.originalTrigger, kind: occ.kind });
|
|
2123
|
-
}
|
|
2124
|
-
return tx(REDUNDANT_TARGET_REFERENCE_TEXTS.occurrence, { trigger: occ.originalTrigger, kind: occ.kind, line: occ.line });
|
|
2125
|
-
}
|
|
2126
|
-
function buildNameIndex2(nodes) {
|
|
2127
|
-
const out = /* @__PURE__ */ new Map();
|
|
2128
|
-
for (const node of nodes) {
|
|
2129
|
-
for (const candidate of collectIdentifiers(node)) {
|
|
2130
|
-
const normalised = normalizeTrigger(candidate);
|
|
2131
|
-
if (!normalised) continue;
|
|
2132
|
-
const bucket = out.get(normalised);
|
|
2133
|
-
if (bucket) bucket.push(node.path);
|
|
2134
|
-
else out.set(normalised, [node.path]);
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
return out;
|
|
2138
|
-
}
|
|
2139
|
-
function collectIdentifiers(node) {
|
|
2140
|
-
const out = [];
|
|
2141
|
-
const fmName = node.frontmatter?.["name"];
|
|
2142
|
-
if (typeof fmName === "string" && fmName.length > 0) out.push(fmName);
|
|
2143
|
-
const segs = node.path.split("/");
|
|
2144
|
-
const last = segs[segs.length - 1] ?? "";
|
|
2145
|
-
if (last) {
|
|
2146
|
-
const stem = last.replace(/\.[^.]+$/, "");
|
|
2147
|
-
if (stem) out.push(stem);
|
|
2148
|
-
}
|
|
2149
|
-
if (segs.length >= 2) {
|
|
2150
|
-
const dirBase = segs[segs.length - 2];
|
|
2151
|
-
if (dirBase) out.push(dirBase);
|
|
2152
|
-
}
|
|
2153
|
-
return out;
|
|
2154
|
-
}
|
|
2155
|
-
function resolveTargetPath(link, byPath3, byName) {
|
|
2156
|
-
if (byPath3.has(link.target)) return link.target;
|
|
2157
|
-
const trigger = link.trigger?.normalizedTrigger;
|
|
2158
|
-
if (!trigger) return null;
|
|
2159
|
-
const stripped = trigger.replace(/^[/@]/, "").trim();
|
|
2160
|
-
if (!stripped) return null;
|
|
2161
|
-
const candidates = byName.get(stripped);
|
|
2162
|
-
if (!candidates || candidates.length === 0) return null;
|
|
2163
|
-
return candidates[0] ?? null;
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
// kernel/orchestrator/node-identifiers.ts
|
|
2167
|
-
import { posix as pathPosix4 } from "path";
|
|
2168
|
-
function deriveNodeIdentifiers(node, kindDescriptor) {
|
|
2169
|
-
const sources = kindDescriptor?.identifiers;
|
|
2170
|
-
if (!sources || sources.length === 0) return [];
|
|
2171
|
-
const out = [];
|
|
2172
|
-
for (const source of sources) {
|
|
2173
|
-
const raw = readIdentifier(source, node);
|
|
2174
|
-
if (!raw) continue;
|
|
2175
|
-
const normalised = normalizeTrigger(raw);
|
|
2176
|
-
if (normalised) out.push(normalised);
|
|
2108
|
+
for (const source of sources) {
|
|
2109
|
+
const raw = readIdentifier(source, node);
|
|
2110
|
+
if (!raw) continue;
|
|
2111
|
+
const normalised = normalizeTrigger(raw);
|
|
2112
|
+
if (normalised) out.push(normalised);
|
|
2177
2113
|
}
|
|
2178
2114
|
return out;
|
|
2179
2115
|
}
|
|
@@ -2188,16 +2124,16 @@ function readFrontmatterName(node) {
|
|
|
2188
2124
|
return raw.length > 0 ? raw : null;
|
|
2189
2125
|
}
|
|
2190
2126
|
function readFilenameBasename(node) {
|
|
2191
|
-
const base =
|
|
2127
|
+
const base = pathPosix3.basename(node.path);
|
|
2192
2128
|
if (!base) return null;
|
|
2193
|
-
const ext =
|
|
2129
|
+
const ext = pathPosix3.extname(base);
|
|
2194
2130
|
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
2195
2131
|
return stem.length > 0 ? stem : null;
|
|
2196
2132
|
}
|
|
2197
2133
|
function readDirname(node) {
|
|
2198
|
-
const dir =
|
|
2134
|
+
const dir = pathPosix3.dirname(node.path);
|
|
2199
2135
|
if (!dir || dir === "." || dir === "/") return null;
|
|
2200
|
-
const base =
|
|
2136
|
+
const base = pathPosix3.basename(dir);
|
|
2201
2137
|
return base.length > 0 ? base : null;
|
|
2202
2138
|
}
|
|
2203
2139
|
|
|
@@ -2266,8 +2202,8 @@ function kindKey(node) {
|
|
|
2266
2202
|
return `${node.provider}/${node.kind}`;
|
|
2267
2203
|
}
|
|
2268
2204
|
|
|
2269
|
-
// plugins/core/analyzers/reserved
|
|
2270
|
-
var
|
|
2205
|
+
// plugins/core/analyzers/name-reserved/text.ts
|
|
2206
|
+
var NAME_RESERVED_TEXTS = {
|
|
2271
2207
|
/**
|
|
2272
2208
|
* Target-side message: emitted on the user file that collides with
|
|
2273
2209
|
* a runtime built-in. Same wording skill-map shipped before the
|
|
@@ -2284,14 +2220,14 @@ var RESERVED_NAME_TEXTS = {
|
|
|
2284
2220
|
linkMessage: "Link `{{kind}} {{target}}` resolves to a name reserved by the {{provider}} runtime ({{reservedKind}} `{{reservedPath}}`). The runtime shadows the user file, so this edge is downgraded to confidence {{confidence}} instead of 1.0. Rename the target file or its `frontmatter.name` to a non-reserved value."
|
|
2285
2221
|
};
|
|
2286
2222
|
|
|
2287
|
-
// plugins/core/analyzers/reserved
|
|
2288
|
-
var ID16 = "reserved
|
|
2289
|
-
var
|
|
2223
|
+
// plugins/core/analyzers/name-reserved/index.ts
|
|
2224
|
+
var ID16 = "name-reserved";
|
|
2225
|
+
var nameReservedAnalyzer = {
|
|
2290
2226
|
id: ID16,
|
|
2291
|
-
pluginId:
|
|
2227
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2292
2228
|
kind: "analyzer",
|
|
2293
2229
|
version: "1.0.0",
|
|
2294
|
-
description: "Flags reserved-name
|
|
2230
|
+
description: "Flags two kinds of reserved-name collision: a file whose name shadows a built-in command of the active runtime, and a link that resolves to one of those reserved names.",
|
|
2295
2231
|
mode: "deterministic",
|
|
2296
2232
|
// eslint-disable-next-line complexity
|
|
2297
2233
|
evaluate(ctx) {
|
|
@@ -2307,7 +2243,7 @@ var reservedNameAnalyzer = {
|
|
|
2307
2243
|
analyzerId: ID16,
|
|
2308
2244
|
severity: "warn",
|
|
2309
2245
|
nodeIds: [node.path],
|
|
2310
|
-
message: tx(
|
|
2246
|
+
message: tx(NAME_RESERVED_TEXTS.message, {
|
|
2311
2247
|
path: node.path,
|
|
2312
2248
|
provider: node.provider,
|
|
2313
2249
|
kind: node.kind
|
|
@@ -2323,7 +2259,7 @@ var reservedNameAnalyzer = {
|
|
|
2323
2259
|
analyzerId: ID16,
|
|
2324
2260
|
severity: "warn",
|
|
2325
2261
|
nodeIds: [link.source],
|
|
2326
|
-
message: tx(
|
|
2262
|
+
message: tx(NAME_RESERVED_TEXTS.linkMessage, {
|
|
2327
2263
|
kind: link.kind,
|
|
2328
2264
|
target: link.target,
|
|
2329
2265
|
provider: reservedNode.provider,
|
|
@@ -2380,206 +2316,16 @@ function normaliseId(raw) {
|
|
|
2380
2316
|
return raw.normalize("NFD").replace(new RegExp("\\p{Mn}+", "gu"), "").toLowerCase().replace(/[-_\s]+/g, " ").replace(/ +/g, " ").trim();
|
|
2381
2317
|
}
|
|
2382
2318
|
|
|
2383
|
-
// plugins/core/analyzers/
|
|
2384
|
-
var
|
|
2385
|
-
/**
|
|
2386
|
-
* Per-edge warn: a node body references itself via the slash /
|
|
2387
|
-
* at-directive / markdown-link surface (most commonly because the
|
|
2388
|
-
* file's heading IS the invocation token, e.g. `# /deploy` inside
|
|
2389
|
-
* `commands/deploy.md`). The link is structurally valid but rarely
|
|
2390
|
-
* the operator's intent; UI consumers MAY hide it by default and
|
|
2391
|
-
* surface a count.
|
|
2392
|
-
*/
|
|
2393
|
-
message: "{{source}} references itself via `{{trigger}}` ({{kind}}). Self-loops typically come from the file's own heading or label and are noise rather than intent. Either remove the in-body token or treat this finding as expected and acknowledged."
|
|
2394
|
-
};
|
|
2395
|
-
|
|
2396
|
-
// plugins/core/analyzers/self-loop/index.ts
|
|
2397
|
-
var ID17 = "self-loop";
|
|
2398
|
-
var selfLoopAnalyzer = {
|
|
2399
|
-
id: ID17,
|
|
2400
|
-
pluginId: "core",
|
|
2401
|
-
kind: "analyzer",
|
|
2402
|
-
version: "1.0.0",
|
|
2403
|
-
description: "Flags links whose source is its own resolved target (a body heading like `# /deploy` inside the file that defines `/deploy`). One warn per self-looping link, attached to the source. UI consumers hide self-loops by default; this analyzer is the authoritative detector.",
|
|
2404
|
-
mode: "deterministic",
|
|
2405
|
-
evaluate(ctx) {
|
|
2406
|
-
if (ctx.links.length === 0) return [];
|
|
2407
|
-
const issues = [];
|
|
2408
|
-
for (const link of ctx.links) {
|
|
2409
|
-
if (!isSelfLoop(link)) continue;
|
|
2410
|
-
issues.push({
|
|
2411
|
-
analyzerId: ID17,
|
|
2412
|
-
severity: "warn",
|
|
2413
|
-
nodeIds: [link.source],
|
|
2414
|
-
message: tx(SELF_LOOP_TEXTS.message, {
|
|
2415
|
-
source: link.source,
|
|
2416
|
-
trigger: link.trigger?.originalTrigger ?? link.target,
|
|
2417
|
-
kind: link.kind
|
|
2418
|
-
}),
|
|
2419
|
-
data: {
|
|
2420
|
-
target: link.target,
|
|
2421
|
-
resolvedTarget: link.resolvedTarget ?? link.target,
|
|
2422
|
-
kind: link.kind,
|
|
2423
|
-
// Mark explicitly so UI / downstream consumers can read this
|
|
2424
|
-
// single field instead of re-computing the `source === target`
|
|
2425
|
-
// predicate themselves.
|
|
2426
|
-
selfLoop: true
|
|
2427
|
-
}
|
|
2428
|
-
});
|
|
2429
|
-
}
|
|
2430
|
-
return issues;
|
|
2431
|
-
}
|
|
2432
|
-
};
|
|
2433
|
-
function isSelfLoop(link) {
|
|
2434
|
-
if (link.source === link.target) return true;
|
|
2435
|
-
if (link.resolvedTarget && link.source === link.resolvedTarget) return true;
|
|
2436
|
-
return false;
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
// plugins/core/analyzers/signal-collision/text.ts
|
|
2440
|
-
var SIGNAL_COLLISION_TEXTS = {
|
|
2441
|
-
/**
|
|
2442
|
-
* Per-Signal warn issue: two extractors detected something at
|
|
2443
|
-
* overlapping byte ranges within the same node and the resolver
|
|
2444
|
-
* dropped the loser. Surfaces WHO lost, WHO won, and the tiebreak
|
|
2445
|
-
* reason so the operator can understand why a candidate edge did NOT
|
|
2446
|
-
* become a Link.
|
|
2447
|
-
*
|
|
2448
|
-
* Placeholders are deliberately verbose because this is one of the
|
|
2449
|
-
* few diagnostic surfaces where the operator may need to disambiguate
|
|
2450
|
-
* a confusing graph (e.g. a `[link](path)` followed by `@path` inside
|
|
2451
|
-
* the same paragraph, the markdown-link wins and the at-directive
|
|
2452
|
-
* silently disappears without this warning).
|
|
2453
|
-
*/
|
|
2454
|
-
message: "{{loserExtractor}} detected `{{loserRaw}}` at offset {{loserRange}} but {{winnerExtractor}}'s detection at {{winnerRange}} won the overlap collision ({{reason}}). The graph shows the winning edge only; the loser is not persisted.",
|
|
2455
|
-
/**
|
|
2456
|
-
* Same warn but for the rare case the resolver rejected a Signal
|
|
2457
|
-
* because the operator disabled its extractor via
|
|
2458
|
-
* `plugins.<id>.extensions.<extId>.enabled`. Phase 4+ stub: today the
|
|
2459
|
-
* filter is not wired so this template is unreachable from the
|
|
2460
|
-
* resolver; documented now so the analyzer stays forward-compatible
|
|
2461
|
-
* with the upcoming filter pass.
|
|
2462
|
-
*/
|
|
2463
|
-
messageExtractorDisabled: "Extension `{{extractorId}}` is disabled; its detection `{{loserRaw}}` (offset {{loserRange}}) did not produce a Link. Re-enable the extension in Settings or via `sm plugins enable` to surface its edges.",
|
|
2464
|
-
/**
|
|
2465
|
-
* Same warn but for the future confidence floor case. Phase 4+ stub:
|
|
2466
|
-
* today the resolver materialises every winning candidate regardless
|
|
2467
|
-
* of confidence, so this template is unreachable; documented for
|
|
2468
|
-
* forward compatibility.
|
|
2469
|
-
*/
|
|
2470
|
-
messageBelowFloor: "Detection `{{loserRaw}}` (offset {{loserRange}}, confidence {{confidence}}) fell below the configured threshold {{threshold}} and was dropped."
|
|
2471
|
-
};
|
|
2472
|
-
|
|
2473
|
-
// plugins/core/analyzers/signal-collision/index.ts
|
|
2474
|
-
var ID18 = "signal-collision";
|
|
2475
|
-
var signalCollisionAnalyzer = {
|
|
2476
|
-
id: ID18,
|
|
2477
|
-
pluginId: "core",
|
|
2478
|
-
kind: "analyzer",
|
|
2479
|
-
version: "1.0.0",
|
|
2480
|
-
description: "Surfaces Signal IR resolver rejections (range-overlap losers, disabled extractors, below-floor candidates) as warn issues attached to the Signal's source node.",
|
|
2481
|
-
mode: "deterministic",
|
|
2482
|
-
evaluate(ctx) {
|
|
2483
|
-
const signals = ctx.signals;
|
|
2484
|
-
if (!signals || signals.length === 0) return [];
|
|
2485
|
-
const issues = [];
|
|
2486
|
-
for (const signal of signals) {
|
|
2487
|
-
const issue = makeIssue(signal);
|
|
2488
|
-
if (issue) issues.push(issue);
|
|
2489
|
-
}
|
|
2490
|
-
return issues;
|
|
2491
|
-
}
|
|
2492
|
-
};
|
|
2493
|
-
function makeIssue(signal) {
|
|
2494
|
-
const resolution = signal.resolution;
|
|
2495
|
-
if (!resolution || resolution.outcome !== "rejected") return null;
|
|
2496
|
-
if (resolution.rejectedBy) {
|
|
2497
|
-
const winner = resolution.rejectedBy;
|
|
2498
|
-
const winnerCandidate = signal.candidates[resolution.winnerIndex ?? 0];
|
|
2499
|
-
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
2500
|
-
const winnerRange = `${winner.range.start}-${winner.range.end}`;
|
|
2501
|
-
return {
|
|
2502
|
-
analyzerId: ID18,
|
|
2503
|
-
severity: "warn",
|
|
2504
|
-
nodeIds: [signal.source],
|
|
2505
|
-
message: tx(SIGNAL_COLLISION_TEXTS.message, {
|
|
2506
|
-
loserExtractor: winnerCandidate.extractorId,
|
|
2507
|
-
loserRaw: signal.raw,
|
|
2508
|
-
loserRange,
|
|
2509
|
-
winnerExtractor: winner.extractorId,
|
|
2510
|
-
winnerRange,
|
|
2511
|
-
reason: winner.reason
|
|
2512
|
-
}),
|
|
2513
|
-
data: {
|
|
2514
|
-
loser: {
|
|
2515
|
-
extractorId: winnerCandidate.extractorId,
|
|
2516
|
-
raw: signal.raw,
|
|
2517
|
-
range: signal.range ?? null,
|
|
2518
|
-
candidate: {
|
|
2519
|
-
kind: winnerCandidate.kind,
|
|
2520
|
-
target: winnerCandidate.target,
|
|
2521
|
-
confidence: winnerCandidate.confidence
|
|
2522
|
-
}
|
|
2523
|
-
},
|
|
2524
|
-
winner: {
|
|
2525
|
-
extractorId: winner.extractorId,
|
|
2526
|
-
range: winner.range
|
|
2527
|
-
},
|
|
2528
|
-
reason: winner.reason
|
|
2529
|
-
}
|
|
2530
|
-
};
|
|
2531
|
-
}
|
|
2532
|
-
if (resolution.extractorDisabled) {
|
|
2533
|
-
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
2534
|
-
return {
|
|
2535
|
-
analyzerId: ID18,
|
|
2536
|
-
severity: "warn",
|
|
2537
|
-
nodeIds: [signal.source],
|
|
2538
|
-
message: tx(SIGNAL_COLLISION_TEXTS.messageExtractorDisabled, {
|
|
2539
|
-
extractorId: resolution.extractorDisabled.extractorId,
|
|
2540
|
-
loserRaw: signal.raw,
|
|
2541
|
-
loserRange
|
|
2542
|
-
}),
|
|
2543
|
-
data: {
|
|
2544
|
-
extractorDisabled: resolution.extractorDisabled,
|
|
2545
|
-
raw: signal.raw,
|
|
2546
|
-
range: signal.range ?? null
|
|
2547
|
-
}
|
|
2548
|
-
};
|
|
2549
|
-
}
|
|
2550
|
-
if (resolution.belowFloor) {
|
|
2551
|
-
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
2552
|
-
const topCandidate = signal.candidates[0];
|
|
2553
|
-
return {
|
|
2554
|
-
analyzerId: ID18,
|
|
2555
|
-
severity: "warn",
|
|
2556
|
-
nodeIds: [signal.source],
|
|
2557
|
-
message: tx(SIGNAL_COLLISION_TEXTS.messageBelowFloor, {
|
|
2558
|
-
loserRaw: signal.raw,
|
|
2559
|
-
loserRange,
|
|
2560
|
-
confidence: topCandidate.confidence,
|
|
2561
|
-
threshold: resolution.belowFloor.threshold
|
|
2562
|
-
}),
|
|
2563
|
-
data: {
|
|
2564
|
-
belowFloor: resolution.belowFloor,
|
|
2565
|
-
raw: signal.raw,
|
|
2566
|
-
range: signal.range ?? null
|
|
2567
|
-
}
|
|
2568
|
-
};
|
|
2569
|
-
}
|
|
2570
|
-
return null;
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
|
-
// plugins/core/analyzers/stability/index.ts
|
|
2574
|
-
var ID19 = "stability";
|
|
2319
|
+
// plugins/core/analyzers/node-stability/index.ts
|
|
2320
|
+
var ID17 = "node-stability";
|
|
2575
2321
|
var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
|
|
2576
2322
|
var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
|
|
2577
|
-
var
|
|
2578
|
-
id:
|
|
2579
|
-
pluginId:
|
|
2323
|
+
var nodeStabilityAnalyzer = {
|
|
2324
|
+
id: ID17,
|
|
2325
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2580
2326
|
kind: "analyzer",
|
|
2581
2327
|
version: "1.0.0",
|
|
2582
|
-
description: "Reports node
|
|
2328
|
+
description: "Reports a node's stability stage (`experimental`, `deprecated`) on the card.",
|
|
2583
2329
|
mode: "deterministic",
|
|
2584
2330
|
ui: {
|
|
2585
2331
|
experimental: {
|
|
@@ -2607,7 +2353,7 @@ var stabilityAnalyzer = {
|
|
|
2607
2353
|
tooltip: EXPERIMENTAL_TOOLTIP
|
|
2608
2354
|
});
|
|
2609
2355
|
issues.push({
|
|
2610
|
-
analyzerId:
|
|
2356
|
+
analyzerId: ID17,
|
|
2611
2357
|
severity: "info",
|
|
2612
2358
|
nodeIds: [node.path],
|
|
2613
2359
|
message: `Node '${node.path}' is marked experimental: API may change.`,
|
|
@@ -2620,7 +2366,7 @@ var stabilityAnalyzer = {
|
|
|
2620
2366
|
severity: "warn"
|
|
2621
2367
|
});
|
|
2622
2368
|
issues.push({
|
|
2623
|
-
analyzerId:
|
|
2369
|
+
analyzerId: ID17,
|
|
2624
2370
|
severity: "warn",
|
|
2625
2371
|
nodeIds: [node.path],
|
|
2626
2372
|
message: `Node '${node.path}' is marked deprecated: avoid in new code.`,
|
|
@@ -2647,20 +2393,20 @@ function isStability(value) {
|
|
|
2647
2393
|
return value === "experimental" || value === "deprecated" || value === "stable";
|
|
2648
2394
|
}
|
|
2649
2395
|
|
|
2650
|
-
// plugins/core/analyzers/superseded/text.ts
|
|
2651
|
-
var
|
|
2396
|
+
// plugins/core/analyzers/node-superseded/text.ts
|
|
2397
|
+
var NODE_SUPERSEDED_TEXTS = {
|
|
2652
2398
|
/** `<path> is superseded by <supersededBy>` */
|
|
2653
2399
|
message: "{{path}} is superseded by {{supersededBy}}"
|
|
2654
2400
|
};
|
|
2655
2401
|
|
|
2656
|
-
// plugins/core/analyzers/superseded/index.ts
|
|
2657
|
-
var
|
|
2658
|
-
var
|
|
2659
|
-
id:
|
|
2660
|
-
pluginId:
|
|
2402
|
+
// plugins/core/analyzers/node-superseded/index.ts
|
|
2403
|
+
var ID18 = "node-superseded";
|
|
2404
|
+
var nodeSupersededAnalyzer = {
|
|
2405
|
+
id: ID18,
|
|
2406
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2661
2407
|
kind: "analyzer",
|
|
2662
2408
|
version: "1.0.0",
|
|
2663
|
-
description: "
|
|
2409
|
+
description: "Marks nodes replaced by a newer one via `supersededBy`.",
|
|
2664
2410
|
mode: "deterministic",
|
|
2665
2411
|
evaluate(ctx) {
|
|
2666
2412
|
const issues = [];
|
|
@@ -2668,10 +2414,10 @@ var supersededAnalyzer = {
|
|
|
2668
2414
|
const supersededBy = pickSupersededBy(node);
|
|
2669
2415
|
if (supersededBy === null) continue;
|
|
2670
2416
|
issues.push({
|
|
2671
|
-
analyzerId:
|
|
2417
|
+
analyzerId: ID18,
|
|
2672
2418
|
severity: "info",
|
|
2673
2419
|
nodeIds: [node.path],
|
|
2674
|
-
message: tx(
|
|
2420
|
+
message: tx(NODE_SUPERSEDED_TEXTS.message, {
|
|
2675
2421
|
path: node.path,
|
|
2676
2422
|
supersededBy
|
|
2677
2423
|
}),
|
|
@@ -2691,351 +2437,334 @@ function pickSupersededBy(node) {
|
|
|
2691
2437
|
return value;
|
|
2692
2438
|
}
|
|
2693
2439
|
|
|
2694
|
-
// plugins/core/analyzers/
|
|
2695
|
-
|
|
2696
|
-
/**
|
|
2697
|
-
* Top-level message when `analyzeTriggerBucket` accumulated exactly one
|
|
2698
|
-
* cause part. Used for the advertiser-ambiguous-only, invocation-
|
|
2699
|
-
* ambiguous-only, and cross-kind-only branches.
|
|
2700
|
-
*/
|
|
2701
|
-
messageOnePart: 'Trigger "{{normalized}}" has {{part}}.',
|
|
2702
|
-
/**
|
|
2703
|
-
* Top-level message when `analyzeTriggerBucket` accumulated two cause
|
|
2704
|
-
* parts (advertiser-ambiguous AND invocation-ambiguous fire together).
|
|
2705
|
-
* The joiner lives inside the template so future locales can adapt it
|
|
2706
|
-
* (e.g. `'; y '` in Spanish) without touching the rule code.
|
|
2707
|
-
*/
|
|
2708
|
-
messageTwoParts: 'Trigger "{{normalized}}" has {{first}}; and {{second}}.',
|
|
2709
|
-
/** `<n> nodes advertise it: <list>` part, fires on the advertiser-ambiguous branch. */
|
|
2710
|
-
partAdvertisers: "{{count}} nodes advertise it: {{paths}}",
|
|
2711
|
-
/** `<n> distinct invocation forms: <list>` part, fires on the invocation-ambiguous branch. */
|
|
2712
|
-
partInvocations: "{{count}} distinct invocation forms: {{forms}}",
|
|
2713
|
-
/** Singular cross-kind cause: `non-canonical invocation <form> against advertiser <path>`. */
|
|
2714
|
-
partNonCanonicalSingular: "non-canonical invocation {{forms}} against advertiser {{advertiser}}",
|
|
2715
|
-
/** Plural cross-kind cause: `non-canonical invocations <forms> against advertiser <path>`. */
|
|
2716
|
-
partNonCanonicalPlural: "non-canonical invocations {{forms}} against advertiser {{advertiser}}"
|
|
2717
|
-
};
|
|
2718
|
-
|
|
2719
|
-
// plugins/core/analyzers/trigger-collision/index.ts
|
|
2720
|
-
var ID21 = "trigger-collision";
|
|
2721
|
-
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
2722
|
-
"command",
|
|
2723
|
-
"skill",
|
|
2724
|
-
"agent"
|
|
2725
|
-
]);
|
|
2726
|
-
var triggerCollisionAnalyzer = {
|
|
2727
|
-
id: ID21,
|
|
2728
|
-
pluginId: "core",
|
|
2729
|
-
kind: "analyzer",
|
|
2730
|
-
mode: "deterministic",
|
|
2731
|
-
version: "1.0.0",
|
|
2732
|
-
description: "Detects and flags two or more nodes claiming the same `/command` or `@agent` name.",
|
|
2733
|
-
// Two claim-collection passes (advertisement + invocation) feeding
|
|
2734
|
-
// the bucket map. Per-bucket analysis lives in `analyzeTriggerBucket`.
|
|
2735
|
-
// eslint-disable-next-line complexity
|
|
2736
|
-
evaluate(ctx) {
|
|
2737
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
2738
|
-
const push = (key, claim) => {
|
|
2739
|
-
const bucket = buckets.get(key) ?? [];
|
|
2740
|
-
bucket.push(claim);
|
|
2741
|
-
buckets.set(key, bucket);
|
|
2742
|
-
};
|
|
2743
|
-
for (const node of ctx.nodes) {
|
|
2744
|
-
if (!ADVERTISING_KINDS.has(node.kind)) continue;
|
|
2745
|
-
const raw = node.frontmatter?.["name"];
|
|
2746
|
-
if (typeof raw !== "string" || raw.length === 0) continue;
|
|
2747
|
-
const normalized = `/${normalizeTrigger(raw)}`;
|
|
2748
|
-
if (normalized === "/") continue;
|
|
2749
|
-
push(normalized, {
|
|
2750
|
-
kind: "advertiser",
|
|
2751
|
-
token: node.path,
|
|
2752
|
-
nodeId: node.path,
|
|
2753
|
-
canonicalForm: `/${raw}`
|
|
2754
|
-
});
|
|
2755
|
-
}
|
|
2756
|
-
for (const link of ctx.links) {
|
|
2757
|
-
const normalized = link.trigger?.normalizedTrigger;
|
|
2758
|
-
if (!normalized) continue;
|
|
2759
|
-
push(normalized, {
|
|
2760
|
-
kind: "invocation",
|
|
2761
|
-
token: link.target,
|
|
2762
|
-
nodeId: link.source
|
|
2763
|
-
});
|
|
2764
|
-
}
|
|
2765
|
-
const issues = [];
|
|
2766
|
-
for (const [normalized, claims] of buckets) {
|
|
2767
|
-
const issue = analyzeTriggerBucket(normalized, claims);
|
|
2768
|
-
if (issue) issues.push(issue);
|
|
2769
|
-
}
|
|
2770
|
-
return issues;
|
|
2771
|
-
}
|
|
2772
|
-
};
|
|
2773
|
-
function analyzeTriggerBucket(normalized, claims) {
|
|
2774
|
-
const advertiserPaths = [
|
|
2775
|
-
...new Set(claims.filter((c) => c.kind === "advertiser").map((c) => c.token))
|
|
2776
|
-
].sort();
|
|
2777
|
-
const invocationTargets = [
|
|
2778
|
-
...new Set(claims.filter((c) => c.kind === "invocation").map((c) => c.token))
|
|
2779
|
-
].sort();
|
|
2780
|
-
const advertisers = claims.filter(
|
|
2781
|
-
(c) => c.kind === "advertiser"
|
|
2782
|
-
);
|
|
2783
|
-
const advertiserAmbiguous = advertiserPaths.length >= 2;
|
|
2784
|
-
const invocationAmbiguous = invocationTargets.length >= 2;
|
|
2785
|
-
const canonicalForms = new Set(advertisers.map((a) => a.canonicalForm));
|
|
2786
|
-
const nonCanonicalInvocations = invocationTargets.filter((t) => !canonicalForms.has(t));
|
|
2787
|
-
const crossKindAmbiguous = advertiserPaths.length === 1 && nonCanonicalInvocations.length >= 1;
|
|
2788
|
-
if (!advertiserAmbiguous && !invocationAmbiguous && !crossKindAmbiguous) {
|
|
2789
|
-
return null;
|
|
2790
|
-
}
|
|
2791
|
-
const nodeIds = [...new Set(claims.map((c) => c.nodeId))].sort();
|
|
2792
|
-
const parts = [];
|
|
2793
|
-
if (advertiserAmbiguous) {
|
|
2794
|
-
parts.push(
|
|
2795
|
-
tx(TRIGGER_COLLISION_TEXTS.partAdvertisers, {
|
|
2796
|
-
count: advertiserPaths.length,
|
|
2797
|
-
paths: advertiserPaths.join(", ")
|
|
2798
|
-
})
|
|
2799
|
-
);
|
|
2800
|
-
}
|
|
2801
|
-
if (invocationAmbiguous) {
|
|
2802
|
-
parts.push(
|
|
2803
|
-
tx(TRIGGER_COLLISION_TEXTS.partInvocations, {
|
|
2804
|
-
count: invocationTargets.length,
|
|
2805
|
-
forms: invocationTargets.join(", ")
|
|
2806
|
-
})
|
|
2807
|
-
);
|
|
2808
|
-
} else if (crossKindAmbiguous) {
|
|
2809
|
-
const template = nonCanonicalInvocations.length > 1 ? TRIGGER_COLLISION_TEXTS.partNonCanonicalPlural : TRIGGER_COLLISION_TEXTS.partNonCanonicalSingular;
|
|
2810
|
-
parts.push(
|
|
2811
|
-
tx(template, {
|
|
2812
|
-
forms: nonCanonicalInvocations.join(", "),
|
|
2813
|
-
advertiser: advertiserPaths[0]
|
|
2814
|
-
})
|
|
2815
|
-
);
|
|
2816
|
-
}
|
|
2817
|
-
const message = parts.length === 2 ? tx(TRIGGER_COLLISION_TEXTS.messageTwoParts, {
|
|
2818
|
-
normalized,
|
|
2819
|
-
first: parts[0],
|
|
2820
|
-
second: parts[1]
|
|
2821
|
-
}) : tx(TRIGGER_COLLISION_TEXTS.messageOnePart, {
|
|
2822
|
-
normalized,
|
|
2823
|
-
part: parts[0]
|
|
2824
|
-
});
|
|
2825
|
-
return {
|
|
2826
|
-
analyzerId: ID21,
|
|
2827
|
-
severity: "error",
|
|
2828
|
-
nodeIds,
|
|
2829
|
-
message,
|
|
2830
|
-
data: {
|
|
2831
|
-
normalizedTrigger: normalized,
|
|
2832
|
-
invocationTargets,
|
|
2833
|
-
advertiserPaths
|
|
2834
|
-
}
|
|
2835
|
-
};
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
|
-
// plugins/core/analyzers/unknown-field/index.ts
|
|
2839
|
-
import { readFileSync } from "fs";
|
|
2840
|
-
import { dirname, resolve as resolve3 } from "path";
|
|
2841
|
-
import { createRequire } from "module";
|
|
2842
|
-
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2843
|
-
|
|
2844
|
-
// kernel/util/ajv-interop.ts
|
|
2845
|
-
import addFormatsModule from "ajv-formats";
|
|
2846
|
-
var addFormats = addFormatsModule.default ?? addFormatsModule;
|
|
2847
|
-
function applyAjvFormats(ajv) {
|
|
2848
|
-
addFormats(ajv);
|
|
2849
|
-
}
|
|
2440
|
+
// plugins/core/analyzers/reference-broken/index.ts
|
|
2441
|
+
import { posix as pathPosix4, resolve as resolve3 } from "path";
|
|
2850
2442
|
|
|
2851
|
-
// plugins/core/analyzers/
|
|
2852
|
-
var
|
|
2853
|
-
/**
|
|
2854
|
-
|
|
2855
|
-
/** Top-level key is neither reserved, nor a registered plugin namespace, nor a registered root key. */
|
|
2856
|
-
unknownRootKey: "{{path}}: sidecar declares unknown top-level key '{{key}}'; not a reserved block, not a registered plugin namespace, not a registered root contribution.",
|
|
2857
|
-
/** Value under a registered plugin namespace fails the contributed schema. */
|
|
2858
|
-
pluginNamespaceInvalid: "{{path}}: sidecar block '{{pluginId}}.{{key}}' fails the schema contributed by plugin '{{pluginId}}': {{errors}}.",
|
|
2443
|
+
// plugins/core/analyzers/reference-broken/text.ts
|
|
2444
|
+
var REFERENCE_BROKEN_TEXTS = {
|
|
2445
|
+
/** `Broken <kind> reference from <source> → <target>` */
|
|
2446
|
+
message: "Broken {{kind}} reference from {{source}} \u2192 {{target}}",
|
|
2859
2447
|
// Tooltips for the per-node view-contribution badges. Singular vs
|
|
2860
2448
|
// plural keeps the count grammar correct without a sub-template.
|
|
2861
|
-
alertTooltipSingle: "This node has
|
|
2862
|
-
alertTooltipMany: "This node has {{count}}
|
|
2449
|
+
alertTooltipSingle: "This node has a broken reference. Open the inspector for details.",
|
|
2450
|
+
alertTooltipMany: "This node has {{count}} broken references. Open the inspector for details.",
|
|
2451
|
+
// Fix-summary copy when the broken trigger has a same-named file on
|
|
2452
|
+
// disk that does not advertise `name:` in its frontmatter. Two
|
|
2453
|
+
// variants for single vs multiple candidates; same template family
|
|
2454
|
+
// as the alert tooltips above.
|
|
2455
|
+
hintSummarySingle: "Add `name: {{name}}` to the frontmatter of {{candidate}} so this reference resolves.",
|
|
2456
|
+
hintSummaryMany: "Add `name: {{name}}` to the frontmatter of one of these files so this reference resolves: {{candidates}}."
|
|
2863
2457
|
};
|
|
2864
2458
|
|
|
2865
|
-
// plugins/core/analyzers/
|
|
2866
|
-
var
|
|
2867
|
-
var
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
pluginId: "core",
|
|
2459
|
+
// plugins/core/analyzers/reference-broken/index.ts
|
|
2460
|
+
var ID19 = "reference-broken";
|
|
2461
|
+
var referenceBrokenAnalyzer = {
|
|
2462
|
+
id: ID19,
|
|
2463
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2871
2464
|
kind: "analyzer",
|
|
2872
2465
|
version: "1.0.0",
|
|
2873
|
-
description: "
|
|
2466
|
+
description: "Flags arrows pointing at a node not part of the current scan.",
|
|
2874
2467
|
mode: "deterministic",
|
|
2875
2468
|
ui: {
|
|
2876
2469
|
// Corner badge on the graph card; count omitted when there is a
|
|
2877
|
-
// single
|
|
2470
|
+
// single broken ref (avoids a noisy "icon + 1" chip).
|
|
2878
2471
|
alert: {
|
|
2879
2472
|
slot: "graph.node.alert",
|
|
2880
|
-
|
|
2881
|
-
// alert's "attention-grabbing solid" pattern; the footer chip
|
|
2882
|
-
// below stays outlined for the quieter counter pairing.
|
|
2883
|
-
icon: "fa-solid fa-triangle-exclamation",
|
|
2473
|
+
icon: "fa-solid fa-circle-xmark",
|
|
2884
2474
|
emitWhenEmpty: false
|
|
2885
2475
|
},
|
|
2886
|
-
// Footer chip on the card
|
|
2887
|
-
//
|
|
2888
|
-
//
|
|
2889
|
-
//
|
|
2890
|
-
//
|
|
2891
|
-
// is required: with `value: 0` the slot treats the payload as
|
|
2892
|
-
// empty, so the manifest has to opt in to keep the emission.
|
|
2476
|
+
// Footer chip on the card. `_counter` shape, `value` always shows,
|
|
2477
|
+
// so the operator sees "how many" at a glance. Renders OUTLINED
|
|
2478
|
+
// (`fa-regular`) so the corner alert (filled, attention-grabbing)
|
|
2479
|
+
// and the footer chip (quieter, paired with a number) read as two
|
|
2480
|
+
// beats of the same signal rather than two identical glyphs.
|
|
2893
2481
|
chip: {
|
|
2894
2482
|
slot: "card.footer.right",
|
|
2895
|
-
icon: "
|
|
2896
|
-
emitWhenEmpty:
|
|
2897
|
-
priority:
|
|
2483
|
+
icon: "fa-regular fa-circle-xmark",
|
|
2484
|
+
emitWhenEmpty: false,
|
|
2485
|
+
priority: 40
|
|
2898
2486
|
}
|
|
2899
2487
|
},
|
|
2900
|
-
//
|
|
2901
|
-
//
|
|
2902
|
-
//
|
|
2903
|
-
//
|
|
2904
|
-
// `context/lint.md` category 7 (recursive type-discriminator walkers).
|
|
2488
|
+
// The resolver, the reference-paths escape hatch, the per-source
|
|
2489
|
+
// aggregation, and the dual-slot emit (with single/plural tooltip and
|
|
2490
|
+
// optional count) all live in one flow because they share the per-link
|
|
2491
|
+
// loop. Splitting them would re-walk `ctx.links` three times.
|
|
2905
2492
|
// eslint-disable-next-line complexity
|
|
2906
2493
|
evaluate(ctx) {
|
|
2907
|
-
const
|
|
2908
|
-
|
|
2909
|
-
const
|
|
2910
|
-
const
|
|
2911
|
-
const namespacedByPlugin = indexNamespacedContributions(contributions);
|
|
2912
|
-
const rootKeys = indexRootContributions(contributions);
|
|
2913
|
-
const knownPluginIds = collectPluginIds(contributions);
|
|
2494
|
+
const byPath3 = new Set(ctx.nodes.map((n) => n.path));
|
|
2495
|
+
const byNormalizedName = indexByNormalizedName(ctx.nodes);
|
|
2496
|
+
const byBasenameWithoutName = indexByBasenameWithoutName(ctx.nodes);
|
|
2497
|
+
const refIndex = ctx.referenceablePaths && ctx.referenceablePaths.size > 0 && ctx.cwd ? { paths: ctx.referenceablePaths, cwd: ctx.cwd } : null;
|
|
2914
2498
|
const issues = [];
|
|
2915
2499
|
const perNode = /* @__PURE__ */ new Map();
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2500
|
+
for (const link of ctx.links) {
|
|
2501
|
+
if (isResolved(link, byPath3, byNormalizedName)) continue;
|
|
2502
|
+
if (refIndex && resolvesViaReferencePaths(link, refIndex)) continue;
|
|
2503
|
+
const candidates = findHintCandidates(link, byBasenameWithoutName);
|
|
2504
|
+
issues.push(buildIssue(link, candidates));
|
|
2505
|
+
perNode.set(link.source, (perNode.get(link.source) ?? 0) + 1);
|
|
2506
|
+
}
|
|
2507
|
+
for (const [nodePath, count] of perNode) {
|
|
2508
|
+
const tooltip = count === 1 ? REFERENCE_BROKEN_TEXTS.alertTooltipSingle : tx(REFERENCE_BROKEN_TEXTS.alertTooltipMany, { count });
|
|
2509
|
+
const capped = Math.min(count, 99);
|
|
2510
|
+
ctx.emitContribution(nodePath, "alert", {
|
|
2511
|
+
icon: "fa-solid fa-circle-xmark",
|
|
2512
|
+
severity: "danger",
|
|
2513
|
+
tooltip
|
|
2514
|
+
});
|
|
2515
|
+
ctx.emitContribution(nodePath, "chip", {
|
|
2516
|
+
value: capped,
|
|
2517
|
+
severity: "danger",
|
|
2518
|
+
tooltip
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
return issues;
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
2524
|
+
function buildIssue(link, hintCandidates = []) {
|
|
2525
|
+
const data = {
|
|
2526
|
+
target: link.target,
|
|
2527
|
+
kind: link.kind,
|
|
2528
|
+
trigger: link.trigger?.normalizedTrigger ?? null
|
|
2529
|
+
};
|
|
2530
|
+
const issue = {
|
|
2531
|
+
analyzerId: ID19,
|
|
2532
|
+
severity: "warn",
|
|
2533
|
+
nodeIds: [link.source],
|
|
2534
|
+
message: tx(REFERENCE_BROKEN_TEXTS.message, {
|
|
2535
|
+
kind: link.kind,
|
|
2536
|
+
source: link.source,
|
|
2537
|
+
target: link.target
|
|
2538
|
+
}),
|
|
2539
|
+
data
|
|
2540
|
+
};
|
|
2541
|
+
if (hintCandidates.length > 0) {
|
|
2542
|
+
const suggestedName = (link.trigger?.normalizedTrigger ?? "").replace(/^[/@]/, "").trim();
|
|
2543
|
+
const candidatePaths = hintCandidates.map((n) => n.path);
|
|
2544
|
+
data["hint"] = {
|
|
2545
|
+
kind: "missing-frontmatter-name",
|
|
2546
|
+
suggestedName,
|
|
2547
|
+
candidates: candidatePaths
|
|
2918
2548
|
};
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2549
|
+
issue.fix = {
|
|
2550
|
+
summary: candidatePaths.length === 1 ? tx(REFERENCE_BROKEN_TEXTS.hintSummarySingle, {
|
|
2551
|
+
name: suggestedName,
|
|
2552
|
+
candidate: candidatePaths[0]
|
|
2553
|
+
}) : tx(REFERENCE_BROKEN_TEXTS.hintSummaryMany, {
|
|
2554
|
+
name: suggestedName,
|
|
2555
|
+
candidates: candidatePaths.join(", ")
|
|
2556
|
+
}),
|
|
2557
|
+
autofixable: false
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
return issue;
|
|
2561
|
+
}
|
|
2562
|
+
function resolvesViaReferencePaths(link, refIndex) {
|
|
2563
|
+
if (!isPathStyleLink(link)) return false;
|
|
2564
|
+
return refIndex.paths.has(resolve3(refIndex.cwd, link.target));
|
|
2565
|
+
}
|
|
2566
|
+
function indexByNormalizedName(nodes) {
|
|
2567
|
+
const out = /* @__PURE__ */ new Map();
|
|
2568
|
+
for (const node of nodes) {
|
|
2569
|
+
const raw = node.frontmatter?.["name"];
|
|
2570
|
+
const name = typeof raw === "string" ? raw : "";
|
|
2571
|
+
if (!name) continue;
|
|
2572
|
+
const key = normalizeTrigger(name);
|
|
2573
|
+
const bucket = out.get(key) ?? [];
|
|
2574
|
+
bucket.push(node);
|
|
2575
|
+
out.set(key, bucket);
|
|
2576
|
+
}
|
|
2577
|
+
return out;
|
|
2578
|
+
}
|
|
2579
|
+
function basenameWithoutExt(path) {
|
|
2580
|
+
const base = pathPosix4.basename(path);
|
|
2581
|
+
const ext = pathPosix4.extname(base);
|
|
2582
|
+
return ext ? base.slice(0, -ext.length) : base;
|
|
2583
|
+
}
|
|
2584
|
+
function indexByBasenameWithoutName(nodes) {
|
|
2585
|
+
const out = /* @__PURE__ */ new Map();
|
|
2586
|
+
for (const node of nodes) {
|
|
2587
|
+
const raw = node.frontmatter?.["name"];
|
|
2588
|
+
const name = typeof raw === "string" ? raw : "";
|
|
2589
|
+
if (name) continue;
|
|
2590
|
+
const bare = basenameWithoutExt(node.path);
|
|
2591
|
+
if (!bare) continue;
|
|
2592
|
+
const key = normalizeTrigger(bare);
|
|
2593
|
+
if (!key) continue;
|
|
2594
|
+
const bucket = out.get(key) ?? [];
|
|
2595
|
+
bucket.push(node);
|
|
2596
|
+
out.set(key, bucket);
|
|
2597
|
+
}
|
|
2598
|
+
return out;
|
|
2599
|
+
}
|
|
2600
|
+
function findHintCandidates(link, idx) {
|
|
2601
|
+
const normalized = link.trigger?.normalizedTrigger;
|
|
2602
|
+
if (!normalized) return [];
|
|
2603
|
+
const sigil = normalized.charAt(0);
|
|
2604
|
+
if (sigil !== "/" && sigil !== "@") return [];
|
|
2605
|
+
const withoutSigil = normalized.slice(1).trim();
|
|
2606
|
+
if (!withoutSigil) return [];
|
|
2607
|
+
return idx.get(withoutSigil) ?? [];
|
|
2608
|
+
}
|
|
2609
|
+
function isResolved(link, byPath3, byNormalizedName) {
|
|
2610
|
+
const normalized = link.trigger?.normalizedTrigger;
|
|
2611
|
+
if (normalized) {
|
|
2612
|
+
const withoutSigil = normalized.replace(/^[/@]/, "").trim();
|
|
2613
|
+
if (byNormalizedName.has(withoutSigil)) return true;
|
|
2614
|
+
}
|
|
2615
|
+
if (byPath3.has(link.target)) return true;
|
|
2616
|
+
return false;
|
|
2617
|
+
}
|
|
2618
|
+
function isPathStyleLink(link) {
|
|
2619
|
+
const sigil = link.trigger?.normalizedTrigger?.charAt(0);
|
|
2620
|
+
if (sigil === "/" || sigil === "@") return false;
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
// plugins/core/analyzers/reference-redundant/text.ts
|
|
2625
|
+
var REFERENCE_REDUNDANT_TEXTS = {
|
|
2626
|
+
/**
|
|
2627
|
+
* Multi-form / multi-occurrence reference message. Lists each
|
|
2628
|
+
* occurrence (trigger + line) so the operator sees the full
|
|
2629
|
+
* authorial surface without having to grep the body.
|
|
2630
|
+
*/
|
|
2631
|
+
message: "{{source}} references {{resolvedTarget}} via {{count}} occurrences: {{occurrences}}. Consider consolidating to a single form to reduce maintenance surface and avoid duplicate inlining at runtime.",
|
|
2632
|
+
/** Inline separator between occurrences in the message. */
|
|
2633
|
+
occurrenceSeparator: ", ",
|
|
2634
|
+
/** Per-occurrence formatting (trigger + line). */
|
|
2635
|
+
occurrence: "`{{trigger}}` ({{kind}}, line {{line}})",
|
|
2636
|
+
/** Per-occurrence formatting when the extractor did not record a line. */
|
|
2637
|
+
occurrenceUnknownLine: "`{{trigger}}` ({{kind}}, unknown line)"
|
|
2638
|
+
};
|
|
2639
|
+
|
|
2640
|
+
// plugins/core/analyzers/reference-redundant/index.ts
|
|
2641
|
+
var ID20 = "reference-redundant";
|
|
2642
|
+
var referenceRedundantAnalyzer = {
|
|
2643
|
+
id: ID20,
|
|
2644
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2645
|
+
kind: "analyzer",
|
|
2646
|
+
version: "1.0.0",
|
|
2647
|
+
description: "Flags when one node references the same target through two or more different links (e.g. a markdown link plus a `references:` entry).",
|
|
2648
|
+
mode: "deterministic",
|
|
2649
|
+
evaluate(ctx) {
|
|
2650
|
+
if (ctx.links.length === 0) return [];
|
|
2651
|
+
const byPath3 = /* @__PURE__ */ new Map();
|
|
2652
|
+
for (const node of ctx.nodes) byPath3.set(node.path, node);
|
|
2653
|
+
const byName = buildNameIndex2(ctx.nodes);
|
|
2654
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2655
|
+
for (const link of ctx.links) {
|
|
2656
|
+
const resolved = resolveTargetPath(link, byPath3, byName);
|
|
2657
|
+
if (!resolved) continue;
|
|
2658
|
+
const key = `${link.source}\0${resolved}`;
|
|
2659
|
+
const bucket = groups.get(key);
|
|
2660
|
+
if (bucket) bucket.push(link);
|
|
2661
|
+
else groups.set(key, [link]);
|
|
2981
2662
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
value: 0,
|
|
2663
|
+
const issues = [];
|
|
2664
|
+
for (const [key, links] of groups) {
|
|
2665
|
+
const totalOccurrences = links.reduce((acc, l) => acc + (l.occurrences?.length ?? 1), 0);
|
|
2666
|
+
if (totalOccurrences < 2) continue;
|
|
2667
|
+
const [source, resolvedTarget] = key.split("\0");
|
|
2668
|
+
const flat = flattenOccurrences(links);
|
|
2669
|
+
issues.push({
|
|
2670
|
+
analyzerId: ID20,
|
|
2991
2671
|
severity: "warn",
|
|
2992
|
-
|
|
2672
|
+
nodeIds: [source],
|
|
2673
|
+
message: tx(REFERENCE_REDUNDANT_TEXTS.message, {
|
|
2674
|
+
source,
|
|
2675
|
+
resolvedTarget,
|
|
2676
|
+
count: flat.length,
|
|
2677
|
+
occurrences: flat.map(formatOccurrence).join(REFERENCE_REDUNDANT_TEXTS.occurrenceSeparator)
|
|
2678
|
+
}),
|
|
2679
|
+
data: {
|
|
2680
|
+
target: resolvedTarget,
|
|
2681
|
+
resolvedTarget,
|
|
2682
|
+
occurrences: flat.map((o) => ({
|
|
2683
|
+
kind: o.kind,
|
|
2684
|
+
trigger: o.originalTrigger,
|
|
2685
|
+
line: o.line ?? null,
|
|
2686
|
+
extractor: o.extractor
|
|
2687
|
+
}))
|
|
2688
|
+
}
|
|
2993
2689
|
});
|
|
2994
2690
|
}
|
|
2995
2691
|
return issues;
|
|
2996
2692
|
}
|
|
2997
2693
|
};
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
2694
|
+
function flattenOccurrences(links) {
|
|
2695
|
+
const out = [];
|
|
2696
|
+
for (const link of links) {
|
|
2697
|
+
if (link.occurrences && link.occurrences.length > 0) {
|
|
2698
|
+
for (const occ of link.occurrences) {
|
|
2699
|
+
out.push({
|
|
2700
|
+
kind: link.kind,
|
|
2701
|
+
originalTrigger: occ.originalTrigger,
|
|
2702
|
+
extractor: occ.extractor,
|
|
2703
|
+
line: occ.location?.line ?? null
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
const trigger = link.trigger?.originalTrigger ?? link.target;
|
|
2709
|
+
out.push({
|
|
2710
|
+
kind: link.kind,
|
|
2711
|
+
originalTrigger: trigger,
|
|
2712
|
+
extractor: link.sources[0] ?? "unknown",
|
|
2713
|
+
line: link.location?.line ?? null
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
out.sort((a, b) => {
|
|
2717
|
+
const la = a.line ?? Number.MAX_SAFE_INTEGER;
|
|
2718
|
+
const lb = b.line ?? Number.MAX_SAFE_INTEGER;
|
|
2719
|
+
if (la !== lb) return la - lb;
|
|
2720
|
+
return a.originalTrigger.localeCompare(b.originalTrigger);
|
|
2721
|
+
});
|
|
2722
|
+
return out;
|
|
3009
2723
|
}
|
|
3010
|
-
function
|
|
2724
|
+
function formatOccurrence(occ) {
|
|
2725
|
+
if (occ.line === null) {
|
|
2726
|
+
return tx(REFERENCE_REDUNDANT_TEXTS.occurrenceUnknownLine, { trigger: occ.originalTrigger, kind: occ.kind });
|
|
2727
|
+
}
|
|
2728
|
+
return tx(REFERENCE_REDUNDANT_TEXTS.occurrence, { trigger: occ.originalTrigger, kind: occ.kind, line: occ.line });
|
|
2729
|
+
}
|
|
2730
|
+
function buildNameIndex2(nodes) {
|
|
3011
2731
|
const out = /* @__PURE__ */ new Map();
|
|
3012
|
-
const
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
out.set(entry.pluginId, bucket);
|
|
3020
|
-
}
|
|
3021
|
-
try {
|
|
3022
|
-
bucket.set(entry.key, ajv.compile(entry.schema));
|
|
3023
|
-
} catch {
|
|
2732
|
+
for (const node of nodes) {
|
|
2733
|
+
for (const candidate of collectIdentifiers(node)) {
|
|
2734
|
+
const normalised = normalizeTrigger(candidate);
|
|
2735
|
+
if (!normalised) continue;
|
|
2736
|
+
const bucket = out.get(normalised);
|
|
2737
|
+
if (bucket) bucket.push(node.path);
|
|
2738
|
+
else out.set(normalised, [node.path]);
|
|
3024
2739
|
}
|
|
3025
2740
|
}
|
|
3026
2741
|
return out;
|
|
3027
2742
|
}
|
|
3028
|
-
function
|
|
3029
|
-
const out =
|
|
3030
|
-
|
|
3031
|
-
|
|
2743
|
+
function collectIdentifiers(node) {
|
|
2744
|
+
const out = [];
|
|
2745
|
+
const fmName = node.frontmatter?.["name"];
|
|
2746
|
+
if (typeof fmName === "string" && fmName.length > 0) out.push(fmName);
|
|
2747
|
+
const segs = node.path.split("/");
|
|
2748
|
+
const last = segs[segs.length - 1] ?? "";
|
|
2749
|
+
if (last) {
|
|
2750
|
+
const stem = last.replace(/\.[^.]+$/, "");
|
|
2751
|
+
if (stem) out.push(stem);
|
|
2752
|
+
}
|
|
2753
|
+
if (segs.length >= 2) {
|
|
2754
|
+
const dirBase = segs[segs.length - 2];
|
|
2755
|
+
if (dirBase) out.push(dirBase);
|
|
3032
2756
|
}
|
|
3033
2757
|
return out;
|
|
3034
2758
|
}
|
|
3035
|
-
function
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
return
|
|
2759
|
+
function resolveTargetPath(link, byPath3, byName) {
|
|
2760
|
+
if (byPath3.has(link.target)) return link.target;
|
|
2761
|
+
const trigger = link.trigger?.normalizedTrigger;
|
|
2762
|
+
if (!trigger) return null;
|
|
2763
|
+
const stripped = trigger.replace(/^[/@]/, "").trim();
|
|
2764
|
+
if (!stripped) return null;
|
|
2765
|
+
const candidates = byName.get(stripped);
|
|
2766
|
+
if (!candidates || candidates.length === 0) return null;
|
|
2767
|
+
return candidates[0] ?? null;
|
|
3039
2768
|
}
|
|
3040
2769
|
|
|
3041
2770
|
// kernel/adapters/schema-validators.ts
|
|
@@ -3224,185 +2953,463 @@ function registerProviderAuxiliarySchemas(ajv, providers) {
|
|
|
3224
2953
|
ajv.addSchema(aux);
|
|
3225
2954
|
}
|
|
3226
2955
|
}
|
|
3227
|
-
}
|
|
3228
|
-
function resolveSpecRoot() {
|
|
3229
|
-
const require2 = createRequire2(import.meta.url);
|
|
3230
|
-
try {
|
|
3231
|
-
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
3232
|
-
return dirname2(indexPath);
|
|
3233
|
-
} catch {
|
|
3234
|
-
throw new Error(
|
|
3235
|
-
"@skill-map/spec not resolvable: ensure the workspace is linked or the package is installed."
|
|
3236
|
-
);
|
|
2956
|
+
}
|
|
2957
|
+
function resolveSpecRoot() {
|
|
2958
|
+
const require2 = createRequire2(import.meta.url);
|
|
2959
|
+
try {
|
|
2960
|
+
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
2961
|
+
return dirname2(indexPath);
|
|
2962
|
+
} catch {
|
|
2963
|
+
throw new Error(
|
|
2964
|
+
"@skill-map/spec not resolvable: ensure the workspace is linked or the package is installed."
|
|
2965
|
+
);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
function existsSyncSafe(path) {
|
|
2969
|
+
try {
|
|
2970
|
+
readFileSync2(path, "utf8");
|
|
2971
|
+
return true;
|
|
2972
|
+
} catch {
|
|
2973
|
+
return false;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// plugins/core/analyzers/schema-violation/text.ts
|
|
2978
|
+
var SCHEMA_VIOLATION_TEXTS = {
|
|
2979
|
+
/** `Node <path> failed schema validation: <errors>` */
|
|
2980
|
+
nodeFailure: "Node {{path}} failed schema validation: {{errors}}",
|
|
2981
|
+
/** `Link <source> → <target> failed schema validation: <errors>` */
|
|
2982
|
+
linkFailure: "Link {{source}} \u2192 {{target}} failed schema validation: {{errors}}",
|
|
2983
|
+
/** `Node <path> is missing required frontmatter fields: <missing>` */
|
|
2984
|
+
frontmatterBaseFailure: "Node {{path}} is missing required frontmatter fields: {{missing}}.",
|
|
2985
|
+
/** Singular tooltip on the alert / chip when a node has exactly one validation failure. */
|
|
2986
|
+
alertTooltipSingle: "Frontmatter or schema validation failed.",
|
|
2987
|
+
/** Plural tooltip; `{{count}}` capped at 99 in the chip badge but the tooltip text shows the raw count. */
|
|
2988
|
+
alertTooltipMany: "{{count}} schema validation issues on this node."
|
|
2989
|
+
};
|
|
2990
|
+
|
|
2991
|
+
// plugins/core/analyzers/schema-violation/index.ts
|
|
2992
|
+
var ID21 = "schema-violation";
|
|
2993
|
+
var schemaViolationAnalyzer = {
|
|
2994
|
+
id: ID21,
|
|
2995
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2996
|
+
kind: "analyzer",
|
|
2997
|
+
version: "1.0.0",
|
|
2998
|
+
description: "Flags nodes or links that violate the project schemas.",
|
|
2999
|
+
mode: "deterministic",
|
|
3000
|
+
ui: {
|
|
3001
|
+
// Corner badge on the graph card; surfaces when the node body /
|
|
3002
|
+
// frontmatter fails schema validation (parse error, missing
|
|
3003
|
+
// `name`/`description`, malformed YAML, etc.). Same visual
|
|
3004
|
+
// chassis as `core/reference-broken`, danger severity.
|
|
3005
|
+
alert: {
|
|
3006
|
+
slot: "graph.node.alert",
|
|
3007
|
+
icon: "fa-solid fa-triangle-exclamation",
|
|
3008
|
+
emitWhenEmpty: false
|
|
3009
|
+
},
|
|
3010
|
+
// Footer chip that mirrors the corner alert with the actual
|
|
3011
|
+
// count so the operator can scan the cards and prioritise.
|
|
3012
|
+
// Outlined (vs the filled corner alert) per the broken-ref
|
|
3013
|
+
// pattern: two beats of the same signal.
|
|
3014
|
+
chip: {
|
|
3015
|
+
slot: "card.footer.right",
|
|
3016
|
+
icon: "fa-regular fa-triangle-exclamation",
|
|
3017
|
+
emitWhenEmpty: false,
|
|
3018
|
+
priority: 35
|
|
3019
|
+
}
|
|
3020
|
+
},
|
|
3021
|
+
evaluate(ctx) {
|
|
3022
|
+
const validators = loadSchemaValidators();
|
|
3023
|
+
const findings = [];
|
|
3024
|
+
const perNode = /* @__PURE__ */ new Map();
|
|
3025
|
+
for (const node of ctx.nodes) {
|
|
3026
|
+
const before = findings.length;
|
|
3027
|
+
collectNodeFindings(validators, node, findings);
|
|
3028
|
+
collectFrontmatterBaseFindings(node, findings);
|
|
3029
|
+
if (findings.length > before) {
|
|
3030
|
+
perNode.set(node.path, (perNode.get(node.path) ?? 0) + (findings.length - before));
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
for (const link of ctx.links) {
|
|
3034
|
+
collectLinkFindings(validators, link, findings);
|
|
3035
|
+
}
|
|
3036
|
+
for (const [nodePath, count] of perNode) {
|
|
3037
|
+
const tooltip = count === 1 ? SCHEMA_VIOLATION_TEXTS.alertTooltipSingle : tx(SCHEMA_VIOLATION_TEXTS.alertTooltipMany, { count });
|
|
3038
|
+
const capped = Math.min(count, 99);
|
|
3039
|
+
ctx.emitContribution(nodePath, "alert", {
|
|
3040
|
+
icon: "fa-solid fa-triangle-exclamation",
|
|
3041
|
+
severity: "danger",
|
|
3042
|
+
tooltip
|
|
3043
|
+
});
|
|
3044
|
+
ctx.emitContribution(nodePath, "chip", {
|
|
3045
|
+
value: capped,
|
|
3046
|
+
severity: "danger",
|
|
3047
|
+
tooltip
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
return findings;
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
function collectNodeFindings(v, node, out) {
|
|
3054
|
+
const result = v.validate("node", toNodeForSchema(node));
|
|
3055
|
+
if (result.ok) return;
|
|
3056
|
+
out.push({
|
|
3057
|
+
analyzerId: ID21,
|
|
3058
|
+
severity: "error",
|
|
3059
|
+
nodeIds: [node.path],
|
|
3060
|
+
message: tx(SCHEMA_VIOLATION_TEXTS.nodeFailure, {
|
|
3061
|
+
path: node.path,
|
|
3062
|
+
errors: result.errors
|
|
3063
|
+
}),
|
|
3064
|
+
data: { target: "node", path: node.path }
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
function collectFrontmatterBaseFindings(node, out) {
|
|
3068
|
+
if (node.provider === "markdown") return;
|
|
3069
|
+
if (node.bytes.frontmatter === 0) return;
|
|
3070
|
+
const fm = node.frontmatter ?? {};
|
|
3071
|
+
const missing = [];
|
|
3072
|
+
if (isMissingStringField(fm, "name")) missing.push("name");
|
|
3073
|
+
if (isMissingStringField(fm, "description")) missing.push("description");
|
|
3074
|
+
if (missing.length === 0) return;
|
|
3075
|
+
out.push({
|
|
3076
|
+
analyzerId: ID21,
|
|
3077
|
+
// `warn` (not `error`) so the default `sm scan` exit code stays
|
|
3078
|
+
// 0 even when nodes are missing frontmatter base fields. Strict
|
|
3079
|
+
// mode (`sm scan --strict`) still escalates to exit 1. Matches
|
|
3080
|
+
// the `frontmatter-invalid` severity policy of the orchestrator.
|
|
3081
|
+
severity: "warn",
|
|
3082
|
+
nodeIds: [node.path],
|
|
3083
|
+
message: tx(SCHEMA_VIOLATION_TEXTS.frontmatterBaseFailure, {
|
|
3084
|
+
path: node.path,
|
|
3085
|
+
missing: missing.join(", ")
|
|
3086
|
+
}),
|
|
3087
|
+
data: { target: "frontmatter", path: node.path, missing }
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
function isMissingStringField(fm, field) {
|
|
3091
|
+
const v = fm[field];
|
|
3092
|
+
return typeof v !== "string" || v.length === 0;
|
|
3093
|
+
}
|
|
3094
|
+
function collectLinkFindings(v, link, out) {
|
|
3095
|
+
const result = v.validate("link", toLinkForSchema(link));
|
|
3096
|
+
if (result.ok) return;
|
|
3097
|
+
out.push({
|
|
3098
|
+
analyzerId: ID21,
|
|
3099
|
+
severity: "error",
|
|
3100
|
+
nodeIds: [link.source],
|
|
3101
|
+
message: tx(SCHEMA_VIOLATION_TEXTS.linkFailure, {
|
|
3102
|
+
source: link.source,
|
|
3103
|
+
target: link.target,
|
|
3104
|
+
errors: result.errors
|
|
3105
|
+
}),
|
|
3106
|
+
data: { target: "link", source: link.source, to: link.target }
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
function toNodeForSchema(node) {
|
|
3110
|
+
return {
|
|
3111
|
+
path: node.path,
|
|
3112
|
+
kind: node.kind,
|
|
3113
|
+
provider: node.provider,
|
|
3114
|
+
bodyHash: node.bodyHash,
|
|
3115
|
+
frontmatterHash: node.frontmatterHash,
|
|
3116
|
+
bytes: node.bytes,
|
|
3117
|
+
tokens: node.tokens ?? void 0,
|
|
3118
|
+
linksOutCount: node.linksOutCount,
|
|
3119
|
+
linksInCount: node.linksInCount,
|
|
3120
|
+
externalRefsCount: node.externalRefsCount,
|
|
3121
|
+
frontmatter: node.frontmatter ?? {},
|
|
3122
|
+
sidecar: node.sidecar ?? void 0
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
function toLinkForSchema(link) {
|
|
3126
|
+
return {
|
|
3127
|
+
source: link.source,
|
|
3128
|
+
target: link.target,
|
|
3129
|
+
kind: link.kind,
|
|
3130
|
+
confidence: link.confidence,
|
|
3131
|
+
sources: link.sources,
|
|
3132
|
+
trigger: link.trigger ?? void 0,
|
|
3133
|
+
location: link.location ?? void 0,
|
|
3134
|
+
raw: link.raw ?? void 0
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// plugins/core/analyzers/signal-collision/text.ts
|
|
3139
|
+
var SIGNAL_COLLISION_TEXTS = {
|
|
3140
|
+
/**
|
|
3141
|
+
* Per-Signal warn issue: two extractors detected something at
|
|
3142
|
+
* overlapping byte ranges within the same node and the resolver
|
|
3143
|
+
* dropped the loser. Surfaces WHO lost, WHO won, and the tiebreak
|
|
3144
|
+
* reason so the operator can understand why a candidate edge did NOT
|
|
3145
|
+
* become a Link.
|
|
3146
|
+
*
|
|
3147
|
+
* Placeholders are deliberately verbose because this is one of the
|
|
3148
|
+
* few diagnostic surfaces where the operator may need to disambiguate
|
|
3149
|
+
* a confusing graph (e.g. a `[link](path)` followed by `@path` inside
|
|
3150
|
+
* the same paragraph, the markdown-link wins and the at-directive
|
|
3151
|
+
* silently disappears without this warning).
|
|
3152
|
+
*/
|
|
3153
|
+
message: "{{loserExtractor}} detected `{{loserRaw}}` at offset {{loserRange}} but {{winnerExtractor}}'s detection at {{winnerRange}} won the overlap collision ({{reason}}). The graph shows the winning edge only; the loser is not persisted.",
|
|
3154
|
+
/**
|
|
3155
|
+
* Same warn but for the rare case the resolver rejected a Signal
|
|
3156
|
+
* because the operator disabled its extractor via
|
|
3157
|
+
* `plugins.<id>.extensions.<extId>.enabled`. Phase 4+ stub: today the
|
|
3158
|
+
* filter is not wired so this template is unreachable from the
|
|
3159
|
+
* resolver; documented now so the analyzer stays forward-compatible
|
|
3160
|
+
* with the upcoming filter pass.
|
|
3161
|
+
*/
|
|
3162
|
+
messageExtractorDisabled: "Extension `{{extractorId}}` is disabled; its detection `{{loserRaw}}` (offset {{loserRange}}) did not produce a Link. Re-enable the extension in Settings or via `sm plugins enable` to surface its edges.",
|
|
3163
|
+
/**
|
|
3164
|
+
* Same warn but for the future confidence floor case. Phase 4+ stub:
|
|
3165
|
+
* today the resolver materialises every winning candidate regardless
|
|
3166
|
+
* of confidence, so this template is unreachable; documented for
|
|
3167
|
+
* forward compatibility.
|
|
3168
|
+
*/
|
|
3169
|
+
messageBelowFloor: "Detection `{{loserRaw}}` (offset {{loserRange}}, confidence {{confidence}}) fell below the configured threshold {{threshold}} and was dropped."
|
|
3170
|
+
};
|
|
3171
|
+
|
|
3172
|
+
// plugins/core/analyzers/signal-collision/index.ts
|
|
3173
|
+
var ID22 = "signal-collision";
|
|
3174
|
+
var signalCollisionAnalyzer = {
|
|
3175
|
+
id: ID22,
|
|
3176
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3177
|
+
kind: "analyzer",
|
|
3178
|
+
version: "1.0.0",
|
|
3179
|
+
description: "Reports when two extractors fight over the same span of body text, or when a candidate link is dropped (extractor disabled, confidence too low) before it reaches the graph.",
|
|
3180
|
+
mode: "deterministic",
|
|
3181
|
+
evaluate(ctx) {
|
|
3182
|
+
const signals = ctx.signals;
|
|
3183
|
+
if (!signals || signals.length === 0) return [];
|
|
3184
|
+
const issues = [];
|
|
3185
|
+
for (const signal of signals) {
|
|
3186
|
+
const issue = makeIssue(signal);
|
|
3187
|
+
if (issue) issues.push(issue);
|
|
3188
|
+
}
|
|
3189
|
+
return issues;
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
function makeIssue(signal) {
|
|
3193
|
+
const resolution = signal.resolution;
|
|
3194
|
+
if (!resolution || resolution.outcome !== "rejected") return null;
|
|
3195
|
+
if (resolution.rejectedBy) {
|
|
3196
|
+
const winner = resolution.rejectedBy;
|
|
3197
|
+
const winnerCandidate = signal.candidates[resolution.winnerIndex ?? 0];
|
|
3198
|
+
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3199
|
+
const winnerRange = `${winner.range.start}-${winner.range.end}`;
|
|
3200
|
+
return {
|
|
3201
|
+
analyzerId: ID22,
|
|
3202
|
+
severity: "warn",
|
|
3203
|
+
nodeIds: [signal.source],
|
|
3204
|
+
message: tx(SIGNAL_COLLISION_TEXTS.message, {
|
|
3205
|
+
loserExtractor: winnerCandidate.extractorId,
|
|
3206
|
+
loserRaw: signal.raw,
|
|
3207
|
+
loserRange,
|
|
3208
|
+
winnerExtractor: winner.extractorId,
|
|
3209
|
+
winnerRange,
|
|
3210
|
+
reason: winner.reason
|
|
3211
|
+
}),
|
|
3212
|
+
data: {
|
|
3213
|
+
loser: {
|
|
3214
|
+
extractorId: winnerCandidate.extractorId,
|
|
3215
|
+
raw: signal.raw,
|
|
3216
|
+
range: signal.range ?? null,
|
|
3217
|
+
candidate: {
|
|
3218
|
+
kind: winnerCandidate.kind,
|
|
3219
|
+
target: winnerCandidate.target,
|
|
3220
|
+
confidence: winnerCandidate.confidence
|
|
3221
|
+
}
|
|
3222
|
+
},
|
|
3223
|
+
winner: {
|
|
3224
|
+
extractorId: winner.extractorId,
|
|
3225
|
+
range: winner.range
|
|
3226
|
+
},
|
|
3227
|
+
reason: winner.reason
|
|
3228
|
+
}
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
if (resolution.extractorDisabled) {
|
|
3232
|
+
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3233
|
+
return {
|
|
3234
|
+
analyzerId: ID22,
|
|
3235
|
+
severity: "warn",
|
|
3236
|
+
nodeIds: [signal.source],
|
|
3237
|
+
message: tx(SIGNAL_COLLISION_TEXTS.messageExtractorDisabled, {
|
|
3238
|
+
extractorId: resolution.extractorDisabled.extractorId,
|
|
3239
|
+
loserRaw: signal.raw,
|
|
3240
|
+
loserRange
|
|
3241
|
+
}),
|
|
3242
|
+
data: {
|
|
3243
|
+
extractorDisabled: resolution.extractorDisabled,
|
|
3244
|
+
raw: signal.raw,
|
|
3245
|
+
range: signal.range ?? null
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3237
3248
|
}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3249
|
+
if (resolution.belowFloor) {
|
|
3250
|
+
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3251
|
+
const topCandidate = signal.candidates[0];
|
|
3252
|
+
return {
|
|
3253
|
+
analyzerId: ID22,
|
|
3254
|
+
severity: "warn",
|
|
3255
|
+
nodeIds: [signal.source],
|
|
3256
|
+
message: tx(SIGNAL_COLLISION_TEXTS.messageBelowFloor, {
|
|
3257
|
+
loserRaw: signal.raw,
|
|
3258
|
+
loserRange,
|
|
3259
|
+
confidence: topCandidate.confidence,
|
|
3260
|
+
threshold: resolution.belowFloor.threshold
|
|
3261
|
+
}),
|
|
3262
|
+
data: {
|
|
3263
|
+
belowFloor: resolution.belowFloor,
|
|
3264
|
+
raw: signal.raw,
|
|
3265
|
+
range: signal.range ?? null
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3245
3268
|
}
|
|
3269
|
+
return null;
|
|
3246
3270
|
}
|
|
3247
3271
|
|
|
3248
|
-
// plugins/core/analyzers/
|
|
3249
|
-
var
|
|
3250
|
-
/**
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
/**
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3272
|
+
// plugins/core/analyzers/trigger-collision/text.ts
|
|
3273
|
+
var TRIGGER_COLLISION_TEXTS = {
|
|
3274
|
+
/**
|
|
3275
|
+
* Top-level message when `analyzeTriggerBucket` accumulated exactly one
|
|
3276
|
+
* cause part. Used for the advertiser-ambiguous-only, invocation-
|
|
3277
|
+
* ambiguous-only, and cross-kind-only branches.
|
|
3278
|
+
*/
|
|
3279
|
+
messageOnePart: 'Trigger "{{normalized}}" has {{part}}.',
|
|
3280
|
+
/**
|
|
3281
|
+
* Top-level message when `analyzeTriggerBucket` accumulated two cause
|
|
3282
|
+
* parts (advertiser-ambiguous AND invocation-ambiguous fire together).
|
|
3283
|
+
* The joiner lives inside the template so future locales can adapt it
|
|
3284
|
+
* (e.g. `'; y '` in Spanish) without touching the rule code.
|
|
3285
|
+
*/
|
|
3286
|
+
messageTwoParts: 'Trigger "{{normalized}}" has {{first}}; and {{second}}.',
|
|
3287
|
+
/** `<n> nodes advertise it: <list>` part, fires on the advertiser-ambiguous branch. */
|
|
3288
|
+
partAdvertisers: "{{count}} nodes advertise it: {{paths}}",
|
|
3289
|
+
/** `<n> distinct invocation forms: <list>` part, fires on the invocation-ambiguous branch. */
|
|
3290
|
+
partInvocations: "{{count}} distinct invocation forms: {{forms}}",
|
|
3291
|
+
/** Singular cross-kind cause: `non-canonical invocation <form> against advertiser <path>`. */
|
|
3292
|
+
partNonCanonicalSingular: "non-canonical invocation {{forms}} against advertiser {{advertiser}}",
|
|
3293
|
+
/** Plural cross-kind cause: `non-canonical invocations <forms> against advertiser <path>`. */
|
|
3294
|
+
partNonCanonicalPlural: "non-canonical invocations {{forms}} against advertiser {{advertiser}}"
|
|
3260
3295
|
};
|
|
3261
3296
|
|
|
3262
|
-
// plugins/core/analyzers/
|
|
3263
|
-
var ID23 = "
|
|
3264
|
-
var
|
|
3297
|
+
// plugins/core/analyzers/trigger-collision/index.ts
|
|
3298
|
+
var ID23 = "trigger-collision";
|
|
3299
|
+
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
3300
|
+
"command",
|
|
3301
|
+
"skill",
|
|
3302
|
+
"agent"
|
|
3303
|
+
]);
|
|
3304
|
+
var triggerCollisionAnalyzer = {
|
|
3265
3305
|
id: ID23,
|
|
3266
|
-
pluginId:
|
|
3306
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3267
3307
|
kind: "analyzer",
|
|
3268
|
-
version: "1.0.0",
|
|
3269
|
-
description: "Detects and flags nodes or links violating the project schemas.",
|
|
3270
3308
|
mode: "deterministic",
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
alert: {
|
|
3277
|
-
slot: "graph.node.alert",
|
|
3278
|
-
icon: "fa-solid fa-triangle-exclamation",
|
|
3279
|
-
emitWhenEmpty: false
|
|
3280
|
-
},
|
|
3281
|
-
// Footer chip that mirrors the corner alert with the actual
|
|
3282
|
-
// count so the operator can scan the cards and prioritise.
|
|
3283
|
-
// Outlined (vs the filled corner alert) per the broken-ref
|
|
3284
|
-
// pattern: two beats of the same signal.
|
|
3285
|
-
chip: {
|
|
3286
|
-
slot: "card.footer.right",
|
|
3287
|
-
icon: "fa-regular fa-triangle-exclamation",
|
|
3288
|
-
emitWhenEmpty: false,
|
|
3289
|
-
priority: 35
|
|
3290
|
-
}
|
|
3291
|
-
},
|
|
3309
|
+
version: "1.0.0",
|
|
3310
|
+
description: "Flags two or more nodes that claim the same `/command` or `@agent` name.",
|
|
3311
|
+
// Two claim-collection passes (advertisement + invocation) feeding
|
|
3312
|
+
// the bucket map. Per-bucket analysis lives in `analyzeTriggerBucket`.
|
|
3313
|
+
// eslint-disable-next-line complexity
|
|
3292
3314
|
evaluate(ctx) {
|
|
3293
|
-
const
|
|
3294
|
-
const
|
|
3295
|
-
|
|
3315
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
3316
|
+
const push = (key, claim) => {
|
|
3317
|
+
const bucket = buckets.get(key) ?? [];
|
|
3318
|
+
bucket.push(claim);
|
|
3319
|
+
buckets.set(key, bucket);
|
|
3320
|
+
};
|
|
3296
3321
|
for (const node of ctx.nodes) {
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3322
|
+
if (!ADVERTISING_KINDS.has(node.kind)) continue;
|
|
3323
|
+
const raw = node.frontmatter?.["name"];
|
|
3324
|
+
if (typeof raw !== "string" || raw.length === 0) continue;
|
|
3325
|
+
const normalized = `/${normalizeTrigger(raw)}`;
|
|
3326
|
+
if (normalized === "/") continue;
|
|
3327
|
+
push(normalized, {
|
|
3328
|
+
kind: "advertiser",
|
|
3329
|
+
token: node.path,
|
|
3330
|
+
nodeId: node.path,
|
|
3331
|
+
canonicalForm: `/${raw}`
|
|
3332
|
+
});
|
|
3303
3333
|
}
|
|
3304
3334
|
for (const link of ctx.links) {
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
icon: "fa-solid fa-triangle-exclamation",
|
|
3312
|
-
severity: "danger",
|
|
3313
|
-
tooltip
|
|
3314
|
-
});
|
|
3315
|
-
ctx.emitContribution(nodePath, "chip", {
|
|
3316
|
-
value: capped,
|
|
3317
|
-
severity: "danger",
|
|
3318
|
-
tooltip
|
|
3335
|
+
const normalized = link.trigger?.normalizedTrigger;
|
|
3336
|
+
if (!normalized) continue;
|
|
3337
|
+
push(normalized, {
|
|
3338
|
+
kind: "invocation",
|
|
3339
|
+
token: link.target,
|
|
3340
|
+
nodeId: link.source
|
|
3319
3341
|
});
|
|
3320
3342
|
}
|
|
3321
|
-
|
|
3343
|
+
const issues = [];
|
|
3344
|
+
for (const [normalized, claims] of buckets) {
|
|
3345
|
+
const issue = analyzeTriggerBucket(normalized, claims);
|
|
3346
|
+
if (issue) issues.push(issue);
|
|
3347
|
+
}
|
|
3348
|
+
return issues;
|
|
3322
3349
|
}
|
|
3323
3350
|
};
|
|
3324
|
-
function
|
|
3325
|
-
const
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
if (
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
const
|
|
3343
|
-
|
|
3344
|
-
if (
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3351
|
+
function analyzeTriggerBucket(normalized, claims) {
|
|
3352
|
+
const advertiserPaths = [
|
|
3353
|
+
...new Set(claims.filter((c) => c.kind === "advertiser").map((c) => c.token))
|
|
3354
|
+
].sort();
|
|
3355
|
+
const invocationTargets = [
|
|
3356
|
+
...new Set(claims.filter((c) => c.kind === "invocation").map((c) => c.token))
|
|
3357
|
+
].sort();
|
|
3358
|
+
const advertisers = claims.filter(
|
|
3359
|
+
(c) => c.kind === "advertiser"
|
|
3360
|
+
);
|
|
3361
|
+
const advertiserAmbiguous = advertiserPaths.length >= 2;
|
|
3362
|
+
const invocationAmbiguous = invocationTargets.length >= 2;
|
|
3363
|
+
const canonicalForms = new Set(advertisers.map((a) => a.canonicalForm));
|
|
3364
|
+
const nonCanonicalInvocations = invocationTargets.filter((t) => !canonicalForms.has(t));
|
|
3365
|
+
const crossKindAmbiguous = advertiserPaths.length === 1 && nonCanonicalInvocations.length >= 1;
|
|
3366
|
+
if (!advertiserAmbiguous && !invocationAmbiguous && !crossKindAmbiguous) {
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
const nodeIds = [...new Set(claims.map((c) => c.nodeId))].sort();
|
|
3370
|
+
const parts = [];
|
|
3371
|
+
if (advertiserAmbiguous) {
|
|
3372
|
+
parts.push(
|
|
3373
|
+
tx(TRIGGER_COLLISION_TEXTS.partAdvertisers, {
|
|
3374
|
+
count: advertiserPaths.length,
|
|
3375
|
+
paths: advertiserPaths.join(", ")
|
|
3376
|
+
})
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
if (invocationAmbiguous) {
|
|
3380
|
+
parts.push(
|
|
3381
|
+
tx(TRIGGER_COLLISION_TEXTS.partInvocations, {
|
|
3382
|
+
count: invocationTargets.length,
|
|
3383
|
+
forms: invocationTargets.join(", ")
|
|
3384
|
+
})
|
|
3385
|
+
);
|
|
3386
|
+
} else if (crossKindAmbiguous) {
|
|
3387
|
+
const template = nonCanonicalInvocations.length > 1 ? TRIGGER_COLLISION_TEXTS.partNonCanonicalPlural : TRIGGER_COLLISION_TEXTS.partNonCanonicalSingular;
|
|
3388
|
+
parts.push(
|
|
3389
|
+
tx(template, {
|
|
3390
|
+
forms: nonCanonicalInvocations.join(", "),
|
|
3391
|
+
advertiser: advertiserPaths[0]
|
|
3392
|
+
})
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
const message = parts.length === 2 ? tx(TRIGGER_COLLISION_TEXTS.messageTwoParts, {
|
|
3396
|
+
normalized,
|
|
3397
|
+
first: parts[0],
|
|
3398
|
+
second: parts[1]
|
|
3399
|
+
}) : tx(TRIGGER_COLLISION_TEXTS.messageOnePart, {
|
|
3400
|
+
normalized,
|
|
3401
|
+
part: parts[0]
|
|
3359
3402
|
});
|
|
3360
|
-
|
|
3361
|
-
function isMissingStringField(fm, field) {
|
|
3362
|
-
const v = fm[field];
|
|
3363
|
-
return typeof v !== "string" || v.length === 0;
|
|
3364
|
-
}
|
|
3365
|
-
function collectLinkFindings(v, link, out) {
|
|
3366
|
-
const result = v.validate("link", toLinkForSchema(link));
|
|
3367
|
-
if (result.ok) return;
|
|
3368
|
-
out.push({
|
|
3403
|
+
return {
|
|
3369
3404
|
analyzerId: ID23,
|
|
3370
3405
|
severity: "error",
|
|
3371
|
-
nodeIds
|
|
3372
|
-
message
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
});
|
|
3379
|
-
}
|
|
3380
|
-
function toNodeForSchema(node) {
|
|
3381
|
-
return {
|
|
3382
|
-
path: node.path,
|
|
3383
|
-
kind: node.kind,
|
|
3384
|
-
provider: node.provider,
|
|
3385
|
-
bodyHash: node.bodyHash,
|
|
3386
|
-
frontmatterHash: node.frontmatterHash,
|
|
3387
|
-
bytes: node.bytes,
|
|
3388
|
-
tokens: node.tokens ?? void 0,
|
|
3389
|
-
linksOutCount: node.linksOutCount,
|
|
3390
|
-
linksInCount: node.linksInCount,
|
|
3391
|
-
externalRefsCount: node.externalRefsCount,
|
|
3392
|
-
frontmatter: node.frontmatter ?? {},
|
|
3393
|
-
sidecar: node.sidecar ?? void 0
|
|
3394
|
-
};
|
|
3395
|
-
}
|
|
3396
|
-
function toLinkForSchema(link) {
|
|
3397
|
-
return {
|
|
3398
|
-
source: link.source,
|
|
3399
|
-
target: link.target,
|
|
3400
|
-
kind: link.kind,
|
|
3401
|
-
confidence: link.confidence,
|
|
3402
|
-
sources: link.sources,
|
|
3403
|
-
trigger: link.trigger ?? void 0,
|
|
3404
|
-
location: link.location ?? void 0,
|
|
3405
|
-
raw: link.raw ?? void 0
|
|
3406
|
+
nodeIds,
|
|
3407
|
+
message,
|
|
3408
|
+
data: {
|
|
3409
|
+
normalizedTrigger: normalized,
|
|
3410
|
+
invocationTargets,
|
|
3411
|
+
advertiserPaths
|
|
3412
|
+
}
|
|
3406
3413
|
};
|
|
3407
3414
|
}
|
|
3408
3415
|
|
|
@@ -3438,11 +3445,11 @@ var ID24 = "ascii";
|
|
|
3438
3445
|
var KIND_ORDER = ["agent", "command", "skill", "markdown"];
|
|
3439
3446
|
var asciiFormatter = {
|
|
3440
3447
|
id: ID24,
|
|
3441
|
-
pluginId:
|
|
3448
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3442
3449
|
kind: "formatter",
|
|
3443
3450
|
formatId: ID24,
|
|
3444
3451
|
version: "1.0.0",
|
|
3445
|
-
description: "Renders the scan as plain text
|
|
3452
|
+
description: "Renders the scan as plain text in three sections: nodes (grouped by kind), arrows, and issues. Used by `sm scan --format ascii`.",
|
|
3446
3453
|
// ASCII tree formatter, header + per-kind sections + per-issue
|
|
3447
3454
|
// section. Each section iterates and renders; splitting per section
|
|
3448
3455
|
// would multiply the for-loop boilerplate.
|
|
@@ -3538,10 +3545,10 @@ function renderSection(out, kind, group) {
|
|
|
3538
3545
|
var ID25 = "json";
|
|
3539
3546
|
var jsonFormatter = {
|
|
3540
3547
|
id: ID25,
|
|
3541
|
-
pluginId:
|
|
3548
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3542
3549
|
kind: "formatter",
|
|
3543
3550
|
version: "1.0.0",
|
|
3544
|
-
description: "Renders the persisted scan as JSON (conforms to `scan-result.schema.json`
|
|
3551
|
+
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`.",
|
|
3545
3552
|
formatId: ID25,
|
|
3546
3553
|
format(ctx) {
|
|
3547
3554
|
if (ctx.scanResult !== void 0) {
|
|
@@ -3680,15 +3687,14 @@ function resolveSpecRoot2() {
|
|
|
3680
3687
|
}
|
|
3681
3688
|
}
|
|
3682
3689
|
|
|
3683
|
-
// plugins/core/actions/bump/index.ts
|
|
3684
|
-
var ID26 = "bump";
|
|
3685
|
-
var
|
|
3686
|
-
var bumpAction = {
|
|
3690
|
+
// plugins/core/actions/node-bump/index.ts
|
|
3691
|
+
var ID26 = "node-bump";
|
|
3692
|
+
var nodeBumpAction = {
|
|
3687
3693
|
id: ID26,
|
|
3688
|
-
pluginId:
|
|
3694
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3689
3695
|
kind: "action",
|
|
3690
3696
|
version: "1.0.0",
|
|
3691
|
-
description: "Marks a node as updated: bumps version
|
|
3697
|
+
description: "Marks a node as updated: bumps `annotations.version`, refreshes sidecar hashes, and records the timestamp.",
|
|
3692
3698
|
mode: "deterministic",
|
|
3693
3699
|
// The runtime contract uses generic <TInput, TReport>; bump narrows
|
|
3694
3700
|
// both. The cast is the standard pattern for built-ins that want
|
|
@@ -3742,15 +3748,14 @@ function pickCurrentVersion(overlay) {
|
|
|
3742
3748
|
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
3743
3749
|
}
|
|
3744
3750
|
|
|
3745
|
-
// plugins/core/actions/
|
|
3746
|
-
var ID27 = "
|
|
3747
|
-
var
|
|
3748
|
-
var markSupersededAction = {
|
|
3751
|
+
// plugins/core/actions/node-supersede/index.ts
|
|
3752
|
+
var ID27 = "node-supersede";
|
|
3753
|
+
var nodeSupersedeAction = {
|
|
3749
3754
|
id: ID27,
|
|
3750
|
-
pluginId:
|
|
3755
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3751
3756
|
kind: "action",
|
|
3752
3757
|
version: "0.0.0",
|
|
3753
|
-
description: "Declares the current node as superseded by another (writes `supersededBy` to the sidecar).
|
|
3758
|
+
description: "Declares the current node as superseded by another (writes `supersededBy` to the sidecar).",
|
|
3754
3759
|
mode: "deterministic",
|
|
3755
3760
|
invoke(_input, _ctx) {
|
|
3756
3761
|
const report = { ok: true, noop: true };
|
|
@@ -3856,7 +3861,7 @@ var UPDATE_CHECK_TEXTS = {
|
|
|
3856
3861
|
// package.json
|
|
3857
3862
|
var package_default = {
|
|
3858
3863
|
name: "@skill-map/cli",
|
|
3859
|
-
version: "0.
|
|
3864
|
+
version: "0.39.0",
|
|
3860
3865
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
3861
3866
|
license: "MIT",
|
|
3862
3867
|
type: "module",
|
|
@@ -3939,7 +3944,7 @@ var package_default = {
|
|
|
3939
3944
|
semver: "7.7.4",
|
|
3940
3945
|
"smol-toml": "1.6.1",
|
|
3941
3946
|
typanion: "3.14.0",
|
|
3942
|
-
ws: "8.
|
|
3947
|
+
ws: "8.21.0"
|
|
3943
3948
|
},
|
|
3944
3949
|
devDependencies: {
|
|
3945
3950
|
"@eslint/js": "10.0.1",
|
|
@@ -4006,10 +4011,67 @@ function ansiFor(opts) {
|
|
|
4006
4011
|
}
|
|
4007
4012
|
|
|
4008
4013
|
// cli/util/user-settings-store.ts
|
|
4009
|
-
import { existsSync as
|
|
4014
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
4010
4015
|
import { homedir } from "os";
|
|
4011
4016
|
import { join as join2 } from "path";
|
|
4012
4017
|
|
|
4018
|
+
// core/config/atomic-write.ts
|
|
4019
|
+
import {
|
|
4020
|
+
closeSync,
|
|
4021
|
+
constants as fsConstants,
|
|
4022
|
+
existsSync as existsSync2,
|
|
4023
|
+
mkdirSync,
|
|
4024
|
+
openSync,
|
|
4025
|
+
readFileSync as readFileSync4,
|
|
4026
|
+
renameSync,
|
|
4027
|
+
unlinkSync,
|
|
4028
|
+
writeSync
|
|
4029
|
+
} from "fs";
|
|
4030
|
+
import { randomBytes } from "crypto";
|
|
4031
|
+
import { dirname as dirname4 } from "path";
|
|
4032
|
+
function readJsonObjectOrEmpty(path) {
|
|
4033
|
+
if (!existsSync2(path)) return {};
|
|
4034
|
+
try {
|
|
4035
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
4036
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
4037
|
+
return raw;
|
|
4038
|
+
}
|
|
4039
|
+
} catch {
|
|
4040
|
+
}
|
|
4041
|
+
return {};
|
|
4042
|
+
}
|
|
4043
|
+
function writeFileAtomicExclusive(path, content, mode = 384) {
|
|
4044
|
+
const tmp = `${path}.tmp.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
4045
|
+
let fd = null;
|
|
4046
|
+
try {
|
|
4047
|
+
fd = openSync(
|
|
4048
|
+
tmp,
|
|
4049
|
+
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW,
|
|
4050
|
+
mode
|
|
4051
|
+
);
|
|
4052
|
+
writeSync(fd, content);
|
|
4053
|
+
closeSync(fd);
|
|
4054
|
+
fd = null;
|
|
4055
|
+
renameSync(tmp, path);
|
|
4056
|
+
} catch (err) {
|
|
4057
|
+
if (fd !== null) {
|
|
4058
|
+
try {
|
|
4059
|
+
closeSync(fd);
|
|
4060
|
+
} catch {
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
try {
|
|
4064
|
+
unlinkSync(tmp);
|
|
4065
|
+
} catch {
|
|
4066
|
+
}
|
|
4067
|
+
throw err;
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
function writeJsonAtomic(path, content) {
|
|
4071
|
+
mkdirSync(dirname4(path), { recursive: true });
|
|
4072
|
+
writeFileAtomicExclusive(path, JSON.stringify(content, null, 2) + "\n");
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4013
4075
|
// core/paths/db-path.ts
|
|
4014
4076
|
import { join, resolve as resolve6 } from "path";
|
|
4015
4077
|
var SKILL_MAP_DIR = ".skill-map";
|
|
@@ -4067,9 +4129,9 @@ function readUserSettings() {
|
|
|
4067
4129
|
}
|
|
4068
4130
|
function readParsedFile() {
|
|
4069
4131
|
const path = userSettingsFilePath();
|
|
4070
|
-
if (!
|
|
4132
|
+
if (!existsSync3(path)) return null;
|
|
4071
4133
|
try {
|
|
4072
|
-
const parsed = JSON.parse(
|
|
4134
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
4073
4135
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4074
4136
|
return null;
|
|
4075
4137
|
}
|
|
@@ -4102,8 +4164,8 @@ function writeUserSettings(patch) {
|
|
|
4102
4164
|
const result = validators.validate("user-settings", merged);
|
|
4103
4165
|
if (!result.ok) return;
|
|
4104
4166
|
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4167
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
4168
|
+
writeFileAtomicExclusive(path, JSON.stringify(merged, null, 2) + "\n");
|
|
4107
4169
|
} catch {
|
|
4108
4170
|
}
|
|
4109
4171
|
}
|
|
@@ -4214,10 +4276,10 @@ ${footer}
|
|
|
4214
4276
|
// plugins/core/hooks/update-check/index.ts
|
|
4215
4277
|
var updateCheckHook = {
|
|
4216
4278
|
id: "update-check",
|
|
4217
|
-
pluginId:
|
|
4279
|
+
pluginId: CORE_PLUGIN_ID,
|
|
4218
4280
|
kind: "hook",
|
|
4219
4281
|
version: "1.0.0",
|
|
4220
|
-
description: "Checks daily for a newer skill-map version on npm. Shows an `update available` banner when one is found.",
|
|
4282
|
+
description: "Checks daily for a newer `skill-map` version on npm. Shows an `update available` banner when one is found.",
|
|
4221
4283
|
triggers: ["boot"],
|
|
4222
4284
|
async on(ctx) {
|
|
4223
4285
|
const payload = ctx.event.data ?? {};
|
|
@@ -4234,7 +4296,7 @@ var updateCheckHook = {
|
|
|
4234
4296
|
// plugins/built-ins.ts
|
|
4235
4297
|
var claudeProvider2 = { ...claudeProvider, pluginId: "claude" };
|
|
4236
4298
|
var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude" };
|
|
4237
|
-
var
|
|
4299
|
+
var slashCommandExtractor2 = { ...slashCommandExtractor, pluginId: "claude" };
|
|
4238
4300
|
var antigravityProvider2 = { ...antigravityProvider, pluginId: "antigravity" };
|
|
4239
4301
|
var openaiProvider2 = { ...openaiProvider, pluginId: "openai" };
|
|
4240
4302
|
var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills" };
|
|
@@ -4243,43 +4305,43 @@ var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core" };
|
|
|
4243
4305
|
var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core" };
|
|
4244
4306
|
var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core" };
|
|
4245
4307
|
var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core" };
|
|
4246
|
-
var
|
|
4308
|
+
var toolsCounterExtractor2 = { ...toolsCounterExtractor, pluginId: "core" };
|
|
4309
|
+
var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, pluginId: "core" };
|
|
4247
4310
|
var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core" };
|
|
4248
4311
|
var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core" };
|
|
4249
|
-
var brokenRefAnalyzer2 = { ...brokenRefAnalyzer, pluginId: "core" };
|
|
4250
4312
|
var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core" };
|
|
4251
|
-
var
|
|
4313
|
+
var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core" };
|
|
4252
4314
|
var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core" };
|
|
4253
|
-
var
|
|
4254
|
-
var
|
|
4255
|
-
var
|
|
4256
|
-
var
|
|
4315
|
+
var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core" };
|
|
4316
|
+
var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core" };
|
|
4317
|
+
var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core" };
|
|
4318
|
+
var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core" };
|
|
4319
|
+
var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core" };
|
|
4320
|
+
var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core" };
|
|
4321
|
+
var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core" };
|
|
4322
|
+
var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core" };
|
|
4257
4323
|
var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core" };
|
|
4258
|
-
var stabilityAnalyzer2 = { ...stabilityAnalyzer, pluginId: "core" };
|
|
4259
|
-
var supersededAnalyzer2 = { ...supersededAnalyzer, pluginId: "core" };
|
|
4260
4324
|
var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core" };
|
|
4261
|
-
var unknownFieldAnalyzer2 = { ...unknownFieldAnalyzer, pluginId: "core" };
|
|
4262
|
-
var validateAllAnalyzer2 = { ...validateAllAnalyzer, pluginId: "core" };
|
|
4263
4325
|
var asciiFormatter2 = { ...asciiFormatter, pluginId: "core" };
|
|
4264
4326
|
var jsonFormatter2 = { ...jsonFormatter, pluginId: "core" };
|
|
4265
|
-
var
|
|
4266
|
-
var
|
|
4327
|
+
var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core" };
|
|
4328
|
+
var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core" };
|
|
4267
4329
|
var updateCheckHook2 = { ...updateCheckHook, pluginId: "core" };
|
|
4268
4330
|
var builtInBundles = [
|
|
4269
4331
|
{
|
|
4270
4332
|
id: "claude",
|
|
4271
4333
|
granularity: "bundle",
|
|
4272
|
-
description: "Claude Code platform integration. Classifies files under `.claude/{agents,commands,skills}` and
|
|
4334
|
+
description: "Claude Code platform integration. Classifies files under `.claude/{agents,commands,skills}` and detects Claude-flavored slash commands and at-directives in their bodies.",
|
|
4273
4335
|
extensions: [
|
|
4274
4336
|
claudeProvider2,
|
|
4275
4337
|
atDirectiveExtractor2,
|
|
4276
|
-
|
|
4338
|
+
slashCommandExtractor2
|
|
4277
4339
|
]
|
|
4278
4340
|
},
|
|
4279
4341
|
{
|
|
4280
4342
|
id: "antigravity",
|
|
4281
4343
|
granularity: "bundle",
|
|
4282
|
-
description: "Google Antigravity CLI platform integration (
|
|
4344
|
+
description: "Google Antigravity CLI platform integration (replaces the retired Gemini CLI). Antigravity adopted the open-standard `.agents/` layout, so skills are classified by the neutral `agent-skills` provider; this plugin contributes the Antigravity runtime identity and a seed list of reserved built-in names.",
|
|
4283
4345
|
extensions: [
|
|
4284
4346
|
antigravityProvider2
|
|
4285
4347
|
]
|
|
@@ -4287,7 +4349,7 @@ var builtInBundles = [
|
|
|
4287
4349
|
{
|
|
4288
4350
|
id: "openai",
|
|
4289
4351
|
granularity: "bundle",
|
|
4290
|
-
description: "OpenAI Codex CLI platform integration. Classifies TOML sub-agent definitions under `.codex/agents/*.toml
|
|
4352
|
+
description: "OpenAI Codex CLI platform integration. Classifies TOML sub-agent definitions under `.codex/agents/*.toml`.",
|
|
4291
4353
|
extensions: [
|
|
4292
4354
|
openaiProvider2
|
|
4293
4355
|
]
|
|
@@ -4295,7 +4357,7 @@ var builtInBundles = [
|
|
|
4295
4357
|
{
|
|
4296
4358
|
id: "agent-skills",
|
|
4297
4359
|
granularity: "bundle",
|
|
4298
|
-
description: "Agent Skills
|
|
4360
|
+
description: "Open-standard Agent Skills layout. Classifies skills under the vendor-neutral path `.agents/skills/<name>/SKILL.md` (adopted by Anthropic, OpenAI, Google). See agentskills.io.",
|
|
4299
4361
|
extensions: [
|
|
4300
4362
|
agentSkillsProvider2
|
|
4301
4363
|
]
|
|
@@ -4303,34 +4365,34 @@ var builtInBundles = [
|
|
|
4303
4365
|
{
|
|
4304
4366
|
id: "core",
|
|
4305
4367
|
granularity: "extension",
|
|
4306
|
-
description: "Core extensions shared across providers: extractors, analyzers,
|
|
4368
|
+
description: "Core extensions shared across providers: parsers, extractors, analyzers, actions, hooks, formatters, and the universal `.md` fallback provider.",
|
|
4307
4369
|
extensions: [
|
|
4308
4370
|
coreMarkdownProvider2,
|
|
4309
4371
|
annotationsExtractor2,
|
|
4310
4372
|
externalUrlCounterExtractor2,
|
|
4311
4373
|
markdownLinkExtractor2,
|
|
4312
4374
|
mcpToolsExtractor2,
|
|
4313
|
-
|
|
4375
|
+
toolsCounterExtractor2,
|
|
4376
|
+
annotationFieldUnknownAnalyzer2,
|
|
4314
4377
|
annotationOrphanAnalyzer2,
|
|
4315
4378
|
annotationStaleAnalyzer2,
|
|
4316
|
-
brokenRefAnalyzer2,
|
|
4317
4379
|
contributionOrphanAnalyzer2,
|
|
4318
|
-
|
|
4380
|
+
jobFileOrphanAnalyzer2,
|
|
4319
4381
|
linkConflictAnalyzer2,
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4382
|
+
linkCounterAnalyzer2,
|
|
4383
|
+
linkSelfLoopAnalyzer2,
|
|
4384
|
+
nameReservedAnalyzer2,
|
|
4385
|
+
nodeStabilityAnalyzer2,
|
|
4386
|
+
nodeSupersededAnalyzer2,
|
|
4387
|
+
referenceBrokenAnalyzer2,
|
|
4388
|
+
referenceRedundantAnalyzer2,
|
|
4389
|
+
schemaViolationAnalyzer2,
|
|
4324
4390
|
signalCollisionAnalyzer2,
|
|
4325
|
-
stabilityAnalyzer2,
|
|
4326
|
-
supersededAnalyzer2,
|
|
4327
4391
|
triggerCollisionAnalyzer2,
|
|
4328
|
-
unknownFieldAnalyzer2,
|
|
4329
|
-
validateAllAnalyzer2,
|
|
4330
4392
|
asciiFormatter2,
|
|
4331
4393
|
jsonFormatter2,
|
|
4332
|
-
|
|
4333
|
-
|
|
4394
|
+
nodeBumpAction2,
|
|
4395
|
+
nodeSupersedeAction2,
|
|
4334
4396
|
updateCheckHook2
|
|
4335
4397
|
]
|
|
4336
4398
|
}
|
|
@@ -4457,21 +4519,42 @@ function localTimeFromIso(iso) {
|
|
|
4457
4519
|
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
4458
4520
|
return `${hh}:${mm}:${ss}`;
|
|
4459
4521
|
}
|
|
4460
|
-
|
|
4522
|
+
function paintLevelPrefix(level, ansi) {
|
|
4523
|
+
const label = level.toUpperCase().padEnd(5);
|
|
4524
|
+
switch (level) {
|
|
4525
|
+
case "error":
|
|
4526
|
+
return `${ansi.red("\u2715")} ${ansi.red(label)}`;
|
|
4527
|
+
case "warn":
|
|
4528
|
+
return `${ansi.yellow("\u26A0")} ${ansi.yellow(label)}`;
|
|
4529
|
+
case "info":
|
|
4530
|
+
return `${ansi.cyan("\u2139")} ${ansi.cyan(label)}`;
|
|
4531
|
+
case "debug":
|
|
4532
|
+
case "trace":
|
|
4533
|
+
return `${ansi.dim("\xB7")} ${ansi.dim(label)}`;
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
var defaultFormat = (record, ansi) => {
|
|
4461
4537
|
const time = localTimeFromIso(record.timestamp);
|
|
4462
|
-
const
|
|
4463
|
-
const ctx = record.context && Object.keys(record.context).length > 0 ? ` | ${JSON.stringify(record.context)}` : "";
|
|
4464
|
-
return `${time}
|
|
4538
|
+
const prefix = paintLevelPrefix(record.level, ansi);
|
|
4539
|
+
const ctx = record.context && Object.keys(record.context).length > 0 ? ` ${ansi.dim("|")} ${ansi.dim(JSON.stringify(record.context))}` : "";
|
|
4540
|
+
return `${ansi.dim(time)} ${prefix} ${record.message}${ctx}
|
|
4465
4541
|
`;
|
|
4466
4542
|
};
|
|
4467
4543
|
var Logger = class {
|
|
4468
4544
|
#level;
|
|
4469
4545
|
#stream;
|
|
4470
4546
|
#format;
|
|
4547
|
+
#ansi;
|
|
4471
4548
|
constructor(opts) {
|
|
4472
4549
|
this.#level = opts.level;
|
|
4473
4550
|
this.#stream = opts.stream;
|
|
4474
4551
|
this.#format = opts.format ?? defaultFormat;
|
|
4552
|
+
const streamTty = opts.stream;
|
|
4553
|
+
this.#ansi = ansiFor({
|
|
4554
|
+
isTTY: streamTty.isTTY === true,
|
|
4555
|
+
noColorFlag: opts.noColorFlag === true,
|
|
4556
|
+
...opts.env !== void 0 ? { env: opts.env } : {}
|
|
4557
|
+
});
|
|
4475
4558
|
}
|
|
4476
4559
|
setLevel(level) {
|
|
4477
4560
|
this.#level = level;
|
|
@@ -4502,7 +4585,7 @@ var Logger = class {
|
|
|
4502
4585
|
message,
|
|
4503
4586
|
...context !== void 0 ? { context } : {}
|
|
4504
4587
|
};
|
|
4505
|
-
this.#stream.write(this.#format(record));
|
|
4588
|
+
this.#stream.write(this.#format(record, this.#ansi));
|
|
4506
4589
|
}
|
|
4507
4590
|
};
|
|
4508
4591
|
function resolveLogLevel(opts) {
|
|
@@ -4546,7 +4629,7 @@ function extractLogLevelFlag(argv) {
|
|
|
4546
4629
|
var LOGGER_ENV_VAR = ENV_VAR;
|
|
4547
4630
|
|
|
4548
4631
|
// cli/util/db-path.ts
|
|
4549
|
-
import { existsSync as
|
|
4632
|
+
import { existsSync as existsSync4 } from "fs";
|
|
4550
4633
|
|
|
4551
4634
|
// cli/i18n/util.texts.ts
|
|
4552
4635
|
var UTIL_TEXTS = {
|
|
@@ -4589,7 +4672,7 @@ var ExitCode = {
|
|
|
4589
4672
|
|
|
4590
4673
|
// cli/util/db-path.ts
|
|
4591
4674
|
function assertDbExists(path, stderr) {
|
|
4592
|
-
if (path === ":memory:" ||
|
|
4675
|
+
if (path === ":memory:" || existsSync4(path)) return true;
|
|
4593
4676
|
const stderrTty = stderr;
|
|
4594
4677
|
const ansi = ansiFor({ isTTY: stderrTty.isTTY === true, noColorFlag: false });
|
|
4595
4678
|
stderr.write(
|
|
@@ -4784,10 +4867,10 @@ import { Command as Command2, Option as Option2 } from "clipanion";
|
|
|
4784
4867
|
|
|
4785
4868
|
// core/config/helper.ts
|
|
4786
4869
|
import { homedir as osHomedir } from "os";
|
|
4787
|
-
import { isAbsolute, join as join4, resolve as resolve7 } from "path";
|
|
4870
|
+
import { isAbsolute, join as join4, resolve as resolve7, sep } from "path";
|
|
4788
4871
|
|
|
4789
4872
|
// kernel/config/loader.ts
|
|
4790
|
-
import { existsSync as
|
|
4873
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
4791
4874
|
|
|
4792
4875
|
// kernel/i18n/config-loader.texts.ts
|
|
4793
4876
|
var CONFIG_LOADER_TEXTS = {
|
|
@@ -4869,7 +4952,7 @@ function loadConfig(opts) {
|
|
|
4869
4952
|
{ path: kernelLocalSettingsPath(cwd), layer: "project-local" }
|
|
4870
4953
|
];
|
|
4871
4954
|
for (const { path, layer } of filePairs) {
|
|
4872
|
-
if (!
|
|
4955
|
+
if (!existsSync5(path)) continue;
|
|
4873
4956
|
const partial = readJsonSafe(path, layer, warnings, strict);
|
|
4874
4957
|
if (partial === null) continue;
|
|
4875
4958
|
const cleaned = validateAndStrip(validators, partial, layer, warnings, strict);
|
|
@@ -4890,7 +4973,7 @@ function loadConfig(opts) {
|
|
|
4890
4973
|
function readJsonSafe(path, layer, warnings, strict) {
|
|
4891
4974
|
let text;
|
|
4892
4975
|
try {
|
|
4893
|
-
text =
|
|
4976
|
+
text = readFileSync6(path, "utf8");
|
|
4894
4977
|
} catch (err) {
|
|
4895
4978
|
return reportAndSkip(
|
|
4896
4979
|
tx(CONFIG_LOADER_TEXTS.readFailure, { layer, path, message: formatErrorMessage(err) }),
|
|
@@ -5132,63 +5215,6 @@ function enumerateConfigPaths(obj, prefix = "") {
|
|
|
5132
5215
|
return out;
|
|
5133
5216
|
}
|
|
5134
5217
|
|
|
5135
|
-
// core/config/atomic-write.ts
|
|
5136
|
-
import {
|
|
5137
|
-
closeSync,
|
|
5138
|
-
constants as fsConstants,
|
|
5139
|
-
existsSync as existsSync5,
|
|
5140
|
-
mkdirSync as mkdirSync2,
|
|
5141
|
-
openSync,
|
|
5142
|
-
readFileSync as readFileSync6,
|
|
5143
|
-
renameSync,
|
|
5144
|
-
unlinkSync,
|
|
5145
|
-
writeSync
|
|
5146
|
-
} from "fs";
|
|
5147
|
-
import { randomBytes } from "crypto";
|
|
5148
|
-
import { dirname as dirname4 } from "path";
|
|
5149
|
-
function readJsonObjectOrEmpty(path) {
|
|
5150
|
-
if (!existsSync5(path)) return {};
|
|
5151
|
-
try {
|
|
5152
|
-
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
5153
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
5154
|
-
return raw;
|
|
5155
|
-
}
|
|
5156
|
-
} catch {
|
|
5157
|
-
}
|
|
5158
|
-
return {};
|
|
5159
|
-
}
|
|
5160
|
-
function writeFileAtomicExclusive(path, content, mode = 384) {
|
|
5161
|
-
const tmp = `${path}.tmp.${process.pid}.${randomBytes(8).toString("hex")}`;
|
|
5162
|
-
let fd = null;
|
|
5163
|
-
try {
|
|
5164
|
-
fd = openSync(
|
|
5165
|
-
tmp,
|
|
5166
|
-
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW,
|
|
5167
|
-
mode
|
|
5168
|
-
);
|
|
5169
|
-
writeSync(fd, content);
|
|
5170
|
-
closeSync(fd);
|
|
5171
|
-
fd = null;
|
|
5172
|
-
renameSync(tmp, path);
|
|
5173
|
-
} catch (err) {
|
|
5174
|
-
if (fd !== null) {
|
|
5175
|
-
try {
|
|
5176
|
-
closeSync(fd);
|
|
5177
|
-
} catch {
|
|
5178
|
-
}
|
|
5179
|
-
}
|
|
5180
|
-
try {
|
|
5181
|
-
unlinkSync(tmp);
|
|
5182
|
-
} catch {
|
|
5183
|
-
}
|
|
5184
|
-
throw err;
|
|
5185
|
-
}
|
|
5186
|
-
}
|
|
5187
|
-
function writeJsonAtomic(path, content) {
|
|
5188
|
-
mkdirSync2(dirname4(path), { recursive: true });
|
|
5189
|
-
writeFileAtomicExclusive(path, JSON.stringify(content, null, 2) + "\n");
|
|
5190
|
-
}
|
|
5191
|
-
|
|
5192
5218
|
// core/config/helper.ts
|
|
5193
5219
|
var PRIVACY_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
5194
5220
|
"scan.referencePaths"
|
|
@@ -5282,7 +5308,7 @@ function resolveScanPathForExposure(raw, cwd) {
|
|
|
5282
5308
|
}
|
|
5283
5309
|
function isUnderProject(absPath, cwd) {
|
|
5284
5310
|
const projectRoot = resolve7(cwd);
|
|
5285
|
-
return absPath === projectRoot || absPath.startsWith(`${projectRoot}
|
|
5311
|
+
return absPath === projectRoot || absPath.startsWith(`${projectRoot}${sep}`);
|
|
5286
5312
|
}
|
|
5287
5313
|
|
|
5288
5314
|
// core/config/sidecar-consent.ts
|
|
@@ -5674,7 +5700,11 @@ var SmCommand = class extends Command {
|
|
|
5674
5700
|
this.printer = createPrinter({
|
|
5675
5701
|
stdout: this.context.stdout,
|
|
5676
5702
|
stderr: this.context.stderr,
|
|
5677
|
-
|
|
5703
|
+
// `--json` suppresses info banners even on stderr: users piping
|
|
5704
|
+
// JSON through `jq` (or asserting machine output in tests) don't
|
|
5705
|
+
// want decorative lines polluting either channel. Aligns CLI
|
|
5706
|
+
// behaviour with the printer docstring.
|
|
5707
|
+
quietInfo: this.quiet || this.json
|
|
5678
5708
|
});
|
|
5679
5709
|
try {
|
|
5680
5710
|
return await this.run();
|
|
@@ -7237,9 +7267,9 @@ async function sweepPerTupleContributions(trx, contributions, freshlyRunTuples)
|
|
|
7237
7267
|
const bufferKeys = buildContributionsBufferKeys(contributions);
|
|
7238
7268
|
const tuplesByPluginExt = groupFreshlyRunTuplesByPluginExt(freshlyRunTuples);
|
|
7239
7269
|
for (const [pe, nodes] of tuplesByPluginExt) {
|
|
7240
|
-
const
|
|
7241
|
-
if (
|
|
7242
|
-
await deleteStaleTupleRows(trx, pe.slice(0,
|
|
7270
|
+
const sep8 = pe.indexOf("\0");
|
|
7271
|
+
if (sep8 < 0) continue;
|
|
7272
|
+
await deleteStaleTupleRows(trx, pe.slice(0, sep8), pe.slice(sep8 + 1), [...nodes], bufferKeys);
|
|
7243
7273
|
}
|
|
7244
7274
|
}
|
|
7245
7275
|
function buildContributionsBufferKeys(contributions) {
|
|
@@ -8018,8 +8048,24 @@ function applyIssueFilters(query, filter) {
|
|
|
8018
8048
|
)
|
|
8019
8049
|
);
|
|
8020
8050
|
}
|
|
8051
|
+
if (filter.nodePaths !== void 0) {
|
|
8052
|
+
q = applyNodePathsFilter(q, filter.nodePaths);
|
|
8053
|
+
}
|
|
8021
8054
|
return q;
|
|
8022
8055
|
}
|
|
8056
|
+
function applyNodePathsFilter(query, nodePaths) {
|
|
8057
|
+
if (nodePaths.length === 0) {
|
|
8058
|
+
return query.where(sql3`0`, "=", 1);
|
|
8059
|
+
}
|
|
8060
|
+
const targets = [...nodePaths];
|
|
8061
|
+
return query.where(
|
|
8062
|
+
({ exists, selectFrom }) => exists(
|
|
8063
|
+
selectFrom(
|
|
8064
|
+
sql3`json_each(scan_issues.node_ids_json)`.as("je")
|
|
8065
|
+
).select(sql3`1`.as("one")).where(sql3.ref("je.value"), "in", targets)
|
|
8066
|
+
)
|
|
8067
|
+
);
|
|
8068
|
+
}
|
|
8023
8069
|
async function findActiveIssues(db, predicate) {
|
|
8024
8070
|
const rows = await db.selectFrom("scan_issues").selectAll().execute();
|
|
8025
8071
|
const out = [];
|
|
@@ -8308,15 +8354,32 @@ async function tryWithSqlite(options, fn) {
|
|
|
8308
8354
|
import { resolve as resolve14 } from "path";
|
|
8309
8355
|
|
|
8310
8356
|
// core/paths/path-guard.ts
|
|
8311
|
-
import {
|
|
8357
|
+
import { lstatSync } from "fs";
|
|
8358
|
+
import { isAbsolute as isAbsolute2, resolve as resolve13, sep as sep2 } from "path";
|
|
8312
8359
|
function assertContained(cwd, rel) {
|
|
8313
8360
|
if (isAbsolute2(rel)) {
|
|
8314
8361
|
throw new Error(`node path is absolute, refusing to read: ${rel}`);
|
|
8315
8362
|
}
|
|
8316
8363
|
const abs = resolve13(cwd, rel);
|
|
8317
|
-
if (abs !== cwd && !abs.startsWith(cwd +
|
|
8364
|
+
if (abs !== cwd && !abs.startsWith(cwd + sep2)) {
|
|
8318
8365
|
throw new Error(`node path escapes repo root: ${rel}`);
|
|
8319
8366
|
}
|
|
8367
|
+
let isSymlink;
|
|
8368
|
+
try {
|
|
8369
|
+
isSymlink = lstatSync(abs).isSymbolicLink();
|
|
8370
|
+
} catch (err) {
|
|
8371
|
+
if (isAllowedLstatError(err)) return;
|
|
8372
|
+
throw err;
|
|
8373
|
+
}
|
|
8374
|
+
if (isSymlink) {
|
|
8375
|
+
throw new Error(`node path is a symlink, refusing to dereference: ${rel}`);
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
var ALLOWED_LSTAT_ERROR_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR"]);
|
|
8379
|
+
function isAllowedLstatError(err) {
|
|
8380
|
+
if (err === null || typeof err !== "object") return false;
|
|
8381
|
+
const code = err.code;
|
|
8382
|
+
return typeof code === "string" && ALLOWED_LSTAT_ERROR_CODES.has(code);
|
|
8320
8383
|
}
|
|
8321
8384
|
|
|
8322
8385
|
// cli/commands/bump-plan.ts
|
|
@@ -8364,12 +8427,12 @@ function planOne(node, options) {
|
|
|
8364
8427
|
};
|
|
8365
8428
|
}
|
|
8366
8429
|
function invokeBumpFor(node, absPath, force) {
|
|
8367
|
-
if (!
|
|
8430
|
+
if (!nodeBumpAction.invoke) {
|
|
8368
8431
|
throw new Error("built-in bump action is missing its invoke()");
|
|
8369
8432
|
}
|
|
8370
8433
|
const input = {};
|
|
8371
8434
|
if (force) input.force = true;
|
|
8372
|
-
return
|
|
8435
|
+
return nodeBumpAction.invoke(input, {
|
|
8373
8436
|
node,
|
|
8374
8437
|
nodeAbsolutePath: absPath,
|
|
8375
8438
|
invoker: "cli",
|
|
@@ -8385,7 +8448,7 @@ var BumpCommand = class extends SmCommand {
|
|
|
8385
8448
|
category: "Actions",
|
|
8386
8449
|
description: "Bump a node's sidecar (`<basename>.sm`): increment annotations.version, refresh hashes, stamp audit.",
|
|
8387
8450
|
details: `
|
|
8388
|
-
Wraps the built-in deterministic \`core/bump\` Action. Single-node
|
|
8451
|
+
Wraps the built-in deterministic \`core/node-bump\` Action. Single-node
|
|
8389
8452
|
mode bumps one path; \`--pending\` walks every node whose sidecar
|
|
8390
8453
|
overlay reports drift and bumps them all.
|
|
8391
8454
|
|
|
@@ -9259,9 +9322,9 @@ function providerKindFailure(opts, status, fileName, errDescription) {
|
|
|
9259
9322
|
}
|
|
9260
9323
|
};
|
|
9261
9324
|
}
|
|
9262
|
-
function isDirectorySafe(path,
|
|
9325
|
+
function isDirectorySafe(path, statSync12) {
|
|
9263
9326
|
try {
|
|
9264
|
-
return
|
|
9327
|
+
return statSync12(path).isDirectory();
|
|
9265
9328
|
} catch {
|
|
9266
9329
|
return false;
|
|
9267
9330
|
}
|
|
@@ -9781,14 +9844,14 @@ var LOCKED_PLUGIN_IDS = /* @__PURE__ */ new Set([
|
|
|
9781
9844
|
// unreachable from CLI / BFF / UI. Re-evaluate if a third-party ever
|
|
9782
9845
|
// ships a competing supersession extractor.
|
|
9783
9846
|
"core/annotations",
|
|
9784
|
-
// `core/
|
|
9847
|
+
// `core/schema-violation` validates every scanned Node against
|
|
9785
9848
|
// `node.schema.json` and every Link against `link.schema.json` (the
|
|
9786
9849
|
// authoritative @skill-map/spec). Disabling it makes the system
|
|
9787
9850
|
// persist non-conformant content silently, breaking the spec
|
|
9788
9851
|
// invariant "what reaches the DB conforms to the spec". The check is
|
|
9789
9852
|
// foundational, not advisory; lock it on so the guarantee holds
|
|
9790
9853
|
// regardless of user / DB / settings hand-edits.
|
|
9791
|
-
"core/
|
|
9854
|
+
"core/schema-violation",
|
|
9792
9855
|
// `core/ascii` is the only built-in Formatter today and the default
|
|
9793
9856
|
// for `sm graph` (`--format ascii`). Disabling it breaks the verb
|
|
9794
9857
|
// entirely (`composeFormatters` returns the empty list, the CLI
|
|
@@ -9824,7 +9887,8 @@ function isBuiltInExtensionEnabled(bundle, ext, resolveEnabled) {
|
|
|
9824
9887
|
}
|
|
9825
9888
|
function isBundleEntryEnabled(bundle, extId, resolveEnabled) {
|
|
9826
9889
|
if (bundle.granularity === "bundle") {
|
|
9827
|
-
|
|
9890
|
+
if (!resolveEnabled(bundle.id)) return false;
|
|
9891
|
+
return resolveEnabled(qualifiedExtensionId(bundle.id, extId));
|
|
9828
9892
|
}
|
|
9829
9893
|
return resolveEnabled(qualifiedExtensionId(bundle.id, extId));
|
|
9830
9894
|
}
|
|
@@ -9837,7 +9901,10 @@ function buildGranularityMap(discovered) {
|
|
|
9837
9901
|
}
|
|
9838
9902
|
function isPluginExtensionEnabled(ext, granularityMap, resolveEnabled) {
|
|
9839
9903
|
const granularity = granularityMap.get(ext.pluginId) ?? "bundle";
|
|
9840
|
-
if (granularity === "bundle")
|
|
9904
|
+
if (granularity === "bundle") {
|
|
9905
|
+
if (!resolveEnabled(ext.pluginId)) return false;
|
|
9906
|
+
return resolveEnabled(qualifiedExtensionId(ext.pluginId, ext.id));
|
|
9907
|
+
}
|
|
9841
9908
|
return resolveEnabled(qualifiedExtensionId(ext.pluginId, ext.id));
|
|
9842
9909
|
}
|
|
9843
9910
|
async function buildEnabledResolver(ctx) {
|
|
@@ -9855,7 +9922,7 @@ async function buildEnabledResolver(ctx) {
|
|
|
9855
9922
|
|
|
9856
9923
|
// kernel/scan/walk-content.ts
|
|
9857
9924
|
import { readFile, readdir, lstat } from "fs/promises";
|
|
9858
|
-
import { join as join9, relative as relative2, sep as
|
|
9925
|
+
import { join as join9, relative as relative2, sep as sep3 } from "path";
|
|
9859
9926
|
|
|
9860
9927
|
// kernel/scan/ignore.ts
|
|
9861
9928
|
import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
|
|
@@ -10036,7 +10103,7 @@ async function* walkContent(roots, options) {
|
|
|
10036
10103
|
const extensions = options.extensions;
|
|
10037
10104
|
for (const root of roots) {
|
|
10038
10105
|
for await (const file of walkRoot(root, root, filter, extensions)) {
|
|
10039
|
-
const relPath = relative2(root, file).split(
|
|
10106
|
+
const relPath = relative2(root, file).split(sep3).join("/");
|
|
10040
10107
|
let raw;
|
|
10041
10108
|
try {
|
|
10042
10109
|
raw = await readFile(file, "utf8");
|
|
@@ -10068,7 +10135,7 @@ async function* walkRoot(root, current, filter, extensions) {
|
|
|
10068
10135
|
for (const entry of entries) {
|
|
10069
10136
|
const name = entry.name;
|
|
10070
10137
|
const full = join9(current, name);
|
|
10071
|
-
const rel = relative2(root, full).split(
|
|
10138
|
+
const rel = relative2(root, full).split(sep3).join("/");
|
|
10072
10139
|
if (filter.ignores(rel)) continue;
|
|
10073
10140
|
if (entry.isSymbolicLink()) continue;
|
|
10074
10141
|
if (entry.isDirectory()) {
|
|
@@ -10508,7 +10575,7 @@ var CheckCommand = class extends SmCommand {
|
|
|
10508
10575
|
["Print every current issue", "$0 check"],
|
|
10509
10576
|
["Machine-readable issue list", "$0 check --json"],
|
|
10510
10577
|
["Restrict to a single node", "$0 check -n .claude/agents/architect.md"],
|
|
10511
|
-
["Restrict to specific rules", "$0 check --analyzers core/broken
|
|
10578
|
+
["Restrict to specific rules", "$0 check --analyzers core/reference-broken,core/schema-violation"],
|
|
10512
10579
|
["Opt in to probabilistic analyzers (stub until Step 10)", "$0 check --include-prob"],
|
|
10513
10580
|
["Use a non-default DB file", "$0 check --db /path/to/skill-map.db"]
|
|
10514
10581
|
]
|
|
@@ -11489,6 +11556,52 @@ function disableEnv(setup) {
|
|
|
11489
11556
|
if (setup?.disableAllAnalyzers) env["SKILL_MAP_DISABLE_ALL_ANALYZERS"] = "1";
|
|
11490
11557
|
return env;
|
|
11491
11558
|
}
|
|
11559
|
+
var SAFE_CONFORMANCE_ENV_KEYS = [
|
|
11560
|
+
"PATH",
|
|
11561
|
+
"HOME",
|
|
11562
|
+
"USERPROFILE",
|
|
11563
|
+
"TMPDIR",
|
|
11564
|
+
"TMP",
|
|
11565
|
+
"TEMP",
|
|
11566
|
+
"SystemRoot",
|
|
11567
|
+
"SystemDrive",
|
|
11568
|
+
"OS",
|
|
11569
|
+
"COMSPEC",
|
|
11570
|
+
"PATHEXT",
|
|
11571
|
+
"NODE_OPTIONS",
|
|
11572
|
+
"NODE_PATH",
|
|
11573
|
+
"NODE_NO_WARNINGS",
|
|
11574
|
+
"NODE_DEBUG",
|
|
11575
|
+
"LANG",
|
|
11576
|
+
"TERM",
|
|
11577
|
+
"COLORTERM",
|
|
11578
|
+
"NO_COLOR",
|
|
11579
|
+
"FORCE_COLOR",
|
|
11580
|
+
"CI"
|
|
11581
|
+
];
|
|
11582
|
+
var SAFE_CONFORMANCE_ENV_PREFIXES = [
|
|
11583
|
+
"LC_",
|
|
11584
|
+
"SKILL_MAP_",
|
|
11585
|
+
"SM_"
|
|
11586
|
+
];
|
|
11587
|
+
function pickSafeEnv(source) {
|
|
11588
|
+
const out = {};
|
|
11589
|
+
for (const key of SAFE_CONFORMANCE_ENV_KEYS) {
|
|
11590
|
+
const value = source[key];
|
|
11591
|
+
if (value !== void 0) out[key] = value;
|
|
11592
|
+
}
|
|
11593
|
+
for (const key of Object.keys(source)) {
|
|
11594
|
+
if (out[key] !== void 0) continue;
|
|
11595
|
+
for (const prefix of SAFE_CONFORMANCE_ENV_PREFIXES) {
|
|
11596
|
+
if (key.startsWith(prefix)) {
|
|
11597
|
+
const value = source[key];
|
|
11598
|
+
if (value !== void 0) out[key] = value;
|
|
11599
|
+
break;
|
|
11600
|
+
}
|
|
11601
|
+
}
|
|
11602
|
+
}
|
|
11603
|
+
return out;
|
|
11604
|
+
}
|
|
11492
11605
|
function runConformanceCase(options) {
|
|
11493
11606
|
const raw = readFileSync14(options.casePath, "utf8");
|
|
11494
11607
|
const c = JSON.parse(raw);
|
|
@@ -11508,7 +11621,7 @@ function runConformanceCase(options) {
|
|
|
11508
11621
|
if (c.invoke.flags) argv.push(...c.invoke.flags);
|
|
11509
11622
|
const child = spawnSync2(process.execPath, [options.binary, ...argv], {
|
|
11510
11623
|
cwd: scope,
|
|
11511
|
-
env: { ...process.env, ...options.env, ...setupEnv },
|
|
11624
|
+
env: { ...pickSafeEnv(process.env), ...options.env, ...setupEnv },
|
|
11512
11625
|
encoding: "utf8"
|
|
11513
11626
|
});
|
|
11514
11627
|
const stdout = child.stdout ?? "";
|
|
@@ -11536,7 +11649,7 @@ function runPriorScansSetup(c, options, scope, fixturesRoot, setupEnv) {
|
|
|
11536
11649
|
const stepArgv = ["scan", ...step.flags ?? []];
|
|
11537
11650
|
const stepChild = spawnSync2(process.execPath, [options.binary, ...stepArgv], {
|
|
11538
11651
|
cwd: scope,
|
|
11539
|
-
env: { ...process.env, ...options.env, ...setupEnv },
|
|
11652
|
+
env: { ...pickSafeEnv(process.env), ...options.env, ...setupEnv },
|
|
11540
11653
|
encoding: "utf8"
|
|
11541
11654
|
});
|
|
11542
11655
|
if ((stepChild.status ?? 0) !== 0) {
|
|
@@ -14035,14 +14148,7 @@ function registeredVerbPaths(cli2) {
|
|
|
14035
14148
|
}
|
|
14036
14149
|
|
|
14037
14150
|
// cli/commands/hooks.ts
|
|
14038
|
-
import {
|
|
14039
|
-
chmodSync,
|
|
14040
|
-
existsSync as existsSync19,
|
|
14041
|
-
mkdirSync as mkdirSync5,
|
|
14042
|
-
readFileSync as readFileSync17,
|
|
14043
|
-
statSync as statSync5,
|
|
14044
|
-
writeFileSync as writeFileSync2
|
|
14045
|
-
} from "fs";
|
|
14151
|
+
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, stat as stat2, writeFile } from "fs/promises";
|
|
14046
14152
|
import { dirname as dirname16, resolve as resolve27 } from "path";
|
|
14047
14153
|
import { Command as Command16, Option as Option15 } from "clipanion";
|
|
14048
14154
|
|
|
@@ -14136,7 +14242,7 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
14136
14242
|
return ExitCode.Error;
|
|
14137
14243
|
}
|
|
14138
14244
|
const ctx = defaultRuntimeContext();
|
|
14139
|
-
const repoRoot = findGitRepoRoot(ctx.cwd);
|
|
14245
|
+
const repoRoot = await findGitRepoRoot(ctx.cwd);
|
|
14140
14246
|
if (repoRoot === null) {
|
|
14141
14247
|
this.printer.error(
|
|
14142
14248
|
tx(HOOKS_TEXTS.notInGitRepo, {
|
|
@@ -14148,7 +14254,7 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
14148
14254
|
}
|
|
14149
14255
|
const hooksDir = resolve27(repoRoot, ".git", "hooks");
|
|
14150
14256
|
const hookPath = resolve27(hooksDir, "pre-commit");
|
|
14151
|
-
const existing =
|
|
14257
|
+
const existing = await pathExists(hookPath) ? await readFile2(hookPath, "utf8") : null;
|
|
14152
14258
|
const planned2 = computePlannedHookContent(existing);
|
|
14153
14259
|
if (planned2.kind === "already-installed") {
|
|
14154
14260
|
this.printer.info(tx(HOOKS_TEXTS.alreadyInstalled, { glyph: okGlyph, hookPath }));
|
|
@@ -14174,9 +14280,9 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
14174
14280
|
return ExitCode.Ok;
|
|
14175
14281
|
}
|
|
14176
14282
|
try {
|
|
14177
|
-
if (!
|
|
14178
|
-
|
|
14179
|
-
ensureExecutableBit(hookPath);
|
|
14283
|
+
if (!await pathExists(hooksDir)) await mkdir3(hooksDir, { recursive: true });
|
|
14284
|
+
await writeFile(hookPath, planned2.content, { encoding: "utf8" });
|
|
14285
|
+
await ensureExecutableBit(hookPath);
|
|
14180
14286
|
} catch (err) {
|
|
14181
14287
|
this.printer.error(
|
|
14182
14288
|
tx(HOOKS_TEXTS.installFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
|
|
@@ -14202,10 +14308,10 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
14202
14308
|
return ExitCode.Ok;
|
|
14203
14309
|
}
|
|
14204
14310
|
};
|
|
14205
|
-
function findGitRepoRoot(cwd) {
|
|
14311
|
+
async function findGitRepoRoot(cwd) {
|
|
14206
14312
|
let current = cwd;
|
|
14207
14313
|
while (true) {
|
|
14208
|
-
if (
|
|
14314
|
+
if (await pathExists(resolve27(current, ".git"))) return current;
|
|
14209
14315
|
const parent = dirname16(current);
|
|
14210
14316
|
if (parent === current) return null;
|
|
14211
14317
|
current = parent;
|
|
@@ -14216,22 +14322,22 @@ function computePlannedHookContent(existing) {
|
|
|
14216
14322
|
if (existing.includes(SKILL_MAP_MARKER)) {
|
|
14217
14323
|
return { kind: "already-installed", content: existing };
|
|
14218
14324
|
}
|
|
14219
|
-
const
|
|
14220
|
-
return { kind: "chained", content: existing +
|
|
14325
|
+
const sep8 = existing.endsWith("\n") ? "" : "\n";
|
|
14326
|
+
return { kind: "chained", content: existing + sep8 + "\n" + SKILL_MAP_BLOCK };
|
|
14221
14327
|
}
|
|
14222
|
-
function ensureExecutableBit(path) {
|
|
14223
|
-
const mode =
|
|
14224
|
-
|
|
14328
|
+
async function ensureExecutableBit(path) {
|
|
14329
|
+
const mode = (await stat2(path)).mode;
|
|
14330
|
+
await chmod2(path, mode | 73);
|
|
14225
14331
|
}
|
|
14226
14332
|
var HOOKS_COMMANDS = [HooksInstallCommand];
|
|
14227
14333
|
|
|
14228
14334
|
// cli/commands/init.ts
|
|
14229
|
-
import { mkdir as
|
|
14335
|
+
import { mkdir as mkdir4, readFile as readFile3, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
14230
14336
|
import { join as join17 } from "path";
|
|
14231
14337
|
import { Command as Command17, Option as Option16 } from "clipanion";
|
|
14232
14338
|
|
|
14233
14339
|
// kernel/orchestrator/index.ts
|
|
14234
|
-
import { existsSync as
|
|
14340
|
+
import { existsSync as existsSync21, statSync as statSync6 } from "fs";
|
|
14235
14341
|
import { isAbsolute as isAbsolute7, resolve as resolve28 } from "path";
|
|
14236
14342
|
import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
|
|
14237
14343
|
import cl100k_base from "js-tiktoken/ranks/cl100k_base";
|
|
@@ -14592,7 +14698,8 @@ function recomputeLinkCounts(nodes, links) {
|
|
|
14592
14698
|
for (const link of links) {
|
|
14593
14699
|
const source = byPath3.get(link.source);
|
|
14594
14700
|
if (source) source.linksOutCount += 1;
|
|
14595
|
-
const
|
|
14701
|
+
const targetKey = link.resolvedTarget ?? link.target;
|
|
14702
|
+
const target = byPath3.get(targetKey);
|
|
14596
14703
|
if (target) target.linksInCount += 1;
|
|
14597
14704
|
}
|
|
14598
14705
|
}
|
|
@@ -15265,8 +15372,8 @@ function computeDriftStatus(args2) {
|
|
|
15265
15372
|
}
|
|
15266
15373
|
|
|
15267
15374
|
// kernel/sidecar/discover-orphans.ts
|
|
15268
|
-
import { existsSync as
|
|
15269
|
-
import { join as join13, relative as relative4, sep as
|
|
15375
|
+
import { existsSync as existsSync19, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
15376
|
+
import { join as join13, relative as relative4, sep as sep4 } from "path";
|
|
15270
15377
|
function discoverOrphanSidecars(roots, shouldSkip) {
|
|
15271
15378
|
const out = [];
|
|
15272
15379
|
for (const root of roots) {
|
|
@@ -15283,7 +15390,7 @@ function walk(root, current, shouldSkip, out) {
|
|
|
15283
15390
|
}
|
|
15284
15391
|
for (const entry of entries) {
|
|
15285
15392
|
const full = join13(current, entry.name);
|
|
15286
|
-
const rel = relative4(root, full).split(
|
|
15393
|
+
const rel = relative4(root, full).split(sep4).join("/");
|
|
15287
15394
|
if (shouldSkip(rel)) continue;
|
|
15288
15395
|
if (entry.isSymbolicLink()) continue;
|
|
15289
15396
|
if (entry.isDirectory()) {
|
|
@@ -15293,13 +15400,13 @@ function walk(root, current, shouldSkip, out) {
|
|
|
15293
15400
|
if (!entry.isFile()) continue;
|
|
15294
15401
|
if (!entry.name.endsWith(".sm")) continue;
|
|
15295
15402
|
const expectedMd = `${full.slice(0, -".sm".length)}.md`;
|
|
15296
|
-
if (
|
|
15403
|
+
if (existsSync19(expectedMd) && safeIsFile(expectedMd)) continue;
|
|
15297
15404
|
out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
|
|
15298
15405
|
}
|
|
15299
15406
|
}
|
|
15300
15407
|
function safeIsFile(path) {
|
|
15301
15408
|
try {
|
|
15302
|
-
return
|
|
15409
|
+
return statSync5(path).isFile();
|
|
15303
15410
|
} catch {
|
|
15304
15411
|
return false;
|
|
15305
15412
|
}
|
|
@@ -15307,7 +15414,7 @@ function safeIsFile(path) {
|
|
|
15307
15414
|
|
|
15308
15415
|
// kernel/orchestrator/node-build.ts
|
|
15309
15416
|
import { createHash } from "crypto";
|
|
15310
|
-
import { existsSync as
|
|
15417
|
+
import { existsSync as existsSync20 } from "fs";
|
|
15311
15418
|
import { isAbsolute as isAbsolute6, resolve as resolvePath } from "path";
|
|
15312
15419
|
import "js-tiktoken/lite";
|
|
15313
15420
|
import yaml4 from "js-yaml";
|
|
@@ -15471,11 +15578,11 @@ function resolveSidecarOverlay(relativePath2, nodePathForIssue, roots, liveBodyH
|
|
|
15471
15578
|
}
|
|
15472
15579
|
function resolveAbsoluteMdPath(relativePath2, roots) {
|
|
15473
15580
|
if (isAbsolute6(relativePath2)) {
|
|
15474
|
-
return
|
|
15581
|
+
return existsSync20(relativePath2) ? relativePath2 : null;
|
|
15475
15582
|
}
|
|
15476
15583
|
for (const root of roots) {
|
|
15477
15584
|
const candidate = resolvePath(root, relativePath2);
|
|
15478
|
-
if (
|
|
15585
|
+
if (existsSync20(candidate)) return candidate;
|
|
15479
15586
|
}
|
|
15480
15587
|
return null;
|
|
15481
15588
|
}
|
|
@@ -16016,7 +16123,7 @@ function validateRoots(roots) {
|
|
|
16016
16123
|
throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
|
|
16017
16124
|
}
|
|
16018
16125
|
for (const root of roots) {
|
|
16019
|
-
if (!
|
|
16126
|
+
if (!existsSync21(root) || !statSync6(root).isDirectory()) {
|
|
16020
16127
|
throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
|
|
16021
16128
|
}
|
|
16022
16129
|
}
|
|
@@ -16025,7 +16132,7 @@ function resolveActiveProviderOption(optionValue, roots) {
|
|
|
16025
16132
|
if (optionValue !== void 0) return optionValue;
|
|
16026
16133
|
for (const root of roots) {
|
|
16027
16134
|
const absRoot = isAbsolute7(root) ? root : resolve28(root);
|
|
16028
|
-
if (!
|
|
16135
|
+
if (!existsSync21(absRoot)) continue;
|
|
16029
16136
|
const detected = resolveActiveProvider(absRoot).resolved;
|
|
16030
16137
|
if (detected !== null) return detected;
|
|
16031
16138
|
}
|
|
@@ -16033,7 +16140,7 @@ function resolveActiveProviderOption(optionValue, roots) {
|
|
|
16033
16140
|
}
|
|
16034
16141
|
|
|
16035
16142
|
// kernel/scan/watcher.ts
|
|
16036
|
-
import { resolve as resolve29, relative as relative5, sep as
|
|
16143
|
+
import { resolve as resolve29, relative as relative5, sep as sep5 } from "path";
|
|
16037
16144
|
import chokidar from "chokidar";
|
|
16038
16145
|
function createChokidarWatcher(opts) {
|
|
16039
16146
|
const absRoots = opts.roots.map((r) => resolve29(opts.cwd, r));
|
|
@@ -16131,8 +16238,8 @@ function relativePathFromRoots2(absolute, absRoots) {
|
|
|
16131
16238
|
for (const root of absRoots) {
|
|
16132
16239
|
const rel = relative5(root, absolute);
|
|
16133
16240
|
if (rel === "" || rel === ".") return "";
|
|
16134
|
-
if (!rel.startsWith("..") && !rel.startsWith(`..${
|
|
16135
|
-
return rel.split(
|
|
16241
|
+
if (!rel.startsWith("..") && !rel.startsWith(`..${sep5}`)) {
|
|
16242
|
+
return rel.split(sep5).join("/");
|
|
16136
16243
|
}
|
|
16137
16244
|
}
|
|
16138
16245
|
return null;
|
|
@@ -16254,13 +16361,13 @@ function createKernel() {
|
|
|
16254
16361
|
}
|
|
16255
16362
|
|
|
16256
16363
|
// kernel/jobs/orphan-files.ts
|
|
16257
|
-
import { readdirSync as readdirSync8, statSync as
|
|
16364
|
+
import { readdirSync as readdirSync8, statSync as statSync7 } from "fs";
|
|
16258
16365
|
import { join as join14, resolve as resolve30 } from "path";
|
|
16259
16366
|
function findOrphanJobFiles(jobsDir, referencedPaths) {
|
|
16260
16367
|
let entries;
|
|
16261
16368
|
try {
|
|
16262
|
-
const
|
|
16263
|
-
if (!
|
|
16369
|
+
const stat3 = statSync7(jobsDir);
|
|
16370
|
+
if (!stat3.isDirectory()) {
|
|
16264
16371
|
return { orphanFilePaths: [], referencedCount: referencedPaths.size };
|
|
16265
16372
|
}
|
|
16266
16373
|
entries = readdirSync8(jobsDir, { withFileTypes: true });
|
|
@@ -16328,7 +16435,7 @@ var SCAN_RUNNER_TEXTS = {
|
|
|
16328
16435
|
priorSchemaValidationFailed: "prior scan-result loaded from DB failed schema validation: {{errors}}. Run `sm db backup` then re-scan without --strict to rebuild from disk.",
|
|
16329
16436
|
/**
|
|
16330
16437
|
* Reference-paths walker hit `REFERENCE_WALK_MAX_FILES` and stopped
|
|
16331
|
-
* early. The set may be incomplete for link validation; `core/broken
|
|
16438
|
+
* early. The set may be incomplete for link validation; `core/reference-broken`
|
|
16332
16439
|
* still works against whatever made it in.
|
|
16333
16440
|
*/
|
|
16334
16441
|
referenceWalkTruncated: "scan.referencePaths: walker truncated at the 50000-file safety cap. Some link targets may flag as broken even though they exist on disk. Trim the configured paths to dirs you actually need to validate against.",
|
|
@@ -16422,7 +16529,7 @@ function resolveScanRoots(inputs) {
|
|
|
16422
16529
|
}
|
|
16423
16530
|
|
|
16424
16531
|
// core/runtime/reference-paths-walker.ts
|
|
16425
|
-
import { readdirSync as readdirSync9, statSync as
|
|
16532
|
+
import { readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
16426
16533
|
import { homedir as osHomedir2 } from "os";
|
|
16427
16534
|
import { isAbsolute as isAbsolute8, join as join15, resolve as resolve31 } from "path";
|
|
16428
16535
|
var REFERENCE_WALK_MAX_FILES = 5e4;
|
|
@@ -16444,8 +16551,8 @@ function walkReferencePaths(rawRoots, cwd) {
|
|
|
16444
16551
|
for (const raw of rawRoots) {
|
|
16445
16552
|
if (truncated) break;
|
|
16446
16553
|
const root = resolveScanPath(raw, cwd);
|
|
16447
|
-
const
|
|
16448
|
-
if (!
|
|
16554
|
+
const stat3 = safeStat(root);
|
|
16555
|
+
if (!stat3 || !stat3.isDirectory()) {
|
|
16449
16556
|
missingRoots.push(root);
|
|
16450
16557
|
continue;
|
|
16451
16558
|
}
|
|
@@ -16476,7 +16583,7 @@ function walkInto(dir, out) {
|
|
|
16476
16583
|
}
|
|
16477
16584
|
function safeStat(path) {
|
|
16478
16585
|
try {
|
|
16479
|
-
return
|
|
16586
|
+
return statSync8(path);
|
|
16480
16587
|
} catch {
|
|
16481
16588
|
return null;
|
|
16482
16589
|
}
|
|
@@ -16979,7 +17086,7 @@ var InitCommand = class extends SmCommand {
|
|
|
16979
17086
|
const printer = this.printer ?? createPrinter({
|
|
16980
17087
|
stdout: this.context.stdout,
|
|
16981
17088
|
stderr: this.context.stderr,
|
|
16982
|
-
quietInfo: this.quiet
|
|
17089
|
+
quietInfo: this.quiet || this.json
|
|
16983
17090
|
});
|
|
16984
17091
|
if (this.dryRun) {
|
|
16985
17092
|
await writeDryRunPlan(printer, {
|
|
@@ -16994,7 +17101,7 @@ var InitCommand = class extends SmCommand {
|
|
|
16994
17101
|
});
|
|
16995
17102
|
return ExitCode.Ok;
|
|
16996
17103
|
}
|
|
16997
|
-
await
|
|
17104
|
+
await mkdir4(skillMapDir, { recursive: true });
|
|
16998
17105
|
writeFileAtomicExclusive(settingsPath, JSON.stringify({ schemaVersion: 1 }, null, 2) + "\n");
|
|
16999
17106
|
if (!await pathExists(localPath) || this.force) {
|
|
17000
17107
|
writeFileAtomicExclusive(localPath, "{}\n");
|
|
@@ -17168,7 +17275,7 @@ async function runFirstScan(scopeRoot, strict, printer, stderr, stdin, ansi) {
|
|
|
17168
17275
|
}
|
|
17169
17276
|
async function previewGitignoreEntries(scopeRoot, entries) {
|
|
17170
17277
|
const path = join17(scopeRoot, ".gitignore");
|
|
17171
|
-
const body = await pathExists(path) ? await
|
|
17278
|
+
const body = await pathExists(path) ? await readFile3(path, "utf8") : "";
|
|
17172
17279
|
const present = new Set(
|
|
17173
17280
|
body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
17174
17281
|
);
|
|
@@ -17178,7 +17285,7 @@ async function ensureGitignoreEntries(scopeRoot, entries) {
|
|
|
17178
17285
|
const path = join17(scopeRoot, ".gitignore");
|
|
17179
17286
|
let body = "";
|
|
17180
17287
|
if (await pathExists(path)) {
|
|
17181
|
-
body = await
|
|
17288
|
+
body = await readFile3(path, "utf8");
|
|
17182
17289
|
}
|
|
17183
17290
|
const present = new Set(
|
|
17184
17291
|
body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
@@ -17192,7 +17299,7 @@ async function ensureGitignoreEntries(scopeRoot, entries) {
|
|
|
17192
17299
|
present.add(entry);
|
|
17193
17300
|
changed = true;
|
|
17194
17301
|
}
|
|
17195
|
-
if (changed) await
|
|
17302
|
+
if (changed) await writeFile2(path, body);
|
|
17196
17303
|
return changed;
|
|
17197
17304
|
}
|
|
17198
17305
|
|
|
@@ -18894,6 +19001,27 @@ var PLUGINS_TEXTS = {
|
|
|
18894
19001
|
slotsListTipText: "Tip: full spec at spec/view-slots.md and spec/input-types.md."
|
|
18895
19002
|
};
|
|
18896
19003
|
|
|
19004
|
+
// plugins/presentation-order.ts
|
|
19005
|
+
var BUILT_IN_BUNDLE_PRESENTATION_ORDER = [
|
|
19006
|
+
"core",
|
|
19007
|
+
"claude",
|
|
19008
|
+
"antigravity",
|
|
19009
|
+
"openai",
|
|
19010
|
+
"agent-skills"
|
|
19011
|
+
];
|
|
19012
|
+
function sortBundlesForPresentation(bundles) {
|
|
19013
|
+
const orderIndex = (id) => {
|
|
19014
|
+
const idx = BUILT_IN_BUNDLE_PRESENTATION_ORDER.indexOf(id);
|
|
19015
|
+
return idx >= 0 ? idx : BUILT_IN_BUNDLE_PRESENTATION_ORDER.length;
|
|
19016
|
+
};
|
|
19017
|
+
return [...bundles].sort((a, b) => {
|
|
19018
|
+
const ai = orderIndex(a.id);
|
|
19019
|
+
const bi = orderIndex(b.id);
|
|
19020
|
+
if (ai !== bi) return ai - bi;
|
|
19021
|
+
return a.id.localeCompare(b.id);
|
|
19022
|
+
});
|
|
19023
|
+
}
|
|
19024
|
+
|
|
18897
19025
|
// cli/commands/plugins/shared.ts
|
|
18898
19026
|
import { resolve as resolve32 } from "path";
|
|
18899
19027
|
function resolveSearchPaths2(opts, cwd) {
|
|
@@ -18923,7 +19051,7 @@ async function loadAll(opts) {
|
|
|
18923
19051
|
return loader.discoverAndLoadAll();
|
|
18924
19052
|
}
|
|
18925
19053
|
function builtInRows(resolveEnabled) {
|
|
18926
|
-
return builtInBundles.map((bundle) => {
|
|
19054
|
+
return sortBundlesForPresentation(builtInBundles).map((bundle) => {
|
|
18927
19055
|
const bundleEnabled = resolveEnabled(bundle.id);
|
|
18928
19056
|
const extensions = bundle.extensions.map((ext) => extensionRowFromBuiltIn(ext, bundle, bundleEnabled, resolveEnabled));
|
|
18929
19057
|
const manifestSummary = bundle.extensions.map((ext) => `${ext.kind}:${qualifiedExtensionId(bundle.id, ext.id)}@${ext.version}`).join(", ");
|
|
@@ -18938,11 +19066,12 @@ function builtInRows(resolveEnabled) {
|
|
|
18938
19066
|
});
|
|
18939
19067
|
}
|
|
18940
19068
|
function extensionRowFromBuiltIn(ext, bundle, bundleEnabled, resolveEnabled) {
|
|
19069
|
+
const qualifiedEnabled = resolveEnabled(qualifiedExtensionId(bundle.id, ext.id));
|
|
18941
19070
|
const row = {
|
|
18942
19071
|
id: ext.id,
|
|
18943
19072
|
kind: ext.kind,
|
|
18944
19073
|
version: ext.version,
|
|
18945
|
-
enabled: bundle.granularity === "bundle" ? bundleEnabled :
|
|
19074
|
+
enabled: bundle.granularity === "bundle" ? bundleEnabled && qualifiedEnabled : qualifiedEnabled,
|
|
18946
19075
|
description: ext.description ?? ""
|
|
18947
19076
|
};
|
|
18948
19077
|
if (ext.entry !== void 0) row.entry = ext.entry;
|
|
@@ -19058,10 +19187,10 @@ function pluginToListRow(p) {
|
|
|
19058
19187
|
}
|
|
19059
19188
|
function wrapNames(names, indent, maxWidth) {
|
|
19060
19189
|
const out = [];
|
|
19061
|
-
const
|
|
19190
|
+
const sep8 = ", ";
|
|
19062
19191
|
let current = "";
|
|
19063
19192
|
for (const name of names) {
|
|
19064
|
-
const candidate = current === "" ? name : `${current}${
|
|
19193
|
+
const candidate = current === "" ? name : `${current}${sep8}${name}`;
|
|
19065
19194
|
if (indent.length + candidate.length > maxWidth && current !== "") {
|
|
19066
19195
|
out.push(`${current},`);
|
|
19067
19196
|
current = name;
|
|
@@ -19629,10 +19758,13 @@ function extensionInstance(ext) {
|
|
|
19629
19758
|
}
|
|
19630
19759
|
function collectKnownKinds(plugins) {
|
|
19631
19760
|
const known = /* @__PURE__ */ new Set();
|
|
19632
|
-
forEachProviderInstance(plugins, ({ instance }) => {
|
|
19761
|
+
forEachProviderInstance(plugins, ({ pluginId, instance }) => {
|
|
19633
19762
|
const map = instance["kinds"];
|
|
19634
19763
|
if (map === null || typeof map !== "object") return;
|
|
19635
|
-
for (const k of Object.keys(map))
|
|
19764
|
+
for (const k of Object.keys(map)) {
|
|
19765
|
+
known.add(k);
|
|
19766
|
+
known.add(qualifiedExtensionId(pluginId, k));
|
|
19767
|
+
}
|
|
19636
19768
|
});
|
|
19637
19769
|
return known;
|
|
19638
19770
|
}
|
|
@@ -19881,7 +20013,7 @@ var TogglePluginsBase = class extends SmCommand {
|
|
|
19881
20013
|
* the plugin's `scan_contributions` rows immediately (matches the
|
|
19882
20014
|
* BFF route, see `server/routes/plugins.ts:applyChangeToAdapter`).
|
|
19883
20015
|
* `targets` carries either a bare bundle id (e.g. `claude`) or a
|
|
19884
|
-
* qualified `<bundle>/<ext>` (e.g. `core/slash`); the split mirrors
|
|
20016
|
+
* qualified `<bundle>/<ext>` (e.g. `core/slash-command`); the split mirrors
|
|
19885
20017
|
* how the catalog sweep groups rows.
|
|
19886
20018
|
*/
|
|
19887
20019
|
async #persistTargets(targets, enabled) {
|
|
@@ -20066,7 +20198,7 @@ function resolveBareToggle(id, catalogue, verb, ansi) {
|
|
|
20066
20198
|
}
|
|
20067
20199
|
|
|
20068
20200
|
// cli/commands/plugins/create.ts
|
|
20069
|
-
import { existsSync as
|
|
20201
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync5, writeFileSync } from "fs";
|
|
20070
20202
|
import { join as join18, resolve as resolve33 } from "path";
|
|
20071
20203
|
import { Command as Command26, Option as Option25 } from "clipanion";
|
|
20072
20204
|
var PluginsCreateCommand = class extends SmCommand {
|
|
@@ -20095,7 +20227,7 @@ var PluginsCreateCommand = class extends SmCommand {
|
|
|
20095
20227
|
const ctx = defaultRuntimeContext();
|
|
20096
20228
|
const baseDir = defaultProjectPluginsDir(ctx);
|
|
20097
20229
|
const targetDir = this.at ? resolve33(this.at) : join18(baseDir, this.pluginId);
|
|
20098
|
-
if (
|
|
20230
|
+
if (existsSync22(targetDir) && !this.force) {
|
|
20099
20231
|
this.printer.error(
|
|
20100
20232
|
tx(PLUGINS_TEXTS.createRefuseOverwrite, {
|
|
20101
20233
|
glyph: errGlyph,
|
|
@@ -20106,7 +20238,7 @@ var PluginsCreateCommand = class extends SmCommand {
|
|
|
20106
20238
|
return ExitCode.Error;
|
|
20107
20239
|
}
|
|
20108
20240
|
const extractorName = `${this.pluginId}-extractor`;
|
|
20109
|
-
|
|
20241
|
+
mkdirSync5(join18(targetDir, "extractors", extractorName), { recursive: true });
|
|
20110
20242
|
const specVersion = installedSpecVersion();
|
|
20111
20243
|
const manifest = {
|
|
20112
20244
|
id: this.pluginId,
|
|
@@ -20125,15 +20257,15 @@ var PluginsCreateCommand = class extends SmCommand {
|
|
|
20125
20257
|
}
|
|
20126
20258
|
}
|
|
20127
20259
|
};
|
|
20128
|
-
|
|
20260
|
+
writeFileSync(
|
|
20129
20261
|
join18(targetDir, "plugin.json"),
|
|
20130
20262
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
20131
20263
|
);
|
|
20132
|
-
|
|
20264
|
+
writeFileSync(
|
|
20133
20265
|
join18(targetDir, "extractors", extractorName, "index.js"),
|
|
20134
20266
|
scaffolderExtractorStub(extractorName)
|
|
20135
20267
|
);
|
|
20136
|
-
|
|
20268
|
+
writeFileSync(join18(targetDir, "README.md"), scaffolderReadme(this.pluginId));
|
|
20137
20269
|
this.printer.data(
|
|
20138
20270
|
tx(PLUGINS_TEXTS.createSuccess, {
|
|
20139
20271
|
targetDir: sanitizeForTerminal(targetDir),
|
|
@@ -20335,7 +20467,7 @@ var PLUGIN_COMMANDS = [
|
|
|
20335
20467
|
];
|
|
20336
20468
|
|
|
20337
20469
|
// cli/commands/refresh.ts
|
|
20338
|
-
import { readFile as
|
|
20470
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
20339
20471
|
import { resolve as resolve34 } from "path";
|
|
20340
20472
|
import { Command as Command29, Option as Option27 } from "clipanion";
|
|
20341
20473
|
|
|
@@ -20642,7 +20774,7 @@ var RefreshCommand = class extends SmCommand {
|
|
|
20642
20774
|
let body;
|
|
20643
20775
|
try {
|
|
20644
20776
|
assertContained(cwd, node.path);
|
|
20645
|
-
const raw = await
|
|
20777
|
+
const raw = await readFile4(resolve34(cwd, node.path), "utf8");
|
|
20646
20778
|
body = stripFrontmatterFence(raw);
|
|
20647
20779
|
} catch (err) {
|
|
20648
20780
|
if (!this.json) {
|
|
@@ -21491,7 +21623,7 @@ var ScanCommand = class extends SmCommand {
|
|
|
21491
21623
|
}
|
|
21492
21624
|
return null;
|
|
21493
21625
|
}
|
|
21494
|
-
/** Render the failure branch of `
|
|
21626
|
+
/** Render the failure branch of `TScanRunResult` to stderr. */
|
|
21495
21627
|
renderFailure(outcome) {
|
|
21496
21628
|
const ansi = this.ansiFor("stderr");
|
|
21497
21629
|
const errGlyph = ansi.red("\u2715");
|
|
@@ -21570,7 +21702,7 @@ var ScanCommand = class extends SmCommand {
|
|
|
21570
21702
|
this.printer.info(
|
|
21571
21703
|
tx(SCAN_TEXTS.jsonSelfValidationFailed, {
|
|
21572
21704
|
glyph: ansi.red("\u2715"),
|
|
21573
|
-
errors: validation.errors
|
|
21705
|
+
errors: JSON.stringify(validation.errors, null, 2)
|
|
21574
21706
|
})
|
|
21575
21707
|
);
|
|
21576
21708
|
return ExitCode.Error;
|
|
@@ -21591,7 +21723,7 @@ function plural(count, word) {
|
|
|
21591
21723
|
}
|
|
21592
21724
|
|
|
21593
21725
|
// cli/commands/scan-compare.ts
|
|
21594
|
-
import {
|
|
21726
|
+
import { access, readFile as readFile5 } from "fs/promises";
|
|
21595
21727
|
import { Command as Command32, Option as Option30 } from "clipanion";
|
|
21596
21728
|
var ScanCompareCommand = class extends SmCommand {
|
|
21597
21729
|
static paths = [["scan", "compare-with"]];
|
|
@@ -21644,7 +21776,7 @@ var ScanCompareCommand = class extends SmCommand {
|
|
|
21644
21776
|
const roots = this.roots.length > 0 ? this.roots : ["."];
|
|
21645
21777
|
let prior;
|
|
21646
21778
|
try {
|
|
21647
|
-
prior = loadAndValidateDump(this.dump);
|
|
21779
|
+
prior = await loadAndValidateDump(this.dump);
|
|
21648
21780
|
} catch (err) {
|
|
21649
21781
|
const message = formatErrorMessage(err);
|
|
21650
21782
|
this.printer.info(tx(SCAN_TEXTS.compareErrorPrefix, { message }));
|
|
@@ -21702,13 +21834,15 @@ var ScanCompareCommand = class extends SmCommand {
|
|
|
21702
21834
|
return exitCode2;
|
|
21703
21835
|
}
|
|
21704
21836
|
};
|
|
21705
|
-
function loadAndValidateDump(path) {
|
|
21706
|
-
|
|
21837
|
+
async function loadAndValidateDump(path) {
|
|
21838
|
+
try {
|
|
21839
|
+
await access(path);
|
|
21840
|
+
} catch {
|
|
21707
21841
|
throw new Error(tx(SCAN_TEXTS.compareDumpNotFound, { path }));
|
|
21708
21842
|
}
|
|
21709
21843
|
let raw;
|
|
21710
21844
|
try {
|
|
21711
|
-
raw =
|
|
21845
|
+
raw = await readFile5(path, "utf8");
|
|
21712
21846
|
} catch (err) {
|
|
21713
21847
|
const message = formatErrorMessage(err);
|
|
21714
21848
|
throw new Error(tx(SCAN_TEXTS.compareDumpReadFailed, { path, message }), { cause: err });
|
|
@@ -21833,9 +21967,21 @@ function renderDeltaIssues(issues) {
|
|
|
21833
21967
|
|
|
21834
21968
|
// cli/commands/serve.ts
|
|
21835
21969
|
import { spawn as spawn2 } from "child_process";
|
|
21836
|
-
import { existsSync as
|
|
21970
|
+
import { existsSync as existsSync28 } from "fs";
|
|
21837
21971
|
import { Command as Command33, Option as Option31 } from "clipanion";
|
|
21838
21972
|
|
|
21973
|
+
// kernel/util/dev-mode.ts
|
|
21974
|
+
import { sep as sep6 } from "path";
|
|
21975
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
21976
|
+
var SELF_PATH = fileURLToPath5(import.meta.url);
|
|
21977
|
+
var IS_DEV_BUILD = isDevBuildFromPath(SELF_PATH, sep6);
|
|
21978
|
+
function isDevBuildFromPath(filePath, separator = sep6) {
|
|
21979
|
+
return !filePath.includes(`${separator}node_modules${separator}`);
|
|
21980
|
+
}
|
|
21981
|
+
function isDevBuild() {
|
|
21982
|
+
return IS_DEV_BUILD;
|
|
21983
|
+
}
|
|
21984
|
+
|
|
21839
21985
|
// cli/util/browser-launch.ts
|
|
21840
21986
|
function validateBrowserUrl(url) {
|
|
21841
21987
|
if (typeof url !== "string" || url.length === 0) return false;
|
|
@@ -22459,7 +22605,7 @@ function registerFavoritesRoutes(app, deps) {
|
|
|
22459
22605
|
);
|
|
22460
22606
|
if (!result || !result.found) {
|
|
22461
22607
|
throw new HTTPException4(404, {
|
|
22462
|
-
message: tx(SERVER_TEXTS.nodeNotFound, { path: nodePath })
|
|
22608
|
+
message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
|
|
22463
22609
|
});
|
|
22464
22610
|
}
|
|
22465
22611
|
return c.body(null, 204);
|
|
@@ -22546,17 +22692,21 @@ function contentTypeFor(format) {
|
|
|
22546
22692
|
}
|
|
22547
22693
|
|
|
22548
22694
|
// server/health.ts
|
|
22549
|
-
import { existsSync as
|
|
22695
|
+
import { existsSync as existsSync23 } from "fs";
|
|
22550
22696
|
var FALLBACK_SCHEMA_VERSION = "1";
|
|
22551
22697
|
function buildHealth(deps) {
|
|
22698
|
+
const dev = isDevBuild();
|
|
22552
22699
|
return {
|
|
22553
22700
|
ok: true,
|
|
22554
22701
|
schemaVersion: FALLBACK_SCHEMA_VERSION,
|
|
22555
22702
|
specVersion: deps.specVersion,
|
|
22556
22703
|
implVersion: VERSION,
|
|
22557
|
-
db:
|
|
22704
|
+
db: existsSync23(deps.dbPath) ? "present" : "missing",
|
|
22558
22705
|
cwd: deps.cwd,
|
|
22559
|
-
dbPath: deps.dbPath
|
|
22706
|
+
dbPath: deps.dbPath,
|
|
22707
|
+
// Only emit when truthy so a published install keeps the wire
|
|
22708
|
+
// shape lean and consumers branch on presence alone.
|
|
22709
|
+
...dev ? { dev: true } : {}
|
|
22560
22710
|
};
|
|
22561
22711
|
}
|
|
22562
22712
|
var cachedSpecVersion = null;
|
|
@@ -22586,47 +22736,60 @@ function registerHealthRoute(app, deps) {
|
|
|
22586
22736
|
});
|
|
22587
22737
|
}
|
|
22588
22738
|
|
|
22589
|
-
// server/
|
|
22739
|
+
// server/limits.ts
|
|
22590
22740
|
var DEFAULT_LIMIT = 100;
|
|
22591
22741
|
var MAX_LIMIT = 1e3;
|
|
22742
|
+
var BFF_MAX_BULK_CONTRIBUTIONS = 200;
|
|
22743
|
+
|
|
22744
|
+
// server/routes/issues.ts
|
|
22592
22745
|
function registerIssuesRoute(app, deps) {
|
|
22593
22746
|
app.get("/api/issues", async (c) => {
|
|
22594
|
-
const
|
|
22595
|
-
const analyzerFilter = parseCsv(c.req.query("analyzerId"));
|
|
22596
|
-
const nodePath = c.req.query("node") ?? null;
|
|
22597
|
-
const { offset, limit } = parsePagination(c.req.query(), {
|
|
22598
|
-
limit: DEFAULT_LIMIT,
|
|
22599
|
-
max: MAX_LIMIT
|
|
22600
|
-
});
|
|
22747
|
+
const inputs = parseIssuesQuery(c.req.query());
|
|
22601
22748
|
const result = await tryWithSqlite(
|
|
22602
22749
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
22603
|
-
(adapter) => adapter.issues.list(
|
|
22604
|
-
severities: severityFilter,
|
|
22605
|
-
analyzerIds: analyzerFilter,
|
|
22606
|
-
nodePath,
|
|
22607
|
-
offset,
|
|
22608
|
-
limit
|
|
22609
|
-
})
|
|
22750
|
+
(adapter) => adapter.issues.list(inputs.filter)
|
|
22610
22751
|
);
|
|
22611
|
-
const items = result?.items ?? [];
|
|
22612
|
-
const total = result?.total ?? 0;
|
|
22613
22752
|
return c.json(
|
|
22614
22753
|
buildListEnvelope({
|
|
22615
22754
|
kind: "issues",
|
|
22616
|
-
items,
|
|
22617
|
-
filters:
|
|
22618
|
-
|
|
22619
|
-
|
|
22620
|
-
node: nodePath
|
|
22621
|
-
},
|
|
22622
|
-
total,
|
|
22623
|
-
page: { offset, limit },
|
|
22755
|
+
items: result?.items ?? [],
|
|
22756
|
+
filters: inputs.echo,
|
|
22757
|
+
total: result?.total ?? 0,
|
|
22758
|
+
page: { offset: inputs.filter.offset, limit: inputs.filter.limit },
|
|
22624
22759
|
kindRegistry: deps.kindRegistry,
|
|
22625
22760
|
contributionsRegistry: deps.contributionsRegistry
|
|
22626
22761
|
})
|
|
22627
22762
|
);
|
|
22628
22763
|
});
|
|
22629
22764
|
}
|
|
22765
|
+
function parseIssuesQuery(query) {
|
|
22766
|
+
const severityFilter = parseCsv(query["severity"]);
|
|
22767
|
+
const analyzerFilter = parseCsv(query["analyzerId"]);
|
|
22768
|
+
const nodePath = query["node"] ?? null;
|
|
22769
|
+
const nodesRaw = parseCsv(query["nodes"]);
|
|
22770
|
+
const nodesFilter = nodesRaw.length > 0 ? nodesRaw : null;
|
|
22771
|
+
const { offset, limit } = parsePagination(query, {
|
|
22772
|
+
limit: DEFAULT_LIMIT,
|
|
22773
|
+
max: MAX_LIMIT
|
|
22774
|
+
});
|
|
22775
|
+
const filter = {
|
|
22776
|
+
severities: severityFilter,
|
|
22777
|
+
analyzerIds: analyzerFilter,
|
|
22778
|
+
nodePath,
|
|
22779
|
+
offset,
|
|
22780
|
+
limit
|
|
22781
|
+
};
|
|
22782
|
+
if (nodesFilter) filter.nodePaths = nodesFilter;
|
|
22783
|
+
return {
|
|
22784
|
+
filter,
|
|
22785
|
+
echo: {
|
|
22786
|
+
severity: severityFilter.length > 0 ? severityFilter : null,
|
|
22787
|
+
analyzerId: analyzerFilter.length > 0 ? analyzerFilter : null,
|
|
22788
|
+
node: nodePath,
|
|
22789
|
+
nodes: nodesFilter
|
|
22790
|
+
}
|
|
22791
|
+
};
|
|
22792
|
+
}
|
|
22630
22793
|
|
|
22631
22794
|
// server/routes/links.ts
|
|
22632
22795
|
function registerLinksRoute(app, deps) {
|
|
@@ -22666,22 +22829,30 @@ function registerLinksRoute(app, deps) {
|
|
|
22666
22829
|
import { HTTPException as HTTPException6 } from "hono/http-exception";
|
|
22667
22830
|
|
|
22668
22831
|
// server/node-body.ts
|
|
22669
|
-
import {
|
|
22670
|
-
import {
|
|
22832
|
+
import { constants as fsConstants2 } from "fs";
|
|
22833
|
+
import { open } from "fs/promises";
|
|
22834
|
+
import { isAbsolute as isAbsolute10, resolve as resolvePath2, relative as relativePath, sep as sep7 } from "path";
|
|
22671
22835
|
async function readNodeBody(cwd, relPath) {
|
|
22672
22836
|
if (isAbsolute10(relPath)) return null;
|
|
22673
22837
|
const absRoot = resolvePath2(cwd);
|
|
22674
22838
|
const absFile = resolvePath2(absRoot, relPath);
|
|
22675
22839
|
const rel = relativePath(absRoot, absFile);
|
|
22676
|
-
if (rel.startsWith("..") || rel.startsWith(
|
|
22840
|
+
if (rel.startsWith("..") || rel.startsWith(sep7) || rel.length === 0) {
|
|
22677
22841
|
return null;
|
|
22678
22842
|
}
|
|
22679
22843
|
let raw;
|
|
22844
|
+
let handle = null;
|
|
22680
22845
|
try {
|
|
22681
|
-
|
|
22846
|
+
handle = await open(absFile, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
|
|
22847
|
+
raw = await handle.readFile("utf-8");
|
|
22682
22848
|
} catch (err) {
|
|
22683
22849
|
if (isExpectedFsError(err)) return null;
|
|
22684
22850
|
throw err;
|
|
22851
|
+
} finally {
|
|
22852
|
+
if (handle !== null) {
|
|
22853
|
+
await handle.close().catch(() => {
|
|
22854
|
+
});
|
|
22855
|
+
}
|
|
22685
22856
|
}
|
|
22686
22857
|
return stripFrontmatter(raw);
|
|
22687
22858
|
}
|
|
@@ -22691,7 +22862,16 @@ function stripFrontmatter(raw) {
|
|
|
22691
22862
|
if (!match) return raw;
|
|
22692
22863
|
return raw.slice(match[0].length);
|
|
22693
22864
|
}
|
|
22694
|
-
var EXPECTED_FS_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
22865
|
+
var EXPECTED_FS_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
22866
|
+
"ENOENT",
|
|
22867
|
+
"EACCES",
|
|
22868
|
+
"EISDIR",
|
|
22869
|
+
"ENOTDIR",
|
|
22870
|
+
// `O_NOFOLLOW` opens fail with `ELOOP` on Linux / macOS when the
|
|
22871
|
+
// leaf is a symlink. Treat the same as "body unavailable" so the
|
|
22872
|
+
// BFF returns null, not a 500.
|
|
22873
|
+
"ELOOP"
|
|
22874
|
+
]);
|
|
22695
22875
|
function isExpectedFsError(err) {
|
|
22696
22876
|
if (err === null || typeof err !== "object") return false;
|
|
22697
22877
|
const code = err.code;
|
|
@@ -22749,9 +22929,6 @@ function splitCsv(raw) {
|
|
|
22749
22929
|
}
|
|
22750
22930
|
|
|
22751
22931
|
// server/routes/nodes.ts
|
|
22752
|
-
var DEFAULT_LIMIT2 = 100;
|
|
22753
|
-
var MAX_LIMIT2 = 1e3;
|
|
22754
|
-
var BFF_MAX_BULK_CONTRIBUTIONS = 200;
|
|
22755
22932
|
function registerNodesRoutes(app, deps) {
|
|
22756
22933
|
app.get("/api/nodes/:pathB64", async (c) => {
|
|
22757
22934
|
const pathB64 = c.req.param("pathB64");
|
|
@@ -22813,8 +22990,8 @@ function registerNodesRoutes(app, deps) {
|
|
|
22813
22990
|
const params = new URL(c.req.url).searchParams;
|
|
22814
22991
|
const { query, filters } = urlParamsToExportQuery(params);
|
|
22815
22992
|
const { offset, limit } = parsePagination(c.req.query(), {
|
|
22816
|
-
limit:
|
|
22817
|
-
max:
|
|
22993
|
+
limit: DEFAULT_LIMIT,
|
|
22994
|
+
max: MAX_LIMIT
|
|
22818
22995
|
});
|
|
22819
22996
|
const opened = await tryWithSqlite(
|
|
22820
22997
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
@@ -23100,7 +23277,7 @@ function listItems(deps, resolveEnabled) {
|
|
|
23100
23277
|
];
|
|
23101
23278
|
}
|
|
23102
23279
|
function buildBuiltInItems(resolveEnabled) {
|
|
23103
|
-
return builtInBundles.map((bundle) => {
|
|
23280
|
+
return sortBundlesForPresentation(builtInBundles).map((bundle) => {
|
|
23104
23281
|
const bundleEnabled = resolveEnabled(bundle.id);
|
|
23105
23282
|
const bundleLocked = isPluginLocked(bundle.id);
|
|
23106
23283
|
const extensions = bundle.extensions.map((ext) => {
|
|
@@ -23389,15 +23566,15 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
|
|
|
23389
23566
|
import { HTTPException as HTTPException10 } from "hono/http-exception";
|
|
23390
23567
|
|
|
23391
23568
|
// server/util/skillmapignore-io.ts
|
|
23392
|
-
import { existsSync as
|
|
23569
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
|
|
23393
23570
|
import { resolve as resolve35 } from "path";
|
|
23394
23571
|
var IGNORE_FILENAME2 = ".skillmapignore";
|
|
23395
23572
|
function readPatterns(cwd) {
|
|
23396
23573
|
const path = resolve35(cwd, IGNORE_FILENAME2);
|
|
23397
|
-
if (!
|
|
23574
|
+
if (!existsSync24(path)) return [];
|
|
23398
23575
|
let raw;
|
|
23399
23576
|
try {
|
|
23400
|
-
raw =
|
|
23577
|
+
raw = readFileSync17(path, "utf8");
|
|
23401
23578
|
} catch {
|
|
23402
23579
|
return [];
|
|
23403
23580
|
}
|
|
@@ -23405,13 +23582,13 @@ function readPatterns(cwd) {
|
|
|
23405
23582
|
}
|
|
23406
23583
|
function writePatterns(cwd, nextPatterns) {
|
|
23407
23584
|
const path = resolve35(cwd, IGNORE_FILENAME2);
|
|
23408
|
-
const prior =
|
|
23585
|
+
const prior = existsSync24(path) ? safeRead(path) : "";
|
|
23409
23586
|
const content = buildContent(prior, nextPatterns);
|
|
23410
|
-
|
|
23587
|
+
writeFileSync2(path, content, "utf8");
|
|
23411
23588
|
}
|
|
23412
23589
|
function safeRead(path) {
|
|
23413
23590
|
try {
|
|
23414
|
-
return
|
|
23591
|
+
return readFileSync17(path, "utf8");
|
|
23415
23592
|
} catch {
|
|
23416
23593
|
return "";
|
|
23417
23594
|
}
|
|
@@ -23566,7 +23743,7 @@ var parsePatchBody3 = makeBodyValidator(PATCH_BODY_SCHEMA2, {
|
|
|
23566
23743
|
});
|
|
23567
23744
|
|
|
23568
23745
|
// server/routes/project-preferences.ts
|
|
23569
|
-
import { statSync as
|
|
23746
|
+
import { statSync as statSync9 } from "fs";
|
|
23570
23747
|
import { HTTPException as HTTPException11 } from "hono/http-exception";
|
|
23571
23748
|
function registerProjectPreferencesRoute(app, deps) {
|
|
23572
23749
|
app.get("/api/project-preferences", (c) => {
|
|
@@ -23715,7 +23892,7 @@ function formatPathDetail(path, cwd) {
|
|
|
23715
23892
|
function isExistingDirectory(entry, cwd) {
|
|
23716
23893
|
const abs = resolveScanPath(entry, cwd);
|
|
23717
23894
|
try {
|
|
23718
|
-
return
|
|
23895
|
+
return statSync9(abs).isDirectory();
|
|
23719
23896
|
} catch {
|
|
23720
23897
|
return false;
|
|
23721
23898
|
}
|
|
@@ -23755,7 +23932,7 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
|
|
|
23755
23932
|
});
|
|
23756
23933
|
|
|
23757
23934
|
// server/routes/active-provider.ts
|
|
23758
|
-
import { existsSync as
|
|
23935
|
+
import { existsSync as existsSync25 } from "fs";
|
|
23759
23936
|
import { HTTPException as HTTPException12 } from "hono/http-exception";
|
|
23760
23937
|
function registerActiveProviderRoute(app, deps) {
|
|
23761
23938
|
app.get("/api/active-provider", (c) => {
|
|
@@ -23788,7 +23965,7 @@ function applyLensSwitch(deps, newValue) {
|
|
|
23788
23965
|
});
|
|
23789
23966
|
}
|
|
23790
23967
|
const dbPath = resolveDbPath({ db: void 0, cwd });
|
|
23791
|
-
if (!
|
|
23968
|
+
if (!existsSync25(dbPath)) return { dropped: null };
|
|
23792
23969
|
const dropResult = dropScanZone(dbPath);
|
|
23793
23970
|
return {
|
|
23794
23971
|
dropped: {
|
|
@@ -24180,7 +24357,7 @@ function registerSidecarRoutes(app, deps) {
|
|
|
24180
24357
|
assertContained(deps.runtimeContext.cwd, node.path);
|
|
24181
24358
|
absPath = resolve36(deps.runtimeContext.cwd, node.path);
|
|
24182
24359
|
} catch (err) {
|
|
24183
|
-
throw new HTTPException14(
|
|
24360
|
+
throw new HTTPException14(400, { message: formatErrorMessage(err) });
|
|
24184
24361
|
}
|
|
24185
24362
|
const result = invokeBump2(node, absPath, body);
|
|
24186
24363
|
if (result.report.ok === false && result.report.reason === "fresh") {
|
|
@@ -24255,12 +24432,12 @@ async function loadNode(deps, nodePath) {
|
|
|
24255
24432
|
return node;
|
|
24256
24433
|
}
|
|
24257
24434
|
function invokeBump2(node, absPath, body) {
|
|
24258
|
-
if (!
|
|
24435
|
+
if (!nodeBumpAction.invoke) {
|
|
24259
24436
|
throw new HTTPException14(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
|
|
24260
24437
|
}
|
|
24261
24438
|
const input = {};
|
|
24262
24439
|
if (body.force === true) input.force = true;
|
|
24263
|
-
return
|
|
24440
|
+
return nodeBumpAction.invoke(input, {
|
|
24264
24441
|
node,
|
|
24265
24442
|
nodeAbsolutePath: absPath,
|
|
24266
24443
|
invoker: "ui",
|
|
@@ -24302,8 +24479,8 @@ function registerUpdateStatusRoute(app, deps) {
|
|
|
24302
24479
|
}
|
|
24303
24480
|
|
|
24304
24481
|
// server/static.ts
|
|
24305
|
-
import { existsSync as
|
|
24306
|
-
import { readFile as
|
|
24482
|
+
import { existsSync as existsSync26 } from "fs";
|
|
24483
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
24307
24484
|
import { extname, join as join19 } from "path";
|
|
24308
24485
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
24309
24486
|
var INDEX_HTML = "index.html";
|
|
@@ -24357,7 +24534,7 @@ function createSpaFallback(opts) {
|
|
|
24357
24534
|
if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
|
|
24358
24535
|
if (opts.uiDist === null) return htmlResponse(c, placeholder);
|
|
24359
24536
|
const indexPath = join19(opts.uiDist, INDEX_HTML);
|
|
24360
|
-
if (!
|
|
24537
|
+
if (!existsSync26(indexPath)) return htmlResponse(c, placeholder);
|
|
24361
24538
|
return fileResponse(c, indexPath);
|
|
24362
24539
|
};
|
|
24363
24540
|
}
|
|
@@ -24399,7 +24576,7 @@ function htmlResponse(c, html) {
|
|
|
24399
24576
|
return c.body(html, 200, { "content-type": "text/html; charset=UTF-8" });
|
|
24400
24577
|
}
|
|
24401
24578
|
async function fileResponse(c, absPath) {
|
|
24402
|
-
const buf = await
|
|
24579
|
+
const buf = await readFile6(absPath);
|
|
24403
24580
|
return c.body(buf, 200, { "content-type": mimeFor(absPath) });
|
|
24404
24581
|
}
|
|
24405
24582
|
|
|
@@ -24929,9 +25106,9 @@ function validateNoUi(noUi, uiDist) {
|
|
|
24929
25106
|
}
|
|
24930
25107
|
|
|
24931
25108
|
// server/paths.ts
|
|
24932
|
-
import { existsSync as
|
|
25109
|
+
import { existsSync as existsSync27, statSync as statSync10 } from "fs";
|
|
24933
25110
|
import { dirname as dirname18, isAbsolute as isAbsolute11, join as join20, resolve as resolve37 } from "path";
|
|
24934
|
-
import { fileURLToPath as
|
|
25111
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
24935
25112
|
var DEFAULT_UI_REL = join20("ui", "dist", "ui", "browser");
|
|
24936
25113
|
var PACKAGE_UI_REL = "ui";
|
|
24937
25114
|
var INDEX_HTML2 = "index.html";
|
|
@@ -24944,10 +25121,10 @@ function resolveExplicitUiDist(ctx, raw) {
|
|
|
24944
25121
|
return isAbsolute11(raw) ? raw : resolve37(ctx.cwd, raw);
|
|
24945
25122
|
}
|
|
24946
25123
|
function isUiBundleDir(path) {
|
|
24947
|
-
if (!
|
|
25124
|
+
if (!existsSync27(path)) return false;
|
|
24948
25125
|
try {
|
|
24949
|
-
if (!
|
|
24950
|
-
return
|
|
25126
|
+
if (!statSync10(path).isDirectory()) return false;
|
|
25127
|
+
return existsSync27(join20(path, INDEX_HTML2));
|
|
24951
25128
|
} catch {
|
|
24952
25129
|
return false;
|
|
24953
25130
|
}
|
|
@@ -24955,7 +25132,7 @@ function isUiBundleDir(path) {
|
|
|
24955
25132
|
function resolvePackageBundledUi() {
|
|
24956
25133
|
let here;
|
|
24957
25134
|
try {
|
|
24958
|
-
here = dirname18(
|
|
25135
|
+
here = dirname18(fileURLToPath6(import.meta.url));
|
|
24959
25136
|
} catch {
|
|
24960
25137
|
return null;
|
|
24961
25138
|
}
|
|
@@ -25247,7 +25424,9 @@ var ESC2 = {
|
|
|
25247
25424
|
/** 256-color violet (xterm 141). */
|
|
25248
25425
|
violet: "\x1B[38;5;141m",
|
|
25249
25426
|
/** 256-color green (xterm 42). */
|
|
25250
|
-
green: "\x1B[38;5;42m"
|
|
25427
|
+
green: "\x1B[38;5;42m",
|
|
25428
|
+
/** 256-color yellow (xterm 214), matches `cli/util/ansi.ts:yellow`. */
|
|
25429
|
+
yellow: "\x1B[38;5;214m"
|
|
25251
25430
|
};
|
|
25252
25431
|
var LOGO_LINES = [
|
|
25253
25432
|
" ____ _ _ _ _ __ __ ",
|
|
@@ -25267,7 +25446,8 @@ function renderBanner(input) {
|
|
|
25267
25446
|
host: input.host,
|
|
25268
25447
|
port: input.port,
|
|
25269
25448
|
dbPath: input.dbPath,
|
|
25270
|
-
openBrowser: input.openBrowser
|
|
25449
|
+
openBrowser: input.openBrowser,
|
|
25450
|
+
dev: input.dev === true
|
|
25271
25451
|
});
|
|
25272
25452
|
}
|
|
25273
25453
|
return renderFiglet({
|
|
@@ -25277,7 +25457,8 @@ function renderBanner(input) {
|
|
|
25277
25457
|
pathDisplay: formatCwdPath(input.cwd),
|
|
25278
25458
|
browserLine,
|
|
25279
25459
|
colorEnabled: input.colorEnabled,
|
|
25280
|
-
referencePaths: input.referencePaths ?? []
|
|
25460
|
+
referencePaths: input.referencePaths ?? [],
|
|
25461
|
+
dev: input.dev === true
|
|
25281
25462
|
});
|
|
25282
25463
|
}
|
|
25283
25464
|
function resolveColorEnabled(opts) {
|
|
@@ -25292,8 +25473,9 @@ function renderFlat(input) {
|
|
|
25292
25473
|
const safeHost = sanitizeForTerminal(input.host);
|
|
25293
25474
|
const safeDb = sanitizeForTerminal(input.dbPath);
|
|
25294
25475
|
const url = `http://${safeHost}:${input.port}`;
|
|
25476
|
+
const devSuffix = input.dev ? " [dev]" : "";
|
|
25295
25477
|
const linesOut = [];
|
|
25296
|
-
linesOut.push(`sm serve: listening on ${url} (db=${safeDb})`);
|
|
25478
|
+
linesOut.push(`sm serve${devSuffix}: listening on ${url} (db=${safeDb})`);
|
|
25297
25479
|
if (input.openBrowser) {
|
|
25298
25480
|
linesOut.push(`sm serve: opening ${url}/ in your browser. Press Ctrl+C to stop.`);
|
|
25299
25481
|
} else {
|
|
@@ -25320,12 +25502,16 @@ function renderFiglet(input) {
|
|
|
25320
25502
|
greenUnderline,
|
|
25321
25503
|
greenUnderlineClose,
|
|
25322
25504
|
violetOpen,
|
|
25323
|
-
violetClose
|
|
25505
|
+
violetClose,
|
|
25506
|
+
yellowOpen,
|
|
25507
|
+
yellowClose
|
|
25324
25508
|
} = resolveAnsi(input.colorEnabled);
|
|
25325
25509
|
const logoLines = LOGO_LINES.map((line) => `${violetOpen}${line}${violetClose}`);
|
|
25326
25510
|
const versionText = `v${input.version}`;
|
|
25327
|
-
const
|
|
25328
|
-
const
|
|
25511
|
+
const devText = "[dev]";
|
|
25512
|
+
const versionWidth = input.dev ? devText.length : versionText.length;
|
|
25513
|
+
const versionPad = Math.max(0, LOGO_WIDTH - versionWidth);
|
|
25514
|
+
const versionLine = input.dev ? `${" ".repeat(versionPad)}${yellowOpen}${devText}${yellowClose}` : `${" ".repeat(versionPad)}${dimOpen}${versionText}${dimClose}`;
|
|
25329
25515
|
const lines = [];
|
|
25330
25516
|
lines.push(...logoLines);
|
|
25331
25517
|
lines.push("");
|
|
@@ -25357,7 +25543,9 @@ var EMPTY_ANSI = {
|
|
|
25357
25543
|
greenUnderline: "",
|
|
25358
25544
|
greenUnderlineClose: "",
|
|
25359
25545
|
violetOpen: "",
|
|
25360
|
-
violetClose: ""
|
|
25546
|
+
violetClose: "",
|
|
25547
|
+
yellowOpen: "",
|
|
25548
|
+
yellowClose: ""
|
|
25361
25549
|
};
|
|
25362
25550
|
var ENABLED_ANSI = {
|
|
25363
25551
|
dimOpen: ESC2.dim,
|
|
@@ -25365,7 +25553,9 @@ var ENABLED_ANSI = {
|
|
|
25365
25553
|
greenUnderline: `${ESC2.green}${ESC2.underline}`,
|
|
25366
25554
|
greenUnderlineClose: ESC2.reset,
|
|
25367
25555
|
violetOpen: ESC2.violet,
|
|
25368
|
-
violetClose: ESC2.reset
|
|
25556
|
+
violetClose: ESC2.reset,
|
|
25557
|
+
yellowOpen: ESC2.yellow,
|
|
25558
|
+
yellowClose: ESC2.reset
|
|
25369
25559
|
};
|
|
25370
25560
|
function resolveAnsi(colorEnabled) {
|
|
25371
25561
|
return colorEnabled ? ENABLED_ANSI : EMPTY_ANSI;
|
|
@@ -25478,7 +25668,7 @@ var ServeCommand = class extends SmCommand {
|
|
|
25478
25668
|
return ExitCode.Error;
|
|
25479
25669
|
}
|
|
25480
25670
|
const dbPath = resolveDbPath({ db: this.db, ...runtimeCtx });
|
|
25481
|
-
if (this.db !== void 0 && !
|
|
25671
|
+
if (this.db !== void 0 && !existsSync28(dbPath)) {
|
|
25482
25672
|
this.printer.info(
|
|
25483
25673
|
tx(SERVE_TEXTS.dbNotFound, {
|
|
25484
25674
|
glyph: errGlyph,
|
|
@@ -25586,7 +25776,8 @@ var ServeCommand = class extends SmCommand {
|
|
|
25586
25776
|
openBrowser: validation.options.open,
|
|
25587
25777
|
isTTY,
|
|
25588
25778
|
colorEnabled,
|
|
25589
|
-
referencePaths
|
|
25779
|
+
referencePaths,
|
|
25780
|
+
dev: isDevBuild()
|
|
25590
25781
|
})
|
|
25591
25782
|
);
|
|
25592
25783
|
if (validation.options.open) {
|
|
@@ -26028,7 +26219,7 @@ function rankConfidenceForGrouping(c) {
|
|
|
26028
26219
|
}
|
|
26029
26220
|
|
|
26030
26221
|
// cli/commands/sidecar.ts
|
|
26031
|
-
import {
|
|
26222
|
+
import { unlink as unlink3 } from "fs/promises";
|
|
26032
26223
|
import { resolve as resolve38 } from "path";
|
|
26033
26224
|
import { Command as Command35, Option as Option33 } from "clipanion";
|
|
26034
26225
|
|
|
@@ -26320,7 +26511,7 @@ var SidecarPruneCommand = class extends SmCommand {
|
|
|
26320
26511
|
continue;
|
|
26321
26512
|
}
|
|
26322
26513
|
try {
|
|
26323
|
-
|
|
26514
|
+
await unlink3(orphan.sidecarPath);
|
|
26324
26515
|
items.push({
|
|
26325
26516
|
sidecarPath: orphan.sidecarPath,
|
|
26326
26517
|
expectedMd: orphan.expectedMdPath,
|
|
@@ -26477,7 +26668,8 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
26477
26668
|
return ExitCode.Error;
|
|
26478
26669
|
}
|
|
26479
26670
|
const sidecarAbsPath = sidecarPathFor(absPath);
|
|
26480
|
-
|
|
26671
|
+
const sidecarExists = await pathExists(sidecarAbsPath);
|
|
26672
|
+
if (sidecarExists && this.force !== true) {
|
|
26481
26673
|
this.printer.error(
|
|
26482
26674
|
tx(SIDECAR_TEXTS.annotateExists, {
|
|
26483
26675
|
glyph: errGlyph,
|
|
@@ -26487,9 +26679,9 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
26487
26679
|
);
|
|
26488
26680
|
return ExitCode.Error;
|
|
26489
26681
|
}
|
|
26490
|
-
if (
|
|
26682
|
+
if (sidecarExists && this.force === true) {
|
|
26491
26683
|
try {
|
|
26492
|
-
|
|
26684
|
+
await unlink3(sidecarAbsPath);
|
|
26493
26685
|
} catch (err) {
|
|
26494
26686
|
this.printer.error(
|
|
26495
26687
|
tx(SIDECAR_TEXTS.annotateFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
|
|
@@ -26717,9 +26909,9 @@ var STUB_COMMANDS = [
|
|
|
26717
26909
|
];
|
|
26718
26910
|
|
|
26719
26911
|
// cli/commands/tutorial.ts
|
|
26720
|
-
import { cpSync as cpSync2, existsSync as
|
|
26912
|
+
import { cpSync as cpSync2, existsSync as existsSync29, mkdirSync as mkdirSync6, rmSync as rmSync2, statSync as statSync11 } from "fs";
|
|
26721
26913
|
import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
|
|
26722
|
-
import { fileURLToPath as
|
|
26914
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
26723
26915
|
import { Command as Command37, Option as Option35 } from "clipanion";
|
|
26724
26916
|
|
|
26725
26917
|
// cli/i18n/tutorial.texts.ts
|
|
@@ -26816,7 +27008,7 @@ var TutorialCommand = class extends SmCommand {
|
|
|
26816
27008
|
const spec = VARIANT_SPECS[variant];
|
|
26817
27009
|
const targetDir = join21(ctx.cwd, ".claude", "skills", spec.slug);
|
|
26818
27010
|
const targetDisplay = `.claude/skills/${spec.slug}/`;
|
|
26819
|
-
if (
|
|
27011
|
+
if (existsSync29(targetDir) && !this.force) {
|
|
26820
27012
|
this.printer.error(
|
|
26821
27013
|
tx(TUTORIAL_TEXTS.alreadyExists, {
|
|
26822
27014
|
glyph: errGlyph,
|
|
@@ -26842,7 +27034,7 @@ var TutorialCommand = class extends SmCommand {
|
|
|
26842
27034
|
}
|
|
26843
27035
|
try {
|
|
26844
27036
|
rmSync2(targetDir, { recursive: true, force: true });
|
|
26845
|
-
|
|
27037
|
+
mkdirSync6(dirname19(targetDir), { recursive: true });
|
|
26846
27038
|
cpSync2(sourceDir, targetDir, { recursive: true });
|
|
26847
27039
|
} catch (err) {
|
|
26848
27040
|
this.printer.error(
|
|
@@ -26889,7 +27081,7 @@ function resolveSkillSourceDir(variant) {
|
|
|
26889
27081
|
const cached = cachedSourceDirs.get(variant);
|
|
26890
27082
|
if (cached !== void 0) return cached;
|
|
26891
27083
|
const spec = VARIANT_SPECS[variant];
|
|
26892
|
-
const here = dirname19(
|
|
27084
|
+
const here = dirname19(fileURLToPath7(import.meta.url));
|
|
26893
27085
|
const candidates = [
|
|
26894
27086
|
// dev: src/cli/commands/ → repo-root .claude/skills/<slug>/
|
|
26895
27087
|
resolve39(here, "../../..", spec.sourceDir),
|
|
@@ -26899,7 +27091,7 @@ function resolveSkillSourceDir(variant) {
|
|
|
26899
27091
|
resolve39(here, "../cli/tutorial", spec.slug)
|
|
26900
27092
|
];
|
|
26901
27093
|
for (const candidate of candidates) {
|
|
26902
|
-
if (
|
|
27094
|
+
if (existsSync29(candidate) && statSync11(candidate).isDirectory()) {
|
|
26903
27095
|
cachedSourceDirs.set(variant, candidate);
|
|
26904
27096
|
return candidate;
|
|
26905
27097
|
}
|
|
@@ -26937,6 +27129,7 @@ var VersionCommand = class extends SmCommand {
|
|
|
26937
27129
|
const kernelVersion = VERSION;
|
|
26938
27130
|
const specVersion = await resolveSpecVersion3();
|
|
26939
27131
|
const dbSchema = await resolveDbSchemaVersion();
|
|
27132
|
+
const dev = isDevBuild();
|
|
26940
27133
|
if (this.json) {
|
|
26941
27134
|
const payload = {
|
|
26942
27135
|
sm: VERSION,
|
|
@@ -26944,17 +27137,19 @@ var VersionCommand = class extends SmCommand {
|
|
|
26944
27137
|
spec: specVersion,
|
|
26945
27138
|
dbSchema
|
|
26946
27139
|
};
|
|
27140
|
+
if (dev) payload["dev"] = true;
|
|
26947
27141
|
this.printer.data(JSON.stringify(payload) + "\n");
|
|
26948
27142
|
return ExitCode.Ok;
|
|
26949
27143
|
}
|
|
27144
|
+
const ansi = this.ansiFor("stdout");
|
|
27145
|
+
const smValue = dev ? `${VERSION} ${ansi.yellow("[dev]")}` : VERSION;
|
|
26950
27146
|
const lines = [
|
|
26951
|
-
["sm",
|
|
27147
|
+
["sm", smValue],
|
|
26952
27148
|
["kernel", kernelVersion],
|
|
26953
27149
|
["spec", specVersion],
|
|
26954
27150
|
["runtime", runtime],
|
|
26955
27151
|
["db-schema", dbSchema]
|
|
26956
27152
|
];
|
|
26957
|
-
const ansi = this.ansiFor("stdout");
|
|
26958
27153
|
const pad = Math.max(...lines.map(([k]) => k.length));
|
|
26959
27154
|
for (const [k, v] of lines) {
|
|
26960
27155
|
this.printer.data(
|
|
@@ -27076,7 +27271,7 @@ await lifecycleDispatcher.dispatch(
|
|
|
27076
27271
|
process.exit(exitCode);
|
|
27077
27272
|
function resolveBareDefault() {
|
|
27078
27273
|
const ctx = defaultRuntimeContext();
|
|
27079
|
-
if (
|
|
27274
|
+
if (existsSync30(defaultProjectDbPath(ctx))) {
|
|
27080
27275
|
return ["serve"];
|
|
27081
27276
|
}
|
|
27082
27277
|
const stderr = process.stderr;
|