@skill-map/spec 0.18.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.
- package/CHANGELOG.md +338 -0
- package/architecture.md +220 -24
- package/cli-contract.md +16 -9
- package/conformance/cases/orphan-markdown-fallback.json +22 -0
- package/conformance/cases/plugin-missing-ui-rejected.json +2 -1
- package/conformance/cases/sidecar-end-to-end.json +1 -2
- package/conformance/coverage.md +4 -2
- package/conformance/fixtures/orphan-markdown/.claude/agents/reviewer.md +6 -0
- package/conformance/fixtures/orphan-markdown/ARCHITECTURE.md +10 -0
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm +1 -1
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm +1 -1
- package/conformance/fixtures/sidecar-example/agent-example.sm +1 -1
- package/db-schema.md +57 -13
- package/index.json +28 -23
- package/package.json +1 -1
- package/plugin-author-guide.md +303 -29
- package/schemas/annotations.schema.json +2 -6
- package/schemas/api/rest-envelope.schema.json +55 -11
- package/schemas/extensions/base.schema.json +11 -1
- package/schemas/extensions/extractor.schema.json +3 -10
- package/schemas/extensions/provider.schema.json +1 -1
- package/schemas/frontmatter/base.schema.json +6 -1
- package/schemas/input-types.schema.json +260 -0
- package/schemas/node.schema.json +1 -19
- package/schemas/plugins-registry.schema.json +14 -2
- package/schemas/project-config.schema.json +11 -0
- package/schemas/sidecar.schema.json +5 -5
- package/schemas/summaries/markdown.schema.json +1 -1
- package/schemas/view-contracts.schema.json +298 -0
|
@@ -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 Rules. Extractors are
|
|
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 Rules. 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
|
],
|
|
@@ -11,16 +11,9 @@
|
|
|
11
11
|
"unevaluatedProperties": false,
|
|
12
12
|
"properties": {
|
|
13
13
|
"kind": { "const": "extractor" },
|
|
14
|
-
"mode": {
|
|
15
|
-
"type": "string",
|
|
16
|
-
"enum": ["deterministic", "probabilistic"],
|
|
17
|
-
"default": "deterministic",
|
|
18
|
-
"description": "`deterministic` (default): pure code, runs synchronously during `sm scan`. Same input → same output, every run. `probabilistic`: invokes an LLM via `ctx.runner` and runs only as a queued job (`sm job submit extractor:<id>`); never participates in `sm scan`. The kernel rejects probabilistic extractors that try to register scan-time hooks at load time. Omitting the field is equivalent to declaring `deterministic`."
|
|
19
|
-
},
|
|
20
14
|
"emitsLinkKinds": {
|
|
21
15
|
"type": "array",
|
|
22
|
-
"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`.",
|
|
23
|
-
"minItems": 1,
|
|
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: { ... } }`).",
|
|
24
17
|
"items": {
|
|
25
18
|
"type": "string",
|
|
26
19
|
"enum": ["invokes", "references", "mentions", "supersedes"]
|
|
@@ -42,7 +35,7 @@
|
|
|
42
35
|
"minItems": 1,
|
|
43
36
|
"items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
44
37
|
"uniqueItems": true,
|
|
45
|
-
"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
|
|
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."
|
|
46
39
|
}
|
|
47
40
|
}
|
|
48
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/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, the per-kind frontmatter schema each kind follows, and the filesystem directory (`explorationDir`) where its content lives. 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
|
|
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, the per-kind frontmatter schema each kind follows, and the filesystem directory (`explorationDir`) where its content lives. 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
|
],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/frontmatter/base.schema.json",
|
|
4
4
|
"title": "FrontmatterBase",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Universal frontmatter shape every Provider's per-kind schema extends via `allOf` + `$ref` to this `$id`. `name` and `description` are the two universally required fields; `tags` is a universally accepted optional field for author-supplied taxonomy. Per-vendor schemas (Anthropic Claude, Cursor, Obsidian, …) declare additional fields on top via the per-kind extension. `additionalProperties: true` is intentional: skill-map AGGREGATES vendor specs, it does not curate them. Vendor-specific fields (`tools`, `allowedTools`, `model`, etc.) flow through validation silently because the per-kind extension declares them.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["name", "description"],
|
|
8
8
|
"additionalProperties": true,
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"type": "string",
|
|
17
17
|
"minLength": 1,
|
|
18
18
|
"description": "One-to-three-sentence description. REQUIRED."
|
|
19
|
+
},
|
|
20
|
+
"tags": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"items": { "type": "string", "minLength": 1 },
|
|
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."
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
}
|
|
@@ -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
|
+
}
|
package/schemas/node.schema.json
CHANGED
|
@@ -20,24 +20,6 @@
|
|
|
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": ["integer", "null"],
|
|
38
|
-
"minimum": 1,
|
|
39
|
-
"description": "Monotonic version counter denormalised from sidecar `annotations.version` (Step 9.6.2). Pre-9.6.2 this field was a semver string sourced from `frontmatter.metadata.version`; the new shape is a single integer monotonic counter, orthogonal to `stability`. Major bumps mean `create a new node, supersede the old one`, not increment. See Decision #125 and `annotations.schema.json#/properties/version`."
|
|
40
|
-
},
|
|
41
23
|
"frontmatter": {
|
|
42
24
|
"type": "object",
|
|
43
25
|
"description": "Full parsed frontmatter. See `frontmatter/base.schema.json` and `frontmatter/<kind>.schema.json`.",
|
|
@@ -118,7 +100,7 @@
|
|
|
118
100
|
"root": {
|
|
119
101
|
"type": ["object", "null"],
|
|
120
102
|
"additionalProperties": true,
|
|
121
|
-
"description": "Parsed YAML root of the matching `.sm` sidecar. Mirrors the shape of `sidecar.schema.json` (top-level reserved blocks `
|
|
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."
|
|
122
104
|
}
|
|
123
105
|
}
|
|
124
106
|
}
|
|
@@ -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
|
}
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/sidecar.schema.json",
|
|
4
4
|
"title": "Sidecar",
|
|
5
|
-
"description": "Root shape of a co-located YAML sidecar (`<basename>.sm` next to `<basename>.md`).
|
|
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
6
|
"type": "object",
|
|
7
|
-
"required": ["
|
|
7
|
+
"required": ["identity"],
|
|
8
8
|
"additionalProperties": true,
|
|
9
9
|
"properties": {
|
|
10
|
-
"
|
|
10
|
+
"identity": {
|
|
11
11
|
"$ref": "#/$defs/identity",
|
|
12
|
-
"description": "
|
|
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
13
|
},
|
|
14
14
|
"annotations": {
|
|
15
15
|
"$ref": "annotations.schema.json",
|
|
16
|
-
"description": "Skill-map annotation catalog. See `annotations.schema.json` for the curated
|
|
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
17
|
},
|
|
18
18
|
"settings": {
|
|
19
19
|
"type": "object",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/summaries/markdown.schema.json",
|
|
4
4
|
"title": "SummaryMarkdown",
|
|
5
|
-
"description": "Report produced by `markdown-summarizer` for a single `markdown` node (the format-named generic fallback
|
|
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
|
],
|