@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/job-lifecycle.md
CHANGED
|
@@ -53,11 +53,12 @@ Any other transition attempt MUST be rejected and MUST NOT mutate state. Impleme
|
|
|
53
53
|
2. Resolve the target node (`bodyHash`, `frontmatterHash`). Fail with exit 5 if the node does not exist.
|
|
54
54
|
3. Compute `contentHash = sha256(actionId + actionVersion + bodyHash + frontmatterHash + promptTemplateHash)`.
|
|
55
55
|
4. **Duplicate check**: query `state_jobs` for any row with `(actionId, actionVersion, nodeId, contentHash)` AND `status IN ('queued', 'running')`. If found, refuse with exit 3 and print the existing job id (unless `--force`).
|
|
56
|
-
5. Compute `ttlSeconds
|
|
57
|
-
6.
|
|
58
|
-
7.
|
|
59
|
-
8.
|
|
60
|
-
9.
|
|
56
|
+
5. Compute `ttlSeconds` per §TTL resolution below. Frozen on `state_jobs.ttlSeconds` for the life of this job.
|
|
57
|
+
6. Resolve `priority` (integer, default `0`). Precedence (lowest → highest): action manifest `defaultPriority` → user config `jobs.perActionPriority.<actionId>` → flag `--priority <n>`. Higher runs first; ties broken by `createdAt ASC`. Negative values are permitted and run after the default bucket. The resolved value is frozen on `state_jobs.priority` at submit time and is immutable for the life of the job.
|
|
58
|
+
7. Generate `nonce` (implementation-chosen; MUST be cryptographically random, ≥ 128 bits of entropy).
|
|
59
|
+
8. Render the job file at `.skill-map/jobs/<id>.md`, applying the canonical preamble (see `prompt-preamble.md`).
|
|
60
|
+
9. Insert a row in `state_jobs` with `status = 'queued'`, `createdAt = now`.
|
|
61
|
+
10. Return the job id.
|
|
61
62
|
|
|
62
63
|
`--all` fans out one job per node matching the action's `preconditions`. Each fan-out job is independent: some may be duplicates and be refused, others succeed. The CLI reports a summary.
|
|
63
64
|
|
|
@@ -88,7 +89,7 @@ The second `AND status = 'queued'` guards against a race where two runners selec
|
|
|
88
89
|
|
|
89
90
|
**Non-SQLite implementations**: MUST provide an equivalent single-statement atomic transition. A two-step `SELECT then UPDATE` is NOT acceptable — it is observable as a double-claim bug.
|
|
90
91
|
|
|
91
|
-
`sm job claim` exposes this primitive to Skill
|
|
92
|
+
`sm job claim` exposes this primitive to Skill agents (and any driving adapter that wants to drain from outside a CLI-runner loop): returns the id on stdout (exit 0) or exits 1 if the queue is empty.
|
|
92
93
|
|
|
93
94
|
---
|
|
94
95
|
|
|
@@ -113,16 +114,47 @@ Number of rows affected is reported as `run.reap.completed.reapedCount` in the e
|
|
|
113
114
|
|
|
114
115
|
Implementations MAY expose `sm job reap` as an explicit verb for diagnostics, but MUST perform reaping automatically inside `sm job run`.
|
|
115
116
|
|
|
116
|
-
### TTL
|
|
117
|
+
### TTL resolution
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
The kernel resolves the effective TTL for a new job in three conceptual steps. The resolved value is written to `state_jobs.ttlSeconds` at submit time and is immutable for the life of the job.
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
2. Action manifest (`expectedDurationSeconds`).
|
|
122
|
-
3. User config override (`jobs.perActionTtl.<actionId>`).
|
|
123
|
-
4. Flag (`sm job submit --ttl <seconds>`).
|
|
121
|
+
#### Step 1 — Base duration
|
|
124
122
|
|
|
125
|
-
|
|
123
|
+
A seconds integer that represents how long the action is expected to run before the grace multiplier kicks in:
|
|
124
|
+
|
|
125
|
+
1. Action manifest `expectedDurationSeconds`, if declared.
|
|
126
|
+
2. Otherwise, config `jobs.ttlSeconds` (default: `3600`).
|
|
127
|
+
|
|
128
|
+
The base duration exists even for actions that cannot estimate their own runtime (typically `mode: local`); the global config value ensures the formula below is always well-defined.
|
|
129
|
+
|
|
130
|
+
#### Step 2 — Computed TTL
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
computed = max(base × jobs.graceMultiplier, jobs.minimumTtlSeconds)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Config defaults: `jobs.graceMultiplier = 3`, `jobs.minimumTtlSeconds = 60`.
|
|
137
|
+
|
|
138
|
+
`minimumTtlSeconds` is a **floor**, not a default. It guarantees no job is claimed with a sub-minute deadline regardless of how small the base duration is. It never participates as an initial value.
|
|
139
|
+
|
|
140
|
+
#### Step 3 — User overrides
|
|
141
|
+
|
|
142
|
+
Two optional overrides, evaluated in order; the later one wins and replaces everything above it:
|
|
143
|
+
|
|
144
|
+
1. Config `jobs.perActionTtl.<actionId>` — integer seconds. Replaces the computed TTL entirely; the formula is skipped for that action id.
|
|
145
|
+
2. Flag `sm job submit --ttl <seconds>` — integer seconds. Highest precedence. Replaces anything.
|
|
146
|
+
|
|
147
|
+
Negative or zero values MUST be rejected with exit 2 at submit time.
|
|
148
|
+
|
|
149
|
+
#### Worked examples
|
|
150
|
+
|
|
151
|
+
| Action manifest | Config | Flag | Result |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| `expectedDurationSeconds: 120` | defaults | — | `max(120 × 3, 60) = 360` |
|
|
154
|
+
| none | defaults | — | `max(3600 × 3, 60) = 10800` |
|
|
155
|
+
| `expectedDurationSeconds: 10` | defaults | — | `max(10 × 3, 60) = 60` (floor bites) |
|
|
156
|
+
| `expectedDurationSeconds: 120` | `jobs.perActionTtl.foo: 900` | — | `900` (override skips formula) |
|
|
157
|
+
| any | any | `--ttl 45` | `45` (flag wins outright) |
|
|
126
158
|
|
|
127
159
|
---
|
|
128
160
|
|
|
@@ -158,7 +190,7 @@ Post-completion, the check is NOT performed: resubmitting a completed job is alw
|
|
|
158
190
|
|
|
159
191
|
## Concurrency
|
|
160
192
|
|
|
161
|
-
|
|
193
|
+
Through `v1.0` (spec `v0.x`): **one job at a time**. `sm job run --all` drains sequentially. Enforced by the claim semantics above — there is no pool or scheduler.
|
|
162
194
|
|
|
163
195
|
The event schema carries a `jobId` on every event specifically so that parallel execution becomes a non-breaking extension. A future implementation MAY spawn multiple claim/run loops concurrently and interleave events; consumers identify which job an event belongs to by `jobId`.
|
|
164
196
|
|
|
@@ -211,3 +243,5 @@ The state machine diagram above is **stable** as of spec v1.0.0. Adding a new st
|
|
|
211
243
|
The `contentHash` formula is **stable**. Changing what goes into the hash breaks duplicate detection across versions and is a major bump.
|
|
212
244
|
|
|
213
245
|
The atomic-claim semantics are **stable**. A double-claim would be a silent correctness bug observable through event-stream anomalies.
|
|
246
|
+
|
|
247
|
+
The TTL resolution procedure (§TTL resolution) is **stable** as of the next spec release. The three-step structure (base → computed → overrides) and the four config keys (`jobs.ttlSeconds`, `jobs.graceMultiplier`, `jobs.minimumTtlSeconds`, `jobs.perActionTtl`) are locked; adding a new override source is a minor bump, changing the formula shape is a major bump.
|
package/package.json
CHANGED
package/plugin-kv-api.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Normative contract for plugin-accessible persistence. Two modes exist (see `db-schema.md` for the catalog entries):
|
|
4
4
|
|
|
5
|
-
- **Mode A — KV**: plugin uses the kernel-provided `ctx.store.*` accessor. Backed by the shared `
|
|
5
|
+
- **Mode A — KV**: plugin uses the kernel-provided `ctx.store.*` accessor. Backed by the shared `state_plugin_kvs` table.
|
|
6
6
|
- **Mode B — Dedicated**: plugin owns its own tables with the `plugin_<normalizedId>_` prefix, migrated by the kernel.
|
|
7
7
|
|
|
8
8
|
This document defines mode A in full and clarifies the boundary with mode B. Implementations MUST expose this API to every plugin that declares `"storage": { "mode": "kv" }` in its manifest.
|
|
@@ -54,7 +54,7 @@ Operations MAY be additionally scoped by `nodePath`:
|
|
|
54
54
|
- **Global KV (no `nodePath`)**: `{pluginId, nodePath: null, key}`. One row per plugin + key.
|
|
55
55
|
- **Node-scoped KV (with `nodePath`)**: `{pluginId, nodePath: "<path>", key}`. One row per plugin + node + key.
|
|
56
56
|
|
|
57
|
-
Both scopes share the same underlying `
|
|
57
|
+
Both scopes share the same underlying `state_plugin_kvs` table (see `db-schema.md`). The `nodePath` column is nullable; implementations MUST use a sentinel empty string internally when the backing engine rejects NULL in composite primary keys.
|
|
58
58
|
|
|
59
59
|
### Semantics
|
|
60
60
|
|
|
@@ -182,10 +182,12 @@ A plugin MUST declare **exactly one** storage mode. Mixing modes in the same plu
|
|
|
182
182
|
|
|
183
183
|
## Backup and retention
|
|
184
184
|
|
|
185
|
-
- Mode A rows are stored in `
|
|
185
|
+
- Mode A rows are stored in `state_plugin_kvs` and are backed up with `sm db backup`.
|
|
186
186
|
- Mode B rows live in the plugin's dedicated tables, prefixed `plugin_<id>_`, and are likewise backed up.
|
|
187
|
-
- `sm plugins disable <id>` does NOT drop the plugin's data — disabled plugins keep their KV rows and dedicated tables. `sm plugins forget <id>` (post
|
|
188
|
-
- `sm db reset` drops `scan_
|
|
187
|
+
- `sm plugins disable <id>` does NOT drop the plugin's data — disabled plugins keep their KV rows and dedicated tables. `sm plugins forget <id>` (deferred to post-`v1.0`) is the verb that wipes.
|
|
188
|
+
- `sm db reset` (no modifier) drops only `scan_*`. Plugin KV rows (mode A) and plugin-dedicated tables (mode B) are **preserved** — the reset is non-destructive to plugin storage.
|
|
189
|
+
- `sm db reset --state` drops `state_*` AND every `plugin_<normalized_id>_*` table, which includes `state_plugin_kvs` (mode A) AND the plugin-dedicated tables (mode B). The CLI MUST require interactive confirmation unless `--yes` is passed.
|
|
190
|
+
- `sm db reset --hard` deletes the DB file entirely, destroying all plugin storage regardless of mode.
|
|
189
191
|
|
|
190
192
|
---
|
|
191
193
|
|
|
@@ -195,7 +197,7 @@ Mode A is perfectly isolated at the row level: the accessor physically cannot se
|
|
|
195
197
|
|
|
196
198
|
Mode B is **isolated against accidents, not hostile code**. The scoped `Database` wrapper rejects cross-namespace queries at runtime. But a malicious plugin running in the same JavaScript process can bypass the wrapper by importing raw engine bindings directly. Plugins are user-placed code; the kernel trusts the user's judgement at install time.
|
|
197
199
|
|
|
198
|
-
Post-v1.0 work: signed manifest, sandboxed worker-thread isolation, per-plugin DB file. None of these land before
|
|
200
|
+
Post-v1.0 work: signed manifest, sandboxed worker-thread isolation, per-plugin DB file. None of these land before `v0.5.0`.
|
|
199
201
|
|
|
200
202
|
---
|
|
201
203
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/extensions/action.schema.json",
|
|
4
|
+
"title": "ExtensionAction",
|
|
5
|
+
"description": "Manifest shape for an `Action` extension. An action operates on one or more nodes in one of two modes: `local` (code runs in-process, returns a report JSON directly) or `invocation-template` (kernel renders a prompt, a runner executes it, the callback closes the job). The `mode` discriminator drives which additional fields are required.",
|
|
6
|
+
"allOf": [
|
|
7
|
+
{ "$ref": "base.schema.json" }
|
|
8
|
+
],
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["id", "kind", "version", "mode", "reportSchemaRef"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"kind": { "const": "action" },
|
|
14
|
+
"mode": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["local", "invocation-template"],
|
|
17
|
+
"description": "`local`: the plugin's code computes the report synchronously, no job file, no runner. `invocation-template`: the kernel renders a prompt + preamble into a job file; a runner executes it; `sm record` closes the job."
|
|
18
|
+
},
|
|
19
|
+
"reportSchemaRef": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Absolute or relative reference to the JSON Schema the report MUST validate against. MUST extend `report-base.schema.json` (directly or transitively). Validation failure → job transitions to `failed` with reason `report-invalid`."
|
|
22
|
+
},
|
|
23
|
+
"expectedDurationSeconds": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 1,
|
|
26
|
+
"description": "Best-effort estimate of wall-clock duration. Drives TTL (`ttl = max(expectedDurationSeconds × graceMultiplier, minimumTtlSeconds)`). Required for `invocation-template`; advisory for `local`."
|
|
27
|
+
},
|
|
28
|
+
"promptTemplateRef": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Path (relative to the extension file) to the prompt template the kernel renders at `sm job submit`. REQUIRED when `mode: invocation-template`; FORBIDDEN when `mode: local`. The template MUST NOT interpolate user text outside `<user-content>` blocks (see `prompt-preamble.md`)."
|
|
31
|
+
},
|
|
32
|
+
"precondition": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"description": "Declarative filter that nodes must satisfy for this action to be applicable. Consumed by `--all` fan-out, UI button gating, `sm actions show`.",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"properties": {
|
|
37
|
+
"kind": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": { "type": "string", "enum": ["skill", "agent", "command", "hook", "note"] },
|
|
40
|
+
"description": "Node kinds this action accepts. Omitted → any kind."
|
|
41
|
+
},
|
|
42
|
+
"adapter": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"items": { "type": "string" },
|
|
45
|
+
"description": "Adapter ids whose nodes this action accepts. Omitted → any adapter."
|
|
46
|
+
},
|
|
47
|
+
"stability": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "string", "enum": ["experimental", "stable", "deprecated"] },
|
|
50
|
+
"description": "Node stability filter."
|
|
51
|
+
},
|
|
52
|
+
"custom": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": { "type": "string" },
|
|
55
|
+
"description": "Free-form precondition strings the kernel forwards to the action for runtime evaluation. Example: `frontmatter.metadata.source != null`."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"expectedTools": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"description": "Hint to a Skill agent / CLI runner about what tools the rendered prompt expects access to (`Bash`, `Read`, `WebSearch`, …). Host MAY filter or warn. No normative enforcement in v0.",
|
|
62
|
+
"items": { "type": "string" }
|
|
63
|
+
},
|
|
64
|
+
"fanOutPolicy": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"enum": ["per-node", "batch"],
|
|
67
|
+
"default": "per-node",
|
|
68
|
+
"description": "`per-node` (default): `sm job submit --all` produces one job per matching node. `batch`: produces one job whose prompt template receives the full list. Batch actions tend to hit context limits; use sparingly."
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"allOf": [
|
|
72
|
+
{
|
|
73
|
+
"if": { "properties": { "mode": { "const": "invocation-template" } } },
|
|
74
|
+
"then": { "required": ["promptTemplateRef", "expectedDurationSeconds"] }
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"if": { "properties": { "mode": { "const": "local" } } },
|
|
78
|
+
"then": { "not": { "required": ["promptTemplateRef"] } }
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/extensions/adapter.schema.json",
|
|
4
|
+
"title": "ExtensionAdapter",
|
|
5
|
+
"description": "Manifest shape for an `Adapter` extension. An adapter recognizes a platform (Claude Code, Codex, Gemini, Obsidian vault, generic MD) and classifies each candidate file into a node `kind`. Exactly zero or one adapter MUST match any given file; multiple matches → the kernel emits an issue `adapter-ambiguous` and the file is left unclassified. Stability: stable as of spec v1.0.0 except where noted.",
|
|
6
|
+
"allOf": [
|
|
7
|
+
{ "$ref": "base.schema.json" }
|
|
8
|
+
],
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["id", "kind", "version", "emits", "defaultRefreshAction"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"kind": { "const": "adapter" },
|
|
14
|
+
"emits": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"description": "Node kinds this adapter may classify files into.",
|
|
17
|
+
"minItems": 1,
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"enum": ["skill", "agent", "command", "hook", "note"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"roots": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"description": "Path globs (relative to scope root) that this adapter SHOULD be consulted for. Advisory — the kernel walks all roots and consults every adapter regardless, but this field lets `sm doctor` warn when no file matched a specific adapter (i.e. the adapter was loaded for a platform that isn't in this scope).",
|
|
26
|
+
"items": { "type": "string" }
|
|
27
|
+
},
|
|
28
|
+
"defaultRefreshAction": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"description": "Map from node `kind` to the action id the UI's probabilistic-refresh surface (`🧠 prob`) submits for that kind. MUST cover every kind listed in `emits`. Referenced action ids MUST resolve at load time; a dangling reference disables the adapter with status `invalid-manifest`.",
|
|
31
|
+
"propertyNames": {
|
|
32
|
+
"enum": ["skill", "agent", "command", "hook", "note"]
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/extensions/audit.schema.json",
|
|
4
|
+
"title": "ExtensionAudit",
|
|
5
|
+
"description": "Manifest shape for an `Audit` extension. An audit is a hardcoded, deterministic workflow that composes rules and/or local actions into a single report. Audits MUST NOT submit LLM-backed actions — their defining property is reproducibility. An audit that needs probabilistic signal is the wrong shape; emit a `Findings` surface via LLM verbs instead.",
|
|
6
|
+
"allOf": [
|
|
7
|
+
{ "$ref": "base.schema.json" }
|
|
8
|
+
],
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["id", "kind", "version", "composes", "reportSchemaRef"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"kind": { "const": "audit" },
|
|
14
|
+
"composes": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"description": "Ordered list of rule ids and/or local action ids the audit executes in sequence. The kernel resolves each id in the registry at load time; a dangling reference disables the audit with status `invalid-manifest`.",
|
|
17
|
+
"minItems": 1,
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"required": ["kind", "id"],
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"kind": { "type": "string", "enum": ["rule", "action"] },
|
|
24
|
+
"id": { "type": "string" },
|
|
25
|
+
"failFast": {
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"default": false,
|
|
28
|
+
"description": "If true, the audit stops at the first error-severity issue this step emits. Default: collect everything and continue."
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"reportSchemaRef": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Reference to the JSON Schema of the audit's report shape. Audits do NOT extend `report-base.schema.json` — they are deterministic and therefore carry no `safety` / `confidence`. Their shape is kind-specific."
|
|
36
|
+
},
|
|
37
|
+
"exitCodeMap": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"description": "How the audit report maps to CLI exit codes. Absent → standard behaviour: exit 0 on pass, 1 on any error-severity issue, 2 on operational error.",
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"properties": {
|
|
42
|
+
"pass": { "type": "integer", "default": 0 },
|
|
43
|
+
"fail": { "type": "integer", "default": 1 }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/extensions/base.schema.json",
|
|
4
|
+
"title": "ExtensionBase",
|
|
5
|
+
"description": "Base manifest shape common to every extension kind. Kind-specific schemas (`adapter`, `detector`, `rule`, `action`, `audit`, `renderer`) extend this via `allOf` and add a discriminant `kind` literal plus kind-specific fields. camelCase keys throughout. `additionalProperties: false` — the extension manifest shape is kernel-controlled and unknown keys are a bug.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["id", "kind", "version"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
|
13
|
+
"description": "Kebab-case identifier unique within the extension's kind across all loaded plugins. The built-in default set uses bare ids (`claude`, `frontmatter`, `trigger-collision`). Third-party extensions SHOULD namespace with their plugin id (`my-plugin/foo-detector`) to avoid collisions."
|
|
14
|
+
},
|
|
15
|
+
"kind": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": ["adapter", "detector", "rule", "action", "audit", "renderer"],
|
|
18
|
+
"description": "Discriminant. MUST match the file exporting this manifest; kind mismatch → load-error."
|
|
19
|
+
},
|
|
20
|
+
"version": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Extension semver. Bumped independently from the plugin version; frozen into `state_executions.extension_version` on every run for audit reproducibility."
|
|
23
|
+
},
|
|
24
|
+
"description": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "One-to-three sentences explaining what the extension does. Rendered by `sm <kind>s list`."
|
|
27
|
+
},
|
|
28
|
+
"stability": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": ["experimental", "stable", "deprecated"],
|
|
31
|
+
"default": "stable",
|
|
32
|
+
"description": "Maturity tag. The kernel MAY warn when an `experimental` extension is used in CI (`--strict`)."
|
|
33
|
+
},
|
|
34
|
+
"preconditions": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"description": "Free-form predicates the kernel evaluates before offering this extension. Canonical keys: `kind=<node-kind>`, `adapter=<adapter-id>`, `frontmatter.<path>=<value>`. Unknown keys are skipped with a warning, not rejected.",
|
|
37
|
+
"items": { "type": "string" }
|
|
38
|
+
},
|
|
39
|
+
"entry": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Optional path to the module exporting this extension (relative to the plugin root). Absent → the kernel uses the path listed in the plugin manifest's `extensions[]` array."
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/extensions/detector.schema.json",
|
|
4
|
+
"title": "ExtensionDetector",
|
|
5
|
+
"description": "Manifest shape for a `Detector` extension. A detector consumes a parsed node (frontmatter + body) and emits `Link[]` pointing to other nodes or to external URLs (the latter only if it is the designated URL-counter detector). Detectors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Rules.",
|
|
6
|
+
"allOf": [
|
|
7
|
+
{ "$ref": "base.schema.json" }
|
|
8
|
+
],
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["id", "kind", "version", "emitsLinkKinds", "defaultConfidence"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"kind": { "const": "detector" },
|
|
14
|
+
"emitsLinkKinds": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"description": "Subset of `Link.kind` values this detector is allowed to emit. Emitting an unlisted kind at runtime → kernel rejects the link and logs `detector-kind-violation`.",
|
|
17
|
+
"minItems": 1,
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"enum": ["invokes", "references", "mentions", "supersedes"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"defaultConfidence": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"enum": ["high", "medium", "low"],
|
|
26
|
+
"description": "Confidence attached to emitted links by default. Detectors MAY override per-link at emission time."
|
|
27
|
+
},
|
|
28
|
+
"scope": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": ["frontmatter", "body", "both"],
|
|
31
|
+
"default": "both",
|
|
32
|
+
"description": "Which part of the node this detector consumes. The kernel passes only the declared scope to the detector — a `frontmatter` detector that tries to read `body` receives an empty string."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/extensions/renderer.schema.json",
|
|
4
|
+
"title": "ExtensionRenderer",
|
|
5
|
+
"description": "Manifest shape for a `Renderer` extension. A renderer serializes the graph (or a filtered subgraph) into a string in a declared format. Renderers are invoked by `sm graph --format <format>` and `sm export`. Output MUST be byte-deterministic for the same input graph — the snapshot-test suite relies on this.",
|
|
6
|
+
"allOf": [
|
|
7
|
+
{ "$ref": "base.schema.json" }
|
|
8
|
+
],
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["id", "kind", "version", "format"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"kind": { "const": "renderer" },
|
|
14
|
+
"format": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Format identifier consumed by `--format`. Built-in set: `ascii`, `mermaid`, `dot`, `json`. Third-party renderers MAY register new format ids; collisions are a load-time error."
|
|
17
|
+
},
|
|
18
|
+
"contentType": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "MIME-like hint used by the Server when streaming rendered output over HTTP (e.g. `text/plain`, `image/svg+xml`, `application/json`). Advisory.",
|
|
21
|
+
"default": "text/plain"
|
|
22
|
+
},
|
|
23
|
+
"supportsFilter": {
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"default": true,
|
|
26
|
+
"description": "If true, the renderer accepts the `--filter` expression used by `sm export`. If false, `sm export --format <this>` rejects `--filter` with exit 2."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/extensions/rule.schema.json",
|
|
4
|
+
"title": "ExtensionRule",
|
|
5
|
+
"description": "Manifest shape for a `Rule` extension. A rule consumes the full graph (nodes + links) after all detectors have run and emits `Issue[]`. Rules MUST be deterministic: same graph in → same issues out, byte-for-byte. Any source of non-determinism (time, random, network) is forbidden and is a conformance violation.",
|
|
6
|
+
"allOf": [
|
|
7
|
+
{ "$ref": "base.schema.json" }
|
|
8
|
+
],
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["id", "kind", "version", "emitsRuleIds", "defaultSeverity"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"kind": { "const": "rule" },
|
|
14
|
+
"emitsRuleIds": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"description": "List of `rule_id` values this rule may emit on issues. Typically a singleton (`trigger-collision` → emits `trigger-collision`). A rule emitting a `rule_id` not in this list → kernel logs `rule-id-violation` but keeps the issue (forward compatibility).",
|
|
17
|
+
"minItems": 1,
|
|
18
|
+
"items": { "type": "string" }
|
|
19
|
+
},
|
|
20
|
+
"defaultSeverity": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"enum": ["error", "warn", "info"],
|
|
23
|
+
"description": "Severity attached by default to emitted issues. Rules MAY override per-issue."
|
|
24
|
+
},
|
|
25
|
+
"consumes": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": ["nodes", "links", "both"],
|
|
28
|
+
"default": "both",
|
|
29
|
+
"description": "Which slices of the graph the rule reads. The kernel MAY pass a restricted view when this is a strict subset."
|
|
30
|
+
},
|
|
31
|
+
"configurable": {
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"default": false,
|
|
34
|
+
"description": "If true, the rule reads its own config from `config_preferences` under the key `rules.<id>.<field>`. Implementations MAY surface these in `sm config`."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/frontmatter/agent.schema.json",
|
|
4
4
|
"title": "FrontmatterAgent",
|
|
5
|
-
"description": "Frontmatter shape for nodes classified as `agent`. Extends `base.schema.json`. Kind-specific
|
|
5
|
+
"description": "Frontmatter shape for nodes classified as `agent`. Extends `base.schema.json`. Kind-specific field: `model`. The `tools` and `allowedTools` fields live on `base` and apply here unchanged (see `base.schema.json`). Color, when needed, lives in `metadata.color` (inherited from base).",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -12,11 +12,6 @@
|
|
|
12
12
|
"model": {
|
|
13
13
|
"type": "string",
|
|
14
14
|
"description": "Model identifier for this agent (e.g. `sonnet`, `opus`, `haiku`, or a full `claude-*` id). Free-form string; adapters MAY validate against a platform-specific enum."
|
|
15
|
-
},
|
|
16
|
-
"tools": {
|
|
17
|
-
"type": "array",
|
|
18
|
-
"description": "Tools available to this agent. Tokens are platform-specific.",
|
|
19
|
-
"items": { "type": "string" }
|
|
20
15
|
}
|
|
21
16
|
}
|
|
22
17
|
}
|
|
@@ -34,6 +34,16 @@
|
|
|
34
34
|
"type": "string",
|
|
35
35
|
"description": "SPDX license identifier (e.g. `MIT`, `Apache-2.0`)."
|
|
36
36
|
},
|
|
37
|
+
"tools": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"description": "Tools this node requires when executed by a host. Allowlist semantics: if present, the host MUST restrict the node to exactly these tools (matching Claude Code subagent `tools` frontmatter). Free-form strings — token vocabulary is platform-specific (e.g. `Read`, `Grep`, `Bash`, `Bash(git add *)`). Kind-specific interpretation: an `agent` node's allowlist constrains the spawned subagent; a `skill` node's allowlist is a hint for the host since skills typically inherit their parent's tools. Omit to inherit the host's default. Denormalized into `node.tools_json` for query.",
|
|
40
|
+
"items": { "type": "string" }
|
|
41
|
+
},
|
|
42
|
+
"allowedTools": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"description": "Tools the host MAY use without triggering a per-use permission prompt while this node is active (matching Claude Code skill `allowed-tools` frontmatter). Pre-approval semantics, distinct from `tools` (allowlist): every tool not listed remains callable, but governed by the host's normal permission settings. Accepts argument-scoped patterns where the host supports them (`Bash(git add *)`). Free-form strings. Omit to defer to host defaults.",
|
|
45
|
+
"items": { "type": "string" }
|
|
46
|
+
},
|
|
37
47
|
"metadata": {
|
|
38
48
|
"type": "object",
|
|
39
49
|
"description": "Structured metadata. REQUIRED. `version` inside is REQUIRED; all other fields are optional.",
|