skillscript-runtime 0.19.3 → 0.19.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.
- package/README.md +8 -4
- package/dist/cli.js +13 -2
- package/dist/cli.js.map +1 -1
- package/dist/compile.js +15 -0
- 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 +175 -3
- 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 +6 -4
- 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 +6 -4
- package/scaffold/skills/skill-store-roundtrip.skill.md +4 -3
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
|
@@ -1877,16 +1877,23 @@ const UNUSED_AUGMENTING_HEADER = {
|
|
|
1877
1877
|
},
|
|
1878
1878
|
};
|
|
1879
1879
|
// v0.8.0 — tier-2 lint warns per the delivery-model lockdown (`bb34de4e`).
|
|
1880
|
+
// v0.19.4 — a body-text-as-output template also populates the delivery
|
|
1881
|
+
// payload (per complementary-channels semantic in c7ddfc50). When a
|
|
1882
|
+
// template is authored, the lint does NOT fire — the template IS the
|
|
1883
|
+
// content the lifecycle hook delivers.
|
|
1880
1884
|
const OUTPUT_AGENT_TARGET_NO_EMIT = {
|
|
1881
1885
|
id: "output-agent-target-no-emit",
|
|
1882
1886
|
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: "
|
|
1887
|
+
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.",
|
|
1888
|
+
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
1889
|
check: (ctx) => {
|
|
1886
1890
|
const findings = [];
|
|
1887
1891
|
const agentBoundOutputs = ctx.parsed.outputs.filter((o) => (o.kind === "agent" || o.kind === "template") && o.target !== undefined);
|
|
1888
1892
|
if (agentBoundOutputs.length === 0)
|
|
1889
1893
|
return findings;
|
|
1894
|
+
// v0.19.4 — body template populates delivery payload too.
|
|
1895
|
+
if (ctx.parsed.outputTemplate !== null)
|
|
1896
|
+
return findings;
|
|
1890
1897
|
let hasEmit = false;
|
|
1891
1898
|
for (const [, target] of ctx.parsed.targets) {
|
|
1892
1899
|
walkOps(target.ops, (op) => { if (op.kind === "emit")
|
|
@@ -1900,7 +1907,7 @@ const OUTPUT_AGENT_TARGET_NO_EMIT = {
|
|
|
1900
1907
|
findings.push({
|
|
1901
1908
|
rule: "output-agent-target-no-emit",
|
|
1902
1909
|
severity: "warning",
|
|
1903
|
-
message: `\`# Output: ${decl.kind}: ${decl.target}\` declared but skill has no \`emit()\` ops; delivery fires with empty content.`,
|
|
1910
|
+
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
1911
|
});
|
|
1905
1912
|
}
|
|
1906
1913
|
return findings;
|
|
@@ -2157,6 +2164,167 @@ const UNEXPORTED_FINAL_VAR_ACCESS = {
|
|
|
2157
2164
|
return findings;
|
|
2158
2165
|
},
|
|
2159
2166
|
};
|
|
2167
|
+
// ─── v0.19.4 body-text-as-output template rules ──────────────────────────
|
|
2168
|
+
//
|
|
2169
|
+
// Four rules covering the new authoring surface. The template region is the
|
|
2170
|
+
// text between the frontmatter and the first target; runtime renders it as
|
|
2171
|
+
// canonical output. Per Perry+CC sign-off in c7ddfc50 / 920078c8 / ad0b868e.
|
|
2172
|
+
/**
|
|
2173
|
+
* v0.19.4 — every `${VAR}` / `$(VAR)` in the template must resolve to a
|
|
2174
|
+
* declared input (`# Vars:` / `# Requires:`), a `$set` or `->` binding
|
|
2175
|
+
* anywhere in the skill, an ambient ref, or a dotted access on one of the
|
|
2176
|
+
* above. Mechanical coupling — no semantic guessing. Same machinery as
|
|
2177
|
+
* `undeclared-var` (tier-1), applied to the template instead of op bodies.
|
|
2178
|
+
* Tier-1: a template referencing unbound vars renders empty at runtime,
|
|
2179
|
+
* silently producing wrong-looking output.
|
|
2180
|
+
*/
|
|
2181
|
+
const UNSET_TEMPLATE_VAR = {
|
|
2182
|
+
id: "unset-template-var",
|
|
2183
|
+
severity: "error",
|
|
2184
|
+
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.",
|
|
2185
|
+
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.",
|
|
2186
|
+
check: (ctx) => {
|
|
2187
|
+
if (ctx.parsed.outputTemplate === null)
|
|
2188
|
+
return [];
|
|
2189
|
+
const declared = new Set(AMBIENT_VARS);
|
|
2190
|
+
for (const v of ctx.parsed.vars)
|
|
2191
|
+
declared.add(v.name);
|
|
2192
|
+
for (const r of ctx.parsed.requires)
|
|
2193
|
+
declared.add(r.target);
|
|
2194
|
+
for (const target of ctx.parsed.targets.values()) {
|
|
2195
|
+
const collect = (op) => {
|
|
2196
|
+
if (op.setName !== undefined)
|
|
2197
|
+
declared.add(op.setName);
|
|
2198
|
+
if (op.outputVar !== undefined)
|
|
2199
|
+
declared.add(op.outputVar);
|
|
2200
|
+
if (op.foreachIter !== undefined)
|
|
2201
|
+
declared.add(op.foreachIter);
|
|
2202
|
+
};
|
|
2203
|
+
walkOps(target.ops, collect);
|
|
2204
|
+
if (target.elseBlock !== undefined)
|
|
2205
|
+
walkOps(target.elseBlock, collect);
|
|
2206
|
+
}
|
|
2207
|
+
const findings = [];
|
|
2208
|
+
const reported = new Set();
|
|
2209
|
+
// Match both legacy `$(NAME)` and canonical `${NAME}` — first identifier
|
|
2210
|
+
// segment only; dotted access (`R.field`) checks against the base var.
|
|
2211
|
+
const re = /\$[({]([A-Za-z_]\w*)/g;
|
|
2212
|
+
let m;
|
|
2213
|
+
while ((m = re.exec(ctx.parsed.outputTemplate)) !== null) {
|
|
2214
|
+
const ref = m[1];
|
|
2215
|
+
if (declared.has(ref))
|
|
2216
|
+
continue;
|
|
2217
|
+
if (reported.has(ref))
|
|
2218
|
+
continue;
|
|
2219
|
+
reported.add(ref);
|
|
2220
|
+
findings.push({
|
|
2221
|
+
rule: "unset-template-var",
|
|
2222
|
+
severity: "error",
|
|
2223
|
+
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.`,
|
|
2224
|
+
extras: { var_name: ref },
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
return findings;
|
|
2228
|
+
},
|
|
2229
|
+
};
|
|
2230
|
+
/**
|
|
2231
|
+
* v0.19.4 — Pin 4 tier-2 advisory. Parser captures lines in the template
|
|
2232
|
+
* region that match bare `<word>:` alone (no content after colon, no
|
|
2233
|
+
* following op-block). These read ambiguously: an author might have meant
|
|
2234
|
+
* a target header that they forgot to indent under, or template prose
|
|
2235
|
+
* (section header). Flag for disambiguation.
|
|
2236
|
+
*/
|
|
2237
|
+
const TEMPLATE_LOOKS_LIKE_TARGET = {
|
|
2238
|
+
id: "template-looks-like-target",
|
|
2239
|
+
severity: "warning",
|
|
2240
|
+
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.",
|
|
2241
|
+
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.",
|
|
2242
|
+
check: (ctx) => {
|
|
2243
|
+
const findings = [];
|
|
2244
|
+
for (const lineNum of ctx.parsed.templateAmbiguousLines) {
|
|
2245
|
+
findings.push({
|
|
2246
|
+
rule: "template-looks-like-target",
|
|
2247
|
+
severity: "warning",
|
|
2248
|
+
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.`,
|
|
2249
|
+
extras: { line: lineNum },
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
return findings;
|
|
2253
|
+
},
|
|
2254
|
+
};
|
|
2255
|
+
/**
|
|
2256
|
+
* v0.19.4 — tier-3 advisory. Non-blank, non-`#` lines in the body-template
|
|
2257
|
+
* region detected, AND no `# Output:` declaration that consumes text, AND
|
|
2258
|
+
* no `${...}` interpolations in the template. Three-condition guard so
|
|
2259
|
+
* legitimate interpolating templates never fire. Catches the "I wrote
|
|
2260
|
+
* prose; it became template by accident" case.
|
|
2261
|
+
*/
|
|
2262
|
+
const BODY_TEMPLATE_DETECTED = {
|
|
2263
|
+
id: "body-template-detected",
|
|
2264
|
+
severity: "info",
|
|
2265
|
+
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.",
|
|
2266
|
+
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.",
|
|
2267
|
+
check: (ctx) => {
|
|
2268
|
+
if (ctx.parsed.outputTemplate === null)
|
|
2269
|
+
return [];
|
|
2270
|
+
if (/\$[({][A-Za-z_]/.test(ctx.parsed.outputTemplate))
|
|
2271
|
+
return [];
|
|
2272
|
+
const hasTextConsumingDecl = ctx.parsed.outputs.some((d) => d.kind === "text" || d.kind === "agent" || d.kind === "template" || d.kind === "file");
|
|
2273
|
+
if (hasTextConsumingDecl)
|
|
2274
|
+
return [];
|
|
2275
|
+
return [{
|
|
2276
|
+
rule: "body-template-detected",
|
|
2277
|
+
severity: "info",
|
|
2278
|
+
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.",
|
|
2279
|
+
}];
|
|
2280
|
+
},
|
|
2281
|
+
};
|
|
2282
|
+
/**
|
|
2283
|
+
* v0.19.4 — tier-3 advisory. Skill has both a body template AND at least
|
|
2284
|
+
* one `emit(text=...)` call AND `# Output:` is `text` or absent. Warns the
|
|
2285
|
+
* author about the complementary-channels semantic (`c7ddfc50`): template
|
|
2286
|
+
* owns canonical output, emit() feeds transcript only. Silently changing
|
|
2287
|
+
* the channel semantics when an emit-based skill adds a template is the
|
|
2288
|
+
* surprise case Perry called out in `ad0b868e`.
|
|
2289
|
+
*/
|
|
2290
|
+
const EMIT_WITH_TEMPLATE = {
|
|
2291
|
+
id: "emit-with-template",
|
|
2292
|
+
severity: "info",
|
|
2293
|
+
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.",
|
|
2294
|
+
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.",
|
|
2295
|
+
check: (ctx) => {
|
|
2296
|
+
if (ctx.parsed.outputTemplate === null)
|
|
2297
|
+
return [];
|
|
2298
|
+
// Filter to text-consuming kinds that route through canonical output.
|
|
2299
|
+
// `none` skips the canonical output channel; agent/template/file each
|
|
2300
|
+
// consume the template payload, so the emit-demotion is silent there.
|
|
2301
|
+
const outputKinds = ctx.parsed.outputs.length === 0 ? ["text"] : ctx.parsed.outputs.map((d) => d.kind);
|
|
2302
|
+
const hasTextLike = outputKinds.some((k) => k === "text" || k === "agent" || k === "template" || k === "file");
|
|
2303
|
+
if (!hasTextLike)
|
|
2304
|
+
return [];
|
|
2305
|
+
let hasEmit = false;
|
|
2306
|
+
for (const target of ctx.parsed.targets.values()) {
|
|
2307
|
+
walkOps(target.ops, (op) => {
|
|
2308
|
+
if (op.kind === "emit")
|
|
2309
|
+
hasEmit = true;
|
|
2310
|
+
});
|
|
2311
|
+
if (target.elseBlock !== undefined)
|
|
2312
|
+
walkOps(target.elseBlock, (op) => {
|
|
2313
|
+
if (op.kind === "emit")
|
|
2314
|
+
hasEmit = true;
|
|
2315
|
+
});
|
|
2316
|
+
if (hasEmit)
|
|
2317
|
+
break;
|
|
2318
|
+
}
|
|
2319
|
+
if (!hasEmit)
|
|
2320
|
+
return [];
|
|
2321
|
+
return [{
|
|
2322
|
+
rule: "emit-with-template",
|
|
2323
|
+
severity: "info",
|
|
2324
|
+
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.",
|
|
2325
|
+
}];
|
|
2326
|
+
},
|
|
2327
|
+
};
|
|
2160
2328
|
const TRANSCRIPT_FOOTGUN = {
|
|
2161
2329
|
id: "transcript-footgun",
|
|
2162
2330
|
severity: "warning",
|
|
@@ -2870,6 +3038,7 @@ const RULES = [
|
|
|
2870
3038
|
UNDECLARED_VAR,
|
|
2871
3039
|
UNKNOWN_RETURNS_REF,
|
|
2872
3040
|
UNKNOWN_FILTER,
|
|
3041
|
+
UNSET_TEMPLATE_VAR,
|
|
2873
3042
|
MALFORMED_OP_GRAMMAR,
|
|
2874
3043
|
INVALID_CONDITIONAL_SYNTAX,
|
|
2875
3044
|
SINGLE_EQUALS,
|
|
@@ -2911,6 +3080,7 @@ const RULES = [
|
|
|
2911
3080
|
DEPRECATED_ADDRESSED_TO,
|
|
2912
3081
|
LEGACY_FRONTMATTER_HEADER,
|
|
2913
3082
|
TRANSCRIPT_FOOTGUN,
|
|
3083
|
+
TEMPLATE_LOOKS_LIKE_TARGET,
|
|
2914
3084
|
UNEXPORTED_FINAL_VAR_ACCESS,
|
|
2915
3085
|
SET_JSON_LITERAL_ADVISORY,
|
|
2916
3086
|
SKILL_NAME_COLLISION,
|
|
@@ -2927,6 +3097,8 @@ const RULES = [
|
|
|
2927
3097
|
UNPARSED_JSON_FIELD_ACCESS,
|
|
2928
3098
|
OBJECT_ITERATION_ADVISORY,
|
|
2929
3099
|
ADDRESS_ROUTED_WAKE_INFO,
|
|
3100
|
+
BODY_TEMPLATE_DETECTED,
|
|
3101
|
+
EMIT_WITH_TEMPLATE,
|
|
2930
3102
|
];
|
|
2931
3103
|
/** Read-only view of the rule registry — for tooling that introspects v1 rules. */
|
|
2932
3104
|
export function listRules() {
|