skillscript-runtime 0.2.10 → 0.2.12

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.
@@ -264,6 +264,44 @@ Skill files open with \`# Key: value\` headers. Order isn't significant.
264
264
  \`\`\`
265
265
 
266
266
  Trigger sources today: \`cron\` (poll-based), \`session\` (\`start\` / \`end\` phases). Parse-only in v0.2: \`event\`, \`agent-event\`, \`file-watch\`, \`sensor\` (firing lands in v1.0).
267
+
268
+ ## Ambient variables (auto-populated by the runtime)
269
+
270
+ The runtime injects these refs — don't declare them in \`# Vars:\` / \`# Requires:\`.
271
+
272
+ | Ref | Source | Notes |
273
+ |---|---|---|
274
+ | \`$(NOW)\` | runtime clock | ISO-8601 timestamp at op-dispatch time |
275
+ | \`$(USER)\` | invocation context | Identity passed via \`agentId\` / CLI user |
276
+ | \`$(SESSION_CONTEXT)\` | runtime session | Free-form session snapshot for cross-skill carry |
277
+ | \`$(TRIGGER_TYPE)\` | scheduler | \`cron\` / \`session\` / \`manual\` / \`agent-event\` |
278
+ | \`$(TRIGGER_PAYLOAD)\` | scheduler | JSON-serializable payload attached to the firing trigger |
279
+ | \`$(ERROR_CONTEXT)\` | runtime error handler | Inside \`else:\` and \`# OnError:\` only; \`.kind\` / \`.message\` / \`.target\` accessible |
280
+
281
+ \`EVENT.*\` auto-populates on cron-fired skills (v0.2.7 scheduler):
282
+
283
+ | Ref | Value |
284
+ |---|---|
285
+ | \`$(EVENT.fired_at)\` | epoch milliseconds |
286
+ | \`$(EVENT.fired_at_unix)\` | epoch seconds |
287
+ | \`$(EVENT.fired_at_plus_1h_unix)\` | \`fired_at_unix + 3600\` |
288
+ | \`$(EVENT.fired_at_plus_1d_unix)\` | \`fired_at_unix + 86_400\` |
289
+ | \`$(EVENT.fired_at_plus_7d_unix)\` | \`fired_at_unix + 604_800\` |
290
+
291
+ (v0.2.12 Bug 24 — \`EVENT.*\` was undocumented before this release.)
292
+
293
+ ## Variable reference forms
294
+
295
+ \`\`\`
296
+ $(VAR) bare ref (any declared/output-bound/ambient name)
297
+ $(VAR.field) dotted field access on JSON-bound vars + ambient family
298
+ $(LIST.0) indexed access (v0.2.12 Bug 25 — was undocumented)
299
+ $(LIST.0.id) mixed indexed + field-access (chains arbitrarily deep)
300
+ $(VAR|filter) filter pipe (see \`help({topic: "ops"})\` for filter list)
301
+ $(VAR.field|filter) field-access then filter
302
+ \`\`\`
303
+
304
+ Unresolved refs: tier-1 \`undeclared-var\` at compile, \`UnresolvedVariableError\` at runtime.
267
305
  `;
