@skill-map/spec 0.17.0 → 0.19.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +672 -0
  2. package/README.md +1 -1
  3. package/architecture.md +281 -16
  4. package/cli-contract.md +122 -6
  5. package/conformance/cases/orphan-markdown-fallback.json +22 -0
  6. package/conformance/cases/plugin-missing-ui-rejected.json +4 -1
  7. package/conformance/cases/sidecar-end-to-end.json +25 -0
  8. package/conformance/coverage.md +9 -3
  9. package/conformance/fixtures/orphan-markdown/.claude/agents/reviewer.md +6 -0
  10. package/conformance/fixtures/orphan-markdown/ARCHITECTURE.md +10 -0
  11. package/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js +6 -6
  12. package/conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm +12 -0
  13. package/conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.md +8 -0
  14. package/conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm +20 -0
  15. package/conformance/fixtures/sidecar-example/agent-example.md +17 -0
  16. package/conformance/fixtures/sidecar-example/agent-example.sm +53 -0
  17. package/db-schema.md +73 -15
  18. package/index.json +42 -19
  19. package/package.json +1 -1
  20. package/plugin-author-guide.md +426 -27
  21. package/schemas/annotations.schema.json +75 -0
  22. package/schemas/api/rest-envelope.schema.json +159 -46
  23. package/schemas/bump-report.schema.json +29 -0
  24. package/schemas/extensions/base.schema.json +36 -1
  25. package/schemas/extensions/extractor.schema.json +3 -10
  26. package/schemas/extensions/provider.schema.json +23 -1
  27. package/schemas/frontmatter/base.schema.json +6 -1
  28. package/schemas/input-types.schema.json +260 -0
  29. package/schemas/node.schema.json +36 -23
  30. package/schemas/plugins-registry.schema.json +14 -2
  31. package/schemas/project-config.schema.json +11 -0
  32. package/schemas/report-base-deterministic.schema.json +15 -0
  33. package/schemas/sidecar.schema.json +96 -0
  34. package/schemas/summaries/{note.schema.json → markdown.schema.json} +5 -5
  35. package/schemas/view-contracts.schema.json +298 -0
