@skill-map/spec 0.22.0 → 0.24.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/CHANGELOG.md +91 -0
- package/README.md +4 -4
- package/architecture.md +130 -119
- package/cli-contract.md +102 -96
- package/conformance/README.md +13 -13
- package/conformance/coverage.md +41 -38
- package/db-schema.md +45 -45
- package/index.json +40 -37
- package/interfaces/security-scanner.md +20 -20
- package/job-events.md +21 -21
- package/job-lifecycle.md +21 -21
- package/package.json +1 -1
- package/plugin-author-guide.md +133 -108
- package/plugin-kv-api.md +10 -10
- package/prompt-preamble.md +8 -8
- package/schemas/annotations.schema.json +3 -3
- package/schemas/api/rest-envelope.schema.json +10 -10
- package/schemas/conformance-result.schema.json +120 -0
- package/schemas/execution-record.schema.json +2 -2
- package/schemas/extensions/analyzer.schema.json +9 -0
- package/schemas/extensions/base.schema.json +4 -4
- package/schemas/extensions/extractor.schema.json +4 -4
- package/schemas/extensions/formatter.schema.json +1 -1
- package/schemas/extensions/hook.schema.json +3 -3
- package/schemas/extensions/provider.schema.json +5 -5
- package/schemas/frontmatter/base.schema.json +1 -1
- package/schemas/history-stats.schema.json +4 -4
- package/schemas/input-types.schema.json +3 -3
- package/schemas/issue.schema.json +1 -1
- package/schemas/job.schema.json +2 -2
- package/schemas/node.schema.json +6 -5
- package/schemas/plugins-doctor.schema.json +97 -0
- package/schemas/plugins-registry.schema.json +2 -2
- package/schemas/project-config.schema.json +9 -9
- package/schemas/refresh-report.schema.json +52 -0
- package/schemas/report-base-deterministic.schema.json +1 -1
- package/schemas/sidecar.schema.json +3 -3
- package/schemas/summaries/markdown.schema.json +1 -1
- package/schemas/summaries/skill.schema.json +1 -1
- package/schemas/view-slots.schema.json +7 -7
- package/versioning.md +7 -7
package/plugin-kv-api.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Normative contract for plugin-accessible persistence. Two modes exist (see [`db-schema.md`](./db-schema.md) for the catalog entries):
|
|
4
4
|
|
|
5
|
-
- **Mode A
|
|
6
|
-
- **Mode B
|
|
5
|
+
- **Mode A, KV**: plugin uses the kernel-provided `ctx.store.*` accessor. Backed by the shared `state_plugin_kvs` table.
|
|
6
|
+
- **Mode B, Dedicated**: plugin owns its own tables with the `plugin_<normalizedId>_` prefix, migrated by the kernel.
|
|
7
7
|
|
|
8
8
|
This document defines mode A in full and clarifies the boundary with mode B. Implementations MUST expose this API to every plugin that declares `"storage": { "mode": "kv" }` in its manifest.
|
|
9
9
|
|
|
@@ -47,7 +47,7 @@ Implementations in other languages MUST expose the same semantic surface.
|
|
|
47
47
|
|
|
48
48
|
### Scoping
|
|
49
49
|
|
|
50
|
-
Every operation is scoped by the caller's `pluginId`. The plugin cannot specify, override, or observe another plugin's `pluginId`. This is enforced by the kernel when constructing the `ctx.store
|
|
50
|
+
Every operation is scoped by the caller's `pluginId`. The plugin cannot specify, override, or observe another plugin's `pluginId`. This is enforced by the kernel when constructing the `ctx.store`, the `pluginId` is captured at registration time and is not an argument.
|
|
51
51
|
|
|
52
52
|
Operations MAY be additionally scoped by `nodePath`:
|
|
53
53
|
|
|
@@ -81,7 +81,7 @@ Return order of `list` is NOT specified by this spec; consumers MUST NOT rely on
|
|
|
81
81
|
|
|
82
82
|
### Transactions
|
|
83
83
|
|
|
84
|
-
The `KvStore` operations are individually atomic. There is NO multi-operation transaction in mode A
|
|
84
|
+
The `KvStore` operations are individually atomic. There is NO multi-operation transaction in mode A, plugins that need transactional semantics across several rows MUST use mode B.
|
|
85
85
|
|
|
86
86
|
Implementations MUST NOT expose a `transaction()` method on `KvStore` in mode A. The shape is intentionally minimal to keep the backing table simple.
|
|
87
87
|
|
|
@@ -126,7 +126,7 @@ interface DedicatedStore {
|
|
|
126
126
|
}
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
`DedicatedStore.db` is a wrapper
|
|
129
|
+
`DedicatedStore.db` is a wrapper, NOT a raw handle. Every query passes through a validator that rejects:
|
|
130
130
|
|
|
131
131
|
- References to tables whose name doesn't start with this plugin's prefix.
|
|
132
132
|
- DDL statements (`CREATE`, `ALTER`, `DROP`, `TRUNCATE`). Mode B DDL is runtime-immutable after migrations; plugins change shape via a new migration, not at runtime.
|
|
@@ -168,7 +168,7 @@ Non-normative; descriptive guidance for plugin authors.
|
|
|
168
168
|
- Your data model is actually tabular (cache with TTL, observation log, provider registry).
|
|
169
169
|
- You are willing to own migrations forever.
|
|
170
170
|
|
|
171
|
-
A plugin MUST declare **exactly one** storage mode. Mixing modes in the same plugin is forbidden. The [`plugins-registry.schema.json`](./schemas/plugins-registry.schema.json) enforces this at the manifest level (`storage` is a `oneOf` between `kv` and `dedicated`), and at runtime `ctx.store` exposes either the `KvStore` or the `DedicatedStore` shape
|
|
171
|
+
A plugin MUST declare **exactly one** storage mode. Mixing modes in the same plugin is forbidden. The [`plugins-registry.schema.json`](./schemas/plugins-registry.schema.json) enforces this at the manifest level (`storage` is a `oneOf` between `kv` and `dedicated`), and at runtime `ctx.store` exposes either the `KvStore` or the `DedicatedStore` shape, never both. A plugin that needs both KV-like and relational access MUST use mode B and implement KV-style rows as a dedicated table.
|
|
172
172
|
|
|
173
173
|
---
|
|
174
174
|
|
|
@@ -184,8 +184,8 @@ A plugin MUST declare **exactly one** storage mode. Mixing modes in the same plu
|
|
|
184
184
|
|
|
185
185
|
- Mode A rows are stored in `state_plugin_kvs` and are backed up with `sm db backup`.
|
|
186
186
|
- Mode B rows live in the plugin's dedicated tables, prefixed `plugin_<id>_`, and are likewise backed up.
|
|
187
|
-
- `sm plugins disable <id>` does NOT drop the plugin's data
|
|
188
|
-
- `sm db reset` (no modifier) drops only `scan_*`. Plugin KV rows (mode A) and plugin-dedicated tables (mode B) are **preserved
|
|
187
|
+
- `sm plugins disable <id>` does NOT drop the plugin's data, disabled plugins keep their KV rows and dedicated tables. (`scan_contributions` rows ARE purged eagerly on disable, see `db-schema.md` § `scan_contributions`, because those are scan-derived and would otherwise keep rendering in the UI until the next scan. The KV / dedicated-table data is plugin-managed and survives toggle cycles so re-enabling restores state.) `sm plugins forget <id>` (deferred to post-`v1.0`) is the verb that wipes everything.
|
|
188
|
+
- `sm db reset` (no modifier) drops only `scan_*`. Plugin KV rows (mode A) and plugin-dedicated tables (mode B) are **preserved**, the reset is non-destructive to plugin storage.
|
|
189
189
|
- `sm db reset --state` drops `state_*` AND every `plugin_<normalized_id>_*` table, which includes `state_plugin_kvs` (mode A) AND the plugin-dedicated tables (mode B). The CLI MUST require interactive confirmation unless `--yes` is passed.
|
|
190
190
|
- `sm db reset --hard` deletes the DB file entirely, destroying all plugin storage regardless of mode.
|
|
191
191
|
|
|
@@ -203,8 +203,8 @@ Post-v1.0 work: signed manifest, sandboxed worker-thread isolation, per-plugin D
|
|
|
203
203
|
|
|
204
204
|
## See also
|
|
205
205
|
|
|
206
|
-
- [`db-schema.md`](./db-schema.md)
|
|
207
|
-
- [`architecture.md`](./architecture.md)
|
|
206
|
+
- [`db-schema.md`](./db-schema.md), table catalog, migration analyzers, triple protection for mode B.
|
|
207
|
+
- [`architecture.md`](./architecture.md), extension contract analyzers and `ctx.store` injection via the kernel.
|
|
208
208
|
|
|
209
209
|
---
|
|
210
210
|
|
package/prompt-preamble.md
CHANGED
|
@@ -116,7 +116,7 @@ On `sm job submit`:
|
|
|
116
116
|
2. The kernel validates that the template does not interpolate user text outside of `<user-content>` blocks.
|
|
117
117
|
3. The kernel prepends the verbatim preamble text above.
|
|
118
118
|
4. The kernel renders the template by interpolating the node content, wrapping it in `<user-content>`.
|
|
119
|
-
5. The kernel stores the result in `state_job_contents` keyed by `contentHash` (content-addressed: multiple jobs that resolve to the same `contentHash` share one row). There is no canonical filesystem artifact
|
|
119
|
+
5. The kernel stores the result in `state_job_contents` keyed by `contentHash` (content-addressed: multiple jobs that resolve to the same `contentHash` share one row). There is no canonical filesystem artifact, `sm job preview` and `sm job claim --json` both read directly from this table. Subprocess runners that need a file (e.g., `claude -p` reading stdin from a path) materialize a temporary file from the DB row and remove it after spawn; the temp file is operationally ephemeral, not part of the contract.
|
|
120
120
|
6. The kernel computes `contentHash` over (among other things) the concatenation of preamble + template. A changed preamble (e.g., spec bump) MUST produce a different hash and therefore MUST NOT collide with prior jobs.
|
|
121
121
|
|
|
122
122
|
Implementations MUST NOT modify the preamble text at runtime (e.g., based on locale, model, or config). The text is universal and invariant.
|
|
@@ -127,9 +127,9 @@ Implementations MUST NOT modify the preamble text at runtime (e.g., based on loc
|
|
|
127
127
|
|
|
128
128
|
The preamble text is a **normative artifact** of the spec. Any change follows [`versioning.md`](./versioning.md):
|
|
129
129
|
|
|
130
|
-
- Editorial fixes to examples (none exist today, keep it that way)
|
|
131
|
-
- Tightening the instructions (e.g., adding a new refusal clause)
|
|
132
|
-
- Changing the shape the model must emit (`safety` structure)
|
|
130
|
+
- Editorial fixes to examples (none exist today, keep it that way), patch bump.
|
|
131
|
+
- Tightening the instructions (e.g., adding a new refusal clause), minor bump.
|
|
132
|
+
- Changing the shape the model must emit (`safety` structure), major bump, because it propagates to [`report-base.schema.json`](./schemas/report-base.schema.json).
|
|
133
133
|
|
|
134
134
|
Every spec release that modifies the preamble MUST record the rationale in [`CHANGELOG.md`](./CHANGELOG.md).
|
|
135
135
|
|
|
@@ -149,10 +149,10 @@ Defense-in-depth: the deterministic analyzer `injection-pattern` (shipped as a b
|
|
|
149
149
|
|
|
150
150
|
## See also
|
|
151
151
|
|
|
152
|
-
- [`job-lifecycle.md`](./job-lifecycle.md)
|
|
153
|
-
- [`architecture.md`](./architecture.md)
|
|
154
|
-
- [`interfaces/security-scanner.md`](./interfaces/security-scanner.md)
|
|
155
|
-
- [`conformance/`](./conformance/README.md)
|
|
152
|
+
- [`job-lifecycle.md`](./job-lifecycle.md), submit flow that renders job files with the preamble.
|
|
153
|
+
- [`architecture.md`](./architecture.md), kernel's role in applying the preamble.
|
|
154
|
+
- [`interfaces/security-scanner.md`](./interfaces/security-scanner.md), `SecurityReport` convention that extends `report-base`.
|
|
155
|
+
- [`conformance/`](./conformance/README.md), `preamble-bitwise-match` case (deferred to Step 10).
|
|
156
156
|
|
|
157
157
|
---
|
|
158
158
|
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/annotations.schema.json",
|
|
4
4
|
"title": "Annotations",
|
|
5
|
-
"description": "Catalog of conventional annotation fields skill-map ships out of the box, written into the `annotations:` block of a sidecar (`<basename>.sm`). Every field is OPTIONAL
|
|
5
|
+
"description": "Catalog of conventional annotation fields skill-map ships out of the box, written into the `annotations:` block of a sidecar (`<basename>.sm`). Every field is OPTIONAL, a sidecar with an empty `annotations: {}` is valid. Schema is `additionalProperties: true` so users / plugins can add custom keys without coordination; the built-in `unknown-field` analyzer emits a warning on unrecognized keys (typo guard). The curated catalog is the load-bearing 13 fields below, versioning + supersession (`version`, `stability`, `supersedes`, `supersededBy`, `requires`, `conflictsWith`, `related`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), docs (`docsUrl`). The activity timestamp lives in the reserved `audit:` block (`audit.lastBumpedAt`), not in `annotations:`. Plugins that want first-class custom keys with their own validation declare `annotationContributions` in their manifest (see Step 9.6.6).",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": true,
|
|
8
8
|
"properties": {
|
|
9
9
|
"version": {
|
|
10
10
|
"type": "integer",
|
|
11
11
|
"minimum": 1,
|
|
12
|
-
"description": "Monotonic counter. Bumped via the built-in `bump` Action when the underlying node changes meaningfully. Orthogonal to `stability
|
|
12
|
+
"description": "Monotonic counter. Bumped via the built-in `bump` Action when the underlying node changes meaningfully. Orthogonal to `stability`, `stability` carries the lifecycle stage; `version` is just a counter. There is no major: a change so big it would justify a major bump uses the convention `create a new node, supersede the old one` instead. Default: missing == unversioned."
|
|
13
13
|
},
|
|
14
14
|
"stability": {
|
|
15
15
|
"type": "string",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"tags": {
|
|
65
65
|
"type": "array",
|
|
66
66
|
"items": { "type": "string", "minLength": 1 },
|
|
67
|
-
"description": "**User-supplied** taxonomy tags. Skill-map's tag system is dual-source: this field carries the user's post-hoc tags (curator's view of the node from the `.sm` sidecar); `frontmatter.tags` carries the author's tags (intrinsic categories written in the `.md`). Both surfaces are first-class
|
|
67
|
+
"description": "**User-supplied** taxonomy tags. Skill-map's tag system is dual-source: this field carries the user's post-hoc tags (curator's view of the node from the `.sm` sidecar); `frontmatter.tags` carries the author's tags (intrinsic categories written in the `.md`). Both surfaces are first-class, `sm list --tag <name>` and UI faceted search match the union and the UI distinguishes them visually (`sm list --tag-source author|user` filters one source). Empty array and missing field are equivalent."
|
|
68
68
|
},
|
|
69
69
|
"docsUrl": {
|
|
70
70
|
"type": "string",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/api/rest-envelope.schema.json",
|
|
4
4
|
"title": "RestEnvelope",
|
|
5
|
-
"description": "Wrapper shape for REST responses under `/api/*` (Step 14.2). Five variants distinguished by the `kind` discriminator and which payload field is present (`items` for list kinds AND `'annotations.registered'`, `item` for single-resource kinds, `value` for `kind: 'config'` and `'sidecar.bumped'`). The `/api/scan` and `/api/health` responses are exempt
|
|
5
|
+
"description": "Wrapper shape for REST responses under `/api/*` (Step 14.2). Five variants distinguished by the `kind` discriminator and which payload field is present (`items` for list kinds AND `'annotations.registered'`, `item` for single-resource kinds, `value` for `kind: 'config'` and `'sidecar.bumped'`). The `/api/scan` and `/api/health` responses are exempt, they carry the underlying `ScanResult` / `IHealthResponse` shape directly. The `/api/graph` response is also exempt, it returns the formatter's native textual output (text/plain or text/markdown). Step 14.5.d adds the required `kindRegistry` field on every payload-bearing list / single / config variant so the UI can render Provider-declared kinds (label, color, icon) without hardcoding visuals; sentinel kinds (`health`, `scan`, `graph`) stay exempt because they don't carry an envelope payload. Step 9.6 closes the `'sidecar.bumped'` (R7) and `'annotations.registered'` (R7) gaps, both are payload-bearing but carry their own variant shapes (sidecar.bumped: `value` + `elapsedMs`, no `filters`/`counts`/`kindRegistry`; annotations.registered: `items` + `counts.total`, no `filters`/`kindRegistry`) because they project read-only kernel surfaces orthogonal to the kindRegistry. The change keeps `schemaVersion` at `'1'`, the BFF is greenfield (no released consumers depend on the prior shape), so a versioned migration buys nothing.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["schemaVersion", "kind"],
|
|
8
8
|
"properties": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"annotations.registered",
|
|
28
28
|
"contributions.registered"
|
|
29
29
|
],
|
|
30
|
-
"description": "Discriminator. List kinds (`nodes`, `links`, `issues`, `plugins`) carry `items` + `filters` + `counts` + `kindRegistry`. The `node` kind carries `item` + `kindRegistry`. The `config` kind carries `value` + `kindRegistry`. The `sidecar.bumped` kind (Step 9.6.5, BFF half) carries `value` + `elapsedMs` (no `filters` / `counts` / `kindRegistry
|
|
30
|
+
"description": "Discriminator. List kinds (`nodes`, `links`, `issues`, `plugins`) carry `items` + `filters` + `counts` + `kindRegistry`. The `node` kind carries `item` + `kindRegistry`. The `config` kind carries `value` + `kindRegistry`. The `sidecar.bumped` kind (Step 9.6.5, BFF half) carries `value` + `elapsedMs` (no `filters` / `counts` / `kindRegistry`, it's an action-result projection orthogonal to the kindRegistry surface). The `annotations.registered` kind (Step 9.6.6, BFF half) carries `items` + `counts.total` (no `filters` / `kindRegistry`, pure read-only catalog projection). The `health` / `scan` / `graph` values are reserved for documentation parity with the routes that DON'T use this envelope."
|
|
31
31
|
},
|
|
32
32
|
"items": {
|
|
33
33
|
"type": "array",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"returned": {
|
|
141
141
|
"type": "integer",
|
|
142
142
|
"minimum": 0,
|
|
143
|
-
"description": "Rows actually carried in `items` (≤ `limit`). Present on list-kind envelopes (`nodes`, `links`, `issues`, `plugins`); absent on `'annotations.registered'` (no pagination, no filters
|
|
143
|
+
"description": "Rows actually carried in `items` (≤ `limit`). Present on list-kind envelopes (`nodes`, `links`, `issues`, `plugins`); absent on `'annotations.registered'` (no pagination, no filters, the catalog ships in its entirety in every response)."
|
|
144
144
|
},
|
|
145
145
|
"page": {
|
|
146
146
|
"type": "object",
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
},
|
|
168
168
|
"oneOf": [
|
|
169
169
|
{
|
|
170
|
-
"description": "List envelope
|
|
170
|
+
"description": "List envelope, `items` payload + `filters` + `counts` (with `returned`) + `kindRegistry` + `contributionsRegistry`. Used by `/api/nodes`, `/api/links`, `/api/issues`, `/api/plugins`.",
|
|
171
171
|
"required": ["items", "counts", "filters", "kindRegistry", "contributionsRegistry"],
|
|
172
172
|
"properties": {
|
|
173
173
|
"kind": { "enum": ["nodes", "links", "issues", "plugins"] },
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
}
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
|
-
"description": "Single-resource envelope
|
|
185
|
+
"description": "Single-resource envelope, `item` payload + `kindRegistry` + `contributionsRegistry`, no `counts` / `filters`. Used by `/api/nodes/:pathB64`.",
|
|
186
186
|
"required": ["item", "kindRegistry", "contributionsRegistry"],
|
|
187
187
|
"properties": {
|
|
188
188
|
"kind": { "const": "node" }
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
}
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
|
-
"description": "Value envelope
|
|
199
|
+
"description": "Value envelope, `value` payload + `kindRegistry` + `contributionsRegistry`, no `counts` / `filters`. Used by `/api/config`.",
|
|
200
200
|
"required": ["value", "kindRegistry", "contributionsRegistry"],
|
|
201
201
|
"properties": {
|
|
202
202
|
"kind": { "const": "config" }
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
}
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
|
-
"description": "Action-result envelope
|
|
213
|
+
"description": "Action-result envelope, `value` + `elapsedMs` siblings, no `filters` / `counts` / `kindRegistry` / `contributionsRegistry`. Used by `POST /api/sidecar/bump` (Step 9.6.5, BFF half) where the response carries the bump report (`{ nodePath, version, status }`) plus the wall-clock duration. The registries are intentionally absent, the action result is orthogonal to both catalogs and the SPA already has them cached from a prior list call.",
|
|
214
214
|
"required": ["value", "elapsedMs"],
|
|
215
215
|
"properties": {
|
|
216
216
|
"kind": { "const": "sidecar.bumped" }
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
}
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
|
-
"description": "Annotation-catalog envelope
|
|
230
|
+
"description": "Annotation-catalog envelope, `items` + `counts.total` only, no `filters` / `kindRegistry` / `contributionsRegistry` / `returned`. Used by `GET /api/annotations/registered` (Step 9.6.6, BFF half). The catalog is small (typically 0–50 entries), ships in its entirety on every response, and does not paginate; `counts.total` doubles as `items.length`.",
|
|
231
231
|
"required": ["items", "counts"],
|
|
232
232
|
"properties": {
|
|
233
233
|
"kind": { "const": "annotations.registered" },
|
|
@@ -247,7 +247,7 @@
|
|
|
247
247
|
}
|
|
248
248
|
},
|
|
249
249
|
{
|
|
250
|
-
"description": "View-contributions-catalog envelope
|
|
250
|
+
"description": "View-contributions-catalog envelope, `items` + `counts.total` only, no `filters` / `kindRegistry` / `contributionsRegistry` / `returned`. Used by `GET /api/contributions/registered`. Mirror of `annotations.registered`. The catalog ships in entirety; `counts.total` doubles as `items.length`. Each item is an `IRegisteredViewContribution` shape: `{ pluginId, extensionId, contributionId, slot, label?, tooltip?, icon?, emptyText?, emitWhenEmpty? }`.",
|
|
251
251
|
"required": ["items", "counts"],
|
|
252
252
|
"properties": {
|
|
253
253
|
"kind": { "const": "contributions.registered" },
|
|
@@ -267,7 +267,7 @@
|
|
|
267
267
|
}
|
|
268
268
|
},
|
|
269
269
|
{
|
|
270
|
-
"description": "Sentinel kinds
|
|
270
|
+
"description": "Sentinel kinds, reserved for routes that do NOT carry an envelope payload at the wire level (`health`, `scan`, `graph`). They do not carry `kindRegistry` or `contributionsRegistry` either; clients that need either must call any payload-bearing endpoint at boot.",
|
|
271
271
|
"properties": {
|
|
272
272
|
"kind": { "enum": ["health", "scan", "graph"] }
|
|
273
273
|
},
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/conformance-result.schema.json",
|
|
4
|
+
"title": "ConformanceResult",
|
|
5
|
+
"description": "Machine-readable output of `sm conformance run --json`. Aggregates pass / fail totals across the selected scope set plus per-scope and per-case breakdowns. The `elapsedMs` top-level field is the command's own wall-clock (see `cli-contract.md` §Elapsed time).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["ok", "kind", "totals", "scopes", "elapsedMs"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"ok": {
|
|
11
|
+
"type": "boolean",
|
|
12
|
+
"const": true,
|
|
13
|
+
"description": "Always `true` on the happy path. Error envelopes use the shared `{ ok: false, error: { code, message } }` shape from `cli-contract.md` §Error envelope (unknown scope, missing binary) and do NOT carry this schema's other fields. A run that surfaces failing cases still returns `ok: true` (the verb succeeded; failures live under `scopes[].cases[].status === 'fail'` and gate the exit code)."
|
|
14
|
+
},
|
|
15
|
+
"kind": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"const": "conformance.result",
|
|
18
|
+
"description": "Discriminator pinning this envelope to the conformance-run verb."
|
|
19
|
+
},
|
|
20
|
+
"totals": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"required": ["scopes", "cases", "passCount", "failCount"],
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"description": "Grand totals across every selected scope.",
|
|
25
|
+
"properties": {
|
|
26
|
+
"scopes": {
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"minimum": 0,
|
|
29
|
+
"description": "Number of scopes the run iterated."
|
|
30
|
+
},
|
|
31
|
+
"cases": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"minimum": 0,
|
|
34
|
+
"description": "Total case count, sum of `scopes[].caseCount`."
|
|
35
|
+
},
|
|
36
|
+
"passCount": {
|
|
37
|
+
"type": "integer",
|
|
38
|
+
"minimum": 0,
|
|
39
|
+
"description": "Total cases with `status === 'pass'`."
|
|
40
|
+
},
|
|
41
|
+
"failCount": {
|
|
42
|
+
"type": "integer",
|
|
43
|
+
"minimum": 0,
|
|
44
|
+
"description": "Total cases with `status === 'fail'`. Equal to `cases - passCount`."
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"scopes": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"description": "Per-scope breakdown in the order the verb iterated. Empty when no scope was selected.",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"required": ["label", "passCount", "caseCount", "cases"],
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"properties": {
|
|
56
|
+
"label": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"minLength": 1,
|
|
59
|
+
"description": "Scope label as registered (`spec`, `provider:claude`, ...)."
|
|
60
|
+
},
|
|
61
|
+
"passCount": {
|
|
62
|
+
"type": "integer",
|
|
63
|
+
"minimum": 0,
|
|
64
|
+
"description": "Cases in this scope with `status === 'pass'`."
|
|
65
|
+
},
|
|
66
|
+
"caseCount": {
|
|
67
|
+
"type": "integer",
|
|
68
|
+
"minimum": 0,
|
|
69
|
+
"description": "Total case count under this scope."
|
|
70
|
+
},
|
|
71
|
+
"cases": {
|
|
72
|
+
"type": "array",
|
|
73
|
+
"description": "Per-case status, in the runner's iteration order.",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"required": ["id", "status", "failures"],
|
|
77
|
+
"additionalProperties": false,
|
|
78
|
+
"properties": {
|
|
79
|
+
"id": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"minLength": 1,
|
|
82
|
+
"description": "Case id (kebab-case stem matching the case filename, see `conformance-case.schema.json`)."
|
|
83
|
+
},
|
|
84
|
+
"status": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"enum": ["pass", "fail"],
|
|
87
|
+
"description": "Verdict for the case as a whole. A case passes iff every assertion passes."
|
|
88
|
+
},
|
|
89
|
+
"failures": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"description": "Failed assertions when `status === 'fail'`. Empty when `status === 'pass'`.",
|
|
92
|
+
"items": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"required": ["type", "reason"],
|
|
95
|
+
"additionalProperties": false,
|
|
96
|
+
"properties": {
|
|
97
|
+
"type": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Assertion discriminator (`exit-code`, `json-path`, `file-exists`, ...) per `conformance-case.schema.json#/$defs/Assertion`. Includes the runner-internal `runtime-error` pseudo-type for crashes during dispatch."
|
|
100
|
+
},
|
|
101
|
+
"reason": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Sanitised + length-capped explanation. Runner-internal types may splice subprocess stderr verbatim into this field, the CLI applies the same sanitisation as the human renderer (see `formatAssertionFailureDetail`)."
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"elapsedMs": {
|
|
115
|
+
"type": "integer",
|
|
116
|
+
"minimum": 0,
|
|
117
|
+
"description": "Command's own wall-clock duration in milliseconds (see `cli-contract.md` §Elapsed time)."
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/execution-record.schema.json",
|
|
4
4
|
"title": "ExecutionRecord",
|
|
5
|
-
"description": "A single row in the execution history (`state_executions`). One record per action invocation
|
|
5
|
+
"description": "A single row in the execution history (`state_executions`). One record per action invocation, regardless of whether the runner was CLI, Skill, or in-process.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["id", "kind", "extensionId", "extensionVersion", "status", "startedAt", "finishedAt"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"status": {
|
|
38
38
|
"type": "string",
|
|
39
39
|
"enum": ["completed", "failed", "cancelled"],
|
|
40
|
-
"description": "Terminal status. History never stores `queued` or `running
|
|
40
|
+
"description": "Terminal status. History never stores `queued` or `running`, those live in `state_jobs` until they reach a terminal state."
|
|
41
41
|
},
|
|
42
42
|
"failureReason": {
|
|
43
43
|
"type": ["string", "null"],
|
|
@@ -38,6 +38,15 @@
|
|
|
38
38
|
"type": "boolean",
|
|
39
39
|
"default": false,
|
|
40
40
|
"description": "If true, the analyzer reads its own config from `config_preferences` under the key `analyzers.<id>.<field>`. Implementations MAY surface these in `sm config`."
|
|
41
|
+
},
|
|
42
|
+
"recommendedActions": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"description": "Qualified `<pluginId>/<id>` action ids the analyzer recommends running to address its findings. Per-node Actions only (Actions are per-node by design, see `IActionPrecondition`). Distinct from `Action.precondition`: the precondition declares 'this action applies to nodes matching X'; `recommendedActions` declares 'when this analyzer fires, these are the relevant actions to resolve the finding'. The UI surfaces matching Actions in the node inspector under 'Recommended for issues' alongside the always-applicable list driven by `Action.precondition`. Project-level cleanup operations (e.g. orphan file prune, contribution relink) are CLI verbs, not Actions, and therefore are NOT linked through this field. Each entry MUST be the qualified id of a registered Action; the kernel logs `recommended-action-missing` when a referenced action is not loaded but keeps the analyzer registered.",
|
|
45
|
+
"items": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"pattern": "^[a-z0-9-]+/[a-z0-9-]+$"
|
|
48
|
+
},
|
|
49
|
+
"uniqueItems": true
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
52
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"id": {
|
|
10
10
|
"type": "string",
|
|
11
11
|
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
|
12
|
-
"description": "Kebab-case identifier unique within the extension's kind across the same plugin. The kernel registers every extension under the **qualified** id `<plugin-id>/<id>` (e.g. `core/annotations`, `core/slash`, `hello-world/greet`)
|
|
12
|
+
"description": "Kebab-case identifier unique within the extension's kind across the same plugin. The kernel registers every extension under the **qualified** id `<plugin-id>/<id>` (e.g. `core/annotations`, `core/slash`, `hello-world/greet`), see `architecture.md` §Extension kinds and `plugin-author-guide.md` §Qualified extension ids. Authors declare only the short id here; the qualifier is composed by the loader from the manifest's `id` (or, for built-ins, from the bundle declaration in `built-ins.ts`). The pattern is intentionally constrained to a single kebab-case segment without the `/` separator: the qualifier always lives in the plugin id, never in the extension id."
|
|
13
13
|
},
|
|
14
14
|
"kind": {
|
|
15
15
|
"type": "string",
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"ownership": {
|
|
54
54
|
"enum": ["exclusive", "shared"],
|
|
55
55
|
"default": "shared",
|
|
56
|
-
"description": "Conflict policy for this key. `shared` (default)
|
|
56
|
+
"description": "Conflict policy for this key. `shared` (default), the key is namespaced by default; multiple plugins MAY contribute to the same key, last-write-wins per the SidecarStore's deep-merge semantics. `exclusive`, only this plugin may write the key. REQUIRED when `location: 'root'` (a top-level reserved key cannot be silently shared between plugins)."
|
|
57
57
|
},
|
|
58
58
|
"location": {
|
|
59
59
|
"enum": ["namespaced", "root"],
|
|
60
60
|
"default": "namespaced",
|
|
61
|
-
"description": "Where the key lands inside the sidecar. `namespaced` (default)
|
|
61
|
+
"description": "Where the key lands inside the sidecar. `namespaced` (default), written under the plugin's `<plugin-id>:` block at the sidecar root. `root`, written as a top-level key alongside the reserved blocks (`for`, `annotations`, `settings`, `audit`); requires `ownership: 'exclusive'` and is treated as elevated trust (two plugins claiming the same root key with `exclusive` is a hard-fail at orchestrator init). See `plugin-author-guide.md` §Annotation contributions."
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
},
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"propertyNames": {
|
|
73
73
|
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
|
|
74
74
|
},
|
|
75
|
-
"description": "Plugin-contributed view contributions. Each entry declares one rendering surface in the UI by picking a `slot` name from the closed catalog at `view-slots.schema.json#/$defs/SlotName`. The kernel validates the manifest at load (`invalid-manifest` on unknown slot); the plugin emits per-node payloads via `ctx.emitContribution(<contributionId>, payload)` during scan; the runtime validates payloads against the slot's payload schema in `view-slots.schema.json#/$defs/payloads/<slot>`; off-shape payloads emit `extension.error` and drop silently (mirror of `emitLink` off-contract drop). The kernel exposes the runtime catalog via `kernel.getRegisteredViewContributions()`; the BFF surfaces it at `GET /api/contributions/registered`. The plugin author picks ONE slot
|
|
75
|
+
"description": "Plugin-contributed view contributions. Each entry declares one rendering surface in the UI by picking a `slot` name from the closed catalog at `view-slots.schema.json#/$defs/SlotName`. The kernel validates the manifest at load (`invalid-manifest` on unknown slot); the plugin emits per-node payloads via `ctx.emitContribution(<contributionId>, payload)` during scan; the runtime validates payloads against the slot's payload schema in `view-slots.schema.json#/$defs/payloads/<slot>`; off-shape payloads emit `extension.error` and drop silently (mirror of `emitLink` off-contract drop). The kernel exposes the runtime catalog via `kernel.getRegisteredViewContributions()`; the BFF surfaces it at `GET /api/contributions/registered`. The plugin author picks ONE slot, that is the only choice; the slot fixes both the renderer and the payload shape. See `plugin-author-guide.md` §View contributions for worked examples."
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/extractor.schema.json",
|
|
4
4
|
"title": "ExtensionExtractor",
|
|
5
|
-
"description": "Manifest shape for an `Extractor` extension. An extractor consumes a parsed node (frontmatter + body) and emits output through three context-supplied callbacks rather than returning a value: `ctx.emitLink(link)` writes to the kernel's `links` table (validated against `emitsLinkKinds` before persistence), `ctx.enrichNode(partial)` merges author-canonical properties into the kernel's enrichment layer (separate from the author-supplied frontmatter), and `ctx.store` persists into the plugin's own KV namespace or dedicated tables. The runtime method is `extract(ctx) → void`. Extractors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Analyzers. Extractors are deterministic-only: pure code, runs synchronously inside `sm scan`, same input → same output every run. LLM-driven enrichment of a node is an Action concern (queued as a job), not an Extractor concern. See `architecture.md` §Execution modes for the full contract. Renamed from `detector` in spec 0.8.x
|
|
5
|
+
"description": "Manifest shape for an `Extractor` extension. An extractor consumes a parsed node (frontmatter + body) and emits output through three context-supplied callbacks rather than returning a value: `ctx.emitLink(link)` writes to the kernel's `links` table (validated against `emitsLinkKinds` before persistence), `ctx.enrichNode(partial)` merges author-canonical properties into the kernel's enrichment layer (separate from the author-supplied frontmatter), and `ctx.store` persists into the plugin's own KV namespace or dedicated tables. The runtime method is `extract(ctx) → void`. Extractors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Analyzers. Extractors are deterministic-only: pure code, runs synchronously inside `sm scan`, same input → same output every run. LLM-driven enrichment of a node is an Action concern (queued as a job), not an Extractor concern. See `architecture.md` §Execution modes for the full contract. Renamed from `detector` in spec 0.8.x, the FindBugs/SpotBugs lineage of \"detector\" connoted bug finding, while this kind extracts signals (relations, enrichments, custom data); ENRE (Entity Relationship Extractor) is the closer precedent.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"kind": { "const": "extractor" },
|
|
14
14
|
"emitsLinkKinds": {
|
|
15
15
|
"type": "array",
|
|
16
|
-
"description": "Subset of `Link.kind` values this extractor is allowed to emit through `ctx.emitLink(...)`. Emitting an unlisted kind at runtime → kernel rejects the link and logs `extractor-kind-violation`. Empty array (`[]`) is the honest declaration for pure-contributions extractors that emit only via `ctx.emitContribution(...)` and never call `ctx.emitLink(...)
|
|
16
|
+
"description": "Subset of `Link.kind` values this extractor is allowed to emit through `ctx.emitLink(...)`. Emitting an unlisted kind at runtime → kernel rejects the link and logs `extractor-kind-violation`. Empty array (`[]`) is the honest declaration for pure-contributions extractors that emit only via `ctx.emitContribution(...)` and never call `ctx.emitLink(...)`, Phase 3 of the View contribution system relaxed `minItems: 1` to admit this case (`{ kind: 'extractor', emitsLinkKinds: [], viewContributions: { ... } }`).",
|
|
17
17
|
"items": {
|
|
18
18
|
"type": "string",
|
|
19
19
|
"enum": ["invokes", "references", "mentions", "supersedes"]
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"type": "string",
|
|
29
29
|
"enum": ["frontmatter", "body", "both"],
|
|
30
30
|
"default": "both",
|
|
31
|
-
"description": "Which part of the node this extractor consumes. The kernel passes only the declared scope to the extractor
|
|
31
|
+
"description": "Which part of the node this extractor consumes. The kernel passes only the declared scope to the extractor, a `frontmatter` extractor that tries to read `body` receives an empty string."
|
|
32
32
|
},
|
|
33
33
|
"applicableKinds": {
|
|
34
34
|
"type": "array",
|
|
35
35
|
"minItems": 1,
|
|
36
36
|
"items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
37
37
|
"uniqueItems": true,
|
|
38
|
-
"description": "Optional opt-in filter. If declared, the extractor runs only on nodes whose kind is in this list. Absent = applies to all kinds (default). No wildcards
|
|
38
|
+
"description": "Optional opt-in filter. If declared, the extractor runs only on nodes whose kind is in this list. Absent = applies to all kinds (default). No wildcards, the absence of the field already means \"every kind\". Empty array is invalid (`minItems: 1`). Unknown kinds (not declared by any installed Provider) load OK but emit a warning in `sm plugins doctor`, the Provider may arrive later. The kernel filters fail-fast: nodes whose kind is excluded never see `extract()`, so an extractor wastes zero CPU on inapplicable nodes."
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/formatter.schema.json",
|
|
4
4
|
"title": "ExtensionFormatter",
|
|
5
|
-
"description": "Manifest shape for a `Formatter` extension. A formatter serializes the graph (or a filtered subgraph) into a string in a declared format. Formatters are invoked by `sm graph --format <format>` and `sm export`. Formatters are deterministic-only
|
|
5
|
+
"description": "Manifest shape for a `Formatter` extension. A formatter serializes the graph (or a filtered subgraph) into a string in a declared format. Formatters are invoked by `sm graph --format <format>` and `sm export`. Formatters are deterministic-only, they sit at the graph-to-string boundary and their output MUST be byte-deterministic for the same input graph (the snapshot-test suite relies on this). The `mode` field MUST NOT appear in formatter manifests. Probabilistic narrators of the graph are a valid product but they live in jobs and emit Findings, not in formatters.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/hook.schema.json",
|
|
4
4
|
"title": "ExtensionHook",
|
|
5
|
-
"description": "Manifest shape for a `Hook` extension. Subscribes declaratively to a curated set of kernel lifecycle events. Dual-mode (deterministic / probabilistic). Hooks react to events; they cannot block or alter the main pipeline. Probabilistic hooks are deferred to the job subsystem and never run in-scan. The set of hookable triggers is intentionally small
|
|
5
|
+
"description": "Manifest shape for a `Hook` extension. Subscribes declaratively to a curated set of kernel lifecycle events. Dual-mode (deterministic / probabilistic). Hooks react to events; they cannot block or alter the main pipeline. Probabilistic hooks are deferred to the job subsystem and never run in-scan. The set of hookable triggers is intentionally small, ten events out of the full job-events catalog. Eight are pipeline-driven (emitted from inside `runScan`); two (`boot`, `shutdown`) are CLI-process-driven (emitted by the driving binary before / after the verb runs, fire-and-forget so `process.exit` is never blocked). Other events (per-node `scan.progress`, `model.delta`, `run.*`, internal job lifecycle) are deliberately not hookable: too verbose for a reactive surface, internal to the runner, or covered elsewhere. Declaring a trigger outside the hookable set yields `invalid-manifest` at load time. See `architecture.md` §Hook · curated trigger set for the per-trigger payload contracts.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["id", "kind", "version", "triggers"],
|
|
8
8
|
"unevaluatedProperties": false,
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"type": "string",
|
|
13
13
|
"enum": ["deterministic", "probabilistic"],
|
|
14
14
|
"default": "deterministic",
|
|
15
|
-
"description": "`deterministic`: the hook's `on(ctx)` runs in-process during the dispatch of the matching event, synchronously between the event's emission and the next pipeline step. `probabilistic`: the hook is enqueued as a job (handled by the job subsystem; lands at Step 10). A probabilistic hook is loaded but not dispatched in-scan
|
|
15
|
+
"description": "`deterministic`: the hook's `on(ctx)` runs in-process during the dispatch of the matching event, synchronously between the event's emission and the next pipeline step. `probabilistic`: the hook is enqueued as a job (handled by the job subsystem; lands at Step 10). A probabilistic hook is loaded but not dispatched in-scan, the kernel surfaces a stderr advisory and skips it until the job subsystem ships."
|
|
16
16
|
},
|
|
17
17
|
"triggers": {
|
|
18
18
|
"type": "array",
|
|
19
19
|
"minItems": 1,
|
|
20
20
|
"uniqueItems": true,
|
|
21
|
-
"description": "List of lifecycle events this hook subscribes to. Each entry MUST be one of the hookable triggers below. Declaring an unknown event (e.g. `scan.progress`, `model.delta`) is rejected at load time with `invalid-manifest
|
|
21
|
+
"description": "List of lifecycle events this hook subscribes to. Each entry MUST be one of the hookable triggers below. Declaring an unknown event (e.g. `scan.progress`, `model.delta`) is rejected at load time with `invalid-manifest`, the curated set is the contract.",
|
|
22
22
|
"items": {
|
|
23
23
|
"type": "string",
|
|
24
24
|
"enum": [
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/provider.schema.json",
|
|
4
4
|
"title": "ExtensionProvider",
|
|
5
|
-
"description": "Manifest shape for a `Provider` extension. A Provider declares its own universe: the platform it recognises (Claude Code, Codex, Gemini, Obsidian vault, generic MD), the catalog of node `kind`s it emits, and the per-kind frontmatter schema each kind follows. The catalog lives in the `kinds` map, keyed by kind name. Each map entry declares the relative path to the kind's frontmatter schema (resolved against the Provider's directory) and the qualified `defaultRefreshAction` id the UI's probabilistic-refresh surface dispatches for that kind. Spec only ships `frontmatter/base.schema.json` (universal); per-kind schemas live with their owning Provider so that adding a new platform is purely additive
|
|
5
|
+
"description": "Manifest shape for a `Provider` extension. A Provider declares its own universe: the platform it recognises (Claude Code, Codex, Gemini, Obsidian vault, generic MD), the catalog of node `kind`s it emits, and the per-kind frontmatter schema each kind follows. The catalog lives in the `kinds` map, keyed by kind name. Each map entry declares the relative path to the kind's frontmatter schema (resolved against the Provider's directory) and the qualified `defaultRefreshAction` id the UI's probabilistic-refresh surface dispatches for that kind. Spec only ships `frontmatter/base.schema.json` (universal); per-kind schemas live with their owning Provider so that adding a new platform is purely additive, no spec bump needed to introduce kinds. Exactly zero or one Provider MUST match any given file; multiple matches → the kernel emits an issue `provider-ambiguous` and the file is left unclassified. Providers are deterministic-only, they sit at the filesystem boundary and run during boot; probabilistic classification would make boot slow, costly, and non-reproducible. The `mode` field MUST NOT appear in Provider manifests. If you need LLM-assisted classification, write a probabilistic Action that runs as a queued job and writes back through the enrichment layer; Extractors are deterministic-only and Providers stay on the deterministic boot path. Distinct from the **hexagonal-architecture** 'adapter' (`RunnerPort.adapter`, `StoragePort.adapter`, etc.), which is an internal driven-adapter implementing a port, Providers live in the extension surface, hexagonal adapters live in `src/kernel/adapters/`. Stability: stable as of spec v1.0.0 except where noted.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -13,19 +13,19 @@
|
|
|
13
13
|
"kind": { "const": "provider" },
|
|
14
14
|
"roots": {
|
|
15
15
|
"type": "array",
|
|
16
|
-
"description": "Path globs (relative to scope root) that this Provider SHOULD be consulted for. Advisory
|
|
16
|
+
"description": "Path globs (relative to scope root) that this Provider SHOULD be consulted for. Advisory, the kernel walks all roots and consults every Provider regardless, but this field lets `sm doctor` warn when no file matched a specific Provider (i.e. the Provider was loaded for a platform that isn't in this scope).",
|
|
17
17
|
"items": { "type": "string" }
|
|
18
18
|
},
|
|
19
19
|
"read": {
|
|
20
20
|
"type": "object",
|
|
21
21
|
"required": ["extensions", "parser"],
|
|
22
22
|
"additionalProperties": false,
|
|
23
|
-
"description": "Declarative file-discovery config consumed by the kernel walker. When present, the kernel walks every root, includes files whose extension matches `extensions`, parses each with the parser registered as `parser`, and yields raw nodes the orchestrator consumes. When absent, the kernel applies the default `{ extensions: ['.md'], parser: 'frontmatter-yaml' }` so the most common Provider shape needs no configuration. When a Provider also declares the runtime `walk()` method (TypeScript-only
|
|
23
|
+
"description": "Declarative file-discovery config consumed by the kernel walker. When present, the kernel walks every root, includes files whose extension matches `extensions`, parses each with the parser registered as `parser`, and yields raw nodes the orchestrator consumes. When absent, the kernel applies the default `{ extensions: ['.md'], parser: 'frontmatter-yaml' }` so the most common Provider shape needs no configuration. When a Provider also declares the runtime `walk()` method (TypeScript-only, never appears in this manifest), `walk()` wins and `read` is ignored: the runtime field is the escape hatch for Providers with non-standard discovery requirements. Built-in parsers ship with the kernel (`frontmatter-yaml`, `plain`); the set is closed by design and user plugins cannot register their own.",
|
|
24
24
|
"properties": {
|
|
25
25
|
"extensions": {
|
|
26
26
|
"type": "array",
|
|
27
27
|
"minItems": 1,
|
|
28
|
-
"description": "File extensions the walker yields. Strings include the leading dot. Lowercase-only by convention
|
|
28
|
+
"description": "File extensions the walker yields. Strings include the leading dot. Lowercase-only by convention, match is case-sensitive and Providers MUST list every casing they want recognised.",
|
|
29
29
|
"items": {
|
|
30
30
|
"type": "string",
|
|
31
31
|
"pattern": "^\\.[a-z0-9]+$"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"parser": {
|
|
35
35
|
"type": "string",
|
|
36
36
|
"minLength": 1,
|
|
37
|
-
"description": "Identifier of a parser registered in the kernel-internal registry. Built-ins: `frontmatter-yaml` (markdown with `--- … ---` YAML frontmatter, prototype-pollution-safe, `js-yaml` JSON_SCHEMA-pinned), `plain` (entire body, empty frontmatter
|
|
37
|
+
"description": "Identifier of a parser registered in the kernel-internal registry. Built-ins: `frontmatter-yaml` (markdown with `--- … ---` YAML frontmatter, prototype-pollution-safe, `js-yaml` JSON_SCHEMA-pinned), `plain` (entire body, empty frontmatter, for files carrying no frontmatter convention; the Provider derives `name` from the path inside `classify()`). Unknown ids surface as `UnknownParserError` from the walker; the orchestrator translates the error into a Provider issue with status `invalid-manifest`."
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
},
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"tags": {
|
|
21
21
|
"type": "array",
|
|
22
22
|
"items": { "type": "string", "minLength": 1 },
|
|
23
|
-
"description": "**Author-supplied** taxonomy tags written in the markdown frontmatter
|
|
23
|
+
"description": "**Author-supplied** taxonomy tags written in the markdown frontmatter, what the file's author considers the node's intrinsic categories. Skill-map's tag system is **dual-source**: this field carries author tags; `sidecar.annotations.tags` carries user tags (post-hoc, written by whoever curates the project from their `.sm` sidecar). Both surfaces are first-class, searches and listings (`sm list --tag`, UI faceted search) match the union and the UI distinguishes them visually so the attribution stays explicit. Empty array and missing field are equivalent."
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|