skillscript-runtime 0.13.2 → 0.13.5

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.
@@ -0,0 +1,167 @@
1
+ # SqliteSkillStore — example
2
+
3
+ Worked example of `SkillStore` against a SQLite database. Copy this directory into your codebase, customize per your storage needs, register with skillscript-runtime's `Registry`. This README is written for the agent implementing your adopter's connector — including the human reviewing the PR.
4
+
5
+ **What this demonstrates**: the locked SkillStore contract surface (8 methods + `staticCapabilities`) wired through real SQL with two-table versioning, transactional status transitions, and JSON-extract tag filters.
6
+
7
+ ---
8
+
9
+ ## The three-leg model
10
+
11
+ SkillStore is pluggable. Three legs ship out of the box, but the third is the open one:
12
+
13
+ ```
14
+ SkillStore (choose which connector)
15
+ ├── FilesystemSkillStore (bundled, FS-backed)
16
+ ├── SqliteSkillStore (this example, DB-backed)
17
+ └── Adopter-custom (you write your own)
18
+ ```
19
+
20
+ If your substrate is AMP, Pinecone, S3, Postgres, or anything else, write a `class FooSkillStore implements SkillStore { ... }` and call `registry.registerSkillStore("primary", new FooSkillStore(...))`. The runtime is none the wiser.
21
+
22
+ This SqliteSkillStore is one such impl — useful as a copy-paste starting point, or directly usable if your needs match.
23
+
24
+ ---
25
+
26
+ ## Quick start
27
+
28
+ ```typescript
29
+ import { Registry } from "skillscript-runtime";
30
+ import { SqliteSkillStore } from "./SqliteSkillStore.js";
31
+
32
+ const registry = new Registry();
33
+ registry.registerSkillStore("primary", new SqliteSkillStore({ dbPath: "skills.db" }));
34
+
35
+ // Wire the rest of the runtime (scheduler, mcpServer) using the registry.
36
+ ```
37
+
38
+ Author skills programmatically:
39
+
40
+ ```typescript
41
+ const store = registry.getSkillStore("primary");
42
+ await store.store("morning-status", `# Skill: morning-status
43
+ # Status: Approved
44
+ t:
45
+ ! report status
46
+ default: t
47
+ `);
48
+ ```
49
+
50
+ Or via the dashboard, or via `skill_write` MCP tool — same backend, same result.
51
+
52
+ ---
53
+
54
+ ## When to use SqliteSkillStore (and when not)
55
+
56
+ **Use it when**:
57
+
58
+ - You're embedding skillscript-runtime as a library and want skills in a database rather than `.skill.md` files
59
+ - Your deployment has no persistent filesystem (container-only) but does have SQLite
60
+ - You need richer query semantics than filesystem listing (tag filters, transactional status transitions)
61
+
62
+ **Don't use it when**:
63
+
64
+ - You're using the bundled CLI (`skillfile compile`, `skillfile lint`, `skillfile list`). The CLI is filesystem-first by design — `vim foo.skill.md && skillfile lint foo` is the natural authoring loop. The CLI does NOT use SqliteSkillStore. Sqlite-backed skills are authored via dashboard or `skill_write` MCP tool.
65
+ - Your skills are authored as files-on-disk and committed to git as part of the source tree. FilesystemSkillStore is the right choice there.
66
+
67
+ This is "first-class" in the **programmatic-embedding** sense, not the CLI sense. Treat the example as a substrate-portability proof point + a copy-paste starting point.
68
+
69
+ ---
70
+
71
+ ## Schema
72
+
73
+ Two tables:
74
+
75
+ ```sql
76
+ CREATE TABLE skills (
77
+ name TEXT PRIMARY KEY,
78
+ current_version TEXT NOT NULL,
79
+ content_hash TEXT NOT NULL,
80
+ status TEXT NOT NULL, -- 'Draft' | 'Approved' | 'Disabled'
81
+ source TEXT NOT NULL, -- the .skill.md body bytes
82
+ description TEXT, -- extracted from `# Description:` header
83
+ metadata_json TEXT, -- optional metadata bag (tags, author, etc.)
84
+ created_at INTEGER NOT NULL,
85
+ updated_at INTEGER NOT NULL,
86
+ status_changed_at INTEGER
87
+ );
88
+
89
+ CREATE TABLE skill_versions (
90
+ name TEXT NOT NULL,
91
+ version TEXT NOT NULL, -- short hash (12 chars)
92
+ content_hash TEXT NOT NULL,
93
+ source TEXT NOT NULL, -- full body, preserved per version
94
+ status TEXT NOT NULL,
95
+ previous_status TEXT, -- populated by update_status
96
+ changed_at INTEGER NOT NULL,
97
+ changed_by TEXT,
98
+ PRIMARY KEY (name, version)
99
+ );
100
+ ```
101
+
102
+ `skills` is the fast-path read for `load()` / `metadata()` / `query()`. `skill_versions` is the append-only history for `versions()` — and unlike FilesystemSkillStore, it preserves full body bytes per version, so `load(name, version)` can return historical content.
103
+
104
+ WAL is enabled at bootstrap so concurrent readers don't block writers.
105
+
106
+ ---
107
+
108
+ ## Footgun guard: `delete()` is hard-cascade
109
+
110
+ **`delete()` removes both the `skills` row AND its `skill_versions` rows.** If you need recovery, back up adopter-side BEFORE calling delete. Skill names can be reused after delete (no orphan history left behind).
111
+
112
+ This is the locked semantic. If your adopter substrate has compliance requirements (audit-grade retention) or you hit a "wait, I deleted that" moment, the upgrade path is soft-delete (tombstone `status='Deleted'` + filter from query results) — but that's an adopter-side choice; the bundled SqliteSkillStore stays hard-cascade.
113
+
114
+ ---
115
+
116
+ ## Feature flags
117
+
118
+ `staticCapabilities()` declares:
119
+
120
+ | Feature | Value | What it means |
121
+ |---|---|---|
122
+ | `supports_writes` | ✓ | `store()` / `update_status()` / `delete()` mutate state |
123
+ | `supports_versioning` | ✓ | `versions()` returns history; `load(name, version)` returns historical bytes |
124
+ | `supports_tag_filter` | ✓ | `query({ tag: "foo" })` works via `json_extract(metadata_json, '$.tags')` (O(n) scan) |
125
+ | `supports_audit_trail` | ✓ | `update_status()` populates `previous_status` on every transition |
126
+ | `supports_atomic_status_transitions` | ✓ | UPDATE skills + INSERT skill_versions wrapped in a transaction |
127
+
128
+ The atomic transitions are the SQL advantage over FilesystemSkillStore (which declares `supports_atomic_status_transitions: false` because filesystem writes can tear between body rewrite + sidecar append).
129
+
130
+ ---
131
+
132
+ ## Authoring loop
133
+
134
+ SqliteSkillStore is the storage layer. Authoring happens above:
135
+
136
+ - **Dashboard**: visit `http://localhost:7878`, create/edit skills through the UI; dashboard writes via `skill_write` MCP tool → SqliteSkillStore
137
+ - **MCP tool**: agents call `skill_write` directly; same path as the dashboard
138
+ - **Programmatic**: your code calls `store.store(name, source, metadata?)` directly
139
+
140
+ The dashboard does NOT default to SqliteSkillStore today — `skillfile dashboard` bootstraps with FilesystemSkillStore. If you want a SqliteSkillStore-backed dashboard, write a custom bootstrap (small surface; see the runtime `Registry` API).
141
+
142
+ ---
143
+
144
+ ## Approval-token stamping
145
+
146
+ Skill bodies that declare `# Status: Approved` get a token stamped on `store()` automatically: `# Status: Approved v1:<hex>`. Same behavior as FilesystemSkillStore. Transitions to `Approved` via `update_status()` stamp the token; transitions away strip it.
147
+
148
+ Adopters who want a stronger `f()` for the token (HMAC-SHA256 instead of the default) can register a custom approval fn before calling `store()`/`update_status()`:
149
+
150
+ ```typescript
151
+ import { registerApprovalFn } from "skillscript-runtime";
152
+ registerApprovalFn("v2", (body) => hmacSha256(SECRET, body));
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Forking checklist
158
+
159
+ When forking into your codebase:
160
+
161
+ 1. Rename the class (e.g., `PostgresSkillStore`, `AmpSkillStore`)
162
+ 2. Replace the SQL with your substrate's API (HTTP, MemoryStore, vector DB, etc.)
163
+ 3. Update `staticCapabilities()` to match what your substrate actually supports — drop `supports_versioning` if you can't track history, drop `supports_tag_filter` if querying tags isn't tractable
164
+ 4. Update `manifest()` to describe your substrate (`kind: "amp"` or whatever)
165
+ 5. Tests: copy `tests/SqliteSkillStore.test.ts` as a starting point + run the conformance suite (`SkillStoreConformance.buildTests()` from `skillscript-runtime/testing`)
166
+
167
+ The conformance suite catches drift from the contract surface. If your fork passes, the runtime treats it interchangeably with FilesystemSkillStore / SqliteSkillStore / etc.
@@ -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-29T18:50:40.018Z",
5
+ "compiled_at": "2026-05-29T20:29:47.591Z",
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.13.2",
3
+ "version": "0.13.5",
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>",
@@ -61,7 +61,7 @@
61
61
  "dist",
62
62
  "scaffold",
63
63
  "examples",
64
- "ARCHITECTURE.md",
64
+ "docs/*.md",
65
65
  "README.md",
66
66
  "LICENSE"
67
67
  ],
@@ -69,7 +69,8 @@
69
69
  "node": ">=22.5"
70
70
  },
71
71
  "scripts": {
72
- "build": "tsc -p tsconfig.build.json && node scripts/copy-dashboard-assets.mjs",
72
+ "build:dist": "tsc -p tsconfig.build.json && node scripts/copy-dashboard-assets.mjs",
73
+ "build": "pnpm run build:dist && node scripts/check-published-paths.mjs",
73
74
  "dev": "tsc -p tsconfig.build.json --watch",
74
75
  "test": "vitest run",
75
76
  "test:watch": "vitest",
package/ARCHITECTURE.md DELETED
@@ -1,102 +0,0 @@
1
- # Architecture
2
-
3
- One-page map of the `skillscript-runtime` codebase. Per ERD §1, the *narrow core* (parser + compile + runtime + lint + connectors/) stays under the nudged LOC ceiling — currently 5650, tracked by `scripts/loc-ceiling.mjs` with full history in that file's header comment. Auxiliary surface (CLI, dashboard, MCP server, scheduler, observability) is reported but doesn't gate the build. Tests count separately.
4
-
5
- ## Top-level layout
6
-
7
- ```
8
- src/
9
- index.ts — library entrypoint; named exports for embedders
10
- cli.ts — `skillfile` CLI entrypoint
11
- parser.ts — source text → AST (NARROW CORE)
12
- compile.ts — AST → resolved skill model → rendered artifact (NARROW CORE; owns toposort)
13
- filters.ts — pipe-filter implementations (NARROW CORE via connectors/; see notes)
14
- lint.ts — structural validation (NARROW CORE)
15
- runtime.ts — executor: walks compiled artifact, dispatches ops (NARROW CORE)
16
- composition.ts — `$ execute_skill` + `& invoke` runtime dispatch (v0.2.8)
17
- version.ts — single-source RUNTIME_VERSION from package.json (v0.2.12)
18
- help-content.ts — `help({topic})` MCP tool content
19
- errors.ts — OpError class hierarchy + structured runtime errors
20
- provenance.ts — ProvenanceBlock + content_hash recording (Phase 3)
21
- scheduler.ts — trigger registry + cron scan + EVENT.* ambient population
22
- audit.ts — `skillfile audit` recompile-staleness detector
23
- trace.ts — TraceBuilder + on-disk trace store
24
- metrics.ts — health-metrics aggregator
25
- skill-manager.ts — high-level skill lifecycle helpers
26
- bootstrap.ts — wires everything: SkillStore + Registry + MCP server + dashboard
27
- mcp-server.ts — JSON-RPC 2.0 MCP server (13 tools)
28
- connectors/ — NARROW CORE
29
- types.ts — contracts: SkillStore, MemoryStore, LocalModel, McpConnector
30
- agent.ts — AgentConnector contract (Augmenting/Template delivery)
31
- agent-noop.ts — default AgentConnector (no-op delivery)
32
- registry.ts — per-kind instance registry + three-layer resolution
33
- skill-store.ts — bundled default: filesystem at $SKILLSCRIPT_HOME/skills/
34
- memory-store.ts — bundled default: SQLite + FTS at $SKILLSCRIPT_HOME/memory.db
35
- local-model.ts — bundled default: Ollama at localhost:11434
36
- mcp.ts — bundled default: stub; no servers wired by default
37
- config.ts — connectors.json loader (v0.4.0+): parse + validate + ${ENV}/env-block-as-scope substitution + closed-set class registry + gitignore-detection (v0.4.1)
38
- mcp-remote.ts — RemoteMcpConnector (v0.4.1): child-process spawn + JSON-RPC stdio framing (lsp + newline) + initialize handshake + tool dispatch + lifecycle
39
- index.ts — barrel re-exports
40
- dashboard/ — Vite SPA + dashboard HTTP server (v0.2.7)
41
- testing/ — test-only helpers shipped with the package
42
- ```
43
-
44
- Narrow-core LOC history (nudges driven by language extensions):
45
- - 5000 (T7 baseline) → 5100 (v0.2.10 parser robustness) → 5200 (v0.2.12 lint coverage) → 5400 (v0.3.0 `$append` accumulator) → 5500 (v0.3.1 forward-reference deferred resolution) → 5650 (v0.3.2 and/or/not + filter chain + `|json_parse`) → 5700 (v0.3.3 `$ json_parse` op + lint advisory + Bug D parser-recovery; `|json_parse` filter removed) → 5750 (v0.3.4 conditional multi-filter chain + parse-error dedup + unified sink-scope recovery) → 6000 (v0.4.0 connectors.json loader + closed-set class registry + two new lint rules + credential discipline) → 6600 (v0.4.1 RemoteMcpConnector + allowed_tools allowlist + env-block-as-scope + framing config + gitignore-detect + lint auto-wire + foreach-over-parsed-JSON + kwarg coercion)
46
-
47
- ## What each narrow-core file owns
48
-
49
- | File | Responsibility |
50
- | --- | --- |
51
- | `parser.ts` | Tokenize and parse skill source. Header lines, target blocks, op grammar (`!`/`$`/`$set`/`$append`/`?`/`@`/`>`/`~`/`&`/`??`), `if`/`elif`/`else`/`foreach`, compound conditions (`and`/`or`/`not` + parens since v0.3.2). Produces AST. Recursive structural decomposition for compound conditions in `validateCondition`. Syntax errors only — semantic checks downstream. |
52
- | `compile.ts` | Three subsystems: (1) variable resolution against `# Requires:` cascade + caller inputs; (2) data-skill compile-time inlining; (3) topo-sort + render. Output formats: `prompt` (canonical), `prose`. Forward-reference deferral for missing `&` targets (v0.3.1). Produces compiled artifact + provenance sidecar. |
53
- | `filters.ts` | Pipe-filter implementations dispatched by `$(NAME\|filter)` syntax. v1 set: `url`, `shell`, `json`, `trim`, `length`. (`json_parse` was a v0.3.2 addition removed in v0.3.3 — use the `$ json_parse $(VAR) -> OUT` op instead, which binds structured shape.) Adding a new filter = adding a case to `applyFilter` + registering in `KNOWN_FILTERS` + documenting in `help-content.ts`. |
54
- | `lint.ts` | Structured diagnostics across 3 tiers. ~30 rules covering parse errors, var resolution, condition grammar, composition refs (`unknown-skill-reference` demoted to tier-2 in v0.3.1 with tier-3 `deferred-skill-reference` advisory), shell safety (`unsafe-shell-op`, `unsafe-shell-disabled`, `unsafe-shell-ambiguous-subst`), mutation safety (`unconfirmed-mutation`), accumulator safety (v0.3.0 `uninitialized-append`, `foreach-local-accumulator-target`, `append-to-non-list`), retrieval-arg validation, credential leak detection. |
55
- | `runtime.ts` | Executor that walks the compiled artifact and dispatches ops through connector instances. Owns `evalCondition` (compound + leaf shapes), `substituteRuntime` (filter-chain aware since v0.3.2), `resolveRef` (dotted + indexed field access). Handles error propagation, per-op timeout chain, `foreach` iteration with loop-local scope, target-level `else:` error handler, `# OnError:` skill fallback, mechanical-mode placeholders. |
56
- | `connectors/*` | The integration boundary — every external system (skill storage, memory, local model, MCP, agent delivery) plugs in through one of the typed contracts. Registry handles multi-instance + three-layer resolution: per-call override > skill-declared > primary default. |
57
-
58
- ## Auxiliary surface (outside narrow core)
59
-
60
- | File | Responsibility |
61
- | --- | --- |
62
- | `cli.ts` | `skillfile` CLI entrypoint. Commands: `init`, `execute` (since v0.2.11; `run` alias dropped v0.2.12), `compile`, `audit`, `lint`, `list`, `fires`, `diagram`, `sign`, `verify`, `replay`, `health`, `serve`, `dashboard`. Per-command `--help`. Version from `src/version.ts`. |
63
- | `mcp-server.ts` | JSON-RPC 2.0 MCP server exposing 13 tools: `skill_list/metadata/status/write`, `list/register/unregister_trigger`, `health_metrics`, `runtime_capabilities`, `lint_skill`, `compile_skill`, `execute_skill`, `help`. Rolled-by-hand JSON-RPC handler (no `@modelcontextprotocol/sdk` dependency). |
64
- | `composition.ts` | In-skill composition primitive runtime. `$ execute_skill` intercept + recursion-depth guard (default 10). Distinct from data-skill `&` inlining which is compile-time. |
65
- | `scheduler.ts` | Trigger registry + cron firing + EVENT.* ambient auto-population (`fired_at`, `fired_at_unix`, `fired_at_plus_{1h,1d,7d}_unix`). Status-aware: skips Draft/Disabled at fire time. Persistent trigger registry on disk (v0.2.7). |
66
- | `bootstrap.ts` | Top-level wiring: takes `{skillsDir, traceDir, enableUnsafeShell?}` → returns `{registry, skillStore, scheduler, mcpServer, ...}`. The integration test entry point. |
67
- | `dashboard/` | Vite SPA + Express HTTP server. Skill list + status + trace viewer. Mounted under `/` when `skillfile dashboard` runs; serve-headless mode (`skillfile serve`) omits the SPA. |
68
- | `audit.ts` | `skillfile audit <provenance.json>` — detects stale compiled artifacts when source data-skills have been re-stored since compile. |
69
- | `trace.ts` | TraceBuilder + FilesystemTraceStore. Records per-op timing, dispatch, error chain. |
70
- | `metrics.ts` | Aggregates trace data into `health_metrics` MCP response (request counts, op latencies, error rates). |
71
- | `help-content.ts` | Static markdown content for the `help({topic})` MCP tool. Topics: `ops`, `frontmatter`, `examples`, `composition`, `connectors`, `lint-codes`. |
72
- | `version.ts` | Reads `package.json` at module load to single-source the runtime version (added v0.2.12 after a missed bump exposed the triple-source duplication). |
73
-
74
- ## Non-source
75
-
76
- ```
77
- docs/ — spec docs (ERD, Language Reference, README)
78
- examples/ — bundled example skills (5 worked examples, see help({topic:"examples"}))
79
- scripts/loc-ceiling.mjs — CI check; fails if narrow core exceeds budget. Header has full nudge history.
80
- tests/ — vitest specs (39 files, 829 passing)
81
- tests/fixtures/harness/ — 66 cold-author skills + classified manifest (regression corpus)
82
- .github/workflows/ — CI: release.yml fires on tag push → test → GHCR multi-arch + GitHub Release + npm publish
83
- Dockerfile — multi-arch (linux/amd64 + linux/arm64) image base
84
- ```
85
-
86
- ## CI pipeline (release.yml)
87
-
88
- Tag push (`vX.Y.Z`) → typecheck → loc-check → build → full vitest → version verify (tag matches package.json) → Docker Buildx multi-arch GHCR push → GitHub Release with CHANGELOG section as body → npm publish.
89
-
90
- Required secret: `NPM_TOKEN` (granular access token with **Bypass two-factor authentication when publishing** enabled). Token requirement documented in memory `feedback_npm_publish_2fa`. Resolved end-to-end at v0.3.1 after the chronic v0.2.5–v0.2.12 failure pattern (root cause: secret missing, then set without bypass-2FA flag).
91
-
92
- ## Build + dev
93
-
94
- - `pnpm install --frozen-lockfile` — install deps
95
- - `pnpm run build` — `tsc -p tsconfig.build.json` + copy dashboard SPA assets to `dist/`
96
- - `pnpm exec vitest run` — full suite
97
- - `pnpm run loc-check` — narrow-core ceiling check (CI gate)
98
- - `node dist/cli.js dashboard --host 0.0.0.0 --port 7878` — local dashboard
99
- - `node dist/cli.js execute <skill>` — run a skill end-to-end
100
- - `node dist/cli.js compile <skill>` — render compiled artifact without executing
101
-
102
- ESM-only. Node 22+ required (`node:sqlite`). pnpm 11.