@skill-map/cli 0.54.0 → 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 +594 -485
- package/dist/index.js +127 -13
- package/dist/kernel/index.d.ts +187 -94
- package/dist/kernel/index.js +127 -13
- package/dist/migrations/001_initial.sql +5 -0
- package/dist/ui/chunk-CN6IOM67.js +2 -0
- package/dist/ui/chunk-HPKRDGLH.js +123 -0
- package/dist/ui/{chunk-CXTU4HQV.js → chunk-LREXXX7T.js} +1 -1
- package/dist/ui/{chunk-BUNPMGDX.js → chunk-RGB5FBZL.js} +28 -28
- package/dist/ui/{chunk-4CXAL43H.js → chunk-XAM6VKXM.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/ui/{main-HP3MOLI2.js → main-7YXBWYHE.js} +3 -3
- package/dist/ui/{styles-4SNVM34O.css → styles-HRJG67XW.css} +1 -1
- package/migrations/001_initial.sql +5 -0
- package/package.json +2 -2
- package/dist/cli/tutorial/sm-tutorial/references/part-live-site.md +0 -155
- package/dist/cli/tutorial/sm-tutorial/references/part-maintain.md +0 -284
- package/dist/cli/tutorial/sm-tutorial/references/part-run-harness.md +0 -181
- package/dist/ui/chunk-DSNBKMYU.js +0 -2
- package/dist/ui/chunk-MVRQGDZJ.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",
|
|
@@ -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`.
|
|
@@ -554,13 +470,24 @@ type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueLis
|
|
|
554
470
|
*/
|
|
555
471
|
|
|
556
472
|
/**
|
|
557
|
-
* Lifecycle label an extension manifest MAY declare.
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
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
|
|
563
489
|
* `spec/schemas/extensions/base.schema.json#/properties/stability`.
|
|
490
|
+
*
|
|
564
491
|
* Deliberately a superset of the node-level annotations enum (which has
|
|
565
492
|
* no `beta`): this describes the maturity of the extension itself, not
|
|
566
493
|
* of a scanned node.
|
|
@@ -620,8 +547,9 @@ interface IExtensionBase {
|
|
|
620
547
|
description: string;
|
|
621
548
|
/**
|
|
622
549
|
* Optional lifecycle label (`experimental` / `beta` / `stable` /
|
|
623
|
-
* `deprecated`). Missing == `stable`, no badge rendered.
|
|
624
|
-
* `
|
|
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.
|
|
625
553
|
*/
|
|
626
554
|
stability?: TExtensionStability;
|
|
627
555
|
/**
|
|
@@ -657,14 +585,107 @@ interface IExtensionBase {
|
|
|
657
585
|
* `viewContributions` with the structure-as-truth refactor. Each
|
|
658
586
|
* entry maps a local contribution id (kebab-case, unique within the
|
|
659
587
|
* extension) to an `IViewContribution` that picks a view slot by
|
|
660
|
-
* name from the closed catalog.
|
|
661
|
-
*
|
|
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`).
|
|
662
592
|
*/
|
|
663
593
|
ui?: Record<string, IViewContribution>;
|
|
664
594
|
/** Runtime-only, absolute path of the extension entry file. */
|
|
665
595
|
entry?: string;
|
|
666
596
|
}
|
|
667
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
|
+
|
|
668
689
|
/**
|
|
669
690
|
* Domain types, byte-aligned with `spec/schemas/{node,link,issue,scan-result}.schema.json`.
|
|
670
691
|
*
|
|
@@ -897,6 +918,16 @@ interface Node {
|
|
|
897
918
|
externalRefs?: IExternalRef[];
|
|
898
919
|
frontmatter?: Record<string, unknown>;
|
|
899
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;
|
|
900
931
|
/**
|
|
901
932
|
* Step 9.6.2, sidecar denormalisation surface. Populated by the
|
|
902
933
|
* orchestrator at scan time; absent when the orchestrator did not
|
|
@@ -2190,6 +2221,15 @@ interface IRawNode {
|
|
|
2190
2221
|
frontmatterRaw: string;
|
|
2191
2222
|
/** Parsed frontmatter, or `{}` when absent / unparseable. */
|
|
2192
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;
|
|
2193
2233
|
/**
|
|
2194
2234
|
* Parser diagnostics (audit L1). Populated by the walker when the
|
|
2195
2235
|
* parser surfaced `IParseIssue` entries (e.g. malformed YAML).
|
|
@@ -3126,6 +3166,32 @@ interface IActionContext {
|
|
|
3126
3166
|
*/
|
|
3127
3167
|
settings: Record<string, unknown>;
|
|
3128
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
|
+
}
|
|
3129
3195
|
/**
|
|
3130
3196
|
* Declarative filter applied by `--all` fan-out, UI button gating, and
|
|
3131
3197
|
* `sm actions show`. Same shape used by Extractor and Analyzer so the
|
|
@@ -3182,6 +3248,23 @@ interface IAction extends IExtensionBase {
|
|
|
3182
3248
|
* kernel materialises any returned `writes` after the call.
|
|
3183
3249
|
*/
|
|
3184
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;
|
|
3185
3268
|
}
|
|
3186
3269
|
|
|
3187
3270
|
/**
|
|
@@ -3648,6 +3731,16 @@ interface IScanExtensions {
|
|
|
3648
3731
|
* advisory until the job subsystem ships once the job subsystem ships.
|
|
3649
3732
|
*/
|
|
3650
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[];
|
|
3651
3744
|
}
|
|
3652
3745
|
interface RunScanOptions {
|
|
3653
3746
|
/**
|