@@ -0,0 +1,260 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://skill-map.dev/spec/v0/input-types.schema.json",
4
+ "title": "InputTypes",
5
+ "description": "Closed catalog of input-types for plugin settings. The plugin author declares each user-configurable setting in the manifest's `settings` map by picking an `input-type` from this catalog; the kernel knows the schema for each type, the UI ships a generated form per type, and the CLI's `sm plugins config <id>` command exposes the same surface. Plugin authors NEVER write JSON Schema for settings — they pick a type by name and supply per-type parameters (label, default, min/max, options for enums, etc.). Closed catalog by design: every new input-type requires spec + UI form + CLI prompter + tests. Versioned via the manifest field `catalogCompat` (semver against the catalog as a whole). For the rationale and open issues, see ROADMAP.md §UI contribution system.",
6
+ "type": "object",
7
+ "$defs": {
8
+ "InputTypeName": {
9
+ "type": "string",
10
+ "enum": [
11
+ "string-list",
12
+ "single-string",
13
+ "boolean-flag",
14
+ "integer",
15
+ "enum-pick",
16
+ "enum-multipick",
17
+ "path-glob",
18
+ "regex",
19
+ "secret",
20
+ "key-value-list"
21
+ ],
22
+ "description": "Closed enum of input-type identifiers. Adding an entry requires the full spec/UI/CLI/tests round-trip. Removing or renaming an entry is a catalog-major-bump and triggers `sm plugins upgrade` migration."
23
+ },
24
+ "ISettingDeclaration": {
25
+ "description": "Manifest-side declaration of a single setting, keyed in `IPluginManifest.settings[<settingId>]`. Discriminated by `type`; per-type parameters live in the `oneOf` branches below. The plugin author NEVER writes JSON Schema — `type` is a name from `InputTypeName`, the kernel validates the user-supplied value against the per-type value schema.",
26
+ "oneOf": [
27
+ { "$ref": "#/$defs/Setting_StringList" },
28
+ { "$ref": "#/$defs/Setting_SingleString" },
29
+ { "$ref": "#/$defs/Setting_BooleanFlag" },
30
+ { "$ref": "#/$defs/Setting_Integer" },
31
+ { "$ref": "#/$defs/Setting_EnumPick" },
32
+ { "$ref": "#/$defs/Setting_EnumMultipick" },
33
+ { "$ref": "#/$defs/Setting_PathGlob" },
34
+ { "$ref": "#/$defs/Setting_Regex" },
35
+ { "$ref": "#/$defs/Setting_Secret" },
36
+ { "$ref": "#/$defs/Setting_KeyValueList" }
37
+ ]
38
+ },
39
+ "_Common": {
40
+ "type": "object",
41
+ "required": ["type", "label"],
42
+ "properties": {
43
+ "label": {
44
+ "type": "string",
45
+ "minLength": 1,
46
+ "maxLength": 64,
47
+ "description": "Short human-readable label shown above the form control. English-only per AGENTS.md."
48
+ },
49
+ "description": {
50
+ "type": "string",
51
+ "maxLength": 256,
52
+ "description": "Optional helper text shown below the control. English-only."
53
+ }
54
+ }
55
+ },
56
+ "Setting_StringList": {
57
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "required": ["type", "label"],
61
+ "properties": {
62
+ "type": { "const": "string-list" },
63
+ "label": true,
64
+ "description": true,
65
+ "default": {
66
+ "type": "array",
67
+ "items": { "type": "string" }
68
+ },
69
+ "min": { "type": "integer", "minimum": 0, "description": "Minimum item count required to validate." },
70
+ "max": { "type": "integer", "minimum": 1, "description": "Maximum item count permitted." },
71
+ "itemMaxLength": { "type": "integer", "minimum": 1, "default": 256 }
72
+ },
73
+ "description": "Array of free-form strings. Renders as a tag input. Use for keyword lists, ignore patterns, allow-lists. Value type at runtime: `string[]`."
74
+ },
75
+ "Setting_SingleString": {
76
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "required": ["type", "label"],
80
+ "properties": {
81
+ "type": { "const": "single-string" },
82
+ "label": true,
83
+ "description": true,
84
+ "default": { "type": "string" },
85
+ "minLength": { "type": "integer", "minimum": 0 },
86
+ "maxLength": { "type": "integer", "minimum": 1 },
87
+ "pattern": {
88
+ "type": "string",
89
+ "description": "Optional ECMAScript regex (no flags) the value must match. Validated at form submit AND at extractor invocation."
90
+ }
91
+ },
92
+ "description": "Single text input. Use for short identifiers, URLs, names. Value type at runtime: `string`."
93
+ },
94
+ "Setting_BooleanFlag": {
95
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
96
+ "type": "object",
97
+ "additionalProperties": false,
98
+ "required": ["type", "label"],
99
+ "properties": {
100
+ "type": { "const": "boolean-flag" },
101
+ "label": true,
102
+ "description": true,
103
+ "default": { "type": "boolean", "default": false }
104
+ },
105
+ "description": "On/off toggle. Renders as PrimeNG `<p-toggleswitch>`. Value type at runtime: `boolean`."
106
+ },
107
+ "Setting_Integer": {
108
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
109
+ "type": "object",
110
+ "additionalProperties": false,
111
+ "required": ["type", "label"],
112
+ "properties": {
113
+ "type": { "const": "integer" },
114
+ "label": true,
115
+ "description": true,
116
+ "default": { "type": "integer" },
117
+ "min": { "type": "integer" },
118
+ "max": { "type": "integer" },
119
+ "step": { "type": "integer", "minimum": 1, "default": 1 }
120
+ },
121
+ "description": "Integer input with optional bounds. Renders as PrimeNG `<p-inputnumber>` with spinner. Value type at runtime: `number` (always integer)."
122
+ },
123
+ "Setting_EnumPick": {
124
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
125
+ "type": "object",
126
+ "additionalProperties": false,
127
+ "required": ["type", "label", "options"],
128
+ "properties": {
129
+ "type": { "const": "enum-pick" },
130
+ "label": true,
131
+ "description": true,
132
+ "options": {
133
+ "type": "array",
134
+ "minItems": 2,
135
+ "items": {
136
+ "type": "object",
137
+ "additionalProperties": false,
138
+ "required": ["value", "label"],
139
+ "properties": {
140
+ "value": { "type": "string", "minLength": 1, "maxLength": 64 },
141
+ "label": { "type": "string", "minLength": 1, "maxLength": 64 }
142
+ }
143
+ }
144
+ },
145
+ "default": { "type": "string" }
146
+ },
147
+ "description": "Pick one from a closed set. Renders as PrimeNG `<p-select>` (≤ 7 options) or `<p-radiobutton>` group (≤ 4 options). Value type at runtime: `string` (the picked option's `value`)."
148
+ },
149
+ "Setting_EnumMultipick": {
150
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
151
+ "type": "object",
152
+ "additionalProperties": false,
153
+ "required": ["type", "label", "options"],
154
+ "properties": {
155
+ "type": { "const": "enum-multipick" },
156
+ "label": true,
157
+ "description": true,
158
+ "options": {
159
+ "type": "array",
160
+ "minItems": 2,
161
+ "items": {
162
+ "type": "object",
163
+ "additionalProperties": false,
164
+ "required": ["value", "label"],
165
+ "properties": {
166
+ "value": { "type": "string", "minLength": 1, "maxLength": 64 },
167
+ "label": { "type": "string", "minLength": 1, "maxLength": 64 }
168
+ }
169
+ }
170
+ },
171
+ "default": { "type": "array", "items": { "type": "string" } },
172
+ "min": { "type": "integer", "minimum": 0 },
173
+ "max": { "type": "integer", "minimum": 1 }
174
+ },
175
+ "description": "Pick zero or more from a closed set. Renders as PrimeNG `<p-multiselect>` or checkbox group. Value type at runtime: `string[]`."
176
+ },
177
+ "Setting_PathGlob": {
178
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
179
+ "type": "object",
180
+ "additionalProperties": false,
181
+ "required": ["type", "label"],
182
+ "properties": {
183
+ "type": { "const": "path-glob" },
184
+ "label": true,
185
+ "description": true,
186
+ "default": { "type": "string" },
187
+ "multiple": {
188
+ "type": "boolean",
189
+ "default": false,
190
+ "description": "When true, accepts `string[]` of glob patterns; when false (default), single `string`."
191
+ }
192
+ },
193
+ "description": "Glob pattern (POSIX-style, `**` / `*` / `?`). Validated against the project's installed glob library at form submit. Value type at runtime: `string` (when `multiple: false`) or `string[]`."
194
+ },
195
+ "Setting_Regex": {
196
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
197
+ "type": "object",
198
+ "additionalProperties": false,
199
+ "required": ["type", "label"],
200
+ "properties": {
201
+ "type": { "const": "regex" },
202
+ "label": true,
203
+ "description": true,
204
+ "default": { "type": "string" },
205
+ "flags": {
206
+ "type": "string",
207
+ "pattern": "^[gimsuy]*$",
208
+ "default": "",
209
+ "description": "ECMAScript regex flags allowed. Subset: `g`, `i`, `m`, `s`, `u`, `y`. Author chooses which flags the user-supplied pattern compiles with — the user supplies only the body."
210
+ }
211
+ },
212
+ "description": "ECMAScript regex pattern (the body, no `/` delimiters). Validated by attempting `new RegExp(value, flags)` at form submit and at extractor invocation. Compilation failure → form error / `invalid-settings` at runtime. Value type at runtime: `string`."
213
+ },
214
+ "Setting_Secret": {
215
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
216
+ "type": "object",
217
+ "additionalProperties": false,
218
+ "required": ["type", "label"],
219
+ "properties": {
220
+ "type": { "const": "secret" },
221
+ "label": true,
222
+ "description": true,
223
+ "envVar": {
224
+ "type": "string",
225
+ "pattern": "^[A-Z][A-Z0-9_]*$",
226
+ "description": "Optional env var name the kernel checks first; if set in the process environment, that value wins over any stored value (lets CI inject without writing to disk). The user-supplied value is stored encrypted at rest."
227
+ }
228
+ },
229
+ "description": "Sensitive string (token, password, API key). Renders as `<input type=\"password\">` with reveal toggle. Stored encrypted at rest (kernel-managed key in `state_secrets` table). Logged as `<redacted>` in CLI output. Value type at runtime: `string`. Triggers an `audit.secret-read` event on every read."
230
+ },
231
+ "Setting_KeyValueList": {
232
+ "allOf": [{ "$ref": "#/$defs/_Common" }],
233
+ "type": "object",
234
+ "additionalProperties": false,
235
+ "required": ["type", "label"],
236
+ "properties": {
237
+ "type": { "const": "key-value-list" },
238
+ "label": true,
239
+ "description": true,
240
+ "keyLabel": { "type": "string", "minLength": 1, "maxLength": 32, "default": "Key" },
241
+ "valueLabel": { "type": "string", "minLength": 1, "maxLength": 32, "default": "Value" },
242
+ "default": {
243
+ "type": "array",
244
+ "items": {
245
+ "type": "object",
246
+ "additionalProperties": false,
247
+ "required": ["key", "value"],
248
+ "properties": {
249
+ "key": { "type": "string" },
250
+ "value": { "type": "string" }
251
+ }
252
+ }
253
+ },
254
+ "min": { "type": "integer", "minimum": 0 },
255
+ "max": { "type": "integer", "minimum": 1 }
256
+ },
257
+ "description": "Editable mapping of strings to strings. Renders as a small editable table. Use for custom translations, alias maps, header overrides. Value type at runtime: `Array<{ key: string, value: string }>`."
258
+ }
259
+ }
260
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://skill-map.dev/spec/v0/node.schema.json",
4
4
  "title": "Node",
