skillscript-runtime 0.2.4

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 (132) hide show
  1. package/ARCHITECTURE.md +70 -0
  2. package/LICENSE +21 -0
  3. package/README.md +346 -0
  4. package/dist/audit.d.ts +33 -0
  5. package/dist/audit.d.ts.map +1 -0
  6. package/dist/audit.js +76 -0
  7. package/dist/audit.js.map +1 -0
  8. package/dist/bootstrap.d.ts +69 -0
  9. package/dist/bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap.js +117 -0
  11. package/dist/bootstrap.js.map +1 -0
  12. package/dist/cli.d.ts +3 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +805 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/compile.d.ts +88 -0
  17. package/dist/compile.d.ts.map +1 -0
  18. package/dist/compile.js +544 -0
  19. package/dist/compile.js.map +1 -0
  20. package/dist/connectors/agent-noop.d.ts +23 -0
  21. package/dist/connectors/agent-noop.d.ts.map +1 -0
  22. package/dist/connectors/agent-noop.js +43 -0
  23. package/dist/connectors/agent-noop.js.map +1 -0
  24. package/dist/connectors/agent.d.ts +54 -0
  25. package/dist/connectors/agent.d.ts.map +1 -0
  26. package/dist/connectors/agent.js +21 -0
  27. package/dist/connectors/agent.js.map +1 -0
  28. package/dist/connectors/index.d.ts +13 -0
  29. package/dist/connectors/index.d.ts.map +1 -0
  30. package/dist/connectors/index.js +17 -0
  31. package/dist/connectors/index.js.map +1 -0
  32. package/dist/connectors/local-model.d.ts +41 -0
  33. package/dist/connectors/local-model.d.ts.map +1 -0
  34. package/dist/connectors/local-model.js +106 -0
  35. package/dist/connectors/local-model.js.map +1 -0
  36. package/dist/connectors/mcp.d.ts +22 -0
  37. package/dist/connectors/mcp.d.ts.map +1 -0
  38. package/dist/connectors/mcp.js +31 -0
  39. package/dist/connectors/mcp.js.map +1 -0
  40. package/dist/connectors/memory-store.d.ts +53 -0
  41. package/dist/connectors/memory-store.d.ts.map +1 -0
  42. package/dist/connectors/memory-store.js +169 -0
  43. package/dist/connectors/memory-store.js.map +1 -0
  44. package/dist/connectors/registry.d.ts +74 -0
  45. package/dist/connectors/registry.d.ts.map +1 -0
  46. package/dist/connectors/registry.js +127 -0
  47. package/dist/connectors/registry.js.map +1 -0
  48. package/dist/connectors/skill-store.d.ts +38 -0
  49. package/dist/connectors/skill-store.d.ts.map +1 -0
  50. package/dist/connectors/skill-store.js +314 -0
  51. package/dist/connectors/skill-store.js.map +1 -0
  52. package/dist/connectors/types.d.ts +188 -0
  53. package/dist/connectors/types.d.ts.map +1 -0
  54. package/dist/connectors/types.js +35 -0
  55. package/dist/connectors/types.js.map +1 -0
  56. package/dist/dashboard/server.d.ts +40 -0
  57. package/dist/dashboard/server.d.ts.map +1 -0
  58. package/dist/dashboard/server.js +122 -0
  59. package/dist/dashboard/server.js.map +1 -0
  60. package/dist/dashboard/spa/app.js +375 -0
  61. package/dist/dashboard/spa/index.html +26 -0
  62. package/dist/dashboard/spa/styles.css +99 -0
  63. package/dist/errors.d.ts +111 -0
  64. package/dist/errors.d.ts.map +1 -0
  65. package/dist/errors.js +187 -0
  66. package/dist/errors.js.map +1 -0
  67. package/dist/filters.d.ts +17 -0
  68. package/dist/filters.d.ts.map +1 -0
  69. package/dist/filters.js +40 -0
  70. package/dist/filters.js.map +1 -0
  71. package/dist/index.d.ts +41 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +33 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/lint.d.ts +97 -0
  76. package/dist/lint.d.ts.map +1 -0
  77. package/dist/lint.js +990 -0
  78. package/dist/lint.js.map +1 -0
  79. package/dist/mcp-server.d.ts +93 -0
  80. package/dist/mcp-server.d.ts.map +1 -0
  81. package/dist/mcp-server.js +505 -0
  82. package/dist/mcp-server.js.map +1 -0
  83. package/dist/metrics.d.ts +51 -0
  84. package/dist/metrics.d.ts.map +1 -0
  85. package/dist/metrics.js +107 -0
  86. package/dist/metrics.js.map +1 -0
  87. package/dist/parser.d.ts +160 -0
  88. package/dist/parser.d.ts.map +1 -0
  89. package/dist/parser.js +991 -0
  90. package/dist/parser.js.map +1 -0
  91. package/dist/provenance.d.ts +43 -0
  92. package/dist/provenance.d.ts.map +1 -0
  93. package/dist/provenance.js +58 -0
  94. package/dist/provenance.js.map +1 -0
  95. package/dist/runtime.d.ts +145 -0
  96. package/dist/runtime.d.ts.map +1 -0
  97. package/dist/runtime.js +1071 -0
  98. package/dist/runtime.js.map +1 -0
  99. package/dist/scheduler.d.ts +121 -0
  100. package/dist/scheduler.d.ts.map +1 -0
  101. package/dist/scheduler.js +271 -0
  102. package/dist/scheduler.js.map +1 -0
  103. package/dist/skill-manager.d.ts +121 -0
  104. package/dist/skill-manager.d.ts.map +1 -0
  105. package/dist/skill-manager.js +251 -0
  106. package/dist/skill-manager.js.map +1 -0
  107. package/dist/testing/conformance.d.ts +57 -0
  108. package/dist/testing/conformance.d.ts.map +1 -0
  109. package/dist/testing/conformance.js +365 -0
  110. package/dist/testing/conformance.js.map +1 -0
  111. package/dist/testing/index.d.ts +3 -0
  112. package/dist/testing/index.d.ts.map +1 -0
  113. package/dist/testing/index.js +5 -0
  114. package/dist/testing/index.js.map +1 -0
  115. package/dist/trace.d.ts +141 -0
  116. package/dist/trace.d.ts.map +1 -0
  117. package/dist/trace.js +226 -0
  118. package/dist/trace.js.map +1 -0
  119. package/examples/README.md +56 -0
  120. package/examples/classify-support-ticket.skill.md +30 -0
  121. package/examples/cut-release-tag.skill.md +40 -0
  122. package/examples/doc-qa-with-citations.skill.md +12 -0
  123. package/examples/feedback-sentiment-scan.skill.md +29 -0
  124. package/examples/hello.skill.md +9 -0
  125. package/examples/hello.skill.provenance.json +10 -0
  126. package/examples/morning-brief.skill.md +24 -0
  127. package/examples/programmatic-trace-demo.mjs +89 -0
  128. package/examples/service-health-watch.skill.md +18 -0
  129. package/package.json +100 -0
  130. package/scaffold/config.toml +35 -0
  131. package/scaffold/connectors.json +19 -0
  132. package/scaffold/examples/hello.skill.md +9 -0
