@skill-map/spec 0.26.0 → 0.28.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 +334 -0
- package/architecture.md +29 -30
- package/cli-contract.md +55 -20
- package/conformance/README.md +4 -0
- package/conformance/cases/no-global-scope.json +13 -0
- package/conformance/cases/sidecar-end-to-end.json +4 -4
- package/conformance/coverage.md +7 -4
- package/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/kinds/markdown/kind.json +3 -0
- package/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/kinds/markdown/schema.json +6 -0
- package/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/plugin.json +3 -2
- package/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/providers/bad-provider/index.js +17 -0
- package/db-schema.md +7 -7
- package/index.json +26 -21
- package/package.json +1 -1
- package/plugin-author-guide.md +94 -47
- package/schemas/extensions/action.schema.json +28 -44
- package/schemas/extensions/analyzer.schema.json +34 -32
- package/schemas/extensions/base.schema.json +26 -55
- package/schemas/extensions/extractor.schema.json +35 -22
- package/schemas/extensions/formatter.schema.json +4 -14
- package/schemas/extensions/hook.schema.json +2 -9
- package/schemas/extensions/provider-kind.schema.json +71 -0
- package/schemas/extensions/provider.schema.json +3 -90
- package/schemas/plugins-registry.schema.json +11 -27
- package/schemas/project-config.schema.json +0 -11
- package/schemas/scan-result.schema.json +1 -6
- package/schemas/user-settings.schema.json +39 -0
- package/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,339 @@
|
|
|
1
1
|
# Spec changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e21216e: Simplify plugin manifest fields beyond the file-layout refactor. The
|
|
8
|
+
previous `structure-as-truth-plugins` changeset moved bundle / kind /
|
|
9
|
+
id discovery onto the filesystem; this one extends the same principle
|
|
10
|
+
into the manifest schemas themselves so the only fields that survive
|
|
11
|
+
are the ones the kernel cannot derive from disk.
|
|
12
|
+
|
|
13
|
+
**Plugin manifest (`plugin.json`):**
|
|
14
|
+
|
|
15
|
+
- Drop `id` (the directory name is the id; AJV rejects manifests that
|
|
16
|
+
declare it).
|
|
17
|
+
- `description` and `catalogCompat` are now required (were optional).
|
|
18
|
+
- `granularity` is now optional with a default of `'extension'` (was
|
|
19
|
+
required). Most plugins drop the field entirely.
|
|
20
|
+
- Drop `settings` at the plugin level; settings move to the extension
|
|
21
|
+
manifests that actually consume them.
|
|
22
|
+
|
|
23
|
+
**Extension base (every kind):**
|
|
24
|
+
|
|
25
|
+
- Drop `id`, `kind`, `stability`, `preconditions` (free-form). The
|
|
26
|
+
loader injects `id` / `kind` / `pluginId` from the folder layout;
|
|
27
|
+
the other two were display-only and free-form respectively, and
|
|
28
|
+
the kernel never consumed them.
|
|
29
|
+
- `description` is now required.
|
|
30
|
+
- Rename `annotationContributions` (map) to `annotation` (singular):
|
|
31
|
+
one extension contributes at most one annotation key, and the key
|
|
32
|
+
is the extension's folder name. Use multiple extensions to
|
|
33
|
+
contribute multiple keys.
|
|
34
|
+
- Rename `viewContributions` to `ui` on the manifest. The
|
|
35
|
+
runtime-aggregated catalog (`Kernel.getRegisteredViewContributions()`,
|
|
36
|
+
`IPluginRuntimeBundle.viewContributions`) keeps its name.
|
|
37
|
+
- Add `settings: Record<id, ISettingDeclaration>` (moved from the
|
|
38
|
+
plugin manifest).
|
|
39
|
+
|
|
40
|
+
**Provider:**
|
|
41
|
+
|
|
42
|
+
- Drop the inline `kinds` map. The kind catalog now lives under
|
|
43
|
+
`<plugin>/kinds/<kindName>/` with two files per kind:
|
|
44
|
+
`schema.json` (frontmatter schema) and `kind.json` carrying the
|
|
45
|
+
`{ ui }` block. The loader walks the directory and projects each
|
|
46
|
+
entry into the runtime `kinds` descriptor.
|
|
47
|
+
- New schema `extensions/provider-kind.schema.json` validates the
|
|
48
|
+
`kind.json` shape.
|
|
49
|
+
- Drop `defaultRefreshAction`. The UI's `🧠 prob` refresh button is
|
|
50
|
+
retired; a replacement UX is TBD.
|
|
51
|
+
- `roots` is enforcement-grade: a Provider with declared `roots`
|
|
52
|
+
only sees files matching at least one glob; a Provider without
|
|
53
|
+
`roots` acts as the fallback for files unmatched by any other
|
|
54
|
+
Provider. Supported patterns: `prefix/**` (deep), `prefix/*`
|
|
55
|
+
(shallow), exact path. Two Providers whose roots both match the
|
|
56
|
+
same file produce `provider-ambiguous` (already in spec) and the
|
|
57
|
+
file stays unclassified.
|
|
58
|
+
|
|
59
|
+
**Extractor:**
|
|
60
|
+
|
|
61
|
+
- Drop `emitsLinkKinds` (the global closed enum of link kinds is the
|
|
62
|
+
contract; off-enum emissions drop with `extension.error`).
|
|
63
|
+
- Drop `defaultConfidence` (declare confidence per-emit on
|
|
64
|
+
`ctx.emitLink({ ..., confidence })`).
|
|
65
|
+
- Drop `applicableKinds` (array). Use `precondition.kind` instead with
|
|
66
|
+
qualified ids like `'claude/agent'`. The same `precondition` shape
|
|
67
|
+
is shared with Analyzer and Action.
|
|
68
|
+
|
|
69
|
+
**Analyzer:**
|
|
70
|
+
|
|
71
|
+
- Drop `emitsAnalyzerIds` (the qualified extension id is the default
|
|
72
|
+
`analyzer_id`).
|
|
73
|
+
- Drop `defaultSeverity` (declare severity per-emit on
|
|
74
|
+
`ctx.emitIssue({ ..., severity })`).
|
|
75
|
+
- Drop `consumes`, `configurable`, `recommendedActions`. The
|
|
76
|
+
analyzer↔action relationship is now declared from the Action side
|
|
77
|
+
via `precondition.analyzerIds` (Modelo B): one Action says "I
|
|
78
|
+
resolve these analyzer findings", instead of one Analyzer saying
|
|
79
|
+
"these actions help".
|
|
80
|
+
- Add `precondition: { kind?, provider? }` (same shape as Extractor).
|
|
81
|
+
|
|
82
|
+
**Action:**
|
|
83
|
+
|
|
84
|
+
- Drop `reportSchemaRef` and `promptTemplateRef`. The kernel now
|
|
85
|
+
resolves these by convention from the action folder:
|
|
86
|
+
`<action-dir>/report.schema.json` (always required) and
|
|
87
|
+
`<action-dir>/prompt.md` (required when `mode='probabilistic'`,
|
|
88
|
+
forbidden when `mode='deterministic'`).
|
|
89
|
+
- Drop `expectedTools`, `fanOutPolicy`, `precondition.stability`,
|
|
90
|
+
`precondition.custom`.
|
|
91
|
+
- Add `precondition.analyzerIds` (Modelo B).
|
|
92
|
+
- Rename `expectedDurationSeconds` to `probExpectedDurationSeconds`
|
|
93
|
+
to mark it as probabilistic-only via the `prob*` prefix convention.
|
|
94
|
+
- `mode` is now optional with default `'deterministic'` (was
|
|
95
|
+
required).
|
|
96
|
+
|
|
97
|
+
**Formatter:**
|
|
98
|
+
|
|
99
|
+
- Drop `formatId` (comes from the folder name; the loader injects it
|
|
100
|
+
into the runtime instance).
|
|
101
|
+
- Drop `supportsFilter` (every formatter supports `--filter`).
|
|
102
|
+
|
|
103
|
+
**Hook:**
|
|
104
|
+
|
|
105
|
+
- Drop `mode`. Hooks are deterministic-only; LLM-dependent reactions
|
|
106
|
+
are modeled as a deterministic hook that enqueues a probabilistic
|
|
107
|
+
Action via `ctx.queue('<plugin>/<action>', payload)`.
|
|
108
|
+
|
|
109
|
+
**Loader changes (`src/kernel/adapters/plugin-loader/`):**
|
|
110
|
+
|
|
111
|
+
- The exported manifest is stripped of any `id` / `kind` / `pluginId`
|
|
112
|
+
/ `kinds` / `formatId` keys before AJV validation; the loader
|
|
113
|
+
injects the canonical values from the folder layout. Legacy
|
|
114
|
+
manifests that still inline these fields load cleanly.
|
|
115
|
+
- New `discoverProviderKinds(...)` reads `<plugin>/kinds/<k>/{schema.json,
|
|
116
|
+
kind.json}` and merges the result into the runtime Provider
|
|
117
|
+
instance. Failure modes: missing or unparseable `schema.json`
|
|
118
|
+
→ `load-error`; missing, unparseable, or AJV-invalid `kind.json`
|
|
119
|
+
→ `invalid-manifest`.
|
|
120
|
+
- New `validateActionFileConventions(...)` enforces the
|
|
121
|
+
`report.schema.json` / `prompt.md` conventions.
|
|
122
|
+
- New `matchesAnyRoot(...)` powers Provider `roots` enforcement
|
|
123
|
+
inside `processRawNode`.
|
|
124
|
+
|
|
125
|
+
**Spec docs:**
|
|
126
|
+
|
|
127
|
+
- `architecture.md` §Extension kinds table, §Provider · `kinds`
|
|
128
|
+
catalog, §View contribution system updated.
|
|
129
|
+
- `plugin-author-guide.md` §Manifest section rewritten (id from
|
|
130
|
+
folder; description/catalogCompat required), §Extractor section
|
|
131
|
+
reworked around `precondition.kind`, drop guidance for
|
|
132
|
+
`emitsLinkKinds` / `defaultConfidence`.
|
|
133
|
+
- `view-slots.md` references `ui` map.
|
|
134
|
+
|
|
135
|
+
**Built-ins migration:**
|
|
136
|
+
|
|
137
|
+
- `core/bump/report.schema.json` and `core/mark-superseded/report.schema.json`
|
|
138
|
+
added (file conventions).
|
|
139
|
+
- `core/tools-count` extractor uses
|
|
140
|
+
`precondition: { kind: ['claude/agent'] }`.
|
|
141
|
+
- All built-in extensions drop `stability`, `preconditions`,
|
|
142
|
+
`emitsLinkKinds`, `defaultConfidence`, `emitsAnalyzerIds`,
|
|
143
|
+
`defaultSeverity`, `consumes`, `configurable`, `recommendedActions`,
|
|
144
|
+
`defaultRefreshAction`, `formatId` (formatter), `supportsFilter`,
|
|
145
|
+
`mode` (hook).
|
|
146
|
+
- `scripts/generate-built-ins.js` updated: `id` from bundle folder,
|
|
147
|
+
`granularity ?? 'extension'`, `toExtensionRow` drops the retired
|
|
148
|
+
display fields.
|
|
149
|
+
|
|
150
|
+
**Testkit:**
|
|
151
|
+
|
|
152
|
+
- `makeExtractorContext` populates `settings: {}` so test fixtures
|
|
153
|
+
satisfy the new required field on `IExtractorContext`.
|
|
154
|
+
|
|
155
|
+
## User-facing
|
|
156
|
+
|
|
157
|
+
**Plugin manifests are smaller.** `plugin.json` drops `id`; every extension declares only `version` + `description` plus kind-specific fields. View contributions move to `ui:`. Provider kinds live under `kinds/<kindName>/`. Run `sm plugins doctor` after upgrading.
|
|
158
|
+
|
|
159
|
+
- 8b7abbf: Structure-as-truth refactor for plugin extensions. The filesystem
|
|
160
|
+
layout (rather than declarative manifest fields) is now the single
|
|
161
|
+
source of truth for bundle / kind / extension id.
|
|
162
|
+
|
|
163
|
+
**Schema changes:**
|
|
164
|
+
|
|
165
|
+
- `PluginManifest` drops the required `extensions: string[]` array;
|
|
166
|
+
the kernel now auto-discovers extensions by walking
|
|
167
|
+
`<plugin-dir>/<kind>s/<name>/index.{js,mjs,ts}` for each known
|
|
168
|
+
kind. `granularity` is now required (no implicit default).
|
|
169
|
+
- `ExtensionBase` drops the `entry` field (it was an override for
|
|
170
|
+
the now-gone `extensions[]` path; the loader computes the entry
|
|
171
|
+
path from the discovered file).
|
|
172
|
+
- `viewContributions` moves from `base.schema.json` to
|
|
173
|
+
`extractor.schema.json` and `analyzer.schema.json`. Runtime
|
|
174
|
+
`ctx.emitContribution` only exists for those two kinds; declaring
|
|
175
|
+
view contributions on other kinds used to be a silent no-op and
|
|
176
|
+
is now rejected at manifest load.
|
|
177
|
+
|
|
178
|
+
**Loader changes:**
|
|
179
|
+
|
|
180
|
+
- `<plugin-dir>/<kind>s/<name>/` is the unit of discovery; the
|
|
181
|
+
loader walks `providers`, `extractors`, `analyzers`, `actions`,
|
|
182
|
+
`formatters`, `hooks` in canonical order, looking for an
|
|
183
|
+
`index.{js,mjs,ts}` inside each name directory.
|
|
184
|
+
- A manifest whose `kind` disagrees with the folder it lives under
|
|
185
|
+
(e.g. an `extractor` placed under `analyzers/`) is rejected as
|
|
186
|
+
`invalid-manifest` with a directed reason.
|
|
187
|
+
- Containment is enforced by construction: the loader never reads
|
|
188
|
+
paths the manifest could redirect, so `..`-escape and
|
|
189
|
+
absolute-path lanes are closed without runtime checks.
|
|
190
|
+
|
|
191
|
+
**Built-ins reorganization:**
|
|
192
|
+
|
|
193
|
+
- Source tree renamed `src/built-in-plugins/` → `src/plugins/` and
|
|
194
|
+
reorganized to `src/plugins/<bundle>/<kind>s/<name>/index.ts`.
|
|
195
|
+
Each bundle (`core`, `claude`, `gemini`, `agent-skills`) gains a
|
|
196
|
+
`plugin.json` with its metadata.
|
|
197
|
+
- `src/plugins/built-ins.ts` is now generated by
|
|
198
|
+
`scripts/generate-built-ins.js` (runs as `prebuild`, checked for
|
|
199
|
+
drift by `built-ins:check` in CI). The generator walks the
|
|
200
|
+
filesystem, reads each bundle's `plugin.json`, and emits static
|
|
201
|
+
imports + the legacy API surface (`builtIns()`,
|
|
202
|
+
`listBuiltIns()`, `builtInBundles`).
|
|
203
|
+
|
|
204
|
+
**Scaffolder (`sm plugins create`):**
|
|
205
|
+
|
|
206
|
+
- Emits the new layout: `extractors/<plugin-id>-extractor/index.js`
|
|
207
|
+
plus a `plugin.json` without `extensions` and without
|
|
208
|
+
`pluginId` on the extension export (the loader injects it from
|
|
209
|
+
`plugin.json#/id`). The legacy `mode: 'deterministic'` field on
|
|
210
|
+
the extractor stub was a no-op holdover from when extractors had
|
|
211
|
+
a mode and has been removed.
|
|
212
|
+
|
|
213
|
+
**Per-extension co-located files convention:**
|
|
214
|
+
|
|
215
|
+
Files that share the extension's folder with `index.{js,mjs,ts}`
|
|
216
|
+
are author-owned siblings. Two blessed names so consumers know
|
|
217
|
+
where to look:
|
|
218
|
+
|
|
219
|
+
- `text.ts` for externalised user-facing strings (one per
|
|
220
|
+
extension, imported by `index.ts` as `./text.js`).
|
|
221
|
+
- `<extension-name>.test.{ts,mjs,js}` for the colocated test
|
|
222
|
+
suite (picked up by the workspace's `plugins/**/*.test.ts`
|
|
223
|
+
glob).
|
|
224
|
+
|
|
225
|
+
Both are optional; the loader ignores anything that is not
|
|
226
|
+
`index.{js,mjs,ts}`, so future schemas / fixtures / conformance
|
|
227
|
+
scopes can live next to the code without manifest plumbing. The
|
|
228
|
+
in-tree built-ins under `src/plugins/` were migrated to this
|
|
229
|
+
shape: each analyzer's user-facing strings now live at
|
|
230
|
+
`<bundle>/analyzers/<name>/text.ts` instead of a centralised
|
|
231
|
+
`i18n/` directory.
|
|
232
|
+
|
|
233
|
+
## User-facing
|
|
234
|
+
|
|
235
|
+
**Plugin layout changed.** Extensions now live at `<kind>s/<name>/index.js` (e.g. `extractors/keyword-counter/index.js`); `plugin.json` no longer lists `extensions[]` and requires `granularity`. Run `sm plugins doctor` after migrating, or use `sm plugins create` for new plugins.
|
|
236
|
+
|
|
237
|
+
## 0.27.0
|
|
238
|
+
|
|
239
|
+
### Minor Changes
|
|
240
|
+
|
|
241
|
+
- f1efd1b: Remove the `-g/--global` flag and every implicit `$HOME` read from
|
|
242
|
+
skill-map. The CLI now operates exclusively on the project scope
|
|
243
|
+
(`<cwd>/.skill-map/`); there is no global / user scope, no
|
|
244
|
+
`SKILL_MAP_SCOPE` env var, no silent merge of user-level config or
|
|
245
|
+
plugins.
|
|
246
|
+
|
|
247
|
+
The user extends the scan beyond the project root via the existing
|
|
248
|
+
`scan.extraFolders` setting in project-local config (privacy-gated
|
|
249
|
+
through `sm config set --yes` or the Settings UI confirm dialog).
|
|
250
|
+
Plugins outside the project install per-project at
|
|
251
|
+
`<cwd>/.skill-map/plugins/` or load via the `--plugin-dir <path>`
|
|
252
|
+
escape hatch on the `sm plugins …` verb family.
|
|
253
|
+
|
|
254
|
+
**Narrow documented exception**: a single `~/.skill-map/settings.json`
|
|
255
|
+
file (validated by `user-settings.schema.json`) holds genuinely
|
|
256
|
+
per-machine preferences. Today it carries the update-check toggle +
|
|
257
|
+
its throttle bookkeeping; future per-machine settings (locale, theme)
|
|
258
|
+
extend it under their own sub-keys. There is no `.local` partner.
|
|
259
|
+
The file is NOT part of the project config layer system; it is read
|
|
260
|
+
directly by the module that owns each feature. `src/cli/util/user-settings-store.ts`
|
|
261
|
+
is the only module that calls `os.homedir()` for this file. The two
|
|
262
|
+
remaining `os.homedir()` callsites (`core/config/helper.ts`,
|
|
263
|
+
`core/runtime/reference-paths-walker.ts`) handle user-typed `~/foo`
|
|
264
|
+
expansion inside `scan.extraFolders` / `scan.referencePaths`, the
|
|
265
|
+
read is user-authored per invocation, not skill-map's own default.
|
|
266
|
+
|
|
267
|
+
Removed surface (`@skill-map/cli`):
|
|
268
|
+
|
|
269
|
+
- `-g/--global` flag inherited by every `SmCommand` verb (`bump`,
|
|
270
|
+
`check`, `config`, `export`, `graph`, `history`, `init`, `jobs`,
|
|
271
|
+
`list`, `orphans`, `refresh`, `scan`, `serve`, `show`, `sidecar`,
|
|
272
|
+
`watch`, every `plugins` subcommand). Calling any verb with
|
|
273
|
+
`-g/--global` now exits 2 with Clipanion's "unknown option" error.
|
|
274
|
+
- `SKILL_MAP_SCOPE=global` env var translation.
|
|
275
|
+
- `sm serve --scope project|global` flag.
|
|
276
|
+
- `sm config --source global` literal in `--source` outputs (the
|
|
277
|
+
source set is now `default | project | project-local | env | flag`).
|
|
278
|
+
- `IRuntimeContext.homedir` field.
|
|
279
|
+
- `IDbLocationOptions.global` field; `resolveDbPath` reduces to
|
|
280
|
+
`db ?? defaultProjectDbPath(ctx)`.
|
|
281
|
+
- `defaultUserPluginsDir` helper.
|
|
282
|
+
- `loadConfig` `scope: 'project' | 'global'` parameter and the
|
|
283
|
+
`user` / `user-local` file-pair iteration; the layer list is now
|
|
284
|
+
`defaults` → `project` → `project-local` → `override`.
|
|
285
|
+
- `USER_ONLY_KEYS` constant and the per-key locality enforcement
|
|
286
|
+
pinned to it. `updateCheck.enabled` is no longer part of the
|
|
287
|
+
config layer system; its toggle lives alongside the throttle
|
|
288
|
+
cache.
|
|
289
|
+
- `GET /api/health` response field `scope: 'project'|'global'`.
|
|
290
|
+
- `GET /api/plugins` item field `source: 'built-in'|'project'|'global'`
|
|
291
|
+
reduces to `'built-in'|'project'`.
|
|
292
|
+
- `scan_meta.scope` SQLite column and the matching `IScanResult.scope`
|
|
293
|
+
kernel field.
|
|
294
|
+
|
|
295
|
+
Removed surface (`@skill-map/spec`):
|
|
296
|
+
|
|
297
|
+
- `spec/cli-contract.md` § Global flags row for `-g/--global` and
|
|
298
|
+
the `SKILL_MAP_SCOPE` row in the env-var table.
|
|
299
|
+
- `spec/cli-contract.md` § serve flag table `--scope project|global`
|
|
300
|
+
row.
|
|
301
|
+
- `spec/architecture.md` § Config layering layers `user` and
|
|
302
|
+
`user-local`; `USER_ONLY_KEYS` set.
|
|
303
|
+
- `spec/db-schema.md` two-scope diagram; `scan_meta.scope` column;
|
|
304
|
+
`scope: 'global'` from `--source` enum text.
|
|
305
|
+
- `spec/schemas/scan-result.schema.json` `scope` property (was in
|
|
306
|
+
`required`).
|
|
307
|
+
- `spec/schemas/project-config.schema.json` `updateCheck`
|
|
308
|
+
description rewritten as the documented exception.
|
|
309
|
+
- `spec/schemas/plugins-registry.schema.json` status description's
|
|
310
|
+
`project / global / --plugin-dir` reference.
|
|
311
|
+
|
|
312
|
+
Added surface:
|
|
313
|
+
|
|
314
|
+
- `spec/cli-contract.md` § "Scope is always project-local"
|
|
315
|
+
normative paragraph at the top of the file, stating the
|
|
316
|
+
no-`$HOME`-reads principle and the update-check exception.
|
|
317
|
+
- `AGENTS.md` § Analyzers gains the matching operating rule for
|
|
318
|
+
agents working in the repo, "Skill-map MUST NEVER read `$HOME`
|
|
319
|
+
by default…".
|
|
320
|
+
- Regression test at `src/test/global-flag-removed.test.ts`
|
|
321
|
+
asserting Clipanion's "unknown option" error on `sm scan -g`.
|
|
322
|
+
|
|
323
|
+
Migration (no compat shim): pre-1.0, greenfield. Users who relied
|
|
324
|
+
on `~/.skill-map/skill-map.db`, `~/.skill-map/settings*.json`, or
|
|
325
|
+
`~/.skill-map/plugins/` move the files into their project
|
|
326
|
+
(`<cwd>/.skill-map/`) or pass `--plugin-dir <path>` per invocation.
|
|
327
|
+
Older DBs are not migrated, a fresh `sm init` regenerates without
|
|
328
|
+
the `scope` column.
|
|
329
|
+
|
|
330
|
+
## User-facing
|
|
331
|
+
|
|
332
|
+
`-g/--global` is gone. `sm` reads only the current project
|
|
333
|
+
(`<cwd>/.skill-map/`). To scan outside the project, add paths via
|
|
334
|
+
`scan.extraFolders` in Settings. User-scope plugins move to
|
|
335
|
+
`<cwd>/.skill-map/plugins/` or load with `--plugin-dir <path>`.
|
|
336
|
+
|
|
3
337
|
## 0.26.0
|
|
4
338
|
|
|
5
339
|
### Minor Changes
|
package/architecture.md
CHANGED
|
@@ -70,7 +70,7 @@ Operations: `discover(scopes)`, `load(pluginPath)`, `validateManifest(json)`.
|
|
|
70
70
|
The loader enforces two id-uniqueness analyzers during discovery (see [`plugin-author-guide.md` §Plugin id uniqueness](./plugin-author-guide.md#plugin-id-uniqueness) for the author-facing summary):
|
|
71
71
|
|
|
72
72
|
1. **Directory name == manifest id.** A plugin lives at `<root>/<id>/plugin.json`. A mismatch surfaces as status `invalid-manifest`. This analyzer eliminates same-root collisions by construction.
|
|
73
|
-
2. **Cross-root id collision blocks both sides.** Two plugins reachable from different roots (project
|
|
73
|
+
2. **Cross-root id collision blocks both sides.** Two plugins reachable from different roots (e.g. the project default `<cwd>/.skill-map/plugins/` and any `--plugin-dir` combination) that declare the same `id` BOTH receive status `id-collision`. No precedence analyzer applies, coherent with §Boot invariant ("no extension is privileged"). The user resolves by renaming one of them.
|
|
74
74
|
|
|
75
75
|
In addition, the loader **qualifies every extension** with its owning plugin id before registering it. The registry stores extensions under the qualified id `<plugin-id>/<extension-id>` (e.g. `core/slash`, `core/broken-ref`, `hello-world/greet`). Authors continue to declare the short `id` in each extension manifest; the loader composes the qualified form from `manifest.id` at load time. Built-in extensions bundled with the reference impl declare their `pluginId` directly in `built-ins.ts`, `core/` for kernel-internal primitives (every analyzer, the formatter, the cross-vendor extractors `annotations` / `slash` / `at-directive` / `markdown-link` / `external-url-counter` / `stability`) and vendor-specific bundles such as `claude/` (the Claude provider) for Provider integrations whose territory is platform-bound. If a plugin author injects a `pluginId` field on an extension that disagrees with `plugin.json`'s `id`, the loader emits `invalid-manifest` with a directed reason.
|
|
76
76
|
|
|
@@ -173,12 +173,12 @@ Six kinds, all first-class, all loaded through the same registry. Each kind has
|
|
|
173
173
|
|
|
174
174
|
| Kind | Role | Input | Output |
|
|
175
175
|
|---|---|---|---|
|
|
176
|
-
| **Provider** | Recognizes a platform.
|
|
177
|
-
| **Extractor** | Extracts signals from a node body. Deterministic-only: runs synchronously inside `sm scan`. Output flows through
|
|
178
|
-
| **Analyzer** | Evaluates the graph. Dual-mode: `deterministic` runs in `sm check`, `probabilistic` runs in jobs. | Full graph (nodes + links). | `Issue[]`. |
|
|
179
|
-
| **Action** | Operates on one or more nodes. Dual-mode: `deterministic` (in-process code) or `probabilistic` (rendered prompt the runner executes). | Node(s), optional args. | Deterministic: report JSON. Probabilistic: rendered prompt that a runner executes. |
|
|
180
|
-
| **Formatter** | Serializes the graph. Deterministic-only. | Graph + optional filter. | String (ASCII / Mermaid / DOT / JSON / user-defined). |
|
|
181
|
-
| **Hook** | Reacts declaratively to one of ten curated lifecycle events, eight pipeline-driven (`scan.started`, `scan.completed`, `extractor.completed`, `analyzer.completed`, `action.completed`, `job.spawning`, `job.completed`, `job.failed`) plus two CLI-process-driven (`boot` before verb routing, `shutdown` after the verb's exit code resolves).
|
|
176
|
+
| **Provider** | Recognizes a platform. The kind catalog lives on disk under `<plugin>/kinds/<kindName>/{schema.json, kind.json}` (structure-as-truth); the loader projects it onto the runtime descriptor. The Provider's walker hardcodes the paths it scans within the project (e.g. `.claude/`, `.gemini/`); it does NOT extend the scan into the user's HOME. `Provider.roots` is enforcement-grade: a Provider with declared roots only sees matching files; a Provider without `roots` acts as the fallback. Deterministic-only. | Filesystem walk results, candidate path. | `{ kind, provider } \| null`. |
|
|
177
|
+
| **Extractor** | Extracts signals from a node body. Deterministic-only: runs synchronously inside `sm scan`. Output flows through context callbacks (no return value): `ctx.emitLink(link)` for the kernel's `links` table (validated against the global closed enum of link kinds; per-extractor allowlist was retired with the structure-as-truth refactor), `ctx.enrichNode(partial)` for the kernel's enrichment layer (separate from the author's frontmatter), `ctx.emitContribution(id, payload)` for view contributions, `ctx.store` for the plugin's own KV / dedicated tables. | Parsed node (frontmatter + body) + callbacks. | `void` (output via callbacks). |
|
|
178
|
+
| **Analyzer** | Evaluates the graph. Dual-mode: `deterministic` runs in `sm check`, `probabilistic` runs in jobs. The analyzer↔action relationship is declared from the Action side via `precondition.analyzerIds` (Modelo B). | Full graph (nodes + links). | `Issue[]`. |
|
|
179
|
+
| **Action** | Operates on one or more nodes. Dual-mode: `deterministic` (in-process code) or `probabilistic` (rendered prompt the runner executes). Files-by-convention: every Action carries `<action-dir>/report.schema.json`; probabilistic Actions additionally carry `<action-dir>/prompt.md`. The retired `reportSchemaRef` / `promptTemplateRef` / `expectedTools` / `fanOutPolicy` manifest fields were replaced by these conventions and the simplified `precondition` block. | Node(s), optional args. | Deterministic: report JSON. Probabilistic: rendered prompt that a runner executes. |
|
|
180
|
+
| **Formatter** | Serializes the graph. Deterministic-only. The `formatId` consumed by `sm graph --format <name>` comes from the formatter's folder name. | Graph + optional filter. | String (ASCII / Mermaid / DOT / JSON / user-defined). |
|
|
181
|
+
| **Hook** | Reacts declaratively to one of ten curated lifecycle events, eight pipeline-driven (`scan.started`, `scan.completed`, `extractor.completed`, `analyzer.completed`, `action.completed`, `job.spawning`, `job.completed`, `job.failed`) plus two CLI-process-driven (`boot` before verb routing, `shutdown` after the verb's exit code resolves). **Deterministic-only** since the structure-as-truth refactor: LLM-dependent reactions are modeled as a deterministic Hook that enqueues a probabilistic Action via `ctx.queue('<plugin>/<action>', payload)`. Hooks REACT to events; they cannot block, mutate, or steer the pipeline. | A curated event payload (run-scoped, scan-scoped, job-scoped, or process-scoped) plus an optional declarative `filter` map. | `void` (reactions are side effects). |
|
|
182
182
|
|
|
183
183
|
### IO discipline, extensions never write to the filesystem
|
|
184
184
|
|
|
@@ -194,13 +194,14 @@ This invariant is what makes the consent gate at the kernel boundary sufficient:
|
|
|
194
194
|
|
|
195
195
|
### Provider · `kinds` catalog
|
|
196
196
|
|
|
197
|
-
Every `Provider`
|
|
197
|
+
Every `Provider` declares its kind catalog via the filesystem (structure-as-truth): each kind lives under `<plugin>/kinds/<kindName>/` and ships exactly two files:
|
|
198
198
|
|
|
199
|
-
- **`schema`**,
|
|
200
|
-
- **`
|
|
201
|
-
- **`ui`**, presentation block: `{ label, color, colorDark?, emoji?, icon? }`. See §Provider · `ui` presentation below.
|
|
199
|
+
- **`schema.json`**, the kind's frontmatter JSON Schema. MUST extend [`frontmatter/base.schema.json`](./schemas/frontmatter/base.schema.json) via `allOf` + `$ref` to base's `$id`. The kernel reads it once at boot, registers it with AJV, and validates every node's frontmatter against the entry matching its classified kind.
|
|
200
|
+
- **`kind.json`**, the per-kind metadata, today just `{ ui: { label, color, colorDark?, emoji?, icon? } }`. See §Provider · `ui` presentation below. Validated against [`schemas/extensions/provider-kind.schema.json`](./schemas/extensions/provider-kind.schema.json) at load time.
|
|
202
201
|
|
|
203
|
-
The
|
|
202
|
+
The loader's discovery (`discoverProviderKinds`) projects every `kinds/<kindName>/` directory into the runtime descriptor `instance.kinds[<kindName>] = { schema, schemaJson, ui }`. The `IProvider` runtime contract derives the kind set from `Object.keys(kinds)`; authors do not write the map by hand any more.
|
|
203
|
+
|
|
204
|
+
The retired manifest field `defaultRefreshAction` (the qualified action id the UI's `🧠 prob` button dispatched) was removed alongside the button. A replacement UX is TBD; until then, the kernel does not surface a Provider-declared "default refresh" path.
|
|
204
205
|
|
|
205
206
|
### Provider · `ui` presentation
|
|
206
207
|
|
|
@@ -362,7 +363,7 @@ Hooks introduce no new persisted state and do NOT participate in the determinist
|
|
|
362
363
|
|
|
363
364
|
### Locality
|
|
364
365
|
|
|
365
|
-
- **Drop-in**: extensions live inside plugins, discovered at boot from
|
|
366
|
+
- **Drop-in**: extensions live inside plugins, discovered at boot from `<cwd>/.skill-map/plugins/<id>/` only. The `--plugin-dir <path>` escape hatch on the `sm plugins …` verb family loads a custom directory per invocation when the user explicitly opts in.
|
|
366
367
|
- **Built-in**: the reference impl bundles a default extension set (one Provider, four extractors, five analyzers, one formatter, one hook). The fifth analyzer, `core/validate-all`, replays every scanned node and link through the authoritative spec schemas via AJV, the kernel-side guard against persisting non-conforming graph rows. The first built-in Hook is `core/update-check`, which subscribes to `shutdown` and runs the once-per-day "update available" probe + banner that lived on the CLI entry path before the Hook kind had concrete consumers. These are loaded from `src/extensions/` and are indistinguishable from plugin-supplied extensions from the kernel's point of view.
|
|
367
368
|
|
|
368
369
|
---
|
|
@@ -440,23 +441,21 @@ This is what makes "CLI-first" a coherent analyzer: every CLI verb is a kernel f
|
|
|
440
441
|
| # | Layer | Source | Audience |
|
|
441
442
|
|---|---|---|---|
|
|
442
443
|
| 1 | `defaults` | Bundled `defaults.json` (ships in the CLI binary). | Every install. |
|
|
443
|
-
| 2 | `
|
|
444
|
-
| 3 | `
|
|
445
|
-
| 4 | `
|
|
446
|
-
| 5 | `project-local` | `<cwd>/.skill-map/settings.local.json` | **Gitignored**, values are per-checkout, never travel via the repo. |
|
|
447
|
-
| 6 | `override` | Caller-supplied (env vars, CLI flags). | Process-scoped, ephemeral. |
|
|
444
|
+
| 2 | `project` | `<cwd>/.skill-map/settings.json` | **Committed to the repo**, values are shared with every collaborator and CI. |
|
|
445
|
+
| 3 | `project-local` | `<cwd>/.skill-map/settings.local.json` | **Gitignored**, values are per-checkout, never travel via the repo. |
|
|
446
|
+
| 4 | `override` | Caller-supplied (env vars, CLI flags). | Process-scoped, ephemeral. |
|
|
448
447
|
|
|
449
448
|
The merge is per dot-path: a value declared at a higher layer replaces the value at lower layers; objects recurse, arrays replace. The loader records which layer last wrote each key in a `sources` map so `sm config show --source` can attribute every effective value.
|
|
450
449
|
|
|
451
|
-
|
|
450
|
+
Only layer 2 (`project`) travels via the shared repo, so values landing in `project` are part of the contract every collaborator inherits. Layers 1, 3, 4 carry **per-machine / per-checkout state** that never leaves the project.
|
|
452
451
|
|
|
453
|
-
|
|
452
|
+
Skill-map deliberately has **no user-scope config layer**: there is no merge of `$HOME` state on top of the project. The CLI honours the principle "never read `$HOME` by default" (see `cli-contract.md` §Scope is always project-local). The narrow exception, `~/.skill-map/settings.json`, holds genuinely per-machine preferences (the update-check toggle + its throttle bookkeeping today; future locale / theme) but is **NOT** part of the config layer system: it is read directly by the module that owns the feature, never merged into the project layers above. See `cli-contract.md` §User-settings file for the contract.
|
|
454
453
|
|
|
455
|
-
|
|
454
|
+
### Per-key locality
|
|
456
455
|
|
|
457
|
-
|
|
456
|
+
One locality class constrains which layers a given key MAY live in. It is enforced in code (reference impl: `core/config/helper.ts`), not in the JSON Schema, the schema stays additive so older settings files keep validating even when a key is reclassified.
|
|
458
457
|
|
|
459
|
-
- **`PROJECT_LOCAL_ONLY_KEYS`**, keys describing per-user-per-project preferences. Valid in layers 1,
|
|
458
|
+
- **`PROJECT_LOCAL_ONLY_KEYS`**, keys describing per-user-per-project preferences. Valid in layers 1, 3, 4. **Stripped (with a warning) from layer 2 (`project`)** because the value is inherently per-user and must not be shared via the committed repo. Writes target `project-local` (`<cwd>/.skill-map/settings.local.json`); `sm config set` rejects writes to `project` for these keys with a directed error.
|
|
460
459
|
|
|
461
460
|
Members:
|
|
462
461
|
- `allowEditSmFiles`, per-project consent to create / modify `.sm` sidecars.
|
|
@@ -465,7 +464,7 @@ Two locality classes constrain which layers a given key MAY live in. Both are en
|
|
|
465
464
|
|
|
466
465
|
All three describe disk access the local operator opted into; sharing them via the repo would silently expand every collaborator's scan surface to paths that only make sense on the original author's machine.
|
|
467
466
|
|
|
468
|
-
Adding a new entry
|
|
467
|
+
Adding a new entry is a behaviour change for older installs that wrote the key into a committed file, the value gets stripped at read time. The changeset that adds the entry MUST document the migration.
|
|
469
468
|
|
|
470
469
|
---
|
|
471
470
|
|
|
@@ -590,7 +589,7 @@ Two schemas describe the wire shape:
|
|
|
590
589
|
|
|
591
590
|
### Identity
|
|
592
591
|
|
|
593
|
-
Each view contribution is identified by the qualified id `<pluginId>/<extensionId>/<contributionId>`. The plugin author declares contributions in the extension manifest under `
|
|
592
|
+
Each view contribution is identified by the qualified id `<pluginId>/<extensionId>/<contributionId>`. The plugin author declares contributions in the extension manifest under `ui: Record<string, IViewContribution>` (renamed from `viewContributions` with the structure-as-truth refactor); the loader composes the qualified id from the plugin id, the extension id, and the Record key. The runtime catalog aggregated by `Kernel.getRegisteredViewContributions()` keeps the original `viewContributions` name, only the manifest-side field changed.
|
|
594
593
|
|
|
595
594
|
### Manifest
|
|
596
595
|
|
|
@@ -598,7 +597,7 @@ Each entry picks a `slot` name from the closed catalog and supplies presentation
|
|
|
598
597
|
|
|
599
598
|
```jsonc
|
|
600
599
|
{
|
|
601
|
-
"
|
|
600
|
+
"ui": {
|
|
602
601
|
"breakdown": {
|
|
603
602
|
"slot": "inspector.body.panel.breakdown",
|
|
604
603
|
"label": "Keyword hits",
|
|
@@ -618,13 +617,13 @@ The plugin author picks ONE slot per contribution; that single decision determin
|
|
|
618
617
|
|
|
619
618
|
### Settings
|
|
620
619
|
|
|
621
|
-
Plugin user-configurable settings live
|
|
620
|
+
Plugin user-configurable settings live **on each extension's manifest** (structure-as-truth) in `settings: Record<string, ISettingDeclaration>` (see [`schemas/extensions/base.schema.json`](./schemas/extensions/base.schema.json) and [`schemas/input-types.schema.json`](./schemas/input-types.schema.json)). Each setting picks an input-type from the closed catalog (`string-list`, `single-string`, `boolean-flag`, `integer`, `enum-pick`, `enum-multipick`, `path-glob`, `regex`, `secret`, `key-value-list`). The kernel exposes resolved settings via `ctx.settings.<settingId>` to the extension's runtime methods (`extract`, `evaluate`, `invoke`, etc.); the UI generates a form per declaration; the CLI's `sm plugins config <plugin>/<extension>` exposes the same surface. Plugin-level settings are no longer supported; the field was moved from `plugin.json` to each extension that consumes it.
|
|
622
621
|
|
|
623
|
-
Settings are read once at
|
|
622
|
+
Settings are read once at extension invocation; changing a setting requires `sm scan` to re-emit affected contributions. The UI surfaces a "settings changed, rescan needed" indicator when the manifest detects mismatch; live re-emission is explicitly out of scope (rescan-required is a stability decision per `ROADMAP.md` §UI contribution system D4).
|
|
624
623
|
|
|
625
624
|
### Runtime catalog
|
|
626
625
|
|
|
627
|
-
The kernel exposes a runtime catalog (`Kernel.getRegisteredViewContributions()`) listing every plugin-contributed view contribution with its `pluginId`, `extensionId`, `contributionId`, `slot`, and the manifest-declared `label` / `tooltip` / `icon` / `emptyText` / `emitWhenEmpty`. The catalog is built once at boot from every loaded extension's `viewContributions`
|
|
626
|
+
The kernel exposes a runtime catalog (`Kernel.getRegisteredViewContributions()`) listing every plugin-contributed view contribution with its `pluginId`, `extensionId`, `contributionId`, `slot`, and the manifest-declared `label` / `tooltip` / `icon` / `emptyText` / `emitWhenEmpty`. The catalog is built once at boot from every loaded extension's `ui` map (renamed from `viewContributions` with the structure-as-truth refactor), AJV-validated, and frozen, same lifecycle as `getRegisteredAnnotationKeys()`.
|
|
628
627
|
|
|
629
628
|
Analyzers see the catalog through `IAnalyzerContext.viewContributions` so cross-cutting checks (`core/unknown-slot`, `core/contribution-orphan`) can reason about emissions.
|
|
630
629
|
|
|
@@ -714,7 +713,7 @@ Same honest-note posture as [`plugin-kv-api.md`](./plugin-kv-api.md): isolated a
|
|
|
714
713
|
|
|
715
714
|
Two built-ins ship with the system to cover catalog evolution and rename edge cases:
|
|
716
715
|
|
|
717
|
-
- **`core/unknown-slot`**, walks every loaded plugin's `
|
|
716
|
+
- **`core/unknown-slot`**, walks every loaded plugin's `ui[*].slot`; emits an `Issue` of severity `warn` for any slot not in the current kernel catalog. Parallel to `core/unknown-field` for annotations. Note: AJV at manifest load already rejects unknown slots as `invalid-manifest`; this analyzer covers the soft-warning path when a plugin remains loaded across a catalog version bump.
|
|
718
717
|
- **`core/contribution-orphan`**, joins `scan_contributions` against the live `scan_nodes` set; emits an `Issue` of severity `warn` for emissions whose `node_path` no longer exists (post-rename heuristic miss).
|
|
719
718
|
|
|
720
719
|
### Catalog versioning
|