5
- "description": "A single markdown file in the graph. Identified by its relative path from the scope root. The `kind` is whatever the classifying Provider declares — open by design; the **built-in Claude Provider** emits `skill` / `agent` / `command` / `hook` / `note` today, but external Providers (Cursor, Obsidian, …) MAY emit their own.",
5
+ "description": "A single markdown file in the graph. Identified by its relative path from the scope root. The `kind` is whatever the classifying Provider declares — open by design; the **built-in Claude Provider** emits `skill` / `agent` / `command` / `markdown` today, but external Providers (Cursor, Obsidian, …) MAY emit their own. Format-named kinds (`markdown`, future `toml`, future `json`) are reserved for the generic fallback only — when a file matches a specific role (agent / command / skill) that classification prevails over format naming.",
6
6
  "type": "object",
7
7
  "required": ["path", "kind", "provider", "bodyHash", "frontmatterHash", "bytes", "linksOutCount", "linksInCount", "externalRefsCount"],
8
8
  "additionalProperties": false,
@@ -14,33 +14,12 @@
14
14
  "kind": {
15
15
  "type": "string",
16
16
  "minLength": 1,
17
- "description": "Category assigned by the Provider. Open-by-design — any non-empty string an enabled Provider declares is valid (built-in Claude Provider catalog: `skill` / `agent` / `command` / `hook` / `note`; external Providers MAY declare their own). Per-kind frontmatter schemas live with the Provider that emits the kind. Stability: stable."
17
+ "description": "Category assigned by the Provider. Open-by-design — any non-empty string an enabled Provider declares is valid (built-in Claude Provider catalog: `skill` / `agent` / `command` / `markdown`; external Providers MAY declare their own). Per-kind frontmatter schemas live with the Provider that emits the kind. Stability: stable."
18
18
  },
