skillscript-runtime 0.19.3 → 0.19.6

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.
@@ -26,13 +26,14 @@ Every skill follows the same shape:
26
26
 
27
27
  1. **Trigger fires** — cron, command, event, session-start, or programmatic invocation
28
28
  2. **Process** — pull data (MCP / memory / file), classify / compose via sub-LLM + iteration, build the deliverable
29
- 3. **Deliver** — via one or more of three channels
29
+ 3. **Deliver** — via one or more of four channels
30
30
 
31
- The three delivery channels are all first-class:
31
+ The four delivery channels are all first-class:
32
32
 
33
33
  | Channel | Op | When you'd use it |
34
34
  |---|---|---|
35
- | **Embedded prompt** | \`emit(text="...")\` | Skill output is delivered to the receiving agent via the \`# Output: agent: <name>\` lifecycle hook |
35
+ | **Body-text-as-output template** | prose between frontmatter + first target | Declarative output skill body IS the output, rendered with var substitution. The clean shape for fixed-form output. |
36
+ | **Embedded prompt** | \`emit(text="...")\` | Imperative output for variable-cardinality cases (\`foreach\` per-item emit), conditionally-different output shapes, or transcript / reasoning trace |
36
37
  | **File handoff** | \`file_write(path="...", content="...")\` | Skill writes a file at a known location for the agent to read |
37
38
  | **Data handoff** | \`$ data_write content="..." recipients=["agent"] -> R\` | Skill writes a record the target agent picks up via mailbox. Routes through the wired \`data_write\` connector (default: \`DataStoreMcpConnector\` bundled). |
38
39
 
@@ -57,16 +58,20 @@ The \`$\` prefix marks **state-affecting ops** (mutation OR external dispatch).
57
58
  # Vars: NAME=default-value, OTHER ← optional: declared variables
58
59
  # Triggers: cron: 0 9 * * * ← optional: autonomous-dispatch sources
59
60
 
61
+ \${SUMMARY} ← body-text-as-output template (optional)
62
+
60
63
  target_a: ← a named block of ops
61
64
  $ ticketing_search query="state:open" -> ISSUES
62
65
  $ llm prompt="Summarize: \${ISSUES}" -> SUMMARY
63
66
 
64
- target_b: target_a Make-style: target_b depends on target_a
65
- emit(text="\${SUMMARY}")
67
+ target_b: needs: target_a ← target_b depends on target_a
68
+ $set _ = "noop"
66
69
 
67
70
  default: target_b ← goal target the runtime walks toward
68
71
  \`\`\`
69
72
 
73
+ Text between the frontmatter and the first target is a **declarative output template**. The runtime interpolates \`\${VAR}\` refs against final vars and publishes the rendered string as the skill's canonical output. No \`emit()\` ceremony for fixed-shape output. See section "Body template" below.
74
+
70
75
  ## 4. Variable substitution
71
76
 
72
77
  Use \`\${VAR}\` (canonical) inside any kwarg value or emit body. Field access works: \`\${ISSUE.title}\`. Filter chains: \`\${VAR|trim|length}\`. Missing-value fallback: \`\${VAR|fallback:"-"}\`.
@@ -97,7 +102,36 @@ foreach M in \${MEMORIES}:
97
102
  emit(text="Processing \${M.id}: \${M.summary}")
98
103
  \`\`\`
99
104
 
100
- ## 8. How to see what's broken
105
+ ## 8. Body template when emit is overkill
106
+
107
+ For fixed-shape output, write the rendered sentence directly as the skill body. The compute block fills in the holes:
108
+
109
+ \`\`\`
110
+ # Skill: get-weather
111
+ # Vars: AREA="Brooklyn"
112
+
113
+ \${AREA}: \${TEMP}°\${UNIT} and \${DESC}.
114
+
115
+ fetch:
116
+ shell(command="curl -s https://wttr.in/\${AREA|url}?format=j1") -> RAW
117
+ $ json_parse \${RAW} -> W
118
+ $set TEMP = "\${W.current_condition.0.temp_F}"
119
+ $set UNIT = "F"
120
+ $set DESC = "\${W.current_condition.0.weatherDesc.0.value|trim}"
121
+
122
+ default: fetch
123
+ \`\`\`
124
+
125
+ **Template vs. emit — complementary channels.** Template owns canonical output (\`outputs.text\` / agent or template delivery payload / file content). \`emit()\` populates the transcript (trace records, dashboard \`/fires\` view, debugging). Skills with both: template = output, emit = transcript. The \`emit-with-template\` advisory lint confirms intent.
126
+
127
+ Keep \`emit()\` when:
128
+ - Variable-cardinality output: \`foreach M in \${MEMORIES}: emit(text="\${M.id}")\` — N lines determined at runtime
129
+ - Conditional output **shapes**: different branches produce different structural outputs (not just different values)
130
+ - Transcript / reasoning trace: emit feeds \`/fires\` even when template feeds output
131
+
132
+ Pin 4 disambiguation — a target is \`<name>:\` alone with an indented op-block following. Content after the colon (\`Summary: hot today\`) or a bare \`<word>:\` with no following op-block reads as template text. Lint \`template-looks-like-target\` flags the genuinely ambiguous shape.
133
+
134
+ ## 9. How to see what's broken
101
135
 
102
136
  - \`lint_skill({source})\` — diagnostics across tier-1 (errors), tier-2 (warnings), tier-3 (advisories)
103
137
  - \`compile_skill({source, inputs?})\` — render the compiled artifact + surface compile errors
@@ -267,7 +301,7 @@ execute_skill(name="extract-json-number", JSON_BLOB="\${RAW}", FIELD_PATH="total
267
301
  emit(text="Extracted: \${RESULT.outputs.text}")
268
302
  \`\`\`
269
303
 
270
- **v0.17.3 — Returns filter.** \`R.final_vars\` is filtered to the called skill's \`# Returns: X, Y, Z\` declaration. Skills without \`# Returns:\` declared export nothing from \`final_vars\` — the caller sees \`outputs\` + \`transcript\` + execution metadata, but no internal vars. Internal scratch (large JSON, intermediate computations, debug values) stays local to the child and never serializes into the caller's \`R\`. To expose a value for caller consumption, declare it in the called skill's \`# Returns:\` header.
304
+ **Returns filter.** \`R.final_vars\` is filtered to the called skill's \`# Returns: X, Y, Z\` declaration. Skills without \`# Returns:\` declared export nothing from \`final_vars\` — the caller sees \`outputs\` + \`transcript\` + execution metadata, but no internal vars. Internal scratch (large JSON, intermediate computations, debug values) stays local to the child and never serializes into the caller's \`R\`. To expose a value for caller consumption, declare it in the called skill's \`# Returns:\` header.
271
305
 
272
306
  \`name\` is the canonical kwarg, aligning with \`skill_read({name})\` / \`skill_write({name})\` / \`skill_status({name})\`. \`skill_name\` is accepted as a silent back-compat alias.
273
307
 
@@ -406,8 +440,8 @@ Skill files open with \`# Key: value\` headers. Order isn't significant.
406
440
  - \`# Type: procedural | data\` — \`procedural\` (default) for runtime-fired skills; \`data\` for compile-time-inlined fragments referenced by \`inline(skill="...")\`.
407
441
  - \`# Vars: NAME=default, OTHER\` — declared variables. \`NAME=default\` provides a default; bare \`NAME\` is required at invocation. Quoted defaults (\`NAME="hello world"\`) strip one matched layer of surrounding quotes at parse time — quoted-spaced values bind correctly; bare values bind unchanged.
408
442
  - \`# Returns: X, Y, Z\` — declared export surface for \`execute_skill\` composition. Names that propagate from this skill's \`final_vars\` into the caller's bound \`R\`. Internal scratch vars NOT listed here stay local — never serialized into the caller's result. Skills without \`# Returns:\` export nothing from \`final_vars\` (outputs + transcript + metadata still flow). Symmetric with \`# Vars:\` (input surface ↔ output surface).
409
- - \`# Triggers: cron: 0 9 * * *, session: start\` — autonomous-dispatch sources. Comma-separated entries split by source-keyword boundary; cron expressions with commas (\`30,45 9 * * 1-5\`) parse correctly.
410
- - \`# Output: text | agent: <name> | template: <name> | file: path | none\` — output routing. Five kinds, all substrate-neutral. **Two substrate-neutral lifecycle hooks**: \`agent: <name>\` routes via AgentConnector as augment-kind delivery; \`template: <name>\` routes as template-kind delivery (receiving agent executes the rendered playbook). Both default to **joined emissions string** (the \`emit(text=...)\` lines concatenated with newlines). \`text\` / \`file:\` default to the **last-bound variable value** (structured), falling back to the emissions array when no var was bound. If your skill emits multiple lines and a downstream consumer only sees the final tool output via \`outputs.text\`, that's the structured-default behavior use \`# Output: agent: <name>\` (or another text-coerced kind) to publish the joined emissions instead. **For substrate-specific delivery destinations** (Slack, WhatsApp, Discord, pagerduty, custom dashboards, etc.) — that's contract-between-the-skill-and-the-substrate territory, downstream of the language. Two paths: (1) \`$ <connector>.<tool> ...\` inside the skill body to dispatch through an adopter-wired MCP connector, or (2) deliver via \`agent: <name>\` to an agent whose AgentConnector decides how to surface the result.
443
+ - \`# Triggers: cron: 0 9 * * *, event: my-event\` — autonomous-dispatch sources. Two primitives: \`cron\` (time-based) and \`event\` (HTTP POST \`/event\` ingress, named registration). Comma-separated entries split by source-keyword boundary; cron expressions with commas (\`30,45 9 * * 1-5\`) parse correctly.
444
+ - \`# Output: text | agent: <name> | template: <name> | file: path | none\` — output routing. Five kinds, all substrate-neutral. **Two substrate-neutral lifecycle hooks**: \`agent: <name>\` routes via AgentConnector as augment-kind delivery; \`template: <name>\` routes as template-kind delivery (receiving agent executes the rendered playbook). **Output content source**: if the skill has a body-text-as-output template (prose between frontmatter and first target), the rendered template populates the canonical output payload. Otherwise: agent/template kinds default to joined emissions; text/file kinds default to the last-bound variable value, falling back to the emissions array. Body template + emit() are complementary template = canonical output, emit() = transcript. **For substrate-specific delivery destinations** (Slack, WhatsApp, Discord, pagerduty, custom dashboards, etc.) — that's contract-between-the-skill-and-the-substrate territory, downstream of the language. Two paths: (1) \`$ <connector>.<tool> ...\` inside the skill body to dispatch through an adopter-wired MCP connector, or (2) deliver via \`agent: <name>\` to an agent whose AgentConnector decides how to surface the result.
411
445
  - \`# OnError: <fallback-skill-name>\` — error-handler skill invoked when an op fails and no target-level \`else:\` catches.
412
446
  - \`# Autonomous: true | false\` — declarative authorship intent for unattended-execution skills (cron-fired, agent-fired, etc.). Silences \`unconfirmed-mutation\` lint warnings for the whole skill (since the user-confirmation pattern doesn't apply to autonomous skills); reserved as the canonical autonomous-skill category marker for future rules + scheduling defaults + discovery surfaces. Omitted = interactive (default).
413
447
 
@@ -431,11 +465,11 @@ Skill files open with \`# Key: value\` headers. Order isn't significant.
431
465
 
432
466
  \`\`\`
433
467
  # Triggers: cron: 30,45 9 * * 1-5
434
- # Triggers: session: start, session: end
435
- # Triggers: cron: 0 7 * * *, agent-event: drift-detected
468
+ # Triggers: event: ticket-created
469
+ # Triggers: cron: 0 7 * * *, event: drift-detected
436
470
  \`\`\`
437
471
 
438
- Trigger sources: \`cron\` (poll-based), \`session\` (\`start\` / \`end\` phases). Parse-only (firing not yet wired): \`event\`, \`agent-event\`, \`file-watch\`, \`sensor\`.
472
+ Trigger sources: two primitives — \`cron\` (poll-based) and \`event\` (HTTP \`/event\` ingress with named registration; an external service POSTs to drive the skill). Anything substrate-coupled session lifecycle, agent events, file-watch, sensors — is adapter responsibility: external code POSTs \`/event\` when relevant.
439
473
 
440
474
  ## Ambient variables (auto-populated by the runtime)
441
475
 
@@ -446,7 +480,7 @@ The runtime injects these refs — don't declare them in \`# Vars:\` / \`# Requi
446
480
  | \`$(NOW)\` | runtime clock | ISO-8601 timestamp at op-dispatch time |
447
481
  | \`$(USER)\` | invocation context | Identity passed via \`agentId\` / CLI user |
448
482
  | \`$(SESSION_CONTEXT)\` | runtime session | Free-form session snapshot for cross-skill carry |
449
- | \`$(TRIGGER_TYPE)\` | scheduler | \`cron\` / \`session\` / \`webhook\` / \`agent\` / \`cli\` / \`dashboard\` / \`inline\` |
483
+ | \`$(TRIGGER_TYPE)\` | scheduler | \`cron\` / \`event\` / \`webhook\` / \`agent\` / \`cli\` / \`dashboard\` / \`inline\` |
450
484
  | \`$(TRIGGER_PAYLOAD)\` | scheduler | JSON-serializable payload attached to the firing trigger |
451
485
  | \`$(ERROR_CONTEXT)\` | runtime error handler | Inside \`else:\` and \`# OnError:\` only; \`.kind\` / \`.message\` / \`.target\` accessible |
452
486
 
@@ -484,14 +518,16 @@ const EXAMPLES = `# Five canonical worked skills
484
518
  # Status: Approved
