@skill-map/cli 0.28.0 → 0.29.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.md +94 -20
- package/dist/cli.js +1408 -1193
- package/dist/cli.js.map +1 -1
- package/dist/index.js +116 -99
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +903 -1004
- package/dist/kernel/index.js +116 -99
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/chunk-3SI3TVER.js +7 -0
- package/dist/ui/{chunk-4GTCV7V4.js → chunk-47OZB7LR.js} +1 -1
- package/dist/ui/{chunk-JMP2LDMI.js → chunk-5JBW2LUN.js} +1 -1
- package/dist/ui/chunk-BL7KARTN.js +317 -0
- package/dist/ui/chunk-DMSZOXER.js +1 -0
- package/dist/ui/{chunk-Y7MXGXU3.js → chunk-DZBSELHN.js} +1 -1
- package/dist/ui/chunk-EFKSD7PT.js +123 -0
- package/dist/ui/{chunk-Z2667C3S.js → chunk-FEPH4VNB.js} +1 -1
- package/dist/ui/{chunk-PY2R7LHN.js → chunk-FQOZBFJ5.js} +1 -1
- package/dist/ui/{chunk-WOLLYGGL.js → chunk-KJQEO6P3.js} +1 -1
- package/dist/ui/{chunk-VO6NF24F.js → chunk-LS2NXZQZ.js} +1 -1
- package/dist/ui/{chunk-J3YWUNFO.js → chunk-LTQTJU54.js} +1 -1
- package/dist/ui/{chunk-6BG7PBUN.js → chunk-NGIFGXW7.js} +1 -1
- package/dist/ui/{chunk-5W6J6H76.js → chunk-SBCO7ZSP.js} +1 -1
- package/dist/ui/chunk-VB56BUGO.js +1 -0
- package/dist/ui/{chunk-UXCAEDR6.js → chunk-VDQLDTTR.js} +1 -1
- package/dist/ui/{chunk-AD7RBRD3.js → chunk-WJLIYGWJ.js} +5 -5
- package/dist/ui/index.html +2 -2
- package/dist/ui/{main-TZL26MZU.js → main-LGW7AYEA.js} +2 -2
- package/dist/ui/{styles-IKG3B6AM.css → styles-CDN434T2.css} +1 -1
- package/package.json +10 -7
- package/dist/ui/chunk-BGDH7CDV.js +0 -1
- package/dist/ui/chunk-H2J55DNK.js +0 -7
- package/dist/ui/chunk-Q7L6LLAK.js +0 -1
- package/dist/ui/chunk-VH5GRUT7.js +0 -255
- package/dist/ui/chunk-XCUOAV77.js +0 -123
package/dist/kernel/index.d.ts
CHANGED
|
@@ -1,760 +1,767 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* The kernel is the reference consumer of the spec; these types are therefore
|
|
5
|
-
* derived from the schemas, not invented. When a schema changes, this file
|
|
6
|
-
* follows. Until automatic AJV-driven derivation lands, the mapping is
|
|
7
|
-
* hand-maintained and the release gate is the conformance suite.
|
|
8
|
-
*
|
|
9
|
-
* --- Naming convention (kernel-wide) -------------------------------------
|
|
10
|
-
*
|
|
11
|
-
* Five categories with distinct prefix rules; the rules are deliberate
|
|
12
|
-
* even though they look mixed at first read:
|
|
13
|
-
*
|
|
14
|
-
* 1. **Domain types**, every shape that mirrors a `spec/schemas/*.json`
|
|
15
|
-
* file: `Node`, `Link`, `Issue`, `ScanResult`, `ScanStats`,
|
|
16
|
-
* `ExecutionRecord`, `HistoryStats`, …. **No prefix.** Names track
|
|
17
|
-
* the spec verbatim because the spec is the source of truth.
|
|
18
|
-
* Renaming any of these is a spec change.
|
|
19
|
-
*
|
|
20
|
-
* 2. **Hexagonal ports**, the abstract boundaries the kernel calls
|
|
21
|
-
* out to (`StoragePort`, `RunnerPort`, `ProgressEmitterPort`,
|
|
22
|
-
* `FilesystemPort`, `PluginLoaderPort`). **`Port` suffix.** The
|
|
23
|
-
* suffix calls out the architectural role and avoids name clashes
|
|
24
|
-
* with the concrete adapter classes (`SqliteStorageAdapter`
|
|
25
|
-
* implements `StoragePort`).
|
|
26
|
-
*
|
|
27
|
-
* 3. **Runtime extension contracts**, what a plugin author
|
|
28
|
-
* implements: `IProvider`, `IExtractor`, `IAnalyzer`, `IFormatter`,
|
|
29
|
-
* `IExtensionBase`. **`I` prefix.** The prefix flags "this is a
|
|
30
|
-
* contract you supply, not a value the kernel hands you", same
|
|
31
|
-
* reading as the rest of TypeScript's plugin ecosystems where a
|
|
32
|
-
* shape is implementable.
|
|
2
|
+
* Extension registry, six kinds, first-class, loaded through a single API.
|
|
33
3
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `IDbLocationOptions`. **`I` prefix.** The prefix matches
|
|
39
|
-
* category 3 because both are "shapes that live in TypeScript
|
|
40
|
-
* only, never in JSON".
|
|
4
|
+
* The `Extension` 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.
|
|
41
8
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* prefix.** Use this bucket when `interface` is the wrong shape
|
|
49
|
-
* (a union, a callback signature, an `Exclude<…>` derivation).
|
|
9
|
+
* **Spec § A.6, qualified ids.** Every extension is keyed in the registry
|
|
10
|
+
* by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash`,
|
|
11
|
+
* `hello-world/greet`). `Extension.id` carries the **short** id as authored;
|
|
12
|
+
* `Extension.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).
|
|
50
15
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 Extension {
|
|
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: Extension): 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): Extension | 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): Extension | undefined;
|
|
56
|
+
all(kind: ExtensionKind): Extension[];
|
|
57
|
+
count(kind: ExtensionKind): number;
|
|
58
|
+
totalCount(): number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Step 9.6.6, runtime annotation-contribution catalog types.
|
|
63
63
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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
68
|
*/
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
* `
|
|
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
|
+
/**
|
|
86
|
+
* Step 11.x, runtime view-contribution catalog types.
|
|
72
87
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* `node.schema.json#/properties/kind`, the contract is open-by-design
|
|
78
|
-
* (matches `IProvider.kinds` "open by design" docstring).
|
|
88
|
+
* Lives in its own module (rather than `kernel/index.ts`) so consumers
|
|
89
|
+
* deep inside the kernel, `IAnalyzerContext`, the BFF route factories,
|
|
90
|
+
* future Action contexts, can depend on the catalog shape without
|
|
91
|
+
* dragging the whole kernel barrel and risking a cycle.
|
|
79
92
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* named after the *format* because the file is generic markdown with
|
|
86
|
-
* no specific role; format-named kinds apply only as the generic
|
|
87
|
-
* fallback, a file that matches a specific role (agent / command /
|
|
88
|
-
* skill) classifies under that role, not under `markdown`.
|
|
93
|
+
* Mirrors `annotation-catalog.ts` for the annotation contribution side
|
|
94
|
+
* (Step 9.6.6). The two systems share the "plugin contributes data,
|
|
95
|
+
* kernel exposes catalog, UI renders" pattern but never overlap in
|
|
96
|
+
* storage or routing, see `architecture.md` §View contribution system
|
|
97
|
+
* for the comparison table.
|
|
89
98
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
99
|
+
* **Closed catalog by design.** Both `TSlotName` and `TInputTypeName`
|
|
100
|
+
* mirror the closed enums in `spec/schemas/view-slots.schema.json`
|
|
101
|
+
* and `spec/schemas/input-types.schema.json`. Adding a member is a
|
|
102
|
+
* coordinated kernel + spec + UI + scaffolder change. The closed-enum
|
|
103
|
+
* shape lets TypeScript surface unknown slots at author time
|
|
104
|
+
* (in plugin authors' editors when their plugin imports `@skill-map/cli`)
|
|
105
|
+
* AND lets the runtime exhaustively dispatch slot → renderer in the
|
|
106
|
+
* UI without `default:` fallbacks.
|
|
107
|
+
*/
|
|
108
|
+
/**
|
|
109
|
+
* Closed enum of view slot names. Mirror of
|
|
110
|
+
* `spec/schemas/view-slots.schema.json#/$defs/SlotName`.
|
|
98
111
|
*
|
|
99
|
-
*
|
|
100
|
-
* `
|
|
112
|
+
* Plugins pick one of these by name in their extension manifest's
|
|
113
|
+
* `viewContributions[<contributionId>].slot` field. The kernel
|
|
114
|
+
* validates each pick at load time (`invalid-manifest` on miss); the
|
|
115
|
+
* slot fixes both the renderer and the payload shape.
|
|
101
116
|
*/
|
|
102
|
-
type
|
|
103
|
-
type LinkKind = 'invokes' | 'references' | 'mentions' | 'supersedes';
|
|
104
|
-
type Confidence = 'high' | 'medium' | 'low';
|
|
105
|
-
type Severity = 'error' | 'warn' | 'info';
|
|
106
|
-
type Stability = 'experimental' | 'stable' | 'deprecated';
|
|
117
|
+
type TSlotName = 'card.title.right' | 'card.subtitle.left' | 'card.footer.left' | 'card.footer.right' | 'graph.node.alert' | 'inspector.header.badge.counter' | 'inspector.header.badge.tag' | 'inspector.body.panel.breakdown' | 'inspector.body.panel.records' | 'inspector.body.panel.tree' | 'inspector.body.panel.key-values' | 'inspector.body.panel.link-list' | 'inspector.body.panel.markdown' | 'topbar.nav.start';
|
|
107
118
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
119
|
+
* Closed enum of input-type names for plugin settings. Mirror of
|
|
120
|
+
* `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
|
|
110
121
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
122
|
+
* Plugins pick one of these by name in their plugin manifest's
|
|
123
|
+
* `settings[<settingId>].type` field. The kernel exposes the resolved
|
|
124
|
+
* value via `ctx.settings.<settingId>` typed per the input-type's
|
|
125
|
+
* value-type promise.
|
|
126
|
+
*/
|
|
127
|
+
type TInputTypeName = 'string-list' | 'single-string' | 'boolean-flag' | 'integer' | 'enum-pick' | 'enum-multipick' | 'path-glob' | 'regex' | 'secret' | 'key-value-list';
|
|
128
|
+
/** Closed severity palette aligned with PrimeNG `<p-tag>` / `<p-message>`. */
|
|
129
|
+
type TSeverity = 'info' | 'warn' | 'success' | 'danger';
|
|
130
|
+
/**
|
|
131
|
+
* Manifest-side declaration of a single view contribution. The plugin
|
|
132
|
+
* author writes one of these per Record key in
|
|
133
|
+
* `IExtensionBase.viewContributions[<contributionId>]`.
|
|
116
134
|
*
|
|
117
|
-
*
|
|
118
|
-
* omitted in the manifest). Provider / Formatter are deterministic-only and
|
|
119
|
-
* MUST NOT carry the field.
|
|
135
|
+
* Mirror of `view-slots.schema.json#/$defs/IViewContribution`.
|
|
120
136
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
interface IViewContribution {
|
|
138
|
+
/**
|
|
139
|
+
* Required. Closed-catalog slot name. Unknown name rejects the
|
|
140
|
+
* extension as `invalid-manifest` at load. The slot fixes both the
|
|
141
|
+
* renderer and the payload shape; there is no separate "contract"
|
|
142
|
+
* abstraction.
|
|
143
|
+
*/
|
|
144
|
+
slot: TSlotName;
|
|
145
|
+
/**
|
|
146
|
+
* Optional human-readable label. English-only per `AGENTS.md`
|
|
147
|
+
* (`Externalized texts, not internationalized`).
|
|
148
|
+
*/
|
|
149
|
+
label?: string;
|
|
150
|
+
/** Optional hover tooltip. English-only. */
|
|
151
|
+
tooltip?: string;
|
|
152
|
+
/**
|
|
153
|
+
* Optional emoji codepoint OR PrimeIcons class id (without the
|
|
154
|
+
* `pi-` prefix). The UI discriminates: matches Unicode
|
|
155
|
+
* `\p{Extended_Pictographic}` → emoji text, otherwise → PrimeIcon.
|
|
156
|
+
* Required for counter slots and `card.title.right` (enforced by
|
|
157
|
+
* the manifest-side conditional in `view-slots.schema.json`).
|
|
158
|
+
*/
|
|
159
|
+
icon?: string;
|
|
138
160
|
/**
|
|
139
|
-
*
|
|
140
|
-
* `
|
|
141
|
-
*
|
|
142
|
-
* their own. Code that intentionally switches on the claude catalog
|
|
143
|
-
* narrows via `if (kind === 'skill' \| ... )`; everything else
|
|
144
|
-
* accepts the open string and treats unknown values as opaque labels.
|
|
161
|
+
* Optional empty placeholder text shown when the payload is empty
|
|
162
|
+
* AND `emitWhenEmpty` is true. Falls back to a UI-supplied generic
|
|
163
|
+
* 'No data.' string. English-only.
|
|
145
164
|
*/
|
|
146
|
-
|
|
147
|
-
provider: string;
|
|
148
|
-
bodyHash: string;
|
|
149
|
-
frontmatterHash: string;
|
|
150
|
-
bytes: TripleSplit;
|
|
151
|
-
linksOutCount: number;
|
|
152
|
-
linksInCount: number;
|
|
153
|
-
externalRefsCount: number;
|
|
154
|
-
frontmatter?: Record<string, unknown>;
|
|
155
|
-
tokens?: TripleSplit;
|
|
165
|
+
emptyText?: string;
|
|
156
166
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
167
|
+
* When false (default), the kernel drops emissions whose payload is
|
|
168
|
+
* structurally empty so the slot stays silent. When true, the
|
|
169
|
+
* renderer surfaces an empty placeholder. Per-slot definition of
|
|
170
|
+
* "empty" lives in the slot's payload schema.
|
|
161
171
|
*/
|
|
162
|
-
|
|
172
|
+
emitWhenEmpty?: boolean;
|
|
163
173
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* truthy `isFavorite` only ever lands when the BFF set it.
|
|
174
|
+
* Optional ordering hint (default 100). Slots configured with
|
|
175
|
+
* `order: 'priority'` sort contributions ASC by this value, with
|
|
176
|
+
* alphabetical tie-break by qualified id. The plugin uses this to
|
|
177
|
+
* suggest where its contribution belongs relative to others sharing
|
|
178
|
+
* the same slot, the slot has the final say.
|
|
170
179
|
*/
|
|
171
|
-
|
|
180
|
+
priority?: number;
|
|
172
181
|
}
|
|
173
182
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
183
|
+
* Single row of the runtime view-contribution catalog surfaced by
|
|
184
|
+
* `kernel.getRegisteredViewContributions()`. One row per
|
|
185
|
+
* `(pluginId × extensionId × contributionId)` tuple. Composed at boot
|
|
186
|
+
* by `loadPluginRuntime` from every loaded extension's
|
|
187
|
+
* `viewContributions` map.
|
|
188
|
+
*
|
|
189
|
+
* The qualified id is `<pluginId>/<extensionId>/<contributionId>`,
|
|
190
|
+
* matches the qualified id pattern used elsewhere in the kernel
|
|
191
|
+
* (`<pluginId>/<extensionId>` for extensions; this adds the third
|
|
192
|
+
* segment for per-contribution identity).
|
|
176
193
|
*/
|
|
177
|
-
|
|
194
|
+
interface IRegisteredViewContribution {
|
|
195
|
+
pluginId: string;
|
|
196
|
+
extensionId: string;
|
|
197
|
+
contributionId: string;
|
|
198
|
+
slot: TSlotName;
|
|
199
|
+
/** Optional manifest-declared label (English-only). */
|
|
200
|
+
label?: string;
|
|
201
|
+
tooltip?: string;
|
|
202
|
+
icon?: string;
|
|
203
|
+
emptyText?: string;
|
|
204
|
+
emitWhenEmpty: boolean;
|
|
205
|
+
/** Manifest-declared ordering hint (default 100). See `IViewContribution.priority`. */
|
|
206
|
+
priority?: number;
|
|
207
|
+
}
|
|
178
208
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* sidecar accompanies the node); the other fields are absent or null
|
|
182
|
-
* in that case. When `present === true` and parse + validation
|
|
183
|
-
* succeeded, `status` carries the drift state and `annotations` carries
|
|
184
|
-
* the parsed (typed) `annotations:` block.
|
|
209
|
+
* Common fields on every setting declaration. The discriminated union
|
|
210
|
+
* `ISettingDeclaration` extends one of these per `type` value.
|
|
185
211
|
*/
|
|
186
|
-
interface
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
* `spec/schemas/annotations.schema.json`. Null when no sidecar or
|
|
192
|
-
* the block is empty/absent.
|
|
193
|
-
*/
|
|
194
|
-
annotations?: Record<string, unknown> | null;
|
|
195
|
-
/**
|
|
196
|
-
* R15 closure (2026-05-07), full parsed YAML root of the sidecar
|
|
197
|
-
* (the entire `.sm` payload, mirroring `sidecar.schema.json`). Surfaced
|
|
198
|
-
* so the UI inspector can render `for:`, `audit:`, `settings:`, and
|
|
199
|
-
* `<plugin-id>:` namespace blocks without re-reading the file. NULL
|
|
200
|
-
* when no sidecar is present, or when the sidecar exists but failed
|
|
201
|
-
* to parse / validate. The `annotations` field above stays, it
|
|
202
|
-
* duplicates `root.annotations` intentionally so existing consumers
|
|
203
|
-
* keep working unchanged.
|
|
204
|
-
*/
|
|
205
|
-
root?: Record<string, unknown> | null;
|
|
212
|
+
interface ISettingCommon {
|
|
213
|
+
/** Required. Short human-readable label. English-only. */
|
|
214
|
+
label: string;
|
|
215
|
+
/** Optional helper text shown below the control. English-only. */
|
|
216
|
+
description?: string;
|
|
206
217
|
}
|
|
207
|
-
interface
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
kind: LinkKind;
|
|
214
|
-
confidence: Confidence;
|
|
215
|
-
/** Identifiers of the extractors / extensions that contributed evidence
|
|
216
|
-
* for this link (one link can be confirmed by multiple extractors).
|
|
217
|
-
* Plural; NOT the same as `source` (singular) above, which is the
|
|
218
|
-
* originating node path. Naming is unfortunate but spec-frozen. */
|
|
219
|
-
sources: string[];
|
|
220
|
-
trigger?: LinkTrigger | null;
|
|
221
|
-
location?: LinkLocation | null;
|
|
222
|
-
raw?: string | null;
|
|
218
|
+
interface ISetting_StringList extends ISettingCommon {
|
|
219
|
+
type: 'string-list';
|
|
220
|
+
default?: string[];
|
|
221
|
+
min?: number;
|
|
222
|
+
max?: number;
|
|
223
|
+
itemMaxLength?: number;
|
|
223
224
|
}
|
|
224
|
-
interface
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
interface ISetting_SingleString extends ISettingCommon {
|
|
226
|
+
type: 'single-string';
|
|
227
|
+
default?: string;
|
|
228
|
+
minLength?: number;
|
|
229
|
+
maxLength?: number;
|
|
230
|
+
/** Optional ECMAScript regex pattern (no flags). */
|
|
231
|
+
pattern?: string;
|
|
227
232
|
}
|
|
228
|
-
interface
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
nodeIds: string[];
|
|
232
|
-
message: string;
|
|
233
|
-
linkIndices?: number[];
|
|
234
|
-
detail?: string | null;
|
|
235
|
-
fix?: IssueFix | null;
|
|
236
|
-
data?: Record<string, unknown>;
|
|
233
|
+
interface ISetting_BooleanFlag extends ISettingCommon {
|
|
234
|
+
type: 'boolean-flag';
|
|
235
|
+
default?: boolean;
|
|
237
236
|
}
|
|
238
|
-
interface
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
filesWalked: number;
|
|
245
|
-
/**
|
|
246
|
-
* Files walked but not classified by any Provider. Today every walked
|
|
247
|
-
* file is classified by its Provider (the `claude` Provider falls back to
|
|
248
|
-
* `'markdown'`), so this is always 0; the field will matter once
|
|
249
|
-
* multiple Providers can claim the same file.
|
|
250
|
-
*/
|
|
251
|
-
filesSkipped: number;
|
|
252
|
-
nodesCount: number;
|
|
253
|
-
linksCount: number;
|
|
254
|
-
issuesCount: number;
|
|
255
|
-
durationMs: number;
|
|
237
|
+
interface ISetting_Integer extends ISettingCommon {
|
|
238
|
+
type: 'integer';
|
|
239
|
+
default?: number;
|
|
240
|
+
min?: number;
|
|
241
|
+
max?: number;
|
|
242
|
+
step?: number;
|
|
256
243
|
}
|
|
257
|
-
interface
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
specVersion: string;
|
|
244
|
+
interface ISetting_EnumOption {
|
|
245
|
+
value: string;
|
|
246
|
+
label: string;
|
|
261
247
|
}
|
|
262
|
-
|
|
263
|
-
type
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* One row of execution history (`state_executions`). Matches
|
|
268
|
-
* `spec/schemas/execution-record.schema.json`. `nodeIds` is the camelCased
|
|
269
|
-
* domain field name; storage flattens it to `node_ids_json`.
|
|
270
|
-
*/
|
|
271
|
-
interface ExecutionRecord {
|
|
272
|
-
id: string;
|
|
273
|
-
kind: ExecutionKind;
|
|
274
|
-
extensionId: string;
|
|
275
|
-
extensionVersion: string;
|
|
276
|
-
nodeIds?: string[];
|
|
277
|
-
contentHash?: string | null;
|
|
278
|
-
status: ExecutionStatus;
|
|
279
|
-
failureReason?: ExecutionFailureReason | null;
|
|
280
|
-
exitCode?: number | null;
|
|
281
|
-
runner?: ExecutionRunner | null;
|
|
282
|
-
startedAt: number;
|
|
283
|
-
finishedAt: number;
|
|
284
|
-
durationMs?: number | null;
|
|
285
|
-
tokensIn?: number | null;
|
|
286
|
-
tokensOut?: number | null;
|
|
287
|
-
reportPath?: string | null;
|
|
288
|
-
jobId?: string | null;
|
|
248
|
+
interface ISetting_EnumPick extends ISettingCommon {
|
|
249
|
+
type: 'enum-pick';
|
|
250
|
+
options: ISetting_EnumOption[];
|
|
251
|
+
default?: string;
|
|
289
252
|
}
|
|
290
|
-
interface
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
durationMsTotal: number;
|
|
253
|
+
interface ISetting_EnumMultipick extends ISettingCommon {
|
|
254
|
+
type: 'enum-multipick';
|
|
255
|
+
options: ISetting_EnumOption[];
|
|
256
|
+
default?: string[];
|
|
257
|
+
min?: number;
|
|
258
|
+
max?: number;
|
|
297
259
|
}
|
|
298
|
-
interface
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
tokensOut: number;
|
|
304
|
-
durationMsMean: number | null;
|
|
305
|
-
durationMsMedian: number | null;
|
|
260
|
+
interface ISetting_PathGlob extends ISettingCommon {
|
|
261
|
+
type: 'path-glob';
|
|
262
|
+
default?: string;
|
|
263
|
+
/** When true, accepts string[]; when false (default), single string. */
|
|
264
|
+
multiple?: boolean;
|
|
306
265
|
}
|
|
307
|
-
interface
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
tokensOut: number;
|
|
266
|
+
interface ISetting_Regex extends ISettingCommon {
|
|
267
|
+
type: 'regex';
|
|
268
|
+
default?: string;
|
|
269
|
+
/** Subset of `gimsuy`. Default `''`. */
|
|
270
|
+
flags?: string;
|
|
313
271
|
}
|
|
314
|
-
interface
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
272
|
+
interface ISetting_Secret extends ISettingCommon {
|
|
273
|
+
type: 'secret';
|
|
274
|
+
/**
|
|
275
|
+
* Optional uppercase-ASCII identifier. When set in the process
|
|
276
|
+
* environment, that value wins over any stored value (lets CI
|
|
277
|
+
* inject without writing to disk).
|
|
278
|
+
*/
|
|
279
|
+
envVar?: string;
|
|
318
280
|
}
|
|
319
|
-
interface
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
executionsCount: number;
|
|
323
|
-
failedCount: number;
|
|
281
|
+
interface ISetting_KeyValueListEntry {
|
|
282
|
+
key: string;
|
|
283
|
+
value: string;
|
|
324
284
|
}
|
|
325
|
-
interface
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
285
|
+
interface ISetting_KeyValueList extends ISettingCommon {
|
|
286
|
+
type: 'key-value-list';
|
|
287
|
+
keyLabel?: string;
|
|
288
|
+
valueLabel?: string;
|
|
289
|
+
default?: ISetting_KeyValueListEntry[];
|
|
290
|
+
min?: number;
|
|
291
|
+
max?: number;
|
|
329
292
|
}
|
|
330
293
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
294
|
+
* Discriminated union of every setting declaration shape. The plugin
|
|
295
|
+
* author NEVER writes JSON Schema for settings, they pick one of
|
|
296
|
+
* these `type` values and supply per-type parameters.
|
|
297
|
+
*
|
|
298
|
+
* Mirror of `input-types.schema.json#/$defs/ISettingDeclaration`.
|
|
334
299
|
*/
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
executionsPerPeriod: HistoryStatsExecutionsPerPeriod[];
|
|
344
|
-
topNodes: HistoryStatsTopNode[];
|
|
345
|
-
errorRates: HistoryStatsErrorRates;
|
|
346
|
-
elapsedMs: number;
|
|
347
|
-
}
|
|
348
|
-
interface ScanResult {
|
|
349
|
-
schemaVersion: 1;
|
|
350
|
-
/** Unix milliseconds when the scan started. */
|
|
351
|
-
scannedAt: number;
|
|
352
|
-
/**
|
|
353
|
-
* Filesystem roots that were walked during this scan. Spec requires
|
|
354
|
-
* `minItems: 1`, `runScan` throws if `roots: []` is supplied.
|
|
355
|
-
*/
|
|
356
|
-
roots: string[];
|
|
357
|
-
/** Provider ids that participated in classification. Empty if no Provider matched. */
|
|
358
|
-
providers: string[];
|
|
359
|
-
/** Implementation metadata. Populated by `runScan` for self-describing output. */
|
|
360
|
-
scannedBy?: ScanScannedBy;
|
|
361
|
-
nodes: Node[];
|
|
362
|
-
links: Link[];
|
|
363
|
-
issues: Issue[];
|
|
364
|
-
stats: ScanStats;
|
|
365
|
-
}
|
|
300
|
+
type ISettingDeclaration = ISetting_StringList | ISetting_SingleString | ISetting_BooleanFlag | ISetting_Integer | ISetting_EnumPick | ISetting_EnumMultipick | ISetting_PathGlob | ISetting_Regex | ISetting_Secret | ISetting_KeyValueList;
|
|
301
|
+
/**
|
|
302
|
+
* Runtime value type for a setting, derived from its declaration. The
|
|
303
|
+
* kernel exposes settings to extractors as `Record<string, TSettingValue>`
|
|
304
|
+
* via `ctx.settings.<settingId>`; consumers that want narrow typing
|
|
305
|
+
* narrow at the call site by reading `manifest.settings[id].type`.
|
|
306
|
+
*/
|
|
307
|
+
type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueListEntry[];
|
|
366
308
|
|
|
367
309
|
/**
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* The `Extension` shape is aligned with `spec/schemas/extensions/base.schema.json`.
|
|
371
|
-
* Kind-specific manifests (provider / extractor / analyzer / action / formatter /
|
|
372
|
-
* hook) extend this base structurally; the registry stores the base view
|
|
373
|
-
* and each kind's code carries its own fuller type where needed.
|
|
374
|
-
*
|
|
375
|
-
* **Spec § A.6, qualified ids.** Every extension is keyed in the registry
|
|
376
|
-
* by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash`,
|
|
377
|
-
* `hello-world/greet`). `Extension.id` carries the **short** id as authored;
|
|
378
|
-
* `Extension.pluginId` carries the namespace; the registry composes the
|
|
379
|
-
* qualifier internally and exposes lookup APIs that operate on either form
|
|
380
|
-
* (qualified for direct lookup, kind-scoped listing for enumeration).
|
|
310
|
+
* Base extension shape shared by every kind. Mirrors
|
|
311
|
+
* `spec/schemas/extensions/base.schema.json` at the TypeScript level.
|
|
381
312
|
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
313
|
+
* **Structure-as-truth**: the manifest authored on disk no longer carries
|
|
314
|
+
* `id` or `kind`. Both fields are derived from the filesystem path
|
|
315
|
+
* (`<plugin>/<kind-plural>/<id>/index.ts`, parent folder dictates kind, leaf
|
|
316
|
+
* folder dictates id). The loader strips/rejects any `id` / `kind` literals
|
|
317
|
+
* a hand-written manifest carries and injects the derived values into the
|
|
318
|
+
* runtime descriptor before it reaches the registry. The qualified registry
|
|
319
|
+
* key is `<pluginId>/<id>`; `pluginId` similarly comes from the plugin's
|
|
320
|
+
* folder name and is injected at load time.
|
|
385
321
|
*/
|
|
386
322
|
|
|
387
|
-
type ExtensionKind = 'provider' | 'extractor' | 'analyzer' | 'action' | 'formatter' | 'hook';
|
|
388
|
-
declare const EXTENSION_KINDS: readonly ExtensionKind[];
|
|
389
|
-
interface Extension {
|
|
390
|
-
/** Short (unqualified) extension id as declared in the manifest. */
|
|
391
|
-
id: string;
|
|
392
|
-
/** Owning plugin namespace. Composed with `id` to form the qualified key. */
|
|
393
|
-
pluginId: string;
|
|
394
|
-
kind: ExtensionKind;
|
|
395
|
-
version: string;
|
|
396
|
-
description?: string;
|
|
397
|
-
stability?: Stability;
|
|
398
|
-
preconditions?: string[];
|
|
399
|
-
entry?: string;
|
|
400
|
-
}
|
|
401
323
|
/**
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
324
|
+
* Single declaration of an extension's optional sidecar annotation
|
|
325
|
+
* contribution. The annotation key is the extension's id (the leaf folder
|
|
326
|
+
* name); to contribute additional keys, split into additional extensions.
|
|
327
|
+
* Mirrors `spec/schemas/extensions/base.schema.json#/properties/annotation`.
|
|
405
328
|
*/
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
declare class Registry {
|
|
411
|
-
#private;
|
|
412
|
-
constructor();
|
|
413
|
-
register(ext: Extension): void;
|
|
329
|
+
interface IAnnotationContribution {
|
|
330
|
+
/** Inline JSON Schema describing the value written under this key. */
|
|
331
|
+
schema: Record<string, unknown>;
|
|
414
332
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
333
|
+
* Conflict policy. `shared` (default): multiple plugins MAY write the
|
|
334
|
+
* key; `exclusive`: only this plugin may. REQUIRED to be `'exclusive'`
|
|
335
|
+
* when `location: 'root'`.
|
|
417
336
|
*/
|
|
418
|
-
|
|
337
|
+
ownership?: 'exclusive' | 'shared';
|
|
419
338
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
339
|
+
* Where the key lands. `namespaced` (default): under the plugin's
|
|
340
|
+
* `<plugin-id>:` block; `root`: top-level, alongside `for` /
|
|
341
|
+
* `annotations` / `settings` / `audit`. Cross-plugin root-key
|
|
342
|
+
* collisions on `exclusive` are a fatal startup error.
|
|
422
343
|
*/
|
|
423
|
-
|
|
424
|
-
all(kind: ExtensionKind): Extension[];
|
|
425
|
-
count(kind: ExtensionKind): number;
|
|
426
|
-
totalCount(): number;
|
|
344
|
+
location?: 'namespaced' | 'root';
|
|
427
345
|
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Step 9.6.6, runtime annotation-contribution catalog types.
|
|
431
|
-
*
|
|
432
|
-
* Lives in its own module (rather than `kernel/index.ts`) so consumers
|
|
433
|
-
* deep inside the kernel, `IAnalyzerContext`, the BFF route factories,
|
|
434
|
-
* future Action contexts, can depend on the catalog shape without
|
|
435
|
-
* dragging the whole kernel barrel and risking a cycle.
|
|
436
|
-
*/
|
|
437
346
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* included, this catalog is plugin-only; the UI knows the built-in
|
|
442
|
-
* catalog via the schema bundle.
|
|
347
|
+
* Runtime extension descriptor as seen by the registry / orchestrator.
|
|
348
|
+
* Authors writing a manifest on disk do NOT declare `id`, `kind`, or
|
|
349
|
+
* `pluginId`; the loader injects all three from the filesystem layout.
|
|
443
350
|
*/
|
|
444
|
-
interface
|
|
351
|
+
interface IExtensionBase {
|
|
352
|
+
/**
|
|
353
|
+
* Short id, the leaf folder name of the extension. Injected by the
|
|
354
|
+
* loader. Hand-authored manifests carrying an `id` literal are
|
|
355
|
+
* rejected as `invalid-manifest`.
|
|
356
|
+
*/
|
|
357
|
+
id: string;
|
|
358
|
+
/**
|
|
359
|
+
* Owning plugin namespace, the plugin folder name. Composed with `id`
|
|
360
|
+
* to produce the qualified registry key `<pluginId>/<id>`. Injected
|
|
361
|
+
* by the loader.
|
|
362
|
+
*/
|
|
445
363
|
pluginId: string;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
|
|
364
|
+
version: string;
|
|
365
|
+
/** Required short description shown by `sm <kind>s list` / UI. */
|
|
366
|
+
description: string;
|
|
367
|
+
/**
|
|
368
|
+
* Optional opt-in single annotation contribution. Renamed from
|
|
369
|
+
* `annotationContributions` (mapa) with the structure-as-truth
|
|
370
|
+
* refactor; the key is the extension's id, so only the schema +
|
|
371
|
+
* ownership + location triple lives in the manifest.
|
|
372
|
+
*/
|
|
373
|
+
annotation?: IAnnotationContribution;
|
|
374
|
+
/**
|
|
375
|
+
* Optional extension-scoped user settings. Moved here from
|
|
376
|
+
* `plugin.json` with the structure-as-truth refactor: settings now
|
|
377
|
+
* live with the extension that consumes them, exposed at runtime as
|
|
378
|
+
* `ctx.settings.<settingId>`. Settings are read once at extension
|
|
379
|
+
* invocation; changing a setting requires `sm scan` to re-emit.
|
|
380
|
+
*/
|
|
381
|
+
settings?: Record<string, ISettingDeclaration>;
|
|
382
|
+
/**
|
|
383
|
+
* Resolved values of the settings declared above, populated by the
|
|
384
|
+
* orchestrator from project config + user overrides. Runtime-only,
|
|
385
|
+
* never written to disk by authors.
|
|
386
|
+
*/
|
|
387
|
+
resolvedSettings?: Record<string, TSettingValue>;
|
|
388
|
+
/**
|
|
389
|
+
* Optional plugin-contributed view contributions. Renamed from
|
|
390
|
+
* `viewContributions` with the structure-as-truth refactor. Each
|
|
391
|
+
* entry maps a local contribution id (kebab-case, unique within the
|
|
392
|
+
* extension) to an `IViewContribution` that picks a view slot by
|
|
393
|
+
* name from the closed catalog. Only `extractor` and `analyzer` kinds
|
|
394
|
+
* may declare this field.
|
|
395
|
+
*/
|
|
396
|
+
ui?: Record<string, IViewContribution>;
|
|
397
|
+
/** Runtime-only, absolute path of the extension entry file. */
|
|
398
|
+
entry?: string;
|
|
451
399
|
}
|
|
452
400
|
|
|
453
401
|
/**
|
|
454
|
-
*
|
|
402
|
+
* Domain types, byte-aligned with `spec/schemas/{node,link,issue,scan-result}.schema.json`.
|
|
455
403
|
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
404
|
+
* The kernel is the reference consumer of the spec; these types are therefore
|
|
405
|
+
* derived from the schemas, not invented. When a schema changes, this file
|
|
406
|
+
* follows. Until automatic AJV-driven derivation lands, the mapping is
|
|
407
|
+
* hand-maintained and the release gate is the conformance suite.
|
|
460
408
|
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
409
|
+
* --- Naming convention (kernel-wide) -------------------------------------
|
|
410
|
+
*
|
|
411
|
+
* Five categories with distinct prefix rules; the rules are deliberate
|
|
412
|
+
* even though they look mixed at first read:
|
|
413
|
+
*
|
|
414
|
+
* 1. **Domain types**, every shape that mirrors a `spec/schemas/*.json`
|
|
415
|
+
* file: `Node`, `Link`, `Issue`, `ScanResult`, `ScanStats`,
|
|
416
|
+
* `ExecutionRecord`, `HistoryStats`, …. **No prefix.** Names track
|
|
417
|
+
* the spec verbatim because the spec is the source of truth.
|
|
418
|
+
* Renaming any of these is a spec change.
|
|
419
|
+
*
|
|
420
|
+
* 2. **Hexagonal ports**, the abstract boundaries the kernel calls
|
|
421
|
+
* out to (`StoragePort`, `RunnerPort`, `ProgressEmitterPort`,
|
|
422
|
+
* `FilesystemPort`, `PluginLoaderPort`). **`Port` suffix.** The
|
|
423
|
+
* suffix calls out the architectural role and avoids name clashes
|
|
424
|
+
* with the concrete adapter classes (`SqliteStorageAdapter`
|
|
425
|
+
* implements `StoragePort`).
|
|
426
|
+
*
|
|
427
|
+
* 3. **Runtime extension contracts**, what a plugin author
|
|
428
|
+
* implements: `IProvider`, `IExtractor`, `IAnalyzer`, `IFormatter`,
|
|
429
|
+
* `IExtensionBase`. **`I` prefix.** The prefix flags "this is a
|
|
430
|
+
* contract you supply, not a value the kernel hands you", same
|
|
431
|
+
* reading as the rest of TypeScript's plugin ecosystems where a
|
|
432
|
+
* shape is implementable.
|
|
433
|
+
*
|
|
434
|
+
* 4. **Internal interfaces**, option bags, result records, config
|
|
435
|
+
* slices, anything declared as `interface` and passed across
|
|
436
|
+
* function boundaries inside the kernel / CLI but not part of the
|
|
437
|
+
* spec: `IPluginRuntimeBundle`, `IPruneResult`, `IMigrationFile`,
|
|
438
|
+
* `IDbLocationOptions`. **`I` prefix.** The prefix matches
|
|
439
|
+
* category 3 because both are "shapes that live in TypeScript
|
|
440
|
+
* only, never in JSON".
|
|
441
|
+
*
|
|
442
|
+
* 5. **Internal type aliases**, anything declared as `type` (string-
|
|
443
|
+
* literal unions, function types, mapped/derived types) that lives
|
|
444
|
+
* only in TS: `TLogLevel`, `TLogMethodLevel`, `TProgressListener`,
|
|
445
|
+
* `TLogFormatter`, `TActionWrite`, `TExecutionMode`, `TGranularity`,
|
|
446
|
+
* `THookFilter`, `THookTrigger`, `TNodeChangeReason`,
|
|
447
|
+
* `TPluginLoadStatus`, `TPluginStorage`, `TWatchEventKind`. **`T`
|
|
448
|
+
* prefix.** Use this bucket when `interface` is the wrong shape
|
|
449
|
+
* (a union, a callback signature, an `Exclude<…>` derivation).
|
|
450
|
+
*
|
|
451
|
+
* Edge cases worth knowing:
|
|
452
|
+
* - The following category-4 names lack the `I` prefix because
|
|
453
|
+
* they are part of the public kernel surface and renaming is a
|
|
454
|
+
* breaking change for downstream consumers. The list is closed:
|
|
455
|
+
* option bags / records: `RunScanOptions`, `RenameOp`;
|
|
456
|
+
* TS-only exports from `kernel/index.ts` / `kernel/ports/*`:
|
|
457
|
+
* `Kernel`, `ProgressEvent`, `LogRecord`, `NodeStat`.
|
|
458
|
+
* New public option bags MUST still use `I*`; new public type
|
|
459
|
+
* aliases MUST still use `T*`. Removing a name from this list is a
|
|
460
|
+
* breaking change.
|
|
461
|
+
* - `IDatabase` (SQLite schema) is category 4 but lives in
|
|
462
|
+
* `adapters/sqlite/schema.ts`, not here. Same rule applies.
|
|
463
|
+
*
|
|
464
|
+
* If you find yourself wanting to add a new type and aren't sure which
|
|
465
|
+
* bucket it falls in: ask "does this shape exist in the spec?". If
|
|
466
|
+
* yes, no prefix and align the name with the schema. If no, `I` prefix
|
|
467
|
+
* for `interface`, `T` prefix for `type` aliases.
|
|
468
|
+
*/
|
|
469
|
+
/**
|
|
470
|
+
* The four node kinds the **built-in Claude Provider** declares, `skill`,
|
|
471
|
+
* `agent`, `command`, `note`. **NOT** the kernel-wide kind type.
|
|
472
|
+
*
|
|
473
|
+
* `Node.kind` is `string`. An external Provider (Cursor, Obsidian, …)
|
|
474
|
+
* MAY classify into its own kinds (e.g. `'cursorRule'`, `'daily'`); the
|
|
475
|
+
* orchestrator, persistence layer, and AJV `node.schema.json` accept any
|
|
476
|
+
* non-empty string. Per `spec/db-schema.md` § scan_nodes and
|
|
477
|
+
* `node.schema.json#/properties/kind`, the contract is open-by-design
|
|
478
|
+
* (matches `IProvider.kinds` "open by design" docstring).
|
|
479
|
+
*
|
|
480
|
+
* Step 9.5 dropped `hook` from the catalog: `.claude/hooks/*.md` is NOT
|
|
481
|
+
* an Anthropic-defined node type, hooks live in `settings.json` or as
|
|
482
|
+
* sub-objects of agent / skill frontmatter (see
|
|
483
|
+
* https://code.claude.com/docs/en/hooks.md). Files at the old path
|
|
484
|
+
* classify as `markdown` via the Provider's fallback. The fallback is
|
|
485
|
+
* named after the *format* because the file is generic markdown with
|
|
486
|
+
* no specific role; format-named kinds apply only as the generic
|
|
487
|
+
* fallback, a file that matches a specific role (agent / command /
|
|
488
|
+
* skill) classifies under that role, not under `markdown`.
|
|
466
489
|
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*/
|
|
476
|
-
/**
|
|
477
|
-
* Closed enum of view slot names. Mirror of
|
|
478
|
-
* `spec/schemas/view-slots.schema.json#/$defs/SlotName`.
|
|
490
|
+
* This alias survives because:
|
|
491
|
+
* - claude-specific code legitimately wants to switch on the four
|
|
492
|
+
* hard-coded values (filter widgets, kind-aware UI cards, the
|
|
493
|
+
* `validate-all` built-in rule that maps each kind to its
|
|
494
|
+
* frontmatter schema);
|
|
495
|
+
* - sorting helpers want a stable `KIND_ORDER` for the canonical
|
|
496
|
+
* catalog;
|
|
497
|
+
* - tests expect to enumerate the four kinds when seeding fixtures.
|
|
479
498
|
*
|
|
480
|
-
*
|
|
481
|
-
* `
|
|
482
|
-
* validates each pick at load time (`invalid-manifest` on miss); the
|
|
483
|
-
* slot fixes both the renderer and the payload shape.
|
|
499
|
+
* For "any kind a Provider could declare", use plain `string`. Only use
|
|
500
|
+
* `NodeKind` when the code is intentionally claude-catalog-specific.
|
|
484
501
|
*/
|
|
485
|
-
type
|
|
502
|
+
type NodeKind = 'skill' | 'agent' | 'command' | 'markdown';
|
|
503
|
+
type LinkKind = 'invokes' | 'references' | 'mentions' | 'supersedes';
|
|
504
|
+
type Confidence = 'high' | 'medium' | 'low';
|
|
505
|
+
type Severity = 'error' | 'warn' | 'info';
|
|
506
|
+
type Stability = 'experimental' | 'stable' | 'deprecated';
|
|
486
507
|
/**
|
|
487
|
-
*
|
|
488
|
-
* `spec/
|
|
508
|
+
* Execution mode of an analytical extension. Mirrors the per-kind capability
|
|
509
|
+
* matrix in `spec/architecture.md` §Execution modes:
|
|
489
510
|
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
|
|
495
|
-
type TInputTypeName = 'string-list' | 'single-string' | 'boolean-flag' | 'integer' | 'enum-pick' | 'enum-multipick' | 'path-glob' | 'regex' | 'secret' | 'key-value-list';
|
|
496
|
-
/** Closed severity palette aligned with PrimeNG `<p-tag>` / `<p-message>`. */
|
|
497
|
-
type TSeverity = 'info' | 'warn' | 'success' | 'danger';
|
|
498
|
-
/**
|
|
499
|
-
* Manifest-side declaration of a single view contribution. The plugin
|
|
500
|
-
* author writes one of these per Record key in
|
|
501
|
-
* `IExtensionBase.viewContributions[<contributionId>]`.
|
|
511
|
+
* - `deterministic`, pure code, runs synchronously inside `sm scan` /
|
|
512
|
+
* `sm check`. Same input → same output, every run.
|
|
513
|
+
* - `probabilistic`, calls an LLM through `RunnerPort`, dispatches only
|
|
514
|
+
* as a queued job (`sm job submit <kind>:<id>`); never participates in
|
|
515
|
+
* scan-time pipelines.
|
|
502
516
|
*
|
|
503
|
-
*
|
|
517
|
+
* Extractor / Rule / Action declare it directly (default `deterministic` when
|
|
518
|
+
* omitted in the manifest). Provider / Formatter are deterministic-only and
|
|
519
|
+
* MUST NOT carry the field.
|
|
504
520
|
*/
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
* `pi-` prefix). The UI discriminates: matches Unicode
|
|
523
|
-
* `\p{Extended_Pictographic}` → emoji text, otherwise → PrimeIcon.
|
|
524
|
-
* Required for counter slots and `card.title.right` (enforced by
|
|
525
|
-
* the manifest-side conditional in `view-slots.schema.json`).
|
|
526
|
-
*/
|
|
527
|
-
icon?: string;
|
|
521
|
+
type TExecutionMode = 'deterministic' | 'probabilistic';
|
|
522
|
+
interface TripleSplit {
|
|
523
|
+
frontmatter: number;
|
|
524
|
+
body: number;
|
|
525
|
+
total: number;
|
|
526
|
+
}
|
|
527
|
+
interface LinkTrigger {
|
|
528
|
+
originalTrigger: string;
|
|
529
|
+
normalizedTrigger: string;
|
|
530
|
+
}
|
|
531
|
+
interface LinkLocation {
|
|
532
|
+
line: number;
|
|
533
|
+
column?: number;
|
|
534
|
+
offset?: number;
|
|
535
|
+
}
|
|
536
|
+
interface Node {
|
|
537
|
+
path: string;
|
|
528
538
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
539
|
+
* Provider-declared category. Open string (matches
|
|
540
|
+
* `node.schema.json#/properties/kind`): the built-in Claude Provider
|
|
541
|
+
* emits one of `NodeKind`'s values, but external Providers MAY emit
|
|
542
|
+
* their own. Code that intentionally switches on the claude catalog
|
|
543
|
+
* narrows via `if (kind === 'skill' \| ... )`; everything else
|
|
544
|
+
* accepts the open string and treats unknown values as opaque labels.
|
|
532
545
|
*/
|
|
533
|
-
|
|
546
|
+
kind: string;
|
|
547
|
+
provider: string;
|
|
548
|
+
bodyHash: string;
|
|
549
|
+
frontmatterHash: string;
|
|
550
|
+
bytes: TripleSplit;
|
|
551
|
+
linksOutCount: number;
|
|
552
|
+
linksInCount: number;
|
|
553
|
+
externalRefsCount: number;
|
|
554
|
+
frontmatter?: Record<string, unknown>;
|
|
555
|
+
tokens?: TripleSplit;
|
|
534
556
|
/**
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
557
|
+
* Step 9.6.2, sidecar denormalisation surface. Populated by the
|
|
558
|
+
* orchestrator at scan time; absent when the orchestrator did not
|
|
559
|
+
* inspect sidecars (legacy code paths) or when no sidecar accompanies
|
|
560
|
+
* the node. Read by `annotation-stale` rule and the persistence layer.
|
|
539
561
|
*/
|
|
540
|
-
|
|
562
|
+
sidecar?: ISidecarOverlay | null;
|
|
541
563
|
/**
|
|
542
|
-
*
|
|
543
|
-
* `
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
564
|
+
* Per-user "favorite" flag, decorated by the BFF on `/api/nodes` and
|
|
565
|
+
* `/api/nodes/:pathB64` responses via in-memory `Set` lookup against
|
|
566
|
+
* `state_node_favorites`. Absent on emissions that don't carry per-user
|
|
567
|
+
* state (e.g. `sm export --json`); consumers that don't recognise the
|
|
568
|
+
* field MUST treat the absence as "unknown" rather than "false", a
|
|
569
|
+
* truthy `isFavorite` only ever lands when the BFF set it.
|
|
547
570
|
*/
|
|
548
|
-
|
|
571
|
+
isFavorite?: boolean;
|
|
549
572
|
}
|
|
550
573
|
/**
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
* `(pluginId × extensionId × contributionId)` tuple. Composed at boot
|
|
554
|
-
* by `loadPluginRuntime` from every loaded extension's
|
|
555
|
-
* `viewContributions` map.
|
|
556
|
-
*
|
|
557
|
-
* The qualified id is `<pluginId>/<extensionId>/<contributionId>`,
|
|
558
|
-
* matches the qualified id pattern used elsewhere in the kernel
|
|
559
|
-
* (`<pluginId>/<extensionId>` for extensions; this adds the third
|
|
560
|
-
* segment for per-contribution identity).
|
|
574
|
+
* Drift status of a co-located `.sm` sidecar relative to the live
|
|
575
|
+
* node hashes. Mirrors `TSidecarStatus` on the SQLite schema.
|
|
561
576
|
*/
|
|
562
|
-
|
|
563
|
-
pluginId: string;
|
|
564
|
-
extensionId: string;
|
|
565
|
-
contributionId: string;
|
|
566
|
-
slot: TSlotName;
|
|
567
|
-
/** Optional manifest-declared label (English-only). */
|
|
568
|
-
label?: string;
|
|
569
|
-
tooltip?: string;
|
|
570
|
-
icon?: string;
|
|
571
|
-
emptyText?: string;
|
|
572
|
-
emitWhenEmpty: boolean;
|
|
573
|
-
/** Manifest-declared ordering hint (default 100). See `IViewContribution.priority`. */
|
|
574
|
-
priority?: number;
|
|
575
|
-
}
|
|
577
|
+
type SidecarStatus = 'fresh' | 'stale-body' | 'stale-frontmatter' | 'stale-both';
|
|
576
578
|
/**
|
|
577
|
-
*
|
|
578
|
-
* `
|
|
579
|
+
* Sidecar overlay attached to a `Node` after the orchestrator parses
|
|
580
|
+
* `<basename>.sm`. `present === false` is the empty overlay (no
|
|
581
|
+
* sidecar accompanies the node); the other fields are absent or null
|
|
582
|
+
* in that case. When `present === true` and parse + validation
|
|
583
|
+
* succeeded, `status` carries the drift state and `annotations` carries
|
|
584
|
+
* the parsed (typed) `annotations:` block.
|
|
579
585
|
*/
|
|
580
|
-
interface
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
|
|
586
|
+
interface ISidecarOverlay {
|
|
587
|
+
present: boolean;
|
|
588
|
+
status?: SidecarStatus | null;
|
|
589
|
+
/**
|
|
590
|
+
* Parsed `annotations:` block. Untyped object, schema lives in
|
|
591
|
+
* `spec/schemas/annotations.schema.json`. Null when no sidecar or
|
|
592
|
+
* the block is empty/absent.
|
|
593
|
+
*/
|
|
594
|
+
annotations?: Record<string, unknown> | null;
|
|
595
|
+
/**
|
|
596
|
+
* R15 closure (2026-05-07), full parsed YAML root of the sidecar
|
|
597
|
+
* (the entire `.sm` payload, mirroring `sidecar.schema.json`). Surfaced
|
|
598
|
+
* so the UI inspector can render `for:`, `audit:`, `settings:`, and
|
|
599
|
+
* `<plugin-id>:` namespace blocks without re-reading the file. NULL
|
|
600
|
+
* when no sidecar is present, or when the sidecar exists but failed
|
|
601
|
+
* to parse / validate. The `annotations` field above stays, it
|
|
602
|
+
* duplicates `root.annotations` intentionally so existing consumers
|
|
603
|
+
* keep working unchanged.
|
|
604
|
+
*/
|
|
605
|
+
root?: Record<string, unknown> | null;
|
|
585
606
|
}
|
|
586
|
-
interface
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
607
|
+
interface Link {
|
|
608
|
+
/** The originating node, the path of the file the extractor was reading
|
|
609
|
+
* when it emitted this link. Singular, NOT to be confused with
|
|
610
|
+
* `sources` (plural) below. */
|
|
611
|
+
source: string;
|
|
612
|
+
target: string;
|
|
613
|
+
kind: LinkKind;
|
|
614
|
+
confidence: Confidence;
|
|
615
|
+
/** Identifiers of the extractors / extensions that contributed evidence
|
|
616
|
+
* for this link (one link can be confirmed by multiple extractors).
|
|
617
|
+
* Plural; NOT the same as `source` (singular) above, which is the
|
|
618
|
+
* originating node path. Naming is unfortunate but spec-frozen. */
|
|
619
|
+
sources: string[];
|
|
620
|
+
trigger?: LinkTrigger | null;
|
|
621
|
+
location?: LinkLocation | null;
|
|
622
|
+
raw?: string | null;
|
|
592
623
|
}
|
|
593
|
-
interface
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
minLength?: number;
|
|
597
|
-
maxLength?: number;
|
|
598
|
-
/** Optional ECMAScript regex pattern (no flags). */
|
|
599
|
-
pattern?: string;
|
|
624
|
+
interface IssueFix {
|
|
625
|
+
summary?: string;
|
|
626
|
+
autofixable?: boolean;
|
|
600
627
|
}
|
|
601
|
-
interface
|
|
602
|
-
|
|
603
|
-
|
|
628
|
+
interface Issue {
|
|
629
|
+
analyzerId: string;
|
|
630
|
+
severity: Severity;
|
|
631
|
+
nodeIds: string[];
|
|
632
|
+
message: string;
|
|
633
|
+
linkIndices?: number[];
|
|
634
|
+
detail?: string | null;
|
|
635
|
+
fix?: IssueFix | null;
|
|
636
|
+
data?: Record<string, unknown>;
|
|
604
637
|
}
|
|
605
|
-
interface
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
638
|
+
interface ScanStats {
|
|
639
|
+
/**
|
|
640
|
+
* Files visited by the Provider walkers. With a single Provider this
|
|
641
|
+
* matches `nodesCount`; with multiple Providers running on overlapping
|
|
642
|
+
* roots it can diverge (each yielded `IRawNode` is one walked file).
|
|
643
|
+
*/
|
|
644
|
+
filesWalked: number;
|
|
645
|
+
/**
|
|
646
|
+
* Files walked but not classified by any Provider. Today every walked
|
|
647
|
+
* file is classified by its Provider (the `claude` Provider falls back to
|
|
648
|
+
* `'markdown'`), so this is always 0; the field will matter once
|
|
649
|
+
* multiple Providers can claim the same file.
|
|
650
|
+
*/
|
|
651
|
+
filesSkipped: number;
|
|
652
|
+
nodesCount: number;
|
|
653
|
+
linksCount: number;
|
|
654
|
+
issuesCount: number;
|
|
655
|
+
durationMs: number;
|
|
611
656
|
}
|
|
612
|
-
interface
|
|
613
|
-
|
|
614
|
-
|
|
657
|
+
interface ScanScannedBy {
|
|
658
|
+
name: string;
|
|
659
|
+
version: string;
|
|
660
|
+
specVersion: string;
|
|
615
661
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
662
|
+
type ExecutionKind = 'action';
|
|
663
|
+
type ExecutionStatus = 'completed' | 'failed' | 'cancelled';
|
|
664
|
+
type ExecutionFailureReason = 'runner-error' | 'report-invalid' | 'timeout' | 'abandoned' | 'job-file-missing' | 'user-cancelled';
|
|
665
|
+
type ExecutionRunner = 'cli' | 'skill' | 'in-process';
|
|
666
|
+
/**
|
|
667
|
+
* One row of execution history (`state_executions`). Matches
|
|
668
|
+
* `spec/schemas/execution-record.schema.json`. `nodeIds` is the camelCased
|
|
669
|
+
* domain field name; storage flattens it to `node_ids_json`.
|
|
670
|
+
*/
|
|
671
|
+
interface ExecutionRecord {
|
|
672
|
+
id: string;
|
|
673
|
+
kind: ExecutionKind;
|
|
674
|
+
extensionId: string;
|
|
675
|
+
extensionVersion: string;
|
|
676
|
+
nodeIds?: string[];
|
|
677
|
+
contentHash?: string | null;
|
|
678
|
+
status: ExecutionStatus;
|
|
679
|
+
failureReason?: ExecutionFailureReason | null;
|
|
680
|
+
exitCode?: number | null;
|
|
681
|
+
runner?: ExecutionRunner | null;
|
|
682
|
+
startedAt: number;
|
|
683
|
+
finishedAt: number;
|
|
684
|
+
durationMs?: number | null;
|
|
685
|
+
tokensIn?: number | null;
|
|
686
|
+
tokensOut?: number | null;
|
|
687
|
+
reportPath?: string | null;
|
|
688
|
+
jobId?: string | null;
|
|
620
689
|
}
|
|
621
|
-
interface
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
690
|
+
interface HistoryStatsTotals {
|
|
691
|
+
executionsCount: number;
|
|
692
|
+
completedCount: number;
|
|
693
|
+
failedCount: number;
|
|
694
|
+
tokensIn: number;
|
|
695
|
+
tokensOut: number;
|
|
696
|
+
durationMsTotal: number;
|
|
627
697
|
}
|
|
628
|
-
interface
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
698
|
+
interface HistoryStatsTokensPerAction {
|
|
699
|
+
actionId: string;
|
|
700
|
+
actionVersion: string;
|
|
701
|
+
executionsCount: number;
|
|
702
|
+
tokensIn: number;
|
|
703
|
+
tokensOut: number;
|
|
704
|
+
durationMsMean: number | null;
|
|
705
|
+
durationMsMedian: number | null;
|
|
633
706
|
}
|
|
634
|
-
interface
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
707
|
+
interface HistoryStatsExecutionsPerPeriod {
|
|
708
|
+
periodStart: string;
|
|
709
|
+
periodUnit: 'day' | 'week' | 'month';
|
|
710
|
+
executionsCount: number;
|
|
711
|
+
tokensIn: number;
|
|
712
|
+
tokensOut: number;
|
|
639
713
|
}
|
|
640
|
-
interface
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
* environment, that value wins over any stored value (lets CI
|
|
645
|
-
* inject without writing to disk).
|
|
646
|
-
*/
|
|
647
|
-
envVar?: string;
|
|
714
|
+
interface HistoryStatsTopNode {
|
|
715
|
+
nodePath: string;
|
|
716
|
+
executionsCount: number;
|
|
717
|
+
lastExecutedAt: number;
|
|
648
718
|
}
|
|
649
|
-
interface
|
|
650
|
-
|
|
651
|
-
|
|
719
|
+
interface HistoryStatsPerActionRate {
|
|
720
|
+
actionId: string;
|
|
721
|
+
rate: number;
|
|
722
|
+
executionsCount: number;
|
|
723
|
+
failedCount: number;
|
|
652
724
|
}
|
|
653
|
-
interface
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
default?: ISetting_KeyValueListEntry[];
|
|
658
|
-
min?: number;
|
|
659
|
-
max?: number;
|
|
725
|
+
interface HistoryStatsErrorRates {
|
|
726
|
+
global: number;
|
|
727
|
+
perAction: HistoryStatsPerActionRate[];
|
|
728
|
+
perFailureReason: Record<ExecutionFailureReason, number>;
|
|
660
729
|
}
|
|
661
730
|
/**
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
* Mirror of `input-types.schema.json#/$defs/ISettingDeclaration`.
|
|
667
|
-
*/
|
|
668
|
-
type ISettingDeclaration = ISetting_StringList | ISetting_SingleString | ISetting_BooleanFlag | ISetting_Integer | ISetting_EnumPick | ISetting_EnumMultipick | ISetting_PathGlob | ISetting_Regex | ISetting_Secret | ISetting_KeyValueList;
|
|
669
|
-
/**
|
|
670
|
-
* Runtime value type for a setting, derived from its declaration. The
|
|
671
|
-
* kernel exposes settings to extractors as `Record<string, TSettingValue>`
|
|
672
|
-
* via `ctx.settings.<settingId>`; consumers that want narrow typing
|
|
673
|
-
* narrow at the call site by reading `manifest.settings[id].type`.
|
|
674
|
-
*/
|
|
675
|
-
type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueListEntry[];
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* Base manifest shape shared by every extension kind. Mirrors
|
|
679
|
-
* `spec/schemas/extensions/base.schema.json` at the TypeScript level.
|
|
680
|
-
*
|
|
681
|
-
* Spec § A.6, every extension is identified in the registry by the
|
|
682
|
-
* qualified id `<pluginId>/<id>`. The `pluginId` field is required at the
|
|
683
|
-
* runtime / TS level: built-ins declare it directly in
|
|
684
|
-
* `src/extensions/built-ins.ts`; user plugins have it injected by the
|
|
685
|
-
* `PluginLoader` from `plugin.json#/id` before the extension reaches the
|
|
686
|
-
* registry. A plugin author who hand-codes a `pluginId` that disagrees
|
|
687
|
-
* with the manifest's `id` is rejected as `invalid-manifest`.
|
|
688
|
-
*
|
|
689
|
-
* The JSON Schema deliberately does NOT model `pluginId`, the qualifier
|
|
690
|
-
* is a runtime concern composed by the loader, not a manifest field
|
|
691
|
-
* authors are expected to set. Stripping it before AJV validation in
|
|
692
|
-
* the loader keeps the spec contract clean ("authors declare only the
|
|
693
|
-
* short id").
|
|
694
|
-
*/
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Step 9.6.6, single entry of an extension's `annotationContributions`
|
|
698
|
-
* map. Mirrors `spec/schemas/extensions/base.schema.json#/properties/annotationContributions/additionalProperties`.
|
|
699
|
-
*
|
|
700
|
-
* `schema` is an INLINE JSON Schema (object literal in the manifest),
|
|
701
|
-
* not a `$ref` to a file. The kernel compiles it at load time; an
|
|
702
|
-
* invalid schema rejects the extension as `invalid-manifest`.
|
|
731
|
+
* `sm history stats --json` payload, conforming to
|
|
732
|
+
* `spec/schemas/history-stats.schema.json`. `elapsedMs` is the command's
|
|
733
|
+
* own wall-clock per `cli-contract.md` §Elapsed time.
|
|
703
734
|
*/
|
|
704
|
-
interface
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
* `annotations` / `settings` / `audit`. Cross-plugin root-key
|
|
717
|
-
* collisions on `exclusive` are a fatal startup error.
|
|
718
|
-
*/
|
|
719
|
-
location?: 'namespaced' | 'root';
|
|
735
|
+
interface HistoryStats {
|
|
736
|
+
schemaVersion: 1;
|
|
737
|
+
range: {
|
|
738
|
+
since: string | null;
|
|
739
|
+
until: string;
|
|
740
|
+
};
|
|
741
|
+
totals: HistoryStatsTotals;
|
|
742
|
+
tokensPerAction: HistoryStatsTokensPerAction[];
|
|
743
|
+
executionsPerPeriod: HistoryStatsExecutionsPerPeriod[];
|
|
744
|
+
topNodes: HistoryStatsTopNode[];
|
|
745
|
+
errorRates: HistoryStatsErrorRates;
|
|
746
|
+
elapsedMs: number;
|
|
720
747
|
}
|
|
721
|
-
interface
|
|
722
|
-
|
|
748
|
+
interface ScanResult {
|
|
749
|
+
schemaVersion: 1;
|
|
750
|
+
/** Unix milliseconds when the scan started. */
|
|
751
|
+
scannedAt: number;
|
|
723
752
|
/**
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
* directly; user plugins have it injected by the `PluginLoader`
|
|
727
|
-
* from `plugin.json#/id`.
|
|
753
|
+
* Filesystem roots that were walked during this scan. Spec requires
|
|
754
|
+
* `minItems: 1`, `runScan` throws if `roots: []` is supplied.
|
|
728
755
|
*/
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
* The kernel surfaces the aggregate via `kernel.getRegisteredAnnotationKeys()`.
|
|
739
|
-
* See `IAnnotationContribution` for the field semantics and
|
|
740
|
-
* `plugin-author-guide.md` §Annotation contributions for examples.
|
|
741
|
-
*/
|
|
742
|
-
annotationContributions?: Record<string, IAnnotationContribution>;
|
|
743
|
-
/**
|
|
744
|
-
* Plugin-contributed view contributions. Each entry maps a local
|
|
745
|
-
* contribution id (kebab-case, unique within the extension) to a
|
|
746
|
-
* `IViewContribution` declaration that picks a view slot by name
|
|
747
|
-
* from the closed kernel catalog (`view-catalog.ts#TSlotName`).
|
|
748
|
-
* The slot fixes both the renderer and the payload shape, there
|
|
749
|
-
* is no separate "contract" abstraction. The kernel validates each
|
|
750
|
-
* `slot` pick at load time (`invalid-manifest` on miss); the plugin
|
|
751
|
-
* emits per-node payloads via `ctx.emitContribution(<contributionId>,
|
|
752
|
-
* payload)` during scan; the runtime validates payloads against
|
|
753
|
-
* the slot's payload schema. The aggregate runtime catalog is
|
|
754
|
-
* exposed via `kernel.getRegisteredViewContributions()`. See
|
|
755
|
-
* `architecture.md` §View contribution system.
|
|
756
|
-
*/
|
|
757
|
-
viewContributions?: Record<string, IViewContribution>;
|
|
756
|
+
roots: string[];
|
|
757
|
+
/** Provider ids that participated in classification. Empty if no Provider matched. */
|
|
758
|
+
providers: string[];
|
|
759
|
+
/** Implementation metadata. Populated by `runScan` for self-describing output. */
|
|
760
|
+
scannedBy?: ScanScannedBy;
|
|
761
|
+
nodes: Node[];
|
|
762
|
+
links: Link[];
|
|
763
|
+
issues: Issue[];
|
|
764
|
+
stats: ScanStats;
|
|
758
765
|
}
|
|
759
766
|
|
|
760
767
|
/**
|
|
@@ -815,42 +822,35 @@ type TPluginStorage = {
|
|
|
815
822
|
* capabilities a user might reasonably want piecemeal.
|
|
816
823
|
*/
|
|
817
824
|
type TGranularity = 'bundle' | 'extension';
|
|
818
|
-
/**
|
|
825
|
+
/**
|
|
826
|
+
* Raw `plugin.json` shape after successful AJV validation.
|
|
827
|
+
*
|
|
828
|
+
* **Structure-as-truth**: the plugin id comes from the directory name
|
|
829
|
+
* (`<root>/<id>/plugin.json`); it is NOT a manifest field. The loader
|
|
830
|
+
* rejects manifests carrying an `id` literal. Settings moved out of
|
|
831
|
+
* `plugin.json` into each extension's own manifest with the same refactor.
|
|
832
|
+
*/
|
|
819
833
|
interface IPluginManifest {
|
|
820
|
-
id: string;
|
|
821
834
|
version: string;
|
|
822
835
|
specCompat: string;
|
|
823
836
|
/**
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
* checking; `sm plugins doctor` warns if such a plugin actually
|
|
830
|
-
* declares `viewContributions` or `settings`.
|
|
837
|
+
* Required semver range against the kernel's view-slots + input-types
|
|
838
|
+
* catalog version. Mismatch surfaces as `incompatible-catalog`. Promoted
|
|
839
|
+
* from optional to required with the structure-as-truth refactor,
|
|
840
|
+
* declaring compat is part of the plugin contract regardless of which
|
|
841
|
+
* catalog surfaces it actually consumes.
|
|
831
842
|
*/
|
|
832
|
-
catalogCompat
|
|
833
|
-
|
|
834
|
-
description
|
|
843
|
+
catalogCompat: string;
|
|
844
|
+
/** Required short description shown in `sm plugins list` and the UI. */
|
|
845
|
+
description: string;
|
|
835
846
|
storage?: TPluginStorage;
|
|
836
847
|
/**
|
|
837
|
-
* Toggle granularity for this plugin.
|
|
838
|
-
* `
|
|
839
|
-
*
|
|
848
|
+
* Toggle granularity for this plugin. Optional with default
|
|
849
|
+
* `'extension'` since the structure-as-truth refactor (more
|
|
850
|
+
* permissive default: each extension is independently toggleable
|
|
851
|
+
* unless the author opts into bundle-level coupling).
|
|
840
852
|
*/
|
|
841
853
|
granularity?: TGranularity;
|
|
842
|
-
/**
|
|
843
|
-
* Plugin user-configurable settings. Each entry picks an `input-type`
|
|
844
|
-
* from the closed catalog at
|
|
845
|
-
* `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
|
|
846
|
-
* The plugin author NEVER writes JSON Schema, they pick `type` by
|
|
847
|
-
* name and supply per-type parameters. The kernel exposes resolved
|
|
848
|
-
* settings to extractors via `ctx.settings.<settingId>`; settings
|
|
849
|
-
* are read once at extractor invocation; changing a setting requires
|
|
850
|
-
* `sm scan` to re-emit. See `architecture.md` §View contribution
|
|
851
|
-
* system → Settings.
|
|
852
|
-
*/
|
|
853
|
-
settings?: Record<string, ISettingDeclaration>;
|
|
854
854
|
author?: string;
|
|
855
855
|
license?: string;
|
|
856
856
|
homepage?: string;
|
|
@@ -1566,12 +1566,14 @@ interface IParseIssue {
|
|
|
1566
1566
|
* the path relative to the scan root; the kernel computes hashes, bytes,
|
|
1567
1567
|
* and tokens on top.
|
|
1568
1568
|
*
|
|
1569
|
-
* **
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1569
|
+
* **Structure-as-truth**: each plugin carries at most one Provider, declared
|
|
1570
|
+
* as `<plugin>/provider.ts`. The kinds catalog lives as folders under
|
|
1571
|
+
* `<plugin>/kinds/<kindName>/`; each kind folder contains `schema.json`
|
|
1572
|
+
* (the frontmatter JSON Schema) and `kind.json` (UI metadata). The loader
|
|
1573
|
+
* discovers each entry by walking the directory and populates the runtime
|
|
1574
|
+
* `kinds` map below. The manifest itself NO LONGER carries a `kinds` map
|
|
1575
|
+
* or a `defaultRefreshAction` field (the UI's Refresh button consumer was
|
|
1576
|
+
* retired alongside it; the replacement TBD).
|
|
1575
1577
|
*/
|
|
1576
1578
|
|
|
1577
1579
|
interface IRawNode {
|
|
@@ -1593,51 +1595,36 @@ interface IRawNode {
|
|
|
1593
1595
|
parseIssues?: readonly IParseIssue[];
|
|
1594
1596
|
}
|
|
1595
1597
|
/**
|
|
1596
|
-
*
|
|
1597
|
-
*
|
|
1598
|
-
*
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
1603
|
-
* letting the runtime instance carry the parsed schema without a second
|
|
1604
|
-
* filesystem read at scan time. Built-in Providers populate `schemaJson`
|
|
1605
|
-
* via `import schema from './schemas/skill.schema.json' with { type: 'json' }`;
|
|
1606
|
-
* user-plugin Providers loaded by `PluginLoader` will have it filled in
|
|
1607
|
-
* by the loader after manifest validation.
|
|
1598
|
+
* Runtime descriptor of one Provider kind, populated by the loader from
|
|
1599
|
+
* the structure under `<plugin>/kinds/<kindName>/`. The loader reads
|
|
1600
|
+
* `schema.json` from the kind folder, parses it once, attaches the path
|
|
1601
|
+
* (for diagnostics) and the parsed object (for AJV registration), and
|
|
1602
|
+
* reads `kind.json` for the UI metadata. The runtime descriptor lives in
|
|
1603
|
+
* memory; no field in this shape comes from the Provider manifest itself
|
|
1604
|
+
* since the structure-as-truth refactor.
|
|
1608
1605
|
*/
|
|
1609
1606
|
interface IProviderKind {
|
|
1610
1607
|
/**
|
|
1611
|
-
* Path to the kind's frontmatter JSON Schema, relative to the
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1608
|
+
* Path to the kind's frontmatter JSON Schema, relative to the Provider's
|
|
1609
|
+
* package directory. Always `kinds/<kindName>/schema.json` under the new
|
|
1610
|
+
* layout. Kept on the descriptor for diagnostics (file references in
|
|
1611
|
+
* error messages, doctor reports).
|
|
1614
1612
|
*/
|
|
1615
1613
|
schema: string;
|
|
1616
1614
|
/**
|
|
1617
1615
|
* Loaded JSON Schema document for the kind. The kernel registers this
|
|
1618
1616
|
* with AJV at scan boot and validates each node's frontmatter against
|
|
1619
1617
|
* it. The schema MUST extend the spec's
|
|
1620
|
-
* `frontmatter/base.schema.json` via `allOf` + `$ref` to base's
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1624
|
-
* `unknown` rather than a stronger type because AJV consumes any JSON
|
|
1625
|
-
* Schema object; tightening to a concrete shape would require mirroring
|
|
1626
|
-
* the JSON Schema vocabulary in TypeScript.
|
|
1618
|
+
* `frontmatter/base.schema.json` via `allOf` + `$ref` to base's `$id`;
|
|
1619
|
+
* the loader registers base into the same AJV instance so cross-package
|
|
1620
|
+
* `$ref`-by-`$id` resolves transparently.
|
|
1627
1621
|
*/
|
|
1628
1622
|
schemaJson: unknown;
|
|
1629
|
-
/**
|
|
1630
|
-
* Qualified action id (`<plugin-id>/<action-id>`) the probabilistic-
|
|
1631
|
-
* refresh UI dispatches for nodes of this kind. The kernel resolves
|
|
1632
|
-
* the id against its qualified action registry; a dangling reference
|
|
1633
|
-
* disables the Provider with status `invalid-manifest`.
|
|
1634
|
-
*/
|
|
1635
|
-
defaultRefreshAction: string;
|
|
1636
1623
|
/**
|
|
1637
1624
|
* Presentation metadata the UI consumes to render nodes of this kind
|
|
1638
|
-
* (palette swatches, list tags, graph nodes, filter chips).
|
|
1639
|
-
* so the UI never has to
|
|
1640
|
-
*
|
|
1625
|
+
* (palette swatches, list tags, graph nodes, filter chips). Read from
|
|
1626
|
+
* `kinds/<kindName>/kind.json#/ui`. Required so the UI never has to
|
|
1627
|
+
* invent visuals for a Provider-declared kind.
|
|
1641
1628
|
*/
|
|
1642
1629
|
ui: IProviderKindUi;
|
|
1643
1630
|
}
|
|
@@ -1699,23 +1686,30 @@ type TProviderKindIcon = {
|
|
|
1699
1686
|
path: string;
|
|
1700
1687
|
};
|
|
1701
1688
|
interface IProvider extends IExtensionBase {
|
|
1689
|
+
/** Discriminant injected by the loader from the folder structure. */
|
|
1702
1690
|
kind: 'provider';
|
|
1703
1691
|
/**
|
|
1704
|
-
* Catalog of node kinds this Provider emits.
|
|
1705
|
-
*
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1692
|
+
* Catalog of node kinds this Provider emits. Populated by the loader
|
|
1693
|
+
* from the `<plugin>/kinds/<kindName>/` directory layout: each subfolder
|
|
1694
|
+
* becomes one entry, with `schema.json` parsed into `schemaJson` and
|
|
1695
|
+
* `kind.json#/ui` projected into `ui`. Authors do NOT write this map by
|
|
1696
|
+
* hand any more, it is a runtime descriptor only.
|
|
1708
1697
|
*
|
|
1709
1698
|
* The string keys are typed loosely (`string`) rather than `NodeKind`
|
|
1710
1699
|
* because the value space is open by design: a future Cursor Provider
|
|
1711
1700
|
* could declare `rule`, an Obsidian Provider could declare `daily`.
|
|
1712
|
-
* The kernel's hard-coded `NodeKind` union represents the kinds the
|
|
1713
|
-
* built-in Claude Provider emits; it is NOT the kernel-wide kind type
|
|
1714
|
-
* (see `kernel/types.ts:NodeKind` docstring). `Node.kind`, the AJV
|
|
1715
|
-
* `node.schema.json` validator, and the SQLite `scan_nodes.kind`
|
|
1716
|
-
* column all accept any non-empty string an enabled Provider returns.
|
|
1717
1701
|
*/
|
|
1718
1702
|
kinds: Record<string, IProviderKind>;
|
|
1703
|
+
/**
|
|
1704
|
+
* Optional path globs the Provider claims. Enforcement-grade since
|
|
1705
|
+
* structure-as-truth: a Provider declaring `roots` only receives files
|
|
1706
|
+
* matching at least one glob; a Provider without `roots` acts as a
|
|
1707
|
+
* fallback for files unmatched by every other Provider's roots. Two
|
|
1708
|
+
* Providers whose `roots` both match the same file produce a
|
|
1709
|
+
* `provider-ambiguous` issue and the file stays unclassified. Mirrors
|
|
1710
|
+
* `extensions/provider.schema.json#/properties/roots`.
|
|
1711
|
+
*/
|
|
1712
|
+
roots?: string[];
|
|
1719
1713
|
/**
|
|
1720
1714
|
* Optional auxiliary JSON Schemas this Provider's per-kind schemas
|
|
1721
1715
|
* `$ref` by `$id`. Registered with AJV via `addSchema` BEFORE the
|
|
@@ -1817,72 +1811,48 @@ interface IProviderReadConfig {
|
|
|
1817
1811
|
|
|
1818
1812
|
/**
|
|
1819
1813
|
* Extractor runtime contract. Consumes a single node (frontmatter + body)
|
|
1820
|
-
* and emits its output through
|
|
1821
|
-
*
|
|
1822
|
-
* nodes, the graph, or the DB. Cross-node reasoning lives in
|
|
1814
|
+
* and emits its output through context-supplied callbacks rather than a
|
|
1815
|
+
* return value. Extractors run in isolation: they MUST NOT read other
|
|
1816
|
+
* nodes, the graph, or the DB. Cross-node reasoning lives in Analyzers.
|
|
1823
1817
|
*
|
|
1824
1818
|
* Extractors are deterministic-only. They run synchronously inside the
|
|
1825
1819
|
* scan loop; LLM-driven enrichment of a node is an Action concern, not
|
|
1826
1820
|
* an Extractor concern. The Extractor context therefore exposes no
|
|
1827
1821
|
* `RunnerPort`, see spec `architecture.md` §Execution modes.
|
|
1828
1822
|
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1835
|
-
* onto the node. Strictly separate from the author-supplied frontmatter
|
|
1836
|
-
* (the latter remains immutable and survives verbatim). Persistence
|
|
1837
|
-
* is spec'd in § A.8.
|
|
1838
|
-
* - `ctx.store`, plugin-scoped persistence. Present only when the
|
|
1839
|
-
* plugin declares `storage.mode` in `plugin.json`; shape depends on the
|
|
1840
|
-
* mode (`KvStore` for mode A, scoped `Database` for mode B). See
|
|
1841
|
-
* `plugin-kv-api.md` for the contract.
|
|
1842
|
-
*
|
|
1843
|
-
* The manifest's `scope` field tells the orchestrator which parts to feed:
|
|
1844
|
-
* `frontmatter` extractors receive an empty string for body and vice versa.
|
|
1845
|
-
*
|
|
1846
|
-
* Renamed from `Detector` in spec 0.8.x. The previous `detect(ctx) → Link[]`
|
|
1847
|
-
* signature is gone; everything now flows through `extract(ctx) → void`
|
|
1848
|
-
* and the callbacks above.
|
|
1823
|
+
* **Structure-as-truth**: the extension's `id` and `kind` come from the
|
|
1824
|
+
* filesystem (`<plugin>/extractors/<id>/index.ts`); the manifest does NOT
|
|
1825
|
+
* declare them. The `emitsLinkKinds` allowlist was retired with the same
|
|
1826
|
+
* refactor: the global closed enum of link kinds is the contract, and an
|
|
1827
|
+
* extractor emitting an off-enum kind keeps surfacing `extension.error`.
|
|
1828
|
+
* Confidence is per-emit (no manifest-level default).
|
|
1849
1829
|
*/
|
|
1850
1830
|
|
|
1851
1831
|
/**
|
|
1852
1832
|
* Output callbacks supplied by the kernel on the extractor context.
|
|
1853
|
-
* Split out so plugin authors can name the callback shape if they
|
|
1854
|
-
* want to mock it in unit tests without depending on the wider
|
|
1855
|
-
* `IExtractorContext`.
|
|
1856
1833
|
*/
|
|
1857
1834
|
interface IExtractorCallbacks {
|
|
1858
1835
|
/**
|
|
1859
|
-
* Emit a single Link.
|
|
1860
|
-
*
|
|
1861
|
-
*
|
|
1836
|
+
* Emit a single Link. Validated against the global closed enum of
|
|
1837
|
+
* link kinds (`invokes`, `references`, `mentions`, `supersedes`)
|
|
1838
|
+
* before insertion; off-enum kinds drop silently with an
|
|
1839
|
+
* `extension.error` event.
|
|
1862
1840
|
*/
|
|
1863
1841
|
emitLink(link: Link): void;
|
|
1864
1842
|
/**
|
|
1865
1843
|
* Merge canonical, kernel-curated properties onto the current node's
|
|
1866
1844
|
* enrichment layer. The author-supplied frontmatter stays untouched
|
|
1867
|
-
* (Decision #109 in `ROADMAP.md`).
|
|
1868
|
-
* semantics live in spec § A.8; the orchestrator already buffers the
|
|
1869
|
-
* partials and `persistScanResult` upserts them.
|
|
1845
|
+
* (Decision #109 in `ROADMAP.md`).
|
|
1870
1846
|
*/
|
|
1871
1847
|
enrichNode(partial: Partial<Node>): void;
|
|
1872
1848
|
/**
|
|
1873
|
-
* Emit a per-node view contribution.
|
|
1874
|
-
*
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1877
|
-
*
|
|
1878
|
-
*
|
|
1879
|
-
*
|
|
1880
|
-
* slot's schema before persisting to `scan_contributions`; off-shape
|
|
1881
|
-
* payloads are silently dropped with an `extension.error` event
|
|
1882
|
-
* (mirror of `emitLink` rejecting off-`emitsLinkKinds` links).
|
|
1883
|
-
* Calling `emitContribution` with a `contributionId` that is not
|
|
1884
|
-
* declared in the manifest is also dropped with an `extension.error`.
|
|
1885
|
-
* See `architecture.md` §View contribution system → Emit path.
|
|
1849
|
+
* Emit a per-node view contribution. `contributionId` MUST be a key
|
|
1850
|
+
* declared under the manifest's `ui` map; the payload MUST conform to
|
|
1851
|
+
* the slot's payload schema in
|
|
1852
|
+
* `spec/schemas/view-slots.schema.json#/$defs/payloads/<slot>`. Off-shape
|
|
1853
|
+
* payloads (or unknown contribution ids) drop silently with an
|
|
1854
|
+
* `extension.error`. Renamed from `viewContributions` with the
|
|
1855
|
+
* structure-as-truth refactor.
|
|
1886
1856
|
*/
|
|
1887
1857
|
emitContribution(contributionId: string, payload: unknown): void;
|
|
1888
1858
|
}
|
|
@@ -1890,49 +1860,48 @@ interface IExtractorContext extends IExtractorCallbacks {
|
|
|
1890
1860
|
node: Node;
|
|
1891
1861
|
body: string;
|
|
1892
1862
|
frontmatter: Record<string, unknown>;
|
|
1863
|
+
/**
|
|
1864
|
+
* Resolved values of the extension's declared `settings`, populated
|
|
1865
|
+
* from project config + user overrides. Empty object when no settings
|
|
1866
|
+
* are declared.
|
|
1867
|
+
*/
|
|
1868
|
+
settings: Record<string, unknown>;
|
|
1893
1869
|
/**
|
|
1894
1870
|
* Plugin-scoped persistence. Optional because not every plugin declares
|
|
1895
|
-
* a `storage.mode` in `plugin.json`.
|
|
1896
|
-
* (`set(key, value)`), `DedicatedStoreWrapper` for mode B
|
|
1897
|
-
* (`write(table, row)`). See `spec/plugin-kv-api.md`.
|
|
1898
|
-
*
|
|
1899
|
-
* Typed as `unknown` so this contract module stays free of any
|
|
1900
|
-
* adapter-side imports, the concrete `IPluginStore` lives in
|
|
1901
|
-
* `kernel/adapters/plugin-store.js`. Plugin authors narrow at the
|
|
1902
|
-
* call site based on the storage mode declared in their manifest.
|
|
1903
|
-
* The orchestrator looks up the wrapper per-extractor in
|
|
1904
|
-
* `RunScanOptions.pluginStores` (keyed by `pluginId`) and attaches
|
|
1905
|
-
* it here.
|
|
1871
|
+
* a `storage.mode` in `plugin.json`. See `spec/plugin-kv-api.md`.
|
|
1906
1872
|
*/
|
|
1907
1873
|
store?: unknown;
|
|
1908
1874
|
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Optional declarative filter shared with Analyzer and Action. The kernel
|
|
1877
|
+
* applies a single matcher: every declared sub-filter must hold for the
|
|
1878
|
+
* extension to be invoked on the candidate node.
|
|
1879
|
+
*/
|
|
1880
|
+
interface IExtensionPrecondition {
|
|
1881
|
+
/**
|
|
1882
|
+
* Qualified node kinds the extension accepts, written as
|
|
1883
|
+
* `<provider-plugin>/<kindName>` (e.g. `claude/agent`). Unknown
|
|
1884
|
+
* qualified kinds load OK but surface a `precondition-kind-unknown`
|
|
1885
|
+
* warning in `sm plugins doctor`.
|
|
1886
|
+
*/
|
|
1887
|
+
kind?: string[];
|
|
1888
|
+
/** Provider ids whose nodes the extension accepts. */
|
|
1889
|
+
provider?: string[];
|
|
1890
|
+
}
|
|
1909
1891
|
interface IExtractor extends IExtensionBase {
|
|
1892
|
+
/** Discriminant injected by the loader from the folder structure. */
|
|
1910
1893
|
kind: 'extractor';
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
1917
|
-
* this list, fail-fast, before context construction, so the extractor
|
|
1918
|
-
* wastes zero CPU on inapplicable nodes.
|
|
1919
|
-
*
|
|
1920
|
-
* Absent (`undefined`) is the default: the extractor applies to every
|
|
1921
|
-
* kind. There are no wildcards, the absence of the field already
|
|
1922
|
-
* encodes "every kind". An empty array (`[]`) is rejected at load
|
|
1923
|
-
* time by AJV (`minItems: 1` in the schema).
|
|
1924
|
-
*
|
|
1925
|
-
* Unknown kinds (no installed Provider declares them) do NOT block
|
|
1926
|
-
* the load: the extractor keeps `loaded` status and `sm plugins doctor`
|
|
1927
|
-
* surfaces a warning. The Provider that declares the kind may arrive
|
|
1928
|
-
* later (e.g. a user installs the corresponding plugin).
|
|
1929
|
-
*
|
|
1930
|
-
* Spec: `spec/schemas/extensions/extractor.schema.json#/properties/applicableKinds`.
|
|
1894
|
+
/** Which slice of the node the orchestrator feeds. Defaults to `both`. */
|
|
1895
|
+
scope?: 'frontmatter' | 'body' | 'both';
|
|
1896
|
+
/**
|
|
1897
|
+
* Optional precondition that gates `extract()` invocation. Replaces
|
|
1898
|
+
* the old `applicableKinds` field; same shape used by Analyzer and
|
|
1899
|
+
* Action so the kernel ships a single matcher.
|
|
1931
1900
|
*/
|
|
1932
|
-
|
|
1901
|
+
precondition?: IExtensionPrecondition;
|
|
1933
1902
|
/**
|
|
1934
1903
|
* Extractor entry point. Returns nothing; output flows through
|
|
1935
|
-
* `ctx.emitLink`, `ctx.enrichNode`,
|
|
1904
|
+
* `ctx.emitLink`, `ctx.enrichNode`, `ctx.emitContribution`, `ctx.store`.
|
|
1936
1905
|
*/
|
|
1937
1906
|
extract(ctx: IExtractorContext): void | Promise<void>;
|
|
1938
1907
|
}
|
|
@@ -2056,26 +2025,26 @@ interface IAnalyzerContext {
|
|
|
2056
2025
|
emitContribution(nodePath: string, contributionId: string, payload: unknown): void;
|
|
2057
2026
|
}
|
|
2058
2027
|
interface IAnalyzer extends IExtensionBase {
|
|
2028
|
+
/** Discriminant injected by the loader from the folder structure. */
|
|
2059
2029
|
kind: 'analyzer';
|
|
2060
2030
|
/**
|
|
2061
2031
|
* Execution mode. Optional in the manifest with a default of
|
|
2062
|
-
* `deterministic`
|
|
2032
|
+
* `deterministic`. `probabilistic` analyzers run only as queued jobs.
|
|
2063
2033
|
*/
|
|
2064
2034
|
mode?: TExecutionMode;
|
|
2065
2035
|
/**
|
|
2066
|
-
*
|
|
2067
|
-
*
|
|
2068
|
-
*
|
|
2069
|
-
*
|
|
2070
|
-
*
|
|
2071
|
-
*
|
|
2072
|
-
*
|
|
2073
|
-
*
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
recommendedActions?: readonly string[];
|
|
2036
|
+
* Optional declarative precondition. Same shape used by Extractor and
|
|
2037
|
+
* Action. The analyzer is invoked only when the graph contains at
|
|
2038
|
+
* least one node matching every declared sub-filter.
|
|
2039
|
+
*
|
|
2040
|
+
* The reverse relationship (which Actions resolve this analyzer's
|
|
2041
|
+
* findings) is now declared on the Action side via
|
|
2042
|
+
* `precondition.analyzerIds` (Modelo B). The old
|
|
2043
|
+
* `recommendedActions` field was retired with the structure-as-truth
|
|
2044
|
+
* refactor; the UI matches against Action manifests when surfacing
|
|
2045
|
+
* "Resolve this issue" affordances.
|
|
2046
|
+
*/
|
|
2047
|
+
precondition?: IExtensionPrecondition;
|
|
2079
2048
|
evaluate(ctx: IAnalyzerContext): Issue[] | Promise<Issue[]>;
|
|
2080
2049
|
}
|
|
2081
2050
|
|
|
@@ -2085,182 +2054,107 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
2085
2054
|
*
|
|
2086
2055
|
* Actions operate on one or more nodes in one of two execution modes:
|
|
2087
2056
|
*
|
|
2088
|
-
* - `deterministic
|
|
2089
|
-
* report synchronously and returns it. No job file, no runner.
|
|
2090
|
-
* - `probabilistic`, the kernel renders
|
|
2091
|
-
* job file; a runner executes it via `RunnerPort` against an
|
|
2092
|
-
* `sm record` closes the job and validates the report against
|
|
2093
|
-
*
|
|
2094
|
-
*
|
|
2095
|
-
* **
|
|
2096
|
-
*
|
|
2097
|
-
*
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2101
|
-
* the
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2104
|
-
*
|
|
2105
|
-
*
|
|
2106
|
-
*
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
*
|
|
2113
|
-
* `allOf` enforces both directions; the runtime contract simply
|
|
2114
|
-
* surfaces the field as optional and lets the loader catch shape
|
|
2115
|
-
* violations at AJV time.
|
|
2116
|
-
* - `expectedDurationSeconds`, REQUIRED for probabilistic (drives
|
|
2117
|
-
* TTL); advisory for deterministic.
|
|
2118
|
-
* - `precondition`, declarative filter consumed by `--all` fan-out,
|
|
2119
|
-
* UI button gating, `sm actions show`.
|
|
2120
|
-
* - `expectedTools`, hint to Skill / CLI runners about expected
|
|
2121
|
-
* tools (no normative enforcement in v0).
|
|
2122
|
-
* - `fanOutPolicy`, `'per-node'` (default) vs `'batch'`.
|
|
2057
|
+
* - `deterministic` (default), code runs in-process; the action computes
|
|
2058
|
+
* the report synchronously and returns it. No job file, no runner.
|
|
2059
|
+
* - `probabilistic`, the kernel renders `<action-dir>/prompt.md` + preamble
|
|
2060
|
+
* into a job file; a runner executes it via `RunnerPort` against an
|
|
2061
|
+
* LLM; `sm record` closes the job and validates the report against
|
|
2062
|
+
* `<action-dir>/report.schema.json`.
|
|
2063
|
+
*
|
|
2064
|
+
* **Structure-as-truth file conventions**: every Action carries
|
|
2065
|
+
* `<action-dir>/report.schema.json` (the JSON Schema for the report, MUST
|
|
2066
|
+
* extend `report-base.schema.json`). Probabilistic Actions additionally
|
|
2067
|
+
* carry `<action-dir>/prompt.md` (the prompt template). The loader resolves
|
|
2068
|
+
* both by convention; missing or mis-placed files surface as `load-error`.
|
|
2069
|
+
* The `reportSchemaRef` / `promptTemplateRef` manifest fields were retired
|
|
2070
|
+
* with the same refactor.
|
|
2071
|
+
*
|
|
2072
|
+
* **`prob*` prefix convention**: manifest fields that only apply when
|
|
2073
|
+
* `mode=probabilistic` start with `prob`. Today only
|
|
2074
|
+
* `probExpectedDurationSeconds` follows this convention.
|
|
2075
|
+
*
|
|
2076
|
+
* **Deferred runtime invocation**: the dispatcher (`Action.invoke(input, ctx)`
|
|
2077
|
+
* for deterministic; the `RunnerPort` + `sm record` round-trip for
|
|
2078
|
+
* probabilistic) lands fully with the job subsystem (Decision #114 in
|
|
2079
|
+
* `ROADMAP.md`). The kernel today still validates manifests and surfaces
|
|
2080
|
+
* the precondition gating to the UI; the runtime entry point stays
|
|
2081
|
+
* optional until the job subsystem ships.
|
|
2123
2082
|
*/
|
|
2124
2083
|
|
|
2125
|
-
/**
|
|
2126
|
-
* Single sidecar write payload an Action can return. Discriminated union so
|
|
2127
|
-
* future write kinds (storage rows, plugin KV, etc.) can land additively
|
|
2128
|
-
* without breaking consumers that only handle `kind: 'sidecar'`.
|
|
2129
|
-
*
|
|
2130
|
-
* - `path`, absolute path to the `.sm` file the kernel must materialise
|
|
2131
|
-
* the change into. Resolved by the Action from the node's absolute
|
|
2132
|
-
* path via `sidecarPathFor()`.
|
|
2133
|
-
* - `changes`, partial sidecar root used as a deep-merge patch (NOT a
|
|
2134
|
-
* full replacement). Arrays REPLACE; objects RECURSE. Reason:
|
|
2135
|
-
* sidecars are shared-write between skill-map core and plugins;
|
|
2136
|
-
* a full replace would clobber `<plugin-id>:` namespaced blocks.
|
|
2137
|
-
*/
|
|
2138
2084
|
type TActionWrite = {
|
|
2139
2085
|
kind: 'sidecar';
|
|
2140
2086
|
path: string;
|
|
2141
2087
|
changes: Record<string, unknown>;
|
|
2142
2088
|
};
|
|
2143
|
-
/**
|
|
2144
|
-
* Result envelope returned by deterministic Actions. The `report` field
|
|
2145
|
-
* carries the typed report payload (each Action declares its shape via
|
|
2146
|
-
* `reportSchemaRef`); `writes` is opt-in, Actions that do not mutate
|
|
2147
|
-
* persistent state simply omit it.
|
|
2148
|
-
*/
|
|
2149
2089
|
interface IActionResult<TReport = unknown> {
|
|
2150
2090
|
report: TReport;
|
|
2151
2091
|
writes?: TActionWrite[];
|
|
2152
2092
|
}
|
|
2153
|
-
/**
|
|
2154
|
-
* Runtime context passed to a deterministic Action's `invoke()` method.
|
|
2155
|
-
* Minimal surface, Actions stay pure (no IO inside `invoke`); the kernel
|
|
2156
|
-
* materialises any returned `writes` after the call.
|
|
2157
|
-
*
|
|
2158
|
-
* - `node`, the target `Node` the Action operates on. Open-by-design;
|
|
2159
|
-
* batch / fan-out flows pick the matching nodes upstream.
|
|
2160
|
-
* - `nodeAbsolutePath`, absolute path to the node's `.md` file on
|
|
2161
|
-
* disk. The Action uses this to compute the sidecar path it returns
|
|
2162
|
-
* in a `TActionWrite`. Surfaced separately from `node.path` (which is
|
|
2163
|
-
* the relative scope-root form) so Actions never compose absolute
|
|
2164
|
-
* paths from `node.path` themselves.
|
|
2165
|
-
* - `invoker`, identity of the caller; written into the sidecar's
|
|
2166
|
-
* `audit.lastBumpedBy` when the Action chooses to. CLI invocations
|
|
2167
|
-
* pass `'cli'`; plugin-driven invocations pass `'plugin:<plugin-id>'`.
|
|
2168
|
-
* - `now`, clock function; tests inject a deterministic source.
|
|
2169
|
-
* Defaults to `() => new Date()` at the composition root.
|
|
2170
|
-
*/
|
|
2171
2093
|
interface IActionContext {
|
|
2172
2094
|
node: Node;
|
|
2173
2095
|
nodeAbsolutePath: string;
|
|
2174
2096
|
invoker: string;
|
|
2175
2097
|
now: () => Date;
|
|
2098
|
+
/**
|
|
2099
|
+
* Resolved values of the Action's declared `settings`. Empty when no
|
|
2100
|
+
* settings are declared on the manifest.
|
|
2101
|
+
*/
|
|
2102
|
+
settings: Record<string, unknown>;
|
|
2176
2103
|
}
|
|
2177
2104
|
/**
|
|
2178
2105
|
* Declarative filter applied by `--all` fan-out, UI button gating, and
|
|
2179
|
-
* `sm actions show`.
|
|
2180
|
-
*
|
|
2106
|
+
* `sm actions show`. Same shape used by Extractor and Analyzer so the
|
|
2107
|
+
* kernel ships a single matcher; the `analyzerIds` field is unique to
|
|
2108
|
+
* Action and powers Modelo B (Action declares which Analyzer findings
|
|
2109
|
+
* it resolves; replaces the deprecated `Analyzer.recommendedActions`).
|
|
2181
2110
|
*/
|
|
2182
2111
|
interface IActionPrecondition {
|
|
2183
2112
|
/**
|
|
2184
|
-
*
|
|
2185
|
-
*
|
|
2186
|
-
*
|
|
2187
|
-
*
|
|
2113
|
+
* Qualified node kinds this action accepts, written as
|
|
2114
|
+
* `<provider-plugin>/<kindName>` (e.g. `claude/agent`). Unknown
|
|
2115
|
+
* qualified kinds load OK but surface a `precondition-kind-unknown`
|
|
2116
|
+
* warning in `sm plugins doctor`.
|
|
2188
2117
|
*/
|
|
2189
2118
|
kind?: string[];
|
|
2190
|
-
/** Provider ids whose nodes this action accepts.
|
|
2119
|
+
/** Provider ids whose nodes this action accepts. */
|
|
2191
2120
|
provider?: string[];
|
|
2192
|
-
/** Node stability filter. */
|
|
2193
|
-
stability?: Array<'experimental' | 'stable' | 'deprecated'>;
|
|
2194
2121
|
/**
|
|
2195
|
-
*
|
|
2196
|
-
*
|
|
2122
|
+
* Qualified analyzer ids whose findings this action resolves
|
|
2123
|
+
* (`<plugin>/<analyzer>` or `<plugin>/<analyzer>:<sub-id>` when the
|
|
2124
|
+
* analyzer emits sub-typed issues). The UI matches against this list
|
|
2125
|
+
* to surface "Resolve this issue" affordances. Dangling references
|
|
2126
|
+
* warn via `recommended-action-missing` in `sm plugins doctor` but
|
|
2127
|
+
* do NOT block load.
|
|
2197
2128
|
*/
|
|
2198
|
-
|
|
2129
|
+
analyzerIds?: string[];
|
|
2199
2130
|
}
|
|
2200
2131
|
interface IAction extends IExtensionBase {
|
|
2132
|
+
/** Discriminant injected by the loader from the folder structure. */
|
|
2201
2133
|
kind: 'action';
|
|
2202
2134
|
/**
|
|
2203
|
-
* Execution mode
|
|
2204
|
-
*
|
|
2205
|
-
*/
|
|
2206
|
-
mode: TExecutionMode;
|
|
2207
|
-
/**
|
|
2208
|
-
* Reference to the JSON Schema the report MUST validate against. MUST
|
|
2209
|
-
* extend `report-base.schema.json` (directly or transitively).
|
|
2210
|
-
* Validation failure → job transitions to `failed` with reason
|
|
2211
|
-
* `report-invalid`.
|
|
2135
|
+
* Execution mode. Optional with default `deterministic` since the
|
|
2136
|
+
* structure-as-truth refactor.
|
|
2212
2137
|
*/
|
|
2213
|
-
|
|
2214
|
-
/**
|
|
2215
|
-
* Best-effort estimate of wall-clock duration in seconds. Drives TTL
|
|
2216
|
-
* (`ttl = max(expectedDurationSeconds × graceMultiplier,
|
|
2217
|
-
* minimumTtlSeconds)`). Required for `probabilistic`; advisory for
|
|
2218
|
-
* `deterministic`.
|
|
2219
|
-
*/
|
|
2220
|
-
expectedDurationSeconds?: number;
|
|
2138
|
+
mode?: TExecutionMode;
|
|
2221
2139
|
/**
|
|
2222
|
-
*
|
|
2223
|
-
*
|
|
2224
|
-
*
|
|
2225
|
-
*
|
|
2226
|
-
*
|
|
2227
|
-
*
|
|
2140
|
+
* Best-effort estimate of wall-clock duration in seconds when
|
|
2141
|
+
* `mode=probabilistic`. Drives TTL
|
|
2142
|
+
* (`ttl = max(probExpectedDurationSeconds × graceMultiplier,
|
|
2143
|
+
* minimumTtlSeconds)`). Required by the schema's conditional for
|
|
2144
|
+
* probabilistic Actions; ignored otherwise. Renamed from
|
|
2145
|
+
* `expectedDurationSeconds` with the `prob*` prefix convention.
|
|
2228
2146
|
*/
|
|
2229
|
-
|
|
2147
|
+
probExpectedDurationSeconds?: number;
|
|
2230
2148
|
/**
|
|
2231
2149
|
* Optional declarative filter; absent → applies to every node.
|
|
2232
2150
|
*/
|
|
2233
2151
|
precondition?: IActionPrecondition;
|
|
2234
2152
|
/**
|
|
2235
|
-
*
|
|
2236
|
-
*
|
|
2237
|
-
*
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
/**
|
|
2241
|
-
* `'per-node'` (default): `sm job submit --all` produces one job per
|
|
2242
|
-
* matching node. `'batch'`: one job whose prompt template receives the
|
|
2243
|
-
* full list. Batch actions tend to hit context limits; use sparingly.
|
|
2244
|
-
*/
|
|
2245
|
-
fanOutPolicy?: 'per-node' | 'batch';
|
|
2246
|
-
/**
|
|
2247
|
-
* Deterministic invocation entry point. OPTIONAL on the runtime
|
|
2248
|
-
* contract for backward compatibility with the manifest-only era
|
|
2249
|
-
* (Decision #114), actions that ship for the future probabilistic
|
|
2250
|
-
* runner / record path leave it absent and the kernel never calls it.
|
|
2251
|
-
* Step 9.6.3 (Decision #125) introduces the first concrete consumer:
|
|
2252
|
-
* the built-in `bump` Action implements `invoke()` and returns a
|
|
2253
|
-
* `writes: [{ kind: 'sidecar', ... }]` payload that the kernel
|
|
2254
|
-
* materialises through `ISidecarStore`.
|
|
2255
|
-
*
|
|
2256
|
-
* Implementations MUST stay pure, no IO inside `invoke()`. The Action
|
|
2257
|
-
* computes the patch and returns it; the kernel reads the on-disk
|
|
2258
|
-
* sidecar, deep-merges, validates, and writes back inside its critical
|
|
2259
|
-
* section.
|
|
2260
|
-
*
|
|
2261
|
-
* `TInput` is action-specific; the built-in `bump` Action declares
|
|
2262
|
-
* `{ force?: boolean; reason?: string }`. The signature stays generic
|
|
2263
|
-
* so each Action narrows it locally without forcing a common base.
|
|
2153
|
+
* Deterministic invocation entry point. Optional on the runtime
|
|
2154
|
+
* contract until the job subsystem ships; Actions that ship for the
|
|
2155
|
+
* future probabilistic runner / record path leave it absent.
|
|
2156
|
+
* Implementations MUST stay pure (no IO inside `invoke()`); the
|
|
2157
|
+
* kernel materialises any returned `writes` after the call.
|
|
2264
2158
|
*/
|
|
2265
2159
|
invoke?: <TInput, TReport>(input: TInput, ctx: IActionContext) => IActionResult<TReport>;
|
|
2266
2160
|
}
|
|
@@ -2269,19 +2163,14 @@ interface IAction extends IExtensionBase {
|
|
|
2269
2163
|
* Formatter runtime contract. Turns the (nodes, links, issues) graph into
|
|
2270
2164
|
* a textual representation for `sm graph --format <name>`.
|
|
2271
2165
|
*
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
2274
|
-
*
|
|
2275
|
-
*
|
|
2276
|
-
*
|
|
2277
|
-
* - `format(ctx) → string`, the runtime method. Receives the full
|
|
2278
|
-
* graph and returns the serialized output. Output MUST be
|
|
2279
|
-
* byte-deterministic for the same input (the snapshot-test suite
|
|
2280
|
-
* relies on this).
|
|
2166
|
+
* **Structure-as-truth**: the format id comes from the formatter's folder
|
|
2167
|
+
* name (`<plugin>/formatters/<formatId>/index.ts`); it is injected by the
|
|
2168
|
+
* loader into `id` and surfaced here as `formatId` for the existing CLI
|
|
2169
|
+
* lookup (`formatters.find((f) => f.formatId === flag)`). Manifests carrying
|
|
2170
|
+
* a `formatId` literal are rejected as `invalid-manifest`.
|
|
2281
2171
|
*
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2284
|
-
* field carries the identifier the user types on the command line.
|
|
2172
|
+
* All formatters accept the `--filter` expression; opting out is no longer
|
|
2173
|
+
* supported (the old `supportsFilter` field was retired).
|
|
2285
2174
|
*/
|
|
2286
2175
|
|
|
2287
2176
|
interface IFormatterContext {
|
|
@@ -2290,19 +2179,28 @@ interface IFormatterContext {
|
|
|
2290
2179
|
issues: Issue[];
|
|
2291
2180
|
/**
|
|
2292
2181
|
* Full persisted scan, when the caller has it on hand. Optional so
|
|
2293
|
-
*
|
|
2294
|
-
*
|
|
2295
|
-
*
|
|
2296
|
-
*
|
|
2297
|
-
* canonical document verbatim. `undefined` when the caller has only
|
|
2298
|
-
* the three primary arrays (back-compat with older drivers).
|
|
2182
|
+
* formatters that only consume (nodes, links, issues) keep working
|
|
2183
|
+
* unchanged; formatters whose output mirrors a `ScanResult` envelope
|
|
2184
|
+
* (today: the built-in `json` formatter) read this to project the
|
|
2185
|
+
* canonical document verbatim.
|
|
2299
2186
|
*/
|
|
2300
2187
|
scanResult?: ScanResult;
|
|
2301
2188
|
}
|
|
2302
2189
|
interface IFormatter extends IExtensionBase {
|
|
2190
|
+
/** Discriminant injected by the loader from the folder structure. */
|
|
2303
2191
|
kind: 'formatter';
|
|
2304
|
-
/**
|
|
2192
|
+
/**
|
|
2193
|
+
* Format identifier consumed by `sm graph --format <name>`. Injected
|
|
2194
|
+
* by the loader from the formatter folder name. Surfaced as a top-level
|
|
2195
|
+
* field (rather than reusing `id`) so the existing CLI lookup keeps its
|
|
2196
|
+
* domain-specific name.
|
|
2197
|
+
*/
|
|
2305
2198
|
formatId: string;
|
|
2199
|
+
/**
|
|
2200
|
+
* MIME-like hint surfaced when streaming over HTTP. Advisory; default
|
|
2201
|
+
* `'text/plain'`.
|
|
2202
|
+
*/
|
|
2203
|
+
contentType?: string;
|
|
2306
2204
|
/** Serialize the graph into a string. Deterministic-only. */
|
|
2307
2205
|
format(ctx: IFormatterContext): string;
|
|
2308
2206
|
}
|
|
@@ -2327,16 +2225,14 @@ interface IFormatter extends IExtensionBase {
|
|
|
2327
2225
|
* Declaring a trigger outside the curated set yields
|
|
2328
2226
|
* `invalid-manifest` at load time.
|
|
2329
2227
|
*
|
|
2330
|
-
*
|
|
2331
|
-
*
|
|
2332
|
-
*
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
*
|
|
2338
|
-
* subsystem ships, probabilistic hooks load but skip dispatch
|
|
2339
|
-
* with a stderr advisory (Decision #114 in `ROADMAP.md`).
|
|
2228
|
+
* **Deterministic-only since the structure-as-truth refactor**: the
|
|
2229
|
+
* `mode` field was removed from the manifest. `on(ctx)` runs in-process
|
|
2230
|
+
* during the dispatch of the matching event, synchronously between the
|
|
2231
|
+
* event's emission and the next pipeline step. Errors are caught by
|
|
2232
|
+
* the dispatcher, logged via `extension.error`, and never block the
|
|
2233
|
+
* main flow. To react to a lifecycle event with an LLM call, write a
|
|
2234
|
+
* deterministic Hook that enqueues a probabilistic Action via
|
|
2235
|
+
* `ctx.queue('<plugin>/<action>', payload)`.
|
|
2340
2236
|
*
|
|
2341
2237
|
* Curated trigger set (per spec § A.11):
|
|
2342
2238
|
*
|
|
@@ -2419,12 +2315,12 @@ interface IHookContext {
|
|
|
2419
2315
|
*/
|
|
2420
2316
|
jobResult?: unknown;
|
|
2421
2317
|
/**
|
|
2422
|
-
*
|
|
2423
|
-
*
|
|
2424
|
-
* the job subsystem
|
|
2425
|
-
*
|
|
2318
|
+
* Enqueue a probabilistic Action as a deferred job. The Hook stays
|
|
2319
|
+
* deterministic; LLM dispatch happens via the job subsystem the
|
|
2320
|
+
* Action drives. Available when the job subsystem is wired in;
|
|
2321
|
+
* placeholder `undefined` for legacy callers.
|
|
2426
2322
|
*/
|
|
2427
|
-
|
|
2323
|
+
queue?: (actionId: string, payload: unknown) => void;
|
|
2428
2324
|
}
|
|
2429
2325
|
/**
|
|
2430
2326
|
* Optional declarative filter applied by the dispatcher BEFORE
|
|
@@ -2444,14 +2340,8 @@ interface IHookContext {
|
|
|
2444
2340
|
*/
|
|
2445
2341
|
type THookFilter = Record<string, string | number | boolean>;
|
|
2446
2342
|
interface IHook extends IExtensionBase {
|
|
2343
|
+
/** Discriminant injected by the loader from the folder structure. */
|
|
2447
2344
|
kind: 'hook';
|
|
2448
|
-
/**
|
|
2449
|
-
* Execution mode. Optional in the manifest with a default of
|
|
2450
|
-
* `deterministic` per `spec/schemas/extensions/hook.schema.json`.
|
|
2451
|
-
* Probabilistic hooks load but skip dispatch with a stderr advisory
|
|
2452
|
-
* until the job subsystem ships (Decision #114).
|
|
2453
|
-
*/
|
|
2454
|
-
mode?: TExecutionMode;
|
|
2455
2345
|
/**
|
|
2456
2346
|
* Subset of the curated lifecycle trigger set this hook subscribes
|
|
2457
2347
|
* to. MUST be non-empty; every entry MUST be a member of
|
|
@@ -2646,8 +2536,17 @@ interface RenameOp {
|
|
|
2646
2536
|
* order so the same input always produces the same matches,
|
|
2647
2537
|
* required for reproducible tests and conformance fixtures (the spec
|
|
2648
2538
|
* does not prescribe an order, but stability is the obvious contract).
|
|
2539
|
+
*
|
|
2540
|
+
* `silenced` (optional): predicate that returns true when a path
|
|
2541
|
+
* disappeared from the current scan because the project's
|
|
2542
|
+
* `.skillmapignore` (or any other ignore source) started excluding
|
|
2543
|
+
* it, not because the file was actually deleted from disk. The
|
|
2544
|
+
* orphan flagger uses it to skip the info-severity issue for those
|
|
2545
|
+
* paths: silencing a node intentionally is not the same as losing
|
|
2546
|
+
* one without a rename match. Callers that don't pass it preserve
|
|
2547
|
+
* the previous behaviour (treat every disappearance as an orphan).
|
|
2649
2548
|
*/
|
|
2650
|
-
declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], issues: Issue[]): RenameOp[];
|
|
2549
|
+
declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], issues: Issue[], silenced?: (path: string) => boolean): RenameOp[];
|
|
2651
2550
|
|
|
2652
2551
|
/**
|
|
2653
2552
|
* Scan orchestrator, runs the Provider → extractor → analyzer pipeline across
|