19
19
  "provider": {
20
20
  "type": "string",
21
21
  "description": "Identifier of the Provider extension that classified this node (e.g. `claude`)."
22
22
  },
23
- "title": {
24
- "type": ["string", "null"],
25
- "description": "Human-readable title. Sourced from frontmatter `name` or falls back to filename. Null if neither is usable."
26
- },
27
- "description": {
28
- "type": ["string", "null"],
29
- "description": "Short description from frontmatter `description`. Null if absent."
30
- },
31
- "stability": {
32
- "type": ["string", "null"],
33
- "enum": ["experimental", "stable", "deprecated", null],
34
- "description": "Denormalized from `metadata.stability` for fast queries."
35
- },
36
- "version": {
37
- "type": ["string", "null"],
38
- "description": "Denormalized from `metadata.version` for fast queries. Must be a valid semver if present."
39
- },
40
- "author": {
41
- "type": ["string", "null"],
42
- "description": "Denormalized from `metadata.author` for fast queries."
43
- },
44
23
  "frontmatter": {
45
24
  "type": "object",
46
25
  "description": "Full parsed frontmatter. See `frontmatter/base.schema.json` and `frontmatter/<kind>.schema.json`.",
@@ -78,6 +57,14 @@
78
57
  "type": "integer",
79
58
  "minimum": 0,
80
59
  "description": "http/https URLs in the body after normalization and exact-match dedup."
60
+ },
61
+ "sidecar": {
62
+ "$ref": "#/$defs/sidecarOverlay",
63
+ "description": "Step 9.6.2 — co-located `.sm` sidecar overlay. Carries presence flag, drift status (null when no sidecar), and the parsed `annotations:` block (null when absent or empty). The kernel re-derives `status` on every scan from the live hashes; clients should treat it as authoritative for the snapshot but never persist it across scans."
64
+ },
65
+ "isFavorite": {
66
+ "type": "boolean",
67
+ "description": "Per-node favorite flag set by the local user from the UI. Sourced from `state_node_favorites` (zone `state_`, persistent across scans). Decorated by the BFF on every `/api/nodes` response via in-memory Set lookup against the favorites table — no SQL JOIN against `scan_nodes`. Absent on emissions that don't carry per-user state (e.g. `sm export --json`); consumers that don't recognise the field MUST ignore it."
81
68
  }
