skillscript-runtime 0.10.0 → 0.13.2

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.
Files changed (79) hide show
  1. package/README.md +18 -42
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +18 -8
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/cli.js +5 -6
  6. package/dist/cli.js.map +1 -1
  7. package/dist/connectors/agent-noop.d.ts +2 -2
  8. package/dist/connectors/agent-noop.d.ts.map +1 -1
  9. package/dist/connectors/index.d.ts.map +1 -1
  10. package/dist/connectors/index.js +5 -3
  11. package/dist/connectors/index.js.map +1 -1
  12. package/dist/connectors/local-model-mcp.d.ts +3 -3
  13. package/dist/connectors/local-model-mcp.d.ts.map +1 -1
  14. package/dist/connectors/local-model.d.ts +30 -11
  15. package/dist/connectors/local-model.d.ts.map +1 -1
  16. package/dist/connectors/local-model.js +44 -12
  17. package/dist/connectors/local-model.js.map +1 -1
  18. package/dist/connectors/mcp-remote.d.ts +3 -3
  19. package/dist/connectors/mcp-remote.d.ts.map +1 -1
  20. package/dist/connectors/mcp.d.ts +3 -3
  21. package/dist/connectors/mcp.d.ts.map +1 -1
  22. package/dist/connectors/memory-store-mcp.d.ts +3 -3
  23. package/dist/connectors/memory-store-mcp.d.ts.map +1 -1
  24. package/dist/connectors/memory-store-mcp.js +3 -1
  25. package/dist/connectors/memory-store-mcp.js.map +1 -1
  26. package/dist/connectors/memory-store.d.ts +26 -6
  27. package/dist/connectors/memory-store.d.ts.map +1 -1
  28. package/dist/connectors/memory-store.js +84 -15
  29. package/dist/connectors/memory-store.js.map +1 -1
  30. package/dist/connectors/registry.d.ts +28 -5
  31. package/dist/connectors/registry.d.ts.map +1 -1
  32. package/dist/connectors/registry.js +34 -11
  33. package/dist/connectors/registry.js.map +1 -1
  34. package/dist/connectors/skill-store.d.ts +3 -3
  35. package/dist/connectors/skill-store.d.ts.map +1 -1
  36. package/dist/connectors/sqlite-skill-store.d.ts +27 -3
  37. package/dist/connectors/sqlite-skill-store.d.ts.map +1 -1
  38. package/dist/connectors/sqlite-skill-store.js +85 -58
  39. package/dist/connectors/sqlite-skill-store.js.map +1 -1
  40. package/dist/connectors/types.d.ts +86 -9
  41. package/dist/connectors/types.d.ts.map +1 -1
  42. package/dist/connectors/types.js.map +1 -1
  43. package/dist/errors.d.ts +6 -0
  44. package/dist/errors.d.ts.map +1 -1
  45. package/dist/errors.js +8 -0
  46. package/dist/errors.js.map +1 -1
  47. package/dist/runtime-config.d.ts +0 -2
  48. package/dist/runtime-config.d.ts.map +1 -1
  49. package/dist/runtime-config.js +1 -1
  50. package/dist/runtime-config.js.map +1 -1
  51. package/dist/runtime.d.ts.map +1 -1
  52. package/dist/runtime.js +24 -29
  53. package/dist/runtime.js.map +1 -1
  54. package/dist/testing/conformance.d.ts.map +1 -1
  55. package/dist/testing/conformance.js +12 -6
  56. package/dist/testing/conformance.js.map +1 -1
  57. package/examples/README.md +13 -13
  58. package/examples/connectors/LocalModelTemplate/LocalModelTemplate.ts +138 -0
  59. package/examples/connectors/LocalModelTemplate/README.md +134 -0
  60. package/examples/connectors/McpConnectorTemplate/McpConnectorTemplate.ts +200 -0
  61. package/examples/connectors/McpConnectorTemplate/README.md +153 -0
  62. package/examples/connectors/MemoryStoreTemplate/MemoryStoreTemplate.ts +155 -0
  63. package/examples/connectors/MemoryStoreTemplate/README.md +169 -0
  64. package/examples/connectors/README.md +47 -0
  65. package/examples/connectors/SkillStoreTemplate/README.md +143 -0
  66. package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +199 -0
  67. package/examples/{hello.skill.provenance.json → skillscripts/hello.skill.provenance.json} +1 -1
  68. package/package.json +1 -1
  69. package/scaffold/config.toml +8 -12
  70. package/scaffold/connectors.json +4 -4
  71. /package/examples/{classify-support-ticket.skill.md → skillscripts/classify-support-ticket.skill.md} +0 -0
  72. /package/examples/{cut-release-tag.skill.md → skillscripts/cut-release-tag.skill.md} +0 -0
  73. /package/examples/{doc-qa-with-citations.skill.md → skillscripts/doc-qa-with-citations.skill.md} +0 -0
  74. /package/examples/{feedback-sentiment-scan.skill.md → skillscripts/feedback-sentiment-scan.skill.md} +0 -0
  75. /package/examples/{hello.skill.md → skillscripts/hello.skill.md} +0 -0
  76. /package/examples/{morning-brief.skill.md → skillscripts/morning-brief.skill.md} +0 -0
  77. /package/examples/{queue-length-monitor.skill.md → skillscripts/queue-length-monitor.skill.md} +0 -0
  78. /package/examples/{service-health-watch.skill.md → skillscripts/service-health-watch.skill.md} +0 -0
  79. /package/examples/{youtrack-morning-sweep.skill.md → skillscripts/youtrack-morning-sweep.skill.md} +0 -0
