@skill-map/spec 0.29.0 → 0.30.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 CHANGED
@@ -1,5 +1,155 @@
1
1
  # Spec changelog
2
2
 
3
+ ## 0.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5f4b181: Remove `@skill-map/testkit` and `examples/hello-world` from the monorepo.
8
+ The packaged plugin-author helper layer is retired. Plugin authors test
9
+ extensions by building fake `ctx` literals against the public types
10
+ re-exported from `@skill-map/cli` (`IExtractor`, `IAnalyzer`,
11
+ `IFormatter`, the matching `*Context` shapes, `Node`, `Link`, `Issue`).
12
+ Reason: zero downstream consumers in the public ecosystem after Step
13
+ 9.3; the maintenance cost of an independently-versioned npm package +
14
+ its own changesets, validate phases, and narrative outweighed the value
15
+ of a thin packaged helper layer.
16
+
17
+ **`spec/plugin-author-guide.md`:**
18
+
19
+ - §Testing rewritten as "Testing your plugin": shows the fake-`ctx`
20
+ pattern inline (extractor + analyzer + formatter + probabilistic
21
+ runner), with the public types coming from `@skill-map/cli`.
22
+ - §Stability footer updated to reference Step 10 for future
23
+ Action / Hook testing patterns instead of testkit coverage.
24
+ - §Providers / Actions advisory wording no longer references the
25
+ testkit roadmap.
26
+
27
+ **`spec/architecture.md`:**
28
+
29
+ - `src/` directory tree drops the `testkit/` row.
30
+ - Qualified-id example list swaps `hello-world/greet` for the
31
+ generic `my-plugin/my-extractor`.
32
+
33
+ **Monorepo plumbing** (no end-user impact):
34
+
35
+ - `pnpm-workspace.yaml`, root `package.json`, `Dockerfile`, and
36
+ `scripts/check-changeset.js` drop the `testkit/` and
37
+ `examples/hello-world/` entries.
38
+ - `context/scripts.md`, `context/kernel.md`, `context/notebooklm.md`,
39
+ `ROADMAP.md`, `CONTRIBUTING.md`, `AGENTS.md`, `.claude/agents/commit.md`,
40
+ and `scripts/build-user-changelog.js` updated to reflect the
41
+ two-public-package surface (`@skill-map/spec` + `@skill-map/cli`).
42
+ - `src/__tests__/integration/dockerfile-demo-assets.spec.ts` drops
43
+ the obsolete `COPY` assertions for both removed workspaces.
44
+ - JSDoc in `src/kernel/registry.ts` replaces the `hello-world/greet`
45
+ example with `my-plugin/my-extractor`.
46
+
47
+ **`web/modules/roadmap.js`:**
48
+
49
+ - Step 9 card (EN + ES, release tag + brief) drops the
50
+ `@skill-map/testkit` mention.
51
+
52
+ **Post-merge action required**: run
53
+ `/usr/bin/npm deprecate "@skill-map/testkit@*" "Subsumed: plugin authors
54
+ test against @skill-map/cli types directly. See
55
+ https://github.com/crystian/skill-map/blob/main/spec/plugin-author-guide.md."`
56
+ against the real `npm` binary (NOT the `pnpm`-aliased `npm` in the
57
+ maintainer's shell, which fails with `ERR_PNPM_REGISTRY_ERROR: 404 Not
58
+ Found` on the deprecate endpoint). `/usr/bin/` bypasses the zsh alias;
59
+ `command npm` and `\npm` are equivalent escapes. Latest published
60
+ version is `0.5.2`; the wildcard range covers every prior tag so anyone
61
+ with the package pinned sees the deprecation notice.
62
+
63
+ - d95e5b8: Remove the `scan.extraFolders` config key. Project-local persistent
64
+ extension of the indexed scan no longer exists; to walk a directory
65
+ outside the project root pass it as a positional argument to
66
+ `sm scan [roots...]` (per-invocation, not persisted). The narrower
67
+ `scan.referencePaths` key (validate links against on-disk files
68
+ without indexing them) is unaffected.
69
+
70
+ **Spec (`spec/`):**
71
+
72
+ - `spec/schemas/project-config.schema.json`: `extraFolders` block
73
+ deleted. `scan.referencePaths` description trimmed of cross-
74
+ references and now reads stand-alone.
75
+ - `spec/architecture.md` §Config layering: `PROJECT_LOCAL_ONLY_KEYS`
76
+ catalogue drops `scan.extraFolders`.
77
+ - `spec/plugin-author-guide.md`: the "the only way to scan paths
78
+ outside the project is `scan.extraFolders`" sentence rewrites to
79
+ point at positional roots.
80
+ - `spec/index.json` regenerated.
81
+
82
+ **Kernel + config (`src/kernel/`, `src/config/`, `src/core/config/`):**
83
+
84
+ - `IScanConfig` drops `extraFolders: string[]`.
85
+ - `PROJECT_LOCAL_ONLY_KEYS` and `PRIVACY_SENSITIVE_KEYS` lose the
86
+ entry.
87
+ - `projectPathExposure` collapses the two-branch list-check to one.
88
+ - `defaults.json` drops the `extraFolders: []` line.
89
+
90
+ **Runtime (`src/core/runtime/`):**
91
+
92
+ - `resolveScanRoots(inputs)` simplifies to `{ positionalRoots } =>
93
+ string[]`; no more `IScanRootsInputs.extraFolders`,
94
+ `IScanRootsResolution.fromExtra`, or `emitRootsAdvisory()`.
95
+ - The `includingExtraFoldersAdvisory` text catalog entry is removed.
96
+
97
+ **CLI (`src/cli/`):**
98
+
99
+ - `sm scan` help text loses the extraFolders sentence; positional
100
+ roots are now the documented way to extend the scan.
101
+ - `sm serve` boot banner reads only `scan.referencePaths` from the
102
+ effective config; the banner row labelled `Extras` (and the
103
+ matching shape on `IBannerInput` / `IFigletInput`) is removed.
104
+ `Refs` stays.
105
+ - `sm config set --yes` description trimmed to reflect the single
106
+ privacy-sensitive key remaining.
107
+
108
+ **Server (`src/server/`):**
109
+
110
+ - `WatcherService` no longer reads config to compute roots; it walks
111
+ `['.']` unconditionally. `loadConfig` and `resolveScanRoots`
112
+ imports drop. `restart()` is still useful (and still wired by
113
+ `PATCH /api/project-preferences`) so the side-set walk picks up
114
+ fresh `scan.referencePaths` on the next batch.
115
+ - `PATCH /api/project-preferences`: AJV body schema, `IPatchBody`,
116
+ `IProjectPreferencesEnvelope`, `IPlannedWrite.key`, `collectWrites`
117
+ all collapse to a single `referencePaths` branch.
118
+ - Catalog strings adjusted (the `extraFolders` example dropped from
119
+ `projectPrefsScanNotObject` etc).
120
+
121
+ **UI (`ui/src/`):**
122
+
123
+ - Settings → Project drops the entire `extraFolders` row (HTML, TS
124
+ signal + computed + add/remove handlers, i18n strings, mocks).
125
+ - `IProjectPreferencesApi` and `IProjectPreferencesPatchApi` lose
126
+ `extraFolders`.
127
+ - Test mocks (`app.spec.ts`, `graph-view.spec.ts`,
128
+ `inspector-view.spec.ts`) updated.
129
+
130
+ **Tests:**
131
+
132
+ - `server/routes/__tests__/project-preferences-route.spec.ts`: 5
133
+ PATCH cases remapped from `extraFolders` to `referencePaths`.
134
+ - `kernel/config/__tests__/config-loader.spec.ts`: strip-test
135
+ renamed and split.
136
+ - `core/runtime/__tests__/scan-roots.spec.ts`: drops 3 cases that
137
+ passed `extraFolders`; keeps the positional + default cases.
138
+ - `core/config/__tests__/config-helper.spec.ts`:
139
+ `PROJECT_LOCAL_ONLY_KEYS` catalogue assertion narrowed; the
140
+ `target=project` rejection test now targets `scan.referencePaths`.
141
+
142
+ **Backward compatibility note**: existing `settings.local.json` files
143
+ that still carry `scan.extraFolders` keep loading without error. The
144
+ loader's per-key resilience drops the unknown key with a generic
145
+ "unknown key ignored" warning; nothing crashes, the rest of the file
146
+ takes effect. Operators who relied on the key should switch to
147
+ positional roots on `sm scan`.
148
+
149
+ ## User-facing
150
+
151
+ We removed `scan.extraFolders`. To extend the scan beyond the project root, pass folders as positional arguments to `sm scan [roots...]`. The `scan.referencePaths` key (validates links against on-disk files without indexing) is unchanged. Existing entries are silently ignored.
152
+
3
153
  ## 0.29.0
4
154
 
5
155
  ### Minor Changes
package/architecture.md CHANGED
@@ -72,7 +72,7 @@ The loader enforces two id-uniqueness analyzers during discovery (see [`plugin-a
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
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
- 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.
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`, `my-plugin/my-extractor`). 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
 
77
77
  Each plugin (and each built-in bundle) declares a **granularity** that controls how its extensions are toggled. `granularity: 'bundle'` (the default) means the plugin id is the only enable/disable key; `granularity: 'extension'` means each extension is independently toggle-able under its qualified id. The loader's pre-import `resolveEnabled(pluginId)` short-circuit is always coarse (bundle level), when a granularity=`extension` bundle is partially enabled, the import work proceeds and the runtime composer (the CLI's `composeScanExtensions` / `composeFormatters` in `src/cli/util/plugin-runtime.ts`) drops the disabled extensions before they reach the orchestrator. Vendor Provider bundles (`claude`, `gemini`, `agent-skills`) ship as granularity=`bundle` (the platform integration is on or off as a whole); the `core` bundle is granularity=`extension` (every kernel built-in is removable, satisfying §Boot invariant: "no extension is privileged"). See [`plugin-author-guide.md` §Granularity, bundle vs extension](./plugin-author-guide.md#granularity--bundle-vs-extension) for the author-facing summary.
78
78
 
@@ -408,7 +408,6 @@ src/
408
408
  ├── kernel/ Registry, Orchestrator, domain types, use cases, port interfaces
409
409
  ├── cli/ Clipanion commands, thin wrappers over kernel
410
410
  ├── server/ Hono + WebSocket, thin wrapper over kernel
411
- ├── testkit/ Kernel mocks for plugin authors
412
411
  └── adapters/
413
412
  ├── sqlite/ node:sqlite + Kysely + CamelCasePlugin (StoragePort)
414
413
  ├── filesystem/ real fs (FilesystemPort)
@@ -459,10 +458,9 @@ One locality class constrains which layers a given key MAY live in. It is enforc
459
458
 
460
459
  Members:
461
460
  - `allowEditSmFiles`, per-project consent to create / modify `.sm` sidecars.
462
- - `scan.extraFolders`, additional scan paths (the ONLY way to extend the scan beyond the project root).
463
461
  - `scan.referencePaths`, additional link-validation paths.
464
462
 
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.
463
+ Both 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.
466
464
 
467
465
  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.
468
466
 
package/index.json CHANGED
@@ -174,13 +174,13 @@
174
174
  }
175
175
  ]
176
176
  },
177
- "specPackageVersion": "0.29.0",
177
+ "specPackageVersion": "0.30.0",
178
178
  "integrity": {
179
179
  "algorithm": "sha256",
180
180
  "files": {
181
- "CHANGELOG.md": "9de333733bb26b8f1044d823568c4e7d0f92a770b9afe562f835b19a16c48e0a",
181
+ "CHANGELOG.md": "9ef9cad9d55872179022ce039ffa02441e17547fec03a6b20c70a83398bda35a",
182
182
  "README.md": "54c4649fa9742bf2f74423ea78788a7474ce09649cbe1e72a270b606cf16a0a5",
183
- "architecture.md": "d40423d3df102c31744186f3b5e91446e57fb78839e67c010501fb210db6d545",
183
+ "architecture.md": "296c8348b630016aac8b21b48c51ba78e4e42c1884805fe426a894e15ba746cc",
184
184
  "cli-contract.md": "c22f7c82d460714efaf34a04a2d2367d21eb04985100aef1291071e6726cbc64",
185
185
  "conformance/README.md": "6871dde25b5770ed945284c9e0f749e0768ec3f5ba4966bdb215985789e43887",
186
186
  "conformance/cases/kernel-empty-boot.json": "2a5be9c93143d07a16d998df09dcc8fa4ea2d2f9a0bff6417573ed5a770352c1",
@@ -206,7 +206,7 @@
206
206
  "interfaces/security-scanner.md": "e8049712b9cf7a07c786bf19f8f775f8ef9638f063f7fba5c7a8b1431b92f38e",
207
207
  "job-events.md": "84206168ac12b536d34470d62f8c8cba95dab181fee66d23203c2cf5dfbee716",
208
208
  "job-lifecycle.md": "9c429121f98a07c8795f8979ed1abc5e5334e3f89db51585a8da55c527ef855b",
209
- "plugin-author-guide.md": "2c5f8e563908f55a5d608d1e35b28b425ef6cd62958cfcb03dfd475c3dc9ffa4",
209
+ "plugin-author-guide.md": "bf7e01b1a36bfe0287d552f12d2211e76de33daf3cd172b841d46e49f7394878",
210
210
  "plugin-kv-api.md": "1acc69ed82433a74e35ada61d63a6d7379fb61046ff83de1e0facbe884c64704",
211
211
  "prompt-preamble.md": "9dd4f6d1bc6a425f8782fcee10cbe75909e8d64e28781fda56c2fae909b02f40",
212
212
  "schemas/annotations.schema.json": "e39990d47f53e25a1b3a5587a5714486d0b819b8eeaac10d42783a675296aee1",
@@ -232,7 +232,7 @@
232
232
  "schemas/node.schema.json": "e5da06c9262cc0f2f7584d5733ebc1c08acd75487952ed7b4d6035fb417aaa4b",
233
233
  "schemas/plugins-doctor.schema.json": "c1d92f30fdb0080e8cd8f7dc5d43e01aae02a16640bc5eb04811c337a275de58",
234
234
  "schemas/plugins-registry.schema.json": "cca7ae65f0c22510ea27ea5ae34e0074f5beb5871a57b005b6b831e6ceaff5c0",
235
- "schemas/project-config.schema.json": "7bb695476015b6b43026db78208aedf67350f4bc2c796c822fa87d0c9093b13f",
235
+ "schemas/project-config.schema.json": "84d41720edc968a8e05499783d5a81ff18aaf2dd8b702140ee84c0045fd68789",
236
236
  "schemas/refresh-report.schema.json": "54519b8caf86ba84c182f9565be9b5084bc1631ae05e9217ee18f34c0039fff3",
237
237
  "schemas/report-base-deterministic.schema.json": "9d318d0181d121097c906ef3af1c52d71c782740bd04cf23418d7627ce2c3ed5",
238
238
  "schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skill-map/spec",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "JSON Schemas, prose contracts, and conformance suite for the skill-map specification.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  # Plugin author guide
2
2
 
3
- How to ship a third-party `skill-map` plugin: directory layout, manifest fields, the six extension kinds, storage choice, version compatibility, dual-mode posture, and how to test the result with `@skill-map/testkit`.
3
+ How to ship a third-party `skill-map` plugin: directory layout, manifest fields, the six extension kinds, storage choice, version compatibility, dual-mode posture, and how to unit-test the result against the kernel's public types.
4
4
 
5
5
  This guide is **descriptive prose**, not the normative contract. The normative pieces live in the schemas and the architecture document, every claim here is cross-linked to its source. When the two disagree, [`architecture.md`](./architecture.md) wins.
6
6
 
@@ -485,7 +485,7 @@ export default {
485
485
 
486
486
  ### Providers / Actions
487
487
 
488
- These ship later in the v1.x line as bundled built-ins; the spec already pins their manifest shapes. Until the testkit grows full helpers for them (planned alongside Step 10), authors are encouraged to test them with a live kernel via `sm scan` against a fixture directory rather than in unit tests.
488
+ These ship later in the v1.x line as bundled built-ins; the spec already pins their manifest shapes. Until Step 10 lands the job subsystem, authors are encouraged to test them with a live kernel via `sm scan` against a fixture directory rather than in unit tests.
489
489
 
490
490
  #### Provider, `kinds` catalog
491
491
 
@@ -497,7 +497,7 @@ Every Provider declares one required top-level field beyond the manifest base: `
497
497
  - **`defaultRefreshAction`**, qualified action id (`<plugin-id>/<action-id>`) the UI's `🧠 prob` button dispatches. The action MUST exist in the registry; a dangling reference disables the Provider with `invalid-manifest`.
498
498
  - **`ui`**, presentation block: `{ label, color, colorDark?, emoji?, icon? }`. The UI ships every `ui` block to the front-end via the `kindRegistry` envelope so built-in and user-plugin kinds render identically. `icon` is a discriminated union (`{ kind: 'pi'; id }` for PrimeIcons, `{ kind: 'svg'; path }` for raw SVG). The `ui` block is required (not optional) so the UI never has to invent visuals for unknown kinds. See [`architecture.md` §Provider · `ui` presentation](./architecture.md#provider--ui-presentation) for the field-by-field contract.
499
499
 
500
- The Provider's walker hardcodes the paths it scans within the project (e.g. `.claude/`, `.cursor/rules`). The kernel does NOT extend the scan into the user's HOME based on Provider hints; the only way to scan paths outside the project is `scan.extraFolders` (set by the operator), which is privacy-sensitive and gated by `--yes`.
500
+ The Provider's walker hardcodes the paths it scans within the project (e.g. `.claude/`, `.cursor/rules`). The kernel does NOT extend the scan into the user's HOME based on Provider hints; the only way to scan paths outside the project is by passing them as positional roots to `sm scan [roots...]` (per-invocation, not persisted).
501
501
 
502
502
  ```jsonc
503
503
  {
@@ -704,45 +704,49 @@ The full per-kind capability matrix lives in [`architecture.md` §Execution mode
704
704
 
705
705
  ---
706
706
 
707
- ## Testing with `@skill-map/testkit`
707
+ ## Testing your plugin
708
708
 
709
- ```bash
710
- npm install --save-dev @skill-map/testkit
711
- ```
712
-
713
- The testkit ships builders, per-kind context factories, in-memory KV / runner fakes, and high-level `runExtractorOnFixture` / `runAnalyzerOnGraph` / `runFormatterOnGraph` helpers. Most plugin tests reduce to one line per assertion.
709
+ Plugin extensions are plain ESM modules with a single entry point per kind (`extract` / `evaluate` / `format` / `run` / `on`); their inputs are well-typed context objects from `@skill-map/cli`. That makes them straightforward to unit-test without a kernel or DB: build a fake `ctx` literal, call the entry point, assert on what it captured.
714
710
 
715
711
  ```javascript
716
712
  import { test } from 'node:test';
717
713
  import { strictEqual } from 'node:assert';
718
- import { runExtractorOnFixture, node } from '@skill-map/testkit';
719
714
 
720
715
  import extractor from '../extractors/my-extractor/index.js';
721
716
 
722
717
  test('emits one reference per [[ref:<name>]] token', async () => {
723
- const { links } = await runExtractorOnFixture(extractor, {
718
+ const links = [];
719
+ await extractor.extract({
720
+ node: { path: 'a.md', kind: 'skill', provider: 'claude' },
724
721
  body: 'Talk to [[ref:architect]] or [[ref:sre]].',
725
- context: { node: node({ path: 'a.md' }) },
722
+ frontmatter: {},
723
+ settings: {},
724
+ emitLink: (link) => links.push(link),
725
+ enrichNode: () => {},
726
+ emitContribution: () => {},
726
727
  });
727
728
  strictEqual(links.length, 2);
728
729
  strictEqual(links[0].target, 'architect');
729
730
  });
730
731
  ```
731
732
 
732
- For analyzer tests, `runAnalyzerOnGraph(analyzer, { context: { nodes, links } })` returns the issue array. For formatter tests, `runFormatterOnGraph(formatter, { context: { nodes, links, issues } })` returns the formatted string.
733
+ For analyzers, the same pattern applies: build a `ctx` with `nodes`, `links`, an `emitContribution` spy if you assert on view contributions, and call `analyzer.evaluate(ctx)`, it returns the issue array. Formatters take `{ nodes, links, issues }` and return a string from `formatter.format(ctx)`.
733
734
 
734
- For probabilistic extensions, `makeFakeRunner()` queues canned responses and records every call:
735
+ For probabilistic extensions (Actions / Hooks running in `mode: 'probabilistic'`), shape a fake `ctx.runner` that records the calls your test cares about:
735
736
 
736
737
  ```javascript
737
- import { makeFakeRunner } from '@skill-map/testkit';
738
-
739
- const runner = makeFakeRunner();
740
- runner.queue({ text: '5 nodes summarized' });
741
- const result = await myAction.run({ runner, ... });
742
- strictEqual(runner.history[0].action, 'skill-summarizer');
738
+ const calls = [];
739
+ const runner = {
740
+ async run(call) {
741
+ calls.push(call);
742
+ return { text: 'mocked response' };
743
+ },
744
+ };
745
+ await myAction.run({ runner, /* … */ });
746
+ strictEqual(calls[0].action, 'skill-summarizer');
743
747
  ```
744
748
 
745
- Full surface in `@skill-map/testkit/index.ts`.
749
+ The public TypeScript types (`IExtractor`, `IAnalyzer`, `IFormatter`, `IExtractorContext`, `IAnalyzerContext`, `IFormatterContext`, `Node`, `Link`, `Issue`, …) are re-exported from `@skill-map/cli` so authors can type-check their fakes against the same surface the kernel consumes.
746
750
 
747
751
  ---
748
752
 
@@ -1218,7 +1222,7 @@ Companion verbs:
1218
1222
 
1219
1223
  ## Stability
1220
1224
 
1221
- - Document status: **stable** as of spec v1.0.0. Future minor revisions add new sections (e.g. richer testkit coverage when actions gain helpers); breaking edits to the documented surface require a major bump per [`versioning.md`](./versioning.md).
1225
+ - Document status: **stable** as of spec v1.0.0. Future minor revisions add new sections (e.g. Action / Hook testing patterns once Step 10 lands the job subsystem); breaking edits to the documented surface require a major bump per [`versioning.md`](./versioning.md).
1222
1226
  - The six plugin statuses (`loaded` / `disabled` / `incompatible-spec` / `invalid-manifest` / `load-error` / `id-collision`) are stable; adding a seventh status is a minor bump.
1223
1227
  - The structural analyzer **directory name MUST equal manifest id** is stable; relaxing it (allowing mismatch) is a major bump.
1224
1228
  - The cross-root id-collision analyzer (both sides blocked, no precedence) is stable; introducing precedence (e.g. project root wins over global) is a major bump.
@@ -58,15 +58,10 @@
58
58
  }
59
59
  }
60
60
  },
61
- "extraFolders": {
62
- "type": "array",
63
- "items": { "type": "string" },
64
- "description": "**Privacy-sensitive, project-local only** (per `core/config/helper:PROJECT_LOCAL_ONLY_KEYS`) when entries point outside the project, opens disk access there. Default `[]`. Additional directories appended to the scan roots; same parsing / indexing as the project root. Paths starting with `~` resolve against the user home; relative paths resolve against the project root. Reference impl gates writes that introduce out-of-project paths behind `--yes` (CLI) and a confirm dialog (UI). **Stripped with a warning when found in the committed `project` layer**, paths are inherently per-machine and must not travel via the shared repo. This is the ONLY mechanism to extend the scan beyond the project root: skill-map does NOT auto-include the user's HOME based on Provider hints, every out-of-project path must be listed here explicitly."
65
- },
66
61
  "referencePaths": {
67
62
  "type": "array",
68
63
  "items": { "type": "string" },
69
- "description": "**Privacy-sensitive, project-local only** (per `core/config/helper:PROJECT_LOCAL_ONLY_KEYS`) when entries point outside the project, opens read-only disk access for link validation only. Default `[]`. Directories walked in parallel by the scan to collect existing absolute paths into a side set; the kernel passes the set to analyzers via `IAnalyzerContext.referenceablePaths` so `core/broken-ref` can resolve a link against the filesystem when the in-graph lookup misses. Files under these paths are NOT parsed and NOT indexed as nodes, the only effect is suppressing `broken-ref` warnings for targets that exist on disk outside the scan. Same write-gate analyzers as `extraFolders`. **Stripped with a warning when found in the committed `project` layer**."
64
+ "description": "**Privacy-sensitive, project-local only** (per `core/config/helper:PROJECT_LOCAL_ONLY_KEYS`) when entries point outside the project, opens read-only disk access for link validation only. Default `[]`. Directories walked in parallel by the scan to collect existing absolute paths into a side set; the kernel passes the set to analyzers via `IAnalyzerContext.referenceablePaths` so `core/broken-ref` can resolve a link against the filesystem when the in-graph lookup misses. Files under these paths are NOT parsed and NOT indexed as nodes, the only effect is suppressing `broken-ref` warnings for targets that exist on disk outside the scan. Reference impl gates writes that introduce out-of-project paths behind `--yes` (CLI) and a confirm dialog (UI). **Stripped with a warning when found in the committed `project` layer**, paths are inherently per-machine and must not travel via the shared repo."
70
65
  }
71
66
  }
72
67
  },