82
69
  },
83
70
  "$defs": {
@@ -90,6 +77,32 @@
90
77
  "body": { "type": "integer", "minimum": 0 },
91
78
  "total": { "type": "integer", "minimum": 0 }
92
79
  }
80
+ },
81
+ "sidecarOverlay": {
82
+ "type": "object",
83
+ "required": ["present"],
84
+ "additionalProperties": false,
85
+ "properties": {
86
+ "present": {
87
+ "type": "boolean",
88
+ "description": "True when a `<basename>.sm` file accompanies the node on disk; false otherwise."
89
+ },
90
+ "status": {
91
+ "type": ["string", "null"],
92
+ "enum": ["fresh", "stale-body", "stale-frontmatter", "stale-both", null],
93
+ "description": "Drift status. NULL when no sidecar is present, or when the sidecar exists but failed to parse / validate (the row still records `present: true` so clients can distinguish absent from broken)."
94
+ },
95
+ "annotations": {
96
+ "type": ["object", "null"],
97
+ "additionalProperties": true,
98
+ "description": "Parsed `annotations:` block from the sidecar. NULL when no sidecar is present, when the block is absent / empty, or when the sidecar failed to parse / validate."
99
+ },
100
+ "root": {
101
+ "type": ["object", "null"],
102
+ "additionalProperties": true,
103
+ "description": "Parsed YAML root of the matching `.sm` sidecar. Mirrors the shape of `sidecar.schema.json` (top-level reserved blocks `identity` / `annotations` / `settings` / `audit` plus opt-in `<plugin-id>:` namespaces). Surfaced for the UI inspector's audit panel, plugin-contributions panel, and debug panel; NULL when the sidecar is absent or failed to parse. Note the duplication with `annotations` at this overlay level — existing consumers read from `annotations`, new consumers read structured sub-fields off `root` (`root.identity.*`, `root.audit.*`, etc.). The duplication is intentional and documented; do NOT remove the top-level `annotations` field."
104
+ }
105
+ }
93
106
  }
94
107
  }
95
108
  }
@@ -27,6 +27,10 @@
27
27
  "type": "string",
