@skill-map/spec 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,109 @@
1
1
  # Spec changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9a89124: Step 5.1 — Persist scan-result metadata in a new `scan_meta` table so
8
+ `loadScanResult` returns real values for `scope` / `roots` / `scannedAt` /
9
+ `scannedBy` / `adapters` / `stats.filesWalked` / `stats.filesSkipped` /
10
+ `stats.durationMs` instead of the synthetic envelope shipped at Step 4.7.
11
+
12
+ **Spec change (additive, minor)**:
13
+
14
+ - New `scan_meta` table in zone `scan_*`, single-row (CHECK `id = 1`).
15
+ Columns: `scope`, `roots_json`, `scanned_at`, `scanned_by_name`,
16
+ `scanned_by_version`, `scanned_by_spec_version`, `adapters_json`,
17
+ `stats_files_walked`, `stats_files_skipped`, `stats_duration_ms`.
18
+ `nodesCount` / `linksCount` / `issuesCount` are not stored — they are
19
+ derived from `COUNT(*)` of the sibling tables.
20
+ - Replaced atomically with the rest of `scan_*` on every `sm scan`.
21
+
22
+ **Runtime change**:
23
+
24
+ - New kernel migration `002_scan_meta.sql`.
25
+ - `IScanMetaTable` added to `src/kernel/adapters/sqlite/schema.ts` and
26
+ bound in `IDatabase`.
27
+ - `persistScanResult` writes the row (and deletes prior rows in the same
28
+ transaction).
29
+ - `loadScanResult` reads from `scan_meta` when the row exists; degrades
30
+ to the previous synthetic envelope when it does not (DB freshly
31
+ migrated, never scanned, or pre-5.1 snapshot).
32
+ - The Step 4.7 follow-up notes in `scan-load.ts` documenting the
33
+ synthetic envelope are simplified to describe both branches.
34
+
35
+ Test count: 151 → 154 (+3 covering meta round-trip, replace-all
36
+ single-row invariant, and synthetic-fallback on empty DB).
37
+
38
+ - 9a89124: Step 5.7 — Conformance coverage for the rename heuristic.
39
+
40
+ **Spec change (additive, minor)**:
41
+
42
+ - `spec/schemas/conformance-case.schema.json` gains
43
+ `setup.priorScans: Array<{ fixture, flags? }>` — an ordered list of
44
+ staging scans the runner executes BEFORE the main `invoke`. Each
45
+ step replaces every non-`.skill-map/` directory in the scope with
46
+ the named fixture and runs `sm scan` (with optional flags). The DB
47
+ persists across steps because `.skill-map/` is preserved between
48
+ swaps. After the last step, the runner copies the top-level
49
+ `fixture` and runs the case's `invoke`.
50
+
51
+ Required to express scenarios that need a prior snapshot (rename
52
+ heuristic, future incremental cases). The schema is purely
53
+ additive — every existing case keeps passing without modification.
54
+
55
+ - Two new conformance cases under `spec/conformance/cases/`:
56
+
57
+ - **`rename-high`** — moving a single file with identical body
58
+ triggers a high-confidence auto-rename. Asserts:
59
+ `stats.nodesCount === 1`, `stats.issuesCount === 0`,
60
+ `nodes[0].path === skills/bar.md`. Verifies the spec invariant
61
+ that high-confidence renames emit NO issue.
62
+ - **`orphan-detection`** — deleting a file with no replacement
63
+ emits exactly one `orphan` issue (severity `info`). Asserts the
64
+ `ruleId` and `severity` directly.
65
+
66
+ - Four new fixture directories under `spec/conformance/fixtures/`:
67
+ `rename-high-before/`, `rename-high-after/`,
68
+ `orphan-before/`, `orphan-after/`.
69
+
70
+ - `spec/conformance/coverage.md`: row I (Rename heuristic) flips
71
+ from `🔴 missing` to `🟢 covered`. Notes the medium / ambiguous
72
+ branches stay covered by `src/test/rename-heuristic.test.ts` for
73
+ now (assertion vocabulary in the schema is not rich enough to
74
+ express "the issues array contains an item with ruleId X and
75
+ data.confidence === 'medium'" — when the conformance schema gains
76
+ array-filter assertions, those branches can land here too).
77
+
78
+ **Runtime change**:
79
+
80
+ - `src/conformance/index.ts` runner: implements `setup.priorScans`.
81
+ Helper `replaceFixture(scope, specRoot, fixture)` clears every
82
+ top-level entry in the scope except `.skill-map/`, then copies the
83
+ named fixture on top. Used by both staging steps and the main
84
+ `fixture` phase.
85
+ - `src/test/conformance.test.ts`: includes the two new cases in the
86
+ Step-0b subset. Total conformance cases passing in CI: 1 → 3.
87
+
88
+ **`spec/index.json`** regenerated (50 → 57 files). `npm run spec:check`
89
+ green.
90
+
91
+ Test count: 201 → 203 (+2 conformance cases). The Step 5 totals close
92
+ at: 151 → 203 (+52 across 7 sub-steps).
93
+
94
+ ### Patch Changes
95
+
96
+ - dacd4d9: Move the auto-generated CLI reference from `docs/cli-reference.md` to
97
+ `context/cli-reference.md`. Spec change is editorial: `cli-contract.md`
98
+ references the file path in three spots (`--format md` description, the
99
+ NORMATIVE introspection section, and the "Related" link list); all three
100
+ updated to the new location. No schema or behavioural change.
101
+
102
+ Reference impl: `scripts/build-cli-reference.mjs` writes to the new path,
103
+ the `cli:reference` / `cli:check` npm scripts point there, and `sm help`
104
+ output (which embeds the path in the `--format md` flag description) is
105
+ regenerated. The `docs/` folder is gone.
106
+
3
107
  ## 0.5.1
