@skill-map/spec 0.2.1 → 0.4.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 +321 -2
- package/README.md +68 -39
- package/architecture.md +45 -4
- package/cli-contract.md +69 -10
- package/conformance/coverage.md +66 -0
- package/db-schema.md +47 -11
- package/index.json +23 -13
- package/job-events.md +124 -4
- package/job-lifecycle.md +48 -14
- package/package.json +1 -1
- package/plugin-kv-api.md +8 -6
- package/schemas/extensions/action.schema.json +81 -0
- package/schemas/extensions/adapter.schema.json +40 -0
- package/schemas/extensions/audit.schema.json +47 -0
- package/schemas/extensions/base.schema.json +44 -0
- package/schemas/extensions/detector.schema.json +35 -0
- package/schemas/extensions/renderer.schema.json +29 -0
- package/schemas/extensions/rule.schema.json +37 -0
- package/schemas/frontmatter/agent.schema.json +1 -6
- package/schemas/frontmatter/base.schema.json +10 -0
- package/schemas/history-stats.schema.json +172 -0
- package/schemas/plugins-registry.schema.json +1 -1
- package/schemas/project-config.schema.json +42 -7
- package/versioning.md +1 -1
package/cli-contract.md
CHANGED
|
@@ -39,6 +39,22 @@ CLI flag wins over env var. Env var wins over config file.
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## Targeted fan-out flags
|
|
43
|
+
|
|
44
|
+
`--all` is not global. It is only valid on verbs whose contract explicitly lists it:
|
|
45
|
+
|
|
46
|
+
- `sm job submit <action> --all`
|
|
47
|
+
- `sm job run --all`
|
|
48
|
+
- `sm job cancel --all`
|
|
49
|
+
- `sm plugins enable --all`
|
|
50
|
+
- `sm plugins disable --all`
|
|
51
|
+
|
|
52
|
+
For those verbs, `--all` means "apply to every eligible target matching the verb's preconditions" and is mutually exclusive with a positional target or `-n <path>` on the same invocation.
|
|
53
|
+
|
|
54
|
+
Implementations MUST NOT silently accept `--all` on unrelated verbs. Unsupported `--all` usage is an operational error (exit `2`), the same as any other unknown or invalid flag.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
42
58
|
## Exit codes
|
|
43
59
|
|
|
44
60
|
All verbs use this shared table. Additional codes MAY be defined per-verb (documented under the verb).
|
|
@@ -169,7 +185,8 @@ Exit: 0 on clean, 1 if error-severity issues exist, 2 on operational error.
|
|
|
169
185
|
| `sm graph [--format ascii\|mermaid\|dot]` | Render the full graph via the named renderer. |
|
|
170
186
|
| `sm export <query> --format json\|md\|mermaid` | Filtered export. Query syntax is implementation-defined pre-1.0. |
|
|
171
187
|
| `sm orphans` | History rows whose target node is missing. |
|
|
172
|
-
| `sm orphans reconcile <orphan.path> --to <new.path>` | Migrate history rows from the old path to the new one after a rename. |
|
|
188
|
+
| `sm orphans reconcile <orphan.path> --to <new.path>` | Migrate history rows from the old path to the new one after a rename. Use case: the scan's rename heuristic missed a match (semantic-only rename, body rewrite) and the user wants to stitch history manually. |
|
|
189
|
+
| `sm orphans undo-rename <new.path> [--from <old.path>] [--force]` | Reverse a medium- or ambiguous-confidence auto-rename. Requires an active `auto-rename-medium` or `auto-rename-ambiguous` issue on `<new.path>`. For `auto-rename-medium`, omit `--from` — the previous path is read from `issue.data_json`. For `auto-rename-ambiguous`, `--from <old.path>` is REQUIRED to pick one of the candidates listed in `data_json.candidates`. Migrates `state_*` FKs back and resolves the issue; the previous path becomes an `orphan` (its file no longer exists in FS). Destructive; prompts for confirmation unless `--force`. Exit `5` if no active auto-rename issue targets `<new.path>`, or if `--from` references a path not in `data_json.candidates`. |
|
|
173
190
|
|
|
174
191
|
---
|
|
175
192
|
|
|
@@ -195,15 +212,16 @@ See `job-lifecycle.md` for the state machine; this table is the CLI surface.
|
|
|
195
212
|
| `sm job submit <action> --all` | Fan out to every node matching the action's preconditions. |
|
|
196
213
|
| `sm job submit ... --force` | Bypass duplicate detection. |
|
|
197
214
|
| `sm job submit ... --ttl <seconds>` | Override computed TTL. |
|
|
215
|
+
| `sm job submit ... --priority <n>` | Override job priority. Integer; higher runs first. Default `0`. Negative allowed (deprioritize). Frozen on `state_jobs.priority` at submit time. |
|
|
198
216
|
| `sm job list [--status ...] [--action ...] [--node ...]` | List jobs. |
|
|
199
217
|
| `sm job show <job.id>` | Detail: current state, claim timestamp, TTL remaining, runner, content hash. |
|
|
200
218
|
| `sm job preview <job.id>` | Render the job MD file without executing. |
|
|
201
219
|
| `sm job claim [--filter <action>]` | Atomic primitive: return next queued job id, mark it running. Exit 0 with id on stdout; exit 1 if queue empty. |
|
|
202
220
|
| `sm job run` | Full CLI-runner loop: claim + spawn + record. Runs one job. |
|
|
203
|
-
| `sm job run --all` | Drain the queue (
|
|
221
|
+
| `sm job run --all` | Drain the queue (sequential through `v1.0`; in-runner parallelism deferred). |
|
|
204
222
|
| `sm job run --max N` | Drain at most N jobs. |
|
|
205
223
|
| `sm job status [<job.id>]` | Counts (per status) or single-job status. |
|
|
206
|
-
| `sm job cancel <job.id
|
|
224
|
+
| `sm job cancel <job.id> \| --all` | Force a running job to `failed` state with reason `user-cancelled`. `--all` cancels every `queued` and `running` job. |
|
|
207
225
|
| `sm job prune` | Retention GC for completed/failed jobs (per config policy). |
|
|
208
226
|
| `sm job prune --orphan-files` | Remove MD files with no matching DB row. |
|
|
209
227
|
|
|
@@ -238,8 +256,8 @@ Authentication: the nonce is the sole credential. An implementation MUST reject
|
|
|
238
256
|
|
|
239
257
|
| Command | Purpose |
|
|
240
258
|
|---|---|
|
|
241
|
-
| `sm history [-n <node.path>] [--action <id>] [--status ...] [--since <date>]` | Filter execution records. `--json` emits an array of `execution-record.schema.json` objects. |
|
|
242
|
-
| `sm history stats` | Aggregates
|
|
259
|
+
| `sm history [-n <node.path>] [--action <id>] [--status ...] [--since <date>] [--until <date>]` | Filter execution records. `--json` emits an array of `execution-record.schema.json` objects. |
|
|
260
|
+
| `sm history stats [--since <date>] [--until <date>] [--period day\|week\|month] [--top N]` | Aggregates over `state_executions` in the window. `--json` emits a document conforming to `history-stats.schema.json`: totals, tokens per action, executions per period (granularity from `--period`, default `month`), top N nodes by frequency (default 10), error rates (global + per-action + per failure reason). |
|
|
243
261
|
|
|
244
262
|
---
|
|
245
263
|
|
|
@@ -249,8 +267,8 @@ Authentication: the nonce is the sole credential. An implementation MUST reject
|
|
|
249
267
|
|---|---|
|
|
250
268
|
| `sm plugins list` | Auto-discovered plugins with status. `--json` emits an array of `DiscoveredPlugin`. |
|
|
251
269
|
| `sm plugins show <id>` | Full manifest + compat detail. |
|
|
252
|
-
| `sm plugins enable <id
|
|
253
|
-
| `sm plugins disable <id
|
|
270
|
+
| `sm plugins enable <id> \| --all` | Toggle on. Persists in `config_plugins`. `--all` applies to every discovered plugin. |
|
|
271
|
+
| `sm plugins disable <id> \| --all` | Toggle off; does not delete the plugin directory. `--all` applies to every discovered plugin. |
|
|
254
272
|
| `sm plugins doctor` | Revalidate all plugins against current spec version; update `status` fields. |
|
|
255
273
|
|
|
256
274
|
---
|
|
@@ -272,15 +290,16 @@ See `db-schema.md` for the table catalog.
|
|
|
272
290
|
|
|
273
291
|
| Command | Purpose |
|
|
274
292
|
|---|---|
|
|
275
|
-
| `sm db reset` | Drop `scan_*`
|
|
276
|
-
| `sm db reset --
|
|
293
|
+
| `sm db reset` | Drop `scan_*` only. Keep `state_*` and `config_*`. Non-destructive — no confirmation required. |
|
|
294
|
+
| `sm db reset --state` | Drop `scan_*` AND `state_*` (including `state_plugin_kvs` and every `plugin_<id>_*` table). Keep `config_*`. Destructive. |
|
|
295
|
+
| `sm db reset --hard` | Delete the DB file entirely. Keep the plugins folder so the next boot re-discovers them. Destructive. |
|
|
277
296
|
| `sm db backup [--out <path>]` | WAL checkpoint + file copy. |
|
|
278
297
|
| `sm db restore <path>` | Swap the DB. |
|
|
279
298
|
| `sm db shell` | Interactive SQL shell (implementations backed by SQLite use `sqlite3`; others use equivalent). |
|
|
280
299
|
| `sm db dump [--tables ...]` | SQL dump. |
|
|
281
300
|
| `sm db migrate [--dry-run \| --status \| --to <n> \| --kernel-only \| --plugin <id> \| --no-backup]` | Migration controls. |
|
|
282
301
|
|
|
283
|
-
|
|
302
|
+
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.
|
|
284
303
|
|
|
285
304
|
---
|
|
286
305
|
|
|
@@ -325,6 +344,46 @@ When `--json` is set:
|
|
|
325
344
|
|
|
326
345
|
---
|
|
327
346
|
|
|
347
|
+
## Elapsed time
|
|
348
|
+
|
|
349
|
+
Every verb that does non-trivial work MUST report its own wall-clock duration. Coverage is broad on purpose — operators and agents need to notice regressions without instrumenting the host.
|
|
350
|
+
|
|
351
|
+
### Scope
|
|
352
|
+
|
|
353
|
+
**In scope**: any verb that walks the filesystem, hits the DB, spawns a subprocess, or renders a report. Examples: `sm scan`, `sm check`, `sm list`, `sm show`, `sm findings`, `sm history`, `sm history stats`, `sm graph`, `sm export`, `sm audit run`, `sm job submit`, `sm job run`, `sm job claim`, `sm job preview`, `sm record`, `sm doctor`, `sm db backup`, `sm db restore`, `sm db dump`, `sm db migrate`, `sm plugins list`, `sm plugins doctor`, `sm init`.
|
|
354
|
+
|
|
355
|
+
**Exempt**: informational verbs that return in well under a millisecond and would clutter the output — `sm --version`, `sm --help`, `sm version`, `sm help`, `sm config get`, `sm config list`, `sm config show`.
|
|
356
|
+
|
|
357
|
+
### Pretty output (TTY)
|
|
358
|
+
|
|
359
|
+
The last line written to stderr MUST be `done in <formatted>` where `<formatted>` is:
|
|
360
|
+
|
|
361
|
+
- `< 1000ms` → `<N>ms` (integer, no decimals).
|
|
362
|
+
- `≥ 1s` and `< 60s` → `<N.N>s` (one decimal).
|
|
363
|
+
- `≥ 60s` → `<M>m <S>s` (integer minutes + integer seconds).
|
|
364
|
+
|
|
365
|
+
Examples: `done in 34ms`, `done in 2.4s`, `done in 1m 42s`.
|
|
366
|
+
|
|
367
|
+
The line is suppressed by `--quiet`. It goes to stderr so it never pollutes stdout, including in `--json` mode.
|
|
368
|
+
|
|
369
|
+
### JSON output (`--json`)
|
|
370
|
+
|
|
371
|
+
When the verb's `--json` output is a top-level **object**, the schema includes an `elapsedMs` top-level field (integer, milliseconds). Stdout then carries the timing inside the document. Stderr still emits the `done in …` line unless `--quiet`.
|
|
372
|
+
|
|
373
|
+
When the verb's `--json` output is a top-level **array** or an **ndjson stream**, the schema does NOT include `elapsedMs` (there is no object to attach it to). Stderr is the sole carrier of the timing line.
|
|
374
|
+
|
|
375
|
+
Schemas that already express the command's wall-clock under a nested field (e.g. `scan-result.schema.json` → `stats.durationMs`) MUST treat that field as the elapsed time of the scan command itself. Adding a top-level `elapsedMs` to those schemas for redundancy is a minor bump and MAY happen later for consistency; until then, consumers read the nested field.
|
|
376
|
+
|
|
377
|
+
### Implementations
|
|
378
|
+
|
|
379
|
+
Implementations MUST measure from the moment the verb starts its own work (after Clipanion / arg-parsing overhead) to the moment before writing the terminal output. Sub-millisecond verbs exempt per §Scope MAY skip the measurement entirely.
|
|
380
|
+
|
|
381
|
+
### Stability
|
|
382
|
+
|
|
383
|
+
The `done in …` stderr line, its format grammar, and the `elapsedMs` field contract are **stable** as of spec v1.0.0. Changing the grammar, the time units, or the location (stderr ↔ stdout) is a major bump. Adding `elapsedMs` to a schema that previously omitted it is a minor bump.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
328
387
|
## Stability
|
|
329
388
|
|
|
330
389
|
The **verb list** is stable as of spec v1.0.0. Adding a verb is a minor bump. Removing a verb is a major bump.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Conformance coverage
|
|
2
|
+
|
|
3
|
+
Authoritative map of JSON Schemas in `spec/schemas/` to the conformance cases that exercise them. Every schema MUST have at least one case before spec v1.0.0 ships — missing case → missing release (AGENTS.md §Rules for AI agents editing spec/).
|
|
4
|
+
|
|
5
|
+
This file is hand-maintained. A CI check before spec release compares the schema inventory against this table and fails if any schema lacks a case.
|
|
6
|
+
|
|
7
|
+
## Coverage matrix
|
|
8
|
+
|
|
9
|
+
| # | Schema | Case(s) | Status | Notes |
|
|
10
|
+
|---|---|---|---|---|
|
|
11
|
+
| 1 | `node.schema.json` | `basic-scan` | 🟢 covered | Exercised via ScanResult containment. |
|
|
12
|
+
| 2 | `link.schema.json` | — | 🔴 missing | Needs fixture with at least one `invokes` + `references` + `mentions` link, both `high`/`medium`/`low` confidence. |
|
|
13
|
+
| 3 | `issue.schema.json` | — | 🔴 missing | Needs fixture triggering `trigger-collision` + `broken-ref` + `superseded`. |
|
|
14
|
+
| 4 | `scan-result.schema.json` | `basic-scan`, `kernel-empty-boot` | 🟢 covered | Zero-filled (empty-boot) + populated (minimal-claude) both asserted. |
|
|
15
|
+
| 5 | `execution-record.schema.json` | — | 🔴 missing | Blocked by Step 4 (history). Needs a case that runs a `local` action and inspects `state_executions` via `sm history --json`. |
|
|
16
|
+
| 6 | `project-config.schema.json` | — | 🔴 missing | Case: init a scope, write a partial `.skill-map.json`, assert effective config after merge. |
|
|
17
|
+
| 7 | `plugins-registry.schema.json` | — | 🔴 missing | Two sub-cases required: (a) `PluginManifest` validation via `sm plugins show --json`; (b) aggregate `PluginsRegistry` via `sm plugins list --json`. |
|
|
18
|
+
| 8 | `job.schema.json` | — | 🔴 missing | Blocked by Step 9 (job system). Needs a case that submits a local action (no LLM), inspects `sm job show --json`. |
|
|
19
|
+
| 9 | `report-base.schema.json` | — | 🔴 missing | Indirect coverage once any summarizer case lands. Direct contract case: validate a handcrafted minimal report ({confidence, safety}) against the base schema. |
|
|
20
|
+
| 10 | `conformance-case.schema.json` | — | 🔴 missing | Self-referential: every `*.json` under `cases/` MUST validate against this schema. Add a meta-case that enumerates + validates all cases. |
|
|
21
|
+
| 11 | `frontmatter/base.schema.json` | `basic-scan` (indirect) | 🟡 partial | Covered via every kind schema's `allOf`. Direct case: fixture with min-required frontmatter only. |
|
|
22
|
+
| 12 | `frontmatter/skill.schema.json` | `basic-scan` | 🟢 covered | One skill in `minimal-claude`. |
|
|
23
|
+
| 13 | `frontmatter/agent.schema.json` | `basic-scan` | 🟢 covered | One agent in `minimal-claude`. |
|
|
24
|
+
| 14 | `frontmatter/command.schema.json` | `basic-scan` | 🟢 covered | One command in `minimal-claude`. |
|
|
25
|
+
| 15 | `frontmatter/hook.schema.json` | `basic-scan` | 🟢 covered | One hook in `minimal-claude`. |
|
|
26
|
+
| 16 | `frontmatter/note.schema.json` | `basic-scan` | 🟢 covered | One note in `minimal-claude`. |
|
|
27
|
+
| 17 | `summaries/skill.schema.json` | — | 🔴 missing | Blocked by Step 9 (`skill-summarizer`). Case: submit summarizer, validate report. |
|
|
28
|
+
| 18 | `summaries/agent.schema.json` | — | 🔴 missing | Blocked by Step 10. |
|
|
29
|
+
| 19 | `summaries/command.schema.json` | — | 🔴 missing | Blocked by Step 10. |
|
|
30
|
+
| 20 | `summaries/hook.schema.json` | — | 🔴 missing | Blocked by Step 10. |
|
|
31
|
+
| 21 | `summaries/note.schema.json` | — | 🔴 missing | Blocked by Step 10. |
|
|
32
|
+
| 22 | `extensions/base.schema.json` | — | 🔴 missing | Meta-case: every manifest under `src/extensions/` validates against the appropriate kind schema (which extends base via `allOf`). |
|
|
33
|
+
| 23 | `extensions/adapter.schema.json` | — | 🔴 missing | Case: the `claude` adapter manifest validates; a crafted invalid manifest (missing `defaultRefreshAction`) fails with `invalid-manifest`. |
|
|
34
|
+
| 24 | `extensions/detector.schema.json` | — | 🔴 missing | Case: `frontmatter` + `slash` + `at-directive` detector manifests validate; a detector emitting a disallowed `emitsLinkKinds` value fails. |
|
|
35
|
+
| 25 | `extensions/rule.schema.json` | — | 🔴 missing | Case: `trigger-collision`, `broken-ref`, `superseded` manifests validate. |
|
|
36
|
+
| 26 | `extensions/action.schema.json` | — | 🔴 missing | Case: a `local` action manifest validates; an `invocation-template` action WITHOUT `promptTemplateRef` fails. |
|
|
37
|
+
| 27 | `extensions/audit.schema.json` | — | 🔴 missing | Case: `validate-all` audit manifest validates; an audit referencing a non-existent rule id in `composes` fails at load with `invalid-manifest`. |
|
|
38
|
+
| 28 | `extensions/renderer.schema.json` | — | 🔴 missing | Case: `ascii` renderer manifest validates. |
|
|
39
|
+
| 29 | `history-stats.schema.json` | — | 🔴 missing | Blocked by Step 4 (history). Case: seed `state_executions` with a deterministic fixture, run `sm history stats --json --since <T0> --until <T1> --period month --top 5`, assert the document validates and that `totals.executionsCount == sum(perAction.executionsCount)` and `errorRates.global == totals.failedCount / totals.executionsCount`. Percentiles (`p95`/`p99`) intentionally omitted in v1 — add later as a minor bump without breaking consumers. |
|
|
40
|
+
|
|
41
|
+
Status legend: 🟢 covered (at least one case asserts the schema end-to-end) · 🟡 partial (covered only indirectly or via a sub-shape) · 🔴 missing.
|
|
42
|
+
|
|
43
|
+
## Non-schema normative artifacts
|
|
44
|
+
|
|
45
|
+
These have their own conformance cases even though they are not JSON Schemas.
|
|
46
|
+
|
|
47
|
+
| # | Artifact | Case | Status | Notes |
|
|
48
|
+
|---|---|---|---|---|
|
|
49
|
+
| A | Preamble verbatim text | `preamble-bitwise-match` | 🟠 deferred | Deferred to Step 9 (needs `sm job preview` to render a job file). Fixture: `fixtures/preamble-v1.txt` (already present, byte-identical to `prompt-preamble.md` source). |
|
|
50
|
+
| B | Kernel empty-boot invariant | `kernel-empty-boot` | 🟢 covered | All extensions disabled → empty ScanResult. |
|
|
51
|
+
| C | Atomic-claim race safety | — | 🔴 missing | Blocked by Step 9. Two concurrent `sm job claim` invocations against a single queued row — exactly one MUST succeed. |
|
|
52
|
+
| D | Duplicate detection | — | 🔴 missing | Blocked by Step 9. Two `sm job submit` with same `(action, version, node, contentHash)` — second exits 3. |
|
|
53
|
+
| E | `--force` bypass | — | 🔴 missing | Blocked by Step 9. |
|
|
54
|
+
| F | Nonce mismatch | — | 🔴 missing | Blocked by Step 9. `sm record` with wrong nonce → exit 4. |
|
|
55
|
+
| G | Reap | — | 🔴 missing | Blocked by Step 9. Set TTL to 1s; claim; wait; next `sm job run` reaps with reason `abandoned`. |
|
|
56
|
+
| H | `run.*` event envelope for Skill agent | — | 🔴 missing | Blocked by Step 9. Skill-agent flow emits synthetic `r-ext-*` run envelope around one job. |
|
|
57
|
+
| I | Rename heuristic | — | 🔴 missing | Blocked by Step 4. Move a file; same-`body_hash` → high-confidence auto-rename; `state_*` FK rows migrated; no issue emitted. |
|
|
58
|
+
| J | Plugin DDL rejection | — | 🔴 missing | Blocked by Step 8. Plugin migration referencing `state_jobs` → disabled with `invalid-manifest`. |
|
|
59
|
+
| K | Plugin prefix injection | — | 🔴 missing | Blocked by Step 8. Plugin declares `CREATE TABLE foo` → kernel applies as `plugin_<id>_foo`. |
|
|
60
|
+
| L | Elapsed-time reporting | — | 🔴 missing | Blocked by Step 3 (first real verb work). Run any in-scope verb; stderr last line MUST match `/^done in (\d+ms\|\d+\.\d+s\|\d+m \d+s)$/`. In-scope verb with `--json` returning an object MUST carry `elapsedMs`. Exempt verb (`sm version`) MUST NOT emit the line. |
|
|
61
|
+
|
|
62
|
+
## Release gates
|
|
63
|
+
|
|
64
|
+
- **spec v0.x**: partial coverage acceptable. Every case added as the reference impl lands the verb that makes it runnable.
|
|
65
|
+
- **spec v1.0.0 release**: all rows above MUST be 🟢 covered or explicitly 🟠 deferred to v1.1 with a linked issue.
|
|
66
|
+
- **CI check**: [`scripts/check-coverage.mjs`](../../scripts/check-coverage.mjs) compares `spec/schemas/**/*.schema.json` against the matrix above on every PR. A schema without a row here, or a row pointing at a missing schema, fails CI (exit 1 with a `::error::` annotation). Wired into `ci.yml` §validate and into `npm run spec:check`.
|
package/db-schema.md
CHANGED
|
@@ -35,7 +35,7 @@ Every kernel table belongs to exactly one zone, identified by a mandatory name p
|
|
|
35
35
|
| State | `state_` | Persistent operational data: jobs, executions, summaries, enrichment, plugin KV. | No | Yes | `state_jobs` |
|
|
36
36
|
| Config | `config_` | User-owned configuration: plugin enable/disable, preferences, migration ledger. | No | Yes | `config_plugins` |
|
|
37
37
|
|
|
38
|
-
`sm db reset` drops `scan_*`
|
|
38
|
+
`sm db reset` drops `scan_*` only (non-destructive — equivalent to forcing the next scan from a clean slate). `sm db reset --state` also drops `state_*` (destructive to operational history). `sm db reset --hard` deletes the DB file entirely. `sm db backup` preserves `state_*` + `config_*`; `scan_*` is always regenerated on demand and is never included in backups.
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
@@ -206,7 +206,7 @@ One row per `(node_id, summarizer_action_id)`. See `schemas/summaries/`.
|
|
|
206
206
|
|
|
207
207
|
Primary key: `(node_id, summarizer_action_id)`. Indexes: `ix_state_summaries_generated_at`.
|
|
208
208
|
|
|
209
|
-
### `
|
|
209
|
+
### `state_enrichments`
|
|
210
210
|
|
|
211
211
|
One row per `(node_id, provider_id)`.
|
|
212
212
|
|
|
@@ -219,9 +219,9 @@ One row per `(node_id, provider_id)`.
|
|
|
219
219
|
| `fetched_at` | INTEGER | NOT NULL |
|
|
220
220
|
| `stale_after` | INTEGER | NULL |
|
|
221
221
|
|
|
222
|
-
Primary key: `(node_id, provider_id)`. Indexes: `
|
|
222
|
+
Primary key: `(node_id, provider_id)`. Indexes: `ix_state_enrichments_stale_after`.
|
|
223
223
|
|
|
224
|
-
### `
|
|
224
|
+
### `state_plugin_kvs`
|
|
225
225
|
|
|
226
226
|
Shared key-value store for plugins that declared storage mode `kv`. See `plugin-kv-api.md` for the accessor contract.
|
|
227
227
|
|
|
@@ -233,7 +233,7 @@ Shared key-value store for plugins that declared storage mode `kv`. See `plugin-
|
|
|
233
233
|
| `value_json` | TEXT | NOT NULL |
|
|
234
234
|
| `updated_at` | INTEGER | NOT NULL |
|
|
235
235
|
|
|
236
|
-
Primary key: `(plugin_id, node_id, key)` with `node_id` using a sentinel empty string when NULL to satisfy PK constraints on engines that reject NULL in PK columns. Indexes: `
|
|
236
|
+
Primary key: `(plugin_id, node_id, key)` with `node_id` using a sentinel empty string when NULL to satisfy PK constraints on engines that reject NULL in PK columns. Indexes: `ix_state_plugin_kvs_plugin_id`.
|
|
237
237
|
|
|
238
238
|
---
|
|
239
239
|
|
|
@@ -285,7 +285,7 @@ The kernel ALSO maintains `PRAGMA user_version` (or the engine equivalent) as a
|
|
|
285
285
|
- **Location**: kernel migrations in `src/migrations/` (reference impl); plugin migrations in `<plugin-dir>/migrations/`.
|
|
286
286
|
- **Wrapping**: the kernel wraps each file in `BEGIN; ... ; COMMIT;`. Files contain DDL only.
|
|
287
287
|
- **Strict versioning**: no idempotency is required. `CREATE TABLE IF NOT EXISTS` is DISCOURAGED in kernel migrations (but permitted in plugin migrations, at the plugin author's discretion).
|
|
288
|
-
- **Auto-apply**: on startup, unless `
|
|
288
|
+
- **Auto-apply**: on startup, unless `autoMigrate: false` in config. A backup is written to `.skill-map/backups/skill-map-pre-migrate-v<N>.db` before applying.
|
|
289
289
|
- **Plugin migration order**: plugins are migrated after kernel migrations and in stable alphabetical order by plugin id. A failing plugin migration disables only that plugin; other plugins and the kernel continue.
|
|
290
290
|
|
|
291
291
|
`sm db migrate` controls migration flow manually: `--dry-run`, `--status`, `--to <n>`, `--kernel-only`, `--plugin <id>`, `--no-backup`.
|
|
@@ -298,7 +298,7 @@ Two modes declared in `plugin.json` (see `schemas/plugins-registry.schema.json`)
|
|
|
298
298
|
|
|
299
299
|
| Mode | Manifest | Backing |
|
|
300
300
|
|---|---|---|
|
|
301
|
-
| **KV** (mode A) | `"storage": { "mode": "kv" }` | Shared `
|
|
301
|
+
| **KV** (mode A) | `"storage": { "mode": "kv" }` | Shared `state_plugin_kvs`. See `plugin-kv-api.md`. |
|
|
302
302
|
| **Dedicated** (mode B) | `"storage": { "mode": "dedicated", "tables": [...], "migrations": [...] }` | Plugin-owned tables, prefixed `plugin_<normalized_id>_`. |
|
|
303
303
|
|
|
304
304
|
Normalization of `plugin_id` for the prefix:
|
|
@@ -314,11 +314,19 @@ Collisions after normalization are a load-time error; both plugins are disabled
|
|
|
314
314
|
|
|
315
315
|
### Triple protection for mode B
|
|
316
316
|
|
|
317
|
-
The kernel MUST enforce all three layers:
|
|
317
|
+
The kernel MUST enforce all three layers **in this exact order** for every plugin migration:
|
|
318
318
|
|
|
319
|
-
1. **
|
|
320
|
-
2. **DDL validation
|
|
321
|
-
|
|
319
|
+
1. **Parse** — the kernel parses each plugin migration SQL file into an AST. Parse errors disable the plugin with status `load-error`.
|
|
320
|
+
2. **DDL validation (pre-rewrite)** — the AST is validated against the original table names authored by the plugin. Kernel MUST reject, before any rewrite:
|
|
321
|
+
- References (FK / trigger / view) to any kernel table (prefix `scan_`, `state_`, `config_`) or to another plugin's table (prefix `plugin_<other-id>_`).
|
|
322
|
+
- `DROP` / `ALTER` / `TRUNCATE` against anything outside the plugin's own logical table names.
|
|
323
|
+
- `ATTACH DATABASE` statements.
|
|
324
|
+
- Global `PRAGMA` statements (anything not scoped to a plugin-owned table).
|
|
325
|
+
Rejection here is intentional: validation runs **before** prefix injection so kernel tables are named as the plugin wrote them, making the reject test straightforward.
|
|
326
|
+
3. **Prefix injection (rewrite)** — the kernel rewrites the AST so every table name the plugin authored becomes `plugin_<normalizedId>_<originalName>` if it doesn't already carry the prefix. Index and constraint names get the same treatment. A plugin CANNOT create un-prefixed tables.
|
|
327
|
+
4. **Scoped connection (runtime)** — at runtime, the plugin receives a `Database` wrapper (not a raw handle). The wrapper rejects any query that touches tables whose name doesn't start with this plugin's prefix. This is the last-line defense: even if a migration-time layer were bypassed, runtime queries still cannot reach out-of-namespace data.
|
|
328
|
+
|
|
329
|
+
Step 4 is separate from 1–3 because it applies at query time, not migration time. Together the four steps form the "triple protection" referenced across the spec (the name predates the explicit parse step).
|
|
322
330
|
|
|
323
331
|
Honest note: plugins are user-placed code. Protection guards against accidents (a plugin that mistakenly names a table `state_jobs`), not against hostile plugins. A malicious plugin running in the same process can bypass any JS-level guard. Post-v1.0 evaluates sandboxing (worker threads, VM contexts) and/or signing.
|
|
324
332
|
|
|
@@ -335,6 +343,34 @@ Backups include `state_*` + `config_*` only; `scan_*` is regenerated after resto
|
|
|
335
343
|
|
|
336
344
|
---
|
|
337
345
|
|
|
346
|
+
## Rename detection (automatic)
|
|
347
|
+
|
|
348
|
+
`scan_nodes.path` is the canonical node identifier in v0. Moving a file therefore rewrites the primary key, which would orphan every `state_*` row referencing the old path (`state_executions.node_ids_json`, `state_jobs.node_id`, `state_summaries.node_id`, `state_enrichments.node_id`).
|
|
349
|
+
|
|
350
|
+
Implementations MUST apply a rename heuristic at scan time **before** committing the new scan transaction:
|
|
351
|
+
|
|
352
|
+
1. Compute the set `deletedPaths` (rows present in the previous `scan_nodes` but absent from the new walk) and `newPaths` (rows present in the new walk but absent from the previous scan).
|
|
353
|
+
2. For each pair `(deletedPath, newPath)` where `newPath.bodyHash == deletedPath.bodyHash` → classify as **high-confidence rename**. The kernel MUST:
|
|
354
|
+
- Update every `state_*` row whose `node_id` equals `deletedPath` to reference `newPath`.
|
|
355
|
+
- Emit no issue. Log at `info` level.
|
|
356
|
+
3. Remaining pairs where `newPath.frontmatterHash == deletedPath.frontmatterHash` (body differs, frontmatter is a perfect match) → classify as **medium-confidence rename**. The kernel MUST:
|
|
357
|
+
- Apply the same FK migration.
|
|
358
|
+
- Emit an issue with `ruleId: auto-rename-medium` (severity `warn`) pointing to both paths. The issue's `data` MUST include `{ from: <old.path>, to: <new.path>, confidence: "medium" }` so `sm orphans undo-rename <new.path>` can read the prior path without user input.
|
|
359
|
+
4. Any `deletedPath` left without a match after steps 2–3 becomes an **orphan**: the kernel emits an issue with `ruleId: orphan` (severity `info`) and keeps the `state_*` rows referencing the dead path untouched until the user runs `sm orphans reconcile <dead.path> --to <new.path>` or accepts the orphan.
|
|
360
|
+
|
|
361
|
+
Matching is 1-to-1: once a `newPath` is claimed as the rename target of some `deletedPath`, no other deletion can match it in the same scan. Ambiguity (two deletions share a body hash with the same new path) → fall back to the orphan path for all candidates, with issue `auto-rename-ambiguous` listing every conflict. `auto-rename-ambiguous` issues MUST populate `data` with `{ to: <new.path>, candidates: [<old.path.a>, <old.path.b>, ...] }`; in this case `sm orphans undo-rename` requires the user to pass `--from <old.path>` to disambiguate.
|
|
362
|
+
|
|
363
|
+
Note on casing: `bodyHash` / `frontmatterHash` / `ruleId` / `data` are the domain-object field names (per `node.schema.json` and `issue.schema.json`). The SQLite reference impl stores the same values in `body_hash` / `frontmatter_hash` / `rule_id` / `data_json` columns; the storage adapter bridges the two (see §Naming conventions above). The heuristic is specified against the domain types, not the columns.
|
|
364
|
+
|
|
365
|
+
The heuristic runs inside the scan transaction, so either all renames land or none do. `sm scan` is the only surface that triggers automatic rename detection. Two manual verbs exist for cases the heuristic missed or got wrong:
|
|
366
|
+
|
|
367
|
+
- `sm orphans reconcile <orphan.path> --to <new.path>` — forward direction. Attaches FKs of an orphan to a live node. Use when the heuristic could not match (semantic rename, body rewrite).
|
|
368
|
+
- `sm orphans undo-rename <new.path>` — reverse direction. Reads `issue.data.from` from the active `auto-rename-medium` (or `--from`-disambiguated `auto-rename-ambiguous`) issue on `<new.path>`, migrates `state_*` FKs back, and resolves the issue. The prior path becomes an `orphan`. Use when the heuristic matched two unrelated files that happened to share a frontmatter hash.
|
|
369
|
+
|
|
370
|
+
Both verbs operate on FK ownership only; neither edits files on disk.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
338
374
|
## Integrity
|
|
339
375
|
|
|
340
376
|
`sm doctor` MUST check at least:
|
package/index.json
CHANGED
|
@@ -156,42 +156,52 @@
|
|
|
156
156
|
}
|
|
157
157
|
]
|
|
158
158
|
},
|
|
159
|
+
"specPackageVersion": "0.4.0",
|
|
159
160
|
"integrity": {
|
|
160
161
|
"algorithm": "sha256",
|
|
161
162
|
"files": {
|
|
162
|
-
"CHANGELOG.md": "
|
|
163
|
-
"README.md": "
|
|
164
|
-
"architecture.md": "
|
|
165
|
-
"cli-contract.md": "
|
|
163
|
+
"CHANGELOG.md": "38364d4ca30475e3e1eb35019037532547c1e5bdc245ecfcc60eb5e1570985d0",
|
|
164
|
+
"README.md": "a233e6b5ab1c41c13cb4790485f0514f7c111880eb70165e77e1253999069120",
|
|
165
|
+
"architecture.md": "be4085b7bbb3476f8a9d6df940b5794839b936ac5dd1921203a91558b008c7bc",
|
|
166
|
+
"cli-contract.md": "7657b6e60089afdd3644e7e38d96593158349259ab105d76199ce5eb65993cae",
|
|
166
167
|
"conformance/README.md": "4e41ff823b55ce3c274a033c5152ae0b2759fc714a714d7815593d8be84c8a4c",
|
|
167
168
|
"conformance/cases/basic-scan.json": "24623da0cad8c8c54b3ff9b09820ea1276fe8b8f0fc680bf6e8abeb4edb8e424",
|
|
168
169
|
"conformance/cases/kernel-empty-boot.json": "175524674b14d993d29f10080d7697074b3a2eee25b359ff903344d73c6acc98",
|
|
170
|
+
"conformance/coverage.md": "405a7ee093d6e54993ec5d9e061489f8b924829c7688fe56951bebf87abb2fae",
|
|
169
171
|
"conformance/fixtures/minimal-claude/agents/reviewer.md": "d0dd681ba63838301e480116aa09825329f01832b0116de5c5476fdd8a5dcf54",
|
|
170
172
|
"conformance/fixtures/minimal-claude/commands/status.md": "3f36e053fd1c059ffd902f84a55be8a458c26072f97cb37dd7e97314ae2a9bf5",
|
|
171
173
|
"conformance/fixtures/minimal-claude/hooks/pre-commit.md": "ec9cec8ac4ce34d40ec055ffd90e8f06ea3e5764d6ec3ee84e0d97de71b930c7",
|
|
172
174
|
"conformance/fixtures/minimal-claude/notes/architecture.md": "5a7e6fdbb1556733dacebad63758057dc1e19090b5a983292c0c65e90b98bcf1",
|
|
173
175
|
"conformance/fixtures/minimal-claude/skills/hello.md": "8598074020430f294ff1eac39876302448f004b6c48446d453092159319bcbee",
|
|
174
176
|
"conformance/fixtures/preamble-v1.txt": "1e0aeef224b64477bdc13a949c3ad402e68249caf499ecdba1302371677c068b",
|
|
175
|
-
"db-schema.md": "
|
|
177
|
+
"db-schema.md": "17de515904b8869ea0fe8d3cd608423376396bed6281b784ee1670f184d29f2c",
|
|
176
178
|
"interfaces/security-scanner.md": "468f8ee412594b14b389c36cefa9ca75a5dec652adbf03ab8bbc7f57ca980253",
|
|
177
|
-
"job-events.md": "
|
|
178
|
-
"job-lifecycle.md": "
|
|
179
|
-
"plugin-kv-api.md": "
|
|
179
|
+
"job-events.md": "090733b47dc0bbe62d790a87a29f2a13fe1dbcb9d6a89ea16e1ecff2293e3972",
|
|
180
|
+
"job-lifecycle.md": "793d34de0793596be17d2ed9042eeb4d532e805bceee25d9f9da0c3cb0b34b1c",
|
|
181
|
+
"plugin-kv-api.md": "fe0c02dcc7eaee0ad711f16247df076fa62348710ee1088e1234764dd3cec82e",
|
|
180
182
|
"prompt-preamble.md": "9b7478ddce0a77043983f35932677d702319348048fe5d6b1c60257bd1c9f605",
|
|
181
183
|
"schemas/conformance-case.schema.json": "2740874e00269de6d8121300339401d0283197b6d97dcd77538ec5d108b14de2",
|
|
182
184
|
"schemas/execution-record.schema.json": "ec0f3acf1d0ce099c059d73eb434936bfd1bcf12023693bd572efb2a7352faa6",
|
|
183
|
-
"schemas/
|
|
184
|
-
"schemas/
|
|
185
|
+
"schemas/extensions/action.schema.json": "d22a98aca9ad3d7bee475b370135372913e5756ad5987084c77e91e04c8472e8",
|
|
186
|
+
"schemas/extensions/adapter.schema.json": "882c23b0c914eb37af5d50704268e028eb94e1601ceb5fc61aaf932f1ab82eb2",
|
|
187
|
+
"schemas/extensions/audit.schema.json": "a871f96d340a9b72fc4cd3ce6f56a553bb6b56636e342501dcb5d3b724475502",
|
|
188
|
+
"schemas/extensions/base.schema.json": "845f0abe723f1916354f05f6c1cd134c4b8da25fd612976f5ca1e205bf0d0fdb",
|
|
189
|
+
"schemas/extensions/detector.schema.json": "58957bf96ca58218eadd1fceecfb0203bcc11d93eb8cef6bba90e89deca3e195",
|
|
190
|
+
"schemas/extensions/renderer.schema.json": "a8cfc40c8be525fb69e62d53bd98f622b82599bb95e3fac66ec324d4298e46f5",
|
|
191
|
+
"schemas/extensions/rule.schema.json": "eca5a877f3eb6d52cbe62e97bbd2ca3ccd2724bbc760e16e63d7473dfb4fa61a",
|
|
192
|
+
"schemas/frontmatter/agent.schema.json": "0e63d7692efb29facccc69472fff48a25f44934618346bfc09738864c6917787",
|
|
193
|
+
"schemas/frontmatter/base.schema.json": "e68fbb85d3e873c4897af776eaf873860bd6e86b5abc1799e801d35c4f7937cf",
|
|
185
194
|
"schemas/frontmatter/command.schema.json": "7b8463ce9c83edd2e3073dd4cd1bbeec4b42e53b03b48bc9a59e540136c2de89",
|
|
186
195
|
"schemas/frontmatter/hook.schema.json": "4f935bb2a94c6b08795e7e10a473d5185e5abaa8c215152d41036291835b7aad",
|
|
187
196
|
"schemas/frontmatter/note.schema.json": "9806b371193c802803638682f9a625f8277152ad3ef68939eb5f05fff2ef65f4",
|
|
188
197
|
"schemas/frontmatter/skill.schema.json": "b99b8ab23bee01333b4a04946cd9fc13d373d827ef6ddfc7d058daf637f2f80b",
|
|
198
|
+
"schemas/history-stats.schema.json": "23f472d1de06d23fc775aabba821f8375f347af4dc8d89ba567980d61a11f9de",
|
|
189
199
|
"schemas/issue.schema.json": "40f6f8abadcce0fd8eac9df27ffcc20b2fc9fda6970142ddb8e7e56b1760b9b1",
|
|
190
200
|
"schemas/job.schema.json": "582999899f8846f70c4d745d2813e53b97a4f5ccd3d8d163eeb68b201e7124e4",
|
|
191
201
|
"schemas/link.schema.json": "3e92f5c9def61a857a2c7b22846d82b988157de083463615144ddc92403a489e",
|
|
192
202
|
"schemas/node.schema.json": "14f345fac450f5728c895d1b878e0015eabb9d72ba9da4a8d2236c82933d3fcf",
|
|
193
|
-
"schemas/plugins-registry.schema.json": "
|
|
194
|
-
"schemas/project-config.schema.json": "
|
|
203
|
+
"schemas/plugins-registry.schema.json": "92b2052bd06e366709dd6e1449d99408999e33707c4007afc7662980e73c3ef1",
|
|
204
|
+
"schemas/project-config.schema.json": "286895996eb8cfb8054eb53a9042ea89fbaa36c1eb9e38fdcfbe08440ae692e3",
|
|
195
205
|
"schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
|
|
196
206
|
"schemas/scan-result.schema.json": "5efe9b1954c5e729c4b55dbc4dd51263d97967d16c0b3cea398877ace74d37b7",
|
|
197
207
|
"schemas/summaries/agent.schema.json": "3d22558eeb170e00c4fc32018a810d27333cc632c9e528ff386100cfdfded087",
|
|
@@ -199,7 +209,7 @@
|
|
|
199
209
|
"schemas/summaries/hook.schema.json": "36f876f3b1a60d45be97a0848c79fd18744b434dfdcefc366f033b253d56268c",
|
|
200
210
|
"schemas/summaries/note.schema.json": "ae510f3ee1b6092c1061625e425c9bb7de9c9caa3f3774770c148f658d505753",
|
|
201
211
|
"schemas/summaries/skill.schema.json": "f01bab92c51d64ee23e61587e42cf0dc5b37a2f518f5b12b3d1d456390338aa8",
|
|
202
|
-
"versioning.md": "
|
|
212
|
+
"versioning.md": "e35d4a492f4cba7ff7c9168802fe11a17daa92c60fc3333ed04e2dbb8b3b6795"
|
|
203
213
|
}
|
|
204
214
|
}
|
|
205
215
|
}
|
package/job-events.md
CHANGED
|
@@ -38,7 +38,7 @@ Every event is a JSON object with this envelope:
|
|
|
38
38
|
|---|---|---|
|
|
39
39
|
| `type` | always | One of the canonical event types below. |
|
|
40
40
|
| `timestamp` | always | Unix milliseconds when the event was emitted. |
|
|
41
|
-
| `runId` | always | Identifier of the
|
|
41
|
+
| `runId` | always | Identifier of the invocation that emitted the event. CLI runner loops use `r-YYYYMMDD-HHMMSS-XXXX`; synthetic or non-job runs use one optional mode segment: `r-<mode>-YYYYMMDD-HHMMSS-XXXX`. Canonical modes are `ext` (external Skill claims), `scan` (scan runs), and `check` (standalone issue recomputations). |
|
|
42
42
|
| `jobId` | when job-scoped | The job the event refers to. Null for run-level events (`run.*`). |
|
|
43
43
|
| `data` | per-event | Event-specific payload, shape defined below. |
|
|
44
44
|
|
|
@@ -50,7 +50,7 @@ Unknown fields in `data` MUST be ignored by consumers (forward compatibility).
|
|
|
50
50
|
|
|
51
51
|
## Event catalog
|
|
52
52
|
|
|
53
|
-
Emitted in roughly this order during a `sm job run --all` invocation. The exact sequence may interleave for parallel runs (post
|
|
53
|
+
Emitted in roughly this order during a `sm job run --all` invocation. The exact sequence may interleave for parallel runs (deferred to post-`v1.0`).
|
|
54
54
|
|
|
55
55
|
### `run.started`
|
|
56
56
|
|
|
@@ -200,7 +200,20 @@ Emitted inside `sm record` when the callback arrives and passes nonce validation
|
|
|
200
200
|
}
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
`runId` on this event is the run that originally claimed the job. If the record is called from outside a run
|
|
203
|
+
`runId` on this event is the run that originally claimed the job. If the record is called from outside a CLI run — the canonical case being a Skill agent that called `sm job claim` + `sm record` without ever entering `sm job run` — the kernel MUST synthesize a `runId` of the form `r-ext-YYYYMMDD-HHMMSS-XXXX` (same timestamp + 4-hex shape as real run ids, with the `r-ext-` prefix reserved for externally-driven claims).
|
|
204
|
+
|
|
205
|
+
Synthetic-run envelope: when a Skill agent claims a job, the kernel MUST emit — on the server's WebSocket and in the `--json` ndjson stream if active — a full envelope covering that claim:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
run.started (mode="external")
|
|
209
|
+
→ job.claimed
|
|
210
|
+
→ (no job.spawning — the claim itself is the spawn signal for external runs)
|
|
211
|
+
→ job.callback.received
|
|
212
|
+
→ (job.completed | job.failed)
|
|
213
|
+
→ run.summary
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The `run.started.data.mode` carries the literal string `external` so UI consumers can render skill-driven work differently from CLI-driven work. `run.summary` closes the synthetic run as soon as the callback is processed; one synthetic run always wraps exactly one job. This keeps the WebSocket broadcaster's contract ("every job event lives inside a run envelope") intact across both runner paths.
|
|
204
217
|
|
|
205
218
|
### `job.completed`
|
|
206
219
|
|
|
@@ -291,6 +304,111 @@ A parallel implementation MAY interleave per-job sequences across different `job
|
|
|
291
304
|
|
|
292
305
|
---
|
|
293
306
|
|
|
307
|
+
## Non-job events (Stability: experimental)
|
|
308
|
+
|
|
309
|
+
These event families cover kernel activity other than job execution. They share the common envelope (`type`, `timestamp`, `runId`, `jobId`, `data`). For non-job events `jobId` is always `null`; `runId` identifies the invocation that produced the event — a scan gets an `r-scan-YYYYMMDD-HHMMSS-XXXX` id, an issue recomputation outside a scan gets an `r-check-...` id, following the same `r-<mode>-...` shape as the external-Skill synthetic envelope (`r-ext-...`).
|
|
310
|
+
|
|
311
|
+
The **shapes below are experimental through spec v0.x**. The reference impl starts emitting them at Step 12 alongside the WebSocket broadcaster; once real consumers exercise the stream, the fields lock. Bumping them to `stable` is a minor spec bump; changes to field shapes before `stable` are allowed without a major bump (per `versioning.md` §Pre-1.0).
|
|
312
|
+
|
|
313
|
+
### Scan events
|
|
314
|
+
|
|
315
|
+
#### `scan.started`
|
|
316
|
+
|
|
317
|
+
Emitted once when a scan begins (full, `--changed`, or `-n <node.path>`).
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"type": "scan.started",
|
|
322
|
+
"timestamp": 1745159455123,
|
|
323
|
+
"runId": "r-scan-20260420-143055-a3f2",
|
|
324
|
+
"jobId": null,
|
|
325
|
+
"data": {
|
|
326
|
+
"mode": "full | changed | single",
|
|
327
|
+
"target": "<node.path> | null",
|
|
328
|
+
"rootsCount": 1
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### `scan.progress`
|
|
334
|
+
|
|
335
|
+
Emitted periodically during a scan (implementation-defined cadence; SHOULD throttle to ≥250 ms apart to keep WS traffic cheap).
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"type": "scan.progress",
|
|
340
|
+
"timestamp": 1745159455500,
|
|
341
|
+
"runId": "...",
|
|
342
|
+
"jobId": null,
|
|
343
|
+
"data": {
|
|
344
|
+
"filesSeen": 128,
|
|
345
|
+
"filesProcessed": 64,
|
|
346
|
+
"filesSkipped": 3
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### `scan.completed`
|
|
352
|
+
|
|
353
|
+
Emitted once at scan end.
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"type": "scan.completed",
|
|
358
|
+
"timestamp": 1745159456000,
|
|
359
|
+
"runId": "...",
|
|
360
|
+
"jobId": null,
|
|
361
|
+
"data": {
|
|
362
|
+
"nodes": 187,
|
|
363
|
+
"links": 421,
|
|
364
|
+
"issues": 12,
|
|
365
|
+
"durationMs": 877
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Issue events
|
|
371
|
+
|
|
372
|
+
Emitted by the scan after `scan.completed` when the new scan's issue set differs from the previous one. Enables a UI "issue inbox" to update incrementally without re-fetching the full list.
|
|
373
|
+
|
|
374
|
+
#### `issue.added`
|
|
375
|
+
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"type": "issue.added",
|
|
379
|
+
"timestamp": 1745159456100,
|
|
380
|
+
"runId": "...",
|
|
381
|
+
"jobId": null,
|
|
382
|
+
"data": {
|
|
383
|
+
"ruleId": "trigger-collision",
|
|
384
|
+
"severity": "warn",
|
|
385
|
+
"nodeIds": ["skills/a.md", "skills/b.md"],
|
|
386
|
+
"message": "..."
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### `issue.resolved`
|
|
392
|
+
|
|
393
|
+
Emitted when an issue present in the previous scan is absent from the new one.
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"type": "issue.resolved",
|
|
398
|
+
"timestamp": 1745159456101,
|
|
399
|
+
"runId": "...",
|
|
400
|
+
"jobId": null,
|
|
401
|
+
"data": {
|
|
402
|
+
"ruleId": "broken-ref",
|
|
403
|
+
"nodeIds": ["skills/c.md"]
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Issue diffing is keyed on `(ruleId, nodeIds sorted, message)` — same key → same issue. A payload change on the same key emits no event; consumers re-read full issue detail from `sm check` when needed.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
294
412
|
## Error handling
|
|
295
413
|
|
|
296
414
|
If an event payload cannot be serialized (internal bug), the implementation MUST emit a synthetic event:
|
|
@@ -313,10 +431,12 @@ Consumers MAY treat `emitter.error` as a soft failure (log and continue). Implem
|
|
|
313
431
|
|
|
314
432
|
## Stability
|
|
315
433
|
|
|
316
|
-
The **event type list**
|
|
434
|
+
The **job event type list** (`run.*`, `job.*`, `model.delta`, `emitter.error`) is stable as of spec v1.0.0. Adding a new event type is a minor bump. Removing or renaming one is a major bump.
|
|
317
435
|
|
|
318
436
|
**Adding** fields to `data` is a minor bump. Changing a field's type or removing a field is a major bump.
|
|
319
437
|
|
|
320
438
|
Consumers MUST ignore unknown fields (forward compatibility).
|
|
321
439
|
|
|
322
440
|
The envelope (`type`, `timestamp`, `runId`, `jobId`, `data`) is stable. Adding an envelope field is a major bump because every consumer would need to handle it.
|
|
441
|
+
|
|
442
|
+
The **non-job event families** (`scan.*`, `issue.*`) are marked **experimental** across spec v0.x. They ship alongside the WebSocket broadcaster at Step 12 of the reference impl; shapes may tighten before a stable tag lands. Once promoted to `stable` (a minor spec bump), the same add/remove/rename semantics as the job events apply.
|