28
28
  "description": "Semver range this plugin is compatible with (e.g. `^1.0.0`, `>=0.3.0 <0.4.0`). Checked via `semver.satisfies(specVersion, this)` at load time."
29
29
  },
30
+ "catalogCompat": {
31
+ "type": "string",
32
+ "description": "Optional semver range against the kernel's view-contracts + input-types catalog version (e.g. `^1.0.0`). Independent from `specCompat` because the catalog evolves on its own cadence (new contracts ship as minor bumps; rename/remove ships as catalog-major bumps that trigger `sm plugins upgrade`). Absent = the plugin declares no view contributions or settings AND opts out of catalog-version checking; `sm plugins doctor` will warn if such a plugin actually emits via `viewContributions` or `settings`. Mismatch surfaces as `incompatible-catalog` plugin status."
33
+ },
30
34
  "description": {
31
35
  "type": "string"
32
36
  },
@@ -73,6 +77,14 @@
73
77
  }
74
78
  ]
75
79
  },
80
+ "settings": {
81
+ "type": "object",
82
+ "additionalProperties": { "$ref": "input-types.schema.json#/$defs/ISettingDeclaration" },
83
+ "propertyNames": {
84
+ "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
85
+ },
86
+ "description": "Plugin user-configurable settings. Each entry picks an `input-type` from the closed catalog at `input-types.schema.json#/$defs/InputTypeName`. The plugin author NEVER writes JSON Schema for settings — they pick by `type` name and provide per-type parameters (label, default, min/max, options for enums, etc.). The kernel exposes the resolved settings to extractors via `ctx.settings.<settingId>` (or via the runtime as `IPluginSettings`); the UI generates a form per declaration; the CLI's `sm plugins config <id>` exposes the same surface. Settings are read once at extractor invocation; changing a setting requires `sm scan` to re-emit (per ROADMAP.md decision D4)."
87
+ },
76
88
  "author": { "type": "string" },
77
89
  "license": { "type": "string", "description": "SPDX identifier." },
78
90
  "homepage": { "type": "string", "format": "uri" },
@@ -101,8 +113,8 @@
101
113
  "manifest": { "$ref": "#/$defs/PluginManifest" },
102
114
  "status": {
103
115
  "type": "string",
104
- "enum": ["enabled", "disabled", "incompatible-spec", "invalid-manifest", "load-error", "id-collision"],
105
- "description": "Resolved state after discovery. `disabled` = user-disabled via config; `id-collision` = two plugins (any combination of project / global / --plugin-dir) declared the same `id`, both blocked, no precedence; others = automatic."
116
+ "enum": ["enabled", "disabled", "incompatible-spec", "incompatible-catalog", "invalid-manifest", "load-error", "id-collision"],
117
+ "description": "Resolved state after discovery. `disabled` = user-disabled via config; `id-collision` = two plugins (any combination of project / global / --plugin-dir) declared the same `id`, both blocked, no precedence; `incompatible-catalog` = manifest's `catalogCompat` does not satisfy the kernel's catalog version (resolved via `sm plugins upgrade`); others = automatic."
106
118
  },
107
119
  "statusReason": {
108
120
  "type": ["string", "null"],
@@ -129,6 +129,17 @@
129
129
  "properties": {
130
130
  "locale": { "type": "string", "description": "BCP-47 tag. Default `en`." }
131
131
  }
132
+ },
133
+ "updateCheck": {
134
+ "type": "object",
135
+ "additionalProperties": false,
136
+ "description": "Controls the once-per-day notification when a newer @skill-map/cli release is published on npm. Disabled in CI, when SM_NO_UPDATE_CHECK=1, when stderr is not a TTY, or when the project DB is missing.",
137
+ "properties": {
138
+ "enabled": {
139
+ "type": "boolean",
140
+ "description": "Default true. Set to false to disable both the npm registry probe and the CLI / UI banner."
141
+ }
142
+ }
132
143
  }
133
144
  }