268
306
  const EXAMPLES = `# Three canonical worked skills
269
307
 
@@ -335,6 +373,93 @@ default: route
335
373
  \`\`\`
336
374
 
337
375
  Demonstrates: \`~\` LocalModel op, named model selection, \`|trim\` filter on LLM output, ref-vs-literal comparison, agent delivery via \`prompt-context:\`, augmenting headers (\`# Delivery-context:\` + \`# Templates:\`).
376
+
377
+ ## 4. Composition — orchestrator invoking child skills
378
+
379
+ \`\`\`
380
+ # Skill: morning-brief-orchestrator
381
+ # Description: Fan out to three child skills, gather their outputs into one brief.
382
+ # Status: Approved
383
+ # Vars: USER_NAME=Scott
384
+
385
+ gather:
386
+ $ execute_skill skill_name=calendar-today USER=$(USER_NAME) -> CAL (fallback: "(no calendar data)")
387
+ $ execute_skill skill_name=mailbox-triage USER=$(USER_NAME) -> MAIL (fallback: "(mailbox empty)")
388
+ $ execute_skill skill_name=weather-summary -> WX (fallback: "(weather unavailable)")
389
+
390
+ render: gather
391
+ ! Good morning, $(USER_NAME). Today:
392
+ ! • Calendar: $(CAL)
393
+ ! • Mailbox: $(MAIL)
394
+ ! • Weather: $(WX)
395
+
396
+ default: render
397
+ \`\`\`
398
+
399
+ Demonstrates: in-skill \`$ execute_skill\` composition (each child runs through the runtime under a depth-counted chain), per-call \`(fallback: ...)\` for resilience, kwarg forwarding (\`USER=$(USER_NAME)\`), \`->\` binding child output for downstream reference.
400
+ `;
401
+ const COMPOSITION = `# Composition
402
+
403
+ Skillscript has three composition primitives — all let one skill draw on another's output, with different semantics around when, where, and how the child runs.
404
+
405
+ ## 1. \`& <skill-name>\` — data-skill inline (compile-time)
406
+
407
+ Inlines an *Approved data skill* into the host skill's compiled artifact at the call site. The data skill's body becomes part of the rendered prompt. Use for *static* knowledge or templated content (style guides, voice rules, runbooks).
408
+
409
+ \`\`\`
410
+ brief:
411
+ ~ prompt="$(VOICE_RULES) Now write a one-line status:" model=qwen -> RESULT
412
+ & voice-rules
413
+ \`\`\`
414
+
415
+ - Resolved at \`compile()\` time — the data skill's \`content_hash\` is recorded in the host's provenance block.
416
+ - Provenance lets \`skillfile audit\` detect stale recompiles when a referenced data skill changes.
417
+ - The data skill must be marked \`# Skill-kind: data\` (or live in a path the SkillStore recognizes as data); otherwise it's treated as procedural and won't inline.
418
+
419
+ ## 2. \`& invoke <skill-name>\` — runtime call (per-fire)
420
+
421
+ Calls a procedural skill at runtime. Each call goes through the runtime under a depth-counted chain (default limit 5) — same recursion guard as Style 3 below.
422
+
423
+ \`\`\`
424
+ escalate:
425
+ & invoke notify-oncall
426
+ \`\`\`
427
+
428
+ - Child skill's outputs flow into the parent's variable scope.
429
+ - Failures propagate as \`OpError\`s.
430
+
431
+ ## 3. \`$ execute_skill skill_name="<child>" ...kwargs -> VAR\` — in-skill execute (per-fire)
432
+
433
+ The most general form: the host \`$\`-dispatches the literal tool name \`execute_skill\` (intercepted by the runtime, not sent to any MCP server). Same depth-counted chain as Style 2, plus full kwarg forwarding and \`-> VAR\` binding for downstream use.
434
+
435
+ \`\`\`
436
+ gather:
437
+ $ execute_skill skill_name="calendar-today" USER=$(USER_NAME) -> CAL (fallback: "(no calendar data)")
438
+ $ execute_skill skill_name="mailbox-triage" inputs={"USER": "$(USER_NAME)"} -> MAIL
439
+ \`\`\`
440
+
441
+ Two kwarg styles, both supported (v0.2.9 fix):
442
+ - **Bare kwargs** — \`USER=$(USER_NAME)\` natural skill grammar
443
+ - **\`inputs={...}\` JSON** — MCP-call parity, useful when forwarding many fields verbatim
444
+
445
+ The bound \`-> VAR\` carries the child's final emit through to the host's scope.
446
+
447
+ ## Limits & lint signals
448
+
449
+ - **Recursion**: depth-5 chain by default (\`ExecuteSkillRecursionError\` if exceeded). Both \`& invoke\` and \`$ execute_skill\` share the counter.
450
+ - **Lint** (\`unknown-skill-reference\`, tier-1): \`& <name>\`, \`& invoke <name>\`, and \`$ execute_skill skill_name=<name>\` all validate the child skill exists in the SkillStore at compile time (v0.2.11 Bug 7 closed the \`$ execute_skill\` gap).
451
+ - **Lint** (\`disabled-skill-reference\`, tier-1): any composition primitive pointing at a \`# Status: Disabled\` skill blocks compile.
452
+
453
+ ## When to use which
454
+
455
+ | Use case | Primitive |
456
+ |---|---|
457
+ | Static knowledge in a prompt | \`& <data-skill>\` |
458
+ | Fire-and-forget child call | \`& invoke <skill>\` |
459
+ | Child output bound into parent scope | \`$ execute_skill ... -> VAR\` |
460
+ | Parallel orchestrators (v0.3.0 candidate — not yet shipped) | parked |
461
+
462
+ See \`help({topic: "examples"})\` example 4 for a worked orchestrator skill.
338
463
  `;
