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.
@@ -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: "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.",
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() {