@skill-map/cli 0.52.0 → 0.53.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 +239 -1659
- package/dist/cli/tutorial/sm-tutorial/references/_core.md +332 -0
- package/dist/cli/tutorial/sm-tutorial/references/_manifest.yml +175 -0
- package/dist/cli/tutorial/sm-tutorial/references/fixtures.md +251 -0
- package/dist/cli/tutorial/{sm-master/references/tour-authoring.md → sm-tutorial/references/part-authoring.md} +14 -15
- package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +267 -0
- package/dist/cli/tutorial/sm-tutorial/references/part-connect-harness.md +180 -0
- package/dist/cli/tutorial/sm-tutorial/references/part-fundamentals.md +424 -0
- package/dist/cli/tutorial/sm-tutorial/references/part-live-site.md +156 -0
- package/dist/cli/tutorial/sm-tutorial/references/part-maintain.md +286 -0
- package/dist/cli/tutorial/sm-tutorial/references/part-mcp.md +78 -0
- package/dist/cli/tutorial/{sm-master/references/tour-plugins.md → sm-tutorial/references/part-plugins.md} +11 -11
- package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +186 -0
- package/dist/cli/tutorial/{sm-master/references/tour-settings.md → sm-tutorial/references/part-settings.md} +22 -24
- package/dist/cli.js +1213 -550
- package/dist/index.d.ts +1 -1
- package/dist/index.js +334 -207
- package/dist/kernel/index.d.ts +320 -15
- package/dist/kernel/index.js +334 -207
- package/dist/migrations/001_initial.sql +36 -0
- package/dist/ui/chunk-EQ72PEHT.js +1 -0
- package/dist/ui/chunk-GBKHMJ4B.js +1110 -0
- package/dist/ui/chunk-GEI6INVH.js +515 -0
- package/dist/ui/chunk-JXRIGHET.js +552 -0
- package/dist/ui/{chunk-WQMZOINB.js → chunk-K2MAVAHG.js} +1 -1
- package/dist/ui/{chunk-BV323KTK.js → chunk-KHARMPTZ.js} +1 -1
- package/dist/ui/chunk-L4NIF75A.js +2 -0
- package/dist/ui/chunk-LCOYSPKE.js +1 -0
- package/dist/ui/chunk-OFDQMBSJ.js +1 -0
- package/dist/ui/chunk-P2DAPRK7.js +2 -0
- package/dist/ui/chunk-Q2A6FWC7.js +4 -0
- package/dist/ui/{chunk-ZNDMBION.js → chunk-TXTY24G4.js} +28 -30
- package/dist/ui/chunk-UBQUCSQ4.js +1 -0
- package/dist/ui/chunk-WFLPMCK4.js +392 -0
- package/dist/ui/chunk-YQFIXHKM.js +123 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/{main-2DWVSRRX.js → main-OYITFJ7B.js} +3 -3
- package/dist/ui/{styles-QBTVKEVX.css → styles-Q4NCOJQY.css} +1 -1
- package/migrations/001_initial.sql +36 -0
- package/package.json +10 -8
- package/dist/cli/tutorial/sm-master/SKILL.md +0 -688
- package/dist/cli/tutorial/sm-master/references/fixture-templates.md +0 -212
- package/dist/ui/chunk-5MCXQKRN.js +0 -1066
- package/dist/ui/chunk-6B5EAHIM.js +0 -1110
- package/dist/ui/chunk-AEA5GIA7.js +0 -1
- package/dist/ui/chunk-AQN27TN2.js +0 -123
- package/dist/ui/chunk-CAJ7ZI44.js +0 -1
- package/dist/ui/chunk-E2XO4JVD.js +0 -1
- package/dist/ui/chunk-VJ57LHDR.js +0 -4
- package/dist/ui/chunk-WMGW2UAL.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/entry.ts
|
|
2
2
|
|
|
3
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="82aaa5c6-6dae-53a6-8d4a-1a673357051c")}catch(e){}}();
|
|
4
4
|
import { existsSync as existsSync33 } from "fs";
|
|
5
5
|
import { Builtins, Cli as Cli2 } from "clipanion";
|
|
6
6
|
|
|
@@ -246,7 +246,7 @@ function bucketByKind(kind, instance, bag) {
|
|
|
246
246
|
// package.json
|
|
247
247
|
var package_default = {
|
|
248
248
|
name: "@skill-map/cli",
|
|
249
|
-
version: "0.
|
|
249
|
+
version: "0.53.0",
|
|
250
250
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
251
251
|
license: "MIT",
|
|
252
252
|
type: "module",
|
|
@@ -304,15 +304,16 @@ var package_default = {
|
|
|
304
304
|
prebuild: "pnpm build-built-ins",
|
|
305
305
|
validate: "pnpm validate:compile && pnpm validate:test",
|
|
306
306
|
"validate:compile": "pnpm typecheck && pnpm lint && pnpm build && pnpm built-ins:check && pnpm view-catalog:check",
|
|
307
|
-
"validate:test": "pnpm test:ci",
|
|
307
|
+
"validate:test": "pnpm test:ci && pnpm conformance",
|
|
308
|
+
conformance: "SKILL_MAP_TELEMETRY=0 SM_NO_UPDATE_CHECK=1 node --import tsx cli/entry.ts conformance run",
|
|
308
309
|
pretest: "tsup",
|
|
309
310
|
"pretest:coverage": "tsup",
|
|
310
311
|
"pretest:coverage:html": "tsup",
|
|
311
|
-
test: "tsc --noEmit && SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
312
|
-
"test:ci": "FORCE_COLOR=1 SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
313
|
-
"test:spec": "SKILL_MAP_TELEMETRY=0 node --import tsx --test --test-reporter=spec '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
314
|
-
"test:coverage": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SKILL_MAP_SKIP_BENCHMARK=1 node --experimental-default-config-file --import tsx --test --experimental-test-coverage '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
315
|
-
"test:coverage:html": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SKILL_MAP_SKIP_BENCHMARK=1 c8 node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
312
|
+
test: "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SM_NO_UPDATE_CHECK=1 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
313
|
+
"test:ci": "FORCE_COLOR=1 SKILL_MAP_TELEMETRY=0 SM_NO_UPDATE_CHECK=1 node --import tsx --test --test-reporter=./scripts/test-reporter.js --test-reporter-destination=stdout '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
314
|
+
"test:spec": "SKILL_MAP_TELEMETRY=0 SM_NO_UPDATE_CHECK=1 node --import tsx --test --test-reporter=spec '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
315
|
+
"test:coverage": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SM_NO_UPDATE_CHECK=1 SKILL_MAP_SKIP_BENCHMARK=1 node --experimental-default-config-file --import tsx --test --experimental-test-coverage '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
316
|
+
"test:coverage:html": "tsc --noEmit && SKILL_MAP_TELEMETRY=0 SM_NO_UPDATE_CHECK=1 SKILL_MAP_SKIP_BENCHMARK=1 c8 node --import tsx --test '__tests__/**/*.spec.ts' 'kernel/**/__tests__/**/*.spec.ts' 'cli/**/__tests__/**/*.spec.ts' 'server/**/__tests__/**/*.spec.ts' 'plugins/**/__tests__/**/*.spec.ts' 'core/**/__tests__/**/*.spec.ts' 'conformance/**/__tests__/**/*.spec.ts'",
|
|
316
317
|
clean: "rm -rf dist coverage"
|
|
317
318
|
},
|
|
318
319
|
dependencies: {
|
|
@@ -344,6 +345,7 @@ var package_default = {
|
|
|
344
345
|
c8: "11.0.0",
|
|
345
346
|
eslint: "10.2.1",
|
|
346
347
|
"eslint-plugin-import-x": "4.16.2",
|
|
348
|
+
"json-schema-to-typescript": "15.0.4",
|
|
347
349
|
tsup: "8.5.1",
|
|
348
350
|
tsx: "4.22.3",
|
|
349
351
|
typescript: "5.9.3",
|
|
@@ -1449,6 +1451,13 @@ function stringArray(value) {
|
|
|
1449
1451
|
|
|
1450
1452
|
// plugins/core/extractors/external-url-counter/index.ts
|
|
1451
1453
|
var ID4 = "external-url-counter";
|
|
1454
|
+
var count = {
|
|
1455
|
+
slot: "card.footer.left",
|
|
1456
|
+
icon: "pi-link",
|
|
1457
|
+
label: "urls",
|
|
1458
|
+
emitWhenEmpty: false,
|
|
1459
|
+
priority: 30
|
|
1460
|
+
};
|
|
1452
1461
|
var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
|
|
1453
1462
|
var TRAILING_PUNCT = /[.,;:!?]+$/;
|
|
1454
1463
|
var externalUrlCounterExtractor = {
|
|
@@ -1473,15 +1482,7 @@ var externalUrlCounterExtractor = {
|
|
|
1473
1482
|
* inherited from the footer `.sm-gnode__stat` styles cloned by
|
|
1474
1483
|
* the `NodeCounter` renderer.
|
|
1475
1484
|
*/
|
|
1476
|
-
ui: {
|
|
1477
|
-
count: {
|
|
1478
|
-
slot: "card.footer.left",
|
|
1479
|
-
icon: "pi-link",
|
|
1480
|
-
label: "urls",
|
|
1481
|
-
emitWhenEmpty: false,
|
|
1482
|
-
priority: 30
|
|
1483
|
-
}
|
|
1484
|
-
},
|
|
1485
|
+
ui: { count },
|
|
1485
1486
|
extract(ctx) {
|
|
1486
1487
|
const seen = /* @__PURE__ */ new Set();
|
|
1487
1488
|
const body = stripCodeBlocks(ctx.body);
|
|
@@ -1516,7 +1517,7 @@ var externalUrlCounterExtractor = {
|
|
|
1516
1517
|
});
|
|
1517
1518
|
}
|
|
1518
1519
|
if (seen.size > 0) {
|
|
1519
|
-
ctx.emitContribution(
|
|
1520
|
+
ctx.emitContribution(count, { value: seen.size });
|
|
1520
1521
|
}
|
|
1521
1522
|
}
|
|
1522
1523
|
};
|
|
@@ -1660,6 +1661,13 @@ function collectMcpServers(tools) {
|
|
|
1660
1661
|
|
|
1661
1662
|
// plugins/core/extractors/tools-counter/index.ts
|
|
1662
1663
|
var ID7 = "tools-counter";
|
|
1664
|
+
var count2 = {
|
|
1665
|
+
slot: "card.footer.left",
|
|
1666
|
+
icon: "pi-wrench",
|
|
1667
|
+
label: "tools",
|
|
1668
|
+
emitWhenEmpty: false,
|
|
1669
|
+
priority: 40
|
|
1670
|
+
};
|
|
1663
1671
|
var TOOLTIP_MAX = 255;
|
|
1664
1672
|
var toolsCounterExtractor = {
|
|
1665
1673
|
id: ID7,
|
|
@@ -1668,15 +1676,7 @@ var toolsCounterExtractor = {
|
|
|
1668
1676
|
description: "Counts the tools an agent declares in its frontmatter and shows the count on the agent card.",
|
|
1669
1677
|
scope: "frontmatter",
|
|
1670
1678
|
precondition: { kind: ["claude/agent"] },
|
|
1671
|
-
ui: {
|
|
1672
|
-
count: {
|
|
1673
|
-
slot: "card.footer.left",
|
|
1674
|
-
icon: "pi-wrench",
|
|
1675
|
-
label: "tools",
|
|
1676
|
-
emitWhenEmpty: false,
|
|
1677
|
-
priority: 40
|
|
1678
|
-
}
|
|
1679
|
-
},
|
|
1679
|
+
ui: { count: count2 },
|
|
1680
1680
|
extract(ctx) {
|
|
1681
1681
|
const raw = ctx.frontmatter["tools"];
|
|
1682
1682
|
if (!Array.isArray(raw)) return;
|
|
@@ -1685,7 +1685,7 @@ var toolsCounterExtractor = {
|
|
|
1685
1685
|
if (typeof t === "string" && t.length > 0) names.push(t);
|
|
1686
1686
|
}
|
|
1687
1687
|
if (names.length === 0) return;
|
|
1688
|
-
ctx.emitContribution(
|
|
1688
|
+
ctx.emitContribution(count2, {
|
|
1689
1689
|
value: names.length,
|
|
1690
1690
|
tooltip: buildTooltip(names)
|
|
1691
1691
|
});
|
|
@@ -1923,64 +1923,91 @@ var ANNOTATION_STALE_TEXTS = {
|
|
|
1923
1923
|
// a literal placeholder the operator substitutes.
|
|
1924
1924
|
bodyTooltip: "Sidecar drift since last bump:\n \u2022 body\nRun `sm bump <path>` to refresh.",
|
|
1925
1925
|
frontmatterTooltip: "Sidecar drift since last bump:\n \u2022 frontmatter\nRun `sm bump <path>` to refresh.",
|
|
1926
|
-
bothTooltip: "Sidecar drift since last bump:\n \u2022 body\n \u2022 frontmatter\nRun `sm bump <path>` to refresh."
|
|
1926
|
+
bothTooltip: "Sidecar drift since last bump:\n \u2022 body\n \u2022 frontmatter\nRun `sm bump <path>` to refresh.",
|
|
1927
|
+
/** Label of the inspector action button that dispatches a bump. */
|
|
1928
|
+
bumpLabel: "Bump",
|
|
1929
|
+
/** Tooltip shown when the bump button is disabled (the node is fresh, no drift). */
|
|
1930
|
+
bumpDisabledReason: "No drift to bump."
|
|
1927
1931
|
};
|
|
1928
1932
|
|
|
1929
1933
|
// plugins/core/analyzers/annotation-stale/index.ts
|
|
1930
1934
|
var ID10 = "annotation-stale";
|
|
1935
|
+
var staleIcon = {
|
|
1936
|
+
slot: "card.footer.right",
|
|
1937
|
+
icon: "pi-clock",
|
|
1938
|
+
emitWhenEmpty: true,
|
|
1939
|
+
priority: 20
|
|
1940
|
+
};
|
|
1941
|
+
var staleBadge = {
|
|
1942
|
+
slot: "inspector.header.badge",
|
|
1943
|
+
emitWhenEmpty: false,
|
|
1944
|
+
priority: 20
|
|
1945
|
+
};
|
|
1946
|
+
var bumpButton = {
|
|
1947
|
+
slot: "inspector.action.button",
|
|
1948
|
+
priority: 10
|
|
1949
|
+
};
|
|
1931
1950
|
var annotationStaleAnalyzer = {
|
|
1932
1951
|
id: ID10,
|
|
1933
1952
|
pluginId: CORE_PLUGIN_ID,
|
|
1934
1953
|
kind: "analyzer",
|
|
1935
1954
|
description: "Marks sidecars (`.sm`) that are out of date with their `.md`.",
|
|
1936
1955
|
mode: "deterministic",
|
|
1937
|
-
// The natural fix is to bump the node: refreshes
|
|
1956
|
+
// The natural fix is to bump the node: refreshes the sidecar hashes,
|
|
1938
1957
|
// increments `annotations.version`, and stamps the audit block. The
|
|
1939
|
-
//
|
|
1940
|
-
|
|
1941
|
-
ui: {
|
|
1942
|
-
// A `pi-clock` chip in the footer-right cluster so the operator
|
|
1943
|
-
// spots drift in the list / inspector view (and on the graph card
|
|
1944
|
-
// body). Emitted with `value: 0` and `emitWhenEmpty: true` so the
|
|
1945
|
-
// renderer treats it as icon-only, drift severity is binary at
|
|
1946
|
-
// this surface (the tooltip carries the per-face detail body /
|
|
1947
|
-
// frontmatter / both). The corner badge on `graph.node.alert` was
|
|
1948
|
-
// dropped on purpose: a tooltip on the footer chip is enough, and
|
|
1949
|
-
// the corner badge stacked on top of broken-ref / unknown-field
|
|
1950
|
-
// alerts produced visual noise.
|
|
1951
|
-
staleIcon: {
|
|
1952
|
-
slot: "card.footer.right",
|
|
1953
|
-
icon: "pi-clock",
|
|
1954
|
-
emitWhenEmpty: true,
|
|
1955
|
-
// Sits right after the stability badge and before the severity
|
|
1956
|
-
// counters: stability is the node's declared lifecycle state,
|
|
1957
|
-
// drift is "this node disagrees with its sidecar", then the
|
|
1958
|
-
// warn / error counts anchor the right edge.
|
|
1959
|
-
priority: 20
|
|
1960
|
-
}
|
|
1961
|
-
},
|
|
1958
|
+
// inspector surfaces `core/node-bump` as the `bumpButton` contribution.
|
|
1959
|
+
ui: { staleIcon, staleBadge, bumpButton },
|
|
1962
1960
|
evaluate(ctx) {
|
|
1963
1961
|
const issues = [];
|
|
1964
1962
|
for (const node of ctx.nodes) {
|
|
1965
|
-
const status = node.sidecar
|
|
1966
|
-
if (
|
|
1967
|
-
|
|
1968
|
-
|
|
1963
|
+
const status = staleStatus(node.sidecar);
|
|
1964
|
+
if (node.sidecar?.present === true) {
|
|
1965
|
+
emitBumpButton(ctx, node.path, status !== null);
|
|
1966
|
+
}
|
|
1967
|
+
if (status === null) continue;
|
|
1969
1968
|
issues.push({
|
|
1970
1969
|
analyzerId: ID10,
|
|
1971
1970
|
severity: "info",
|
|
1972
1971
|
nodeIds: [node.path],
|
|
1973
|
-
message,
|
|
1972
|
+
message: messageFor(status, node.path),
|
|
1974
1973
|
data: { status }
|
|
1975
1974
|
});
|
|
1976
|
-
ctx.emitContribution(node.path,
|
|
1975
|
+
ctx.emitContribution(node.path, staleIcon, {
|
|
1977
1976
|
value: 0,
|
|
1978
1977
|
tooltip: tooltipFor(status)
|
|
1979
1978
|
});
|
|
1979
|
+
ctx.emitContribution(node.path, staleBadge, {
|
|
1980
|
+
icon: "pi-clock",
|
|
1981
|
+
tooltip: tooltipFor(status)
|
|
1982
|
+
});
|
|
1980
1983
|
}
|
|
1981
1984
|
return issues;
|
|
1982
1985
|
}
|
|
1983
1986
|
};
|
|
1987
|
+
function staleStatus(overlay) {
|
|
1988
|
+
const status = overlay?.status;
|
|
1989
|
+
if (status === void 0 || status === null || status === "fresh") return null;
|
|
1990
|
+
return status;
|
|
1991
|
+
}
|
|
1992
|
+
function messageFor(status, path) {
|
|
1993
|
+
switch (status) {
|
|
1994
|
+
case "stale-body":
|
|
1995
|
+
return tx(ANNOTATION_STALE_TEXTS.bodyDrift, { path });
|
|
1996
|
+
case "stale-frontmatter":
|
|
1997
|
+
return tx(ANNOTATION_STALE_TEXTS.frontmatterDrift, { path });
|
|
1998
|
+
case "stale-both":
|
|
1999
|
+
return tx(ANNOTATION_STALE_TEXTS.bothDrift, { path });
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
function emitBumpButton(ctx, nodePath, enabled) {
|
|
2003
|
+
ctx.emitContribution(nodePath, bumpButton, {
|
|
2004
|
+
actionId: "core/node-bump",
|
|
2005
|
+
label: ANNOTATION_STALE_TEXTS.bumpLabel,
|
|
2006
|
+
icon: "pi-arrow-up-right",
|
|
2007
|
+
enabled,
|
|
2008
|
+
...enabled ? {} : { disabledReason: ANNOTATION_STALE_TEXTS.bumpDisabledReason }
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
1984
2011
|
function tooltipFor(status) {
|
|
1985
2012
|
switch (status) {
|
|
1986
2013
|
case "stale-body":
|
|
@@ -2015,6 +2042,18 @@ var ISSUE_COUNTER_TEXTS = {
|
|
|
2015
2042
|
|
|
2016
2043
|
// plugins/core/analyzers/issue-counter/index.ts
|
|
2017
2044
|
var ID12 = "issue-counter";
|
|
2045
|
+
var warnCount = {
|
|
2046
|
+
slot: "card.footer.right",
|
|
2047
|
+
icon: "pi-exclamation-triangle",
|
|
2048
|
+
emitWhenEmpty: false,
|
|
2049
|
+
priority: 30
|
|
2050
|
+
};
|
|
2051
|
+
var errorCount = {
|
|
2052
|
+
slot: "card.footer.right",
|
|
2053
|
+
icon: "pi-times-circle",
|
|
2054
|
+
emitWhenEmpty: false,
|
|
2055
|
+
priority: 40
|
|
2056
|
+
};
|
|
2018
2057
|
function countByTier(issues) {
|
|
2019
2058
|
const errors = /* @__PURE__ */ new Map();
|
|
2020
2059
|
const warns = /* @__PURE__ */ new Map();
|
|
@@ -2027,13 +2066,13 @@ function countByTier(issues) {
|
|
|
2027
2066
|
}
|
|
2028
2067
|
return { errors, warns };
|
|
2029
2068
|
}
|
|
2030
|
-
function emitTierChips(ctx,
|
|
2031
|
-
for (const [nodePath,
|
|
2032
|
-
const capped = Math.min(
|
|
2033
|
-
ctx.emitContribution(nodePath,
|
|
2069
|
+
function emitTierChips(ctx, ref, severity, counts, singleTooltip, manyTooltip) {
|
|
2070
|
+
for (const [nodePath, count3] of counts) {
|
|
2071
|
+
const capped = Math.min(count3, 99);
|
|
2072
|
+
ctx.emitContribution(nodePath, ref, {
|
|
2034
2073
|
value: capped,
|
|
2035
2074
|
severity,
|
|
2036
|
-
tooltip:
|
|
2075
|
+
tooltip: count3 === 1 ? singleTooltip : tx(manyTooltip, { count: count3 })
|
|
2037
2076
|
});
|
|
2038
2077
|
}
|
|
2039
2078
|
}
|
|
@@ -2044,33 +2083,14 @@ var issueCounterAnalyzer = {
|
|
|
2044
2083
|
description: "Emits one aggregate severity chip per node (error + warn counts) from the live issue accumulator.",
|
|
2045
2084
|
mode: "deterministic",
|
|
2046
2085
|
phase: "aggregate",
|
|
2047
|
-
ui: {
|
|
2048
|
-
// Third in the footer-right cluster, after the drift chip
|
|
2049
|
-
// (priority 10) and the stability badge (priority 20). The warn
|
|
2050
|
-
// counter sits before the error counter so the operator reads
|
|
2051
|
-
// "advisory → blocking" left-to-right.
|
|
2052
|
-
warnCount: {
|
|
2053
|
-
slot: "card.footer.right",
|
|
2054
|
-
icon: "pi-exclamation-triangle",
|
|
2055
|
-
emitWhenEmpty: false,
|
|
2056
|
-
priority: 30
|
|
2057
|
-
},
|
|
2058
|
-
// Last in the cluster, the red chip pins to the right edge so the
|
|
2059
|
-
// most severe signal anchors the row's reading position.
|
|
2060
|
-
errorCount: {
|
|
2061
|
-
slot: "card.footer.right",
|
|
2062
|
-
icon: "pi-times-circle",
|
|
2063
|
-
emitWhenEmpty: false,
|
|
2064
|
-
priority: 40
|
|
2065
|
-
}
|
|
2066
|
-
},
|
|
2086
|
+
ui: { warnCount, errorCount },
|
|
2067
2087
|
evaluate(ctx) {
|
|
2068
2088
|
const accumulator = ctx.accumulatedIssues ?? [];
|
|
2069
2089
|
if (accumulator.length === 0) return [];
|
|
2070
2090
|
const { errors, warns } = countByTier(accumulator);
|
|
2071
2091
|
emitTierChips(
|
|
2072
2092
|
ctx,
|
|
2073
|
-
|
|
2093
|
+
errorCount,
|
|
2074
2094
|
"danger",
|
|
2075
2095
|
errors,
|
|
2076
2096
|
ISSUE_COUNTER_TEXTS.errorTooltipSingle,
|
|
@@ -2078,7 +2098,7 @@ var issueCounterAnalyzer = {
|
|
|
2078
2098
|
);
|
|
2079
2099
|
emitTierChips(
|
|
2080
2100
|
ctx,
|
|
2081
|
-
|
|
2101
|
+
warnCount,
|
|
2082
2102
|
"warn",
|
|
2083
2103
|
warns,
|
|
2084
2104
|
ISSUE_COUNTER_TEXTS.warnTooltipSingle,
|
|
@@ -2246,28 +2266,27 @@ function resolveLinkTargetToPath(link, nameIndex) {
|
|
|
2246
2266
|
|
|
2247
2267
|
// plugins/core/analyzers/link-counter/index.ts
|
|
2248
2268
|
var ID15 = "link-counter";
|
|
2269
|
+
var linksIn = {
|
|
2270
|
+
slot: "card.footer.left",
|
|
2271
|
+
icon: "pi-download",
|
|
2272
|
+
label: "incoming links",
|
|
2273
|
+
emitWhenEmpty: false,
|
|
2274
|
+
priority: 10
|
|
2275
|
+
};
|
|
2276
|
+
var linksOut = {
|
|
2277
|
+
slot: "card.footer.left",
|
|
2278
|
+
icon: "pi-upload",
|
|
2279
|
+
label: "outgoing links",
|
|
2280
|
+
emitWhenEmpty: false,
|
|
2281
|
+
priority: 20
|
|
2282
|
+
};
|
|
2249
2283
|
var linkCounterAnalyzer = {
|
|
2250
2284
|
id: ID15,
|
|
2251
2285
|
pluginId: CORE_PLUGIN_ID,
|
|
2252
2286
|
kind: "analyzer",
|
|
2253
2287
|
description: "Counts incoming and outgoing links per node.",
|
|
2254
2288
|
mode: "deterministic",
|
|
2255
|
-
ui: {
|
|
2256
|
-
linksIn: {
|
|
2257
|
-
slot: "card.footer.left",
|
|
2258
|
-
icon: "pi-download",
|
|
2259
|
-
label: "incoming links",
|
|
2260
|
-
emitWhenEmpty: false,
|
|
2261
|
-
priority: 10
|
|
2262
|
-
},
|
|
2263
|
-
linksOut: {
|
|
2264
|
-
slot: "card.footer.left",
|
|
2265
|
-
icon: "pi-upload",
|
|
2266
|
-
label: "outgoing links",
|
|
2267
|
-
emitWhenEmpty: false,
|
|
2268
|
-
priority: 20
|
|
2269
|
-
}
|
|
2270
|
-
},
|
|
2289
|
+
ui: { linksIn, linksOut },
|
|
2271
2290
|
evaluate(ctx) {
|
|
2272
2291
|
const nameIndex = buildNameIndex(ctx.nodes);
|
|
2273
2292
|
const perTarget = /* @__PURE__ */ new Map();
|
|
@@ -2279,8 +2298,8 @@ var linkCounterAnalyzer = {
|
|
|
2279
2298
|
bump(perSource, link.source, link.kind);
|
|
2280
2299
|
}
|
|
2281
2300
|
for (const node of ctx.nodes) {
|
|
2282
|
-
emitChip(ctx, node.path,
|
|
2283
|
-
emitChip(ctx, node.path,
|
|
2301
|
+
emitChip(ctx, node.path, linksIn, "in", perTarget.get(node.path));
|
|
2302
|
+
emitChip(ctx, node.path, linksOut, "out", perSource.get(node.path));
|
|
2284
2303
|
}
|
|
2285
2304
|
return [];
|
|
2286
2305
|
}
|
|
@@ -2293,14 +2312,13 @@ function bump(map, key, kind) {
|
|
|
2293
2312
|
}
|
|
2294
2313
|
byKind.set(kind, (byKind.get(kind) ?? 0) + 1);
|
|
2295
2314
|
}
|
|
2296
|
-
function emitChip(ctx, nodePath,
|
|
2315
|
+
function emitChip(ctx, nodePath, ref, direction, byKind) {
|
|
2297
2316
|
if (!byKind) return;
|
|
2298
2317
|
let total = 0;
|
|
2299
2318
|
for (const n of byKind.values()) total += n;
|
|
2300
2319
|
if (total === 0) return;
|
|
2301
2320
|
const capped = Math.min(total, 99);
|
|
2302
|
-
|
|
2303
|
-
ctx.emitContribution(nodePath, contributionId, {
|
|
2321
|
+
ctx.emitContribution(nodePath, ref, {
|
|
2304
2322
|
value: capped,
|
|
2305
2323
|
tooltip: formatBreakdown(byKind, direction)
|
|
2306
2324
|
});
|
|
@@ -2580,42 +2598,58 @@ function normaliseId(raw) {
|
|
|
2580
2598
|
return raw.normalize("NFD").replace(new RegExp("\\p{Mn}+", "gu"), "").toLowerCase().replace(/[-_\s]+/g, " ").replace(/ +/g, " ").trim();
|
|
2581
2599
|
}
|
|
2582
2600
|
|
|
2601
|
+
// plugins/core/analyzers/node-stability/text.ts
|
|
2602
|
+
var NODE_STABILITY_TEXTS = {
|
|
2603
|
+
/** Label of the inspector action button that sets the lifecycle stage. */
|
|
2604
|
+
setLabel: "Set stability",
|
|
2605
|
+
/** Prompt label for the enum-pick stability input. */
|
|
2606
|
+
promptLabel: "Stability",
|
|
2607
|
+
/** Prompt option label for the `experimental` stage. */
|
|
2608
|
+
optionExperimental: "Experimental",
|
|
2609
|
+
/** Prompt option label for the `stable` stage. */
|
|
2610
|
+
optionStable: "Stable",
|
|
2611
|
+
/** Prompt option label for the `deprecated` stage. */
|
|
2612
|
+
optionDeprecated: "Deprecated"
|
|
2613
|
+
};
|
|
2614
|
+
|
|
2583
2615
|
// plugins/core/analyzers/node-stability/index.ts
|
|
2584
2616
|
var ID18 = "node-stability";
|
|
2585
2617
|
var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
|
|
2586
2618
|
var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
|
|
2619
|
+
var experimental = {
|
|
2620
|
+
slot: "card.footer.right",
|
|
2621
|
+
icon: "fa-solid fa-flask",
|
|
2622
|
+
label: "experimental",
|
|
2623
|
+
emitWhenEmpty: false,
|
|
2624
|
+
priority: 10
|
|
2625
|
+
};
|
|
2626
|
+
var deprecated = {
|
|
2627
|
+
slot: "card.footer.right",
|
|
2628
|
+
icon: "pi-ban",
|
|
2629
|
+
label: "deprecated",
|
|
2630
|
+
emitWhenEmpty: false,
|
|
2631
|
+
priority: 10
|
|
2632
|
+
};
|
|
2633
|
+
var setStabilityButton = {
|
|
2634
|
+
slot: "inspector.action.button",
|
|
2635
|
+
priority: 15
|
|
2636
|
+
};
|
|
2587
2637
|
var nodeStabilityAnalyzer = {
|
|
2588
2638
|
id: ID18,
|
|
2589
2639
|
pluginId: CORE_PLUGIN_ID,
|
|
2590
2640
|
kind: "analyzer",
|
|
2591
2641
|
description: "Reports a node's stability stage (`experimental`, `deprecated`) on the card.",
|
|
2592
2642
|
mode: "deterministic",
|
|
2593
|
-
ui: {
|
|
2594
|
-
// First in the footer-right cluster: stability is the node's
|
|
2595
|
-
// declared lifecycle state, so it leads, followed by the drift
|
|
2596
|
-
// chip and then the severity counters. It's a state badge, not a
|
|
2597
|
-
// count, so it stays left of the numeric zone.
|
|
2598
|
-
experimental: {
|
|
2599
|
-
slot: "card.footer.right",
|
|
2600
|
-
icon: "fa-solid fa-flask",
|
|
2601
|
-
label: "experimental",
|
|
2602
|
-
emitWhenEmpty: false,
|
|
2603
|
-
priority: 10
|
|
2604
|
-
},
|
|
2605
|
-
deprecated: {
|
|
2606
|
-
slot: "card.footer.right",
|
|
2607
|
-
icon: "pi-ban",
|
|
2608
|
-
label: "deprecated",
|
|
2609
|
-
emitWhenEmpty: false,
|
|
2610
|
-
priority: 10
|
|
2611
|
-
}
|
|
2612
|
-
},
|
|
2643
|
+
ui: { experimental, deprecated, setStabilityButton },
|
|
2613
2644
|
evaluate(ctx) {
|
|
2614
2645
|
const issues = [];
|
|
2615
2646
|
for (const node of ctx.nodes) {
|
|
2616
2647
|
const stability = readStability(node);
|
|
2648
|
+
if (node.sidecar?.present === true) {
|
|
2649
|
+
emitSetStabilityButton(ctx, node.path, stability ?? "stable");
|
|
2650
|
+
}
|
|
2617
2651
|
if (stability === "experimental") {
|
|
2618
|
-
ctx.emitContribution(node.path,
|
|
2652
|
+
ctx.emitContribution(node.path, experimental, {
|
|
2619
2653
|
value: 0,
|
|
2620
2654
|
tooltip: EXPERIMENTAL_TOOLTIP
|
|
2621
2655
|
});
|
|
@@ -2627,7 +2661,7 @@ var nodeStabilityAnalyzer = {
|
|
|
2627
2661
|
data: { stability }
|
|
2628
2662
|
});
|
|
2629
2663
|
} else if (stability === "deprecated") {
|
|
2630
|
-
ctx.emitContribution(node.path,
|
|
2664
|
+
ctx.emitContribution(node.path, deprecated, {
|
|
2631
2665
|
value: 0,
|
|
2632
2666
|
tooltip: DEPRECATED_TOOLTIP,
|
|
2633
2667
|
severity: "warn"
|
|
@@ -2659,6 +2693,25 @@ function readLegacyMetadataStability(fm) {
|
|
|
2659
2693
|
function isStability(value) {
|
|
2660
2694
|
return value === "experimental" || value === "deprecated" || value === "stable";
|
|
2661
2695
|
}
|
|
2696
|
+
function emitSetStabilityButton(ctx, nodePath, current) {
|
|
2697
|
+
ctx.emitContribution(nodePath, setStabilityButton, {
|
|
2698
|
+
actionId: "core/node-set-stability",
|
|
2699
|
+
label: NODE_STABILITY_TEXTS.setLabel,
|
|
2700
|
+
icon: "pi-flag",
|
|
2701
|
+
enabled: true,
|
|
2702
|
+
prompt: {
|
|
2703
|
+
inputType: "enum-pick",
|
|
2704
|
+
paramKey: "stability",
|
|
2705
|
+
label: NODE_STABILITY_TEXTS.promptLabel,
|
|
2706
|
+
options: [
|
|
2707
|
+
{ value: "experimental", label: NODE_STABILITY_TEXTS.optionExperimental },
|
|
2708
|
+
{ value: "stable", label: NODE_STABILITY_TEXTS.optionStable },
|
|
2709
|
+
{ value: "deprecated", label: NODE_STABILITY_TEXTS.optionDeprecated }
|
|
2710
|
+
],
|
|
2711
|
+
defaultValue: current
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2662
2715
|
|
|
2663
2716
|
// plugins/core/analyzers/node-superseded/text.ts
|
|
2664
2717
|
var NODE_SUPERSEDED_TEXTS = {
|
|
@@ -3021,8 +3074,8 @@ var ALL_SLOT_NAMES = [
|
|
|
3021
3074
|
"card.footer.left",
|
|
3022
3075
|
"card.footer.right",
|
|
3023
3076
|
"graph.node.alert",
|
|
3024
|
-
"inspector.header.badge
|
|
3025
|
-
"inspector.
|
|
3077
|
+
"inspector.header.badge",
|
|
3078
|
+
"inspector.action.button",
|
|
3026
3079
|
"inspector.body.panel.breakdown",
|
|
3027
3080
|
"inspector.body.panel.records",
|
|
3028
3081
|
"inspector.body.panel.tree",
|
|
@@ -3130,12 +3183,12 @@ function buildSchemaValidators() {
|
|
|
3130
3183
|
const v = validators.get(name);
|
|
3131
3184
|
if (!v) throw new Error(`Unknown schema: ${name}`);
|
|
3132
3185
|
if (v(data)) return { ok: true, data };
|
|
3133
|
-
const errors = (v.errors
|
|
3186
|
+
const errors = formatAjvErrors(v.errors);
|
|
3134
3187
|
return { ok: false, errors };
|
|
3135
3188
|
},
|
|
3136
3189
|
validatePluginManifest(data) {
|
|
3137
3190
|
if (pluginManifestValidator(data)) return { ok: true, data };
|
|
3138
|
-
const errors = (pluginManifestValidator.errors
|
|
3191
|
+
const errors = formatAjvErrors(pluginManifestValidator.errors);
|
|
3139
3192
|
return { ok: false, errors };
|
|
3140
3193
|
},
|
|
3141
3194
|
validateContributionPayload(slot, payload) {
|
|
@@ -3144,7 +3197,7 @@ function buildSchemaValidators() {
|
|
|
3144
3197
|
return { ok: false, errors: "unknown-slot" };
|
|
3145
3198
|
}
|
|
3146
3199
|
if (validator(payload)) return { ok: true };
|
|
3147
|
-
const errors = (validator.errors
|
|
3200
|
+
const errors = formatAjvErrors(validator.errors);
|
|
3148
3201
|
return { ok: false, errors };
|
|
3149
3202
|
}
|
|
3150
3203
|
};
|
|
@@ -3176,7 +3229,7 @@ function buildProviderFrontmatterValidator(providers) {
|
|
|
3176
3229
|
const v = compiled.get(key);
|
|
3177
3230
|
if (!v) return { ok: false, errors: "no-schema" };
|
|
3178
3231
|
if (v(data)) return { ok: true };
|
|
3179
|
-
const errors = (v.errors
|
|
3232
|
+
const errors = formatAjvErrors(v.errors);
|
|
3180
3233
|
return { ok: false, errors };
|
|
3181
3234
|
}
|
|
3182
3235
|
};
|
|
@@ -3185,6 +3238,47 @@ function formatError(err) {
|
|
|
3185
3238
|
const path = err.instancePath || "(root)";
|
|
3186
3239
|
return `${path} ${err.message ?? err.keyword}`;
|
|
3187
3240
|
}
|
|
3241
|
+
function formatAjvErrors(errors) {
|
|
3242
|
+
const list = errors ?? [];
|
|
3243
|
+
if (list.length === 0) return "";
|
|
3244
|
+
const byPath3 = /* @__PURE__ */ new Map();
|
|
3245
|
+
for (const e of list) {
|
|
3246
|
+
const path = e.instancePath || "(root)";
|
|
3247
|
+
const bucket = byPath3.get(path);
|
|
3248
|
+
if (bucket) bucket.push(e);
|
|
3249
|
+
else byPath3.set(path, [e]);
|
|
3250
|
+
}
|
|
3251
|
+
const parts = [];
|
|
3252
|
+
for (const [path, errs] of byPath3) parts.push(...formatPathErrors(path, errs));
|
|
3253
|
+
return [...new Set(parts)].join("; ");
|
|
3254
|
+
}
|
|
3255
|
+
function constBranchValues(errs) {
|
|
3256
|
+
let count3 = 0;
|
|
3257
|
+
for (const e of errs) {
|
|
3258
|
+
const isConst = e.keyword === "const" && typeof e.params === "object" && e.params !== null && "allowedValue" in e.params;
|
|
3259
|
+
if (isConst) count3 += 1;
|
|
3260
|
+
}
|
|
3261
|
+
return count3;
|
|
3262
|
+
}
|
|
3263
|
+
function formatPathErrors(path, errs) {
|
|
3264
|
+
if (constBranchValues(errs) >= 2) {
|
|
3265
|
+
const parts2 = [`${path} is not a valid value`];
|
|
3266
|
+
for (const e of errs) {
|
|
3267
|
+
if (e.keyword !== "const" && e.keyword !== "oneOf") parts2.push(formatError(e));
|
|
3268
|
+
}
|
|
3269
|
+
return parts2;
|
|
3270
|
+
}
|
|
3271
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3272
|
+
const parts = [];
|
|
3273
|
+
for (const e of errs) {
|
|
3274
|
+
const msg = formatError(e);
|
|
3275
|
+
if (!seen.has(msg)) {
|
|
3276
|
+
seen.add(msg);
|
|
3277
|
+
parts.push(msg);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
return parts;
|
|
3281
|
+
}
|
|
3188
3282
|
function registerProviderAuxiliarySchemas(ajv, providers) {
|
|
3189
3283
|
for (const provider of providers) {
|
|
3190
3284
|
if (!provider.schemas) continue;
|
|
@@ -3497,6 +3591,122 @@ function makeIssue(signal) {
|
|
|
3497
3591
|
return null;
|
|
3498
3592
|
}
|
|
3499
3593
|
|
|
3594
|
+
// plugins/core/analyzers/supersede/text.ts
|
|
3595
|
+
var SUPERSEDE_TEXTS = {
|
|
3596
|
+
/** Label of the inspector action button that declares supersession. */
|
|
3597
|
+
supersedeLabel: "Supersede",
|
|
3598
|
+
/** Tooltip shown when the supersede button is disabled (already superseded). */
|
|
3599
|
+
supersedeDisabledReason: "Already superseded.",
|
|
3600
|
+
/** Tooltip shown when there is no other node to supersede this one. */
|
|
3601
|
+
supersedeNoTargetsReason: "No other node to supersede this one.",
|
|
3602
|
+
/** Prompt label for the target node-picker (enum-pick over the live node set). */
|
|
3603
|
+
supersedePromptLabel: "Superseded by"
|
|
3604
|
+
};
|
|
3605
|
+
|
|
3606
|
+
// plugins/core/analyzers/supersede/index.ts
|
|
3607
|
+
var ID24 = "supersede";
|
|
3608
|
+
var supersedeButton = {
|
|
3609
|
+
slot: "inspector.action.button",
|
|
3610
|
+
priority: 10
|
|
3611
|
+
};
|
|
3612
|
+
var supersedeAnalyzer = {
|
|
3613
|
+
id: ID24,
|
|
3614
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3615
|
+
kind: "analyzer",
|
|
3616
|
+
description: 'Projects the inspector "Supersede" button (declares a node replaced by another).',
|
|
3617
|
+
mode: "deterministic",
|
|
3618
|
+
ui: { supersedeButton },
|
|
3619
|
+
evaluate(ctx) {
|
|
3620
|
+
const candidates = ctx.nodes.filter((n) => n.virtual !== true).map((n) => n.path);
|
|
3621
|
+
for (const node of ctx.nodes) {
|
|
3622
|
+
if (node.virtual === true) continue;
|
|
3623
|
+
const options = candidates.filter((p) => p !== node.path).map((p) => ({ value: p, label: p }));
|
|
3624
|
+
emitSupersedeButton(ctx, node, options);
|
|
3625
|
+
}
|
|
3626
|
+
return [];
|
|
3627
|
+
}
|
|
3628
|
+
};
|
|
3629
|
+
function emitSupersedeButton(ctx, node, options) {
|
|
3630
|
+
const disabledReason = resolveDisabledReason(node, options.length);
|
|
3631
|
+
ctx.emitContribution(node.path, supersedeButton, {
|
|
3632
|
+
actionId: "core/node-supersede",
|
|
3633
|
+
label: SUPERSEDE_TEXTS.supersedeLabel,
|
|
3634
|
+
icon: "pi-arrow-right-arrow-left",
|
|
3635
|
+
enabled: disabledReason === void 0,
|
|
3636
|
+
...disabledReason === void 0 ? {} : { disabledReason },
|
|
3637
|
+
prompt: {
|
|
3638
|
+
inputType: "enum-pick",
|
|
3639
|
+
paramKey: "supersededBy",
|
|
3640
|
+
label: SUPERSEDE_TEXTS.supersedePromptLabel,
|
|
3641
|
+
options
|
|
3642
|
+
}
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
function resolveDisabledReason(node, optionCount) {
|
|
3646
|
+
if (alreadySuperseded(node)) return SUPERSEDE_TEXTS.supersedeDisabledReason;
|
|
3647
|
+
if (optionCount === 0) return SUPERSEDE_TEXTS.supersedeNoTargetsReason;
|
|
3648
|
+
return void 0;
|
|
3649
|
+
}
|
|
3650
|
+
function alreadySuperseded(node) {
|
|
3651
|
+
const sidecar = node.sidecar;
|
|
3652
|
+
if (!sidecar || sidecar.present !== true) return false;
|
|
3653
|
+
const ann = sidecar.annotations;
|
|
3654
|
+
if (!ann || typeof ann !== "object" || Array.isArray(ann)) return false;
|
|
3655
|
+
const value = ann["supersededBy"];
|
|
3656
|
+
return typeof value === "string" && value.length > 0;
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
// plugins/core/analyzers/tags/text.ts
|
|
3660
|
+
var TAGS_TEXTS = {
|
|
3661
|
+
/** Label of the inspector action button that edits the node's tags. */
|
|
3662
|
+
editLabel: "Edit tags",
|
|
3663
|
+
/** Prompt label for the string-list tags input. */
|
|
3664
|
+
promptLabel: "Tags"
|
|
3665
|
+
};
|
|
3666
|
+
|
|
3667
|
+
// plugins/core/analyzers/tags/index.ts
|
|
3668
|
+
var ID25 = "tags";
|
|
3669
|
+
var setTagsButton = {
|
|
3670
|
+
slot: "inspector.action.button",
|
|
3671
|
+
priority: 15
|
|
3672
|
+
};
|
|
3673
|
+
var tagsAnalyzer = {
|
|
3674
|
+
id: ID25,
|
|
3675
|
+
pluginId: CORE_PLUGIN_ID,
|
|
3676
|
+
kind: "analyzer",
|
|
3677
|
+
description: `Projects the inspector "Edit tags" button (edits a node's taxonomy tags).`,
|
|
3678
|
+
mode: "deterministic",
|
|
3679
|
+
ui: { setTagsButton },
|
|
3680
|
+
evaluate(ctx) {
|
|
3681
|
+
for (const node of ctx.nodes) {
|
|
3682
|
+
if (node.sidecar?.present !== true) continue;
|
|
3683
|
+
emitSetTagsButton(ctx, node);
|
|
3684
|
+
}
|
|
3685
|
+
return [];
|
|
3686
|
+
}
|
|
3687
|
+
};
|
|
3688
|
+
function emitSetTagsButton(ctx, node) {
|
|
3689
|
+
ctx.emitContribution(node.path, setTagsButton, {
|
|
3690
|
+
actionId: "core/node-set-tags",
|
|
3691
|
+
label: TAGS_TEXTS.editLabel,
|
|
3692
|
+
icon: "pi-tags",
|
|
3693
|
+
enabled: true,
|
|
3694
|
+
prompt: {
|
|
3695
|
+
inputType: "string-list",
|
|
3696
|
+
paramKey: "tags",
|
|
3697
|
+
label: TAGS_TEXTS.promptLabel,
|
|
3698
|
+
defaultValue: currentTags(node)
|
|
3699
|
+
}
|
|
3700
|
+
});
|
|
3701
|
+
}
|
|
3702
|
+
function currentTags(node) {
|
|
3703
|
+
const ann = node.sidecar?.annotations;
|
|
3704
|
+
if (!ann || typeof ann !== "object" || Array.isArray(ann)) return [];
|
|
3705
|
+
const value = ann["tags"];
|
|
3706
|
+
if (!Array.isArray(value)) return [];
|
|
3707
|
+
return value.filter((t) => typeof t === "string");
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3500
3710
|
// plugins/core/analyzers/trigger-collision/text.ts
|
|
3501
3711
|
var TRIGGER_COLLISION_TEXTS = {
|
|
3502
3712
|
/**
|
|
@@ -3523,14 +3733,14 @@ var TRIGGER_COLLISION_TEXTS = {
|
|
|
3523
3733
|
};
|
|
3524
3734
|
|
|
3525
3735
|
// plugins/core/analyzers/trigger-collision/index.ts
|
|
3526
|
-
var
|
|
3736
|
+
var ID26 = "trigger-collision";
|
|
3527
3737
|
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
3528
3738
|
"command",
|
|
3529
3739
|
"skill",
|
|
3530
3740
|
"agent"
|
|
3531
3741
|
]);
|
|
3532
3742
|
var triggerCollisionAnalyzer = {
|
|
3533
|
-
id:
|
|
3743
|
+
id: ID26,
|
|
3534
3744
|
pluginId: CORE_PLUGIN_ID,
|
|
3535
3745
|
kind: "analyzer",
|
|
3536
3746
|
mode: "deterministic",
|
|
@@ -3628,7 +3838,7 @@ function analyzeTriggerBucket(normalized, claims) {
|
|
|
3628
3838
|
part: parts[0]
|
|
3629
3839
|
});
|
|
3630
3840
|
return {
|
|
3631
|
-
analyzerId:
|
|
3841
|
+
analyzerId: ID26,
|
|
3632
3842
|
severity: "error",
|
|
3633
3843
|
nodeIds,
|
|
3634
3844
|
message,
|
|
@@ -3668,13 +3878,13 @@ var ASCII_FORMATTER_TEXTS = {
|
|
|
3668
3878
|
};
|
|
3669
3879
|
|
|
3670
3880
|
// plugins/core/formatters/ascii/index.ts
|
|
3671
|
-
var
|
|
3881
|
+
var ID27 = "ascii";
|
|
3672
3882
|
var KIND_ORDER = ["agent", "command", "skill", "markdown"];
|
|
3673
3883
|
var asciiFormatter = {
|
|
3674
|
-
id:
|
|
3884
|
+
id: ID27,
|
|
3675
3885
|
pluginId: CORE_PLUGIN_ID,
|
|
3676
3886
|
kind: "formatter",
|
|
3677
|
-
formatId:
|
|
3887
|
+
formatId: ID27,
|
|
3678
3888
|
description: "Renders the scan as plain text in three sections: nodes (grouped by kind), arrows, and issues. Used by `sm scan --format ascii`.",
|
|
3679
3889
|
// ASCII tree formatter, header + per-kind sections + per-issue
|
|
3680
3890
|
// section. Each section iterates and renders; splitting per section
|
|
@@ -3768,13 +3978,13 @@ function renderSection(out, kind, group) {
|
|
|
3768
3978
|
}
|
|
3769
3979
|
|
|
3770
3980
|
// plugins/core/formatters/json/index.ts
|
|
3771
|
-
var
|
|
3981
|
+
var ID28 = "json";
|
|
3772
3982
|
var jsonFormatter = {
|
|
3773
|
-
id:
|
|
3983
|
+
id: ID28,
|
|
3774
3984
|
pluginId: CORE_PLUGIN_ID,
|
|
3775
3985
|
kind: "formatter",
|
|
3776
3986
|
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`.",
|
|
3777
|
-
formatId:
|
|
3987
|
+
formatId: ID28,
|
|
3778
3988
|
format(ctx) {
|
|
3779
3989
|
if (ctx.scanResult !== void 0) {
|
|
3780
3990
|
return JSON.stringify(ctx.scanResult);
|
|
@@ -3913,9 +4123,9 @@ function resolveSpecRoot2() {
|
|
|
3913
4123
|
}
|
|
3914
4124
|
|
|
3915
4125
|
// plugins/core/actions/node-bump/index.ts
|
|
3916
|
-
var
|
|
4126
|
+
var ID29 = "node-bump";
|
|
3917
4127
|
var nodeBumpAction = {
|
|
3918
|
-
id:
|
|
4128
|
+
id: ID29,
|
|
3919
4129
|
pluginId: CORE_PLUGIN_ID,
|
|
3920
4130
|
kind: "action",
|
|
3921
4131
|
description: "Marks a node as updated: bumps `annotations.version`, refreshes sidecar hashes, and records the timestamp.",
|
|
@@ -3972,45 +4182,166 @@ function pickCurrentVersion(overlay) {
|
|
|
3972
4182
|
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
3973
4183
|
}
|
|
3974
4184
|
|
|
4185
|
+
// plugins/core/actions/node-set-stability/index.ts
|
|
4186
|
+
var STABILITY_VALUES = ["experimental", "stable", "deprecated"];
|
|
4187
|
+
var ID30 = "node-set-stability";
|
|
4188
|
+
var nodeSetStabilityAction = {
|
|
4189
|
+
id: ID30,
|
|
4190
|
+
pluginId: CORE_PLUGIN_ID,
|
|
4191
|
+
kind: "action",
|
|
4192
|
+
description: "Sets the lifecycle stage of the current node (writes `stability` to the sidecar).",
|
|
4193
|
+
mode: "deterministic",
|
|
4194
|
+
// The runtime contract uses generic <TInput, TReport>; this narrows
|
|
4195
|
+
// both. The cast is the standard pattern for built-ins that want
|
|
4196
|
+
// typed local I/O while staying compatible with the open generic.
|
|
4197
|
+
invoke(rawInput, ctx) {
|
|
4198
|
+
const input = rawInput ?? {};
|
|
4199
|
+
return invokeSetStability(input, ctx);
|
|
4200
|
+
}
|
|
4201
|
+
};
|
|
4202
|
+
function invokeSetStability(input, ctx) {
|
|
4203
|
+
const stability = input.stability;
|
|
4204
|
+
if (!STABILITY_VALUES.includes(stability)) {
|
|
4205
|
+
return { report: { ok: false, reason: "invalid" } };
|
|
4206
|
+
}
|
|
4207
|
+
const timestamp = ctx.now().toISOString();
|
|
4208
|
+
const write = {
|
|
4209
|
+
kind: "sidecar",
|
|
4210
|
+
path: sidecarPathFor(ctx.nodeAbsolutePath),
|
|
4211
|
+
changes: {
|
|
4212
|
+
identity: {
|
|
4213
|
+
path: ctx.node.path,
|
|
4214
|
+
bodyHash: ctx.node.bodyHash,
|
|
4215
|
+
frontmatterHash: ctx.node.frontmatterHash
|
|
4216
|
+
},
|
|
4217
|
+
annotations: { stability },
|
|
4218
|
+
audit: {
|
|
4219
|
+
lastBumpedAt: timestamp,
|
|
4220
|
+
lastBumpedBy: ctx.invoker
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
};
|
|
4224
|
+
const report = { ok: true, stability };
|
|
4225
|
+
return { report, writes: [write] };
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
// plugins/core/actions/node-set-tags/index.ts
|
|
4229
|
+
var ID31 = "node-set-tags";
|
|
4230
|
+
var nodeSetTagsAction = {
|
|
4231
|
+
id: ID31,
|
|
4232
|
+
pluginId: CORE_PLUGIN_ID,
|
|
4233
|
+
kind: "action",
|
|
4234
|
+
description: "Sets the taxonomy tags of the current node (writes `tags` to the sidecar; whole-array replace).",
|
|
4235
|
+
mode: "deterministic",
|
|
4236
|
+
// The runtime contract uses generic <TInput, TReport>; this narrows
|
|
4237
|
+
// both. The cast is the standard pattern for built-ins that want
|
|
4238
|
+
// typed local I/O while staying compatible with the open generic.
|
|
4239
|
+
invoke(rawInput, ctx) {
|
|
4240
|
+
const input = rawInput ?? {};
|
|
4241
|
+
return invokeSetTags(input, ctx);
|
|
4242
|
+
}
|
|
4243
|
+
};
|
|
4244
|
+
function invokeSetTags(input, ctx) {
|
|
4245
|
+
const tags = Array.isArray(input.tags) ? input.tags : [];
|
|
4246
|
+
const timestamp = ctx.now().toISOString();
|
|
4247
|
+
const write = {
|
|
4248
|
+
kind: "sidecar",
|
|
4249
|
+
path: sidecarPathFor(ctx.nodeAbsolutePath),
|
|
4250
|
+
changes: {
|
|
4251
|
+
identity: {
|
|
4252
|
+
path: ctx.node.path,
|
|
4253
|
+
bodyHash: ctx.node.bodyHash,
|
|
4254
|
+
frontmatterHash: ctx.node.frontmatterHash
|
|
4255
|
+
},
|
|
4256
|
+
annotations: { tags },
|
|
4257
|
+
audit: {
|
|
4258
|
+
lastBumpedAt: timestamp,
|
|
4259
|
+
lastBumpedBy: ctx.invoker
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
const report = { ok: true, tags };
|
|
4264
|
+
return { report, writes: [write] };
|
|
4265
|
+
}
|
|
4266
|
+
|
|
3975
4267
|
// plugins/core/actions/node-supersede/index.ts
|
|
3976
|
-
var
|
|
4268
|
+
var ID32 = "node-supersede";
|
|
3977
4269
|
var nodeSupersedeAction = {
|
|
3978
|
-
id:
|
|
4270
|
+
id: ID32,
|
|
3979
4271
|
pluginId: CORE_PLUGIN_ID,
|
|
3980
4272
|
kind: "action",
|
|
3981
4273
|
description: "Declares the current node as superseded by another (writes `supersededBy` to the sidecar).",
|
|
3982
4274
|
mode: "deterministic",
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
4275
|
+
// The runtime contract uses generic <TInput, TReport>; supersede
|
|
4276
|
+
// narrows both. The cast is the standard pattern for built-ins that
|
|
4277
|
+
// want typed local I/O while staying compatible with the open generic.
|
|
4278
|
+
invoke(rawInput, ctx) {
|
|
4279
|
+
const input = rawInput ?? {};
|
|
4280
|
+
return invokeSupersede(input, ctx);
|
|
3986
4281
|
}
|
|
3987
4282
|
};
|
|
4283
|
+
function invokeSupersede(input, ctx) {
|
|
4284
|
+
const supersededBy = input.supersededBy;
|
|
4285
|
+
if (supersededBy === ctx.node.path) {
|
|
4286
|
+
return { report: { ok: false, reason: "self" } };
|
|
4287
|
+
}
|
|
4288
|
+
const timestamp = ctx.now().toISOString();
|
|
4289
|
+
const write = {
|
|
4290
|
+
kind: "sidecar",
|
|
4291
|
+
path: sidecarPathFor(ctx.nodeAbsolutePath),
|
|
4292
|
+
changes: {
|
|
4293
|
+
identity: {
|
|
4294
|
+
path: ctx.node.path,
|
|
4295
|
+
bodyHash: ctx.node.bodyHash,
|
|
4296
|
+
frontmatterHash: ctx.node.frontmatterHash
|
|
4297
|
+
},
|
|
4298
|
+
annotations: { supersededBy },
|
|
4299
|
+
audit: {
|
|
4300
|
+
lastBumpedAt: timestamp,
|
|
4301
|
+
lastBumpedBy: ctx.invoker
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
};
|
|
4305
|
+
const report = { ok: true, supersededBy };
|
|
4306
|
+
return { report, writes: [write] };
|
|
4307
|
+
}
|
|
3988
4308
|
|
|
3989
4309
|
// kernel/update-check/index.ts
|
|
3990
4310
|
var SEMVER_SHAPE_RE = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
3991
4311
|
async function fetchLatestVersion(pkg, opts) {
|
|
3992
4312
|
const controller = new AbortController();
|
|
3993
|
-
const
|
|
4313
|
+
const abortTimer = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
4314
|
+
let capTimer;
|
|
4315
|
+
const hardCap = new Promise((_resolve, reject) => {
|
|
4316
|
+
capTimer = setTimeout(
|
|
4317
|
+
() => reject(new Error(`update check timed out after ${opts.timeoutMs}ms`)),
|
|
4318
|
+
opts.timeoutMs
|
|
4319
|
+
);
|
|
4320
|
+
});
|
|
3994
4321
|
try {
|
|
3995
|
-
|
|
3996
|
-
const response = await fetch(url, {
|
|
3997
|
-
signal: controller.signal,
|
|
3998
|
-
headers: { accept: "application/json" }
|
|
3999
|
-
});
|
|
4000
|
-
if (!response.ok) {
|
|
4001
|
-
throw new Error(`registry returned status ${response.status}`);
|
|
4002
|
-
}
|
|
4003
|
-
const payload = await response.json();
|
|
4004
|
-
if (typeof payload.version !== "string" || payload.version.length === 0) {
|
|
4005
|
-
throw new Error("registry payload missing string `version`");
|
|
4006
|
-
}
|
|
4007
|
-
if (!SEMVER_SHAPE_RE.test(payload.version)) {
|
|
4008
|
-
throw new Error("registry payload `version` is not a semver-shaped string");
|
|
4009
|
-
}
|
|
4010
|
-
return payload.version;
|
|
4322
|
+
return await Promise.race([fetchVersion(pkg, controller.signal), hardCap]);
|
|
4011
4323
|
} finally {
|
|
4012
|
-
clearTimeout(
|
|
4324
|
+
clearTimeout(abortTimer);
|
|
4325
|
+
if (capTimer !== void 0) clearTimeout(capTimer);
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
async function fetchVersion(pkg, signal) {
|
|
4329
|
+
const url = `https://registry.npmjs.org/${pkg}/latest`;
|
|
4330
|
+
const response = await fetch(url, {
|
|
4331
|
+
signal,
|
|
4332
|
+
headers: { accept: "application/json" }
|
|
4333
|
+
});
|
|
4334
|
+
if (!response.ok) {
|
|
4335
|
+
throw new Error(`registry returned status ${response.status}`);
|
|
4336
|
+
}
|
|
4337
|
+
const payload = await response.json();
|
|
4338
|
+
if (typeof payload.version !== "string" || payload.version.length === 0) {
|
|
4339
|
+
throw new Error("registry payload missing string `version`");
|
|
4340
|
+
}
|
|
4341
|
+
if (!SEMVER_SHAPE_RE.test(payload.version)) {
|
|
4342
|
+
throw new Error("registry payload `version` is not a semver-shaped string");
|
|
4013
4343
|
}
|
|
4344
|
+
return payload.version;
|
|
4014
4345
|
}
|
|
4015
4346
|
function compareVersions(a, b) {
|
|
4016
4347
|
const pa = parseSemver(a);
|
|
@@ -4479,10 +4810,14 @@ var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", v
|
|
|
4479
4810
|
var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: VERSION };
|
|
4480
4811
|
var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: VERSION };
|
|
4481
4812
|
var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4813
|
+
var supersedeAnalyzer2 = { ...supersedeAnalyzer, pluginId: "core", version: VERSION };
|
|
4814
|
+
var tagsAnalyzer2 = { ...tagsAnalyzer, pluginId: "core", version: VERSION };
|
|
4482
4815
|
var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4483
4816
|
var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: VERSION };
|
|
4484
4817
|
var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: VERSION };
|
|
4485
4818
|
var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: VERSION };
|
|
4819
|
+
var nodeSetStabilityAction2 = { ...nodeSetStabilityAction, pluginId: "core", version: VERSION };
|
|
4820
|
+
var nodeSetTagsAction2 = { ...nodeSetTagsAction, pluginId: "core", version: VERSION };
|
|
4486
4821
|
var nodeSupersedeAction2 = { ...nodeSupersedeAction, pluginId: "core", version: VERSION };
|
|
4487
4822
|
var updateCheckHook2 = { ...updateCheckHook, pluginId: "core", version: VERSION };
|
|
4488
4823
|
var builtInPlugins = [
|
|
@@ -4542,10 +4877,14 @@ var builtInPlugins = [
|
|
|
4542
4877
|
referenceRedundantAnalyzer2,
|
|
4543
4878
|
schemaViolationAnalyzer2,
|
|
4544
4879
|
signalCollisionAnalyzer2,
|
|
4880
|
+
supersedeAnalyzer2,
|
|
4881
|
+
tagsAnalyzer2,
|
|
4545
4882
|
triggerCollisionAnalyzer2,
|
|
4546
4883
|
asciiFormatter2,
|
|
4547
4884
|
jsonFormatter2,
|
|
4548
4885
|
nodeBumpAction2,
|
|
4886
|
+
nodeSetStabilityAction2,
|
|
4887
|
+
nodeSetTagsAction2,
|
|
4549
4888
|
nodeSupersedeAction2,
|
|
4550
4889
|
updateCheckHook2
|
|
4551
4890
|
]
|
|
@@ -5802,13 +6141,16 @@ function ensureSidecarWritesAllowed(opts) {
|
|
|
5802
6141
|
default: false
|
|
5803
6142
|
});
|
|
5804
6143
|
if (allowed === true) return;
|
|
5805
|
-
if (opts.
|
|
6144
|
+
if (opts.always === true) {
|
|
5806
6145
|
writeConfigValue("allowEditSmFiles", true, {
|
|
5807
6146
|
target: "project-local",
|
|
5808
6147
|
cwd: opts.cwd
|
|
5809
6148
|
});
|
|
5810
6149
|
return;
|
|
5811
6150
|
}
|
|
6151
|
+
if (opts.confirm === true) {
|
|
6152
|
+
return;
|
|
6153
|
+
}
|
|
5812
6154
|
throw new EConsentRequiredError({
|
|
5813
6155
|
key: "allowEditSmFiles",
|
|
5814
6156
|
hintTarget: "project-local"
|
|
@@ -7433,7 +7775,7 @@ async function loadPluginOverrideMap(db) {
|
|
|
7433
7775
|
}
|
|
7434
7776
|
|
|
7435
7777
|
// kernel/util/enum-parsers.ts
|
|
7436
|
-
var
|
|
7778
|
+
var STABILITY_VALUES2 = Object.freeze([
|
|
7437
7779
|
"experimental",
|
|
7438
7780
|
"stable",
|
|
7439
7781
|
"deprecated"
|
|
@@ -7756,6 +8098,41 @@ async function replaceAllScanContributions(trx, contributions, livePaths = /* @_
|
|
|
7756
8098
|
await sweepPerTupleContributions(trx, contributions, freshlyRunTuples);
|
|
7757
8099
|
await upsertContributionsBuffer(trx, contributions);
|
|
7758
8100
|
}
|
|
8101
|
+
async function replaceAllScanContributionErrors(trx, contributionErrors) {
|
|
8102
|
+
await trx.deleteFrom("scan_contribution_errors").execute();
|
|
8103
|
+
if (contributionErrors.length === 0) return;
|
|
8104
|
+
const CHUNK = 400;
|
|
8105
|
+
for (let i = 0; i < contributionErrors.length; i += CHUNK) {
|
|
8106
|
+
const slice = contributionErrors.slice(i, i + CHUNK);
|
|
8107
|
+
const rows = slice.map((e) => ({
|
|
8108
|
+
pluginId: e.pluginId,
|
|
8109
|
+
extensionId: e.extensionId,
|
|
8110
|
+
nodePath: e.nodePath,
|
|
8111
|
+
reason: e.reason,
|
|
8112
|
+
message: e.message,
|
|
8113
|
+
contributionId: e.contributionId ?? null,
|
|
8114
|
+
slot: e.slot ?? null,
|
|
8115
|
+
emittedAt: e.emittedAt
|
|
8116
|
+
}));
|
|
8117
|
+
await trx.insertInto("scan_contribution_errors").values(rows).execute();
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
async function listAllContributionErrors(db) {
|
|
8121
|
+
const rows = await db.selectFrom("scan_contribution_errors").selectAll().orderBy("pluginId", "asc").orderBy("extensionId", "asc").orderBy("nodePath", "asc").orderBy("emittedAt", "asc").execute();
|
|
8122
|
+
return rows.map(rowToContributionError);
|
|
8123
|
+
}
|
|
8124
|
+
function rowToContributionError(row) {
|
|
8125
|
+
return {
|
|
8126
|
+
pluginId: row.pluginId,
|
|
8127
|
+
extensionId: row.extensionId,
|
|
8128
|
+
nodePath: row.nodePath,
|
|
8129
|
+
reason: row.reason,
|
|
8130
|
+
message: row.message,
|
|
8131
|
+
...row.contributionId !== null ? { contributionId: row.contributionId } : {},
|
|
8132
|
+
...row.slot !== null ? { slot: row.slot } : {},
|
|
8133
|
+
emittedAt: row.emittedAt
|
|
8134
|
+
};
|
|
8135
|
+
}
|
|
7759
8136
|
async function sweepOrphanContributions(trx, livePaths) {
|
|
7760
8137
|
if (livePaths.size > 0) {
|
|
7761
8138
|
await trx.deleteFrom("scan_contributions").where("nodePath", "not in", [...livePaths]).execute();
|
|
@@ -7965,7 +8342,7 @@ async function findNodesByTag(db, tag) {
|
|
|
7965
8342
|
}
|
|
7966
8343
|
|
|
7967
8344
|
// kernel/adapters/sqlite/scan-persistence.ts
|
|
7968
|
-
async function persistScanResult(db, result, renameOps = [], extractorRuns = [], enrichments = [], contributions = [], registeredContributionKeys = /* @__PURE__ */ new Set(), freshlyRunTuples = /* @__PURE__ */ new Set()) {
|
|
8345
|
+
async function persistScanResult(db, result, renameOps = [], extractorRuns = [], enrichments = [], contributions = [], registeredContributionKeys = /* @__PURE__ */ new Set(), freshlyRunTuples = /* @__PURE__ */ new Set(), contributionErrors = []) {
|
|
7969
8346
|
const scannedAt = validateScannedAt(result.scannedAt);
|
|
7970
8347
|
const renames = [];
|
|
7971
8348
|
await db.transaction().execute(async (trx) => {
|
|
@@ -7980,6 +8357,7 @@ async function persistScanResult(db, result, renameOps = [], extractorRuns = [],
|
|
|
7980
8357
|
registeredContributionKeys,
|
|
7981
8358
|
freshlyRunTuples
|
|
7982
8359
|
);
|
|
8360
|
+
await replaceAllScanContributionErrors(trx, contributionErrors);
|
|
7983
8361
|
const tagRecords = nodesToTagRecords(result.nodes);
|
|
7984
8362
|
await replaceAllScanTags(trx, tagRecords, livePathsForContrib);
|
|
7985
8363
|
await upsertEnrichmentLayer(trx, result, renameOps, enrichments);
|
|
@@ -8424,7 +8802,8 @@ var SqliteStorageAdapter = class {
|
|
|
8424
8802
|
listForNode: (nodePath) => loadContributionsForNode(this.db, nodePath),
|
|
8425
8803
|
listForPaths: (paths) => loadContributionsForPaths(this.db, paths),
|
|
8426
8804
|
lookup: (pluginId, contributionId, nodePath, extensionId) => loadContributionLookup(this.db, pluginId, contributionId, nodePath, extensionId),
|
|
8427
|
-
purgeByPlugin: (pluginId, extensionId) => purgeContributionsByPlugin(this.db, pluginId, extensionId)
|
|
8805
|
+
purgeByPlugin: (pluginId, extensionId) => purgeContributionsByPlugin(this.db, pluginId, extensionId),
|
|
8806
|
+
listAllErrors: () => listAllContributionErrors(this.db)
|
|
8428
8807
|
};
|
|
8429
8808
|
this.tags = {
|
|
8430
8809
|
listForNode: (nodePath) => loadTagsForNode(this.db, nodePath),
|
|
@@ -8497,7 +8876,8 @@ async function persistScansThroughNonTx(db, result, opts) {
|
|
|
8497
8876
|
defaults.enrichments,
|
|
8498
8877
|
defaults.contributions,
|
|
8499
8878
|
defaults.registeredContributionKeys,
|
|
8500
|
-
defaults.freshlyRunTuples
|
|
8879
|
+
defaults.freshlyRunTuples,
|
|
8880
|
+
defaults.contributionErrors
|
|
8501
8881
|
);
|
|
8502
8882
|
}
|
|
8503
8883
|
function applyPersistDefaults(opts) {
|
|
@@ -8508,6 +8888,7 @@ function applyPersistDefaults(opts) {
|
|
|
8508
8888
|
contributions: [],
|
|
8509
8889
|
registeredContributionKeys: /* @__PURE__ */ new Set(),
|
|
8510
8890
|
freshlyRunTuples: /* @__PURE__ */ new Set(),
|
|
8891
|
+
contributionErrors: [],
|
|
8511
8892
|
...opts
|
|
8512
8893
|
};
|
|
8513
8894
|
}
|
|
@@ -8672,7 +9053,8 @@ function buildTxSubset(trx) {
|
|
|
8672
9053
|
d.enrichments,
|
|
8673
9054
|
d.contributions,
|
|
8674
9055
|
d.registeredContributionKeys,
|
|
8675
|
-
d.freshlyRunTuples
|
|
9056
|
+
d.freshlyRunTuples,
|
|
9057
|
+
d.contributionErrors
|
|
8676
9058
|
).then(() => void 0);
|
|
8677
9059
|
}
|
|
8678
9060
|
},
|
|
@@ -9167,10 +9549,11 @@ var BumpCommand = class extends SmCommand {
|
|
|
9167
9549
|
* `EConsentRequiredError` thrown by `FilesystemSidecarStore.applyPatch`
|
|
9168
9550
|
* (via `ensureSidecarWritesAllowed`), prompt the operator if stdin is
|
|
9169
9551
|
* a TTY and `--yes` was not passed. On accept, flip `this.yes` to
|
|
9170
|
-
* true and re-run `dispatch` (the second pass passes `
|
|
9171
|
-
* to the store and the gate persists the flag to project-local
|
|
9172
|
-
*
|
|
9173
|
-
*
|
|
9552
|
+
* true and re-run `dispatch` (the second pass passes `always: true`
|
|
9553
|
+
* to the store and the gate persists the flag to project-local, the
|
|
9554
|
+
* CLI's documented "never asked again" contract, Step 17 consent
|
|
9555
|
+
* split). On decline or non-TTY without `--yes`, print a directed
|
|
9556
|
+
* message and return `ExitCode.Error`.
|
|
9174
9557
|
*/
|
|
9175
9558
|
async #runWithConsent(ansi, dispatch) {
|
|
9176
9559
|
try {
|
|
@@ -9269,7 +9652,11 @@ var BumpCommand = class extends SmCommand {
|
|
|
9269
9652
|
async #applyBumpedSingle(item, node, ansi) {
|
|
9270
9653
|
const ctx = defaultRuntimeContext();
|
|
9271
9654
|
const consent = {
|
|
9655
|
+
// Step 17 split: the CLI's accept / `--yes` persists the grant
|
|
9656
|
+
// (its documented "never asked again" contract), so it threads
|
|
9657
|
+
// `always`, not the new one-shot `confirm`.
|
|
9272
9658
|
confirm: this.yes,
|
|
9659
|
+
always: this.yes,
|
|
9273
9660
|
cwd: ctx.cwd
|
|
9274
9661
|
};
|
|
9275
9662
|
const applied = await applyBumpWrites(item, consent);
|
|
@@ -9359,7 +9746,9 @@ var BumpCommand = class extends SmCommand {
|
|
|
9359
9746
|
const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
|
|
9360
9747
|
const ctx = defaultRuntimeContext();
|
|
9361
9748
|
const consent = {
|
|
9749
|
+
// Step 17 split: CLI accept / `--yes` persists (see #runSingle).
|
|
9362
9750
|
confirm: this.yes,
|
|
9751
|
+
always: this.yes,
|
|
9363
9752
|
cwd: ctx.cwd
|
|
9364
9753
|
};
|
|
9365
9754
|
const outcomes = [];
|
|
@@ -9588,16 +9977,23 @@ function readConformanceKillSwitches(env = process.env) {
|
|
|
9588
9977
|
}
|
|
9589
9978
|
|
|
9590
9979
|
// kernel/i18n/plugin-loader.texts.ts
|
|
9980
|
+
var SPEC_GITHUB_BASE = "https://github.com/crystian/skill-map/blob/main";
|
|
9591
9981
|
var PLUGIN_LOADER_TEXTS = {
|
|
9592
9982
|
invalidManifestJsonParse: "{{manifestPath}}: {{errDescription}}. Validate the JSON (e.g. `npx jsonlint plugin.json`).",
|
|
9593
|
-
invalidManifestAjv:
|
|
9983
|
+
invalidManifestAjv: `{{manifestPath}}: {{errors}}. See ${SPEC_GITHUB_BASE}/spec/schemas/plugins-registry.schema.json#/$defs/PluginManifest.`,
|
|
9594
9984
|
invalidSpecCompat: 'specCompat "{{specCompat}}" is not a valid semver range. Use a range like "^1.0.0".',
|
|
9595
9985
|
incompatibleSpec: `@skill-map/spec {{installedSpecVersion}} does not satisfy specCompat "{{specCompat}}". Either update the plugin's specCompat (and re-test) or pin sm to a compatible spec version.`,
|
|
9596
9986
|
loadErrorFileNotFound: "extension file not found: {{relEntry}} (resolved to {{abs}}). Check plugin.json#/extensions paths.",
|
|
9597
9987
|
loadErrorImportFailed: "{{relEntry}}: import failed: {{errDescription}}",
|
|
9598
9988
|
loadErrorMissingKind: "{{relEntry}}: default export missing a string `kind` field. Expected one of: {{knownKindsList}}.",
|
|
9599
9989
|
loadErrorUnknownKind: '{{relEntry}}: unknown extension kind "{{kindReceived}}". Expected one of: {{knownKindsList}}.',
|
|
9600
|
-
|
|
9990
|
+
// No "manifest invalid" framing here: the warning wrapper already
|
|
9991
|
+
// carries the `(invalid-manifest)` status, so this reason is just the
|
|
9992
|
+
// file + the specific error + the doc link. `{{docUrl}}` is chosen by
|
|
9993
|
+
// the caller: a bad view-slot value points to the slot catalog
|
|
9994
|
+
// (`spec/view-slots.md`), every other manifest-shape error to the kind
|
|
9995
|
+
// schema. Both are GitHub blob URLs.
|
|
9996
|
+
invalidManifestExtensionShape: "{{relEntry}}: {{errors}}. See {{docUrl}}.",
|
|
9601
9997
|
importExceededTimeout: "import exceeded {{timeoutMs}}ms; likely a top-level side effect (network call, infinite loop, large blocking work). Move side effects into the runtime methods (`detect` / `evaluate` / `render` / etc.).",
|
|
9602
9998
|
disabledByConfig: "disabled by config_plugins or settings.json",
|
|
9603
9999
|
invalidManifestDirMismatch: "directory name '{{dirName}}' does not match manifest id '{{manifestId}}'. Rename the directory to match the id, or update the manifest id to match the directory.",
|
|
@@ -9929,7 +10325,7 @@ function loadOneProviderKind(opts) {
|
|
|
9929
10325
|
opts.pluginPath,
|
|
9930
10326
|
opts.pluginId,
|
|
9931
10327
|
"invalid-manifest",
|
|
9932
|
-
`Provider kind \`${opts.entry}\` (declared at \`${opts.relEntry}\`) failed validation in \`kinds/${opts.entry}/kind.json\`: ${validation.errors}. See spec/schemas/extensions/provider-kind.schema.json.`
|
|
10328
|
+
`Provider kind \`${opts.entry}\` (declared at \`${opts.relEntry}\`) failed validation in \`kinds/${opts.entry}/kind.json\`: ${validation.errors}. See ${SPEC_GITHUB_BASE}/spec/schemas/extensions/provider-kind.schema.json.`
|
|
9933
10329
|
),
|
|
9934
10330
|
manifest: opts.manifest
|
|
9935
10331
|
}
|
|
@@ -10351,13 +10747,17 @@ var PluginLoader = class {
|
|
|
10351
10747
|
}
|
|
10352
10748
|
const extValidator = this.#options.validators.validatorForExtension(kind);
|
|
10353
10749
|
if (!extValidator(manifestView)) {
|
|
10354
|
-
const errors = (extValidator.errors
|
|
10750
|
+
const errors = formatAjvErrors(extValidator.errors);
|
|
10751
|
+
const slotError = (extValidator.errors ?? []).some(
|
|
10752
|
+
(e) => (e.instancePath ?? "").endsWith("/slot")
|
|
10753
|
+
);
|
|
10754
|
+
const docUrl = slotError ? `${SPEC_GITHUB_BASE}/spec/view-slots.md` : `${SPEC_GITHUB_BASE}/spec/schemas/extensions/${kind}.schema.json`;
|
|
10355
10755
|
return { ok: false, failure: {
|
|
10356
10756
|
...fail(
|
|
10357
10757
|
pluginPath,
|
|
10358
10758
|
pluginId,
|
|
10359
10759
|
"invalid-manifest",
|
|
10360
|
-
tx(PLUGIN_LOADER_TEXTS.invalidManifestExtensionShape, { relEntry,
|
|
10760
|
+
tx(PLUGIN_LOADER_TEXTS.invalidManifestExtensionShape, { relEntry, errors, docUrl })
|
|
10361
10761
|
),
|
|
10362
10762
|
manifest
|
|
10363
10763
|
} };
|
|
@@ -10835,6 +11235,9 @@ function collectViewContributions(pluginId, extensionId, instance, out, options
|
|
|
10835
11235
|
const raw = instance["ui"];
|
|
10836
11236
|
if (typeof raw !== "object" || raw === null) return;
|
|
10837
11237
|
const exclude = options.excludeQualifiedIds;
|
|
11238
|
+
const pluginOrder = options.pluginOrder;
|
|
11239
|
+
const extOrder = instance["order"];
|
|
11240
|
+
const extensionOrder = typeof extOrder === "number" ? extOrder : void 0;
|
|
10838
11241
|
for (const [contributionId, value] of Object.entries(raw)) {
|
|
10839
11242
|
if (typeof value !== "object" || value === null) continue;
|
|
10840
11243
|
const entry = value;
|
|
@@ -10853,13 +11256,15 @@ function collectViewContributions(pluginId, extensionId, instance, out, options
|
|
|
10853
11256
|
...typeof entry.icon === "string" ? { icon: entry.icon } : {},
|
|
10854
11257
|
...typeof entry.emptyText === "string" ? { emptyText: entry.emptyText } : {},
|
|
10855
11258
|
...typeof entry.priority === "number" ? { priority: entry.priority } : {},
|
|
11259
|
+
...typeof pluginOrder === "number" ? { pluginOrder } : {},
|
|
11260
|
+
...typeof extensionOrder === "number" ? { extensionOrder } : {},
|
|
10856
11261
|
emitWhenEmpty: entry.emitWhenEmpty === true
|
|
10857
11262
|
});
|
|
10858
11263
|
}
|
|
10859
11264
|
}
|
|
10860
11265
|
|
|
10861
11266
|
// core/runtime/plugin-runtime/bucketing.ts
|
|
10862
|
-
function bucketLoaded(loaded, runtime) {
|
|
11267
|
+
function bucketLoaded(loaded, runtime, pluginOrder) {
|
|
10863
11268
|
for (const ext of loaded) {
|
|
10864
11269
|
const instance = ext.instance;
|
|
10865
11270
|
if (!isExtensionInstance(instance)) continue;
|
|
@@ -10880,7 +11285,9 @@ function bucketLoaded(loaded, runtime) {
|
|
|
10880
11285
|
...ext.entryPath ? { entry: ext.entryPath } : {}
|
|
10881
11286
|
});
|
|
10882
11287
|
collectAnnotationContributions(ext.pluginId, instance, runtime.annotationContributions);
|
|
10883
|
-
collectViewContributions(ext.pluginId, ext.id, instance, runtime.viewContributions
|
|
11288
|
+
collectViewContributions(ext.pluginId, ext.id, instance, runtime.viewContributions, {
|
|
11289
|
+
...typeof pluginOrder === "number" ? { pluginOrder } : {}
|
|
11290
|
+
});
|
|
10884
11291
|
}
|
|
10885
11292
|
}
|
|
10886
11293
|
function collectAnnotationContributions(pluginId, instance, out) {
|
|
@@ -10926,11 +11333,14 @@ function truncateTail(s, max) {
|
|
|
10926
11333
|
// core/runtime/i18n/plugin-runtime.texts.ts
|
|
10927
11334
|
var PLUGIN_RUNTIME_TEXTS = {
|
|
10928
11335
|
/**
|
|
10929
|
-
* Stderr-ready warning for one non-loaded plugin.
|
|
10930
|
-
*
|
|
10931
|
-
* `
|
|
11336
|
+
* Stderr-ready warning for one non-loaded plugin. The status in parens
|
|
11337
|
+
* stays greppable (`invalid-manifest` / `incompatible-spec` /
|
|
11338
|
+
* `load-error`); `all extensions skipped` states the consequence (the
|
|
11339
|
+
* loader rejects the plugin whole, aborting on the first bad
|
|
11340
|
+
* extension). The reason carries the specifics, so it must NOT restate
|
|
11341
|
+
* the status (no second "manifest invalid").
|
|
10932
11342
|
*/
|
|
10933
|
-
warningRow: "plugin {{id}}
|
|
11343
|
+
warningRow: "plugin {{id}} ({{status}}), all extensions skipped: {{reason}}",
|
|
10934
11344
|
/** Placeholder when a non-loaded plugin record carries no `reason`. */
|
|
10935
11345
|
warningReasonMissing: "(no reason recorded)"
|
|
10936
11346
|
};
|
|
@@ -10992,7 +11402,7 @@ async function loadPluginRuntime(opts = {}) {
|
|
|
10992
11402
|
};
|
|
10993
11403
|
for (const plugin of discovered) {
|
|
10994
11404
|
if (plugin.status === "enabled") {
|
|
10995
|
-
bucketLoaded(plugin.extensions ?? [], runtime);
|
|
11405
|
+
bucketLoaded(plugin.extensions ?? [], runtime, plugin.manifest?.order);
|
|
10996
11406
|
continue;
|
|
10997
11407
|
}
|
|
10998
11408
|
if (plugin.status === "disabled") continue;
|
|
@@ -13330,8 +13740,8 @@ var DbResetCommand = class extends SmCommand {
|
|
|
13330
13740
|
return ExitCode.Ok;
|
|
13331
13741
|
}
|
|
13332
13742
|
const withCounts = rows.map((r) => {
|
|
13333
|
-
const
|
|
13334
|
-
return { name: r.name, rowCount: Number(
|
|
13743
|
+
const count3 = db.prepare(`SELECT COUNT(*) AS c FROM "${r.name}"`).get();
|
|
13744
|
+
return { name: r.name, rowCount: Number(count3.c) };
|
|
13335
13745
|
});
|
|
13336
13746
|
const totalRows = withCounts.reduce((acc, r) => acc + r.rowCount, 0);
|
|
13337
13747
|
const lines = withCounts.map((r) => ` - ${r.name}: ${r.rowCount} row(s)`).join("\n");
|
|
@@ -15148,7 +15558,7 @@ var ORCHESTRATOR_TEXTS = {
|
|
|
15148
15558
|
frontmatterMalformedMissingClose: "Frontmatter in {{path}} opens with `---` but never closes (no matching `---` line at column 0 was found). The file was scanned as body-only and every metadata field was silently lost. Add a closing `---` line below the metadata block.",
|
|
15149
15559
|
extensionErrorLinkKindNotDeclared: 'Extractor "{{extractorId}}" emitted a link of kind "{{linkKind}}" outside its declared `emitsLinkKinds` set [{{declaredKinds}}]. Link dropped.',
|
|
15150
15560
|
extensionErrorIssueInvalidSeverity: `Rule "{{analyzerId}}" emitted an issue with invalid severity {{severity}} (allowed: 'error' | 'warn' | 'info'). Issue dropped.`,
|
|
15151
|
-
|
|
15561
|
+
extensionErrorContributionUndeclaredRef: 'Extension "{{extractorId}}" emitted a view contribution on {{nodePath}} whose object is not one declared in its `ui` map (pass the declared const by reference, do not spread or inline it). Contribution dropped.',
|
|
15152
15562
|
extensionErrorContributionPayloadInvalid: 'Extractor "{{extractorId}}" emitted contribution "{{contributionId}}" on {{nodePath}}; payload failed the "{{slot}}" schema: {{errors}}. Contribution dropped.',
|
|
15153
15563
|
extensionErrorRecommendedActionMissing: 'Analyzer "{{analyzerId}}" declares recommendedAction "{{actionId}}" but no Action is registered under that qualified id. The analyzer stays registered; the recommendation will not surface in the inspector.',
|
|
15154
15564
|
runScanRootEmptyArray: "runScan: roots must contain at least one path (spec requires minItems: 1)",
|
|
@@ -15168,6 +15578,7 @@ async function runExtractorsForNode(opts) {
|
|
|
15168
15578
|
const externalLinks = [];
|
|
15169
15579
|
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
15170
15580
|
const contributions = [];
|
|
15581
|
+
const contributionErrors = [];
|
|
15171
15582
|
const signals = [];
|
|
15172
15583
|
const virtualNodes = [];
|
|
15173
15584
|
const virtualNodePaths = /* @__PURE__ */ new Set();
|
|
@@ -15199,36 +15610,54 @@ async function runExtractorsForNode(opts) {
|
|
|
15199
15610
|
});
|
|
15200
15611
|
}
|
|
15201
15612
|
};
|
|
15202
|
-
const declaredContributions =
|
|
15203
|
-
const emitContribution = (
|
|
15204
|
-
const declared = declaredContributions.get(
|
|
15613
|
+
const declaredContributions = readDeclaredContributionRefs(extractor);
|
|
15614
|
+
const emitContribution = (ref, payload) => {
|
|
15615
|
+
const declared = typeof ref === "object" && ref !== null ? declaredContributions.get(ref) : void 0;
|
|
15205
15616
|
if (!declared) {
|
|
15617
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUndeclaredRef, {
|
|
15618
|
+
extractorId: qualifiedId2,
|
|
15619
|
+
nodePath: opts.node.path
|
|
15620
|
+
});
|
|
15206
15621
|
emitExtensionError(opts.emitter, qualifiedId2, opts.node.path, {
|
|
15207
15622
|
phase: "emitContribution",
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15623
|
+
reason: "undeclared-contribution-ref",
|
|
15624
|
+
message
|
|
15625
|
+
});
|
|
15626
|
+
contributionErrors.push({
|
|
15627
|
+
pluginId: extractor.pluginId,
|
|
15628
|
+
extensionId: extractor.id,
|
|
15629
|
+
nodePath: opts.node.path,
|
|
15630
|
+
reason: "undeclared-contribution-ref",
|
|
15631
|
+
message,
|
|
15632
|
+
emittedAt: Date.now()
|
|
15215
15633
|
});
|
|
15216
15634
|
return;
|
|
15217
15635
|
}
|
|
15218
15636
|
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
15219
15637
|
if (!result.ok) {
|
|
15638
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
15639
|
+
extractorId: qualifiedId2,
|
|
15640
|
+
contributionId: declared.id,
|
|
15641
|
+
nodePath: opts.node.path,
|
|
15642
|
+
slot: declared.slot,
|
|
15643
|
+
errors: result.errors
|
|
15644
|
+
});
|
|
15220
15645
|
emitExtensionError(opts.emitter, qualifiedId2, opts.node.path, {
|
|
15221
15646
|
phase: "emitContribution",
|
|
15222
|
-
contributionId,
|
|
15647
|
+
contributionId: declared.id,
|
|
15223
15648
|
slot: declared.slot,
|
|
15224
15649
|
reason: result.errors,
|
|
15225
|
-
message
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15650
|
+
message
|
|
15651
|
+
});
|
|
15652
|
+
contributionErrors.push({
|
|
15653
|
+
pluginId: extractor.pluginId,
|
|
15654
|
+
extensionId: extractor.id,
|
|
15655
|
+
nodePath: opts.node.path,
|
|
15656
|
+
reason: result.errors,
|
|
15657
|
+
message,
|
|
15658
|
+
contributionId: declared.id,
|
|
15659
|
+
slot: declared.slot,
|
|
15660
|
+
emittedAt: Date.now()
|
|
15232
15661
|
});
|
|
15233
15662
|
return;
|
|
15234
15663
|
}
|
|
@@ -15236,7 +15665,7 @@ async function runExtractorsForNode(opts) {
|
|
|
15236
15665
|
pluginId: extractor.pluginId,
|
|
15237
15666
|
extensionId: extractor.id,
|
|
15238
15667
|
nodePath: opts.node.path,
|
|
15239
|
-
contributionId,
|
|
15668
|
+
contributionId: declared.id,
|
|
15240
15669
|
slot: declared.slot,
|
|
15241
15670
|
payload,
|
|
15242
15671
|
emittedAt: Date.now()
|
|
@@ -15274,11 +15703,12 @@ async function runExtractorsForNode(opts) {
|
|
|
15274
15703
|
externalLinks,
|
|
15275
15704
|
enrichments: Array.from(enrichmentBuffer.values()),
|
|
15276
15705
|
contributions,
|
|
15706
|
+
contributionErrors,
|
|
15277
15707
|
signals,
|
|
15278
15708
|
virtualNodes
|
|
15279
15709
|
};
|
|
15280
15710
|
}
|
|
15281
|
-
function
|
|
15711
|
+
function readDeclaredContributionRefs(extension) {
|
|
15282
15712
|
const out = /* @__PURE__ */ new Map();
|
|
15283
15713
|
const raw = extension.ui;
|
|
15284
15714
|
if (typeof raw !== "object" || raw === null) return out;
|
|
@@ -15286,7 +15716,7 @@ function readDeclaredContributions(extension) {
|
|
|
15286
15716
|
if (typeof value !== "object" || value === null) continue;
|
|
15287
15717
|
const slot = value.slot;
|
|
15288
15718
|
if (typeof slot !== "string") continue;
|
|
15289
|
-
out.set(
|
|
15719
|
+
out.set(value, { id, slot });
|
|
15290
15720
|
}
|
|
15291
15721
|
return out;
|
|
15292
15722
|
}
|
|
@@ -15534,6 +15964,7 @@ function isExternalUrlLink(link) {
|
|
|
15534
15964
|
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals, seedIssues = []) {
|
|
15535
15965
|
const issues = [...seedIssues];
|
|
15536
15966
|
const contributions = [];
|
|
15967
|
+
const contributionErrors = [];
|
|
15537
15968
|
const validators = loadSchemaValidators();
|
|
15538
15969
|
void registeredActionIds;
|
|
15539
15970
|
const analyzerOrphans = orphanSidecars.map((o) => ({
|
|
@@ -15543,36 +15974,54 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
15543
15974
|
const scheduled = orderAnalyzersByPhase(analyzers);
|
|
15544
15975
|
for (const analyzer of scheduled) {
|
|
15545
15976
|
const qualifiedId2 = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
15546
|
-
const declaredContributions =
|
|
15547
|
-
const emitContribution = (nodePath,
|
|
15548
|
-
const declared = declaredContributions.get(
|
|
15977
|
+
const declaredContributions = readDeclaredContributionRefs(analyzer);
|
|
15978
|
+
const emitContribution = (nodePath, ref, payload) => {
|
|
15979
|
+
const declared = typeof ref === "object" && ref !== null ? declaredContributions.get(ref) : void 0;
|
|
15549
15980
|
if (!declared) {
|
|
15981
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUndeclaredRef, {
|
|
15982
|
+
extractorId: qualifiedId2,
|
|
15983
|
+
nodePath
|
|
15984
|
+
});
|
|
15550
15985
|
emitExtensionError(emitter, qualifiedId2, nodePath, {
|
|
15551
15986
|
phase: "emitContribution",
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15987
|
+
reason: "undeclared-contribution-ref",
|
|
15988
|
+
message
|
|
15989
|
+
});
|
|
15990
|
+
contributionErrors.push({
|
|
15991
|
+
pluginId: analyzer.pluginId,
|
|
15992
|
+
extensionId: analyzer.id,
|
|
15993
|
+
nodePath,
|
|
15994
|
+
reason: "undeclared-contribution-ref",
|
|
15995
|
+
message,
|
|
15996
|
+
emittedAt: Date.now()
|
|
15559
15997
|
});
|
|
15560
15998
|
return;
|
|
15561
15999
|
}
|
|
15562
16000
|
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
15563
16001
|
if (!result.ok) {
|
|
16002
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
16003
|
+
extractorId: qualifiedId2,
|
|
16004
|
+
contributionId: declared.id,
|
|
16005
|
+
nodePath,
|
|
16006
|
+
slot: declared.slot,
|
|
16007
|
+
errors: result.errors
|
|
16008
|
+
});
|
|
15564
16009
|
emitExtensionError(emitter, qualifiedId2, nodePath, {
|
|
15565
16010
|
phase: "emitContribution",
|
|
15566
|
-
contributionId,
|
|
16011
|
+
contributionId: declared.id,
|
|
15567
16012
|
slot: declared.slot,
|
|
15568
16013
|
reason: result.errors,
|
|
15569
|
-
message
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
16014
|
+
message
|
|
16015
|
+
});
|
|
16016
|
+
contributionErrors.push({
|
|
16017
|
+
pluginId: analyzer.pluginId,
|
|
16018
|
+
extensionId: analyzer.id,
|
|
16019
|
+
nodePath,
|
|
16020
|
+
reason: result.errors,
|
|
16021
|
+
message,
|
|
16022
|
+
contributionId: declared.id,
|
|
16023
|
+
slot: declared.slot,
|
|
16024
|
+
emittedAt: Date.now()
|
|
15576
16025
|
});
|
|
15577
16026
|
return;
|
|
15578
16027
|
}
|
|
@@ -15580,7 +16029,7 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
15580
16029
|
pluginId: analyzer.pluginId,
|
|
15581
16030
|
extensionId: analyzer.id,
|
|
15582
16031
|
nodePath,
|
|
15583
|
-
contributionId,
|
|
16032
|
+
contributionId: declared.id,
|
|
15584
16033
|
slot: declared.slot,
|
|
15585
16034
|
payload,
|
|
15586
16035
|
emittedAt: Date.now()
|
|
@@ -15613,7 +16062,7 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
15613
16062
|
emitter.emit(evt);
|
|
15614
16063
|
await hookDispatcher.dispatch("analyzer.completed", evt);
|
|
15615
16064
|
}
|
|
15616
|
-
return { issues, contributions };
|
|
16065
|
+
return { issues, contributions, contributionErrors };
|
|
15617
16066
|
}
|
|
15618
16067
|
function orderAnalyzersByPhase(analyzers) {
|
|
15619
16068
|
return analyzers.slice().sort((a, b) => phaseRank(a) - phaseRank(b));
|
|
@@ -16501,6 +16950,7 @@ async function walkAndExtract(opts) {
|
|
|
16501
16950
|
enrichments: [...accum.enrichmentBuffer.values()],
|
|
16502
16951
|
extractorRuns: accum.extractorRuns,
|
|
16503
16952
|
contributions: accum.contributionsBuffer,
|
|
16953
|
+
contributionErrors: accum.contributionErrorsBuffer,
|
|
16504
16954
|
freshlyRunTuples: accum.freshlyRunTuples,
|
|
16505
16955
|
orphanSidecars,
|
|
16506
16956
|
sidecarRoots: accum.sidecarRoots,
|
|
@@ -16517,6 +16967,7 @@ function createWalkAccumulators() {
|
|
|
16517
16967
|
frontmatterIssues: [],
|
|
16518
16968
|
enrichmentBuffer: /* @__PURE__ */ new Map(),
|
|
16519
16969
|
contributionsBuffer: [],
|
|
16970
|
+
contributionErrorsBuffer: [],
|
|
16520
16971
|
freshlyRunTuples: /* @__PURE__ */ new Set(),
|
|
16521
16972
|
extractorRuns: [],
|
|
16522
16973
|
sidecarRoots: /* @__PURE__ */ new Map()
|
|
@@ -16667,7 +17118,11 @@ function mergeExtractResult(extractResult, accum) {
|
|
|
16667
17118
|
accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
16668
17119
|
}
|
|
16669
17120
|
for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
|
|
16670
|
-
for (const
|
|
17121
|
+
for (const e of extractResult.contributionErrors) accum.contributionErrorsBuffer.push(e);
|
|
17122
|
+
mergeVirtualNodes(extractResult.virtualNodes, accum);
|
|
17123
|
+
}
|
|
17124
|
+
function mergeVirtualNodes(virtualNodes, accum) {
|
|
17125
|
+
for (const vn of virtualNodes) {
|
|
16671
17126
|
if (accum.nodes.some((n) => n.path === vn.path)) continue;
|
|
16672
17127
|
accum.nodes.push(vn);
|
|
16673
17128
|
}
|
|
@@ -16943,6 +17398,7 @@ async function dispatchExtractorCompleted(extractors, emitter, hookDispatcher) {
|
|
|
16943
17398
|
}
|
|
16944
17399
|
function mergeAnalyzerEmissions(walked, analyzerResult, analyzers) {
|
|
16945
17400
|
for (const c of analyzerResult.contributions) walked.contributions.push(c);
|
|
17401
|
+
for (const e of analyzerResult.contributionErrors) walked.contributionErrors.push(e);
|
|
16946
17402
|
for (const analyzer of analyzers ?? []) {
|
|
16947
17403
|
if (analyzer.ui === void 0) continue;
|
|
16948
17404
|
for (const node of walked.nodes) {
|
|
@@ -16988,6 +17444,7 @@ function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
|
|
|
16988
17444
|
extractorRuns: walked.extractorRuns,
|
|
16989
17445
|
enrichments: walked.enrichments,
|
|
16990
17446
|
contributions: walked.contributions,
|
|
17447
|
+
contributionErrors: walked.contributionErrors,
|
|
16991
17448
|
freshlyRunTuples: walked.freshlyRunTuples
|
|
16992
17449
|
};
|
|
16993
17450
|
}
|
|
@@ -18017,6 +18474,7 @@ async function runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanW
|
|
|
18017
18474
|
extractorRuns: scanned.extractorRuns,
|
|
18018
18475
|
enrichments: scanned.enrichments,
|
|
18019
18476
|
contributions: scanned.contributions,
|
|
18477
|
+
contributionErrors: scanned.contributionErrors,
|
|
18020
18478
|
registeredContributionKeys: collectRegisteredContributionKeys(extensions),
|
|
18021
18479
|
freshlyRunTuples: scanned.freshlyRunTuples
|
|
18022
18480
|
});
|
|
@@ -18873,11 +19331,11 @@ function renderStatsFailures(failures, ansi) {
|
|
|
18873
19331
|
tx(HISTORY_TEXTS.statsSectionHeader, { title: HISTORY_TEXTS.statsSectionTitleFailures })
|
|
18874
19332
|
];
|
|
18875
19333
|
const reasonWidth = Math.max(...failures.map(([reason]) => reason.length));
|
|
18876
|
-
for (const [reason,
|
|
19334
|
+
for (const [reason, count3] of failures) {
|
|
18877
19335
|
lines.push(
|
|
18878
19336
|
tx(HISTORY_TEXTS.statsFailuresRow, {
|
|
18879
19337
|
reason: sanitizeForTerminal(reason).padEnd(reasonWidth),
|
|
18880
|
-
count: ansi.red(String(
|
|
19338
|
+
count: ansi.red(String(count3))
|
|
18881
19339
|
})
|
|
18882
19340
|
);
|
|
18883
19341
|
}
|
|
@@ -19579,14 +20037,14 @@ var OrphansReconcileCommand = class extends SmCommand {
|
|
|
19579
20037
|
tx(ORPHANS_TEXTS.reconcileSuccessBody, { breakdown: ansi.dim(breakdown) })
|
|
19580
20038
|
);
|
|
19581
20039
|
if (summary.collisions.length > 0) {
|
|
19582
|
-
const
|
|
20040
|
+
const count3 = summary.collisions.length;
|
|
19583
20041
|
this.printer.info(
|
|
19584
20042
|
tx(
|
|
19585
20043
|
dryRun ? ORPHANS_TEXTS.reconcileCollisionsNoteDryRun : ORPHANS_TEXTS.reconcileCollisionsNote,
|
|
19586
20044
|
{
|
|
19587
20045
|
glyph: ansi.yellow("\u26A0"),
|
|
19588
|
-
count,
|
|
19589
|
-
plural:
|
|
20046
|
+
count: count3,
|
|
20047
|
+
plural: count3 === 1 ? "" : "s"
|
|
19590
20048
|
}
|
|
19591
20049
|
)
|
|
19592
20050
|
);
|
|
@@ -19903,6 +20361,23 @@ var PLUGINS_TEXTS = {
|
|
|
19903
20361
|
doctorIssuesHeader: "\n Issues ({{count}})\n",
|
|
19904
20362
|
doctorIssueEntry: " {{glyph}} {{id}} {{status}}\n",
|
|
19905
20363
|
doctorIssueBody: " {{line}}\n",
|
|
20364
|
+
// --- runtime contribution errors (last scan) -------------------------
|
|
20365
|
+
/**
|
|
20366
|
+
* "off-shape visible" follow-up. Section heading for view
|
|
20367
|
+
* contributions the last scan REJECTED at emit time (undeclared ref,
|
|
20368
|
+
* or payload failed the slot's AJV schema). Rendered only when at
|
|
20369
|
+
* least one error was persisted; promotes the exit code to 1.
|
|
20370
|
+
* `count` is the total error row count across every plugin.
|
|
20371
|
+
*/
|
|
20372
|
+
doctorContribErrorsHeader: "\n Runtime contribution errors (last scan) ({{count}})\n",
|
|
20373
|
+
/** Per-plugin group header: red glyph + plugin id + this plugin's error count. */
|
|
20374
|
+
doctorContribErrorEntry: " {{glyph}} {{pluginId}} ({{count}})\n",
|
|
20375
|
+
/** Sample line under a plugin group: wrapped, dimmed diagnostic message. */
|
|
20376
|
+
doctorContribErrorBody: " {{line}}\n",
|
|
20377
|
+
/** Trailing dimmed note when a plugin has more errors than the sample cap shows. */
|
|
20378
|
+
doctorContribErrorMore: " {{line}}\n",
|
|
20379
|
+
/** Body of the "more" note: the count of samples omitted under this plugin. */
|
|
20380
|
+
doctorContribErrorMoreText: "... and {{count}} more",
|
|
19906
20381
|
// --- enable / disable -----------------------------------------------
|
|
19907
20382
|
/**
|
|
19908
20383
|
* §3.1b two-line block. Mutex between explicit ids and `--all`; the
|
|
@@ -20406,7 +20881,7 @@ function sortExtensionsCanonical(exts) {
|
|
|
20406
20881
|
}
|
|
20407
20882
|
function renderBuiltInDetail(b, ansi) {
|
|
20408
20883
|
const glyph = b.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
20409
|
-
const
|
|
20884
|
+
const count3 = b.extensions.length;
|
|
20410
20885
|
const sorted = sortExtensionsCanonical(b.extensions);
|
|
20411
20886
|
const items = sorted.map((ext) => ({
|
|
20412
20887
|
glyph: ext.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff),
|
|
@@ -20417,8 +20892,8 @@ function renderBuiltInDetail(b, ansi) {
|
|
|
20417
20892
|
glyph,
|
|
20418
20893
|
id: b.id,
|
|
20419
20894
|
source: ansi.dim(PLUGINS_TEXTS.sourceBuiltIn),
|
|
20420
|
-
count,
|
|
20421
|
-
plural:
|
|
20895
|
+
count: count3,
|
|
20896
|
+
plural: count3 === 1 ? "" : "s"
|
|
20422
20897
|
}) + PLUGINS_TEXTS.detailExtensionsBlock + renderExtensionItems(items);
|
|
20423
20898
|
}
|
|
20424
20899
|
function renderPluginDetail(match, ansi) {
|
|
@@ -20600,6 +21075,7 @@ function renderExtensionFields(meta) {
|
|
|
20600
21075
|
|
|
20601
21076
|
// cli/commands/plugins/doctor.ts
|
|
20602
21077
|
import { Command as Command24, Option as Option23 } from "clipanion";
|
|
21078
|
+
var CONTRIB_ERROR_SAMPLE_CAP = 3;
|
|
20603
21079
|
var STATUS_ORDER = [
|
|
20604
21080
|
"enabled",
|
|
20605
21081
|
"disabled",
|
|
@@ -20625,6 +21101,8 @@ var PluginsDoctorCommand = class extends SmCommand {
|
|
|
20625
21101
|
const knownKinds = collectKnownKinds(plugins);
|
|
20626
21102
|
const applicableKindWarnings = collectApplicableKindWarnings(plugins, knownKinds);
|
|
20627
21103
|
const unknownSlotWarnings = collectUnknownSlotWarnings(plugins, KNOWN_SLOT_NAMES);
|
|
21104
|
+
const contribErrors = await loadContributionErrors();
|
|
21105
|
+
const contribErrorGroups = groupContributionErrorsByPlugin(contribErrors);
|
|
20628
21106
|
const bad = plugins.filter((p) => p.status !== "enabled" && p.status !== "disabled");
|
|
20629
21107
|
const totalWarnings = applicableKindWarnings.length + unknownSlotWarnings.length;
|
|
20630
21108
|
if (this.json) {
|
|
@@ -20634,23 +21112,51 @@ var PluginsDoctorCommand = class extends SmCommand {
|
|
|
20634
21112
|
applicableKindWarnings,
|
|
20635
21113
|
unknownSlotWarnings,
|
|
20636
21114
|
totalWarnings,
|
|
21115
|
+
contribErrors,
|
|
20637
21116
|
elapsedMs: this.elapsed.ms()
|
|
20638
21117
|
});
|
|
20639
21118
|
this.printer.data(JSON.stringify(envelope) + "\n");
|
|
20640
|
-
return bad.length > 0 ? ExitCode.Issues : ExitCode.Ok;
|
|
20641
|
-
}
|
|
21119
|
+
return bad.length > 0 || contribErrors.length > 0 ? ExitCode.Issues : ExitCode.Ok;
|
|
21120
|
+
}
|
|
21121
|
+
this.#renderHumanReport({
|
|
21122
|
+
counts,
|
|
21123
|
+
builtInCount: builtIns2.length,
|
|
21124
|
+
userCount: plugins.length,
|
|
21125
|
+
applicableKindWarnings,
|
|
21126
|
+
unknownSlotWarnings,
|
|
21127
|
+
totalWarnings,
|
|
21128
|
+
bad,
|
|
21129
|
+
contribErrorGroups,
|
|
21130
|
+
contribErrorCount: contribErrors.length
|
|
21131
|
+
});
|
|
21132
|
+
return bad.length > 0 || contribErrors.length > 0 ? ExitCode.Issues : ExitCode.Ok;
|
|
21133
|
+
}
|
|
21134
|
+
/**
|
|
21135
|
+
* Render the full human-mode report in section order: summary header,
|
|
21136
|
+
* source + status tables, then the gated warnings / issues / runtime
|
|
21137
|
+
* contribution-error sections. Pulled out of `run` so the verb body
|
|
21138
|
+
* stays a linear pipeline (load → aggregate → render → exit code)
|
|
21139
|
+
* under the complexity cap.
|
|
21140
|
+
*/
|
|
21141
|
+
#renderHumanReport(args2) {
|
|
20642
21142
|
const ansi = this.ansiFor("stdout");
|
|
20643
|
-
this.#renderSummaryHeader(counts.enabled, bad.length, totalWarnings);
|
|
20644
|
-
this.#renderSourceBreakdown(
|
|
20645
|
-
this.#renderStatusBreakdown(counts, ansi);
|
|
20646
|
-
if (totalWarnings > 0) {
|
|
20647
|
-
this.#renderWarnings(
|
|
21143
|
+
this.#renderSummaryHeader(args2.counts.enabled, args2.bad.length, args2.totalWarnings);
|
|
21144
|
+
this.#renderSourceBreakdown(args2.builtInCount, args2.userCount);
|
|
21145
|
+
this.#renderStatusBreakdown(args2.counts, ansi);
|
|
21146
|
+
if (args2.totalWarnings > 0) {
|
|
21147
|
+
this.#renderWarnings(
|
|
21148
|
+
args2.applicableKindWarnings,
|
|
21149
|
+
args2.unknownSlotWarnings,
|
|
21150
|
+
args2.totalWarnings,
|
|
21151
|
+
ansi
|
|
21152
|
+
);
|
|
20648
21153
|
}
|
|
20649
|
-
if (bad.length > 0) {
|
|
20650
|
-
this.#renderIssues(bad, ansi);
|
|
20651
|
-
|
|
21154
|
+
if (args2.bad.length > 0) {
|
|
21155
|
+
this.#renderIssues(args2.bad, ansi);
|
|
21156
|
+
}
|
|
21157
|
+
if (args2.contribErrorCount > 0) {
|
|
21158
|
+
this.#renderContributionErrors(args2.contribErrorGroups, args2.contribErrorCount, ansi);
|
|
20652
21159
|
}
|
|
20653
|
-
return ExitCode.Ok;
|
|
20654
21160
|
}
|
|
20655
21161
|
#renderSummaryHeader(enabled, badCount, warnings) {
|
|
20656
21162
|
this.printer.data(
|
|
@@ -20687,10 +21193,10 @@ var PluginsDoctorCommand = class extends SmCommand {
|
|
|
20687
21193
|
const statusLabelWidth = Math.max(...STATUS_ORDER.map((s) => s.length));
|
|
20688
21194
|
this.printer.data(PLUGINS_TEXTS.doctorStatusHeader);
|
|
20689
21195
|
for (const status of STATUS_ORDER) {
|
|
20690
|
-
const
|
|
20691
|
-
const isProblem = status !== "enabled" && status !== "disabled" &&
|
|
21196
|
+
const count3 = counts[status];
|
|
21197
|
+
const isProblem = status !== "enabled" && status !== "disabled" && count3 > 0;
|
|
20692
21198
|
const label = status.padEnd(statusLabelWidth);
|
|
20693
|
-
const formattedCount = isProblem ? ansi.red(String(
|
|
21199
|
+
const formattedCount = isProblem ? ansi.red(String(count3)) : String(count3);
|
|
20694
21200
|
this.printer.data(
|
|
20695
21201
|
tx(PLUGINS_TEXTS.doctorStatusRow, {
|
|
20696
21202
|
label: isProblem ? ansi.red(label) : label,
|
|
@@ -20753,6 +21259,42 @@ var PluginsDoctorCommand = class extends SmCommand {
|
|
|
20753
21259
|
}
|
|
20754
21260
|
}
|
|
20755
21261
|
}
|
|
21262
|
+
/**
|
|
21263
|
+
* "off-shape visible" follow-up. Render the last scan's runtime
|
|
21264
|
+
* contribution rejections grouped by plugin: one red entry per plugin
|
|
21265
|
+
* (id + this plugin's error count), then up to
|
|
21266
|
+
* `CONTRIB_ERROR_SAMPLE_CAP` wrapped sample messages, then a dimmed
|
|
21267
|
+
* "... and N more" note when the group overflows the cap. The full
|
|
21268
|
+
* set is always available via `--json`.
|
|
21269
|
+
*/
|
|
21270
|
+
#renderContributionErrors(groups, total, ansi) {
|
|
21271
|
+
this.printer.data(tx(PLUGINS_TEXTS.doctorContribErrorsHeader, { count: total }));
|
|
21272
|
+
const glyph = ansi.red(PLUGINS_TEXTS.rowGlyphOff);
|
|
21273
|
+
for (const group of groups) {
|
|
21274
|
+
this.printer.data(
|
|
21275
|
+
tx(PLUGINS_TEXTS.doctorContribErrorEntry, {
|
|
21276
|
+
glyph,
|
|
21277
|
+
pluginId: sanitizeForTerminal(group.pluginId),
|
|
21278
|
+
count: group.errors.length
|
|
21279
|
+
})
|
|
21280
|
+
);
|
|
21281
|
+
for (const err of group.errors.slice(0, CONTRIB_ERROR_SAMPLE_CAP)) {
|
|
21282
|
+
for (const line of wrapText(sanitizeForTerminal(err.message), 64)) {
|
|
21283
|
+
this.printer.data(
|
|
21284
|
+
tx(PLUGINS_TEXTS.doctorContribErrorBody, { line: ansi.dim(line) })
|
|
21285
|
+
);
|
|
21286
|
+
}
|
|
21287
|
+
}
|
|
21288
|
+
const hidden = group.errors.length - CONTRIB_ERROR_SAMPLE_CAP;
|
|
21289
|
+
if (hidden > 0) {
|
|
21290
|
+
this.printer.data(
|
|
21291
|
+
tx(PLUGINS_TEXTS.doctorContribErrorMore, {
|
|
21292
|
+
line: ansi.dim(tx(PLUGINS_TEXTS.doctorContribErrorMoreText, { count: hidden }))
|
|
21293
|
+
})
|
|
21294
|
+
);
|
|
21295
|
+
}
|
|
21296
|
+
}
|
|
21297
|
+
}
|
|
20756
21298
|
};
|
|
20757
21299
|
function countByStatus(builtIns2, plugins, resolveEnabled) {
|
|
20758
21300
|
const counts = {
|
|
@@ -20949,6 +21491,15 @@ function buildDoctorJsonEnvelope(args2) {
|
|
|
20949
21491
|
})
|
|
20950
21492
|
});
|
|
20951
21493
|
}
|
|
21494
|
+
const contributionErrors = args2.contribErrors.map((e) => ({
|
|
21495
|
+
pluginId: sanitizeForTerminal(e.pluginId),
|
|
21496
|
+
extensionId: sanitizeForTerminal(e.extensionId),
|
|
21497
|
+
nodePath: sanitizeForTerminal(e.nodePath),
|
|
21498
|
+
reason: sanitizeForTerminal(e.reason),
|
|
21499
|
+
message: sanitizeForTerminal(e.message),
|
|
21500
|
+
...e.contributionId !== void 0 ? { contributionId: sanitizeForTerminal(e.contributionId) } : {},
|
|
21501
|
+
...e.slot !== void 0 ? { slot: sanitizeForTerminal(e.slot) } : {}
|
|
21502
|
+
}));
|
|
20952
21503
|
return {
|
|
20953
21504
|
ok: true,
|
|
20954
21505
|
kind: "plugins.doctor",
|
|
@@ -20963,9 +21514,35 @@ function buildDoctorJsonEnvelope(args2) {
|
|
|
20963
21514
|
},
|
|
20964
21515
|
issues,
|
|
20965
21516
|
warnings,
|
|
21517
|
+
contributionErrors,
|
|
20966
21518
|
elapsedMs: args2.elapsedMs
|
|
20967
21519
|
};
|
|
20968
21520
|
}
|
|
21521
|
+
async function loadContributionErrors() {
|
|
21522
|
+
const ctx = defaultRuntimeContext();
|
|
21523
|
+
const dbPath = resolveDbPath({ db: void 0, cwd: ctx.cwd });
|
|
21524
|
+
try {
|
|
21525
|
+
const rows = await tryWithSqlite(
|
|
21526
|
+
{ databasePath: dbPath, autoBackup: false },
|
|
21527
|
+
(adapter) => adapter.contributions.listAllErrors()
|
|
21528
|
+
);
|
|
21529
|
+
return rows ?? [];
|
|
21530
|
+
} catch {
|
|
21531
|
+
return [];
|
|
21532
|
+
}
|
|
21533
|
+
}
|
|
21534
|
+
function groupContributionErrorsByPlugin(errors) {
|
|
21535
|
+
const byPlugin = /* @__PURE__ */ new Map();
|
|
21536
|
+
for (const err of errors) {
|
|
21537
|
+
let group = byPlugin.get(err.pluginId);
|
|
21538
|
+
if (!group) {
|
|
21539
|
+
group = { pluginId: err.pluginId, errors: [] };
|
|
21540
|
+
byPlugin.set(err.pluginId, group);
|
|
21541
|
+
}
|
|
21542
|
+
group.errors.push(err);
|
|
21543
|
+
}
|
|
21544
|
+
return [...byPlugin.values()];
|
|
21545
|
+
}
|
|
20969
21546
|
|
|
20970
21547
|
// cli/commands/plugins/toggle.ts
|
|
20971
21548
|
import { Command as Command25, Option as Option24 } from "clipanion";
|
|
@@ -21481,9 +22058,22 @@ function stub2(extId) {
|
|
|
21481
22058
|
* Declared settings (\`settings\`):
|
|
21482
22059
|
* - 'keywords' (${DEFAULT_INPUT_TYPE}) \u2192 exposed as ctx.settings.keywords
|
|
21483
22060
|
*
|
|
22061
|
+
* View contributions: declare each one as a const, list it in \`ui\` by
|
|
22062
|
+
* shorthand, and pass that SAME const (by reference, not a string id) to
|
|
22063
|
+
* \`ctx.emitContribution\`. The kernel recovers the contribution from the
|
|
22064
|
+
* object identity. (Write the plugin in TypeScript and add
|
|
22065
|
+
* \`satisfies IViewContribution\` from '@skill-map/cli' to get a typed payload.)
|
|
22066
|
+
*
|
|
21484
22067
|
* See: spec/plugin-author-guide.md \xA7View contributions
|
|
21485
22068
|
* spec/view-slots.md
|
|
21486
22069
|
*/
|
|
22070
|
+
const count = {
|
|
22071
|
+
slot: '${DEFAULT_SLOT}',
|
|
22072
|
+
icon: '\u{1F50D}',
|
|
22073
|
+
label: 'kw',
|
|
22074
|
+
emitWhenEmpty: false,
|
|
22075
|
+
};
|
|
22076
|
+
|
|
21487
22077
|
export default {
|
|
21488
22078
|
version: '0.1.0',
|
|
21489
22079
|
description: 'Counts configured keywords per node.',
|
|
@@ -21499,14 +22089,7 @@ export default {
|
|
|
21499
22089
|
},
|
|
21500
22090
|
},
|
|
21501
22091
|
|
|
21502
|
-
ui: {
|
|
21503
|
-
count: {
|
|
21504
|
-
slot: '${DEFAULT_SLOT}',
|
|
21505
|
-
icon: '\u{1F50D}',
|
|
21506
|
-
label: 'kw',
|
|
21507
|
-
emitWhenEmpty: false,
|
|
21508
|
-
},
|
|
21509
|
-
},
|
|
22092
|
+
ui: { count },
|
|
21510
22093
|
|
|
21511
22094
|
extract(ctx) {
|
|
21512
22095
|
const keywords = (ctx.settings && ctx.settings.keywords) || ['TODO', 'FIXME'];
|
|
@@ -21516,7 +22099,7 @@ export default {
|
|
|
21516
22099
|
total += (ctx.body.match(re) || []).length;
|
|
21517
22100
|
}
|
|
21518
22101
|
if (total > 0) {
|
|
21519
|
-
ctx.emitContribution(
|
|
22102
|
+
ctx.emitContribution(count, { value: total });
|
|
21520
22103
|
}
|
|
21521
22104
|
},
|
|
21522
22105
|
};
|
|
@@ -21810,8 +22393,8 @@ var VIEW_SLOTS_CATALOG = [
|
|
|
21810
22393
|
{ id: "card.footer.left", summary: "Counter chip in the left footer of the card." },
|
|
21811
22394
|
{ id: "card.footer.right", summary: "Counter chip in the right footer of the card." },
|
|
21812
22395
|
{ id: "graph.node.alert", summary: 'Reserved corner badge on the graph node, special-case signals only. No core analyzer emits here; routine "this node has a problem" findings belong in `card.footer.right`.' },
|
|
21813
|
-
{ id: "inspector.header.badge
|
|
21814
|
-
{ id: "inspector.
|
|
22396
|
+
{ id: "inspector.header.badge", summary: "Unified badge in the inspector header cluster: icon and/or label and/or count, optional severity. Multi-cardinality, priority order, modeled on card.footer.left. Replaces the retired inspector.header.badge.counter and inspector.header.badge.tag sub-slots." },
|
|
22397
|
+
{ id: "inspector.action.button", summary: "Action button in the inspector. Click dispatches a kernel Action by qualified id via POST /api/actions/:id. Always emitted; the payload `enabled` flag carries the dynamic condition (e.g. isStale for the bump button)." },
|
|
21815
22398
|
{ id: "inspector.body.panel.breakdown", summary: "Top-N labeled values rendered as a bar chart in the inspector body." },
|
|
21816
22399
|
{ id: "inspector.body.panel.records", summary: "Tabular data (rows \xD7 columns \u2264 50 \xD7 6) in the inspector body." },
|
|
21817
22400
|
{ id: "inspector.body.panel.tree", summary: "Recursive label/children hierarchy (depth \u2264 6, total \u2264 200) in the inspector body." },
|
|
@@ -22109,15 +22692,15 @@ var RefreshCommand = class extends SmCommand {
|
|
|
22109
22692
|
return ExitCode.Ok;
|
|
22110
22693
|
}
|
|
22111
22694
|
const glyph = ansi.green("\u2713");
|
|
22112
|
-
const
|
|
22113
|
-
const noun =
|
|
22695
|
+
const count3 = freshEnrichments.length;
|
|
22696
|
+
const noun = count3 === 1 ? REFRESH_TEXTS.refreshNounSingular : REFRESH_TEXTS.refreshNounPlural;
|
|
22114
22697
|
if (this.stale) {
|
|
22115
22698
|
const nodeCount = targetNodes.length;
|
|
22116
22699
|
const nodeNoun = nodeCount === 1 ? REFRESH_TEXTS.refreshNodeNounSingular : REFRESH_TEXTS.refreshNodeNounPlural;
|
|
22117
22700
|
this.printer.data(
|
|
22118
22701
|
tx(REFRESH_TEXTS.refreshSuccessStale, {
|
|
22119
22702
|
glyph,
|
|
22120
|
-
count,
|
|
22703
|
+
count: count3,
|
|
22121
22704
|
noun,
|
|
22122
22705
|
nodeCount,
|
|
22123
22706
|
nodeNoun
|
|
@@ -22127,7 +22710,7 @@ var RefreshCommand = class extends SmCommand {
|
|
|
22127
22710
|
this.printer.data(
|
|
22128
22711
|
tx(REFRESH_TEXTS.refreshSuccessSingle, {
|
|
22129
22712
|
glyph,
|
|
22130
|
-
count,
|
|
22713
|
+
count: count3,
|
|
22131
22714
|
noun,
|
|
22132
22715
|
nodePath: this.nodePath
|
|
22133
22716
|
})
|
|
@@ -22604,7 +23187,15 @@ function createWatcherRuntime(opts) {
|
|
|
22604
23187
|
runOptions.priorExtractorRuns = priorState.extractorRuns;
|
|
22605
23188
|
}
|
|
22606
23189
|
const ran = await runScanWithRenames(kernel, runOptions);
|
|
22607
|
-
const {
|
|
23190
|
+
const {
|
|
23191
|
+
result,
|
|
23192
|
+
renameOps,
|
|
23193
|
+
extractorRuns,
|
|
23194
|
+
enrichments,
|
|
23195
|
+
contributions,
|
|
23196
|
+
contributionErrors,
|
|
23197
|
+
freshlyRunTuples
|
|
23198
|
+
} = ran;
|
|
22608
23199
|
await withSqlite(
|
|
22609
23200
|
{ databasePath: opts.dbPath },
|
|
22610
23201
|
(writer) => writer.scans.persist(result, {
|
|
@@ -22612,6 +23203,7 @@ function createWatcherRuntime(opts) {
|
|
|
22612
23203
|
extractorRuns,
|
|
22613
23204
|
enrichments,
|
|
22614
23205
|
contributions,
|
|
23206
|
+
contributionErrors,
|
|
22615
23207
|
registeredContributionKeys: collectRegisteredContributionKeys(composed),
|
|
22616
23208
|
freshlyRunTuples
|
|
22617
23209
|
})
|
|
@@ -22940,11 +23532,11 @@ async function runWatchLoop(opts) {
|
|
|
22940
23532
|
}
|
|
22941
23533
|
void info;
|
|
22942
23534
|
},
|
|
22943
|
-
onBreakerTripped: (
|
|
23535
|
+
onBreakerTripped: (count3, message) => {
|
|
22944
23536
|
context.stderr.write(
|
|
22945
23537
|
tx(WATCH_TEXTS.breakerTripped, {
|
|
22946
23538
|
glyph: errGlyph,
|
|
22947
|
-
count,
|
|
23539
|
+
count: count3,
|
|
22948
23540
|
hint: stderrAnsi.dim(tx(WATCH_TEXTS.breakerTrippedHint, { message }))
|
|
22949
23541
|
})
|
|
22950
23542
|
);
|
|
@@ -23486,8 +24078,8 @@ function formatScanCounts(opts) {
|
|
|
23486
24078
|
}
|
|
23487
24079
|
return parts.join(" \xB7 ");
|
|
23488
24080
|
}
|
|
23489
|
-
function countNoun(
|
|
23490
|
-
return
|
|
24081
|
+
function countNoun(count3, singular, plural) {
|
|
24082
|
+
return count3 === 1 ? singular : plural;
|
|
23491
24083
|
}
|
|
23492
24084
|
|
|
23493
24085
|
// cli/commands/scan-compare.ts
|
|
@@ -23769,7 +24361,7 @@ import { WebSocketServer } from "ws";
|
|
|
23769
24361
|
// server/app.ts
|
|
23770
24362
|
import { Hono } from "hono";
|
|
23771
24363
|
import { bodyLimit } from "hono/body-limit";
|
|
23772
|
-
import { HTTPException as
|
|
24364
|
+
import { HTTPException as HTTPException17 } from "hono/http-exception";
|
|
23773
24365
|
|
|
23774
24366
|
// core/config/service.ts
|
|
23775
24367
|
var ConfigService = class {
|
|
@@ -23932,33 +24524,33 @@ var SERVER_TEXTS = {
|
|
|
23932
24524
|
// Hono's `app.notFound` fallback, every other unmatched path funnels
|
|
23933
24525
|
// here (after static + SPA fallback have had their turn).
|
|
23934
24526
|
unknownPath: "Not found: {{path}}.",
|
|
23935
|
-
// ----
|
|
23936
|
-
//
|
|
23937
|
-
//
|
|
23938
|
-
//
|
|
23939
|
-
//
|
|
23940
|
-
|
|
24527
|
+
// ---- generic action-dispatch route (routes/actions.ts, Step 17) ----------
|
|
24528
|
+
//
|
|
24529
|
+
// POST /api/actions/:qualifiedId. Generalises the legacy bump route:
|
|
24530
|
+
// resolve the qualified action id off the kernel registry, invoke it,
|
|
24531
|
+
// materialise any sidecar writes through the same consent-gated store,
|
|
24532
|
+
// broadcast `action.applied`. Each error keeps its own message key so
|
|
24533
|
+
// the UI / log can disambiguate without regex on the message.
|
|
23941
24534
|
// 400 envelopes thrown by `parseBody` when the request payload is
|
|
23942
|
-
// malformed.
|
|
23943
|
-
|
|
23944
|
-
|
|
23945
|
-
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
23949
|
-
|
|
23950
|
-
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
23956
|
-
|
|
23957
|
-
//
|
|
23958
|
-
//
|
|
23959
|
-
//
|
|
23960
|
-
|
|
23961
|
-
sidecarBumpInvokeMissing: "built-in bump action is missing its invoke().",
|
|
24535
|
+
// malformed.
|
|
24536
|
+
actionBodyNotJson: "Request body must be valid JSON.",
|
|
24537
|
+
actionBodyNotObject: "Request body must be a JSON object.",
|
|
24538
|
+
actionNodePathRequired: "`nodePath` is required and must be a non-empty string.",
|
|
24539
|
+
actionInputMustBeObject: "`input` must be an object when present.",
|
|
24540
|
+
actionConfirmMustBeBoolean: "`confirm` must be a boolean when present.",
|
|
24541
|
+
actionAlwaysMustBeBoolean: "`always` must be a boolean when present.",
|
|
24542
|
+
// 404 envelope when `:qualifiedId` does not resolve to a registered
|
|
24543
|
+
// action with an `invoke()`. Covers both "no such action" and "action
|
|
24544
|
+
// exists but ships no deterministic entry point" (a probabilistic
|
|
24545
|
+
// action that cannot be dispatched over this synchronous route). The
|
|
24546
|
+
// id is sanitised before interpolation.
|
|
24547
|
+
actionUnknown: 'No invokable action with id "{{actionId}}".',
|
|
24548
|
+
// 409 envelope when an action's report comes back `ok: false`. The
|
|
24549
|
+
// refusal `reason` (when the report carries one) becomes the envelope
|
|
24550
|
+
// `code`; the fallback `action-refused` covers reports that refuse
|
|
24551
|
+
// without naming a reason. Dispatch is via the typed
|
|
24552
|
+
// `ActionRefusedError`; the message is informational only.
|
|
24553
|
+
actionRefused: 'Action "{{actionId}}" refused to run on "{{nodePath}}".',
|
|
23962
24554
|
// ---- POST /api/scan (manual refresh) ------------------------------------
|
|
23963
24555
|
// 400, runtime cannot persist a meaningful scan because the boot
|
|
23964
24556
|
// dropped half the pipeline. Same gate the `?fresh=1` GET applies.
|
|
@@ -25029,6 +25621,8 @@ function registerPluginsRoute(app, deps) {
|
|
|
25029
25621
|
app.get("/api/plugins", async (c) => {
|
|
25030
25622
|
const resolveEnabled = await buildFreshResolver2(deps);
|
|
25031
25623
|
const items = listItems(deps, resolveEnabled);
|
|
25624
|
+
const errorsByPlugin = await loadRuntimeContributionErrors(deps);
|
|
25625
|
+
attachRuntimeContributionErrors(items, errorsByPlugin);
|
|
25032
25626
|
return c.json(
|
|
25033
25627
|
buildListEnvelope({
|
|
25034
25628
|
kind: "plugins",
|
|
@@ -25203,6 +25797,41 @@ function firstVersion(extensions) {
|
|
|
25203
25797
|
function classifyPluginSource(_pluginPath, _deps) {
|
|
25204
25798
|
return "project";
|
|
25205
25799
|
}
|
|
25800
|
+
async function loadRuntimeContributionErrors(deps) {
|
|
25801
|
+
try {
|
|
25802
|
+
const rows = await tryWithSqlite(
|
|
25803
|
+
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
25804
|
+
(adapter) => adapter.contributions.listAllErrors()
|
|
25805
|
+
);
|
|
25806
|
+
if (rows === null) return /* @__PURE__ */ new Map();
|
|
25807
|
+
return groupContributionErrorsByPlugin2(rows);
|
|
25808
|
+
} catch {
|
|
25809
|
+
return /* @__PURE__ */ new Map();
|
|
25810
|
+
}
|
|
25811
|
+
}
|
|
25812
|
+
function groupContributionErrorsByPlugin2(rows) {
|
|
25813
|
+
const out = /* @__PURE__ */ new Map();
|
|
25814
|
+
for (const row of rows) {
|
|
25815
|
+
const projected = {
|
|
25816
|
+
extensionId: row.extensionId,
|
|
25817
|
+
nodePath: row.nodePath,
|
|
25818
|
+
reason: row.reason,
|
|
25819
|
+
message: row.message,
|
|
25820
|
+
...row.contributionId !== void 0 ? { contributionId: row.contributionId } : {},
|
|
25821
|
+
...row.slot !== void 0 ? { slot: row.slot } : {}
|
|
25822
|
+
};
|
|
25823
|
+
const list = out.get(row.pluginId);
|
|
25824
|
+
if (list) list.push(projected);
|
|
25825
|
+
else out.set(row.pluginId, [projected]);
|
|
25826
|
+
}
|
|
25827
|
+
return out;
|
|
25828
|
+
}
|
|
25829
|
+
function attachRuntimeContributionErrors(items, errorsByPlugin) {
|
|
25830
|
+
for (const item of items) {
|
|
25831
|
+
const errors = errorsByPlugin.get(item.id);
|
|
25832
|
+
if (errors && errors.length > 0) item.runtimeContributionErrors = errors;
|
|
25833
|
+
}
|
|
25834
|
+
}
|
|
25206
25835
|
async function persistAndProject(c, deps, configKey, enabled) {
|
|
25207
25836
|
const overrides = await tryWithSqlite(
|
|
25208
25837
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
@@ -25892,8 +26521,168 @@ var parsePatchBody5 = makeBodyValidator(PATCH_BODY_SCHEMA4, {
|
|
|
25892
26521
|
}
|
|
25893
26522
|
});
|
|
25894
26523
|
|
|
25895
|
-
// server/routes/
|
|
26524
|
+
// server/routes/actions.ts
|
|
26525
|
+
import { HTTPException as HTTPException15 } from "hono/http-exception";
|
|
26526
|
+
import { resolve as resolve36 } from "path";
|
|
26527
|
+
|
|
26528
|
+
// server/routes/node-loader.ts
|
|
25896
26529
|
import { HTTPException as HTTPException14 } from "hono/http-exception";
|
|
26530
|
+
async function loadNode(deps, nodePath) {
|
|
26531
|
+
const persisted = await tryWithSqlite(
|
|
26532
|
+
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
26533
|
+
async (adapter) => adapter.scans.load()
|
|
26534
|
+
);
|
|
26535
|
+
const node = persisted?.nodes.find((n) => n.path === nodePath);
|
|
26536
|
+
if (!node) {
|
|
26537
|
+
throw new HTTPException14(404, {
|
|
26538
|
+
message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
|
|
26539
|
+
});
|
|
26540
|
+
}
|
|
26541
|
+
return node;
|
|
26542
|
+
}
|
|
26543
|
+
|
|
26544
|
+
// server/routes/actions.ts
|
|
26545
|
+
var ENVELOPE_KIND2 = "action.applied";
|
|
26546
|
+
var REFUSED_CODE = "action-refused";
|
|
26547
|
+
var INVOKER_FALLBACK = "ui";
|
|
26548
|
+
var QUALIFIED_ID_SEGMENT2 = /^[A-Za-z0-9._-]+$/;
|
|
26549
|
+
var ACTION_BODY_SCHEMA = {
|
|
26550
|
+
type: "object",
|
|
26551
|
+
additionalProperties: false,
|
|
26552
|
+
required: ["nodePath"],
|
|
26553
|
+
properties: {
|
|
26554
|
+
nodePath: { type: "string", minLength: 1 },
|
|
26555
|
+
input: { type: "object" },
|
|
26556
|
+
confirm: { type: "boolean" },
|
|
26557
|
+
always: { type: "boolean" }
|
|
26558
|
+
}
|
|
26559
|
+
};
|
|
26560
|
+
var parseBody = makeBodyValidator(ACTION_BODY_SCHEMA, {
|
|
26561
|
+
notJson: SERVER_TEXTS.actionBodyNotJson,
|
|
26562
|
+
notObject: SERVER_TEXTS.actionBodyNotObject,
|
|
26563
|
+
invalid: SERVER_TEXTS.actionBodyNotObject,
|
|
26564
|
+
mapping: {
|
|
26565
|
+
"/nodePath:required": SERVER_TEXTS.actionNodePathRequired,
|
|
26566
|
+
":type:object": SERVER_TEXTS.actionBodyNotObject,
|
|
26567
|
+
"/nodePath:type:string": SERVER_TEXTS.actionNodePathRequired,
|
|
26568
|
+
"/nodePath:minLength": SERVER_TEXTS.actionNodePathRequired,
|
|
26569
|
+
"/input:type:object": SERVER_TEXTS.actionInputMustBeObject,
|
|
26570
|
+
"/confirm:type:boolean": SERVER_TEXTS.actionConfirmMustBeBoolean,
|
|
26571
|
+
"/always:type:boolean": SERVER_TEXTS.actionAlwaysMustBeBoolean
|
|
26572
|
+
}
|
|
26573
|
+
});
|
|
26574
|
+
function registerActionsRoutes(app, deps) {
|
|
26575
|
+
app.post("/api/actions/:pluginId/:actionId", async (c) => {
|
|
26576
|
+
const startedAt = Date.now();
|
|
26577
|
+
const pluginId = parseSegment(c.req.param("pluginId"), "pluginId");
|
|
26578
|
+
const shortId = parseSegment(c.req.param("actionId"), "actionId");
|
|
26579
|
+
const actionId = qualifiedExtensionId(pluginId, shortId);
|
|
26580
|
+
const action = resolveInvokableAction(deps.kernel, actionId);
|
|
26581
|
+
const body = await parseBody(c.req.raw);
|
|
26582
|
+
const node = await loadNode(deps, body.nodePath);
|
|
26583
|
+
let absPath;
|
|
26584
|
+
try {
|
|
26585
|
+
assertContained(deps.runtimeContext.cwd, node.path);
|
|
26586
|
+
absPath = resolve36(deps.runtimeContext.cwd, node.path);
|
|
26587
|
+
} catch (err) {
|
|
26588
|
+
throw new HTTPException15(400, { message: formatErrorMessage(err) });
|
|
26589
|
+
}
|
|
26590
|
+
const result = invokeAction(action, absPath, node, body, deps.runtimeContext.cwd);
|
|
26591
|
+
const report = result.report;
|
|
26592
|
+
if (report.ok === false) {
|
|
26593
|
+
const reason = typeof report.reason === "string" && report.reason.length > 0 ? sanitizeForTerminal(report.reason) : REFUSED_CODE;
|
|
26594
|
+
throw new ActionRefusedError({
|
|
26595
|
+
code: reason,
|
|
26596
|
+
message: tx(SERVER_TEXTS.actionRefused, {
|
|
26597
|
+
actionId: sanitizeForTerminal(actionId),
|
|
26598
|
+
nodePath: sanitizeForTerminal(node.path)
|
|
26599
|
+
}),
|
|
26600
|
+
actionId,
|
|
26601
|
+
nodePath: node.path,
|
|
26602
|
+
report: result.report
|
|
26603
|
+
});
|
|
26604
|
+
}
|
|
26605
|
+
if (report.ok === true && report.noop === true) {
|
|
26606
|
+
return c.json(buildEnvelope5(actionId, node.path, result.report, startedAt));
|
|
26607
|
+
}
|
|
26608
|
+
await materializeWrites(result.writes, body, deps.runtimeContext.cwd);
|
|
26609
|
+
if (body.always === true) {
|
|
26610
|
+
deps.configService.reload();
|
|
26611
|
+
}
|
|
26612
|
+
const eventData = {
|
|
26613
|
+
actionId,
|
|
26614
|
+
nodePath: node.path,
|
|
26615
|
+
report: result.report
|
|
26616
|
+
};
|
|
26617
|
+
const wsEnvelope = {
|
|
26618
|
+
type: ENVELOPE_KIND2,
|
|
26619
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26620
|
+
data: eventData
|
|
26621
|
+
};
|
|
26622
|
+
deps.broadcaster.broadcast(wsEnvelope);
|
|
26623
|
+
return c.json(buildEnvelope5(actionId, node.path, result.report, startedAt));
|
|
26624
|
+
});
|
|
26625
|
+
}
|
|
26626
|
+
function parseSegment(value, name) {
|
|
26627
|
+
if (!QUALIFIED_ID_SEGMENT2.test(value)) {
|
|
26628
|
+
throw new HTTPException15(400, {
|
|
26629
|
+
message: tx(SERVER_TEXTS.qualifiedIdMalformed, {
|
|
26630
|
+
name,
|
|
26631
|
+
value: sanitizeForTerminal(value)
|
|
26632
|
+
})
|
|
26633
|
+
});
|
|
26634
|
+
}
|
|
26635
|
+
return value;
|
|
26636
|
+
}
|
|
26637
|
+
function resolveInvokableAction(kernel, actionId) {
|
|
26638
|
+
const ext = kernel.registry.get("action", actionId);
|
|
26639
|
+
const action = ext;
|
|
26640
|
+
if (!action || typeof action.invoke !== "function") {
|
|
26641
|
+
throw new HTTPException15(404, {
|
|
26642
|
+
message: tx(SERVER_TEXTS.actionUnknown, { actionId: sanitizeForTerminal(actionId) })
|
|
26643
|
+
});
|
|
26644
|
+
}
|
|
26645
|
+
return action;
|
|
26646
|
+
}
|
|
26647
|
+
function invokeAction(action, absPath, node, body, cwd) {
|
|
26648
|
+
const invoke = action.invoke;
|
|
26649
|
+
const ctx = {
|
|
26650
|
+
node,
|
|
26651
|
+
nodeAbsolutePath: absPath,
|
|
26652
|
+
invoker: resolveGitAuthorName(cwd) ?? INVOKER_FALLBACK,
|
|
26653
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
26654
|
+
settings: {}
|
|
26655
|
+
};
|
|
26656
|
+
return invoke(body.input ?? {}, ctx);
|
|
26657
|
+
}
|
|
26658
|
+
async function materializeWrites(writes, body, cwd) {
|
|
26659
|
+
const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
|
|
26660
|
+
try {
|
|
26661
|
+
for (const w of writes ?? []) {
|
|
26662
|
+
if (w.kind === "sidecar") {
|
|
26663
|
+
await store.applyPatch(w.path, w.changes, {
|
|
26664
|
+
confirm: body.confirm === true,
|
|
26665
|
+
always: body.always === true,
|
|
26666
|
+
cwd
|
|
26667
|
+
});
|
|
26668
|
+
}
|
|
26669
|
+
}
|
|
26670
|
+
} catch (err) {
|
|
26671
|
+
if (err instanceof EConsentRequiredError) throw err;
|
|
26672
|
+
throw new HTTPException15(500, { message: formatErrorMessage(err) });
|
|
26673
|
+
}
|
|
26674
|
+
}
|
|
26675
|
+
function buildEnvelope5(actionId, nodePath, report, startedAt) {
|
|
26676
|
+
return {
|
|
26677
|
+
schemaVersion: "1",
|
|
26678
|
+
kind: ENVELOPE_KIND2,
|
|
26679
|
+
value: { actionId, nodePath, report },
|
|
26680
|
+
elapsedMs: Date.now() - startedAt
|
|
26681
|
+
};
|
|
26682
|
+
}
|
|
26683
|
+
|
|
26684
|
+
// server/routes/scan.ts
|
|
26685
|
+
import { HTTPException as HTTPException16 } from "hono/http-exception";
|
|
25897
26686
|
|
|
25898
26687
|
// server/scan-mutex.ts
|
|
25899
26688
|
var inFlight = null;
|
|
@@ -26072,7 +26861,7 @@ function registerScanRoute(app, deps) {
|
|
|
26072
26861
|
}
|
|
26073
26862
|
async function runPersistedScan(c, deps) {
|
|
26074
26863
|
if (deps.options.noBuiltIns || deps.options.noPlugins) {
|
|
26075
|
-
throw new
|
|
26864
|
+
throw new HTTPException16(400, { message: SERVER_TEXTS.scanPostRequiresFullPipeline });
|
|
26076
26865
|
}
|
|
26077
26866
|
const dbExists = await tryWithSqlite(
|
|
26078
26867
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
@@ -26109,7 +26898,7 @@ async function runPersistedScan(c, deps) {
|
|
|
26109
26898
|
...deps.options.maxNodes !== void 0 ? { maxNodes: deps.options.maxNodes } : {}
|
|
26110
26899
|
});
|
|
26111
26900
|
if (outcome.kind !== "ok") {
|
|
26112
|
-
throw new
|
|
26901
|
+
throw new HTTPException16(500, {
|
|
26113
26902
|
message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.scanGuardTrip, { existing: outcome.existing }) : outcome.message
|
|
26114
26903
|
});
|
|
26115
26904
|
}
|
|
@@ -26187,7 +26976,7 @@ function groupTagsByPath2(rows) {
|
|
|
26187
26976
|
}
|
|
26188
26977
|
async function runFreshScan(deps) {
|
|
26189
26978
|
if (deps.options.noBuiltIns || deps.options.noPlugins) {
|
|
26190
|
-
throw new
|
|
26979
|
+
throw new HTTPException16(400, { message: SERVER_TEXTS.freshScanRequiresPipeline });
|
|
26191
26980
|
}
|
|
26192
26981
|
const resolveEnabledOverride = await buildBffResolverOverride(deps);
|
|
26193
26982
|
const outcome = await runScanForCommand({
|
|
@@ -26222,7 +27011,7 @@ async function runFreshScan(deps) {
|
|
|
26222
27011
|
...deps.options.maxNodes !== void 0 ? { maxNodes: deps.options.maxNodes } : {}
|
|
26223
27012
|
});
|
|
26224
27013
|
if (outcome.kind !== "ok") {
|
|
26225
|
-
throw new
|
|
27014
|
+
throw new HTTPException16(500, {
|
|
26226
27015
|
message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.freshScanGuardTrip, { existing: outcome.existing }) : outcome.message
|
|
26227
27016
|
});
|
|
26228
27017
|
}
|
|
@@ -26268,141 +27057,6 @@ function emptyScanResult() {
|
|
|
26268
27057
|
};
|
|
26269
27058
|
}
|
|
26270
27059
|
|
|
26271
|
-
// server/routes/sidecar.ts
|
|
26272
|
-
import { HTTPException as HTTPException15 } from "hono/http-exception";
|
|
26273
|
-
import { resolve as resolve36 } from "path";
|
|
26274
|
-
var STATUS_FRESH = "fresh";
|
|
26275
|
-
var ENVELOPE_KIND2 = "sidecar.bumped";
|
|
26276
|
-
var BUMP_BODY_SCHEMA = {
|
|
26277
|
-
type: "object",
|
|
26278
|
-
additionalProperties: false,
|
|
26279
|
-
required: ["nodePath"],
|
|
26280
|
-
properties: {
|
|
26281
|
-
nodePath: { type: "string", minLength: 1 },
|
|
26282
|
-
force: { type: "boolean" },
|
|
26283
|
-
confirm: { type: "boolean" }
|
|
26284
|
-
}
|
|
26285
|
-
};
|
|
26286
|
-
var parseBody = makeBodyValidator(BUMP_BODY_SCHEMA, {
|
|
26287
|
-
notJson: SERVER_TEXTS.sidecarBodyNotJson,
|
|
26288
|
-
notObject: SERVER_TEXTS.sidecarBodyNotObject,
|
|
26289
|
-
invalid: SERVER_TEXTS.sidecarBodyNotObject,
|
|
26290
|
-
mapping: {
|
|
26291
|
-
"/nodePath:required": SERVER_TEXTS.sidecarNodePathRequired,
|
|
26292
|
-
":type:object": SERVER_TEXTS.sidecarBodyNotObject,
|
|
26293
|
-
"/nodePath:type:string": SERVER_TEXTS.sidecarNodePathRequired,
|
|
26294
|
-
"/nodePath:minLength": SERVER_TEXTS.sidecarNodePathRequired,
|
|
26295
|
-
"/force:type:boolean": SERVER_TEXTS.sidecarForceMustBeBoolean,
|
|
26296
|
-
"/confirm:type:boolean": SERVER_TEXTS.sidecarConfirmMustBeBoolean
|
|
26297
|
-
}
|
|
26298
|
-
});
|
|
26299
|
-
function registerSidecarRoutes(app, deps) {
|
|
26300
|
-
app.post("/api/sidecar/bump", async (c) => {
|
|
26301
|
-
const startedAt = Date.now();
|
|
26302
|
-
const body = await parseBody(c.req.raw);
|
|
26303
|
-
const node = await loadNode(deps, body.nodePath);
|
|
26304
|
-
let absPath;
|
|
26305
|
-
try {
|
|
26306
|
-
assertContained(deps.runtimeContext.cwd, node.path);
|
|
26307
|
-
absPath = resolve36(deps.runtimeContext.cwd, node.path);
|
|
26308
|
-
} catch (err) {
|
|
26309
|
-
throw new HTTPException15(400, { message: formatErrorMessage(err) });
|
|
26310
|
-
}
|
|
26311
|
-
const result = invokeBump2(node, absPath, body, deps.runtimeContext.cwd);
|
|
26312
|
-
if (result.report.ok === false && result.report.reason === "fresh") {
|
|
26313
|
-
throw new ConflictError({ code: "sidecar-fresh", message: SERVER_TEXTS.sidecarFreshRefusal });
|
|
26314
|
-
}
|
|
26315
|
-
if (result.report.ok === true && result.report.noop === true) {
|
|
26316
|
-
const envelope2 = {
|
|
26317
|
-
schemaVersion: "1",
|
|
26318
|
-
kind: ENVELOPE_KIND2,
|
|
26319
|
-
value: {
|
|
26320
|
-
nodePath: node.path,
|
|
26321
|
-
version: pickExistingVersion(node),
|
|
26322
|
-
status: STATUS_FRESH
|
|
26323
|
-
},
|
|
26324
|
-
elapsedMs: Date.now() - startedAt
|
|
26325
|
-
};
|
|
26326
|
-
return c.json(envelope2);
|
|
26327
|
-
}
|
|
26328
|
-
const store = new FilesystemSidecarStore(ensureSidecarWritesAllowed);
|
|
26329
|
-
try {
|
|
26330
|
-
for (const w of result.writes ?? []) {
|
|
26331
|
-
if (w.kind === "sidecar") {
|
|
26332
|
-
await store.applyPatch(w.path, w.changes, {
|
|
26333
|
-
confirm: body.confirm === true,
|
|
26334
|
-
cwd: deps.runtimeContext.cwd
|
|
26335
|
-
});
|
|
26336
|
-
}
|
|
26337
|
-
}
|
|
26338
|
-
} catch (err) {
|
|
26339
|
-
if (err instanceof EConsentRequiredError) throw err;
|
|
26340
|
-
throw new HTTPException15(500, { message: formatErrorMessage(err) });
|
|
26341
|
-
}
|
|
26342
|
-
if (body.confirm === true) {
|
|
26343
|
-
deps.configService.reload();
|
|
26344
|
-
}
|
|
26345
|
-
const newVersion = result.report.version ?? null;
|
|
26346
|
-
const eventData = {
|
|
26347
|
-
nodePath: node.path,
|
|
26348
|
-
version: newVersion,
|
|
26349
|
-
status: STATUS_FRESH
|
|
26350
|
-
};
|
|
26351
|
-
const wsEnvelope = {
|
|
26352
|
-
type: ENVELOPE_KIND2,
|
|
26353
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26354
|
-
data: eventData
|
|
26355
|
-
};
|
|
26356
|
-
deps.broadcaster.broadcast(wsEnvelope);
|
|
26357
|
-
const envelope = {
|
|
26358
|
-
schemaVersion: "1",
|
|
26359
|
-
kind: ENVELOPE_KIND2,
|
|
26360
|
-
value: {
|
|
26361
|
-
nodePath: node.path,
|
|
26362
|
-
version: newVersion,
|
|
26363
|
-
status: STATUS_FRESH
|
|
26364
|
-
},
|
|
26365
|
-
elapsedMs: Date.now() - startedAt
|
|
26366
|
-
};
|
|
26367
|
-
return c.json(envelope);
|
|
26368
|
-
});
|
|
26369
|
-
}
|
|
26370
|
-
async function loadNode(deps, nodePath) {
|
|
26371
|
-
const persisted = await tryWithSqlite(
|
|
26372
|
-
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
26373
|
-
async (adapter) => adapter.scans.load()
|
|
26374
|
-
);
|
|
26375
|
-
const node = persisted?.nodes.find((n) => n.path === nodePath);
|
|
26376
|
-
if (!node) {
|
|
26377
|
-
throw new HTTPException15(404, {
|
|
26378
|
-
message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
|
|
26379
|
-
});
|
|
26380
|
-
}
|
|
26381
|
-
return node;
|
|
26382
|
-
}
|
|
26383
|
-
function invokeBump2(node, absPath, body, cwd) {
|
|
26384
|
-
if (!nodeBumpAction.invoke) {
|
|
26385
|
-
throw new HTTPException15(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
|
|
26386
|
-
}
|
|
26387
|
-
const input = {};
|
|
26388
|
-
if (body.force === true) input.force = true;
|
|
26389
|
-
return nodeBumpAction.invoke(input, {
|
|
26390
|
-
node,
|
|
26391
|
-
nodeAbsolutePath: absPath,
|
|
26392
|
-
invoker: resolveGitAuthorName(cwd) ?? "ui",
|
|
26393
|
-
now: () => /* @__PURE__ */ new Date(),
|
|
26394
|
-
settings: {}
|
|
26395
|
-
});
|
|
26396
|
-
}
|
|
26397
|
-
function pickExistingVersion(node) {
|
|
26398
|
-
const overlay = node.sidecar;
|
|
26399
|
-
if (!overlay || overlay.present !== true) return null;
|
|
26400
|
-
const annotations = overlay.annotations;
|
|
26401
|
-
if (!annotations) return null;
|
|
26402
|
-
const v = annotations["version"];
|
|
26403
|
-
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
26404
|
-
}
|
|
26405
|
-
|
|
26406
27060
|
// server/routes/update-status.ts
|
|
26407
27061
|
function registerUpdateStatusRoute(app, deps) {
|
|
26408
27062
|
app.get("/api/update-status", async (c) => {
|
|
@@ -26569,13 +27223,13 @@ function attachBroadcasterRoute(app, broadcaster) {
|
|
|
26569
27223
|
|
|
26570
27224
|
// server/app.ts
|
|
26571
27225
|
var BODY_LIMIT_BYTES = 1024 * 1024;
|
|
26572
|
-
var DbMissingError = class extends
|
|
27226
|
+
var DbMissingError = class extends HTTPException17 {
|
|
26573
27227
|
constructor(message) {
|
|
26574
27228
|
super(500, { message });
|
|
26575
27229
|
this.name = "DbMissingError";
|
|
26576
27230
|
}
|
|
26577
27231
|
};
|
|
26578
|
-
var BulkValidationError = class extends
|
|
27232
|
+
var BulkValidationError = class extends HTTPException17 {
|
|
26579
27233
|
id;
|
|
26580
27234
|
code;
|
|
26581
27235
|
constructor(init) {
|
|
@@ -26585,7 +27239,7 @@ var BulkValidationError = class extends HTTPException16 {
|
|
|
26585
27239
|
this.code = init.code;
|
|
26586
27240
|
}
|
|
26587
27241
|
};
|
|
26588
|
-
var LoopbackGateError = class extends
|
|
27242
|
+
var LoopbackGateError = class extends HTTPException17 {
|
|
26589
27243
|
code;
|
|
26590
27244
|
constructor(init) {
|
|
26591
27245
|
super(403, { message: init.message });
|
|
@@ -26593,7 +27247,7 @@ var LoopbackGateError = class extends HTTPException16 {
|
|
|
26593
27247
|
this.code = init.code;
|
|
26594
27248
|
}
|
|
26595
27249
|
};
|
|
26596
|
-
var ConflictError = class extends
|
|
27250
|
+
var ConflictError = class extends HTTPException17 {
|
|
26597
27251
|
code;
|
|
26598
27252
|
constructor(init) {
|
|
26599
27253
|
super(409, { message: init.message });
|
|
@@ -26601,6 +27255,21 @@ var ConflictError = class extends HTTPException16 {
|
|
|
26601
27255
|
this.code = init.code;
|
|
26602
27256
|
}
|
|
26603
27257
|
};
|
|
27258
|
+
var ActionRefusedError = class extends HTTPException17 {
|
|
27259
|
+
/** Refusal code: the report's `reason` when present, else `'action-refused'`. */
|
|
27260
|
+
code;
|
|
27261
|
+
details;
|
|
27262
|
+
constructor(init) {
|
|
27263
|
+
super(409, { message: init.message });
|
|
27264
|
+
this.name = "ActionRefusedError";
|
|
27265
|
+
this.code = init.code;
|
|
27266
|
+
this.details = {
|
|
27267
|
+
actionId: init.actionId,
|
|
27268
|
+
nodePath: init.nodePath,
|
|
27269
|
+
report: init.report
|
|
27270
|
+
};
|
|
27271
|
+
}
|
|
27272
|
+
};
|
|
26604
27273
|
function createApp(deps) {
|
|
26605
27274
|
const app = new Hono();
|
|
26606
27275
|
const configService = new ConfigService({
|
|
@@ -26614,7 +27283,7 @@ function createApp(deps) {
|
|
|
26614
27283
|
bodyLimit({
|
|
26615
27284
|
maxSize: BODY_LIMIT_BYTES,
|
|
26616
27285
|
onError: () => {
|
|
26617
|
-
throw new
|
|
27286
|
+
throw new HTTPException17(413, { message: tx(SERVER_TEXTS.bodyTooLarge, { maxBytes: String(BODY_LIMIT_BYTES) }) });
|
|
26618
27287
|
}
|
|
26619
27288
|
})
|
|
26620
27289
|
);
|
|
@@ -26650,7 +27319,7 @@ function createApp(deps) {
|
|
|
26650
27319
|
registerGraphRoute(app, routeDeps);
|
|
26651
27320
|
registerConfigRoute(app, routeDeps);
|
|
26652
27321
|
registerPluginsRoute(app, routeDeps);
|
|
26653
|
-
|
|
27322
|
+
registerActionsRoutes(app, { ...routeDeps, broadcaster: deps.broadcaster, kernel: deps.kernel });
|
|
26654
27323
|
registerFavoritesRoutes(app, routeDeps);
|
|
26655
27324
|
registerAnnotationsRoute(app, { kernel: deps.kernel });
|
|
26656
27325
|
registerContributionsRoutes(app, { ...routeDeps, kernel: deps.kernel });
|
|
@@ -26660,7 +27329,7 @@ function createApp(deps) {
|
|
|
26660
27329
|
registerActiveProviderRoute(app, routeDeps);
|
|
26661
27330
|
registerProjectIgnoreRoute(app, routeDeps);
|
|
26662
27331
|
app.all("/api/*", (c) => {
|
|
26663
|
-
throw new
|
|
27332
|
+
throw new HTTPException17(404, {
|
|
26664
27333
|
message: tx(SERVER_TEXTS.unknownApiEndpoint, { path: sanitizeForTerminal(c.req.path) })
|
|
26665
27334
|
});
|
|
26666
27335
|
});
|
|
@@ -26668,7 +27337,7 @@ function createApp(deps) {
|
|
|
26668
27337
|
app.use("*", createStaticHandler({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
|
|
26669
27338
|
app.get("*", createSpaFallback({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
|
|
26670
27339
|
app.notFound((c) => {
|
|
26671
|
-
throw new
|
|
27340
|
+
throw new HTTPException17(404, {
|
|
26672
27341
|
message: tx(SERVER_TEXTS.unknownPath, { path: sanitizeForTerminal(c.req.path) })
|
|
26673
27342
|
});
|
|
26674
27343
|
});
|
|
@@ -26719,18 +27388,9 @@ function formatError2(err, c) {
|
|
|
26719
27388
|
};
|
|
26720
27389
|
return c.json(envelope, 403);
|
|
26721
27390
|
}
|
|
26722
|
-
|
|
26723
|
-
|
|
26724
|
-
|
|
26725
|
-
error: {
|
|
26726
|
-
code: err.code,
|
|
26727
|
-
message: err.message,
|
|
26728
|
-
details: null
|
|
26729
|
-
}
|
|
26730
|
-
};
|
|
26731
|
-
return c.json(envelope, 409);
|
|
26732
|
-
}
|
|
26733
|
-
if (err instanceof HTTPException16) {
|
|
27391
|
+
const conflict = formatConflict(err, c);
|
|
27392
|
+
if (conflict) return conflict;
|
|
27393
|
+
if (err instanceof HTTPException17) {
|
|
26734
27394
|
const status = err.status;
|
|
26735
27395
|
const envelope = {
|
|
26736
27396
|
ok: false,
|
|
@@ -26766,6 +27426,23 @@ function formatError2(err, c) {
|
|
|
26766
27426
|
}
|
|
26767
27427
|
return formatInternalErrorFallThrough(err, c);
|
|
26768
27428
|
}
|
|
27429
|
+
function formatConflict(err, c) {
|
|
27430
|
+
if (err instanceof ActionRefusedError) {
|
|
27431
|
+
const envelope = {
|
|
27432
|
+
ok: false,
|
|
27433
|
+
error: { code: err.code, message: err.message, details: err.details }
|
|
27434
|
+
};
|
|
27435
|
+
return c.json(envelope, 409);
|
|
27436
|
+
}
|
|
27437
|
+
if (err instanceof ConflictError) {
|
|
27438
|
+
const envelope = {
|
|
27439
|
+
ok: false,
|
|
27440
|
+
error: { code: err.code, message: err.message, details: null }
|
|
27441
|
+
};
|
|
27442
|
+
return c.json(envelope, 409);
|
|
27443
|
+
}
|
|
27444
|
+
return null;
|
|
27445
|
+
}
|
|
26769
27446
|
function formatInternalErrorFallThrough(err, c) {
|
|
26770
27447
|
const detail = formatErrorMessage(err);
|
|
26771
27448
|
const stack = err instanceof Error && typeof err.stack === "string" ? err.stack : void 0;
|
|
@@ -26991,6 +27668,8 @@ function entryFromRegistered(c) {
|
|
|
26991
27668
|
if (c.icon !== void 0) entry.icon = c.icon;
|
|
26992
27669
|
if (c.emptyText !== void 0) entry.emptyText = c.emptyText;
|
|
26993
27670
|
if (c.priority !== void 0) entry.priority = c.priority;
|
|
27671
|
+
if (c.pluginOrder !== void 0) entry.pluginOrder = c.pluginOrder;
|
|
27672
|
+
if (c.extensionOrder !== void 0) entry.extensionOrder = c.extensionOrder;
|
|
26994
27673
|
return entry;
|
|
26995
27674
|
}
|
|
26996
27675
|
|
|
@@ -27248,8 +27927,10 @@ async function createServer(options, extra = {}) {
|
|
|
27248
27927
|
}
|
|
27249
27928
|
async function assemblePluginRuntime(options, runtimeContext) {
|
|
27250
27929
|
const pluginRuntime = options.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ runtimeContext });
|
|
27251
|
-
|
|
27252
|
-
|
|
27930
|
+
if (options.noWatcher) {
|
|
27931
|
+
for (const warn of pluginRuntime.warnings) {
|
|
27932
|
+
log.warn(sanitizeForTerminal(warn));
|
|
27933
|
+
}
|
|
27253
27934
|
}
|
|
27254
27935
|
const builtInProviders = options.noBuiltIns ? [] : collectBuiltInProviders();
|
|
27255
27936
|
const allProviders = [...builtInProviders, ...pluginRuntime.extensions.providers];
|
|
@@ -27260,6 +27941,11 @@ async function assemblePluginRuntime(options, runtimeContext) {
|
|
|
27260
27941
|
function assembleKernel(pluginRuntime, noBuiltIns) {
|
|
27261
27942
|
const kernel = createKernel();
|
|
27262
27943
|
kernel.setRegisteredAnnotationKeys(pluginRuntime.annotationContributions);
|
|
27944
|
+
if (!noBuiltIns) {
|
|
27945
|
+
for (const action of builtIns().actions) {
|
|
27946
|
+
kernel.registry.register(action);
|
|
27947
|
+
}
|
|
27948
|
+
}
|
|
27263
27949
|
const mergedViewContributions = [...pluginRuntime.viewContributions];
|
|
27264
27950
|
if (!noBuiltIns) {
|
|
27265
27951
|
const userKey = new Set(
|
|
@@ -28129,13 +28815,13 @@ var ShowCommand = class extends SmCommand {
|
|
|
28129
28815
|
}
|
|
28130
28816
|
};
|
|
28131
28817
|
function renderHuman2(doc, ansi) {
|
|
28132
|
-
const { node, linksOut, linksIn, issues } = doc;
|
|
28818
|
+
const { node, linksOut: linksOut2, linksIn: linksIn2, issues } = doc;
|
|
28133
28819
|
const out = [];
|
|
28134
28820
|
out.push(renderHeader(node, ansi));
|
|
28135
28821
|
out.push(renderFieldBlock(node, ansi));
|
|
28136
28822
|
out.push(renderFrontmatter(node, ansi));
|
|
28137
|
-
if (
|
|
28138
|
-
if (
|
|
28823
|
+
if (linksOut2.length > 0) out.push(renderLinksSection("out", linksOut2, ansi));
|
|
28824
|
+
if (linksIn2.length > 0) out.push(renderLinksSection("in", linksIn2, ansi));
|
|
28139
28825
|
if (issues.length > 0) out.push(renderIssuesSection(issues, node.path, ansi));
|
|
28140
28826
|
return out.join("");
|
|
28141
28827
|
}
|
|
@@ -28527,7 +29213,9 @@ var SidecarRefreshCommand = class extends SmCommand {
|
|
|
28527
29213
|
frontmatterHash: node.frontmatterHash
|
|
28528
29214
|
}
|
|
28529
29215
|
},
|
|
28530
|
-
|
|
29216
|
+
// Step 17 split: CLI accept / `--yes` persists the grant, so it
|
|
29217
|
+
// threads `always`, not the new one-shot `confirm`.
|
|
29218
|
+
{ confirm: this.yes, always: this.yes, cwd: ctx.cwd }
|
|
28531
29219
|
);
|
|
28532
29220
|
} catch (err) {
|
|
28533
29221
|
if (err instanceof EConsentRequiredError) throw err;
|
|
@@ -28808,7 +29496,9 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
28808
29496
|
await store.applyPatch(
|
|
28809
29497
|
sidecarAbsPath,
|
|
28810
29498
|
scaffoldSidecarObject(node),
|
|
28811
|
-
|
|
29499
|
+
// Step 17 split: CLI accept / `--yes` persists the grant, so it
|
|
29500
|
+
// threads `always`, not the new one-shot `confirm`.
|
|
29501
|
+
{ confirm: this.yes, always: this.yes, cwd: ctx.cwd }
|
|
28812
29502
|
);
|
|
28813
29503
|
} catch (err) {
|
|
28814
29504
|
if (err instanceof EConsentRequiredError) throw err;
|
|
@@ -29069,11 +29759,13 @@ var TUTORIAL_TEXTS = {
|
|
|
29069
29759
|
// the error shape: glyph + headline + dim hint.
|
|
29070
29760
|
notEmpty: "{{glyph}} sm tutorial: the current directory is not empty (found {{entries}})\n {{hint}}\n",
|
|
29071
29761
|
notEmptyHint: "sm tutorial seeds a self-contained scenario; run it in a fresh empty directory, or pass `--force` to use this one anyway.",
|
|
29072
|
-
//
|
|
29073
|
-
//
|
|
29074
|
-
//
|
|
29075
|
-
|
|
29076
|
-
|
|
29762
|
+
// Legacy positional argument (e.g. a stale `sm tutorial master`). The
|
|
29763
|
+
// verb no longer takes a positional: there is a single umbrella skill
|
|
29764
|
+
// and the advanced walkthrough is a menu choice inside it, not a
|
|
29765
|
+
// separate install. Goes to stderr, exit code 2. Mirrors the error
|
|
29766
|
+
// shape: glyph + headline + dim hint.
|
|
29767
|
+
legacyPositional: "{{glyph}} sm tutorial: unexpected argument '{{arg}}'\n {{hint}}\n",
|
|
29768
|
+
legacyPositionalHint: "sm tutorial takes no positional argument. The master walkthrough is no longer a separate install; run `sm tutorial` and pick the advanced parts from the in-skill menu.",
|
|
29077
29769
|
// I/O failure on write or on reading the bundled skill source.
|
|
29078
29770
|
writeFailed: "{{glyph}} sm tutorial: failed to write {{target}}: {{message}}\n",
|
|
29079
29771
|
sourceMissing: "{{glyph}} sm tutorial: could not read the bundled skill payload for {{target}} from the install.\n {{hint}}\n",
|
|
@@ -29081,47 +29773,36 @@ var TUTORIAL_TEXTS = {
|
|
|
29081
29773
|
};
|
|
29082
29774
|
|
|
29083
29775
|
// cli/commands/tutorial.ts
|
|
29084
|
-
var
|
|
29085
|
-
var
|
|
29086
|
-
var
|
|
29087
|
-
|
|
29088
|
-
slug: "sm-tutorial",
|
|
29089
|
-
sourceDir: ".claude/skills/sm-tutorial",
|
|
29090
|
-
triggerEn: "run the tutorial",
|
|
29091
|
-
triggerEs: "ejecuta el tutorial"
|
|
29092
|
-
},
|
|
29093
|
-
master: {
|
|
29094
|
-
slug: "sm-master",
|
|
29095
|
-
sourceDir: ".claude/skills/sm-master",
|
|
29096
|
-
triggerEn: "run the master tutorial",
|
|
29097
|
-
triggerEs: "ejecuta el tutorial maestro"
|
|
29098
|
-
}
|
|
29099
|
-
};
|
|
29776
|
+
var SKILL_SLUG = "sm-tutorial";
|
|
29777
|
+
var SKILL_SOURCE_DIR = ".claude/skills/sm-tutorial";
|
|
29778
|
+
var TRIGGER_EN = "run the tutorial";
|
|
29779
|
+
var TRIGGER_ES = "ejecuta el tutorial";
|
|
29100
29780
|
var TutorialCommand = class extends SmCommand {
|
|
29101
29781
|
static paths = [["tutorial"]];
|
|
29102
29782
|
static usage = Command37.Usage({
|
|
29103
29783
|
category: "Setup",
|
|
29104
29784
|
description: "Materialize an interactive tester tutorial as a Claude Code skill folder under `<cwd>/.claude/skills/`.",
|
|
29105
29785
|
details: `
|
|
29106
|
-
Drops the canonical skill directory (SKILL.md +
|
|
29107
|
-
sub-folder) under \`<cwd>/.claude/skills/sm-tutorial
|
|
29108
|
-
|
|
29109
|
-
|
|
29110
|
-
|
|
29111
|
-
speaking one of its trigger phrases.
|
|
29786
|
+
Drops the canonical skill directory (SKILL.md + its references/
|
|
29787
|
+
sub-folder) under \`<cwd>/.claude/skills/sm-tutorial/\`. Claude
|
|
29788
|
+
Code auto-discovers the skill the next time it boots in this
|
|
29789
|
+
directory; the tester invokes it by speaking one of its trigger
|
|
29790
|
+
phrases and picks the advanced parts from the in-skill menu.
|
|
29112
29791
|
|
|
29113
29792
|
Does NOT require an initialized .skill-map/ project. Refuses to
|
|
29114
|
-
overwrite the target directory unless --force is passed.
|
|
29115
|
-
|
|
29116
|
-
master.
|
|
29793
|
+
overwrite the target directory unless --force is passed. Takes no
|
|
29794
|
+
positional argument.
|
|
29117
29795
|
`,
|
|
29118
29796
|
examples: [
|
|
29119
|
-
["Materialize the
|
|
29120
|
-
["Materialize the advanced tutorial skill in the cwd", "$0 tutorial master"],
|
|
29797
|
+
["Materialize the tutorial skill in the cwd", "$0 tutorial"],
|
|
29121
29798
|
["Overwrite an existing target directory", "$0 tutorial --force"]
|
|
29122
29799
|
]
|
|
29123
29800
|
});
|
|
29124
|
-
|
|
29801
|
+
// Legacy positional catcher: the verb takes no positional argument any
|
|
29802
|
+
// more. Accept one so a stale `sm tutorial master` lands on a friendly
|
|
29803
|
+
// usage error (guarded in `run()`) instead of clipanion's generic
|
|
29804
|
+
// "extraneous argument" message.
|
|
29805
|
+
legacyPositional = Option35.String({ required: false });
|
|
29125
29806
|
// Named `forProvider`, NOT `for` (reserved word). The CLI surface stays
|
|
29126
29807
|
// `--for`; selects the destination Provider whose `scaffold.skillDir`
|
|
29127
29808
|
// the skill is materialised under, skipping the interactive prompt.
|
|
@@ -29137,9 +29818,16 @@ var TutorialCommand = class extends SmCommand {
|
|
|
29137
29818
|
const stderr = this.context.stderr;
|
|
29138
29819
|
const stderrAnsi = this.ansiFor("stderr");
|
|
29139
29820
|
const errGlyph = stderrAnsi.red("\u2715");
|
|
29140
|
-
|
|
29141
|
-
|
|
29142
|
-
|
|
29821
|
+
if (this.legacyPositional !== void 0) {
|
|
29822
|
+
this.printer.error(
|
|
29823
|
+
tx(TUTORIAL_TEXTS.legacyPositional, {
|
|
29824
|
+
glyph: errGlyph,
|
|
29825
|
+
arg: this.legacyPositional,
|
|
29826
|
+
hint: stderrAnsi.dim(TUTORIAL_TEXTS.legacyPositionalHint)
|
|
29827
|
+
})
|
|
29828
|
+
);
|
|
29829
|
+
return ExitCode.Error;
|
|
29830
|
+
}
|
|
29143
29831
|
if (!this.force && !isDirEmpty(ctx.cwd)) {
|
|
29144
29832
|
this.printer.error(
|
|
29145
29833
|
tx(TUTORIAL_TEXTS.notEmpty, {
|
|
@@ -29153,11 +29841,11 @@ var TutorialCommand = class extends SmCommand {
|
|
|
29153
29841
|
const targets = listScaffoldTargets();
|
|
29154
29842
|
const target = await this.resolveScaffoldTarget(targets, stderrAnsi, errGlyph);
|
|
29155
29843
|
if (target === null) return ExitCode.Error;
|
|
29156
|
-
const targetDir = join21(ctx.cwd, target.skillDir,
|
|
29157
|
-
const targetDisplay = `${target.skillDir}/${
|
|
29844
|
+
const targetDir = join21(ctx.cwd, target.skillDir, SKILL_SLUG);
|
|
29845
|
+
const targetDisplay = `${target.skillDir}/${SKILL_SLUG}/`;
|
|
29158
29846
|
let sourceDir;
|
|
29159
29847
|
try {
|
|
29160
|
-
sourceDir = resolveSkillSourceDir(
|
|
29848
|
+
sourceDir = resolveSkillSourceDir();
|
|
29161
29849
|
} catch {
|
|
29162
29850
|
this.printer.error(
|
|
29163
29851
|
tx(TUTORIAL_TEXTS.sourceMissing, {
|
|
@@ -29192,38 +29880,18 @@ var TutorialCommand = class extends SmCommand {
|
|
|
29192
29880
|
this.printer.data(
|
|
29193
29881
|
tx(TUTORIAL_TEXTS.written, {
|
|
29194
29882
|
glyph: ansi.green("\u2713"),
|
|
29195
|
-
slug:
|
|
29883
|
+
slug: SKILL_SLUG,
|
|
29196
29884
|
target: targetDisplay,
|
|
29197
29885
|
provider: ansi.dim(target.label),
|
|
29198
29886
|
cwd: ansi.dim(displayCwd(ctx.cwd)),
|
|
29199
29887
|
enLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEn),
|
|
29200
29888
|
esLabel: ansi.dim(TUTORIAL_TEXTS.writtenLabelEs),
|
|
29201
|
-
enTrigger:
|
|
29202
|
-
esTrigger:
|
|
29889
|
+
enTrigger: TRIGGER_EN,
|
|
29890
|
+
esTrigger: TRIGGER_ES
|
|
29203
29891
|
})
|
|
29204
29892
|
);
|
|
29205
29893
|
return ExitCode.Ok;
|
|
29206
29894
|
}
|
|
29207
|
-
/**
|
|
29208
|
-
* Validate the positional `variant` arg against the closed catalog.
|
|
29209
|
-
* Returns the resolved variant, or `null` after printing the
|
|
29210
|
-
* `invalidVariant` error (caller exits non-zero). Extracted from
|
|
29211
|
-
* `run()` to keep its cyclomatic complexity within the lint budget.
|
|
29212
|
-
*/
|
|
29213
|
-
resolveVariantArg(errGlyph, stderrAnsi) {
|
|
29214
|
-
const rawVariant = this.variant;
|
|
29215
|
-
if (rawVariant !== void 0 && !isTutorialVariant(rawVariant)) {
|
|
29216
|
-
this.printer.error(
|
|
29217
|
-
tx(TUTORIAL_TEXTS.invalidVariant, {
|
|
29218
|
-
glyph: errGlyph,
|
|
29219
|
-
variant: rawVariant,
|
|
29220
|
-
hint: stderrAnsi.dim(TUTORIAL_TEXTS.invalidVariantHint)
|
|
29221
|
-
})
|
|
29222
|
-
);
|
|
29223
|
-
return null;
|
|
29224
|
-
}
|
|
29225
|
-
return rawVariant ?? DEFAULT_VARIANT;
|
|
29226
|
-
}
|
|
29227
29895
|
/**
|
|
29228
29896
|
* Resolve the destination Provider. Precedence:
|
|
29229
29897
|
* 1. `--for <id>` (validated against the scaffold-capable catalog).
|
|
@@ -29283,9 +29951,6 @@ var TutorialCommand = class extends SmCommand {
|
|
|
29283
29951
|
return picked;
|
|
29284
29952
|
}
|
|
29285
29953
|
};
|
|
29286
|
-
function isTutorialVariant(value) {
|
|
29287
|
-
return VALID_VARIANTS.includes(value);
|
|
29288
|
-
}
|
|
29289
29954
|
function toScaffoldTarget(provider) {
|
|
29290
29955
|
const scaffold = provider.scaffold;
|
|
29291
29956
|
if (!scaffold || !scaffold.skillDir) return null;
|
|
@@ -29352,23 +30017,21 @@ function listCwdEntries(dir) {
|
|
|
29352
30017
|
const more = entries.length > shown.length ? ", ..." : "";
|
|
29353
30018
|
return shown.join(", ") + more;
|
|
29354
30019
|
}
|
|
29355
|
-
var
|
|
29356
|
-
function resolveSkillSourceDir(
|
|
29357
|
-
|
|
29358
|
-
if (cached !== void 0) return cached;
|
|
29359
|
-
const spec = VARIANT_SPECS[variant];
|
|
30020
|
+
var cachedSourceDir;
|
|
30021
|
+
function resolveSkillSourceDir() {
|
|
30022
|
+
if (cachedSourceDir !== void 0) return cachedSourceDir;
|
|
29360
30023
|
const here = dirname20(fileURLToPath7(import.meta.url));
|
|
29361
30024
|
const candidates = [
|
|
29362
|
-
// dev: src/cli/commands/ → repo-root .claude/skills
|
|
29363
|
-
resolve39(here, "../../..",
|
|
29364
|
-
// bundled: dist/cli.js → dist/cli/tutorial
|
|
29365
|
-
resolve39(here, "cli/tutorial",
|
|
29366
|
-
// bundled fallback: any-depth → cli/tutorial
|
|
29367
|
-
resolve39(here, "../cli/tutorial",
|
|
30025
|
+
// dev: src/cli/commands/ → repo-root .claude/skills/sm-tutorial/
|
|
30026
|
+
resolve39(here, "../../..", SKILL_SOURCE_DIR),
|
|
30027
|
+
// bundled: dist/cli.js → dist/cli/tutorial/sm-tutorial (sibling)
|
|
30028
|
+
resolve39(here, "cli/tutorial", SKILL_SLUG),
|
|
30029
|
+
// bundled fallback: any-depth → cli/tutorial/sm-tutorial
|
|
30030
|
+
resolve39(here, "../cli/tutorial", SKILL_SLUG)
|
|
29368
30031
|
];
|
|
29369
30032
|
for (const candidate of candidates) {
|
|
29370
30033
|
if (existsSync32(candidate) && statSync11(candidate).isDirectory()) {
|
|
29371
|
-
|
|
30034
|
+
cachedSourceDir = candidate;
|
|
29372
30035
|
return candidate;
|
|
29373
30036
|
}
|
|
29374
30037
|
}
|
|
@@ -29592,4 +30255,4 @@ function resolveBareDefault() {
|
|
|
29592
30255
|
process.exit(ExitCode.Error);
|
|
29593
30256
|
}
|
|
29594
30257
|
//# sourceMappingURL=cli.js.map
|
|
29595
|
-
//# debugId=
|
|
30258
|
+
//# debugId=82aaa5c6-6dae-53a6-8d4a-1a673357051c
|