@skill-map/spec 0.19.0 → 0.20.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 +347 -6
- package/README.md +6 -6
- package/architecture.md +62 -55
- package/cli-contract.md +35 -14
- package/conformance/README.md +2 -2
- package/conformance/cases/kernel-empty-boot.json +2 -2
- package/conformance/cases/sidecar-end-to-end.json +3 -3
- package/conformance/coverage.md +5 -5
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm +1 -1
- package/conformance/fixtures/sidecar-example/agent-example.md +1 -1
- package/db-schema.md +18 -17
- package/index.json +36 -36
- package/interfaces/security-scanner.md +2 -2
- package/job-events.md +12 -12
- package/job-lifecycle.md +1 -1
- package/package.json +1 -1
- package/plugin-author-guide.md +112 -81
- package/plugin-kv-api.md +5 -5
- package/prompt-preamble.md +1 -1
- package/schemas/annotations.schema.json +4 -4
- package/schemas/api/rest-envelope.schema.json +4 -4
- package/schemas/conformance-case.schema.json +2 -2
- package/schemas/extensions/analyzer.schema.json +43 -0
- package/schemas/extensions/base.schema.json +5 -5
- package/schemas/extensions/extractor.schema.json +1 -1
- package/schemas/extensions/hook.schema.json +6 -4
- package/schemas/issue.schema.json +6 -6
- package/schemas/link.schema.json +2 -2
- package/schemas/plugins-registry.schema.json +1 -1
- package/schemas/project-config.schema.json +15 -1
- package/schemas/sidecar.schema.json +2 -2
- package/schemas/summaries/agent.schema.json +1 -1
- package/schemas/summaries/command.schema.json +1 -1
- package/schemas/summaries/hook.schema.json +1 -1
- package/schemas/{view-contracts.schema.json → view-slots.schema.json} +91 -54
- package/schemas/extensions/rule.schema.json +0 -43
package/architecture.md
CHANGED
|
@@ -67,12 +67,12 @@ Discovers plugin directories, reads `plugin.json`, checks `specCompat`, dynamica
|
|
|
67
67
|
|
|
68
68
|
Operations: `discover(scopes)`, `load(pluginPath)`, `validateManifest(json)`.
|
|
69
69
|
|
|
70
|
-
The loader enforces two id-uniqueness
|
|
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
|
-
1. **Directory name == manifest id.** A plugin lives at `<root>/<id>/plugin.json`. A mismatch surfaces as status `invalid-manifest`. This
|
|
73
|
-
2. **Cross-root id collision blocks both sides.** Two plugins reachable from different roots (project + global, or any `--plugin-dir` combination) that declare the same `id` BOTH receive status `id-collision`. No precedence
|
|
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 + global, or 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
|
|
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`) 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
|
|
|
@@ -142,7 +142,7 @@ Mode is a property of the extension as a whole, not of an individual call. **An
|
|
|
142
142
|
| Kind | Modes | How mode is set |
|
|
143
143
|
|---|---|---|
|
|
144
144
|
| **Extractor** | deterministic-only | implicit; `mode` field MUST NOT appear |
|
|
145
|
-
| **
|
|
145
|
+
| **Analyzer** | deterministic / probabilistic | declared in manifest (`mode` field, optional; defaults to `deterministic`) |
|
|
146
146
|
| **Action** | deterministic / probabilistic | declared in manifest (`mode` field, **required** — no default) |
|
|
147
147
|
| **Hook** | deterministic / probabilistic | declared in manifest (`mode` field, optional; defaults to `deterministic`) |
|
|
148
148
|
| **Provider** | deterministic-only | implicit; `mode` field MUST NOT appear |
|
|
@@ -163,7 +163,7 @@ This separation is normative: a probabilistic extension cannot register a hook t
|
|
|
163
163
|
|
|
164
164
|
The kernel exposes the LLM through the `RunnerPort` (see §Ports above). Reference impl: `ClaudeCliRunner`. Tests: `MockRunner`. Other adapters (OpenAI, local Ollama, etc.) implement the same port without spec changes.
|
|
165
165
|
|
|
166
|
-
A probabilistic Action,
|
|
166
|
+
A probabilistic Action, Analyzer, or Hook receives the runner in its invocation context alongside `ctx.store` (Extractors are deterministic-only and never see the runner). The extension never imports a specific LLM SDK — the runner contract is what the spec normalizes; wire format and model selection are adapter concerns.
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
@@ -175,10 +175,10 @@ Six kinds, all first-class, all loaded through the same registry. Each kind has
|
|
|
175
175
|
|---|---|---|---|
|
|
176
176
|
| **Provider** | Recognizes a platform. Declares the catalog of node `kind`s it emits via the `kinds` map; each map entry pairs the kind's frontmatter schema (path relative to the Provider's package directory) with its `defaultRefreshAction` (a qualified action id that drives the probabilistic-refresh surface). Also declares the filesystem `explorationDir` where its content lives. Deterministic-only. | Filesystem walk results, candidate path. | `{ kind, provider } \| null`. |
|
|
177
177
|
| **Extractor** | Extracts signals from a node body. Deterministic-only: runs synchronously inside `sm scan`. Output flows through three context callbacks (no return value): `ctx.emitLink(link)` for the kernel's `links` table, `ctx.enrichNode(partial)` for the kernel's enrichment layer (separate from the author's frontmatter), `ctx.store` for the plugin's own KV / dedicated tables. | Parsed node (frontmatter + body) + callbacks. | `void` (output via callbacks). |
|
|
178
|
-
| **
|
|
178
|
+
| **Analyzer** | Evaluates the graph. Dual-mode: `deterministic` runs in `sm check`, `probabilistic` runs in jobs. | Full graph (nodes + links). | `Issue[]`. |
|
|
179
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
180
|
| **Formatter** | Serializes the graph. Deterministic-only. | Graph + optional filter. | String (ASCII / Mermaid / DOT / JSON / user-defined). |
|
|
181
|
-
| **Hook** | Reacts declaratively to one of
|
|
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). Dual-mode: `deterministic` runs in-process during the dispatch, `probabilistic` is enqueued as a job. 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
|
### Provider · `kinds` catalog
|
|
184
184
|
|
|
@@ -245,7 +245,7 @@ Read-side merge (`mergeNodeWithEnrichments` in the reference impl):
|
|
|
245
245
|
2. Sort by `enriched_at` ASC.
|
|
246
246
|
3. Spread-merge each `value` over the author frontmatter (last-write-wins per field).
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
Analyzers / `sm check` / `sm export` consume `node.frontmatter` directly (deterministic CI-safe baseline); enrichment consumption is opt-in by the caller.
|
|
249
249
|
|
|
250
250
|
Refresh verbs (`sm refresh <node>` and `sm refresh --stale`) re-run the Extractor pipeline against a node or the stale set and upsert fresh enrichment rows — see [`cli-contract.md` §Scan](./cli-contract.md#scan). With Extractors deterministic-only, `--stale` is a no-op today (no rows are stale-flagged); it remains in the contract for the future Action-prob enrichment revision noted above.
|
|
251
251
|
|
|
@@ -271,7 +271,7 @@ The invariant exists to keep `sm scan --changed` cheap on real corpora: re-parsi
|
|
|
271
271
|
Extractors that emit invocation-style links (slashes, at-directives, command names) populate the `link.trigger` block defined in [`schemas/link.schema.json`](./schemas/link.schema.json):
|
|
272
272
|
|
|
273
273
|
- `originalTrigger` — the exact source text the extractor saw, byte-for-byte. Used only for display.
|
|
274
|
-
- `normalizedTrigger` — the output of the pipeline below. Used for equality and collision detection — the built-in `trigger-collision`
|
|
274
|
+
- `normalizedTrigger` — the output of the pipeline below. Used for equality and collision detection — the built-in `trigger-collision` analyzer keys on this field.
|
|
275
275
|
|
|
276
276
|
Both fields MUST be present whenever `link.trigger` is non-null. Implementations MUST produce byte-identical `normalizedTrigger` output for byte-identical input across platforms and locales.
|
|
277
277
|
|
|
@@ -303,17 +303,19 @@ Characters outside the separator set that are not letters or digits (e.g. `/`, `
|
|
|
303
303
|
|
|
304
304
|
### Hook · curated trigger set
|
|
305
305
|
|
|
306
|
-
Hooks subscribe declaratively to a curated set of kernel lifecycle events and react to them. Reaction-only by design: a hook cannot mutate the pipeline, block emission, or alter outputs. The hookable trigger set is intentionally small —
|
|
306
|
+
Hooks subscribe declaratively to a curated set of kernel lifecycle events and react to them. Reaction-only by design: a hook cannot mutate the pipeline, block emission, or alter outputs. The hookable trigger set is intentionally small — ten events out of the full [`job-events.md`](./job-events.md) catalog. Eight are pipeline-driven (emitted from inside `runScan`); two (`boot`, `shutdown`) are CLI-process-driven (emitted by the driving binary before / after the verb runs, fire-and-forget so `process.exit` is never blocked). Other events (per-node `scan.progress`, `model.delta`, `run.*`, `job.claimed`, `job.callback.received`) are deliberately NOT hookable: too verbose for a reactive surface, internal to the runner, or covered elsewhere. Declaring a trigger outside the curated set yields `invalid-manifest` at load time.
|
|
307
307
|
|
|
308
308
|
| Trigger | When it fires | Payload (key fields) | Hook scope |
|
|
309
309
|
|---|---|---|---|
|
|
310
|
+
| `boot` | Once per CLI process invocation, BEFORE the verb routes. The dispatcher AWAITS subscribed hooks so anything they print lands above the verb's output (the `core/update-check` banner relies on this); a slow hook therefore delays the first verb paint. The dispatcher catches every hook error so a buggy hook never prevents the verb from running — it can only delay it. Use sparingly. | `argv: string[]` (the routed argv slice the CLI is about to parse). | Boot-time output that must appear above the verb (the `core/update-check` banner), pre-flight checks, telemetry warm-up. |
|
|
310
311
|
| `scan.started` | Once at the start of every `sm scan` invocation. | `roots: string[]`. | Pre-scan setup (cache warm-up, telemetry init). |
|
|
311
312
|
| `scan.completed` | Once at the end of every `sm scan` invocation. | `stats: { filesWalked, nodesCount, linksCount, issuesCount, durationMs }`. | Post-scan reaction (Slack notification, CI gate, summary). |
|
|
312
313
|
| `extractor.completed` | Once per registered Extractor, after the full walk completes. Aggregated, NOT per-node. | `extractorId: string` (qualified). | Per-Extractor metrics, audit. |
|
|
313
|
-
| `
|
|
314
|
+
| `analyzer.completed` | Once per Analyzer, after every issue has been validated. | `analyzerId: string` (qualified). | Per-Analyzer alerting, downstream tooling. |
|
|
314
315
|
| `action.completed` | Once per Action invocation, after the report has been recorded. | `actionId: string` (qualified), `node`, `jobResult`. | Per-Action notification, integration glue. |
|
|
315
316
|
| `job.spawning` | Pre-spawn of a runner subprocess (job subsystem; Step 10). | `jobId`, `actionId`, spawn metadata. | Pre-flight checks, audit logging. |
|
|
316
317
|
| `job.spawning`, `job.completed`, `job.failed` | The three job-lifecycle hookables; same payload shapes as the [`job-events.md`](./job-events.md) entries of the same name. | See [`job-events.md` §Event catalog](./job-events.md#event-catalog). | Most common Hook surface (notifications, retries, billing). |
|
|
318
|
+
| `shutdown` | Once per CLI process invocation, AFTER the verb returns its exit code and BEFORE `process.exit`. The dispatcher awaits subscribed hooks so they finish before the process terminates, but every hook MUST be fast (the user already saw the verb's output and is waiting for the prompt back). The dispatcher catches every hook error so a buggy hook never alters the verb's exit code; it can only delay the exit. | `exitCode: number` (the verb's resolved exit code, `0..5`). | Cleanup, post-run telemetry, the `core/update-check` banner. |
|
|
317
319
|
|
|
318
320
|
A hook MAY narrow further with an optional declarative `filter` map: keys are payload field paths (top-level only in v0.x); values are the literal expected match. The dispatcher walks `event.data` for each declared key and short-circuits the invocation when any value disagrees. Examples:
|
|
319
321
|
|
|
@@ -330,7 +332,7 @@ A hook MAY narrow further with an optional declarative `filter` map: keys are pa
|
|
|
330
332
|
|
|
331
333
|
Hooks introduce no new persisted state and do NOT participate in the deterministic scan cache (A.9). A scan that re-runs against an unchanged corpus dispatches `scan.started` / `scan.completed` exactly as before; subscribed hooks fire on every scan regardless of cache hit / miss. Hooks that need cache-aware behaviour MUST inspect their own state via `ctx.store` (declared in their plugin's manifest).
|
|
332
334
|
|
|
333
|
-
### Contract
|
|
335
|
+
### Contract analyzers
|
|
334
336
|
|
|
335
337
|
1. An extension declares its kind in its module export and its manifest. Kind mismatch → load-error.
|
|
336
338
|
2. An extension MAY declare `preconditions` — predicates that must be satisfied for the extension to be offered (e.g., `action.requires: ["kind=skill"]`).
|
|
@@ -341,11 +343,11 @@ Hooks introduce no new persisted state and do NOT participate in the determinist
|
|
|
341
343
|
### Locality
|
|
342
344
|
|
|
343
345
|
- **Drop-in**: extensions live inside plugins, discovered at boot from `.skill-map/plugins/<id>/` and `~/.skill-map/plugins/<id>/`.
|
|
344
|
-
- **Built-in**: the reference impl bundles a default extension set (one Provider, four extractors, five
|
|
346
|
+
- **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.
|
|
345
347
|
|
|
346
348
|
---
|
|
347
349
|
|
|
348
|
-
## Dependency
|
|
350
|
+
## Dependency analyzers
|
|
349
351
|
|
|
350
352
|
The following imports are NORMATIVELY FORBIDDEN:
|
|
351
353
|
|
|
@@ -397,7 +399,7 @@ Alternative implementations MAY use workspaces, separate packages, or a compiled
|
|
|
397
399
|
|
|
398
400
|
---
|
|
399
401
|
|
|
400
|
-
## Driving-adapter peer
|
|
402
|
+
## Driving-adapter peer analyzer
|
|
401
403
|
|
|
402
404
|
The CLI, Server, and Skill driving adapters are **peers**. None depends on another.
|
|
403
405
|
|
|
@@ -407,24 +409,24 @@ The CLI, Server, and Skill driving adapters are **peers**. None depends on anoth
|
|
|
407
409
|
|
|
408
410
|
All three consume the same kernel API. Any use case a driving adapter needs MUST be available as a kernel function — if it isn't, the gap is a kernel bug, not a driving-adapter workaround.
|
|
409
411
|
|
|
410
|
-
This is what makes "CLI-first" a coherent
|
|
412
|
+
This is what makes "CLI-first" a coherent analyzer: every CLI verb is a kernel function call. The UI does not reimplement business logic; it calls the same functions.
|
|
411
413
|
|
|
412
414
|
---
|
|
413
415
|
|
|
414
416
|
## Annotation system
|
|
415
417
|
|
|
416
|
-
Skill-map's own metadata layer (versioning, supersession, provenance, taxonomy, docs) lives in **co-located YAML sidecars** with extension `.sm`, in the same directory as the markdown node they annotate. Vendor files (`.claude/agents/foo.md`, `.cursor/
|
|
418
|
+
Skill-map's own metadata layer (versioning, supersession, provenance, taxonomy, docs) lives in **co-located YAML sidecars** with extension `.sm`, in the same directory as the markdown node they annotate. Vendor files (`.claude/agents/foo.md`, `.cursor/analyzers/bar.mdc`, …) stay untouched; the sidecar (`foo.sm` / `bar.sm`) IS skill-map's "annotations file" for that node — every key under it is, conceptually, an annotation. The YAML root organizes those annotations into structural blocks (identity, the curated annotations catalog, audit timestamps, settings, plugin namespaces); the file as a whole is the annotation surface.
|
|
417
419
|
|
|
418
420
|
Two schemas describe the wire shape:
|
|
419
421
|
|
|
420
422
|
- [`schemas/sidecar.schema.json`](./schemas/sidecar.schema.json) — root shape with reserved blocks `identity` (anchor + drift hashes), `annotations` (the conventional catalog), `settings` (reserved), `audit` (write trail), plus opt-in `<plugin-id>:` namespacing.
|
|
421
|
-
- [`schemas/annotations.schema.json`](./schemas/annotations.schema.json) — curated 13-field catalog: versioning + supersession (`version`, `stability`, `supersedes`, `supersededBy`, `requires`, `conflictsWith`, `related`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), docs (`docsUrl`). The activity timestamp lives in the reserved `audit:` block (`audit.lastBumpedAt`), not in `annotations:`. `additionalProperties: true` so plugins or users add custom keys without coordination; the built-in `unknown-field`
|
|
423
|
+
- [`schemas/annotations.schema.json`](./schemas/annotations.schema.json) — curated 13-field catalog: versioning + supersession (`version`, `stability`, `supersedes`, `supersededBy`, `requires`, `conflictsWith`, `related`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), docs (`docsUrl`). The activity timestamp lives in the reserved `audit:` block (`audit.lastBumpedAt`), not in `annotations:`. `additionalProperties: true` so plugins or users add custom keys without coordination; the built-in `unknown-field` analyzer warns on truly unrecognized keys (typo guard).
|
|
422
424
|
|
|
423
425
|
### Identity and drift
|
|
424
426
|
|
|
425
427
|
`identity` carries `path` (scope-root-relative, matches the canonical Node identifier in [`schemas/node.schema.json`](./schemas/node.schema.json)) plus `bodyHash` and `frontmatterHash`. Both hashes are sha256 over the kernel's canonical form of the markdown body (post-frontmatter bytes) and frontmatter (YAML re-emitted via `js-yaml dump` with `sortKeys: true`, `lineWidth: -1`, `noRefs: true`, `noCompatMode: true`); each sidecar captures the values the kernel saw at the moment it was last written.
|
|
426
428
|
|
|
427
|
-
At scan time the kernel re-computes the live hashes and compares against the stored ones. Mismatch in either is **drift**, surfaced via the built-in `annotation-stale`
|
|
429
|
+
At scan time the kernel re-computes the live hashes and compares against the stored ones. Mismatch in either is **drift**, surfaced via the built-in `annotation-stale` analyzer (severity `warning`, never blocking — soft mode by design). A `.sm` whose `identity.path` no longer points at an existing `.md` is **orphan**, surfaced via the built-in `annotation-orphan` analyzer (also `warning`). Drift state is **derived**, never stored — pure function over existing data, no flag to drift between flag and reality.
|
|
428
430
|
|
|
429
431
|
### Bump model
|
|
430
432
|
|
|
@@ -487,7 +489,7 @@ The **format** (YAML, extension `.sm`, not `.md.sm`) is stable as of spec v1.0.0
|
|
|
487
489
|
|
|
488
490
|
The **reserved block names** (`for`, `annotations`, `settings`, `audit`) are stable as of spec v1.0.0. Adding a new reserved block is a minor bump; renaming or removing one is a major bump.
|
|
489
491
|
|
|
490
|
-
The **identity contract** (`identity.path` + `identity.bodyHash` + `identity.frontmatterHash`, with `resolvedAs` optional) is stable as of spec v1.0.0. Changing the hash algorithm or canonicalization
|
|
492
|
+
The **identity contract** (`identity.path` + `identity.bodyHash` + `identity.frontmatterHash`, with `resolvedAs` optional) is stable as of spec v1.0.0. Changing the hash algorithm or canonicalization analyzer is a major bump.
|
|
491
493
|
|
|
492
494
|
The **bump field set** (the four `audit` fields `lastBumpedAt` / `lastBumpedBy` / `createdAt` / `createdBy`) is stable as of spec v1.0.0. Adding new audit fields is a minor bump; removing or renaming is a major bump. The audit block is `additionalProperties: true` so plugins or future Actions MAY ride additional keys opaquely.
|
|
493
495
|
|
|
@@ -505,14 +507,14 @@ Sibling system to the annotation contributions above. Both let plugins extend th
|
|
|
505
507
|
|---|---|---|
|
|
506
508
|
| **Data lives in** | the user-facing sidecar `.sm` file | the kernel-managed `scan_contributions` table |
|
|
507
509
|
| **Author intent** | extend the metadata catalog | surface per-node data in the UI |
|
|
508
|
-
| **Plugin author writes** | inline JSON Schema for the value | `
|
|
510
|
+
| **Plugin author writes** | inline JSON Schema for the value | `slot` name from a closed catalog |
|
|
509
511
|
| **Validation** | AJV at sidecar-write time | AJV at `ctx.emitContribution(...)` time |
|
|
510
512
|
| **Lifecycle** | persists across scans (file-on-disk) | re-emitted on every scan (table cleared per node) |
|
|
511
|
-
| **Surfaces in** | sidecar consumers + `<sm-plugin-contributions>` panel | renderer per
|
|
513
|
+
| **Surfaces in** | sidecar consumers + `<sm-plugin-contributions>` panel | fixed renderer per slot, mounted at exactly the slot the author declared |
|
|
512
514
|
|
|
513
515
|
Two schemas describe the wire shape:
|
|
514
516
|
|
|
515
|
-
- [`schemas/view-
|
|
517
|
+
- [`schemas/view-slots.schema.json`](./schemas/view-slots.schema.json) — closed catalog: 15 slot names + the `IViewContribution` manifest declaration shape + per-slot payload schemas (in `$defs/payloads`) the kernel uses to validate emit-time payloads.
|
|
516
518
|
- [`schemas/input-types.schema.json`](./schemas/input-types.schema.json) — closed catalog: 10 input-type names + the `ISettingDeclaration` manifest declaration shape (discriminated by `type`).
|
|
517
519
|
|
|
518
520
|
### Identity
|
|
@@ -521,18 +523,18 @@ Each view contribution is identified by the qualified id `<pluginId>/<extensionI
|
|
|
521
523
|
|
|
522
524
|
### Manifest
|
|
523
525
|
|
|
524
|
-
Each entry picks a `
|
|
526
|
+
Each entry picks a `slot` name from the closed catalog and supplies presentation tuning. The slot fixes both the renderer and the payload shape — there is no separate "contract" abstraction:
|
|
525
527
|
|
|
526
528
|
```jsonc
|
|
527
529
|
{
|
|
528
530
|
"viewContributions": {
|
|
529
531
|
"breakdown": {
|
|
530
|
-
"
|
|
532
|
+
"slot": "inspector.body.panel.breakdown",
|
|
531
533
|
"label": "Keyword hits",
|
|
532
534
|
"emptyText": "No matches."
|
|
533
535
|
},
|
|
534
536
|
"total": {
|
|
535
|
-
"
|
|
537
|
+
"slot": "card.footer.left.counter",
|
|
536
538
|
"icon": "🔍",
|
|
537
539
|
"label": "kw",
|
|
538
540
|
"emitWhenEmpty": false
|
|
@@ -541,7 +543,7 @@ Each entry picks a `contract` name from the closed catalog and supplies presenta
|
|
|
541
543
|
}
|
|
542
544
|
```
|
|
543
545
|
|
|
544
|
-
The plugin author
|
|
546
|
+
The plugin author picks ONE slot per contribution; that single decision determines where the data renders, what payload shape `ctx.emitContribution(...)` must produce, and which Angular component draws it. Six manifest fields per contribution + the slot catalog page is the entire mental model. See [`plugin-author-guide.md`](./plugin-author-guide.md) §View contributions for worked examples.
|
|
545
547
|
|
|
546
548
|
### Settings
|
|
547
549
|
|
|
@@ -551,9 +553,9 @@ Settings are read once at extractor invocation; changing a setting requires `sm
|
|
|
551
553
|
|
|
552
554
|
### Runtime catalog
|
|
553
555
|
|
|
554
|
-
The kernel exposes a runtime catalog (`Kernel.getRegisteredViewContributions()`) listing every plugin-contributed view contribution with its `pluginId`, `extensionId`, `contributionId`, `
|
|
556
|
+
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` map, AJV-validated, and frozen — same lifecycle as `getRegisteredAnnotationKeys()`.
|
|
555
557
|
|
|
556
|
-
|
|
558
|
+
Analyzers see the catalog through `IAnalyzerContext.viewContributions` so cross-cutting checks (`core/unknown-slot`, `core/contribution-orphan`) can reason about emissions.
|
|
557
559
|
|
|
558
560
|
### Emit path
|
|
559
561
|
|
|
@@ -563,16 +565,16 @@ Extensions emit per-node payloads via context callbacks:
|
|
|
563
565
|
// Extractors (per-node walk)
|
|
564
566
|
ctx.emitContribution(contributionId, payload);
|
|
565
567
|
|
|
566
|
-
//
|
|
567
|
-
// because the
|
|
568
|
+
// Analyzers (post-merge graph) — same payload contract, explicit nodePath
|
|
569
|
+
// because the analyzer sees every node at once
|
|
568
570
|
ctx.emitContribution(nodePath, contributionId, payload);
|
|
569
571
|
```
|
|
570
572
|
|
|
571
|
-
Parallel to `ctx.emitLink(link)`. The kernel buffers the emission, validates the payload against the
|
|
573
|
+
Parallel to `ctx.emitLink(link)`. The kernel buffers the emission, validates the payload against the slot's payload schema in `$defs/payloads/<slot>` (AJV-compiled at boot), and persists the row to `scan_contributions` during `persistScanResult`. Off-shape payloads emit an `extension.error` event and drop silently — same posture as `emitLink` rejecting off-`emitsLinkKinds` links. Both Extractor and Analyzer emissions land in the same `scan_contributions` rows; the row's `extension_id` records which kind of extension produced it.
|
|
572
574
|
|
|
573
|
-
The Extractor-emit signature binds `nodePath` implicitly (the extractor runs per-node, with `ctx.node.path` available as the only sensible target). The
|
|
575
|
+
The Extractor-emit signature binds `nodePath` implicitly (the extractor runs per-node, with `ctx.node.path` available as the only sensible target). The Analyzer-emit signature requires the analyzer to declare the target node explicitly because Analyzers see the full graph at once and may emit for any subset of nodes — the canonical use case is a analyzer that derives per-node values from cross-graph aggregations (`core/link-counts` projects `linksOutCount` / `linksInCount` this way).
|
|
574
576
|
|
|
575
|
-
|
|
577
|
+
Analyzers MAY also emit scope-level contributions via `IAnalyzerContext.emitScopeContribution(contributionId, payload)` (only slots whose schema permits scope-level emission, today only `topbar.actions.indicator`). That signature is reserved in the spec; the runtime callback lands when the first scope-level adopter arrives.
|
|
576
578
|
|
|
577
579
|
### Persistence
|
|
578
580
|
|
|
@@ -584,21 +586,26 @@ A new table `scan_contributions` (see [`db-schema.md`](./db-schema.md) §scan_co
|
|
|
584
586
|
| `extension_id` | TEXT | extension id within the plugin |
|
|
585
587
|
| `node_path` | TEXT | scope-relative path |
|
|
586
588
|
| `contribution_id` | TEXT | manifest Record key |
|
|
587
|
-
| `
|
|
588
|
-
| `payload_json` | TEXT | JSON-serialized payload (already validated against
|
|
589
|
+
| `slot` | TEXT | denormalized slot name (`view-slots.schema.json#/$defs/SlotName`) |
|
|
590
|
+
| `payload_json` | TEXT | JSON-serialized payload (already validated against the slot's payload schema) |
|
|
589
591
|
| `emitted_at` | INTEGER | unix epoch ms |
|
|
590
592
|
|
|
591
593
|
PK `(plugin_id, extension_id, node_path, contribution_id)` so re-emission upserts. Index on `node_path` (inspector lazy-fetch + orphan sweep) and on `plugin_id` (catalog sweep + `purgeByPlugin`).
|
|
592
594
|
|
|
593
|
-
**NOT pure replace-all** (the way `scan_links` / `scan_issues` are). The watcher's cached pass leaves the contributions buffer empty for cached nodes — the orchestrator skips `extract()` when the per-(node, extractor) cache hits, so no `emitContribution` fires. A naive wipe-all would silently drop the prior valid rows on every watcher boot. The persist runs
|
|
595
|
+
**NOT pure replace-all** (the way `scan_links` / `scan_issues` are). The watcher's cached pass leaves the contributions buffer empty for cached nodes — the orchestrator skips `extract()` when the per-(node, extractor) cache hits, so no `emitContribution` fires. A naive wipe-all would silently drop the prior valid rows on every watcher boot. The persist runs four passes inside the same transaction:
|
|
594
596
|
|
|
595
597
|
1. **Orphan sweep** — drops every row whose `node_path` is NOT in the current live node set. Disappeared nodes lose their contributions.
|
|
596
598
|
2. **Catalog sweep** — drops every row whose qualified id `(pluginId, extensionId, contributionId)` is NOT in the registered runtime catalog (uninstalled plugins, disabled bundles, removed contributions).
|
|
597
|
-
3. **
|
|
599
|
+
3. **Per-tuple sweep** — for every `(pluginId, extensionId, nodePath)` tuple where the extension actually RAN against that node in this scan (extractor cache miss, OR analyzer — analyzers always run), drop any row carrying that triple whose `contribution_id` is NOT present in the buffer for that triple. This catches the "extractor used to emit, now does not" case (e.g. a node body change that removes the trigger). Cached-extractor tuples are NOT in the set, so their rows survive untouched.
|
|
600
|
+
4. **Upsert** — `INSERT ... ON CONFLICT DO UPDATE SET payload_json = excluded.payload_json, slot = excluded.slot` for every row in the buffer. PK conflict refreshes payload + `slot` + `emitted_at`.
|
|
598
601
|
|
|
599
|
-
Cached nodes' rows survive untouched (still in the live set, still in the catalog, no buffer hit). The next time the body changes, the orchestrator re-runs the extractor,
|
|
602
|
+
Cached nodes' rows survive untouched (still in the live set, still in the catalog, the (plugin, extension, node) tuple is not in the freshly-run set, no buffer hit). The next time the body changes, the orchestrator re-runs the extractor, the tuple lands in the freshly-run set, and either the upsert refreshes the row OR the per-tuple sweep drops it (when the extractor no longer emits for that node).
|
|
600
603
|
|
|
601
|
-
Empty buffer + non-empty live set = the cached-pass case (no-op). Empty buffer + empty live set = legacy fallback to wipe-all (cold start).
|
|
604
|
+
Empty buffer + non-empty live set = the cached-pass case (no-op). Empty buffer + empty live set = legacy fallback to wipe-all (cold start). Three `IPersistOptions` fields control which sweeps activate — absent values fall back to legacy behaviour (sweep skipped) so older callers keep working:
|
|
605
|
+
|
|
606
|
+
- `livePaths?: ReadonlySet<string>` — gates the orphan sweep (1).
|
|
607
|
+
- `registeredContributionKeys?: ReadonlySet<string>` — gates the catalog sweep (2). Element format: qualified id `<pluginId>/<extensionId>/<contributionId>`.
|
|
608
|
+
- `freshlyRunTuples?: ReadonlySet<string>` — gates the per-tuple sweep (3). Element format: `<pluginId>/<extensionId>/<nodePath>` (no contribution-id segment — the sweep operates at the (plugin, extension, node) level and inspects the buffer to decide which contribution-ids survive).
|
|
602
609
|
|
|
603
610
|
Cold-start posture: the BFF endpoints below return empty arrays when the table is missing (mirror of the `tryWithSqlite` graceful-null pattern used by `routes/nodes.ts`); never a 500.
|
|
604
611
|
|
|
@@ -621,35 +628,35 @@ Plus per-node embedding on node responses:
|
|
|
621
628
|
|
|
622
629
|
### Isolation
|
|
623
630
|
|
|
624
|
-
View contributions extend the existing plugin-isolation model (see [`plugin-kv-api.md`](./plugin-kv-api.md) §Honest note on isolation) with six
|
|
631
|
+
View contributions extend the existing plugin-isolation model (see [`plugin-kv-api.md`](./plugin-kv-api.md) §Honest note on isolation) with six analyzers specific to UI rendering:
|
|
625
632
|
|
|
626
|
-
1. **No raw DOM from plugin** — contributions are typed data only; the UI renders them via a closed catalog of Angular components mapped from
|
|
633
|
+
1. **No raw DOM from plugin** — contributions are typed data only; the UI renders them via a closed catalog of Angular components mapped from slot id.
|
|
627
634
|
2. **CSS scoping by Angular view encapsulation** — plugin does not write CSS; per-plugin tinting is sourced from a kernel-managed palette derived from `pluginId`.
|
|
628
635
|
3. **Data path namespaced and BFF-enforced** — `GET /api/contributions/:pluginId/:extensionId/:contributionId?path=...` rejects cross-plugin reads at the route level (the qualified id triple is the URL shape).
|
|
629
636
|
4. **Click actions are typed kernel verb dispatches** — a button rendered from a contribution invokes a kernel verb by qualified id; no arbitrary URLs / effects.
|
|
630
|
-
5. **AJV at three layers** — manifest at load (rejects unknown `
|
|
631
|
-
6. **Renderer attr-sanitization** — the UI's renderer components MUST NOT bind contribution data to `[innerHTML]`, `[style]`, `[src]`, `[href]`, or any DomSanitizer DANGEROUS_ATTR. Lint-enforced in the UI workspace; documented in [`context/view-
|
|
637
|
+
5. **AJV at three layers** — manifest at load (rejects unknown `slot` names with `invalid-manifest`), payload at emit (rejects off-shape payloads with `extension.error`), envelope at BFF response.
|
|
638
|
+
6. **Renderer attr-sanitization** — the UI's renderer components MUST NOT bind contribution data to `[innerHTML]`, `[style]`, `[src]`, `[href]`, or any DomSanitizer DANGEROUS_ATTR. Lint-enforced in the UI workspace; documented in [`context/view-slots.md`](../context/view-slots.md).
|
|
632
639
|
|
|
633
640
|
Same honest-note posture as [`plugin-kv-api.md`](./plugin-kv-api.md): isolated against accidents, not hostile code, until worker-thread / iframe sandbox post-v1.0.
|
|
634
641
|
|
|
635
|
-
### Soft-warning
|
|
642
|
+
### Soft-warning analyzers
|
|
636
643
|
|
|
637
644
|
Two built-ins ship with the system to cover catalog evolution and rename edge cases:
|
|
638
645
|
|
|
639
|
-
- **`core/unknown-
|
|
646
|
+
- **`core/unknown-slot`** — walks every loaded plugin's `viewContributions[*].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.
|
|
640
647
|
- **`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).
|
|
641
648
|
|
|
642
649
|
### Catalog versioning
|
|
643
650
|
|
|
644
|
-
The catalog of
|
|
651
|
+
The catalog of slots and input-types evolves on its own cadence, independent of the spec version. Plugin manifests carry an optional `catalogCompat: string` (semver range) field at the root, parallel to `specCompat`. The kernel checks `semver.satisfies(catalogVersion, plugin.catalogCompat)` at load. Mismatch surfaces as `incompatible-catalog` plugin status (new entry in the load-status enum). Resolution: `sm plugins upgrade <id>` runs registered migrations from a closed kernel-side registry of `{ from, to, transform }` triples; auto-migration impossible → CLI exit ≠ 0 + UI dialog naming the offending slot / input-type.
|
|
645
652
|
|
|
646
|
-
Pre-1.0 versioning
|
|
653
|
+
Pre-1.0 versioning analyzer (per [`AGENTS.md`](../AGENTS.md)): catalog breaking changes ship as minor bumps while in `0.y.z`; the first `1.0.0` is a deliberate stabilization moment, not a side-effect.
|
|
647
654
|
|
|
648
655
|
### Stability
|
|
649
656
|
|
|
650
|
-
The **closed catalog of view
|
|
657
|
+
The **closed catalog of view slots** is stable as of the v1 of this system: adding a new slot is a minor bump; renaming or removing one is a catalog-major bump and triggers `sm plugins upgrade` migration of every dependent plugin.
|
|
651
658
|
|
|
652
|
-
The **`IViewContribution` manifest shape** (six fields: `
|
|
659
|
+
The **`IViewContribution` manifest shape** (six fields: `slot`, `label?`, `tooltip?`, `icon?`, `emptyText?`, `emitWhenEmpty?`) is stable. Adding a new optional field is a minor bump; making a field required or removing one is a catalog-major bump.
|
|
653
660
|
|
|
654
661
|
The **closed catalog of input-types** is stable on the same model: adding minor, renaming/removing major.
|
|
655
662
|
|
|
@@ -657,7 +664,7 @@ The **`ctx.emitContribution(id, payload)` signature** is stable. Adding new cont
|
|
|
657
664
|
|
|
658
665
|
The **persistence shape** (`scan_contributions` columns) is stable; column additions are minor bumps. Renames or removals trigger a kernel migration.
|
|
659
666
|
|
|
660
|
-
The **slot catalog ownership**
|
|
667
|
+
The **slot catalog ownership** is now spec-level (kernel + spec own the catalog jointly); the UI implementation may rearrange visual placement WITHOUT renaming a slot — the slot id is the public handle, the visual surface beneath it can evolve. Different driving adapters (UI, future TUI, `sm show --json`) MUST honour the same slot vocabulary; surface-level rendering policy stays adapter-specific (e.g. a TUI may render `card.title.right` as a prefix glyph instead of a right-side marker).
|
|
661
668
|
|
|
662
669
|
The **isolation honest-note** (accidents, not hostile code) is the same posture as [`plugin-kv-api.md`](./plugin-kv-api.md) and migrates together when worker-thread / iframe sandbox lands post-v1.0.
|
|
663
670
|
|
|
@@ -680,12 +687,12 @@ The **isolation honest-note** (accidents, not hostile code) is the same posture
|
|
|
680
687
|
|
|
681
688
|
The **port list** is stable as of spec v1.0.0. Adding a sixth port is a major bump.
|
|
682
689
|
|
|
683
|
-
The **extension kind list** (6 kinds: Provider, Extractor,
|
|
690
|
+
The **extension kind list** (6 kinds: Provider, Extractor, Analyzer, Action, Formatter, Hook) is stable as of spec v1.0.0. Adding a seventh kind is a major bump. Removing or renaming a kind is a major bump.
|
|
684
691
|
|
|
685
|
-
The **Hook curated trigger set** (
|
|
692
|
+
The **Hook curated trigger set** (ten events: `boot`, `scan.started`, `scan.completed`, `extractor.completed`, `analyzer.completed`, `action.completed`, `job.spawning`, `job.completed`, `job.failed`, `shutdown`) is stable as of spec v1.0.0. Adding an eleventh trigger is a minor bump; removing or renaming any of the ten is a major bump.
|
|
686
693
|
|
|
687
694
|
The **execution modes** (`deterministic` / `probabilistic`) and the per-kind mode capability matrix above are stable as of spec v1.0.0. Adding a third mode is a major bump. Renaming or repurposing the mode enum values is a major bump. Pre-1.0, narrowing a kind from dual-mode to single-mode is permitted as a minor bump (Extractor went from `deterministic / probabilistic` to `deterministic-only` in 0.X.0); post-1.0 the same change would be major.
|
|
688
695
|
|
|
689
|
-
The **dependency
|
|
696
|
+
The **dependency analyzers** above are stable as of spec v1.0.0. Relaxing any is a major bump; tightening (forbidding an allowed import) is a minor bump.
|
|
690
697
|
|
|
691
698
|
The **Extractor · trigger normalization** pipeline (six steps, in order) is stable from the next spec release. Adding a new step at the end is a minor bump; reordering, removing, or changing any existing step (including the character classes in step 4) is a major bump. Implementations that produce different `normalizedTrigger` output for equivalent input are non-conforming.
|
package/cli-contract.md
CHANGED
|
@@ -178,7 +178,7 @@ Consumers: docs generator, shell completion, Web UI form generation, IDE extensi
|
|
|
178
178
|
|---|---|
|
|
179
179
|
| `sm config list` | Effective config after layered merge. |
|
|
180
180
|
| `sm config get <key>` | Single value. |
|
|
181
|
-
| `sm config set <key> <value
|
|
181
|
+
| `sm config set <key> <value> [--yes]` | Write to user config (scope-aware: `-g` writes to global). Privacy-sensitive keys require `--yes` to confirm — see §Privacy-sensitive config below. |
|
|
182
182
|
| `sm config reset <key>` | Remove user override; revert to default or higher-scope value. |
|
|
183
183
|
| `sm config show <key> --source` | Reveals origin: `default` / `project` / `global` / `env` / `flag`. |
|
|
184
184
|
|
|
@@ -186,6 +186,16 @@ Config precedence (lowest → highest): library defaults → user config → env
|
|
|
186
186
|
|
|
187
187
|
Keys are dot-paths (`jobs.minimumTtlSeconds`, `scan.tokenize`). Unknown keys → exit 5.
|
|
188
188
|
|
|
189
|
+
#### Privacy-sensitive config
|
|
190
|
+
|
|
191
|
+
Keys whose value opens disk access OUTSIDE the project root (today: `scan.includeHome`, `scan.extraRoots`, `scan.referencePaths`) are gated behind `--yes` so the user never expands the scan surface by accident. The analyzer:
|
|
192
|
+
|
|
193
|
+
- `sm config set <privacy-key> <value>` (without `--yes`) — when the new value would expand the surface (toggling `includeHome` from `false`→`true`, or adding paths to `extraRoots` / `referencePaths` that resolve outside the project root) — exits with code `2` and prints the full list of paths the change would expose to stderr, suggesting `--yes` to confirm.
|
|
194
|
+
- `sm config set <privacy-key> <value> --yes` — proceeds with the write and prints the same list as a confirmation receipt.
|
|
195
|
+
- Writes that NARROW the surface (toggling `includeHome` from `true`→`false`, removing paths) do not require `--yes`.
|
|
196
|
+
|
|
197
|
+
The Settings UI's Project section enforces the same analyzer via a confirm dialog that enumerates the paths.
|
|
198
|
+
|
|
189
199
|
---
|
|
190
200
|
|
|
191
201
|
### Scan
|
|
@@ -196,6 +206,7 @@ Keys are dot-paths (`jobs.minimumTtlSeconds`, `scan.tokenize`). Unknown keys →
|
|
|
196
206
|
| `sm scan -n <node.path>` | Partial scan: one node. |
|
|
197
207
|
| `sm scan --changed` | Incremental: only files changed since last scan (mtime heuristic). |
|
|
198
208
|
| `sm scan --watch` | Long-running: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`. |
|
|
209
|
+
| `sm scan -g` / `sm scan --global` | Global scan. Roots default to every active Provider's `explorationDir` resolved against `~` (typically `~/.claude`, `~/.gemini`, `~/.agents`); the cwd is NOT included. Config + DB resolve from the global scope (`~/.skill-map/...`). Mutually exclusive with explicit positional roots — passing both is rejected with exit `2`. |
|
|
199
210
|
| `sm scan compare-with <dump> [roots...]` | Delta report: run a fresh scan in memory and compare against the saved `ScanResult` dump at `<dump>`. Read-only — does not modify the DB. Exit `0` on empty delta, `1` on any drift, `2` on operational error (missing or malformed dump, schema violation). |
|
|
200
211
|
| `sm watch [roots...]` | Long-running watcher. Same semantics as `sm scan --watch`, exposed as a top-level verb because the watcher is a loop, not a one-shot scan. |
|
|
201
212
|
| `sm refresh <node.path>` | Re-run Extractors against a single node and upsert their outputs into the universal enrichment layer (`node_enrichments`, see [`db-schema.md`](./db-schema.md#node_enrichments)). Extractors are deterministic-only — they run synchronously and persist. Exit `0` on success, `2` on failure, `5` if the node is not in the persisted scan. |
|
|
@@ -203,6 +214,15 @@ Keys are dot-paths (`jobs.minimumTtlSeconds`, `scan.tokenize`). Unknown keys →
|
|
|
203
214
|
|
|
204
215
|
`--json` output conforms to `schemas/scan-result.schema.json`. `sm watch` (and `sm scan --watch`) emit one ScanResult per batch — under `--json` this is an `ndjson` stream of ScanResult documents.
|
|
205
216
|
|
|
217
|
+
**Effective roots** (one-shot `sm scan`):
|
|
218
|
+
|
|
219
|
+
- `sm scan [roots...]`: when positional roots are given, they ARE the effective roots (verbatim). When omitted: the effective roots are `[cwd]` plus the appended sets below.
|
|
220
|
+
- `scan.includeHome === true` appends every active Provider's `explorationDir` resolved against `~` to the effective roots.
|
|
221
|
+
- `scan.extraRoots[]` is appended verbatim (entries starting with `~` resolve against the user home; relative entries resolve against the project root).
|
|
222
|
+
- `sm scan -g`: effective roots are the active Providers' `explorationDir` only; `scan.includeHome` and `scan.extraRoots` are ignored. The cwd is NOT included. Config + DB resolve from the global scope.
|
|
223
|
+
|
|
224
|
+
**Reference paths** (`scan.referencePaths[]`): walked in parallel by the scan to collect existing absolute paths into a side set. Files there are NOT parsed and NOT indexed as nodes; 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.
|
|
225
|
+
|
|
206
226
|
The watcher subscribes to the same roots that `sm scan` walks and respects `.skillmapignore` plus `config.ignore` exactly as the one-shot scan does. Filesystem events are grouped using `scan.watch.debounceMs` (default 300ms) before the watcher re-runs the incremental scan and persists. `SIGINT` / `SIGTERM` close the watcher cleanly. Exit code on clean shutdown is 0.
|
|
207
227
|
|
|
208
228
|
Exit: 0 on clean (or clean watcher shutdown), 1 if error-severity issues exist (one-shot scan only — the watcher does not flip exit code based on per-batch issues), 2 on operational error.
|
|
@@ -215,7 +235,7 @@ Exit: 0 on clean (or clean watcher shutdown), 1 if error-severity issues exist (
|
|
|
215
235
|
|---|---|
|
|
216
236
|
| `sm list [--kind <k>] [--issue] [--sort-by ...] [--limit N]` | Tabular listing. `--json` emits an array conforming to `node.schema.json`. |
|
|
217
237
|
| `sm show <node.path>` | Node detail: weight (bytes/tokens triple-split), frontmatter, links in/out, issues, findings, summary. `--json` emits a detail object with the raw link rows. Pretty output groups identical-shape links (same endpoint, kind, normalized trigger) onto one line and lists the union of extractor ids in a `sources:` field; the section header reports both the raw row count and the unique-after-grouping count, e.g. `Links out (12, 9 unique)`. Storage keeps one row per extractor (`scan_links` is unchanged) — the grouping is purely a read-time presentation choice. |
|
|
218
|
-
| `sm check [-n <node.path>] [--
|
|
238
|
+
| `sm check [-n <node.path>] [--analyzers <ids>] [--include-prob] [--async]` | Print all current issues. Equivalent to `sm scan --json \| jq '.issues'` but faster (reads from DB). `-n` restricts to issues whose `nodeIds` include the path; `--analyzers <ids>` accepts a comma-separated list of qualified or short analyzer ids and restricts the issue read accordingly. Default behaviour is deterministic-only (CI-safe, status quo). `--include-prob` is the opt-in flag for probabilistic Analyzer dispatch (spec § A.7): the verb loads the plugin runtime, finds Analyzers with `mode === 'probabilistic'` (filtered by `--analyzers` if set), and emits a stderr advisory naming the analyzer ids. Full prob dispatch requires the job subsystem (Step 10); until then `--include-prob` is a stub — prob analyzers never produce issues, never alter the exit code, and `--async` (reserved companion: returns job ids without waiting once jobs land) is a no-op the advisory simply mentions. The flag does NOT extend to `sm scan` or `sm list`. |
|
|
219
239
|
| `sm findings [--kind ...] [--since ...] [--threshold <n>]` | Probabilistic findings (injection, stale summaries, low confidence). `--json` emits an array of finding objects. |
|
|
220
240
|
| `sm graph [--format ascii\|mermaid\|dot]` | Render the full graph via the named formatter. |
|
|
221
241
|
| `sm export <query> --format json\|md\|mermaid` | Filtered export. Query syntax is implementation-defined pre-1.0. |
|
|
@@ -243,7 +263,7 @@ The built-in deterministic `core/bump` Action is the canonical write channel for
|
|
|
243
263
|
| `sm bump <node.path> [--force]` | Single-node bump. Wraps `core/bump`. Refuses on a fresh node (`{ ok: false, reason: 'fresh' }`, exit `2`) unless `--force` is passed; with `--force` on a fresh node the verb is a silent no-op (exit `0`, no stdout). On a stale node (or first-time creation) increments `annotations.version`, refreshes `for.{bodyHash, frontmatterHash}`, and stamps the audit block (`audit.lastBumpedAt` + `audit.lastBumpedBy: 'cli'`; on first creation also `audit.createdAt` + `audit.createdBy: 'cli'`). Exit `5` if the node is not in the persisted scan. `--json` emits the report shape declared by `bump-report.schema.json`. |
|
|
244
264
|
| `sm bump --pending [--staged] [--force]` | Batch bump. Walks every node whose sidecar overlay reports drift in `node.path` ASC order and bumps each. `--staged` runs `git add <sidecar-path>` after each successful bump so the new content lands in the same commit; `git add` failure degrades to a stderr warning, the batch keeps running. Empty stale set → exit `0` with a "nothing to do" advisory. `--json` envelope: `{ bumped, refused, skipped, errors[], elapsedMs }`. Exit `0` on a clean run; `1` when at least one per-node error landed in `errors[]`. **Git error matrix for `--staged`**: not inside a git repo (no `.git/` parent of `cwd`) → exit `5`; `git` binary not on PATH (spawn ENOENT) → exit `2`. Both checks run BEFORE any sidecar write so a misconfigured environment never produces partial state. |
|
|
245
265
|
| `sm sidecar refresh <node.path>` | Hash-only update on the sidecar. Refreshes `for.{bodyHash, frontmatterHash}` to match the live node WITHOUT bumping `annotations.version` and WITHOUT touching the audit block. Useful when the user knows a body change is editorial-only and doesn't want to spend a version increment. Distinct from the top-level `sm refresh` (which targets the enrichment layer at Step A.8) — different storage, different concept; the sub-namespace prefix prevents the collision. Exit `5` if the node has no sidecar or is not in the persisted scan. No-op on a fresh node (informational stderr, exit `0`). |
|
|
246
|
-
| `sm sidecar prune [--dry-run] [--yes]` | Delete orphan `.sm` files (sidecars whose accompanying `<basename>.md` does not exist on disk). Destructive — without `--dry-run` prompts for interactive confirmation listing every file to be deleted (per the §Dry-run
|
|
266
|
+
| `sm sidecar prune [--dry-run] [--yes]` | Delete orphan `.sm` files (sidecars whose accompanying `<basename>.md` does not exist on disk). Destructive — without `--dry-run` prompts for interactive confirmation listing every file to be deleted (per the §Dry-run analyzer for destructive verbs). `--yes` (alias `--force`) bypasses the prompt for non-interactive callers (CI, the pre-commit hook, scripts). With `--dry-run` reports what would be deleted without touching disk and never prompts. Different domain from `sm orphans` — that verb operates on the node graph (rename heuristic); this one operates on the filesystem layer. `--json` envelope: `{ deleted, wouldDelete, errors, items[], elapsedMs }`. Exit `1` when delete failures landed in `errors`. |
|
|
247
267
|
| `sm sidecar annotate <node.path> [--force]` | Pure scaffolding. Writes a minimal `.sm` next to the `.md` with the `identity:` block populated and an empty `annotations: {}` block, ready for editing. Refuses if the file exists; `--force` overwrites. The optional legacy-frontmatter migration helper (`--from-frontmatter`) is deferred — no released consumer demands it. |
|
|
248
268
|
| `sm hooks install pre-commit-bump [--dry-run]` | Install (or chain into) a git pre-commit hook that runs `sm bump --pending --staged` so any staged drift in `.sm` sidecars auto-bumps before the commit lands. Idempotent: re-running detects the skill-map marker and no-ops. When the repo already has a custom `pre-commit`, the verb appends the skill-map block to the existing file rather than replacing it. `--dry-run` prints the planned content with `--- target: <path> ---` markers and writes nothing. Exit `5` if no `.git/` parent is found at or above `cwd`; exit `2` on write failures or unknown hook flavours. |
|
|
249
269
|
|
|
@@ -321,7 +341,7 @@ The Hono BFF exposes the single-node bump flow over REST so the UI can drive the
|
|
|
321
341
|
}
|
|
322
342
|
```
|
|
323
343
|
|
|
324
|
-
Emission
|
|
344
|
+
Emission analyzers:
|
|
325
345
|
|
|
326
346
|
- Emitted on a successful 200 bump (stale → fresh, or first-time-create → fresh).
|
|
327
347
|
- **NOT** emitted on a force-on-fresh no-op 200 (nothing changed on disk).
|
|
@@ -432,7 +452,7 @@ See `db-schema.md` for the table catalog.
|
|
|
432
452
|
| `sm db dump [--tables ...]` | SQL dump. |
|
|
433
453
|
| `sm db migrate [--dry-run \| --status \| --to <n> \| --kernel-only \| --plugin <id> \| --no-backup]` | Migration controls. |
|
|
434
454
|
|
|
435
|
-
Destructive verbs (`reset --state`, `reset --hard`, `restore`) require interactive confirmation unless `--yes` (non-interactive mode for scripts) or `--force` (alias, kept for backward compatibility) is passed. `sm db reset` without a modifier is non-destructive and never prompts. **`--dry-run` short-circuits the confirmation prompt entirely** (per §Dry-run
|
|
455
|
+
Destructive verbs (`reset --state`, `reset --hard`, `restore`) require interactive confirmation unless `--yes` (non-interactive mode for scripts) or `--force` (alias, kept for backward compatibility) is passed. `sm db reset` without a modifier is non-destructive and never prompts. **`--dry-run` short-circuits the confirmation prompt entirely** (per §Dry-run analyzer: dry-run MUST NOT depend on `--yes` / `--force`).
|
|
436
456
|
|
|
437
457
|
---
|
|
438
458
|
|
|
@@ -467,12 +487,12 @@ The reference implementation ships a Hono BFF rooted at `src/server/`. One Node
|
|
|
467
487
|
| `GET /api/nodes?kind=&hasIssues=&path=&limit=&offset=` | implemented | `RestEnvelope` (`kind: 'nodes'`) — paginated, filtered list. Filters share the `kind=` / `has=issues` / `path=<glob>` grammar with `sm export`. `hasIssues=false` is a server-side post-filter (not representable in the kernel grammar). Pagination defaults `offset=0`, `limit=100`; max `limit=1000`. |
|
|
468
488
|
| `GET /api/nodes/:pathB64[?include=body]` | implemented | Single-node detail envelope: `{ schemaVersion, kind: 'node', item: Node, links: { incoming: Link[], outgoing: Link[] }, issues: Issue[] }`. `:pathB64` is base64url (RFC 4648 §5, no padding) of `node.path`. Missing node or malformed `pathB64` → 404 `not-found`. **`?include=body`** (Step 14.5.a) — opt-in flag that adds `item.body: string \| null` to the response. The body is read from disk on demand at request time (the kernel persists `bodyHash` only). `null` indicates the source file was missing / unreadable when the request landed (the watcher will re-emit `scan.completed` when it catches up). Without the flag, `item.body` is `undefined` and the handler does not touch the filesystem. |
|
|
469
489
|
| `GET /api/links?kind=&from=&to=` | implemented | `RestEnvelope` (`kind: 'links'`) — list of links. Filters: `kind` (CSV whitelist of `link.kind`), `from` (exact match on `link.source`), `to` (exact match on `link.target`). No pagination at v14.2. |
|
|
470
|
-
| `GET /api/issues?severity=&
|
|
490
|
+
| `GET /api/issues?severity=&analyzerId=&node=` | implemented | `RestEnvelope` (`kind: 'issues'`) — list of issues. Filters: `severity` (CSV from `error\|warn\|info`), `analyzerId` (CSV; qualified or short suffix per `sm check --analyzers`), `node` (filter to issues whose `nodeIds` includes the path). No pagination at v14.2. |
|
|
471
491
|
| `GET /api/graph?format=ascii\|json\|md` | implemented | formatter-rendered graph. `Content-Type` per format: `text/plain` (ascii), `application/json` (json), `text/markdown` (md / mermaid). Default `format=ascii`. Unknown format → 400 `bad-query`. |
|
|
472
492
|
| `GET /api/config` | implemented | `RestEnvelope` (`kind: 'config'`) — merged effective config (defaults → user → user-local → project → project-local → override). |
|
|
473
|
-
| `GET /api/plugins` | implemented | `RestEnvelope` (`kind: 'plugins'`) — list of installed plugins (built-in + drop-in) with status. Item shape: `{ id, version, kinds, status, reason, source: 'built-in'\|'project'\|'global', granularity: 'bundle'\|'extension', description?: string, extensions?: Array<{ id, kind, version, enabled, description?: string }> }`. The `granularity` field reflects the manifest declaration (built-ins: hardcoded per `built-in-plugins/built-ins.ts`; drop-ins: from `plugin.json#/granularity`, default `'bundle'`). The `description` field on the bundle item carries the manifest-declared description (built-ins: hardcoded on `IBuiltInBundle`; drop-ins: `plugin.json#/description`); each `extensions[]` entry carries its extension manifest's `description` per `IExtensionBase` (`extensions/base.schema.json#/properties/description`). The SPA's Settings list renders the descriptions as muted secondary text and includes them in its substring-search index alongside the ids. The `extensions` array is present **only** when `granularity === 'extension'` AND the plugin loaded successfully; each entry's `enabled` reflects the per-extension override resolution (DB > settings.json > installed default). For `granularity: 'bundle'` plugins the array is omitted (the bundle is the only toggle-able key). |
|
|
474
|
-
| `PATCH /api/plugins/:id` | implemented | Toggle one plugin's user override. `:id` MUST be a top-level bundle id; qualified-id form (`bundle/extension`) is the sibling route below. Body `{ enabled: boolean }` (JSON). Persists to `config_plugins` via `IConfigPluginsPort.set` — same path the CLI's `sm plugins enable\|disable` uses. Response is the canonical `{ id, version, kinds, status, reason, source }` row for the affected plugin (post-write `status` reflects the new override resolution). **Granularity** — rejected with 400 `bad-query` when the target bundle declares `granularity: 'extension'` (only the qualified-id form is toggle-able for those). **Restart required** — the loaded plugin runtime is boot-cached; the new value applies on the next `sm scan` or `sm serve` restart. The endpoint does NOT broadcast a WS event today. |
|
|
475
|
-
| `PATCH /api/plugins/:bundleId/extensions/:extensionId` | implemented | Qualified-id form for `granularity: 'extension'` bundles (today: `core` + any user plugin that opts in). Body `{ enabled: boolean }`. Both segments are URL-path-segment-encoded (no slash inside `:bundleId` or `:extensionId`). Rejected with 400 `bad-query` when the target bundle declares `granularity: 'bundle'` (use the sibling route above). Same persistence + restart-required semantics as the bundle form. |
|
|
493
|
+
| `GET /api/plugins` | implemented | `RestEnvelope` (`kind: 'plugins'`) — list of installed plugins (built-in + drop-in) with status. Item shape: `{ id, version, kinds, status, reason, source: 'built-in'\|'project'\|'global', granularity: 'bundle'\|'extension', description?: string, locked?: boolean, extensions?: Array<{ id, kind, version, enabled, description?: string, locked?: boolean }> }`. The `granularity` field reflects the manifest declaration (built-ins: hardcoded per `built-in-plugins/built-ins.ts`; drop-ins: from `plugin.json#/granularity`, default `'bundle'`). The `description` field on the bundle item carries the manifest-declared description (built-ins: hardcoded on `IBuiltInBundle`; drop-ins: `plugin.json#/description`); each `extensions[]` entry carries its extension manifest's `description` per `IExtensionBase` (`extensions/base.schema.json#/properties/description`). The SPA's Settings list renders the descriptions as muted secondary text and includes them in its substring-search index alongside the ids. The `extensions` array is present **only** when `granularity === 'extension'` AND the plugin loaded successfully; each entry's `enabled` reflects the per-extension override resolution (DB > settings.json > installed default). For `granularity: 'bundle'` plugins the array is omitted (the bundle is the only toggle-able key). The optional `locked: true` flag is stamped when the bundle id (or qualified extension id) appears in the host's lock-list (`src/server/locked-plugins.ts`); locked items render the toggle disabled in the SPA and any `PATCH` against them returns `403 locked`. The flag is omitted when false. |
|
|
494
|
+
| `PATCH /api/plugins/:id` | implemented | Toggle one plugin's user override. `:id` MUST be a top-level bundle id; qualified-id form (`bundle/extension`) is the sibling route below. Body `{ enabled: boolean }` (JSON). Persists to `config_plugins` via `IConfigPluginsPort.set` — same path the CLI's `sm plugins enable\|disable` uses. Response is the canonical `{ id, version, kinds, status, reason, source }` row for the affected plugin (post-write `status` reflects the new override resolution). **Granularity** — rejected with 400 `bad-query` when the target bundle declares `granularity: 'extension'` (only the qualified-id form is toggle-able for those). **Lock** — rejected with 403 `locked` when the bundle id is in the host lock-list (`src/server/locked-plugins.ts`). **Restart required** — the loaded plugin runtime is boot-cached; the new value applies on the next `sm scan` or `sm serve` restart. The endpoint does NOT broadcast a WS event today. |
|
|
495
|
+
| `PATCH /api/plugins/:bundleId/extensions/:extensionId` | implemented | Qualified-id form for `granularity: 'extension'` bundles (today: `core` + any user plugin that opts in). Body `{ enabled: boolean }`. Both segments are URL-path-segment-encoded (no slash inside `:bundleId` or `:extensionId`). Rejected with 400 `bad-query` when the target bundle declares `granularity: 'bundle'` (use the sibling route above). **Lock** — rejected with 403 `locked` when either the bundle id or the qualified `bundleId/extensionId` appears in the host lock-list. Same persistence + restart-required semantics as the bundle form. |
|
|
476
496
|
| `ALL /api/*` (other) | reserved | structured 404 envelope (see below); future endpoints land in subsequent sub-steps. |
|
|
477
497
|
| `GET /ws` | implemented (v14.4.a) | accepts WebSocket upgrade and registers the client with the BFF broadcaster. Server-push only — the server fans `scan.*` (and forthcoming `issue.*`) events to every connected client. See **WebSocket protocol** below. |
|
|
478
498
|
| `GET *` | implemented | static asset from the resolved UI bundle, falling back to `index.html` for SPA deep links. |
|
|
@@ -481,7 +501,7 @@ List endpoints conform to [`schemas/api/rest-envelope.schema.json`](schemas/api/
|
|
|
481
501
|
|
|
482
502
|
**`kindRegistry` envelope field.** Every payload-bearing variant of the REST envelope (`nodes` / `links` / `issues` / `plugins` lists, the `node` single, the `config` value envelope) embeds a required `kindRegistry: { [kindName]: { providerId, label, color, colorDark?, emoji?, icon? } }` field. Sentinel envelopes (`health`, `scan`, `graph`) are exempt — they carry no payload at the wire level. The BFF assembles the registry once at boot from every enabled Provider's `kinds[*].ui` block (see [`architecture.md` §Provider · `ui` presentation](architecture.md#provider--ui-presentation)) and attaches the same map to every applicable response. The UI consumes `kindRegistry` directly to render kind palettes, list rows, and inspector headers — built-in and user-plugin kinds render identically. A kind appearing in a response payload (e.g. `node.kind`) without a matching `kindRegistry` entry is a contract violation; the kernel rejects Providers without a `ui` block at load time so the registry is always complete for whatever kinds appear in the response.
|
|
483
503
|
|
|
484
|
-
**Error envelope** (mirrors `§Machine-readable output
|
|
504
|
+
**Error envelope** (mirrors `§Machine-readable output analyzers`):
|
|
485
505
|
|
|
486
506
|
```json
|
|
487
507
|
{
|
|
@@ -504,6 +524,7 @@ Error code sources at v14.2:
|
|
|
504
524
|
- `db-missing` (500) — emitted by mutation endpoints (`PATCH /api/plugins/:id`, `PATCH /api/plugins/:bundleId/extensions/:extensionId`) when the project DB is absent. Read-side routes uniformly degrade to the empty shape (`/api/scan`) or zero items (list endpoints) so they do not emit this code; mutation endpoints cannot persist without a DB so they fail fast instead of silently dropping the write.
|
|
505
525
|
- `not-found` (404) on `PATCH /api/plugins/:id` — unknown plugin id (no built-in bundle, no discovered drop-in matches). The qualified-id form returns the same code when either segment misses.
|
|
506
526
|
- `bad-query` (400) on `PATCH /api/plugins/:id` — granularity mismatch (bundle-level call against a `granularity: 'extension'` bundle, or qualified-id call against a `granularity: 'bundle'` bundle), malformed body (missing `enabled`, wrong type), unknown extension id under a known bundle.
|
|
527
|
+
- `locked` (403) on `PATCH /api/plugins/:id` and the qualified-id sibling — the target bundle id or qualified extension id is in the host lock-list (`src/server/locked-plugins.ts`). The list is hardcoded, host-only, and not user-editable; `GET /api/plugins` mirrors the same analyzer by stamping `locked: true` on the affected items.
|
|
507
528
|
- `bad-query` (400) on `POST /api/scan` — the server was started with `--no-built-ins` or `--no-plugins` (partial pipeline would persist a misleading DB).
|
|
508
529
|
- `scan-busy` (409) on `POST /api/scan` — another scan (a watcher batch or another POST) is already in flight. Retry once the in-flight scan resolves; the WS `scan.completed` envelope is the unambiguous "now safe" signal.
|
|
509
530
|
|
|
@@ -532,9 +553,9 @@ The `/ws` endpoint is the live-events channel for the SPA. Clients connect once
|
|
|
532
553
|
- `scan.started` (per `job-events.md` §Scan events line 325).
|
|
533
554
|
- `scan.progress` (per `job-events.md` line 345 — emitted by the kernel orchestrator at every node; throttling deferred to a follow-up).
|
|
534
555
|
- `scan.completed` (per `job-events.md` line 363).
|
|
535
|
-
- `extractor.completed` (per `job-events.md` line 384) and `
|
|
556
|
+
- `extractor.completed` (per `job-events.md` line 384) and `analyzer.completed` (per `job-events.md` line 404) ride along as side effects of the same emitter bridge.
|
|
536
557
|
- `extension.error` (kernel-internal — emitted when an extension violates its declared contract; the BFF forwards verbatim).
|
|
537
|
-
- `watcher.started` and `watcher.error` — BFF-internal advisories. Non-normative; consumers MUST ignore unknown event types per the forward-compatibility
|
|
558
|
+
- `watcher.started` and `watcher.error` — BFF-internal advisories. Non-normative; consumers MUST ignore unknown event types per the forward-compatibility analyzer.
|
|
538
559
|
- **Deferred to a follow-up**: `issue.added` / `issue.resolved` (per `job-events.md` §Issue events line 446) and `scan.failed`. The 14.4.a surface fans out only the events the kernel emitter already produces; the diff-based issue events and a dedicated batch-failure event require additional plumbing inside the BFF watcher loop.
|
|
539
560
|
- **Connection lifecycle**:
|
|
540
561
|
1. Client opens `ws://<host>:<port>/ws`. The server completes the WS handshake and registers the underlying socket with the broadcaster.
|
|
@@ -567,7 +588,7 @@ Per-Provider conformance suites live next to the Provider's manifest under `<plu
|
|
|
567
588
|
|
|
568
589
|
---
|
|
569
590
|
|
|
570
|
-
## Machine-readable output
|
|
591
|
+
## Machine-readable output analyzers
|
|
571
592
|
|
|
572
593
|
When `--json` is set:
|
|
573
594
|
|
|
@@ -633,7 +654,7 @@ The `done in …` stderr line, its format grammar, and the `elapsedMs` field con
|
|
|
633
654
|
|
|
634
655
|
## See also
|
|
635
656
|
|
|
636
|
-
- [`architecture.md`](./architecture.md) — CLI as a driving adapter; kernel-first design; dependency
|
|
657
|
+
- [`architecture.md`](./architecture.md) — CLI as a driving adapter; kernel-first design; dependency analyzers.
|
|
637
658
|
- [`job-lifecycle.md`](./job-lifecycle.md) — state machine behind `sm job` verbs.
|
|
638
659
|
- [`job-events.md`](./job-events.md) — event stream emitted via `--json` and `--stream-output`.
|
|
639
660
|
- [`db-schema.md`](./db-schema.md) — tables behind `sm db` verbs.
|
package/conformance/README.md
CHANGED
|
@@ -65,7 +65,7 @@ A case is a JSON document with this shape:
|
|
|
65
65
|
"setup": {
|
|
66
66
|
"disableAllProviders": false,
|
|
67
67
|
"disableAllExtractors": false,
|
|
68
|
-
"
|
|
68
|
+
"disableAllAnalyzers": false
|
|
69
69
|
},
|
|
70
70
|
|
|
71
71
|
"invoke": {
|
|
@@ -118,7 +118,7 @@ Assertion types beyond this list MAY be proposed via spec-vX.Y.Z minor bumps. Im
|
|
|
118
118
|
|
|
119
119
|
| Id | Verifies |
|
|
120
120
|
|---|---|
|
|
121
|
-
| `kernel-empty-boot` | With every Provider/Extractor/
|
|
121
|
+
| `kernel-empty-boot` | With every Provider/Extractor/Analyzer disabled, scanning an empty scope returns a valid empty graph. |
|
|
122
122
|
|
|
123
123
|
Cases explicitly referenced elsewhere in the spec (landing before v1.0):
|
|
124
124
|
|