485
519
  # Vars: WHO=world
486
520
 
521
+ Hello, \${WHO}!
522
+ Welcome to Skillscript.
523
+
487
524
  greet:
488
- emit(text="Hello, \${WHO}!")
489
- emit(text="Welcome to Skillscript.")
525
+ $set _ = "noop"
490
526
 
491
527
  default: greet
492
528
  \`\`\`
493
529
 
494
- Demonstrates: required headers, variable defaults, \`emit(text="...")\` with \`\${VAR}\` substitution.
530
+ Demonstrates: required headers, variable defaults, body-text-as-output template with \`\${VAR}\` substitution. No \`emit()\` ceremony needed for fixed-shape output — the body prose IS the output. Compare with example #5 below, which uses \`emit()\` per-item for variable-cardinality output (\`foreach\`-based).
495
531
 
496
532
  ## 2. Cron-fired numeric threshold + count
497
533
 
@@ -710,7 +746,7 @@ The bound \`-> R\` carries the child's execution record into the host's scope:
710
746
  - \`\${R.final_vars.FIELD}\` — the child's declared exports (see \`# Returns:\` below)
711
747
  - \`\${R.errors}\`, \`\${R.target_order}\` — execution metadata
712
748
 
713
- **Declaring what gets exported (v0.17.3).** The child skill controls what's visible to the caller via its \`# Returns: X, Y, Z\` frontmatter header. Internal scratch (large JSON, intermediate computations) stays local; only declared names propagate into \`R.final_vars\`. Skills without \`# Returns:\` export nothing from \`final_vars\` — outputs + transcript + metadata still flow.
749
+ **Declaring what gets exported.** The child skill controls what's visible to the caller via its \`# Returns: X, Y, Z\` frontmatter header. Internal scratch (large JSON, intermediate computations) stays local; only declared names propagate into \`R.final_vars\`. Skills without \`# Returns:\` export nothing from \`final_vars\` — outputs + transcript + metadata still flow.
714
750
 
715
751
  \`\`\`
716
752
  # Skill: get-weather
@@ -828,7 +864,7 @@ Three tiers per ERD §3:
828
864
 
829
865
  ## Tier-2 (warning)
830
866
 
831
- - \`deprecated-question\` — bare \`?\` op (deprecated v1; compile-error in v1.x)
867
+ - \`deprecated-question\` — bare \`?\` op (deprecated; compile-error path)
832
868
  - \`deprecated-substitution-shape\` — \`$(VAR)\` substitution form compiles but warns; rewrite to \`\${VAR}\`.
833
869
  - \`unsafe-shell-ambiguous-subst\` — \`$(NAME)\` inside \`shell(command=..., unsafe=true)\` body that isn't a declared variable; collides with bash command-sub syntax
834
870
  - \`unsafe-shell-op\` — \`shell(command=..., unsafe=true)\` present; requires human review every time
@@ -844,7 +880,7 @@ Three tiers per ERD §3:
844
880
 
845
881
  - \`no-default-target\` — no \`default:\` declaration (relevant for data skills only; procedural skills hit tier-1)
846
882
  - \`duplicate-skill-name\` — name collides with an existing stored skill
847
- - \`plugin-collision\` — placeholder for v1.x plugin-loader name conflicts
883
+ - \`plugin-collision\` — placeholder for future plugin-loader name conflicts
848
884
  - \`deferred-skill-reference\` — composition ref (\`inline\` / \`$ execute_skill\` / \`# Templates:\`) targets a skill not currently in the SkillStore; resolution deferred to execute time. Confirms the forward-reference path is engaged; clears once the target is stored.
849
885
  - \`unparsed-json-field-access\` — op text contains \`$(VAR|json_parse).field\`; the \`|json_parse\` filter is no longer supported. Use \`$ json_parse $(VAR) -> P\` then \`$(P.field)\`.
850
886
  - \`object-iteration-advisory\` — \`foreach IT in \${VAR}\` iterates a bound variable whose origin is a \`$\` MCP tool output, without a \`.field\` accessor. MCP tools commonly wrap arrays in an envelope object (\`.items\`, \`.results\`, \`.issuesPage\`, \`.data\`, \`.records\`). Check the tool's response shape; rewrite as \`foreach IT in \${VAR.items}\` (or the correct field).
@@ -1 +1 @@
1
- {"version":3,"file":"help-content.js","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"help-content.js","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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"}
package/dist/lint.js CHANGED
@@ -133,14 +133,21 @@ const PARSE_ERROR = {
133
133
  const NO_TARGETS = {
134
134
  id: "no-targets",
135
135
  severity: "error",
136
- description: "Skill defines zero targets.",
137
- remediation: "Declare at least one target. A target is a name + `:` + indented op lines.",
136
+ description: "Skill defines zero targets AND no body-text-as-output template.",
137
+ remediation: "Either declare at least one target (a name + `:` + indented op lines), or author a body-text-as-output template (prose between frontmatter and where targets would go). A template-only skill is valid — the template IS the work; the runtime renders it against `# Vars:` inputs.",
138
138
  check: (ctx) => {
139
- if (ctx.parsed.targets.size === 0 && ctx.parsed.parseErrors.length === 0) {
139
+ // A skill needs at least one of: (1) a target with ops, or (2) a
140
+ // body-text-as-output template. Per c7ddfc50 design intent ("strip
141
+ // the compute block → still a valid skill that emits the template"),
142
+ // a template-only skill is the simplest authorability win — the
143
+ // template IS the work. Closes Perry's `9d0a5e7d` dogfood finding.
144
+ const hasTargets = ctx.parsed.targets.size > 0;
145
+ const hasTemplate = ctx.parsed.outputTemplate !== null;
146
+ if (!hasTargets && !hasTemplate && ctx.parsed.parseErrors.length === 0) {
140
147
  return [{
141
148
  rule: "no-targets",
142
149
  severity: "error",
143
- message: "Skill defines no targets. A skill needs at least one target with ops.",
150
+ message: "Skill defines no targets and no body-text-as-output template. A skill needs either at least one target with ops, or a body template (prose between frontmatter and where targets would go).",
144
151
  }];
145
152
  }
146
153
  return [];
@@ -1877,16 +1884,23 @@ const UNUSED_AUGMENTING_HEADER = {
1877
1884
  },
1878
1885
  };
1879
1886
  // v0.8.0 — tier-2 lint warns per the delivery-model lockdown (`bb34de4e`).
1887
+ // v0.19.4 — a body-text-as-output template also populates the delivery
1888
+ // payload (per complementary-channels semantic in c7ddfc50). When a
1889
+ // template is authored, the lint does NOT fire — the template IS the
1890
+ // content the lifecycle hook delivers.
1880
1891
  const OUTPUT_AGENT_TARGET_NO_EMIT = {
1881
1892
  id: "output-agent-target-no-emit",
1882
1893
  severity: "warning",
1883
- description: "`# Output: agent: <name>` or `# Output: template: <name>` declared but skill has no `emit()` ops; delivery fires with empty content.",
1884
- remediation: "Add at least one `emit(text=\"...\")` op so the skill produces content for the lifecycle hook delivery, or remove the `# Output:` header if the skill produces no agent-targeted output.",
1894
+ description: "`# Output: agent: <name>` or `# Output: template: <name>` declared but skill has no `emit()` ops AND no body-text-as-output template; delivery fires with empty content.",
1895
+ remediation: "Author a body-text-as-output template (prose between the frontmatter and the first target), OR add at least one `emit(text=\"...\")` op so the skill produces content for the lifecycle hook delivery. If the skill produces no agent-targeted output, remove the `# Output:` header.",
1885
1896
  check: (ctx) => {
1886
1897
  const findings = [];
1887
1898
  const agentBoundOutputs = ctx.parsed.outputs.filter((o) => (o.kind === "agent" || o.kind === "template") && o.target !== undefined);
1888
1899
  if (agentBoundOutputs.length === 0)
1889
1900
  return findings;
1901
+ // v0.19.4 — body template populates delivery payload too.
1902
+ if (ctx.parsed.outputTemplate !== null)
1903
+ return findings;
1890
1904
  let hasEmit = false;
1891
1905
  for (const [, target] of ctx.parsed.targets) {
1892
1906
  walkOps(target.ops, (op) => { if (op.kind === "emit")
@@ -1900,7 +1914,7 @@ const OUTPUT_AGENT_TARGET_NO_EMIT = {
1900
1914
  findings.push({
1901
1915
  rule: "output-agent-target-no-emit",
1902
1916
  severity: "warning",
1903
- message: `\`# Output: ${decl.kind}: ${decl.target}\` declared but skill has no \`emit()\` ops; delivery fires with empty content.`,
1917
+ message: `\`# Output: ${decl.kind}: ${decl.target}\` declared but skill has no \`emit()\` ops AND no body-text-as-output template; delivery fires with empty content.`,
1904
1918
  });
1905
1919
  }
1906
1920
  return findings;
@@ -2157,6 +2171,167 @@ const UNEXPORTED_FINAL_VAR_ACCESS = {
2157
2171
  return findings;
2158
2172
  },
2159
2173
  };
2174
+ // ─── v0.19.4 body-text-as-output template rules ──────────────────────────
2175
+ //
2176
+ // Four rules covering the new authoring surface. The template region is the
2177
+ // text between the frontmatter and the first target; runtime renders it as
2178
+ // canonical output. Per Perry+CC sign-off in c7ddfc50 / 920078c8 / ad0b868e.
2179
+ /**
2180
+ * v0.19.4 — every `${VAR}` / `$(VAR)` in the template must resolve to a
2181
+ * declared input (`# Vars:` / `# Requires:`), a `$set` or `->` binding
2182
+ * anywhere in the skill, an ambient ref, or a dotted access on one of the
2183
+ * above. Mechanical coupling — no semantic guessing. Same machinery as
2184
+ * `undeclared-var` (tier-1), applied to the template instead of op bodies.
2185
+ * Tier-1: a template referencing unbound vars renders empty at runtime,
2186
+ * silently producing wrong-looking output.
2187
+ */
2188
+ const UNSET_TEMPLATE_VAR = {
2189
+ id: "unset-template-var",
2190
+ severity: "error",
2191
+ description: "A `${VAR}` reference in the body-text-as-output template doesn't resolve to a declared `# Vars:` / `# Requires:` input, an ambient ref, or a `$set` / `->` binding anywhere in the skill body. Substitution will render empty — the published output silently drops the value.",
2192
+ remediation: "Add VAR to `# Vars:`, bind it via `$set VAR = ...` or `<op> -> VAR` in a target's compute block, or check the spelling against the declared / bound variable list.",
2193
+ check: (ctx) => {
2194
+ if (ctx.parsed.outputTemplate === null)
2195
+ return [];
2196
+ const declared = new Set(AMBIENT_VARS);
2197
+ for (const v of ctx.parsed.vars)
2198
+ declared.add(v.name);
2199
+ for (const r of ctx.parsed.requires)
2200
+ declared.add(r.target);
2201
+ for (const target of ctx.parsed.targets.values()) {
2202
+ const collect = (op) => {
2203
+ if (op.setName !== undefined)
2204
+ declared.add(op.setName);
2205
+ if (op.outputVar !== undefined)
2206
+ declared.add(op.outputVar);
2207
+ if (op.foreachIter !== undefined)
2208
+ declared.add(op.foreachIter);
2209
+ };
2210
+ walkOps(target.ops, collect);
2211
+ if (target.elseBlock !== undefined)
2212
+ walkOps(target.elseBlock, collect);
2213
+ }
2214
+ const findings = [];
2215
+ const reported = new Set();
2216
+ // Match both legacy `$(NAME)` and canonical `${NAME}` — first identifier
2217
+ // segment only; dotted access (`R.field`) checks against the base var.
2218
+ const re = /\$[({]([A-Za-z_]\w*)/g;
2219
+ let m;
2220
+ while ((m = re.exec(ctx.parsed.outputTemplate)) !== null) {
2221
+ const ref = m[1];
2222
+ if (declared.has(ref))
2223
+ continue;
2224
+ if (reported.has(ref))
2225
+ continue;
2226
+ reported.add(ref);
2227
+ findings.push({
2228
+ rule: "unset-template-var",
2229
+ severity: "error",
2230
+ message: `Output template references undeclared variable \`\${${ref}}\` — substitution will render empty at runtime. Declare it in \`# Vars:\` or bind it via \`$set\` / \`-> ${ref}\` in a target.`,
2231
+ extras: { var_name: ref },
2232
+ });
2233
+ }
2234
+ return findings;
2235
+ },
2236
+ };
2237
+ /**
2238
+ * v0.19.4 — Pin 4 tier-2 advisory. Parser captures lines in the template
2239
+ * region that match bare `<word>:` alone (no content after colon, no
2240
+ * following op-block). These read ambiguously: an author might have meant
2241
+ * a target header that they forgot to indent under, or template prose
2242
+ * (section header). Flag for disambiguation.
2243
+ */
2244
+ const TEMPLATE_LOOKS_LIKE_TARGET = {
2245
+ id: "template-looks-like-target",
2246
+ severity: "warning",
2247
+ description: "A line in the body-text-as-output template region is shaped like a target header (bare `<word>:` alone on its line, no content after colon, no following indented op-block). Parser treats it as template prose under the Pin 4 disambiguation rule, but the shape is ambiguous to a reader.",
2248
+ remediation: "If you meant a target, add an indented op-block on the next line. If you meant template prose (e.g. a section header), add content after the colon (`Summary: today's outlook`) or rephrase to disambiguate.",
2249
+ check: (ctx) => {
2250
+ const findings = [];
2251
+ for (const lineNum of ctx.parsed.templateAmbiguousLines) {
2252
+ findings.push({
2253
+ rule: "template-looks-like-target",
2254
+ severity: "warning",
2255
+ message: `Line ${lineNum}: a bare \`<word>:\` shape in the output template could read as a target header. Parser treats it as template prose (no following op-block), but the ambiguity is worth disambiguating: add content after the colon, rephrase, or indent an op-block to make it a real target.`,
2256
+ extras: { line: lineNum },
2257
+ });
2258
+ }
2259
+ return findings;
2260
+ },
2261
+ };
2262
+ /**
2263
+ * v0.19.4 — tier-3 advisory. Non-blank, non-`#` lines in the body-template
2264
+ * region detected, AND no `# Output:` declaration that consumes text, AND
2265
+ * no `${...}` interpolations in the template. Three-condition guard so
2266
+ * legitimate interpolating templates never fire. Catches the "I wrote
2267
+ * prose; it became template by accident" case.
2268
+ */
2269
+ const BODY_TEMPLATE_DETECTED = {
2270
+ id: "body-template-detected",
2271
+ severity: "info",
2272
+ description: "Non-blank, non-`#` lines between the frontmatter and the first target were captured as a body-text-as-output template, BUT the template has no `${...}` interpolations AND the skill declares no text-consuming `# Output:` kind. The lines may have been intended as informal documentation.",
2273
+ remediation: "If the lines are intentional output template, add at least one `${VAR}` interpolation OR an explicit `# Output: text` / `# Output: agent: <name>` declaration. If they were intended as documentation, prefix with `#` so the parser treats them as comments.",
2274
+ check: (ctx) => {
2275
+ if (ctx.parsed.outputTemplate === null)
2276
+ return [];
2277
+ if (/\$[({][A-Za-z_]/.test(ctx.parsed.outputTemplate))
2278
+ return [];
2279
+ const hasTextConsumingDecl = ctx.parsed.outputs.some((d) => d.kind === "text" || d.kind === "agent" || d.kind === "template" || d.kind === "file");
2280
+ if (hasTextConsumingDecl)
2281
+ return [];
2282
+ return [{
2283
+ rule: "body-template-detected",
2284
+ severity: "info",
2285
+ message: "Body-text-as-output template captured, but it has no `${...}` interpolations and the skill declares no text-consuming `# Output:` kind. If the lines were intended as documentation, prefix them with `#` to mark as comments; otherwise add an interpolation or a `# Output:` declaration to confirm intent.",
2286
+ }];
2287
+ },
2288
+ };
2289
+ /**
2290
+ * v0.19.4 — tier-3 advisory. Skill has both a body template AND at least
2291
+ * one `emit(text=...)` call AND `# Output:` is `text` or absent. Warns the
2292
+ * author about the complementary-channels semantic (`c7ddfc50`): template
2293
+ * owns canonical output, emit() feeds transcript only. Silently changing
2294
+ * the channel semantics when an emit-based skill adds a template is the
2295
+ * surprise case Perry called out in `ad0b868e`.
2296
+ */
2297
+ const EMIT_WITH_TEMPLATE = {
2298
+ id: "emit-with-template",
2299
+ severity: "info",
2300
+ description: "Skill defines both a body-text-as-output template AND `emit(text=...)` calls. Under v0.19.4 complementary-channels semantics, the template owns canonical output (`outputs.text` / agent delivery payload); `emit()` entries feed the transcript only.",
2301
+ remediation: "Confirm intent: if you want emit() to populate canonical output, remove the body template. If you want the template to populate canonical output and emit() to keep populating the transcript (debug log, reasoning trace), no change needed — this is the intended semantic.",
2302
+ check: (ctx) => {
2303
+ if (ctx.parsed.outputTemplate === null)
2304
+ return [];
2305
+ // Filter to text-consuming kinds that route through canonical output.
2306
+ // `none` skips the canonical output channel; agent/template/file each
2307
+ // consume the template payload, so the emit-demotion is silent there.
2308
+ const outputKinds = ctx.parsed.outputs.length === 0 ? ["text"] : ctx.parsed.outputs.map((d) => d.kind);
2309
+ const hasTextLike = outputKinds.some((k) => k === "text" || k === "agent" || k === "template" || k === "file");
2310
+ if (!hasTextLike)
2311
+ return [];
2312
+ let hasEmit = false;
2313
+ for (const target of ctx.parsed.targets.values()) {
2314
+ walkOps(target.ops, (op) => {
2315
+ if (op.kind === "emit")
2316
+ hasEmit = true;
2317
+ });
2318
+ if (target.elseBlock !== undefined)
2319
+ walkOps(target.elseBlock, (op) => {
2320
+ if (op.kind === "emit")
2321
+ hasEmit = true;
2322
+ });
2323
+ if (hasEmit)
2324
+ break;
2325
+ }
2326
+ if (!hasEmit)
2327
+ return [];
2328
+ return [{
2329
+ rule: "emit-with-template",
2330
+ severity: "info",
2331
+ message: "Skill has both a body-text-as-output template and `emit(text=...)` calls. Under v0.19.4 complementary-channels semantics, the template owns canonical output (`outputs.text` / agent delivery payload); `emit()` entries feed the transcript only. If you want emit() to be the canonical output, remove the template; otherwise this is the intended pattern.",
2332
+ }];
2333
+ },
2334
+ };
2160
2335
  const TRANSCRIPT_FOOTGUN = {
2161
2336
  id: "transcript-footgun",
2162
2337
  severity: "warning",
@@ -2870,6 +3045,7 @@ const RULES = [
2870
3045
  UNDECLARED_VAR,
2871
3046
  UNKNOWN_RETURNS_REF,
2872
3047
  UNKNOWN_FILTER,
3048
+ UNSET_TEMPLATE_VAR,
2873
3049
  MALFORMED_OP_GRAMMAR,
2874
3050
  INVALID_CONDITIONAL_SYNTAX,
2875
3051
  SINGLE_EQUALS,
@@ -2911,6 +3087,7 @@ const RULES = [
2911
3087
  DEPRECATED_ADDRESSED_TO,
2912
3088
  LEGACY_FRONTMATTER_HEADER,
2913
3089
  TRANSCRIPT_FOOTGUN,
3090
+ TEMPLATE_LOOKS_LIKE_TARGET,
2914
3091
  UNEXPORTED_FINAL_VAR_ACCESS,
2915
3092
  SET_JSON_LITERAL_ADVISORY,
2916
3093
  SKILL_NAME_COLLISION,
@@ -2927,6 +3104,8 @@ const RULES = [
2927
3104
  UNPARSED_JSON_FIELD_ACCESS,
2928
3105
  OBJECT_ITERATION_ADVISORY,
2929
3106
  ADDRESS_ROUTED_WAKE_INFO,
3107
+ BODY_TEMPLATE_DETECTED,
3108
+ EMIT_WITH_TEMPLATE,
2930
3109
  ];
2931
3110
  /** Read-only view of the rule registry — for tooling that introspects v1 rules. */
2932
3111
  export function listRules() {