@@ -0,0 +1,169 @@
1
+ # MemoryStoreTemplate — fork-me skeleton
2
+
3
+ A skeleton `MemoryStore` implementation for adopters writing their own. Not runnable; every method throws a `TODO` error. Copy this directory, rename, fill in the substrate-specific work.
4
+
5
+ Use this when you want skillscript memories backed by:
6
+ - A vector database (Pinecone, Weaviate, Qdrant, Chroma)
7
+ - A memory system (AMP, hosted memory API)
8
+ - A different SQL flavor (Postgres + pgvector, MySQL)
9
+ - An HTTP CRUD service
10
+ - Anything else with query + write semantics
11
+
12
+ ## The three legs
13
+
14
+ ```
15
+ MemoryStore (choose which connector)
16
+ ├── SqliteMemoryStore (bundled, SQLite + FTS5 — src/connectors/memory-store.ts)
17
+ ├── (no other bundled impl — vector DBs and remote services are adopter-side)
18
+ └── Your fork from this template
19
+ ```
20
+
21
+ The runtime is substrate-agnostic. Memories don't know which backend they're stored against — the contract is the `MemoryStore` interface, and any class implementing it works.
22
+
23
+ ## Forking workflow
24
+
25
+ ```bash
26
+ cp -r examples/connectors/MemoryStoreTemplate examples/connectors/MyMemoryStore
27
+ ```
28
+
29
+ 1. **Rename the class.** Convention: `<Substrate>MemoryStore` (e.g., `PineconeMemoryStore`, `PostgresMemoryStore`, `AmpMemoryStore`).
30
+ 2. **Define your config interface.** Edit `MemoryStoreTemplateConfig` to declare what your substrate needs (connection URL, API key, vault name, index name, etc.).
31
+ 3. **Implement each method.** Three methods + `staticCapabilities()`. Each has a TODO comment in the skeleton explaining what to do.
32
+ 4. **Update `staticCapabilities()`** to declare what your impl actually supports. The runtime + lint consult these flags before exercising features.
33
+ 5. **Wire from your adopter bootstrap:**
34
+
35
+ ```typescript
36
+ import { Registry } from "skillscript-runtime";
37
+ import { MyMemoryStore } from "./MyMemoryStore.js";
38
+
39
+ const registry = new Registry();
40
+ registry.registerMemoryStore("primary", new MyMemoryStore({
41
+ // your config
42
+ }));
43
+ ```
44
+
45
+ 6. **Validate via the conformance suite:**
46
+
47
+ ```typescript
48
+ import { describe, it } from "vitest";
49
+ import { MemoryStoreConformance } from "skillscript-runtime/testing";
50
+ import { MyMemoryStore } from "./MyMemoryStore.js";
51
+
52
+ describe("MyMemoryStore conformance", () => {
53
+ const tests = MemoryStoreConformance.buildTests({
54
+ build: () => new MyMemoryStore({ /* test config */ }),
55
+ ctor: MyMemoryStore,
56
+ });
57
+ for (const t of tests) it(`[${t.category}] ${t.name}`, t.run);
58
+ });
59
+ ```
60
+
61
+ The conformance suite verifies your impl honors the contract: method presence, return-type shape, capability flag self-consistency. Passes → runtime treats your impl interchangeably with `SqliteMemoryStore`.
62
+
63
+ ## Reference implementation
64
+
65
+ When in doubt about semantics, read the bundled impl:
66
+
67
+ - **`src/connectors/memory-store.ts`** — `SqliteMemoryStore`, SQLite + FTS5-backed. Schema lives in `bootstrap()` (memories table + FTS virtual table + AFTER INSERT/UPDATE/DELETE triggers). Single-process. Capability flags: `supports_writes: true`, `supports_tag_filter: true`, FTS only (semantic / rerank false).
68
+
69
+ ## Contract surface (3 methods)
70
+
71
+ The MemoryStore contract is narrower than SkillStore. Three methods + `staticCapabilities()`:
72
+
73
+ | Method | What it does | When called |
74
+ |---|---|---|
75
+ | `query(filters)` | Read memories by mode + filter; return `PortableMemory[]` | Every `$ memory mode=... query=...` op |
76
+ | `write(entry)` | Persist a new memory; return `{id, created_at}` | Every `$ memory_write content=... -> R` op + `memory_write` skill notify routes |
77
+ | `manifest()` | Capability snapshot for `runtime_capabilities` discovery | At startup + on-demand from MCP clients |
78
+
79
+ Per the curated-subset framing in `src/connectors/types.ts`, `PortableMemory` has a 4-tier field model:
80
+
81
+ 1. **Core fields** — `id`, `summary`, `detail`, `score` (always-meaningful)
82
+ 2. **Curated substrate subset** — top-level fields with portable concepts: `domain_tags`, `payload_type`, `knowledge_type`, `pinned`, `confidence`, `thread_status`, `recipients`, `expires_at`, `agent_id`, `vault`
83
+ 3. **Substrate-specific** — accessed via `metadata.X` (catch-all)
84
+ 4. **Ambient passthrough** — literal `$(MEMORY.field)` references for unknowns
85
+
86
+ **Connector duplication discipline**: a curated-subset field must be at top-level only, never also in `metadata`. Silent divergence otherwise.
87
+
88
+ ## Query modes
89
+
90
+ `QueryFilters.mode` is the dispatch axis:
91
+
92
+ - **`"fts"`** — full-text search; substrate-defined (BM25, BM25F, etc.). `SqliteMemoryStore` supports this via FTS5.
93
+ - **`"semantic"`** — embedding-based similarity. Vector-DB substrates support this; `SqliteMemoryStore` does not.
94
+ - **`"rerank"`** — substrate-defined hybrid (e.g., FTS retrieve + embedding rerank). Optional.
95
+ - **substrate-specific strings** — your fork can define its own modes; document them in `manifest().manifest.supported_modes`.
96
+
97
+ Throw a clear error on unsupported modes (don't silently fall back; cold authors will be confused).
98
+
99
+ ## Filter fields
100
+
101
+ `QueryFilters` extends a base shape with arbitrary additional filter keys. Common filters substrates honor:
102
+
103
+ - **`domain_tags`** — string or array; substrate-specific match semantics (substring vs exact; AND vs OR)
104
+ - **`payload_type`** — usually exact match
105
+ - **`thread_status`** — usually exact match (set if your substrate has thread lifecycle)
106
+ - **`pinned`** — boolean
107
+ - **`agent_id`** — adopter-side scoping
108
+ - **`recipients`** — array match (any-of)
109
+
110
+ Honor what your substrate supports; ignore the rest. Document the supported set in `manifest().manifest.supported_filters`.
111
+
112
+ ## `write()` semantics
113
+
114
+ The `MemoryWrite` shape:
115
+
116
+ - **`content`** (required) — the memory body
117
+ - **`tags`** (optional) — routed to your substrate's tag mechanism
118
+ - **`recipients`** (optional) — advisory hint; substrates with alerting (e.g., AMP's mailbox model) use it
119
+ - **`expires_at`** (optional) — unix seconds; substrates with TTL honor it
120
+ - **`metadata`** (optional) — catch-all for substrate extensions (`vault`, `payload_type`, `confidence`, etc.)
121
+
122
+ Return `{id, created_at}` — the substrate-assigned identifier + creation timestamp (unix seconds).
123
+
124
+ If your substrate doesn't support writes (read-only memory like a search index over a static corpus), set `supports_writes: false` in `staticCapabilities()` and throw from `write()`. The runtime + lint will respect the flag.
125
+
126
+ ## Wiring against the dashboard / MCP
127
+
128
+ Runtime hosts honor whichever MemoryStore impl your registry has. To make your fork visible through `skillfile dashboard`:
129
+
130
+ - Write a custom bootstrap that constructs the runtime with your MemoryStore (see `src/bootstrap.ts` for the reference shape)
131
+ - OR (planned future) declare in `~/.skillscript/connectors.json`:
132
+
133
+ ```json
134
+ {
135
+ "substrate": {
136
+ "memory_store": {
137
+ "type": "custom",
138
+ "module": "./my-memory-store.js",
139
+ "export": "MyMemoryStore",
140
+ "config": { "pineconeApiKey": "${PINECONE_KEY}" }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ Current limitation: sync `bootstrap()` can't dynamic-import, so the `custom` form surfaces an error today. Programmatic bootstrap is the path until async-bootstrap support lands.
147
+
148
+ ## MemoryStore vs SkillStore differences
149
+
150
+ Both are substrate-agnostic, but they differ in shape:
151
+
152
+ | Aspect | SkillStore | MemoryStore |
153
+ |---|---|---|
154
+ | Methods | 8 (load, query, metadata, versions, store, delete, update_status, manifest) | 3 (query, write, manifest) |
155
+ | Versioning | First-class (`versions()`, `load(name, version)`) | Substrate-side (e.g., `supersededById` in AMP); not in contract |
156
+ | Status lifecycle | Draft/Approved/Disabled with approval-token stamping | None |
157
+ | Mutation gates | Approved transitions require approval token | None — writes are append-only |
158
+ | Curated-subset fields | None | Many (`domain_tags`, `pinned`, `confidence`, `recipients`, etc.) |
159
+ | Forking complexity | Higher (multi-method state machine) | Lower (three methods, simpler semantics) |
160
+
161
+ The narrower contract is why MemoryStoreTemplate ships sooner + smaller than SkillStoreTemplate.
162
+
163
+ ## Further reading
164
+
165
+ - **[`../../../docs/configuration.md`](../../../docs/configuration.md)** — substrate selection via `connectors.json`
166
+ - **[`../../../docs/adopter-playbook.md`](../../../docs/adopter-playbook.md)** — programmatic-bootstrap patterns; two-instance posture
167
+ - **`src/connectors/types.ts`** — authoritative `MemoryStore` interface, `PortableMemory`, `QueryFilters`, `MemoryWrite` types
168
+ - **`src/testing/conformance.ts`** — the per-contract conformance test suites
169
+ - **`src/connectors/memory-store.ts`** — `SqliteMemoryStore` reference impl + schema
@@ -0,0 +1,47 @@
1
+ # examples/connectors/
2
+
3
+ Worked examples + fork-me templates for adopter-written connectors. The bundled defaults live in `src/connectors/`; this directory holds patterns for adopters writing their own.
4
+
5
+ ## Connector landscape
6
+
7
+ | Contract | Bundled defaults | Worked example | Fork template |
8
+ |---|---|---|---|
9
+ | `SkillStore` | `FilesystemSkillStore`, `SqliteSkillStore` (in `src/connectors/`) | — | **[SkillStoreTemplate/](./SkillStoreTemplate/)** |
10
+ | `MemoryStore` | `SqliteMemoryStore` (in `src/connectors/`) | — | **[MemoryStoreTemplate/](./MemoryStoreTemplate/)** |
11
+ | `LocalModel` | `OllamaLocalModel` (in `src/connectors/`; opt-in via substrate config) | — | **[LocalModelTemplate/](./LocalModelTemplate/)** |
12
+ | `McpConnector` | `RemoteMcpConnector`, `CallbackMcpConnector`, `LocalModelMcpConnector`, `MemoryStoreMcpConnector` (in `src/connectors/`) | — | **[McpConnectorTemplate/](./McpConnectorTemplate/)** |
13
+ | `AgentConnector` | `NoOpAgentConnector` (in `src/connectors/`) | **[HttpWebhookAgentConnector/](./HttpWebhookAgentConnector/)** | — |
14
+
15
+ **Bundled defaults** are runnable out of the box — wired through `connectors.json` substrate config or programmatic bootstrap.
16
+
17
+ **Worked examples** are real implementations for substrates that aren't bundled — copy + customize for your specific deployment. HttpWebhookAgentConnector demonstrates the AgentConnector contract against a generic HTTP-webhook substrate.
18
+
19
+ **Fork templates** are skeletons (every method throws TODO). Useful when you want the bare contract surface without any specific substrate assumptions. SkillStoreTemplate is the starting point for Postgres-, MongoDB-, AMP-, or vector-DB-backed SkillStore impls.
20
+
21
+ ## Forking workflow (any connector type)
22
+
23
+ 1. **Pick your starting point**:
24
+ - Fork from a worked example if it's close to your substrate (e.g., another HTTP service → fork HttpWebhookAgentConnector)
25
+ - Fork from a template if you're targeting a substrate type not yet demonstrated (e.g., Postgres SkillStore → fork SkillStoreTemplate)
26
+ - Fork from `src/connectors/` if you want the bundled default's behavior as a starting point (e.g., another SQL flavor → adapt sqlite-skill-store.ts)
27
+ 2. **Copy the directory** into your codebase
28
+ 3. **Implement against your substrate** (each method has a TODO comment)
29
+ 4. **Update `staticCapabilities()`** to declare what your impl actually supports
30
+ 5. **Register via `Registry.register*`** from your bootstrap
31
+ 6. **Validate via the conformance suite** (`<Contract>Conformance.buildTests()` from `skillscript-runtime/testing`)
32
+
33
+ ## Reference materials
34
+
35
+ - **[`docs/configuration.md`](../../docs/configuration.md)** — substrate selection via `connectors.json`; programmatic-bootstrap vs declarative patterns
36
+ - **[`docs/adopter-playbook.md`](../../docs/adopter-playbook.md)** — Case 1 vs Case 2 wiring decisions; two-instance posture; upstream-merge-friendly conventions
37
+ - **[`docs/connector-contract-reference.md`](../../docs/connector-contract-reference.md)** — interface contracts for adopter agents writing connector impls
38
+ - **`src/connectors/types.ts`** — authoritative contract interfaces (SkillStore, MemoryStore, LocalModel, McpConnector)
39
+ - **`src/testing/conformance.ts`** — the per-contract conformance test suites
40
+
41
+ ## Naming convention
42
+
43
+ Bundled defaults in `src/connectors/`: `<Substrate><Contract>` (e.g., `FilesystemSkillStore`, `SqliteSkillStore`, `OllamaLocalModel`).
44
+
45
+ Worked examples + adopter forks in `examples/connectors/`: same `<Substrate><Contract>` pattern (e.g., `HttpWebhookAgentConnector`, your `PostgresSkillStore`).
46
+
47
+ Templates: `<Contract>Template` (e.g., `SkillStoreTemplate`).
@@ -0,0 +1,143 @@
1
+ # SkillStoreTemplate — fork-me skeleton
2
+
3
+ A skeleton `SkillStore` implementation for adopters writing their own. Not runnable; every method throws a `TODO` error. Copy this directory, rename, fill in the substrate-specific work.
4
+
5
+ Use this when you want skillscript skills backed by:
6
+ - A database the bundled impls don't cover (Postgres, MySQL, MongoDB, etc.)
7
+ - A vector store (Pinecone, Weaviate, Qdrant)
8
+ - A memory system (AMP, hosted memory API)
9
+ - An HTTP CRUD service
10
+ - Anything else with a query/write interface
11
+
12
+ ## The three legs
13
+
14
+ ```
15
+ SkillStore (choose which connector)
16
+ ├── FilesystemSkillStore (bundled, FS-backed — src/connectors/skill-store.ts)
17
+ ├── SqliteSkillStore (bundled, DB-backed — src/connectors/sqlite-skill-store.ts)
18
+ └── Your fork from this template
19
+ ```
20
+
21
+ The runtime is substrate-agnostic. Skills don't know which backend they're stored against — the contract is the `SkillStore` interface, and any class implementing it works.
22
+
23
+ ## Forking workflow
24
+
25
+ ```bash
26
+ cp -r examples/connectors/SkillStoreTemplate examples/connectors/MyDatabaseSkillStore
27
+ ```
28
+
29
+ 1. **Rename the class.** Convention: `<Substrate>SkillStore` (e.g., `PostgresSkillStore`, `PineconeSkillStore`, `AmpSkillStore`).
30
+ 2. **Define your config interface.** Edit `SkillStoreTemplateConfig` to declare what your substrate needs (connection URL, API key, vault name, etc.).
31
+ 3. **Implement each method.** Eight methods + `staticCapabilities()`. Each has a TODO comment in the skeleton explaining what to do.
32
+ 4. **Update `staticCapabilities()`** to declare what your impl actually supports. The runtime + lint consult these flags before exercising features.
33
+ 5. **Wire from your adopter bootstrap:**
34
+
35
+ ```typescript
36
+ import { Registry } from "skillscript-runtime";
37
+ import { MyDatabaseSkillStore } from "./MyDatabaseSkillStore.js";
38
+
39
+ const registry = new Registry();
40
+ registry.registerSkillStore("primary", new MyDatabaseSkillStore({
41
+ // your config
42
+ }));
43
+ ```
44
+
45
+ 6. **Validate via the conformance suite:**
46
+
47
+ ```typescript
48
+ import { describe, it } from "vitest";
49
+ import { SkillStoreConformance } from "skillscript-runtime/testing";
50
+ import { MyDatabaseSkillStore } from "./MyDatabaseSkillStore.js";
51
+
52
+ describe("MyDatabaseSkillStore conformance", () => {
53
+ const tests = SkillStoreConformance.buildTests({
54
+ build: () => new MyDatabaseSkillStore({ /* test config */ }),
55
+ ctor: MyDatabaseSkillStore,
56
+ });
57
+ for (const t of tests) it(`[${t.category}] ${t.name}`, t.run);
58
+ });
59
+ ```
60
+
61
+ The conformance suite verifies your impl honors the contract: method presence, return-type shape, error-class throw conditions, capability flag self-consistency. If it passes, the runtime treats your impl interchangeably with the bundled defaults.
62
+
63
+ ## Reference implementations
64
+
65
+ When in doubt about semantics, read the bundled impls:
66
+
67
+ - **`src/connectors/skill-store.ts`** — `FilesystemSkillStore`, filesystem-backed. Skills as `.skill.md` files; version history in a sidecar `.versions.jsonl`. Simplest reference.
68
+ - **`src/connectors/sqlite-skill-store.ts`** — `SqliteSkillStore`, SQLite-backed. Two-table schema, transactional status transitions, WAL, JSON-extract tag filter. Closest reference for any database-backed fork.
69
+
70
+ Both implement the same `SkillStore` interface; the differences are in the substrate-specific code.
71
+
72
+ ## Approval token semantics
73
+
74
+ The runtime ships an approval-token mechanism so headless MCP-only adopters get a runnable `Approved` state without a dashboard round-trip. When you `store()` or `update_status()` a body declaring `# Status: Approved`, the impl should stamp `# Status: Approved v1:<hex>` automatically.
75
+
76
+ Helpers from `src/approval.ts`:
77
+
78
+ ```typescript
79
+ import { extractStatusFromBody, stampApprovalToken } from "skillscript-runtime";
80
+
81
+ // In store():
82
+ const extracted = extractStatusFromBody(source);
83
+ const body = extracted?.status === "Approved" ? stampApprovalToken(source) : source;
84
+
85
+ // In update_status() — when transitioning TO Approved:
86
+ const updated = status === "Approved"
87
+ ? stampApprovalToken(rewriteStatusHeader(source, "Approved"))
88
+ : rewriteStatusHeader(source, status);
89
+ ```
90
+
91
+ Both bundled impls follow this pattern; your fork should too unless you're explicitly disabling approval tokens.
92
+
93
+ ## Error classes to throw
94
+
95
+ From `src/errors.ts`:
96
+
97
+ - **`SkillNotFoundError(name, implementationName)`** — `load()` / `metadata()` / `versions()` / `update_status()` / `delete()` on a missing skill
98
+ - **`VersionNotFoundError(name, version, implementationName)`** — `load(name, version)` where `name` exists but `version` doesn't
99
+ - **`StorageConflictError(name, reason, implementationName)`** — `store()` rejects (e.g., name violates substrate constraints)
100
+
101
+ The `implementationName` is your class name — let cold authors trace errors back to the substrate.
102
+
103
+ ## Schema-level decisions
104
+
105
+ Document your substrate's particulars in a README alongside your fork:
106
+
107
+ - **Versioning shape** — full body bytes per version (rich audit), or just hash + status (lightweight)?
108
+ - **Delete semantics** — hard-cascade (removes versions) or preserve (audit-grade retention)?
109
+ - **Concurrency model** — single-writer, multi-reader (e.g., WAL), or fully concurrent?
110
+ - **Tag filter** — indexed lookup, table scan, or unsupported?
111
+
112
+ These trade-offs are substrate-side; the contract just specifies what each method does, not how.
113
+
114
+ ## Wiring against the CLI / dashboard
115
+
116
+ Runtime hosts (MCP server + web dashboard) honor whichever SkillStore impl your registry has. To make your fork visible through `skillfile dashboard`:
117
+
118
+ - Write a custom bootstrap that constructs the runtime with your SkillStore (see `src/bootstrap.ts` for the reference shape + `docs/adopter-playbook.md` for the pattern)
119
+ - OR (planned future) declare in `~/.skillscript/connectors.json`:
120
+
121
+ ```json
122
+ {
123
+ "substrate": {
124
+ "skill_store": {
125
+ "type": "custom",
126
+ "module": "./my-database-skill-store.js",
127
+ "export": "MyDatabaseSkillStore",
128
+ "config": { "postgresUrl": "${POSTGRES_URL}" }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ Current limitation: sync `bootstrap()` can't dynamic-import, so the `custom` form surfaces an error today. Programmatic bootstrap is the path until async-bootstrap support lands.
135
+
136
+ The bundled CLI authoring commands (`skillfile compile`, `skillfile lint`, `skillfile audit`, `skillfile list`) stay filesystem-pinned by design — they're the FS-authoring loop. Adopter-substrate skills are authored via the dashboard UI or the `skill_write` MCP tool.
137
+
138
+ ## Further reading
139
+
140
+ - **[`docs/configuration.md`](../../../docs/configuration.md)** — substrate selection via `connectors.json`
141
+ - **[`docs/sqlite-skill-store.md`](../../../docs/sqlite-skill-store.md)** — the bundled SqliteSkillStore's schema + semantics + forking checklist
142
+ - **`src/connectors/types.ts`** — authoritative `SkillStore` interface spec (lines 192-208)
143
+ - **`src/testing/conformance.ts`** — the conformance test suite
@@ -0,0 +1,199 @@
1
+ /**
2
+ * SkillStoreTemplate — fork-me skeleton for writing your own SkillStore impl.
3
+ *
4
+ * This is NOT a runnable connector. Every method throws a "TODO" error. The
5
+ * purpose is to surface the SkillStore contract surface in a copy-and-customize
6
+ * shape so adopters writing Postgres-, MySQL-, MongoDB-, AMP-, or
7
+ * vector-DB-backed SkillStores have a starting skeleton.
8
+ *
9
+ * Forking workflow:
10
+ * 1. Copy this directory into your codebase (`cp -r examples/connectors/SkillStoreTemplate examples/connectors/MyDatabaseSkillStore`)
11
+ * 2. Rename the class — typically `<Substrate>SkillStore` (e.g., `PostgresSkillStore`)
12
+ * 3. Implement each method against your substrate's API
13
+ * 4. Update `staticCapabilities()` to declare what your impl actually supports
14
+ * 5. Register from your adopter bootstrap:
15
+ * `registry.registerSkillStore("primary", new MyDatabaseSkillStore({ ... }))`
16
+ * 6. Validate via the conformance suite:
17
+ * `SkillStoreConformance.buildTests({ build: () => new MyDatabaseSkillStore(...), ctor: MyDatabaseSkillStore })`
18
+ *
19
+ * See `src/connectors/sqlite-skill-store.ts` for a working reference
20
+ * implementation against SQLite + `node:sqlite`. See `src/connectors/skill-store.ts`
21
+ * for the bundled filesystem default. The full contract spec lives in
22
+ * `src/connectors/types.ts` (`SkillStore` interface, lines 192-208) and
23
+ * `docs/sqlite-skill-store.md` for schema + semantics depth.
24
+ *
25
+ * Runtime hosts (MCP server + web dashboard) honor whichever SkillStore impl
26
+ * you register via the registry, so once your fork passes the conformance
27
+ * suite the entire skillscript surface (skill_write / skill_list /
28
+ * execute_skill / etc.) reads + writes against your substrate.
29
+ */
30
+
31
+ import type {
32
+ SkillStore,
33
+ SkillSource,
34
+ SkillMeta,
35
+ SkillStatus,
36
+ SkillFilter,
37
+ VersionInfo,
38
+ SkillStoreCapabilities,
39
+ ManifestInfo,
40
+ } from "../../../src/connectors/types.js";
41
+
42
+ /** Replace with your substrate's connection config (host, dbName, credentials, etc.). */
43
+ export interface SkillStoreTemplateConfig {
44
+ // TODO — declare the fields your substrate needs to connect.
45
+ // Examples:
46
+ // postgresUrl?: string;
47
+ // pineconeApiKey?: string;
48
+ // ampVault?: string;
49
+ exampleConfigField?: string;
50
+ }
51
+
52
+ export class SkillStoreTemplate implements SkillStore {
53
+ /**
54
+ * Declare what your impl supports. The runtime + lint consult these flags
55
+ * before exercising features. Set conservatively — overclaiming triggers
56
+ * cryptic downstream failures; underclaiming hides usable features.
57
+ */
58
+ static staticCapabilities(): SkillStoreCapabilities {
59
+ return {
60
+ connector_type: "skill_store",
61
+ implementation: "SkillStoreTemplate", // ← rename to your class name
62
+ contract_version: "1.0.0",
63
+ features: {
64
+ // TODO — set each flag based on what your substrate can actually do.
65
+ supports_writes: false, // can you persist new skills?
66
+ supports_versioning: false, // can you return historical bytes via load(name, version)?
67
+ supports_tag_filter: false, // can query() filter by tag?
68
+ supports_audit_trail: false, // does update_status populate previous_status?
69
+ supports_atomic_status_transitions: false, // can UPDATE + INSERT versions be transactional?
70
+ },
71
+ };
72
+ }
73
+
74
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
75
+ constructor(_config: SkillStoreTemplateConfig) {
76
+ // TODO — establish your substrate connection. For SQL: open the database
77
+ // + run schema migrations. For HTTP: store the base URL + auth headers.
78
+ // For AMP/Pinecone: cache the SDK client.
79
+ throw new Error("SkillStoreTemplate is a fork-me skeleton; replace with your impl.");
80
+ }
81
+
82
+ /**
83
+ * Capability snapshot for `runtime_capabilities` discovery. Return free-form
84
+ * substrate-specific metadata (kind, version, supported modes, etc.).
85
+ */
86
+ async manifest(): Promise<ManifestInfo<"skill_store">> {
87
+ // TODO — return a snapshot of your substrate's capabilities.
88
+ throw new Error("TODO: manifest() — return substrate-specific capability snapshot.");
89
+ }
90
+
91
+ /**
92
+ * Read the skill body bytes + metadata. If `version` is supplied, return
93
+ * historical bytes (or throw `VersionNotFoundError` if the version is
94
+ * unknown). If the skill doesn't exist at all, throw `SkillNotFoundError`.
95
+ */
96
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
97
+ async load(_name: string, _version?: string): Promise<SkillSource> {
98
+ // TODO — fetch the skill record from your substrate.
99
+ // - Return SkillSource { name, version, content_hash, source, metadata }
100
+ // - Throw SkillNotFoundError if name doesn't exist
101
+ // - Throw VersionNotFoundError if version is supplied but unknown
102
+ throw new Error("TODO: load() — fetch skill source by name (+ optional version).");
103
+ }
104
+
105
+ /**
106
+ * List skill metadata. Apply the filter where your substrate supports it;
107
+ * fall back to client-side filtering for unsupported fields. Empty result
108
+ * is fine; never throw "not found" — return [].
109
+ */
110
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
111
+ async query(_filter?: SkillFilter): Promise<SkillMeta[]> {
112
+ // TODO — query your substrate for skill metadata.
113
+ // - Apply filter.status / filter.tag / filter.author / etc. as supported
114
+ // - Honor filter.limit + filter.offset
115
+ // - Never throw on no matches; return []
116
+ throw new Error("TODO: query() — return SkillMeta[] honoring the filter.");
117
+ }
118
+
119
+ /**
120
+ * Read skill metadata without the body bytes. Equivalent to `load()` but
121
+ * skips the `source` field — useful for listings + introspection where
122
+ * the body isn't needed.
123
+ */
124
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
125
+ async metadata(_name: string): Promise<SkillMeta> {
126
+ // TODO — return SkillMeta for `name`. Throw SkillNotFoundError if missing.
127
+ throw new Error("TODO: metadata() — return SkillMeta without body bytes.");
128
+ }
129
+
130
+ /**
131
+ * Return version history (chronological). Each entry has `version` +
132
+ * `content_hash` + `status` + `changed_at` + optional `previous_status`
133
+ * (audit trail). Throws `SkillNotFoundError` if the skill doesn't exist.
134
+ *
135
+ * If your substrate doesn't track versions, return a single-element array
136
+ * with the current state (and set `supports_versioning: false`).
137
+ */
138
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
139
+ async versions(_name: string): Promise<VersionInfo[]> {
140
+ // TODO — return version chain. SQL substrates: SELECT FROM skill_versions
141
+ // ORDER BY changed_at. MemoryStore-style: query memories with the right tag.
142
+ throw new Error("TODO: versions() — return VersionInfo[] chronologically.");
143
+ }
144
+
145
+ /**
146
+ * Persist a skill (create or update). Compute content_hash from the source;
147
+ * append a new version row; update the current-version pointer. Returns
148
+ * `VersionInfo` for the new version.
149
+ *
150
+ * For atomicity, wrap your substrate writes in a transaction (or equivalent)
151
+ * so the version row is never created without the current-version pointer
152
+ * updating, and vice versa.
153
+ */
154
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
155
+ async store(_name: string, _source: string, _metadata?: Partial<SkillMeta>): Promise<VersionInfo> {
156
+ // TODO — persist the skill source bytes + metadata.
157
+ // - Compute content_hash (SHA-256 of source) + version (short hash)
158
+ // - Auto-stamp approval token if body declares Approved (see
159
+ // src/approval.ts → stampApprovalToken / extractStatusFromBody)
160
+ // - Write to your substrate atomically
161
+ // - Return VersionInfo
162
+ throw new Error("TODO: store() — persist skill source + metadata.");
163
+ }
164
+
165
+ /**
166
+ * Hard-delete the skill (substrate-only — referential integrity is the
167
+ * runtime's concern). Whether to cascade version history is your substrate
168
+ * choice; the bundled SqliteSkillStore cascades (rationale in
169
+ * `docs/sqlite-skill-store.md`).
170
+ *
171
+ * Throws `SkillNotFoundError` if `name` doesn't exist (so adopters get a
172
+ * clear error rather than a silent no-op).
173
+ */
174
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
175
+ async delete(_name: string): Promise<void> {
176
+ // TODO — remove the skill from your substrate. Decide cascade vs preserve
177
+ // history per your compliance needs. Throw SkillNotFoundError if missing.
178
+ throw new Error("TODO: delete() — remove skill (+ optionally history).");
179
+ }
180
+
181
+ /**
182
+ * Transition a skill's status (Draft / Approved / Disabled). Updates the
183
+ * current state + appends a `skill_versions` row with `previous_status`
184
+ * populated (audit trail). Wrap in a transaction for atomicity.
185
+ *
186
+ * Approved transitions should stamp the approval token (see
187
+ * `src/approval.ts`); transitions away from Approved should strip it.
188
+ */
189
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
190
+ async update_status(_name: string, _status: SkillStatus): Promise<VersionInfo> {
191
+ // TODO — atomically update status + append audit row.
192
+ // - Load current status (for previous_status field)
193
+ // - Apply stampApprovalToken if status=Approved
194
+ // - Strip token if transitioning to Draft/Disabled
195
+ // - UPDATE skills SET status, INSERT skill_versions (transaction)
196
+ // - Return VersionInfo with previous_status populated
197
+ throw new Error("TODO: update_status() — atomic status transition + audit row.");
198
+ }
199
+ }
@@ -2,7 +2,7 @@
2
2
  "provenance_version": "1.0",
3
3
  "language_version": "1.0",
4
4
  "compiler_version": "0.1.0-dev",
5
- "compiled_at": "2026-05-28T19:20:14.463Z",
5
+ "compiled_at": "2026-05-29T18:50:40.018Z",
6
6
  "source_skill": {
7
7
  "name": "hello"
8
8
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillscript-runtime",
3
- "version": "0.10.0",
3
+ "version": "0.13.2",
4
4
  "description": "Runtime, compiler, lint, CLI, and dashboard for Skillscript — a small declarative language for authoring agent workflows.",
5
5
  "license": "MIT",
6
6
  "author": "Scott Shwarts <scotts@pobox.com>",
@@ -16,18 +16,14 @@ plugins_dir = "~/.skillscript/plugins"
16
16
  # security-conscious deployments invert this to ["packages"] only.
17
17
  resolution_order = ["filesystem", "packages"]
18
18
 
19
- [local_model.default]
20
- # Bundled-default LocalModel: Ollama gemma2:9b at localhost.
21
- kind = "ollama"
22
- base_url = "http://localhost:11434"
23
- model_tag = "gemma2:9b"
24
- timeout_ms = 60000
25
-
26
- [local_model.qwen]
27
- kind = "ollama"
28
- base_url = "http://localhost:11434"
29
- model_tag = "qwen2.5:7b"
30
- timeout_ms = 60000
19
+ # LocalModel wiring lives in `connectors.json` under `substrate.local_model`,
20
+ # NOT here. v0.10+ moved substrate selection out of this file. Example:
21
+ # {
22
+ # "substrate": {
23
+ # "local_model": { "type": "ollama", "config": { "defaultModelTag": "gemma2:9b" } }
24
+ # }
25
+ # }
26
+ # See docs/configuration.md.
31
27
 
32
28
  [runtime]
33
29
  # `@@` opt-in unsafe shell. Disabled by default. v1 spec gates this behind
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "_comment": "Per-host connector wiring. Copied to ~/.skillscript/connectors.json by `skillfile init`. The top-level `substrate` key selects singleton substrate connectors (SkillStore, MemoryStore, LocalModel). All other top-level keys define named MCP connector instances referenced via `$ name args` in skill source.",
3
3
 
4
- "_comment_substrate": "v0.10 substrate selection — runtime hosts (MCP server + web dashboard) honor whatever you configure here. The bundled CLI authoring commands (`skillfile compile`, `skillfile lint`, `skillfile audit`, `skillfile list`) stay filesystem-pinned regardless — they're the FS-authoring loop. Three legs: bundled filesystem, bundled sqlite, adopter-custom (write your own SkillStore impl).",
4
+ "_comment_substrate": "Substrate selection — runtime hosts (MCP server + web dashboard) honor whatever you configure here. The bundled CLI authoring commands (`skillfile compile`, `skillfile lint`, `skillfile audit`, `skillfile list`) stay filesystem-pinned regardless — they're the FS-authoring loop. Three legs: bundled filesystem, bundled sqlite, adopter-custom (write your own SkillStore impl).",
5
5
 
6
- "_comment_substrate_forms": "Each substrate slot accepts: (a) bare string for built-in types — \"filesystem\", \"sqlite\", \"ollama\"; (b) null to explicitly disable; (c) object form `{type, config}` to override defaults; (d) object form `{type:\"custom\", module, export, config}` for adopter-custom impls (requires programmatic bootstrap today — sync `bootstrap()` doesn't dynamic-import yet).",
6
+ "_comment_substrate_forms": "Each substrate slot accepts: (a) bare string for built-in types — \"filesystem\", \"sqlite\", \"ollama\"; (b) null to explicitly disable; (c) object form `{type, config}` to override defaults; (d) object form `{type:\"custom\", module, export, config}` for adopter-custom impls (requires programmatic bootstrap; sync `bootstrap()` doesn't dynamic-import).",
7
7
 
8
8
  "substrate": {
9
9
  "skill_store": "filesystem",
@@ -22,7 +22,7 @@
22
22
  "config": { "baseUrl": "http://gpu-host:11434", "defaultModelTag": "gemma2:9b" }
23
23
  },
24
24
 
25
- "_example_custom": "Adopter-custom impl. Write `class FooSkillStore implements SkillStore { ... }` then reference it here. NOTE v0.10 — this form requires a programmatic bootstrap (sync `bootstrap()` can't dynamic-import). Wire via your own bootstrap script today; declarative custom config arrives in a follow-up.",
25
+ "_example_custom": "Adopter-custom impl. Write `class FooSkillStore implements SkillStore { ... }` then reference it here. NOTE — this form requires a programmatic bootstrap (sync `bootstrap()` can't dynamic-import). Wire via your own bootstrap script today; declarative custom config is a planned follow-up.",
26
26
  "_example_custom_object": {
27
27
  "type": "custom",
28
28
  "module": "./my-amp-skill-store.js",
@@ -31,7 +31,7 @@
31
31
  }
32
32
  },
33
33
 
34
- "_comment_classes": "v0.4.0+ closed-set class registry. Available: `RemoteMcpConnector` (stdio-bridged remote MCP). Use `runtime_capabilities({include:['mcpConnectorClasses']})` to introspect the runtime's available class set. v0.7.2 added `LocalModelMcpConnector` + `MemoryStoreMcpConnector` bridges so `$ llm` / `$ memory` work canonically — these auto-wire when their substrates exist.",
34
+ "_comment_classes": "Closed-set class registry for MCP connectors. Available: `RemoteMcpConnector` (stdio-bridged remote MCP). Use `runtime_capabilities({include:['mcpConnectorClasses']})` to introspect the available class set. The `LocalModelMcpConnector` + `MemoryStoreMcpConnector` bridges (for `$ llm` / `$ memory` canonical dispatch) auto-wire when their substrates are configured above.",
35
35
 
36
36
  "_comment_security": "This file is secret-bearing when filled with real credentials. The repo's `.gitignore` excludes `connectors.json` (the live copy) but tracks `connectors.json.example`. Prefer `${VAR}` substitution over in-file literals for deployments.",
37
37