339
464
  const CONNECTORS_PROLOGUE = `# Connectors
340
465
 
@@ -385,19 +510,22 @@ Three tiers per ERD §3:
385
510
  - \`single-equals\` — \`if $(VAR) = "..."\` instead of \`==\` (specific diagnostic)
386
511
  - \`indentation\` — tabs in indentation; mixed tabs/spaces
387
512
  - \`reserved-keyword\` — variable/target/skill name collides with a reserved word
388
- - \`unknown-skill-reference\` — \`&\` op references a skill not in the store
389
- - \`disabled-skill-reference\` — \`&\` op references a Disabled skill
513
+ - \`unknown-skill-reference\` — \`&\` or \`$ execute_skill\` references a skill not in the store (v0.2.11 Bug 7 extended to cover \`$ execute_skill\`)
514
+ - \`unknown-template-reference\` — \`# Templates: <name>\` references a skill not in the store (v0.2.12 Bug 17)
515
+ - \`disabled-skill-reference\` — \`&\` or \`$ execute_skill\` references a Disabled skill
390
516
  - \`credential-in-args\` — op arg looks like a secret literal
391
517
  - \`status-disabled\` — skill marked \`# Status: Disabled\`
392
518
  - \`circular-dependency\` — dep cycle between targets
393
519
  - \`missing-dependency\` — \`needs:\` references a target not declared
394
520
  - \`missing-skillstore-for-data-ref\` — \`&\` op fires without a SkillStore wired
521
+ - \`unsafe-shell-disabled\` — \`@ unsafe\` declared but \`enableUnsafeShell: false\` (v0.2.11 Bug 5; fires only when caller passes the flag explicitly false)
395
522
 
396
523
  ## Tier-2 (warning)
397
524
 
398
525
  - \`deprecated-question\` — bare \`?\` op (deprecated v1; compile-error in v1.x)
399
526
  - \`unsafe-shell-ambiguous-subst\` — \`$(NAME)\` inside \`@ unsafe\` body that isn't a declared variable; collides with bash command-sub syntax
400
527
  - \`unsafe-shell-op\` — \`@ unsafe\` op present; requires human review every time
528
+ - \`unknown-retrieval-arg\` — \`>\` op carries kwargs outside mode/query/limit/connector/fallback (v0.2.12 Bug 26)
401
529
  - \`unconfirmed-mutation\` — \`$\` op invokes a tool whose name suggests mutation (write/update/delete) without a preceding \`??\` confirmation
402
530
  - \`model-contention\` — async + sync ops on the same model serialize on a single runtime worker
403
531
  - \`draft-with-trigger\` — \`# Status: Draft\` skill has \`# Triggers:\` declared; triggers won't fire until Approved
@@ -420,7 +548,7 @@ export function helpResponse(topic, runtimeVersion, registry) {
420
548
  topic: null,
421
549
  version: runtimeVersion,
422
550
  content: QUICKSTART,
423
- available_topics: ["ops", "frontmatter", "examples", "connectors", "lint-codes"],
551
+ available_topics: ["ops", "frontmatter", "examples", "composition", "connectors", "lint-codes"],
424
552
  };
425
553
  }
426
554
  let content;
@@ -434,6 +562,9 @@ export function helpResponse(topic, runtimeVersion, registry) {
434
562
  case "examples":
435
563
  content = EXAMPLES;
436
564
  break;
565
+ case "composition":
566
+ content = COMPOSITION;
567
+ break;
437
568
  case "connectors":
438
569
  content = renderConnectorsTopic(registry);
439
570
  break;
@@ -441,7 +572,7 @@ export function helpResponse(topic, runtimeVersion, registry) {
441
572
  content = LINT_CODES;
442
573
  break;
443
574
  default:
444
- content = `# Unknown topic '${topic}'\n\nValid topics: ops, frontmatter, examples, connectors, lint-codes`;
575
+ content = `# Unknown topic '${topic}'\n\nValid topics: ops, frontmatter, examples, composition, connectors, lint-codes`;
445
576
  }