134
145
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://skill-map.dev/spec/v0/report-base-deterministic.schema.json",
4
+ "title": "ReportBaseDeterministic",
5
+ "description": "Universal base for deterministic Action reports. Every deterministic Action's report MUST extend this base via `allOf` + `$ref`. Symmetric with `report-base.schema.json` (the probabilistic / LLM base, which carries `confidence` + `safety`); deterministic vs probabilistic is the orthogonal axis declared by the Action manifest's `mode` field. Fields: `ok` (boolean — did the Action complete its logical work?), plus action-specific keys via `additionalProperties: true`. Action-specific shapes (e.g. bump's `version` / `noop` / `reason`) ride on the open extension.",
6
+ "type": "object",
7
+ "required": ["ok"],
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "ok": {
11
+ "type": "boolean",
12
+ "description": "True when the Action completed its logical work; false when it refused / errored. Silent no-ops (e.g. `force: true` against an already-fresh node) still report `ok: true` with an action-specific marker (e.g. bump's `noop: true`)."
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://skill-map.dev/spec/v0/sidecar.schema.json",
4
+ "title": "Sidecar",
5
+ "description": "Root shape of a co-located YAML sidecar (`<basename>.sm` next to `<basename>.md`). The `.sm` file IS the annotations file — every key under it is, conceptually, an annotation on the node. The YAML root organizes those annotations into structural blocks: `identity` (anchor + drift-detection hashes), `annotations` (the curated catalog of conventional fields), `audit` (timestamps), `settings` (reserved), and arbitrary `<plugin-id>:` namespaces for plugin-contributed data. Vendor file (`<basename>.md`) stays untouched. Schema is `additionalProperties: true` so plugins can add namespaces without coordination; the built-in `unknown-field` rule warns on truly unrecognized root keys (typo guard). Format is YAML — comments via `#`, multiline strings via `|` / `>`, permissive types per the YAML 1.2 spec. See `architecture.md` §Annotation system and ROADMAP §Step 9.6 for the design rationale.",
6
+ "type": "object",
7
+ "required": ["identity"],
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "identity": {
11
+ "$ref": "#/$defs/identity",
12
+ "description": "Anchor block linking this sidecar to its markdown node and capturing the body / frontmatter hashes at the moment of the last `bump`. Drift detection compares stored hashes against the current file at scan time; mismatch emits the built-in `annotation-stale` warning (soft mode, never blocking)."
13
+ },
14
+ "annotations": {
15
+ "$ref": "annotations.schema.json",
16
+ "description": "Skill-map annotation catalog. See `annotations.schema.json` for the curated 13-field surface; users / plugins MAY add custom keys (rides on `additionalProperties: true`)."
17
+ },
18
+ "settings": {
19
+ "type": "object",
20
+ "additionalProperties": true,
21
+ "description": "Reserved for skill-map's per-node settings. v0.18.0 ships the block empty; consumers ride on `additionalProperties: true`. Concrete fields land in later sub-steps as the runtime grows."
22
+ },
23
+ "audit": {
24
+ "$ref": "#/$defs/audit",
25
+ "description": "Skill-map's audit trail. Populated by the built-in `bump` Action starting in Step 9.6.3 (Decision #125). All fields are optional at the property level — the Action atomically fills `lastBumpedAt` + `lastBumpedBy` on every bump and `createdAt` + `createdBy` on first creation. `additionalProperties: true` so future fields land additively."
26
+ }
27
+ },
28
+ "$defs": {
29
+ "audit": {
30
+ "type": "object",
31
+ "additionalProperties": true,
32
+ "properties": {
33
+ "lastBumpedAt": {
34
+ "type": "string",
35
+ "format": "date-time",
36
+ "description": "ISO 8601 datetime of the last `bump` Action invocation that materialised a write to this sidecar. Atomically set together with `lastBumpedBy` on every bump."
37
+ },
38
+ "lastBumpedBy": {
39
+ "type": "string",
40
+ "minLength": 1,
41
+ "description": "Identity of the bump invoker. Literal `'cli'` for `sm bump` (we are not threading user identity yet); `'plugin:<plugin-id>'` when a plugin's deterministic Action triggered the bump. Tests / future invokers MAY pass any non-empty string."
42
+ },
43
+ "createdAt": {
44
+ "type": "string",
45
+ "format": "date-time",
46
+ "description": "ISO 8601 datetime of the first `bump` invocation that ever wrote this sidecar. Set exactly once (when the `.sm` file did not previously exist on disk); stable across subsequent bumps."
47
+ },
48
+ "createdBy": {
49
+ "type": "string",
50
+ "minLength": 1,
51
+ "description": "Identity of the invoker that created the sidecar (same value form as `lastBumpedBy`). Set exactly once at creation time and stable thereafter."
52
+ }
53
+ }
54
+ },
55
+ "identity": {
56
+ "type": "object",
57
+ "required": ["path", "bodyHash", "frontmatterHash"],
58
+ "additionalProperties": true,
59
+ "properties": {
60
+ "path": {
61
+ "type": "string",
62
+ "minLength": 1,
63
+ "description": "Relative path from the scope root to the `.md` file this sidecar annotates. Matches the canonical Node identifier (see `node.schema.json` #/properties/path). Survives content edits; breaks on file moves — `sm refresh` re-points the sidecar after a move."
64
+ },
65
+ "bodyHash": {
66
+ "type": "string",
67
+ "pattern": "^[a-f0-9]{64}$",
68
+ "description": "sha256 of the body content (post-frontmatter), hex-encoded lowercase, captured at the moment this sidecar was last bumped. Compared against the current body hash to detect drift."
69
+ },
70
+ "frontmatterHash": {
71
+ "type": "string",
72
+ "pattern": "^[a-f0-9]{64}$",
73
+ "description": "sha256 of the canonical frontmatter (per the kernel's `canonicalFrontmatter` rule), hex-encoded lowercase, captured at the moment this sidecar was last bumped. Compared against the current frontmatter hash to detect drift."
74
+ },
75
+ "resolvedAs": {
76
+ "type": "object",
77
+ "required": ["provider", "kind"],
78
+ "additionalProperties": false,
79
+ "properties": {
80
+ "provider": {
81
+ "type": "string",
82
+ "minLength": 1,
83
+ "description": "Provider id that classifies this node (e.g. `claude`)."
84
+ },
85
+ "kind": {
86
+ "type": "string",
87
+ "minLength": 1,
88
+ "description": "Kind within the Provider (e.g. `agent`, `skill`)."
89
+ }
90
+ },
91
+ "description": "Optional override of the Provider/kind classification. Set when path-based + content-based matching are ambiguous (a single `.md` matched by multiple Providers). When present, locks the classification to (`provider`, `kind`); when absent, the kernel uses its normal classification pipeline."
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/summaries/note.schema.json",
4
- "title": "SummaryNote",
5
- "description": "Report produced by `note-summarizer` for a single `note` node. Extends `report-base.schema.json`. Stability: experimental.",
3
+ "$id": "https://skill-map.dev/spec/v0/summaries/markdown.schema.json",
4
+ "title": "SummaryMarkdown",
5
+ "description": "Report produced by `markdown-summarizer` for a single `markdown` node (the format-named generic fallback owned by the built-in `core/markdown` Provider — see `architecture.md` §Provider · dispatch order). Extends `report-base.schema.json`. Stability: experimental.",
6
6
  "allOf": [
7
7
  { "$ref": "../report-base.schema.json" }
8
8
  ],
@@ -14,7 +14,7 @@
14
14
  "safety": true,
15
15
  "whatItCovers": {
16
16
  "type": "string",
17
- "description": "One-sentence summary of the note's subject matter. REQUIRED. Named distinctly from `whatItDoes` since notes describe, not act."
17
+ "description": "One-sentence summary of the file's subject matter. REQUIRED. Named distinctly from `whatItDoes` since markdown notes describe, not act."
18
18
  },
19
19
  "topics": {
20
20
  "type": "array",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "keyFacts": {
25
25
  "type": "array",
26
- "description": "Discrete claims or data points the note asserts. Useful for search and diffing over time.",
26
+ "description": "Discrete claims or data points the file asserts. Useful for search and diffing over time.",
27
27
  "items": { "type": "string" }
28
28
  },
29
29
  "relatedNodes": {