4
108
 
5
109
  ### Patch Changes
package/cli-contract.md CHANGED
@@ -121,7 +121,7 @@ Exit: 0 if all green, 1 if warnings, 2 if any `error`-level problem.
121
121
  Self-describing introspection.
122
122
 
123
123
  - `human` (default): pretty terminal output.
124
- - `md`: canonical markdown for documentation sites. Implementations MUST NOT hand-maintain equivalent markdown; `docs/cli-reference.md` (in the reference impl) is regenerated from this output in CI.
124
+ - `md`: canonical markdown for documentation sites. Implementations MUST NOT hand-maintain equivalent markdown; `context/cli-reference.md` (in the reference impl) is regenerated from this output in CI.
125
125
  - `json`: structured surface dump. Shape:
126
126
 
127
127
  ```json
@@ -314,7 +314,7 @@ Destructive verbs (`reset --state`, `reset --hard`, `restore`) require interacti
314
314
  ### Introspection
315
315
 
316
316
  - `sm help --format json` — structured CLI surface dump.
317
- - `sm help --format md` — canonical markdown, CI-enforced for the reference impl's `docs/cli-reference.md`.
317
+ - `sm help --format md` — canonical markdown, CI-enforced for the reference impl's `context/cli-reference.md`.
318
318
 
319
319
  These two formats are NORMATIVE: any change to verbs, flags, or exit codes MUST reflect in `--format json` output immediately. Third-party consumers rely on this.
320
320
 
@@ -390,7 +390,7 @@ The `done in …` stderr line, its format grammar, and the `elapsedMs` field con
390
390
  - [`job-lifecycle.md`](./job-lifecycle.md) — state machine behind `sm job` verbs.
391
391
  - [`job-events.md`](./job-events.md) — event stream emitted via `--json` and `--stream-output`.
392
392
  - [`db-schema.md`](./db-schema.md) — tables behind `sm db` verbs.
393
- - [`../docs/cli-reference.md`](../docs/cli-reference.md) — auto-generated reference from `sm help --format md`.
393
+ - [`../context/cli-reference.md`](../context/cli-reference.md) — auto-generated reference from `sm help --format md`.
394
394
  - [`conformance/`](./conformance/README.md) — test suite exercising CLI behavior.
395
395
 
396
396
  ---
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://skill-map.dev/spec/v0/conformance-case.schema.json",
3
+ "id": "orphan-detection",
4
+ "description": "Deleting a file with no replacement triggers the orphan branch of the rename heuristic: exactly one issue with ruleId `orphan` is emitted, severity `info`, naming the dead path. The remaining survivor node is reported normally.",
5
+ "fixture": "orphan-after",
6
+ "setup": {
7
+ "priorScans": [
8
+ { "fixture": "orphan-before" }
9
+ ]
10
+ },
11
+ "invoke": {
12
+ "verb": "scan",
13
+ "flags": ["--changed", "--json"]
14
+ },
15
+ "assertions": [
16
+ { "type": "exit-code", "value": 0 },
17
+ { "type": "json-path", "path": "$.stats.nodesCount", "equals": 1 },
18
+ { "type": "json-path", "path": "$.stats.issuesCount", "equals": 1 },
19
+ { "type": "json-path", "path": "$.issues[0].ruleId", "equals": "orphan" },
20
+ { "type": "json-path", "path": "$.issues[0].severity", "equals": "info" }
21
+ ]
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://skill-map.dev/spec/v0/conformance-case.schema.json",
3
+ "id": "rename-high",
4
+ "description": "Moving a single file with identical body across the rename triggers a high-confidence auto-rename: NO issue is emitted (per spec/db-schema.md §Rename detection), the new path is the only node in the result, and the rename heuristic operates silently.",
5
+ "fixture": "rename-high-after",
6
+ "setup": {
7
+ "priorScans": [
8
+ { "fixture": "rename-high-before" }
9
+ ]
10
+ },
11
+ "invoke": {
12
+ "verb": "scan",
13
+ "flags": ["--changed", "--json"]
14
+ },
15
+ "assertions": [
16
+ { "type": "exit-code", "value": 0 },
17
+ { "type": "json-path", "path": "$.stats.nodesCount", "equals": 1 },
18
+ { "type": "json-path", "path": "$.stats.issuesCount", "equals": 0 },
19
+ { "type": "json-path", "path": "$.nodes[0].path", "equals": "skills/bar.md" }
20
+ ]
21
+ }
@@ -54,7 +54,7 @@ These have their own conformance cases even though they are not JSON Schemas.
54
54
  | F | Nonce mismatch | — | 🔴 missing | Blocked by Step 10. `sm record` with wrong nonce → exit 4. |
55
55
  | G | Reap | — | 🔴 missing | Blocked by Step 10. Set TTL to 1s; claim; wait; next `sm job run` reaps with reason `abandoned`. |
56
56
  | H | `run.*` event envelope for Skill agent | — | 🔴 missing | Blocked by Step 10. Skill-agent flow emits synthetic `r-ext-*` run envelope around one job. |
57
- | I | Rename heuristic | | 🔴 missing | Blocked by Step 5. Move a file; same-`body_hash` high-confidence auto-rename; `state_*` FK rows migrated; no issue emitted. |
57
+ | I | Rename heuristic | `rename-high`, `orphan-detection` | 🟢 covered | High-confidence rename emits no issue and the new path is the sole node. Orphan branch emits exactly one `orphan` issue (severity `info`) when a deleted node has no replacement. Medium / ambiguous branches are exercised by `src/test/rename-heuristic.test.ts` until the conformance schema grows richer assertions. |
58
58
  | J | Plugin DDL rejection | — | 🔴 missing | Blocked by Step 9. Plugin migration referencing `state_jobs` → disabled with `invalid-manifest`. |
59
59
  | K | Plugin prefix injection | — | 🔴 missing | Blocked by Step 9. Plugin declares `CREATE TABLE foo` → kernel applies as `plugin_<id>_foo`. |
60
60
  | L | Elapsed-time reporting | — | 🔴 missing | Blocked by Step 4 (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. |
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: orphan-keep
3
+ description: Fixture node that survives across before/after, so the after-state still has at least one node.
4
+ metadata:
5
+ version: 1.0.0
6
+ stability: stable
7
+ author: conformance
8
+ ---
9
+
10
+ # Survivor
11
+
12
+ Keeps the after-state graph non-empty so we can assert that the orphan
13
+ issue is the only one emitted.
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: orphan-keep
3
+ description: Fixture node that survives across before/after, so the after-state still has at least one node.
4
+ metadata:
5
+ version: 1.0.0
6
+ stability: stable
7
+ author: conformance
8
+ ---
9
+
10
+ # Survivor
11
+
12
+ Keeps the after-state graph non-empty so we can assert that the orphan
13
+ issue is the only one emitted.
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: orphan-lonely
3
+ description: Fixture node that gets deleted in the after-state to trigger the orphan path.
4
+ metadata:
5
+ version: 1.0.0
6
+ stability: stable
7
+ author: conformance
8
+ ---
9
+
10
+ # Orphan lonely body
11
+
12
+ This file disappears in `orphan-after`. With no replacement matching
13
+ its hashes, the rename heuristic emits an `orphan` issue.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: rename-high-foo
3
+ description: Fixture node for the high-confidence rename conformance case.
4
+ metadata:
5
+ version: 1.0.0
6
+ stability: stable
7
+ author: conformance
8
+ ---
9
+
10
+ # Rename high body
11
+
12
+ Body content kept identical between the before and after fixtures so
13
+ the body hash matches across the rename. Move it to `bar.md` to trigger
14
+ a high-confidence auto-rename.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: rename-high-foo
3
+ description: Fixture node for the high-confidence rename conformance case.
4
+ metadata:
5
+ version: 1.0.0
6
+ stability: stable
7
+ author: conformance
8
+ ---
9
+
10
+ # Rename high body
11
+
12
+ Body content kept identical between the before and after fixtures so
13
+ the body hash matches across the rename. Move it to `bar.md` to trigger
14
+ a high-confidence auto-rename.
package/db-schema.md CHANGED
@@ -134,6 +134,28 @@ One row per rule-emitted issue, matching [`schemas/issue.schema.json`](./schemas
134
134
 
135
135
  Indexes: `ix_scan_issues_rule_id`, `ix_scan_issues_severity`.
136
136
 
137
+ ### `scan_meta`
138
+
139
+ Single-row table holding the metadata of the last persisted scan. Lets `loadScanResult` return the real `scope` / `roots` / `scannedAt` / `scannedBy` / `adapters` / `stats.filesWalked|filesSkipped|durationMs` instead of synthesising them. Replaced atomically with the rest of the `scan_*` zone on every `sm scan`.
140
+
141
+ `nodesCount` / `linksCount` / `issuesCount` are not stored here — they derive from `COUNT(*)` of the sibling tables.
142
+
143
+ | Column | Type | Constraint |
144
+ |---|---|---|
145
+ | `id` | INTEGER | PRIMARY KEY, CHECK `id = 1` |
146
+ | `scope` | TEXT | NOT NULL, CHECK in (`project`, `global`) |
147
+ | `roots_json` | TEXT | NOT NULL | JSON array of strings (filesystem roots walked). |
148
+ | `scanned_at` | INTEGER | NOT NULL | Unix milliseconds. |
149
+ | `scanned_by_name` | TEXT | NOT NULL |
150
+ | `scanned_by_version` | TEXT | NOT NULL |
151
+ | `scanned_by_spec_version` | TEXT | NOT NULL |
152
+ | `adapters_json` | TEXT | NOT NULL | JSON array of adapter ids. |
153
+ | `stats_files_walked` | INTEGER | NOT NULL |
154
+ | `stats_files_skipped` | INTEGER | NOT NULL |
155
+ | `stats_duration_ms` | INTEGER | NOT NULL |
156
+
157
+ No indexes (single row).
158
+
137
159
  ---
138
160
 
139
161
  ## Table catalog: zone `state_`
package/index.json CHANGED
@@ -190,31 +190,38 @@
190
190
  }
191
191
  ]
192
192
  },
193
- "specPackageVersion": "0.5.1",
193
+ "specPackageVersion": "0.6.0",
194
194
  "integrity": {
195
195
  "algorithm": "sha256",
196
196
  "files": {
197
- "CHANGELOG.md": "b810067bdba655d066242db3159a157880250257c6cff03e96acc614758b8d1d",
197
+ "CHANGELOG.md": "e74921b61ca835c4ebc8f2a4814d2c9513a59ce47c8c719b4d17865b39d93cba",
198
198
  "README.md": "8bd57e02d9a9d3f0a4efd18c0f0bd1f4bbe13eb206add0317659e48eab435e7e",
199
199
  "architecture.md": "99f9d6a1a90e6c96d3c8a6f36c2650da4a1af0a1bc21173ea8eb2c492008539a",
200
- "cli-contract.md": "4f01df640f39eacbb2192d543288e670de9ce2b89bf3931bc09f8488eb3baaea",
200
+ "cli-contract.md": "bab14bb72ddd8a57e00808f7f12741c63a33da99055b278e4407ab9b4bb7e2c1",
201
201
  "conformance/README.md": "79c5e63f18a368951dc9f3e31e9bf9574de3f8b97150b2d75365d4febd8eb6dc",
202
202
  "conformance/cases/basic-scan.json": "24623da0cad8c8c54b3ff9b09820ea1276fe8b8f0fc680bf6e8abeb4edb8e424",
203
203
  "conformance/cases/kernel-empty-boot.json": "175524674b14d993d29f10080d7697074b3a2eee25b359ff903344d73c6acc98",
204
- "conformance/coverage.md": "db9518f5a560b3df01093107de5c09ff38d478ae07c0476eea92644677fb1d83",
204
+ "conformance/cases/orphan-detection.json": "7fea6e866d775d09cadb70ccd764f6c8317ca61316c6d187a97cb2466db4e19e",
205
+ "conformance/cases/rename-high.json": "f23513893e25fc4259db06a497906137de981da775d8ab2ef262554d54af5f27",
206
+ "conformance/coverage.md": "a67fb38c69765bb3cafb78c75e211c530ab00a88d8ccf0254335d3b6d4f69b8d",
205
207
  "conformance/fixtures/minimal-claude/agents/reviewer.md": "d0dd681ba63838301e480116aa09825329f01832b0116de5c5476fdd8a5dcf54",
206
208
  "conformance/fixtures/minimal-claude/commands/status.md": "3f36e053fd1c059ffd902f84a55be8a458c26072f97cb37dd7e97314ae2a9bf5",
207
209
  "conformance/fixtures/minimal-claude/hooks/pre-commit.md": "ec9cec8ac4ce34d40ec055ffd90e8f06ea3e5764d6ec3ee84e0d97de71b930c7",
208
210
  "conformance/fixtures/minimal-claude/notes/architecture.md": "5a7e6fdbb1556733dacebad63758057dc1e19090b5a983292c0c65e90b98bcf1",
209
211
  "conformance/fixtures/minimal-claude/skills/hello.md": "8598074020430f294ff1eac39876302448f004b6c48446d453092159319bcbee",
212
+ "conformance/fixtures/orphan-after/skills/keep.md": "a6fdc33104cb2e8092f386a427784685eee1c8d7cb8fddb934cf65984143c7fd",
213
+ "conformance/fixtures/orphan-before/skills/keep.md": "a6fdc33104cb2e8092f386a427784685eee1c8d7cb8fddb934cf65984143c7fd",
214
+ "conformance/fixtures/orphan-before/skills/lonely.md": "4ba2bb336fa46644d2b0dfef4c3b97879ecbf693b825a5348f032d1af049d80a",
210
215
  "conformance/fixtures/preamble-v1.txt": "1e0aeef224b64477bdc13a949c3ad402e68249caf499ecdba1302371677c068b",
211
- "db-schema.md": "ce4f9ddf84fa2aad746b44d6d9142b29b23c339e4915f97928489e035100fa55",
216
+ "conformance/fixtures/rename-high-after/skills/bar.md": "16f7678829c7702f8ebaeef920a891756da198466a1884badd8d8b4a7d1bab6a",
217
+ "conformance/fixtures/rename-high-before/skills/foo.md": "16f7678829c7702f8ebaeef920a891756da198466a1884badd8d8b4a7d1bab6a",
218
+ "db-schema.md": "e9b436b143e2e56985c69498c7d48527bde0baa441be586e2740a4051b366c2d",
212
219
  "interfaces/security-scanner.md": "81dc3dc2c439a75f4603b6d52e714f44ac564032c8aa424385ebbf4502adae3e",
213
220
  "job-events.md": "08796b7fbeb55e5b03cf3bc394224e70a23438a4d15a46ad1d70121c2c68b967",
214
221
  "job-lifecycle.md": "1fe88b1a2ed204e41bb41ac172fbb3e912dccd0dd8a1f8ea8e21a681b336d6ee",
215
222
  "plugin-kv-api.md": "04b2178f46fb88adeae9240df9c9e1761b660396072001dac32cd402e11a2d7d",
216
223
  "prompt-preamble.md": "23a8eff0477fbbc46192a27781bc781bda4202bb9c669b7a7a002b0d668146b0",
217
- "schemas/conformance-case.schema.json": "2740874e00269de6d8121300339401d0283197b6d97dcd77538ec5d108b14de2",
224
+ "schemas/conformance-case.schema.json": "d69c501bbca079da0ca87685eb4cbdbc2e405334469fc937929ca9134e01a2b3",
218
225
  "schemas/execution-record.schema.json": "ec0f3acf1d0ce099c059d73eb434936bfd1bcf12023693bd572efb2a7352faa6",
219
226
  "schemas/extensions/action.schema.json": "c7520d3cefecf75d27d3e04473821fd6e5dc5a7924eede147f74275ba6caccad",
220
227
  "schemas/extensions/adapter.schema.json": "429b865e738664bb437ac62690a2d7282ce992339fbb300417c73625f5cdb7c8",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skill-map/spec",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "JSON Schemas, prose contracts, and conformance suite for the skill-map specification.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,12 +28,32 @@
28
28
  },
29
29
  "setup": {
30
30
  "type": "object",
31
- "description": "Pre-invocation toggles. All default to `false`.",
31
+ "description": "Pre-invocation toggles and ordered staging steps. All toggles default to `false`. `priorScans`, when present, run BEFORE the main `invoke` so a case can establish a prior snapshot the heuristic-driven verbs (e.g. `sm scan` rename detection) can react to.",
32
32
  "additionalProperties": false,
33
33
  "properties": {
34
34
  "disableAllAdapters": { "type": "boolean" },
35
35
  "disableAllDetectors": { "type": "boolean" },
36
- "disableAllRules": { "type": "boolean" }
36
+ "disableAllRules": { "type": "boolean" },
37
+ "priorScans": {
38
+ "type": "array",
39
+ "description": "Ordered staging scans, each running before the next. For step N, the runner first replaces the scope's adapter content with `priorScans[N].fixture`, then invokes `sm scan` with the supplied flags. After the last step, the runner copies the top-level `fixture` (overwriting again) and runs the main `invoke`. The DB persists across all steps because `.skill-map/` is preserved between fixture swaps.",
40
+ "items": {
41
+ "type": "object",
42
+ "required": ["fixture"],
43
+ "additionalProperties": false,
44
+ "properties": {
45
+ "fixture": {
46
+ "type": "string",
47
+ "description": "Folder under `conformance/fixtures/` to copy into the scope, replacing every non-`.skill-map/` directory."
48
+ },
49
+ "flags": {
50
+ "type": "array",
51
+ "description": "Flags passed to `sm scan`. Default: empty (full scan).",
52
+ "items": { "type": "string" }
53
+ }
54
+ }
55
+ }
56
+ }
37
57
  }
38
58
  },
39
59
  "invoke": {