446
577
  return { topic, version: runtimeVersion, content };
447
578
  }
@@ -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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"}
package/dist/lint.d.ts CHANGED
@@ -69,6 +69,15 @@ export interface LintOptions {
69
69
  * preflight). Default `"api"`.
70
70
  */
71
71
  callSite?: "cli" | "api" | "compile-preflight";
72
+ /**
73
+ * Runtime `enableUnsafeShell` flag, if known to the caller. When
74
+ * explicitly `false`, the `unsafe-shell-disabled` rule (v0.2.11 Bug 5)
75
+ * fires tier-1 on any `@ unsafe` op — the skill would refuse at
76
+ * runtime, and compile should surface that up-front. When `undefined`
77
+ * (caller doesn't know), only the standard tier-2 `unsafe-shell-op`
78
+ * warning fires.
79
+ */
80
+ enableUnsafeShell?: boolean;
72
81
  }
73
82
  interface LintContext {
74
83
  parsed: ParsedSkill;
@@ -78,6 +87,7 @@ interface LintContext {
78
87
  skillStore: SkillStore | undefined;
79
88
  hasSkillStore: boolean;
80
89
  callSite: "cli" | "api" | "compile-preflight";
90
+ enableUnsafeShell: boolean | undefined;
81
91
  }
82
92
  export interface LintRule {
83
93
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,WAAW,EAAgB,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,kBAAkB,IAAI,kBAAkB,CAAA;KAAE,CAAC,CAAC;IAC9D,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,mBAAmB,CAAC;CAChD;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,iBAAiB,EAAE,KAAK,CAAC;QAAE,kBAAkB,IAAI,kBAAkB,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IAC9E,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,mBAAmB,CAAC;CAC/C;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAgCrF;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,CA+B1E;AAED,6HAA6H;AAC7H,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAW3D;AAsyBD,mFAAmF;AACnF,wBAAgB,SAAS,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAElE"}
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,WAAW,EAAgB,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,kBAAkB,IAAI,kBAAkB,CAAA;KAAE,CAAC,CAAC;IAC9D,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,mBAAmB,CAAC;IAC/C;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,iBAAiB,EAAE,KAAK,CAAC;QAAE,kBAAkB,IAAI,kBAAkB,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IAC9E,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,mBAAmB,CAAC;IAC9C,iBAAiB,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAiCrF;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,CAgC1E;AAED,6HAA6H;AAC7H,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAW3D;AAk6BD,mFAAmF;AACnF,wBAAgB,SAAS,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAElE"}
package/dist/lint.js CHANGED
@@ -9,6 +9,7 @@ export async function lint(source, options) {
9
9
  skillStore: options?.skillStore,
10
10
  hasSkillStore: options?.skillStore !== undefined,
11
11
  callSite: options?.callSite ?? "api",
12
+ enableUnsafeShell: options?.enableUnsafeShell,
12
13
  };
13
14
  const findings = [];
14
15
  for (const rule of RULES) {
@@ -41,6 +42,7 @@ export function lintSync(source, options) {
41
42
  skillStore: options?.skillStore,
42
43
  hasSkillStore: options?.skillStore !== undefined,
43
44
  callSite: options?.callSite ?? "api",
45
+ enableUnsafeShell: options?.enableUnsafeShell,
44
46
  };
45
47
  const findings = [];
46
48
  for (const rule of RULES) {
@@ -364,20 +366,21 @@ const UNKNOWN_SKILL_REFERENCE = {
364
366
  const findings = [];
365
367
  const seen = new Set();
366
368
  for (const [targetName, target] of ctx.parsed.targets) {
367
- for (const refName of collectAmpRefsFromOps(target.ops)) {
368
- if (seen.has(refName))
369
+ for (const ref of collectAmpRefsFromOps(target.ops)) {
370
+ const key = `${ref.via}:${ref.name}`;
371
+ if (seen.has(key))
369
372
  continue;
370
- seen.add(refName);
373
+ seen.add(key);
371
374
  try {
372
- await ctx.skillStore.metadata(refName);
375
+ await ctx.skillStore.metadata(ref.name);
373
376
  }
374
377
  catch {
375
378
  findings.push({
376
379
  rule: "unknown-skill-reference",
377
380
  severity: "error",
378
- message: `Skill '${targetName}' references skill '${refName}' via \`&\`, but the SkillStore has no skill by that name.`,
381
+ message: `Skill '${targetName}' references skill '${ref.name}' via \`${ref.via}\`, but the SkillStore has no skill by that name.`,
379
382
  block: targetName,
380
- extras: { referenced_skill: refName },
383
+ extras: { referenced_skill: ref.name, via: ref.via },
381
384
  });
382
385
  }
383
386
  }
@@ -385,6 +388,70 @@ const UNKNOWN_SKILL_REFERENCE = {
385
388
  return findings;
386
389
  },
387
390
  };
391
+ // v0.2.12 Bug 26. Tier-2 warning when a `>` retrieval op carries kwargs
392
+ // outside the documented set. Cold author wrote `since=1h` (hallucinated
393
+ // time-window predicate) and the kwarg passed silently. The documented
394
+ // kwarg set is mode/query/limit/connector/fallback plus filter shapes
395
+ // the connector advertises via staticCapabilities (out of band here).
396
+ const KNOWN_RETRIEVAL_KWARGS = new Set(["mode", "query", "limit", "connector", "fallback"]);
397
+ const UNKNOWN_RETRIEVAL_ARG = {
398
+ id: "unknown-retrieval-arg",
399
+ severity: "warning",
400
+ description: "A `>` retrieval op carries a kwarg outside the documented set (mode/query/limit/connector/fallback).",
401
+ remediation: "Remove the kwarg, or check it against your MemoryStore connector's documentation — extras pass through to the connector but unrecognized ones often indicate hallucinated syntax.",
402
+ check: (ctx) => {
403
+ const findings = [];
404
+ for (const [targetName, target] of ctx.parsed.targets) {
405
+ walkOps(target.ops, (op) => {
406
+ if (op.kind !== ">")
407
+ return;
408
+ const extras = op.retrievalParams?.extra ?? {};
409
+ for (const k of Object.keys(extras)) {
410
+ if (KNOWN_RETRIEVAL_KWARGS.has(k))
411
+ continue;
412
+ findings.push({
413
+ rule: "unknown-retrieval-arg",
414
+ severity: "warning",
415
+ message: `\`>\` op in target '${targetName}' carries unknown kwarg '${k}'. Documented kwargs: ${Array.from(KNOWN_RETRIEVAL_KWARGS).join(", ")}. If '${k}' is a connector-specific filter, confirm it against the connector's docs.`,
416
+ block: targetName,
417
+ extras: { unknown_kwarg: k },
418
+ });
419
+ }
420
+ });
421
+ }
422
+ return findings;
423
+ },
424
+ };
425
+ // v0.2.12 Bug 17. `# Templates:` refs were not lint-validated despite
426
+ // `# OnError:` having compile-time validation (since v0.2.10). Tier-1
427
+ // because a missing template fails delivery at runtime.
428
+ const UNKNOWN_TEMPLATE_REFERENCE = {
429
+ id: "unknown-template-reference",
430
+ severity: "error",
431
+ description: "`# Templates: <name>` references a skill that's not present in the configured SkillStore.",
432
+ remediation: "Check the template name spelling, or store the missing template skill before referencing it. Templates resolve at delivery time; missing ones fail the agent delivery.",
433
+ check: async (ctx) => {
434
+ if (ctx.skillStore === undefined)
435
+ return [];
436
+ if (ctx.parsed.templates.length === 0)
437
+ return [];
438
+ const findings = [];
439
+ for (const name of ctx.parsed.templates) {
440
+ try {
441
+ await ctx.skillStore.metadata(name);
442
+ }
443
+ catch {
444
+ findings.push({
445
+ rule: "unknown-template-reference",
446
+ severity: "error",
447
+ message: `Skill references template '${name}' via \`# Templates:\`, but the SkillStore has no skill by that name.`,
448
+ extras: { referenced_skill: name },
449
+ });
450
+ }
451
+ }
452
+ return findings;
453
+ },
454
+ };
388
455
  const DISABLED_SKILL_REFERENCE = {
389
456
  id: "disabled-skill-reference",
390
457
  severity: "error",
@@ -396,19 +463,19 @@ const DISABLED_SKILL_REFERENCE = {
396
463
  const findings = [];
397
464
  const checked = new Set();
398
465
  for (const [targetName, target] of ctx.parsed.targets) {
399
- for (const refName of collectAmpRefsFromOps(target.ops)) {
400
- if (checked.has(refName))
466
+ for (const ref of collectAmpRefsFromOps(target.ops)) {
467
+ if (checked.has(ref.name))
401
468
  continue;
402
- checked.add(refName);
469
+ checked.add(ref.name);
403
470
  try {
404
- const meta = await ctx.skillStore.metadata(refName);
471
+ const meta = await ctx.skillStore.metadata(ref.name);
405
472
  if (meta.status === "Disabled") {
406
473
  findings.push({
407
474
  rule: "disabled-skill-reference",
408
475
  severity: "error",
409
- message: `Skill '${targetName}' references '${refName}' which is disabled.`,
476
+ message: `Skill '${targetName}' references '${ref.name}' via \`${ref.via}\` which is disabled.`,
410
477
  block: targetName,
411
- extras: { referenced_skill: refName, target_status: meta.status },
478
+ extras: { referenced_skill: ref.name, via: ref.via, target_status: meta.status },
412
479
  });
413
480
  }
414
481
  }
@@ -627,6 +694,19 @@ const UNSAFE_SHELL_AMBIGUOUS_SUBST = {
627
694
  const trimmed = inner.trim();
628
695
  if (/^[A-Za-z_]\w*$/.test(trimmed) && declared.has(trimmed))
629
696
  continue;
697
+ // v0.2.11 Bug 4: dotted refs (EVENT.fired_at_unix, MEMORY.x,
698
+ // <target>.output) are runtime ambient/output families — same
699
+ // dotted-passthrough heuristic as `undeclared-var`. The
700
+ // unsafe-shell warning was telling cold authors to rewrite
701
+ // `$(EVENT.fired_at_unix)` as `$$(EVENT.fired_at_unix)` (bash
702
+ // command-sub), which would just try to execute "EVENT...".
703
+ if (trimmed.includes("."))
704
+ continue;
705
+ // v0.2.11 Bug 4: bare ambient refs (NOW, USER, ERROR_CONTEXT,
706
+ // SESSION_CONTEXT, TRIGGER_TYPE, TRIGGER_PAYLOAD) also pass —
707
+ // runtime injects them, author doesn't declare.
708
+ if (AMBIENT_VARS.includes(trimmed))
709
+ continue;
630
710
  if (reported.has(inner))
631
711
  continue;
632
712
  reported.add(inner);
@@ -665,8 +745,55 @@ const UNSAFE_SHELL_OP = {
665
745
  return findings;
666
746
  },
667
747
  };
668
- /** Tool-name patterns that strongly suggest mutating operations. Conservative — false positives are tolerable for warnings; false negatives are dangerous. */
669
- const MUTATING_TOOL_PATTERN = /^(?:write_|update_|delete_|remove_|set_|create_|insert_|put_|patch_|destroy_).*/;
748
+ /**
749
+ * v0.2.11 Bug 5. Tier-1 escalation of the unsafe-shell signal — only
750
+ * fires when the caller passed `enableUnsafeShell: false` explicitly.
751
+ * Without that knowledge (the field is undefined), this rule stays
752
+ * silent and the tier-2 `unsafe-shell-op` warning is the only signal.
753
+ *
754
+ * When the runtime is known-disabled, every `@ unsafe` op is a guaranteed
755
+ * runtime refusal (`UnsafeShellDisabledError`). Surfacing that at compile
756
+ * time instead of letting the skill compile clean and then fail at first
757
+ * fire avoids the "compiles clean but won't run" gap Perry's harness
758
+ * surfaced (memory `b6176e02`).
759
+ */
760
+ const UNSAFE_SHELL_DISABLED = {
761
+ id: "unsafe-shell-disabled",
762
+ severity: "error",
763
+ description: "Skill uses `@ unsafe`, but the runtime was configured with `enableUnsafeShell: false`. The op will refuse at first fire.",
764
+ remediation: "Either set `enableUnsafeShell: true` on the runtime (after reviewing the shell content), or refactor the `@ unsafe` op to use the structured `@ <binary> <args>` form (sandboxed, no bash).",
765
+ check: (ctx) => {
766
+ if (ctx.enableUnsafeShell !== false)
767
+ return [];
768
+ const findings = [];
769
+ for (const [targetName, target] of ctx.parsed.targets) {
770
+ walkOps(target.ops, (op) => {
771
+ if (op.kind === "@" && op.policy === "unsafe") {
772
+ findings.push({
773
+ rule: "unsafe-shell-disabled",
774
+ severity: "error",
775
+ message: `\`@ unsafe\` op in target '${targetName}' would refuse at runtime: \`enableUnsafeShell\` is false. Command: '${op.body.slice(0, 60)}${op.body.length > 60 ? "..." : ""}'`,
776
+ block: targetName,
777
+ });
778
+ }
779
+ });
780
+ }
781
+ return findings;
782
+ },
783
+ };
784
+ /**
785
+ * Tool-name patterns that strongly suggest mutating operations.
786
+ * Conservative — false positives are tolerable for warnings; false
787
+ * negatives are dangerous.
788
+ *
789
+ * v0.2.11 Bug 6: extended with archive_/prune_/deploy_/expire_/
790
+ * consolidate_/purge_/reset_/rotate_/move_/rename_/drop_/truncate_/
791
+ * upsert_/overwrite_/clear_/wipe_/finalize_ — Perry's wild-and-crazy
792
+ * harness surfaced a cluster of mutating tools that the original
793
+ * write/update/delete/etc. set didn't catch (`archive_old_threads`,
794
+ * `prune_threads`, `deploy_release`, `dangerous-cleanup`'s `expire_*`).
795
+ */
796
+ const MUTATING_TOOL_PATTERN = /^(?:write_|update_|delete_|remove_|set_|create_|insert_|put_|patch_|destroy_|archive_|prune_|deploy_|expire_|consolidate_|purge_|reset_|rotate_|move_|rename_|drop_|truncate_|upsert_|overwrite_|clear_|wipe_|finalize_).*/;
670
797
  const UNCONFIRMED_MUTATION = {
671
798
  id: "unconfirmed-mutation",
672
799
  severity: "warning",
@@ -756,19 +883,19 @@ const REFERENCE_TO_DISABLED_SKILL = {
756
883
  const findings = [];
757
884
  const checked = new Set();
758
885
  for (const [targetName, target] of ctx.parsed.targets) {
759
- for (const refName of collectAmpRefsFromOps(target.ops)) {
760
- if (checked.has(refName))
886
+ for (const ref of collectAmpRefsFromOps(target.ops)) {
887
+ if (checked.has(ref.name))
761
888
  continue;
762
- checked.add(refName);
889
+ checked.add(ref.name);
763
890
  try {
764
- const meta = await ctx.skillStore.metadata(refName);
891
+ const meta = await ctx.skillStore.metadata(ref.name);
765
892
  if (meta.status === "Disabled") {
766
893
  findings.push({
767
894
  rule: "reference-to-disabled-skill",
768
895
  severity: "warning",
769
- message: `Target '${targetName}' references '${refName}' which is disabled.`,
896
+ message: `Target '${targetName}' references '${ref.name}' via \`${ref.via}\` which is disabled.`,
770
897
  block: targetName,
771
- extras: { referenced_skill: refName },
898
+ extras: { referenced_skill: ref.name, via: ref.via },
772
899
  });
773
900
  }
774
901
  }
@@ -872,6 +999,8 @@ const RULES = [
872
999
  INDENTATION,
873
1000
  RESERVED_KEYWORD,
874
1001
  UNKNOWN_SKILL_REFERENCE,
1002
+ UNKNOWN_TEMPLATE_REFERENCE,
1003
+ UNKNOWN_RETRIEVAL_ARG,
875
1004
  DISABLED_SKILL_REFERENCE,
876
1005
  CREDENTIAL_IN_ARGS,
877
1006
  STATUS_DISABLED,
@@ -882,6 +1011,7 @@ const RULES = [
882
1011
  DEPRECATED_QUESTION,
883
1012
  UNSAFE_SHELL_AMBIGUOUS_SUBST,
884
1013
  UNSAFE_SHELL_OP,
1014
+ UNSAFE_SHELL_DISABLED,
885
1015
  UNCONFIRMED_MUTATION,
886
1016
  MODEL_CONTENTION,
887
1017
  DRAFT_WITH_TRIGGER,
@@ -911,10 +1041,27 @@ function walkOps(ops, visit) {
911
1041
  }
912
1042
  }
913
1043
  function collectAmpRefsFromOps(ops) {
914
- const out = new Set();
1044
+ const out = [];
1045
+ const seen = new Set();
1046
+ const emit = (name, via) => {
1047
+ const key = `${via}:${name}`;
1048
+ if (seen.has(key))
1049
+ return;
1050
+ seen.add(key);
1051
+ out.push({ name, via });
1052
+ };
915
1053
  walkOps(ops, (op) => {
916
1054
  if (op.kind === "&" && op.ampParams !== undefined)
917
- out.add(op.ampParams.skillName);
1055
+ emit(op.ampParams.skillName, "&");
1056
+ // v0.2.11 Bug 7: $ execute_skill is also a composition primitive.
1057
+ if (op.kind === "$" && /^execute_skill\b/.test(op.body)) {
1058
+ const m = /\bskill_name\s*=\s*(?:"([^"]+)"|'([^']+)'|([A-Za-z_][\w-]*))/.exec(op.body);
1059
+ if (m !== null) {
1060
+ const name = m[1] ?? m[2] ?? m[3];
1061
+ if (name !== undefined && name !== "")
1062
+ emit(name, "$ execute_skill");
1063
+ }
1064
+ }
918
1065
  });
919
1066
  return out;
920
1067
  }