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.
- package/README.md +8 -4
- package/dist/cli.js +13 -2
- package/dist/cli.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +29 -5
- package/dist/compile.js.map +1 -1
- package/dist/help-content.d.ts.map +1 -1
- package/dist/help-content.js +55 -19
- package/dist/help-content.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +186 -7
- package/dist/lint.js.map +1 -1
- package/dist/parser.d.ts +23 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +94 -1
- package/dist/parser.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +15 -1
- package/dist/runtime.js.map +1 -1
- package/docs/adopter-playbook.md +77 -2
- package/docs/connector-contract-reference.md +4 -4
- package/examples/skillscripts/data-store-roundtrip.skill.md +4 -3
- package/examples/skillscripts/doc-qa-with-citations.skill.md +3 -2
- package/examples/skillscripts/hello-world.skill.md +4 -7
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/examples/skillscripts/morning-brief.skill.md +3 -2
- package/examples/skillscripts/skill-store-roundtrip.skill.md +3 -2
- package/package.json +1 -1
- package/scaffold/skills/data-store-roundtrip.skill.md +4 -3
- package/scaffold/skills/hello-world.skill.md +4 -7
- package/scaffold/skills/skill-store-roundtrip.skill.md +4 -3
package/dist/help-content.js
CHANGED
|
@@ -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
|
|
29
|
+
3. **Deliver** — via one or more of four channels
|
|
30
30
|
|
|
31
|
-
The
|
|
31
|
+
The four delivery channels are all first-class:
|
|
32
32
|
|
|
33
33
|
| Channel | Op | When you'd use it |
|
|
34
34
|
|---|---|---|
|
|
35
|
-
| **
|
|
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
|
|
65
|
-
|
|
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.
|
|
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
|
-
**
|
|
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 * * *,
|
|
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).
|
|
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:
|
|
435
|
-
# Triggers: cron: 0 7 * * *,
|
|
468
|
+
# Triggers: event: ticket-created
|
|
469
|
+
# Triggers: cron: 0 7 * * *, event: drift-detected
|
|
436
470
|
\`\`\`
|
|
437
471
|
|
|
438
|
-
Trigger sources: \`cron\` (poll-based)
|
|
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\` / \`
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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).
|
package/dist/help-content.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help-content.js","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,kEAAkE;AAClE,qEAAqE;AACrE,2BAA2B;AAC3B,EAAE;AACF,kBAAkB;AAClB,0EAA0E;AAC1E,+DAA+D;AAC/D,uCAAuC;AACvC,kFAAkF;AAClF,kFAAkF;AAClF,uDAAuD;AACvD,EAAE;AACF,uEAAuE;AACvE,yDAAyD;AAIzD,MAAM,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"help-content.js","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"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"}
|
package/dist/lint.d.ts.map
CHANGED
|
@@ -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: "
|
|
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
|
-
|
|
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: "
|
|
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() {
|