package/dist/trace.js ADDED
@@ -0,0 +1,226 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readdir, readFile, unlink, writeFile } from "node:fs/promises";
3
+ import { join, basename, dirname } from "node:path";
4
+ const DEFAULT_SAMPLE_PCT = 10;
5
+ const DEFAULT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
6
+ // ─── FilesystemTraceStore (bundled default) ─────────────────────────────────
7
+ /**
8
+ * Writes JSON records under `<rootDir>/<skill_name>/<trace_id>.json`.
9
+ * Zero external dependency; suitable for the standalone runtime out of
10
+ * the box. Operators with a substrate wired can swap for a
11
+ * substrate-backed store via config.
12
+ */
13
+ export class FilesystemTraceStore {
14
+ rootDir;
15
+ constructor(rootDir) {
16
+ this.rootDir = rootDir;
17
+ }
18
+ async write(record) {
19
+ const dir = join(this.rootDir, sanitize(record.skill_name));
20
+ await mkdir(dir, { recursive: true });
21
+ const path = join(dir, `${record.trace_id}.json`);
22
+ await writeFile(path, JSON.stringify(record, null, 2), "utf8");
23
+ }
24
+ async query(filter) {
25
+ const results = [];
26
+ let skillDirs;
27
+ if (filter.skill_name !== undefined) {
28
+ skillDirs = [sanitize(filter.skill_name)];
29
+ }
30
+ else {
31
+ try {
32
+ skillDirs = await readdir(this.rootDir);
33
+ }
34
+ catch (err) {
35
+ if (err.code === "ENOENT")
36
+ return [];
37
+ throw err;
38
+ }
39
+ }
40
+ for (const skillDir of skillDirs) {
41
+ const full = join(this.rootDir, skillDir);
42
+ let entries;
43
+ try {
44
+ entries = await readdir(full);
45
+ }
46
+ catch (err) {
47
+ if (err.code === "ENOENT")
48
+ continue;
49
+ throw err;
50
+ }
51
+ for (const entry of entries) {
52
+ if (!entry.endsWith(".json"))
53
+ continue;
54
+ try {
55
+ const text = await readFile(join(full, entry), "utf8");
56
+ const rec = JSON.parse(text);
57
+ if (filter.since_ms !== undefined && rec.fired_at_ms < filter.since_ms)
58
+ continue;
59
+ if (filter.until_ms !== undefined && rec.fired_at_ms > filter.until_ms)
60
+ continue;
61
+ results.push(rec);
62
+ }
63
+ catch {
64
+ /* unreadable / unparseable — skip */
65
+ }
66
+ }
67
+ }
68
+ results.sort((a, b) => b.fired_at_ms - a.fired_at_ms);
69
+ if (filter.limit !== undefined)
70
+ return results.slice(0, filter.limit);
71
+ return results;
72
+ }
73
+ async get(traceId) {
74
+ let skillDirs;
75
+ try {
76
+ skillDirs = await readdir(this.rootDir);
77
+ }
78
+ catch (err) {
79
+ if (err.code === "ENOENT")
80
+ return null;
81
+ throw err;
82
+ }
83
+ for (const skillDir of skillDirs) {
84
+ const path = join(this.rootDir, skillDir, `${traceId}.json`);
85
+ try {
86
+ const text = await readFile(path, "utf8");
87
+ return JSON.parse(text);
88
+ }
89
+ catch (err) {
90
+ if (err.code === "ENOENT")
91
+ continue;
92
+ throw err;
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ async prune(retentionMs) {
98
+ const cutoff = Date.now() - retentionMs;
99
+ let count = 0;
100
+ let skillDirs;
101
+ try {
102
+ skillDirs = await readdir(this.rootDir);
103
+ }
104
+ catch (err) {
105
+ if (err.code === "ENOENT")
106
+ return 0;
107
+ throw err;
108
+ }
109
+ for (const skillDir of skillDirs) {
110
+ const full = join(this.rootDir, skillDir);
111
+ let entries;
112
+ try {
113
+ entries = await readdir(full);
114
+ }
115
+ catch {
116
+ continue;
117
+ }
118
+ for (const entry of entries) {
119
+ if (!entry.endsWith(".json"))
120
+ continue;
121
+ const path = join(full, entry);
122
+ try {
123
+ const text = await readFile(path, "utf8");
124
+ const rec = JSON.parse(text);
125
+ if (rec.fired_at_ms < cutoff) {
126
+ await unlink(path);
127
+ count++;
128
+ }
129
+ }
130
+ catch {
131
+ /* skip unreadable */
132
+ }
133
+ }
134
+ }
135
+ return count;
136
+ }
137
+ }
138
+ // ─── Sampling decision ─────────────────────────────────────────────────────
139
+ /**
140
+ * Deterministic sample decision per Open Q #2: SHA-256 of `trigger_id +
141
+ * ":" + skill_name`, take the first byte mod 100, fire trace if under
142
+ * `pct`. Same inputs always produce the same sampling decision — useful
143
+ * for reproducible testing + dashboard drill-down.
144
+ */
145
+ export function shouldSample(triggerId, skillName, pct) {
146
+ if (pct >= 100)
147
+ return true;
148
+ if (pct <= 0)
149
+ return false;
150
+ const hash = createHash("sha256").update(`${triggerId}:${skillName}`).digest();
151
+ return hash[0] % 100 < pct;
152
+ }
153
+ // ─── TraceBuilder (mutable, used during execution) ──────────────────────────
154
+ /**
155
+ * Accumulator used by the runtime during a single fire. Records per-op
156
+ * timing + body; finalize() builds the immutable TraceRecord.
157
+ */
158
+ export class TraceBuilder {
159
+ skill_name;
160
+ skill_version;
161
+ trigger;
162
+ identity;
163
+ ops = [];
164
+ firedAtMs;
165
+ trace_id;
166
+ constructor(skill_name, skill_version, trigger, identity) {
167
+ this.skill_name = skill_name;
168
+ this.skill_version = skill_version;
169
+ this.trigger = trigger;
170
+ this.identity = identity;
171
+ this.firedAtMs = trigger.fired_at_ms;
172
+ this.trace_id = randomUUID();
173
+ }
174
+ recordOp(record) {
175
+ this.ops.push(record);
176
+ }
177
+ finalize(emissions, outputs, errors) {
178
+ const completedAtMs = Date.now();
179
+ return {
180
+ version: 1,
181
+ trace_id: this.trace_id,
182
+ skill_name: this.skill_name,
183
+ skill_version: this.skill_version,
184
+ trigger: this.trigger,
185
+ identity: this.identity,
186
+ ops: this.ops,
187
+ emissions: [...emissions],
188
+ outputs: { ...outputs },
189
+ errors: [...errors],
190
+ fired_at_ms: this.firedAtMs,
191
+ completed_at_ms: completedAtMs,
192
+ duration_ms: completedAtMs - this.firedAtMs,
193
+ };
194
+ }
195
+ }
196
+ /**
197
+ * Decide whether a fire should be traced given config + trigger context.
198
+ * Used by the runtime / scheduler at fire start. `mode === "off"` always
199
+ * returns false (errors are still surfaced via `result.errors[]` — the
200
+ * NFR-11 floor); the trace store is bypassed entirely.
201
+ */
202
+ export function shouldTraceFire(config, triggerId, skillName) {
203
+ if (config === undefined || config.mode === "off")
204
+ return false;
205
+ if (config.mode === "on")
206
+ return true;
207
+ const pct = config.samplePct ?? DEFAULT_SAMPLE_PCT;
208
+ return shouldSample(triggerId, skillName, pct);
209
+ }
210
+ export const TRACE_DEFAULTS = {
211
+ SAMPLE_PCT: DEFAULT_SAMPLE_PCT,
212
+ RETENTION_MS: DEFAULT_RETENTION_MS,
213
+ };
214
+ // ─── Internals ──────────────────────────────────────────────────────────────
215
+ function sanitize(name) {
216
+ // Filesystem-safe per the FilesystemSkillStore convention. Same charset
217
+ // — alphanumeric, hyphen, underscore, dot.
218
+ return name.replace(/[^A-Za-z0-9._-]/g, "_");
219
+ }
220
+ /** Convenience for tests: derive the on-disk path FilesystemTraceStore writes to. */
221
+ export function _traceFilePathFor(rootDir, record) {
222
+ void basename;
223
+ void dirname;
224
+ return join(rootDir, sanitize(record.skill_name), `${record.trace_id}.json`);
225
+ }
226
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ # Examples
2
+
3
+ Curated production-quality skills demonstrating distinct language patterns. Each compiles + lints clean and can be run directly via `skillfile run examples/<name>.skill.md` (some require connectors — see the per-skill notes below).
4
+
5
+ For full language semantics, see [`../docs/language-reference.md`](../docs/language-reference.md).
6
+
7
+ | Example | Patterns demonstrated |
8
+ |---|---|
9
+ | [`hello.skill.md`](./hello.skill.md) | Three-command first run; `! emit`; default target; `# Vars:` declared inputs |
10
+ | [`morning-brief.skill.md`](./morning-brief.skill.md) | Multi-target with `needs:`; cron-fired with `EVENT.fired_at_*`; `# OnError:` fallback; dual `# Output:` (slack + prompt-context); `# Requires:` user-var cascade with fallback; LocalModel + retrieval composition |
11
+ | [`doc-qa-with-citations.skill.md`](./doc-qa-with-citations.skill.md) | Single-target retrieval; `(fallback: [])` op-level fallback; LLM-with-citation pattern; pipe filter `\|json` for prompt embedding |
12
+ | [`classify-support-ticket.skill.md`](./classify-support-ticket.skill.md) | `if`/`elif`/`else` multi-branch routing; classifier-cascade pattern; MemoryStore `$` writes with structured `domain_tags`; `$set` literal binding |
13
+ | [`cut-release-tag.skill.md`](./cut-release-tag.skill.md) | `??` interactive ask-for-input; `else:` short-circuit on multiple targets; `@` shell ops with per-op error fallback; rollback-on-failure flow |
14
+ | [`service-health-watch.skill.md`](./service-health-watch.skill.md) | `foreach` iteration; cron-fired with `expires_at=$(EVENT.fired_at_plus_1d_unix)` TTL math; classifier-gated MemoryStore writes; pipe filter `\|url` for path-safe interpolation |
15
+ | [`feedback-sentiment-scan.skill.md`](./feedback-sentiment-scan.skill.md) | `in` / `not in` set-membership ops; dedupe-via-seen-markers idiom; nested `if`/`elif` classification cascade; TTL on bookkeeping writes |
16
+
17
+ ## Connector requirements
18
+
19
+ Most examples assume the bundled-default connectors:
20
+
21
+ - `hello.skill.md` — zero deps (runs cold)
22
+ - `doc-qa-with-citations.skill.md` — Ollama (LocalModel) + MemoryStore for the retrieval cache
23
+ - `morning-brief.skill.md`, `feedback-sentiment-scan.skill.md` — Ollama + MemoryStore + a calendar MCP connector (the `$ calendar.list_events` op)
24
+ - `classify-support-ticket.skill.md` — Ollama + MemoryStore
25
+ - `cut-release-tag.skill.md` — git CLI on `PATH`; expects to run inside a git repo
26
+ - `service-health-watch.skill.md` — `curl` on `PATH`; network egress to `status.internal/*`
27
+
28
+ Skills that talk to MCP tools (like `calendar.list_events`) need that tool wired into the `Registry` via `registerMcpConnector()`. The `CallbackMcpConnector` in `skillscript-runtime/connectors` lets you stub one inline for development.
29
+
30
+ ## Running an example
31
+
32
+ ```sh
33
+ # Cold install — no Ollama, no MCP, no external state
34
+ skillfile run examples/hello.skill.md
35
+
36
+ # With overrides
37
+ skillfile run examples/hello.skill.md --input WHO=Scott
38
+
39
+ # Preview the compiled artifact without execution
40
+ skillfile compile examples/hello.skill.md
41
+
42
+ # Mechanical preview — $/~/> ops short-circuit (useful for examining flow
43
+ # without firing real LLM calls)
44
+ skillfile run examples/morning-brief.skill.md --mechanical
45
+
46
+ # Render the control-flow graph
47
+ skillfile diagram examples/cut-release-tag.skill.md
48
+ ```
49
+
50
+ ## Programmatic example
51
+
52
+ `programmatic-trace-demo.mjs` shows the library API path — wiring a `FilesystemSkillStore` + `FilesystemTraceStore` + `Scheduler`, dispatching a skill, and reading back the recorded trace + per-skill health metrics.
53
+
54
+ ```sh
55
+ node examples/programmatic-trace-demo.mjs
56
+ ```
@@ -0,0 +1,30 @@
1
+ # Skill: classify-support-ticket
2
+ # Status: Approved
3
+ # Description: Read an incoming support ticket and route it: severity-1 tickets get paged to ops-channel, billing tickets get tagged for finance review, everything else gets a draft reply queued for human review
4
+ # Vars: TICKET_TEXT, TICKET_ID
5
+ # Output: slack: ops-pages
6
+
7
+ classify:
8
+ ~ prompt="Categorize this support ticket. Reply with EXACTLY one of: 'sev-1', 'billing', 'general'. No other text.\n\nTicket: $(TICKET_TEXT)" model=qwen maxTokens=10 -> CATEGORY
9
+
10
+ severity_check:
11
+ needs: classify
12
+ ~ prompt="Does this support ticket describe a service outage, data loss, security incident, or other production-severity-1 issue? Reply ONLY 'yes' or 'no'.\n\nTicket: $(TICKET_TEXT)" model=qwen maxTokens=10 -> IS_SEV1
13
+
14
+ route:
15
+ needs: severity_check
16
+ if $(CATEGORY|trim) == "sev-1":
17
+ ! PAGE: sev-1 ticket $(TICKET_ID) - $(TICKET_TEXT)
18
+ $ memorystore.write summary="sev-1 ticket $(TICKET_ID)" detail="$(TICKET_TEXT)" knowledge_type=hard_won vault=private domain_tags=["support","sev-1","page"]
19
+ elif $(IS_SEV1|trim) == "yes":
20
+ ! PAGE: classifier said $(CATEGORY|trim) but severity-check flagged this as sev-1: $(TICKET_ID)
21
+ $ memorystore.write summary="sev-1 escalation $(TICKET_ID)" detail="category=$(CATEGORY|trim) but severity-check=yes" knowledge_type=hard_won vault=private domain_tags=["support","sev-1","disagreement"]
22
+ elif $(CATEGORY|trim) == "billing":
23
+ $set TAG_FOR = "finance"
24
+ ! Tagged for $(TAG_FOR) review: $(TICKET_ID)
25
+ $ memorystore.write summary="billing ticket $(TICKET_ID)" detail="$(TICKET_TEXT)" knowledge_type=common vault=private domain_tags=["support","billing"]
26
+ else:
27
+ ~ prompt="Draft a polite acknowledgment reply for this support ticket. Two short sentences. No greeting, no sign-off.\n\n$(TICKET_TEXT)" model=qwen maxTokens=150 -> DRAFT
28
+ $ memorystore.write summary="support draft $(TICKET_ID)" detail="$(DRAFT|trim)" knowledge_type=common vault=private domain_tags=["support","draft"]
29
+
30
+ default: route
@@ -0,0 +1,40 @@
1
+ # Skill: cut-release-tag
2
+ # Status: Approved
3
+ # Description: Run when the user says "ship a release" or "cut a tag" on the current branch; collects commit-list since last tag, asks the user to confirm the version bump, applies the tag, and pushes it
4
+ # Vars: BUMP_KIND=patch
5
+ # Output: text
6
+
7
+ last_tag:
8
+ @ git describe --tags --abbrev=0 -> PREV_TAG
9
+ else:
10
+ $set PREV_TAG = "(no prior tags)"
11
+ ! no previous tag found; treating this as the first release
12
+
13
+ commits_since:
14
+ needs: last_tag
15
+ @ git log --oneline $(PREV_TAG)..HEAD -> COMMITS
16
+ else:
17
+ $set COMMITS = "(unable to read commit log; proceeding without preview)"
18
+
19
+ propose:
20
+ needs: commits_since
21
+ ~ prompt="Propose the next semver tag given prev tag '$(PREV_TAG)', bump kind '$(BUMP_KIND)', and commits below. Return ONLY the new tag string (e.g. v1.4.3). No commentary.\n\nCommits:\n$(COMMITS)" model=qwen maxTokens=30 -> PROPOSED_TAG
22
+
23
+ confirm:
24
+ needs: propose
25
+ ! Previous tag: $(PREV_TAG)
26
+ ! Commits since:
27
+ ! $(COMMITS)
28
+ ! Proposed new tag: $(PROPOSED_TAG|trim)
29
+ ?? "Cut tag $(PROPOSED_TAG|trim)?" -> APPROVED
30
+
31
+ apply:
32
+ needs: confirm
33
+ @ git tag $(PROPOSED_TAG|trim) -> TAG_OUT
34
+ @ git push origin $(PROPOSED_TAG|trim) -> PUSH_OUT
35
+ ! Tagged and pushed $(PROPOSED_TAG|trim)
36
+ else:
37
+ ! Tag/push failed; rolling back local tag
38
+ @ git tag -d $(PROPOSED_TAG|trim) -> ROLLBACK
39
+
40
+ default: apply
@@ -0,0 +1,12 @@
1
+ # Skill: doc-qa-with-citations
2
+ # Status: Approved
3
+ # Description: When the user asks a question that requires retrieval over the doc set, answer with inline citations to memory IDs
4
+ # Vars: QUESTION, K=6
5
+ # Output: text
6
+
7
+ answer:
8
+ > mode=rerank query="$(QUESTION)" limit=$(K) -> HITS (fallback: [])
9
+ ~ prompt="Answer the question using ONLY the supplied passages. Cite each claim inline as [id:<memory-id>]. Question: $(QUESTION). Passages: $(HITS|json)" maxTokens=900 -> RESPONSE
10
+ ! $(RESPONSE)
11
+
12
+ default: answer
@@ -0,0 +1,29 @@
1
+ # Skill: feedback-sentiment-scan
2
+ # Status: Approved
3
+ # Description: Each night, scan the previous 24h of customer feedback records, classify sentiment via local model, surface entries where sentiment is "frustrated" or "blocking" so the team sees them at start-of-day; skip entries already seen on prior nights
4
+ # Triggers: cron: 0 3 * * *
5
+ # Vars: SCAN_LIMIT=50
6
+ # Output: slack: customer-pulse
7
+ # Output: prompt-context: support-lead
8
+
9
+ fetch_new:
10
+ > mode=fts query="customer feedback" limit=$(SCAN_LIMIT) created_after=$(EVENT.fired_at_unix) -> FEEDBACK
11
+
12
+ fetch_seen:
13
+ > mode=fts query="sentiment-scan seen marker" limit=200 domain_tags=["sentiment-scan-seen"] -> SEEN_MARKERS
14
+
15
+ classify_and_emit:
16
+ needs: fetch_new, fetch_seen
17
+ ! Sentiment scan results for $(NOW):
18
+ foreach F in $(FEEDBACK):
19
+ if $(F.id|trim) in $(SEEN_MARKERS):
20
+ ! - skipped (already classified): $(F.id|trim)
21
+ elif $(F.id|trim) not in $(SEEN_MARKERS):
22
+ ~ prompt="Classify the sentiment of this customer feedback. Respond with ONE word: 'frustrated', 'blocking', 'satisfied', 'neutral'. No explanation.\n\nFeedback: $(F.summary)\nDetail: $(F.detail)" model=gemma2 maxTokens=10 -> VERDICT
23
+ if $(VERDICT|trim) == "frustrated":
24
+ ! - FRUSTRATED [$(F.id|trim)] $(F.summary)
25
+ elif $(VERDICT|trim) == "blocking":
26
+ ! - BLOCKING [$(F.id|trim)] $(F.summary)
27
+ $ memorystore.write summary="sentiment-scan seen $(F.id|trim)" detail="verdict=$(VERDICT|trim) on $(EVENT.fired_at_unix)" knowledge_type=common vault=private domain_tags=["sentiment-scan-seen"] expires_at=$(EVENT.fired_at_plus_7d_unix)
28
+
29
+ default: classify_and_emit
@@ -0,0 +1,9 @@
1
+ # Skill: hello
2
+ # Description: The canonical first-run example — runs without Ollama, MCP, or external state.
3
+ # Vars: WHO=world
4
+
5
+ greet:
6
+ ! Hello, $(WHO)!
7
+ ! Welcome to Skillscript. You ran a skill end-to-end with three commands.
8
+
9
+ default: greet
@@ -0,0 +1,10 @@
1
+ {
2
+ "provenance_version": "1.0",
3
+ "language_version": "1.0",
4
+ "compiler_version": "0.1.0-dev",
5
+ "compiled_at": "2026-05-22T17:41:55.239Z",
6
+ "source_skill": {
7
+ "name": "hello"
8
+ },
9
+ "data_skills_inlined": []
10
+ }
@@ -0,0 +1,24 @@
1
+ # Skill: morning-brief
2
+ # Status: Approved
3
+ # Description: Compose a daily morning brief from calendar, mailbox, and overnight memory writes when the cron trigger fires at 7am.
4
+ # Vars: AGENT, BRIEF_HORIZON_HOURS=24
5
+ # Requires: user-var:morning-brief-channel -> CHANNEL (fallback: dev-null)
6
+ # Triggers: cron: 0 7 * * *
7
+ # OnError: morning-brief-degraded
8
+ # Output: slack: $(CHANNEL)
9
+ # Output: prompt-context: perry
10
+
11
+ calendar:
12
+ $ calendar.list_events horizon_hours=$(BRIEF_HORIZON_HOURS) -> EVENTS
13
+
14
+ mailbox:
15
+ > mode=fts query="addressed:$(AGENT) created_after:$(EVENT.fired_at_unix)" limit=10 -> MAIL
16
+
17
+ overnight:
18
+ > mode=rerank query="overnight writes since:$(EVENT.fired_at_plus_1d_unix)" limit=15 -> NOTES
19
+
20
+ compose: needs: calendar, mailbox, overnight
21
+ ~ prompt="Compose a concise morning brief. Calendar: $(EVENTS|json). Mailbox: $(MAIL|json). Overnight notes: $(NOTES|json). Three sections, six bullets max each." model=qwen maxTokens=1200 -> BRIEF
22
+ ! $(BRIEF)
23
+
24
+ default: compose
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ // Programmatic Scheduler demo — exercises the library API surface that
3
+ // embedders (not just CLI users) would use. Wires:
4
+ // - FilesystemSkillStore for skill sources
5
+ // - FilesystemTraceStore for dispatch traces
6
+ // - Scheduler with trace recording enabled
7
+ // Then registers a cron trigger, dispatches a few times, and queries
8
+ // traces + metrics back. ~60 lines of operator-shaped code.
9
+ //
10
+ // Run: node examples/programmatic-trace-demo.mjs
11
+ // Uses: /tmp/skillscript-prog-demo as a sandbox.
12
+
13
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import {
16
+ Scheduler,
17
+ FilesystemSkillStore,
18
+ FilesystemTraceStore,
19
+ Registry,
20
+ healthMetrics,
21
+ } from "../dist/index.js";
22
+
23
+ const HOME = "/tmp/skillscript-prog-demo";
24
+ rmSync(HOME, { recursive: true, force: true });
25
+ mkdirSync(join(HOME, "skills"), { recursive: true });
26
+ mkdirSync(join(HOME, "traces"), { recursive: true });
27
+
28
+ const skillStore = new FilesystemSkillStore(join(HOME, "skills"));
29
+ const traceStore = new FilesystemTraceStore(join(HOME, "traces"));
30
+
31
+ const SKILL_SRC = `# Skill: heartbeat
32
+ # Description: Per-fire heartbeat exercising the observability surface.
33
+ # Status: Approved
34
+ # Triggers: cron: */1 * * * *
35
+
36
+ emit:
37
+ ! heartbeat at $(EVENT.fired_at_unix)
38
+
39
+ default: emit
40
+ `;
41
+ await skillStore.store("heartbeat", SKILL_SRC);
42
+ console.log(`stored skill at ${HOME}/skills/heartbeat.skill.md\n`);
43
+
44
+ const sched = new Scheduler({
45
+ registry: new Registry(),
46
+ skillStore,
47
+ traceStore,
48
+ trace: { mode: "on" },
49
+ });
50
+
51
+ sched.registerTrigger({
52
+ skillName: "heartbeat",
53
+ source: "cron",
54
+ name: "*/1 * * * *",
55
+ declarative: true,
56
+ });
57
+
58
+ // Fire 5 times via direct dispatch (the scheduler would also fire these
59
+ // via the poll loop; direct dispatch is faster for the demo).
60
+ console.log("dispatching 5 fires...");
61
+ for (let i = 0; i < 5; i++) {
62
+ const firedAtMs = Date.now();
63
+ const result = await sched.dispatchSkill("heartbeat", undefined, {
64
+ source: "cron",
65
+ name: "*/1 * * * *",
66
+ fired_at_ms: firedAtMs,
67
+ trigger_id: `demo-${i}`,
68
+ });
69
+ const status = result.errors.length === 0 ? "ok" : `err:${result.errors[0].class}`;
70
+ console.log(` fire ${i + 1}: ${status} (${result.emissions[0]})`);
71
+ // Brief sleep so fired_at_ms timestamps don't collide.
72
+ await new Promise((r) => setTimeout(r, 20));
73
+ }
74
+
75
+ console.log("\nquerying traces back:");
76
+ const traces = await traceStore.query({ skill_name: "heartbeat", limit: 10 });
77
+ for (const t of traces) {
78
+ const ts = new Date(t.fired_at_ms).toISOString();
79
+ console.log(` ${ts} ${t.trace_id} ops=${t.ops.length} errors=${t.errors.length}`);
80
+ }
81
+
82
+ console.log("\nhealth metrics:");
83
+ const metrics = await healthMetrics(traceStore, { since_ms: Date.now() - 60_000 });
84
+ console.log(` total fires: ${metrics.totalFires}`);
85
+ for (const [name, m] of Object.entries(metrics.perSkill)) {
86
+ console.log(` ${name}: ${m.fireCount} fires, successRate=${(m.successRate * 100).toFixed(0)}%`);
87
+ }
88
+
89
+ console.log(`\ntrace files on disk: ${HOME}/traces/heartbeat/`);
@@ -0,0 +1,18 @@
1
+ # Skill: service-health-watch
2
+ # Status: Approved
3
+ # Description: Every 5 minutes check named service endpoints — if latency or status degrades, write a signal memory and alert
4
+ # Vars: SERVICES=[auth-api, ledger-api, search-api], LATENCY_BUDGET_MS=400
5
+ # Triggers: cron: */5 * * * *
6
+ # Output: none
7
+
8
+ probe:
9
+ foreach SVC in $(SERVICES):
10
+ @ curl -s -o /dev/null -w "%{http_code} %{time_total}" https://status.internal/$(SVC|url) -> RAW
11
+ ~ prompt="From the line '$(RAW|trim)' (http_code time_seconds), and budget $(LATENCY_BUDGET_MS) ms, answer ok or degraded only." -> STATUS
12
+ if $(STATUS|trim) == "degraded":
13
+ $ memorystore.write summary="service degradation: $(SVC)" detail="probe at $(NOW): $(RAW|trim)" domain_tags=[ops, service-health, degraded:$(SVC)] vault=private knowledge_type=common expires_at=$(EVENT.fired_at_plus_1d_unix) -> ACK
14
+ ! $(SVC) degraded — wrote signal
15
+ else:
16
+ ! $(SVC) ok
17
+
18
+ default: probe
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "skillscript-runtime",
3
+ "version": "0.2.4",
4
+ "description": "Runtime, compiler, lint, CLI, and dashboard for Skillscript — a small declarative language for authoring agent workflows.",
5
+ "license": "MIT",
6
+ "author": "Scott Shwarts <scotts@pobox.com>",
7
+ "homepage": "https://github.com/sshwarts/skillscript-runtime",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/sshwarts/skillscript-runtime.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/sshwarts/skillscript-runtime/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./connectors": {
24
+ "types": "./dist/connectors/index.d.ts",
25
+ "import": "./dist/connectors/index.js"
26
+ },
27
+ "./errors": {
28
+ "types": "./dist/errors.d.ts",
29
+ "import": "./dist/errors.js"
30
+ },
31
+ "./runtime": {
32
+ "types": "./dist/runtime.d.ts",
33
+ "import": "./dist/runtime.js"
34
+ },
35
+ "./trace": {
36
+ "types": "./dist/trace.d.ts",
37
+ "import": "./dist/trace.js"
38
+ },
39
+ "./metrics": {
40
+ "types": "./dist/metrics.d.ts",
41
+ "import": "./dist/metrics.js"
42
+ },
43
+ "./scheduler": {
44
+ "types": "./dist/scheduler.d.ts",
45
+ "import": "./dist/scheduler.js"
46
+ },
47
+ "./mcp-server": {
48
+ "types": "./dist/mcp-server.d.ts",
49
+ "import": "./dist/mcp-server.js"
50
+ },
51
+ "./testing": {
52
+ "types": "./dist/testing/index.d.ts",
53
+ "import": "./dist/testing/index.js"
54
+ },
55
+ "./package.json": "./package.json"
56
+ },
57
+ "bin": {
58
+ "skillfile": "./dist/cli.js"
59
+ },
60
+ "files": [
61
+ "dist",
62
+ "scaffold",
63
+ "examples",
64
+ "ARCHITECTURE.md",
65
+ "README.md",
66
+ "LICENSE"
67
+ ],
68
+ "engines": {
69
+ "node": ">=22.5"
70
+ },
71
+ "keywords": [
72
+ "skillscript",
73
+ "agent",
74
+ "agent-workflow",
75
+ "dsl",
76
+ "declarative",
77
+ "workflow",
78
+ "llm",
79
+ "mcp",
80
+ "model-context-protocol"
81
+ ],
82
+ "dependencies": {
83
+ "cron-parser": "^4.9.0"
84
+ },
85
+ "devDependencies": {
86
+ "@types/node": "^22.10.0",
87
+ "tsx": "^4.19.0",
88
+ "typescript": "^5.7.0",
89
+ "vitest": "^2.1.0"
90
+ },
91
+ "scripts": {
92
+ "build": "tsc -p tsconfig.build.json && node scripts/copy-dashboard-assets.mjs",
93
+ "dev": "tsc -p tsconfig.build.json --watch",
94
+ "test": "vitest run",
95
+ "test:watch": "vitest",
96
+ "typecheck": "tsc --noEmit",
97
+ "loc-check": "node scripts/loc-ceiling.mjs",
98
+ "lint": "tsc --noEmit && vitest run"
99
+ }
100
+ }