@skill-map/cli 0.53.6 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/tutorial/sm-tutorial/SKILL.md +22 -24
- package/dist/cli/tutorial/sm-tutorial/references/_core.md +8 -7
- package/dist/cli/tutorial/sm-tutorial/references/_manifest.yml +21 -42
- package/dist/cli/tutorial/sm-tutorial/references/fixtures.md +15 -7
- package/dist/cli/tutorial/sm-tutorial/references/part-authoring.md +2 -2
- package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +1 -1
- package/dist/cli/tutorial/sm-tutorial/references/part-connect-harness.md +9 -10
- package/dist/cli/tutorial/sm-tutorial/references/part-daily-loop.md +563 -0
- package/dist/cli/tutorial/sm-tutorial/references/part-mcp.md +5 -1
- package/dist/cli/tutorial/sm-tutorial/references/part-plugins.md +7 -7
- package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +24 -12
- package/dist/cli/tutorial/sm-tutorial/references/part-settings.md +2 -2
- package/dist/cli.js +1019 -718
- package/dist/index.js +129 -15
- package/dist/kernel/index.d.ts +209 -89
- package/dist/kernel/index.js +129 -15
- package/dist/migrations/001_initial.sql +6 -1
- package/dist/ui/chunk-CN6IOM67.js +2 -0
- package/dist/ui/chunk-HPKRDGLH.js +123 -0
- package/dist/ui/{chunk-NBXEOYS4.js → chunk-LREXXX7T.js} +1 -1
- package/dist/ui/{chunk-7OJPO3XD.js → chunk-RGB5FBZL.js} +31 -30
- package/dist/ui/{chunk-4CXAL43H.js → chunk-XAM6VKXM.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/ui/{main-O4DGYJ62.js → main-7YXBWYHE.js} +3 -3
- package/dist/ui/{styles-L6FZYH7X.css → styles-HRJG67XW.css} +1 -1
- package/migrations/001_initial.sql +6 -1
- package/package.json +2 -2
- package/dist/cli/tutorial/sm-tutorial/references/part-live-site.md +0 -155
- package/dist/cli/tutorial/sm-tutorial/references/part-maintain.md +0 -284
- package/dist/cli/tutorial/sm-tutorial/references/part-run-harness.md +0 -181
- package/dist/ui/chunk-HWQTV6ZL.js +0 -2
- package/dist/ui/chunk-PRP3JSUU.js +0 -123
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// kernel/i18n/registry.texts.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]="1f2129d2-2039-52b2-acb6-f7adcf338044")}catch(e){}}();
|
|
4
4
|
var REGISTRY_TEXTS = {
|
|
5
5
|
duplicateExtension: "Extension already registered: {{kind}}:{{qualifiedId}}",
|
|
6
6
|
unknownKind: "Unknown extension kind: {{kind}}",
|
|
@@ -102,7 +102,7 @@ import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
|
|
|
102
102
|
// package.json
|
|
103
103
|
var package_default = {
|
|
104
104
|
name: "@skill-map/cli",
|
|
105
|
-
version: "0.
|
|
105
|
+
version: "0.55.0",
|
|
106
106
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
107
107
|
license: "MIT",
|
|
108
108
|
type: "module",
|
|
@@ -1027,8 +1027,9 @@ function buildVirtualNode(extractor, emitted, emitter) {
|
|
|
1027
1027
|
if (emitted.frontmatter) node.frontmatter = emitted.frontmatter;
|
|
1028
1028
|
return node;
|
|
1029
1029
|
}
|
|
1030
|
+
var KNOWN_LINK_KINDS = ["invokes", "references", "mentions", "supersedes", "points"];
|
|
1030
1031
|
function validateLink(extractor, link, emitter) {
|
|
1031
|
-
const knownKinds =
|
|
1032
|
+
const knownKinds = KNOWN_LINK_KINDS;
|
|
1032
1033
|
if (!knownKinds.includes(link.kind)) {
|
|
1033
1034
|
const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
|
|
1034
1035
|
emitter.emit(
|
|
@@ -1063,7 +1064,6 @@ function validateLink(extractor, link, emitter) {
|
|
|
1063
1064
|
const confidence = c ?? ConfidenceTier.MEDIUM;
|
|
1064
1065
|
return { ...link, confidence };
|
|
1065
1066
|
}
|
|
1066
|
-
var KNOWN_LINK_KINDS = ["invokes", "references", "mentions", "supersedes"];
|
|
1067
1067
|
function validateSignal(extractor, signal, emitter) {
|
|
1068
1068
|
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
1069
1069
|
if (!Array.isArray(signal.candidates) || signal.candidates.length === 0) {
|
|
@@ -1190,6 +1190,81 @@ function isExternalUrlLink(link) {
|
|
|
1190
1190
|
return EXTERNAL_URL_SCHEME_RE.test(link.target);
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
|
+
// kernel/orchestrator/action-projections.ts
|
|
1194
|
+
function runActionProjections(actions, nodes, links, emitter) {
|
|
1195
|
+
const contributions = [];
|
|
1196
|
+
const contributionErrors = [];
|
|
1197
|
+
const validators = loadSchemaValidators();
|
|
1198
|
+
for (const action of actions) {
|
|
1199
|
+
if (typeof action.project !== "function") continue;
|
|
1200
|
+
const qualifiedId = qualifiedExtensionId(action.pluginId, action.id);
|
|
1201
|
+
const declaredContributions = readDeclaredContributionRefs(action);
|
|
1202
|
+
const emitContribution = (nodePath, ref, payload) => {
|
|
1203
|
+
const declared = typeof ref === "object" && ref !== null ? declaredContributions.get(ref) : void 0;
|
|
1204
|
+
if (!declared) {
|
|
1205
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionUndeclaredRef, {
|
|
1206
|
+
extractorId: qualifiedId,
|
|
1207
|
+
nodePath
|
|
1208
|
+
});
|
|
1209
|
+
emitExtensionError(emitter, qualifiedId, nodePath, {
|
|
1210
|
+
phase: "emitContribution",
|
|
1211
|
+
reason: "undeclared-contribution-ref",
|
|
1212
|
+
message
|
|
1213
|
+
});
|
|
1214
|
+
contributionErrors.push({
|
|
1215
|
+
pluginId: action.pluginId,
|
|
1216
|
+
extensionId: action.id,
|
|
1217
|
+
nodePath,
|
|
1218
|
+
reason: "undeclared-contribution-ref",
|
|
1219
|
+
message,
|
|
1220
|
+
emittedAt: Date.now()
|
|
1221
|
+
});
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const result = validators.validateContributionPayload(declared.slot, payload);
|
|
1225
|
+
if (!result.ok) {
|
|
1226
|
+
const message = tx(ORCHESTRATOR_TEXTS.extensionErrorContributionPayloadInvalid, {
|
|
1227
|
+
extractorId: qualifiedId,
|
|
1228
|
+
contributionId: declared.id,
|
|
1229
|
+
nodePath,
|
|
1230
|
+
slot: declared.slot,
|
|
1231
|
+
errors: result.errors
|
|
1232
|
+
});
|
|
1233
|
+
emitExtensionError(emitter, qualifiedId, nodePath, {
|
|
1234
|
+
phase: "emitContribution",
|
|
1235
|
+
contributionId: declared.id,
|
|
1236
|
+
slot: declared.slot,
|
|
1237
|
+
reason: result.errors,
|
|
1238
|
+
message
|
|
1239
|
+
});
|
|
1240
|
+
contributionErrors.push({
|
|
1241
|
+
pluginId: action.pluginId,
|
|
1242
|
+
extensionId: action.id,
|
|
1243
|
+
nodePath,
|
|
1244
|
+
reason: result.errors,
|
|
1245
|
+
message,
|
|
1246
|
+
contributionId: declared.id,
|
|
1247
|
+
slot: declared.slot,
|
|
1248
|
+
emittedAt: Date.now()
|
|
1249
|
+
});
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
contributions.push({
|
|
1253
|
+
pluginId: action.pluginId,
|
|
1254
|
+
extensionId: action.id,
|
|
1255
|
+
nodePath,
|
|
1256
|
+
contributionId: declared.id,
|
|
1257
|
+
slot: declared.slot,
|
|
1258
|
+
payload,
|
|
1259
|
+
emittedAt: Date.now()
|
|
1260
|
+
});
|
|
1261
|
+
};
|
|
1262
|
+
const ctx = { nodes, links, emitContribution };
|
|
1263
|
+
action.project(ctx);
|
|
1264
|
+
}
|
|
1265
|
+
return { contributions, contributionErrors };
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1193
1268
|
// kernel/orchestrator/analyzers.ts
|
|
1194
1269
|
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, orphanJobFiles, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, signals, seedIssues = []) {
|
|
1195
1270
|
const issues = [...seedIssues];
|
|
@@ -1560,17 +1635,26 @@ function readDirname(node) {
|
|
|
1560
1635
|
|
|
1561
1636
|
// kernel/orchestrator/lift-resolved-link-confidence.ts
|
|
1562
1637
|
var RESERVED_TARGET_CONFIDENCE = 0.1;
|
|
1638
|
+
var BROKEN_TARGET_CONFIDENCE = 0.5;
|
|
1563
1639
|
function liftResolvedLinkConfidence(links, nodes, ctx) {
|
|
1564
1640
|
if (!links.some((l) => l.confidence < 1)) return;
|
|
1565
1641
|
const indexes = buildIndexes(nodes, ctx);
|
|
1566
1642
|
for (const link of links) {
|
|
1567
|
-
if (link.confidence
|
|
1568
|
-
const resolution = resolve5(link, indexes, ctx);
|
|
1569
|
-
if (resolution === "none") continue;
|
|
1570
|
-
link.confidence = ctx.reservedNodePaths.has(resolution) ? RESERVED_TARGET_CONFIDENCE : 1;
|
|
1571
|
-
link.resolvedTarget = resolution;
|
|
1643
|
+
if (link.confidence < 1) applyResolution(link, indexes, ctx);
|
|
1572
1644
|
}
|
|
1573
1645
|
}
|
|
1646
|
+
function applyResolution(link, indexes, ctx) {
|
|
1647
|
+
const resolution = resolve5(link, indexes, ctx);
|
|
1648
|
+
if (resolution === "none") {
|
|
1649
|
+
if (isGenuinelyBroken(link, indexes)) {
|
|
1650
|
+
link.confidence = Math.min(link.confidence, BROKEN_TARGET_CONFIDENCE);
|
|
1651
|
+
}
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
link.resolvedTarget = resolution;
|
|
1655
|
+
if (indexes.nodeByPath.get(resolution)?.virtual) return;
|
|
1656
|
+
link.confidence = ctx.reservedNodePaths.has(resolution) ? RESERVED_TARGET_CONFIDENCE : 1;
|
|
1657
|
+
}
|
|
1574
1658
|
function buildIndexes(nodes, ctx) {
|
|
1575
1659
|
const byPath2 = /* @__PURE__ */ new Set();
|
|
1576
1660
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -1586,6 +1670,12 @@ function resolve5(link, indexes, ctx) {
|
|
|
1586
1670
|
if (indexes.byPath.has(link.target)) return link.target;
|
|
1587
1671
|
return resolveByName(link, indexes, ctx);
|
|
1588
1672
|
}
|
|
1673
|
+
function isGenuinelyBroken(link, indexes) {
|
|
1674
|
+
if (indexes.byPath.has(link.target)) return false;
|
|
1675
|
+
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
1676
|
+
if (stripped !== null && indexes.byName.has(stripped)) return false;
|
|
1677
|
+
return true;
|
|
1678
|
+
}
|
|
1589
1679
|
function resolveByName(link, indexes, ctx) {
|
|
1590
1680
|
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
1591
1681
|
if (stripped === null) return "none";
|
|
@@ -2146,11 +2236,11 @@ async function* walkContent(roots, options) {
|
|
|
2146
2236
|
const extensions = options.extensions;
|
|
2147
2237
|
const sizeLimit = buildSizeLimit(options);
|
|
2148
2238
|
for (const root of roots) {
|
|
2149
|
-
for await (const
|
|
2150
|
-
const relPath = relative2(root,
|
|
2239
|
+
for await (const entry of walkRoot(root, root, filter, extensions, sizeLimit)) {
|
|
2240
|
+
const relPath = relative2(root, entry.full).split(sep).join("/");
|
|
2151
2241
|
let raw;
|
|
2152
2242
|
try {
|
|
2153
|
-
raw = await readFile(
|
|
2243
|
+
raw = await readFile(entry.full, "utf8");
|
|
2154
2244
|
} catch {
|
|
2155
2245
|
continue;
|
|
2156
2246
|
}
|
|
@@ -2160,6 +2250,9 @@ async function* walkContent(roots, options) {
|
|
|
2160
2250
|
body: parsed.body,
|
|
2161
2251
|
frontmatterRaw: parsed.frontmatterRaw,
|
|
2162
2252
|
frontmatter: parsed.frontmatter,
|
|
2253
|
+
// File mtime from the TOCTOU `lstat` (zero extra syscalls).
|
|
2254
|
+
// Threaded onto the persisted `Node` as `modifiedAtMs`.
|
|
2255
|
+
modifiedAtMs: entry.modifiedAtMs,
|
|
2163
2256
|
// Audit L1: forward parser diagnostics (e.g. malformed YAML)
|
|
2164
2257
|
// through the IRawNode surface so the orchestrator can
|
|
2165
2258
|
// convert them into warn-level kernel `Issue` rows. Omitted
|
|
@@ -2200,7 +2293,7 @@ async function* walkRoot(root, current, filter, extensions, sizeLimit) {
|
|
|
2200
2293
|
sizeLimit.onOversizedFile?.({ path: rel, bytes: s.size });
|
|
2201
2294
|
continue;
|
|
2202
2295
|
}
|
|
2203
|
-
yield full;
|
|
2296
|
+
yield { full, modifiedAtMs: Math.round(s.mtimeMs) };
|
|
2204
2297
|
} catch {
|
|
2205
2298
|
}
|
|
2206
2299
|
}
|
|
@@ -2491,6 +2584,7 @@ function buildNode(args) {
|
|
|
2491
2584
|
externalRefsCount: 0,
|
|
2492
2585
|
frontmatter: args.frontmatter
|
|
2493
2586
|
};
|
|
2587
|
+
if (args.modifiedAtMs !== void 0) node.modifiedAtMs = args.modifiedAtMs;
|
|
2494
2588
|
if (args.encoder) {
|
|
2495
2589
|
node.tokens = countTokens(args.encoder, args.frontmatterRaw, args.body);
|
|
2496
2590
|
}
|
|
@@ -2606,7 +2700,10 @@ function buildFreshNodeAndValidateFrontmatter(opts) {
|
|
|
2606
2700
|
frontmatter: opts.raw.frontmatter,
|
|
2607
2701
|
bodyHash: opts.bodyHash,
|
|
2608
2702
|
frontmatterHash: opts.frontmatterHash,
|
|
2609
|
-
encoder: opts.encoder
|
|
2703
|
+
encoder: opts.encoder,
|
|
2704
|
+
// Thread the walker's mtime through; `buildNode` only attaches it
|
|
2705
|
+
// when present, so virtual / walk()-without-stat sources stay absent.
|
|
2706
|
+
modifiedAtMs: opts.raw.modifiedAtMs
|
|
2610
2707
|
});
|
|
2611
2708
|
const frontmatterIssues = [];
|
|
2612
2709
|
if (opts.raw.parseIssues && opts.raw.parseIssues.length > 0) {
|
|
@@ -3056,6 +3153,13 @@ async function runScanInternal(_kernel, options) {
|
|
|
3056
3153
|
walked.frontmatterIssues
|
|
3057
3154
|
);
|
|
3058
3155
|
mergeAnalyzerEmissions(walked, analyzerResult, exts.analyzers);
|
|
3156
|
+
const projectionResult = runActionProjections(
|
|
3157
|
+
exts.actions ?? [],
|
|
3158
|
+
walked.nodes,
|
|
3159
|
+
walked.internalLinks,
|
|
3160
|
+
emitter
|
|
3161
|
+
);
|
|
3162
|
+
mergeActionProjections(walked, projectionResult, exts.actions);
|
|
3059
3163
|
const issues = analyzerResult.issues;
|
|
3060
3164
|
const silenced = options.ignoreFilter ? (path) => options.ignoreFilter.ignores(path) : void 0;
|
|
3061
3165
|
const renameOps = prior ? detectRenamesAndOrphans(prior, walked.nodes, issues, silenced) : [];
|
|
@@ -3164,6 +3268,16 @@ function mergeAnalyzerEmissions(walked, analyzerResult, analyzers) {
|
|
|
3164
3268
|
}
|
|
3165
3269
|
}
|
|
3166
3270
|
}
|
|
3271
|
+
function mergeActionProjections(walked, projectionResult, actions) {
|
|
3272
|
+
for (const c of projectionResult.contributions) walked.contributions.push(c);
|
|
3273
|
+
for (const e of projectionResult.contributionErrors) walked.contributionErrors.push(e);
|
|
3274
|
+
for (const action of actions ?? []) {
|
|
3275
|
+
if (action.ui === void 0 || typeof action.project !== "function") continue;
|
|
3276
|
+
for (const node of walked.nodes) {
|
|
3277
|
+
walked.freshlyRunTuples.add(`${action.pluginId}\0${action.id}\0${node.path}`);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3167
3281
|
function buildScanStats(walked, issues, start) {
|
|
3168
3282
|
return {
|
|
3169
3283
|
// `filesSkipped` is "files walked but not classified by any
|
|
@@ -3632,4 +3746,4 @@ export {
|
|
|
3632
3746
|
runScanWithRenames
|
|
3633
3747
|
};
|
|
3634
3748
|
//# sourceMappingURL=index.js.map
|
|
3635
|
-
//# debugId=
|
|
3749
|
+
//# debugId=1f2129d2-2039-52b2-acb6-f7adcf338044
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -1,87 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extension registry, six kinds, first-class, loaded through a single API.
|
|
3
|
-
*
|
|
4
|
-
* The `IExtension` shape is aligned with `spec/schemas/extensions/base.schema.json`.
|
|
5
|
-
* Kind-specific manifests (provider / extractor / analyzer / action / formatter /
|
|
6
|
-
* hook) extend this base structurally; the registry stores the base view
|
|
7
|
-
* and each kind's code carries its own fuller type where needed.
|
|
8
|
-
*
|
|
9
|
-
* **Spec § A.6, qualified ids.** Every extension is keyed in the registry
|
|
10
|
-
* by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash-command`,
|
|
11
|
-
* `my-plugin/my-extractor`). `IExtension.id` carries the **short** id as authored;
|
|
12
|
-
* `IExtension.pluginId` carries the namespace; the registry composes the
|
|
13
|
-
* qualifier internally and exposes lookup APIs that operate on either form
|
|
14
|
-
* (qualified for direct lookup, kind-scoped listing for enumeration).
|
|
15
|
-
*
|
|
16
|
-
* Boot invariant: `new Registry()` is empty. `registry.totalCount() === 0`
|
|
17
|
-
* when the kernel boots with zero extensions. This is the data side of the
|
|
18
|
-
* `kernel-empty-boot` conformance contract.
|
|
19
|
-
*/
|
|
20
|
-
type ExtensionKind = 'provider' | 'extractor' | 'analyzer' | 'action' | 'formatter' | 'hook';
|
|
21
|
-
declare const EXTENSION_KINDS: readonly ExtensionKind[];
|
|
22
|
-
interface IExtension {
|
|
23
|
-
/** Short (unqualified) extension id, injected by the loader from the leaf folder name. */
|
|
24
|
-
id: string;
|
|
25
|
-
/** Owning plugin namespace, injected by the loader from the plugin folder name. */
|
|
26
|
-
pluginId: string;
|
|
27
|
-
kind: ExtensionKind;
|
|
28
|
-
version: string;
|
|
29
|
-
/** Required short description; surfaced in `sm <kind>s list` and the UI. */
|
|
30
|
-
description: string;
|
|
31
|
-
entry?: string;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Compose the qualified registry key for an extension. Single source of
|
|
35
|
-
* truth so callers don't reinvent the format and a future change (e.g. a
|
|
36
|
-
* different separator) lands in one place.
|
|
37
|
-
*/
|
|
38
|
-
declare function qualifiedExtensionId(pluginId: string, id: string): string;
|
|
39
|
-
declare class DuplicateExtensionError extends Error {
|
|
40
|
-
constructor(kind: ExtensionKind, qualifiedId: string);
|
|
41
|
-
}
|
|
42
|
-
declare class Registry {
|
|
43
|
-
#private;
|
|
44
|
-
constructor();
|
|
45
|
-
register(ext: IExtension): void;
|
|
46
|
-
/**
|
|
47
|
-
* Lookup by qualified id (`<pluginId>/<id>`). Returns `undefined` when
|
|
48
|
-
* no extension of that kind is registered under the qualifier.
|
|
49
|
-
*/
|
|
50
|
-
get(kind: ExtensionKind, qualifiedId: string): IExtension | undefined;
|
|
51
|
-
/**
|
|
52
|
-
* Convenience wrapper that composes the qualified id for the caller.
|
|
53
|
-
* Equivalent to `get(kind, qualifiedExtensionId(pluginId, id))`.
|
|
54
|
-
*/
|
|
55
|
-
find(kind: ExtensionKind, pluginId: string, id: string): IExtension | undefined;
|
|
56
|
-
all(kind: ExtensionKind): IExtension[];
|
|
57
|
-
count(kind: ExtensionKind): number;
|
|
58
|
-
totalCount(): number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Step 9.6.6, runtime annotation-contribution catalog types.
|
|
63
|
-
*
|
|
64
|
-
* Lives in its own module (rather than `kernel/index.ts`) so consumers
|
|
65
|
-
* deep inside the kernel, `IAnalyzerContext`, the BFF route factories,
|
|
66
|
-
* future Action contexts, can depend on the catalog shape without
|
|
67
|
-
* dragging the whole kernel barrel and risking a cycle.
|
|
68
|
-
*/
|
|
69
|
-
/**
|
|
70
|
-
* Single row of the runtime annotation-contribution catalog surfaced by
|
|
71
|
-
* `kernel.getRegisteredAnnotationKeys()`. One row per (plugin × key)
|
|
72
|
-
* tuple. Built-in catalog keys from `annotations.schema.json` are NOT
|
|
73
|
-
* included, this catalog is plugin-only; the UI knows the built-in
|
|
74
|
-
* catalog via the schema bundle.
|
|
75
|
-
*/
|
|
76
|
-
interface IRegisteredAnnotationKey {
|
|
77
|
-
pluginId: string;
|
|
78
|
-
key: string;
|
|
79
|
-
location: 'namespaced' | 'root';
|
|
80
|
-
ownership: 'exclusive' | 'shared';
|
|
81
|
-
/** Inline JSON Schema as declared in the manifest (not the AJV compiled validator). */
|
|
82
|
-
schema: Record<string, unknown>;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
1
|
/**
|
|
86
2
|
* Closed enum of view slot names. Mirror of
|
|
87
3
|
* `spec/schemas/view-slots.schema.json#/$defs/SlotName`.
|
|
@@ -553,6 +469,30 @@ type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueLis
|
|
|
553
469
|
* folder name and is injected at load time.
|
|
554
470
|
*/
|
|
555
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Lifecycle label an extension manifest MAY declare. Renders as a badge
|
|
474
|
+
* next to the extension in `sm plugins list <id>` / `sm plugins show` and
|
|
475
|
+
* the Settings plugins panel for the non-default values.
|
|
476
|
+
*
|
|
477
|
+
* Two values ALSO change behaviour: `experimental` (not ready yet) and
|
|
478
|
+
* `deprecated` (on its way out) flip the extension's installed default
|
|
479
|
+
* to DISABLED, so the extension does not load (does not run, does not
|
|
480
|
+
* register) unless the operator opts in (`sm plugins enable
|
|
481
|
+
* <plugin>/<ext>`, the Settings toggle, or a `settings.json` /
|
|
482
|
+
* `config_plugins` override). The opt-in is a plain enable override,
|
|
483
|
+
* once set it wins over the installed default exactly like any other
|
|
484
|
+
* extension (so a deprecated extension can still be kept running during
|
|
485
|
+
* a migration). The remaining values are presentation-only and default
|
|
486
|
+
* to ENABLED: `beta` runs by default with a badge, `stable` (declared
|
|
487
|
+
* or defaulted) runs with no badge. Missing == `stable` == enabled, no
|
|
488
|
+
* badge. Mirrors
|
|
489
|
+
* `spec/schemas/extensions/base.schema.json#/properties/stability`.
|
|
490
|
+
*
|
|
491
|
+
* Deliberately a superset of the node-level annotations enum (which has
|
|
492
|
+
* no `beta`): this describes the maturity of the extension itself, not
|
|
493
|
+
* of a scanned node.
|
|
494
|
+
*/
|
|
495
|
+
type TExtensionStability = 'experimental' | 'beta' | 'stable' | 'deprecated';
|
|
556
496
|
/**
|
|
557
497
|
* Single declaration of an extension's optional sidecar annotation
|
|
558
498
|
* contribution. The annotation key is the extension's id (the leaf folder
|
|
@@ -605,6 +545,13 @@ interface IExtensionBase {
|
|
|
605
545
|
version: string;
|
|
606
546
|
/** Required short description shown by `sm <kind>s list` / UI. */
|
|
607
547
|
description: string;
|
|
548
|
+
/**
|
|
549
|
+
* Optional lifecycle label (`experimental` / `beta` / `stable` /
|
|
550
|
+
* `deprecated`). Missing == `stable`, no badge rendered. `experimental`
|
|
551
|
+
* and `deprecated` additionally flip the installed default to disabled.
|
|
552
|
+
* See `TExtensionStability` for the full semantics.
|
|
553
|
+
*/
|
|
554
|
+
stability?: TExtensionStability;
|
|
608
555
|
/**
|
|
609
556
|
* Optional inspector-only ordering hint (default 100). Inside the
|
|
610
557
|
* owning plugin's inspector section, orders this extension's
|
|
@@ -638,14 +585,107 @@ interface IExtensionBase {
|
|
|
638
585
|
* `viewContributions` with the structure-as-truth refactor. Each
|
|
639
586
|
* entry maps a local contribution id (kebab-case, unique within the
|
|
640
587
|
* extension) to an `IViewContribution` that picks a view slot by
|
|
641
|
-
* name from the closed catalog.
|
|
642
|
-
*
|
|
588
|
+
* name from the closed catalog. Declared by `extractor` and
|
|
589
|
+
* `analyzer` kinds (emitted during scan / graph evaluation) and by
|
|
590
|
+
* `action` kinds (emitted from the Action's scan-time `project()`
|
|
591
|
+
* self-projection, see `IActionProjectionContext`).
|
|
643
592
|
*/
|
|
644
593
|
ui?: Record<string, IViewContribution>;
|
|
645
594
|
/** Runtime-only, absolute path of the extension entry file. */
|
|
646
595
|
entry?: string;
|
|
647
596
|
}
|
|
648
597
|
|
|
598
|
+
/**
|
|
599
|
+
* Extension registry, six kinds, first-class, loaded through a single API.
|
|
600
|
+
*
|
|
601
|
+
* The `IExtension` shape is aligned with `spec/schemas/extensions/base.schema.json`.
|
|
602
|
+
* Kind-specific manifests (provider / extractor / analyzer / action / formatter /
|
|
603
|
+
* hook) extend this base structurally; the registry stores the base view
|
|
604
|
+
* and each kind's code carries its own fuller type where needed.
|
|
605
|
+
*
|
|
606
|
+
* **Spec § A.6, qualified ids.** Every extension is keyed in the registry
|
|
607
|
+
* by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash-command`,
|
|
608
|
+
* `my-plugin/my-extractor`). `IExtension.id` carries the **short** id as authored;
|
|
609
|
+
* `IExtension.pluginId` carries the namespace; the registry composes the
|
|
610
|
+
* qualifier internally and exposes lookup APIs that operate on either form
|
|
611
|
+
* (qualified for direct lookup, kind-scoped listing for enumeration).
|
|
612
|
+
*
|
|
613
|
+
* Boot invariant: `new Registry()` is empty. `registry.totalCount() === 0`
|
|
614
|
+
* when the kernel boots with zero extensions. This is the data side of the
|
|
615
|
+
* `kernel-empty-boot` conformance contract.
|
|
616
|
+
*/
|
|
617
|
+
|
|
618
|
+
type ExtensionKind = 'provider' | 'extractor' | 'analyzer' | 'action' | 'formatter' | 'hook';
|
|
619
|
+
declare const EXTENSION_KINDS: readonly ExtensionKind[];
|
|
620
|
+
interface IExtension {
|
|
621
|
+
/** Short (unqualified) extension id, injected by the loader from the leaf folder name. */
|
|
622
|
+
id: string;
|
|
623
|
+
/** Owning plugin namespace, injected by the loader from the plugin folder name. */
|
|
624
|
+
pluginId: string;
|
|
625
|
+
kind: ExtensionKind;
|
|
626
|
+
version: string;
|
|
627
|
+
/** Required short description; surfaced in `sm <kind>s list` and the UI. */
|
|
628
|
+
description: string;
|
|
629
|
+
/**
|
|
630
|
+
* Optional lifecycle label (`IExtensionBase.stability`). Carried on the
|
|
631
|
+
* registry view so the enabled-resolver can read it: `experimental`
|
|
632
|
+
* flips an extension's installed default to disabled. Absent == stable.
|
|
633
|
+
*/
|
|
634
|
+
stability?: TExtensionStability;
|
|
635
|
+
entry?: string;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Compose the qualified registry key for an extension. Single source of
|
|
639
|
+
* truth so callers don't reinvent the format and a future change (e.g. a
|
|
640
|
+
* different separator) lands in one place.
|
|
641
|
+
*/
|
|
642
|
+
declare function qualifiedExtensionId(pluginId: string, id: string): string;
|
|
643
|
+
declare class DuplicateExtensionError extends Error {
|
|
644
|
+
constructor(kind: ExtensionKind, qualifiedId: string);
|
|
645
|
+
}
|
|
646
|
+
declare class Registry {
|
|
647
|
+
#private;
|
|
648
|
+
constructor();
|
|
649
|
+
register(ext: IExtension): void;
|
|
650
|
+
/**
|
|
651
|
+
* Lookup by qualified id (`<pluginId>/<id>`). Returns `undefined` when
|
|
652
|
+
* no extension of that kind is registered under the qualifier.
|
|
653
|
+
*/
|
|
654
|
+
get(kind: ExtensionKind, qualifiedId: string): IExtension | undefined;
|
|
655
|
+
/**
|
|
656
|
+
* Convenience wrapper that composes the qualified id for the caller.
|
|
657
|
+
* Equivalent to `get(kind, qualifiedExtensionId(pluginId, id))`.
|
|
658
|
+
*/
|
|
659
|
+
find(kind: ExtensionKind, pluginId: string, id: string): IExtension | undefined;
|
|
660
|
+
all(kind: ExtensionKind): IExtension[];
|
|
661
|
+
count(kind: ExtensionKind): number;
|
|
662
|
+
totalCount(): number;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Step 9.6.6, runtime annotation-contribution catalog types.
|
|
667
|
+
*
|
|
668
|
+
* Lives in its own module (rather than `kernel/index.ts`) so consumers
|
|
669
|
+
* deep inside the kernel, `IAnalyzerContext`, the BFF route factories,
|
|
670
|
+
* future Action contexts, can depend on the catalog shape without
|
|
671
|
+
* dragging the whole kernel barrel and risking a cycle.
|
|
672
|
+
*/
|
|
673
|
+
/**
|
|
674
|
+
* Single row of the runtime annotation-contribution catalog surfaced by
|
|
675
|
+
* `kernel.getRegisteredAnnotationKeys()`. One row per (plugin × key)
|
|
676
|
+
* tuple. Built-in catalog keys from `annotations.schema.json` are NOT
|
|
677
|
+
* included, this catalog is plugin-only; the UI knows the built-in
|
|
678
|
+
* catalog via the schema bundle.
|
|
679
|
+
*/
|
|
680
|
+
interface IRegisteredAnnotationKey {
|
|
681
|
+
pluginId: string;
|
|
682
|
+
key: string;
|
|
683
|
+
location: 'namespaced' | 'root';
|
|
684
|
+
ownership: 'exclusive' | 'shared';
|
|
685
|
+
/** Inline JSON Schema as declared in the manifest (not the AJV compiled validator). */
|
|
686
|
+
schema: Record<string, unknown>;
|
|
687
|
+
}
|
|
688
|
+
|
|
649
689
|
/**
|
|
650
690
|
* Domain types, byte-aligned with `spec/schemas/{node,link,issue,scan-result}.schema.json`.
|
|
651
691
|
*
|
|
@@ -748,7 +788,7 @@ interface IExtensionBase {
|
|
|
748
788
|
* `NodeKind` when the code is intentionally claude-catalog-specific.
|
|
749
789
|
*/
|
|
750
790
|
type NodeKind = 'skill' | 'agent' | 'command' | 'markdown';
|
|
751
|
-
type LinkKind = 'invokes' | 'references' | 'mentions' | 'supersedes';
|
|
791
|
+
type LinkKind = 'invokes' | 'references' | 'mentions' | 'supersedes' | 'points';
|
|
752
792
|
/**
|
|
753
793
|
* Extractor's self-assessed confidence, normalized to `[0..1]`. Drives
|
|
754
794
|
* UI edge opacity in the graph view (more confident = more opaque edge).
|
|
@@ -878,6 +918,16 @@ interface Node {
|
|
|
878
918
|
externalRefs?: IExternalRef[];
|
|
879
919
|
frontmatter?: Record<string, unknown>;
|
|
880
920
|
tokens?: TripleSplit;
|
|
921
|
+
/**
|
|
922
|
+
* File modification time (`mtime`) in Unix milliseconds, captured at
|
|
923
|
+
* scan time from the on-disk `lstat`. Absent for virtual / derived
|
|
924
|
+
* nodes (`virtual === true`, no backing file) and for nodes built by a
|
|
925
|
+
* Provider `walk()` that does not stat its sources. Persisted to
|
|
926
|
+
* `scan_nodes.modified_at_ms` and surfaced on `/api/nodes` /
|
|
927
|
+
* `/api/scan` so the UI can show and sort a "last modified" column.
|
|
928
|
+
* NOT content: never participates in `bodyHash` / `frontmatterHash`.
|
|
929
|
+
*/
|
|
930
|
+
modifiedAtMs?: number;
|
|
881
931
|
/**
|
|
882
932
|
* Step 9.6.2, sidecar denormalisation surface. Populated by the
|
|
883
933
|
* orchestrator at scan time; absent when the orchestrator did not
|
|
@@ -1441,6 +1491,14 @@ interface ILoadedExtension {
|
|
|
1441
1491
|
*/
|
|
1442
1492
|
pluginId: string;
|
|
1443
1493
|
version: string;
|
|
1494
|
+
/**
|
|
1495
|
+
* Optional lifecycle label copied verbatim from the validated
|
|
1496
|
+
* manifest (`IExtensionBase.stability`). Stamped here by the loader
|
|
1497
|
+
* so consumers (CLI list/show, BFF projection) read a typed field
|
|
1498
|
+
* instead of shape-checking `instance`. Absent when the manifest
|
|
1499
|
+
* does not declare it.
|
|
1500
|
+
*/
|
|
1501
|
+
stability?: TExtensionStability;
|
|
1444
1502
|
entryPath: string;
|
|
1445
1503
|
/** Raw module namespace as returned by the dynamic `import()`. */
|
|
1446
1504
|
module: unknown;
|
|
@@ -2163,6 +2221,15 @@ interface IRawNode {
|
|
|
2163
2221
|
frontmatterRaw: string;
|
|
2164
2222
|
/** Parsed frontmatter, or `{}` when absent / unparseable. */
|
|
2165
2223
|
frontmatter: Record<string, unknown>;
|
|
2224
|
+
/**
|
|
2225
|
+
* File modification time (`mtime`) in Unix milliseconds, captured by
|
|
2226
|
+
* the kernel walker from the same `lstat` that guards the read (zero
|
|
2227
|
+
* extra syscalls). Threaded onto the persisted `Node` as
|
|
2228
|
+
* `modifiedAtMs`. Optional: a Provider that ships its own `walk()` and
|
|
2229
|
+
* does not stat its sources MAY omit it; virtual / derived nodes carry
|
|
2230
|
+
* no file and never set it.
|
|
2231
|
+
*/
|
|
2232
|
+
modifiedAtMs?: number;
|
|
2166
2233
|
/**
|
|
2167
2234
|
* Parser diagnostics (audit L1). Populated by the walker when the
|
|
2168
2235
|
* parser surfaced `IParseIssue` entries (e.g. malformed YAML).
|
|
@@ -2736,8 +2803,8 @@ interface IEmittedNode {
|
|
|
2736
2803
|
interface IExtractorCallbacks {
|
|
2737
2804
|
/**
|
|
2738
2805
|
* Emit a single Link. Validated against the global closed enum of
|
|
2739
|
-
* link kinds (`invokes`, `references`, `mentions`, `supersedes
|
|
2740
|
-
* before insertion; off-enum kinds drop silently with an
|
|
2806
|
+
* link kinds (`invokes`, `references`, `mentions`, `supersedes`,
|
|
2807
|
+
* `points`) before insertion; off-enum kinds drop silently with an
|
|
2741
2808
|
* `extension.error` event.
|
|
2742
2809
|
*/
|
|
2743
2810
|
emitLink(link: Link): void;
|
|
@@ -3099,6 +3166,32 @@ interface IActionContext {
|
|
|
3099
3166
|
*/
|
|
3100
3167
|
settings: Record<string, unknown>;
|
|
3101
3168
|
}
|
|
3169
|
+
/**
|
|
3170
|
+
* Read-only graph context handed to an Action's scan-time `project()`
|
|
3171
|
+
* method. Mirrors the Analyzer emit path (`IAnalyzerContext`): the
|
|
3172
|
+
* Action sees the full merged graph (`nodes` + `links`) and emits its
|
|
3173
|
+
* own per-node view contributions via `emitContribution`, supplying the
|
|
3174
|
+
* target node path explicitly because, like the Analyzer, it walks the
|
|
3175
|
+
* whole graph rather than running per-node.
|
|
3176
|
+
*
|
|
3177
|
+
* The contribution is declared in the Action's manifest `ui` map and
|
|
3178
|
+
* passed BY REFERENCE (same object-identity model as Extractor /
|
|
3179
|
+
* Analyzer emit). The orchestrator validates the payload against the
|
|
3180
|
+
* slot's schema at call time, dropping invalid emissions with an
|
|
3181
|
+
* `extension.error` event.
|
|
3182
|
+
*
|
|
3183
|
+
* `project()` is strictly DETERMINISTIC and side-effect-free: no writes,
|
|
3184
|
+
* no runner, no IO. It runs during the scan's contribution phase on
|
|
3185
|
+
* EVERY scan, exactly like an Analyzer's emit path, so its cost is the
|
|
3186
|
+
* same per-scan cost as today's projector analyzers. Even an Action
|
|
3187
|
+
* whose `invoke` is `mode: 'probabilistic'` MUST keep `project()`
|
|
3188
|
+
* deterministic, only `invoke` may be probabilistic.
|
|
3189
|
+
*/
|
|
3190
|
+
interface IActionProjectionContext {
|
|
3191
|
+
readonly nodes: readonly Node[];
|
|
3192
|
+
readonly links: readonly Link[];
|
|
3193
|
+
emitContribution(nodePath: string, ref: IViewContribution, payload: unknown): void;
|
|
3194
|
+
}
|
|
3102
3195
|
/**
|
|
3103
3196
|
* Declarative filter applied by `--all` fan-out, UI button gating, and
|
|
3104
3197
|
* `sm actions show`. Same shape used by Extractor and Analyzer so the
|
|
@@ -3155,6 +3248,23 @@ interface IAction extends IExtensionBase {
|
|
|
3155
3248
|
* kernel materialises any returned `writes` after the call.
|
|
3156
3249
|
*/
|
|
3157
3250
|
invoke?: <TInput, TReport>(input: TInput, ctx: IActionContext) => IActionResult<TReport>;
|
|
3251
|
+
/**
|
|
3252
|
+
* Optional scan-time self-projection. When present, the orchestrator
|
|
3253
|
+
* calls it during the contribution phase (right after the analyzer
|
|
3254
|
+
* pass) with read-only graph access, and the Action emits its OWN
|
|
3255
|
+
* `inspector.action.button` (or any declared `ui` contribution) per
|
|
3256
|
+
* node. This replaces the former "projector analyzer" pattern: the
|
|
3257
|
+
* button now lives with the Action that dispatches it, not in a
|
|
3258
|
+
* sibling Analyzer.
|
|
3259
|
+
*
|
|
3260
|
+
* MUST be deterministic and side-effect-free (no writes, no runner,
|
|
3261
|
+
* no IO), exactly like an Analyzer's emit path. The button declares
|
|
3262
|
+
* its own qualified id as `actionId` in the payload. Actions that ship
|
|
3263
|
+
* for the future probabilistic runner / record path leave it absent;
|
|
3264
|
+
* an Action MAY declare both `project` and `invoke` (advertiser +
|
|
3265
|
+
* executor), or only one.
|
|
3266
|
+
*/
|
|
3267
|
+
project?(ctx: IActionProjectionContext): void;
|
|
3158
3268
|
}
|
|
3159
3269
|
|
|
3160
3270
|
/**
|
|
@@ -3621,6 +3731,16 @@ interface IScanExtensions {
|
|
|
3621
3731
|
* advisory until the job subsystem ships once the job subsystem ships.
|
|
3622
3732
|
*/
|
|
3623
3733
|
hooks?: IHook[];
|
|
3734
|
+
/**
|
|
3735
|
+
* Optional enabled actions. When supplied, the orchestrator runs the
|
|
3736
|
+
* action-projection pass right after the analyzer pass: every action
|
|
3737
|
+
* carrying a scan-time `project()` self-projection emits its own view
|
|
3738
|
+
* contributions (e.g. `inspector.action.button`) onto the merged
|
|
3739
|
+
* graph. Actions without `project` (only `invoke`) ride along inert.
|
|
3740
|
+
* Absent → no projection pass runs (the gate is the composed enabled
|
|
3741
|
+
* set, so a disabled / experimental action never reaches here).
|
|
3742
|
+
*/
|
|
3743
|
+
actions?: IAction[];
|
|
3624
3744
|
}
|
|
3625
3745
|
interface RunScanOptions {
|
|
3626
3746
|
/**
|