skillscript-runtime 0.7.0 → 0.7.2
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 +74 -8
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +15 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +14 -1
- package/dist/compile.js.map +1 -1
- package/dist/connectors/config.d.ts.map +1 -1
- package/dist/connectors/config.js +10 -0
- package/dist/connectors/config.js.map +1 -1
- package/dist/connectors/local-model-mcp.d.ts +9 -0
- package/dist/connectors/local-model-mcp.d.ts.map +1 -0
- package/dist/connectors/local-model-mcp.js +73 -0
- package/dist/connectors/local-model-mcp.js.map +1 -0
- package/dist/connectors/memory-store-mcp.d.ts +9 -0
- package/dist/connectors/memory-store-mcp.d.ts.map +1 -0
- package/dist/connectors/memory-store-mcp.js +94 -0
- package/dist/connectors/memory-store-mcp.js.map +1 -0
- package/dist/help-content.d.ts.map +1 -1
- package/dist/help-content.js +316 -238
- package/dist/help-content.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +282 -44
- package/dist/lint.js.map +1 -1
- package/dist/parser.d.ts +22 -2
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +119 -7
- package/dist/parser.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +13 -1
- package/dist/runtime.js.map +1 -1
- package/examples/hello.skill.provenance.json +1 -1
- package/package.json +1 -1
- package/scaffold/connectors.json +15 -13
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,mEAAmE;AACnE,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
|
@@ -1073,8 +1073,8 @@ const MUTATING_TOOL_PATTERN = /^(?:write_|update_|delete_|remove_|set_|create_|i
|
|
|
1073
1073
|
const UNCONFIRMED_MUTATION = {
|
|
1074
1074
|
id: "unconfirmed-mutation",
|
|
1075
1075
|
severity: "warning",
|
|
1076
|
-
description: "A
|
|
1077
|
-
remediation: "
|
|
1076
|
+
description: "A mutation-class op runs without author authorization. Mutation classes: `$ tool` with mutating-name shape (write/update/delete/...); `$ memory_write` MCP dispatch; `file_write(...)` function-call op. Silent when the skill declares `# Autonomous: true` (v0.4.2), when a preceding `??` / `ask(...)` confirmation gates the op, or (v0.7.0+) when the op carries `approved=\"reason\"` per-op authorization.",
|
|
1077
|
+
remediation: "Three ways to authorize: (1) add `# Autonomous: true` at the skill header for cron/agent-fired skills; (2) add a preceding `??` / `ask(prompt=\"...\")` confirmation op in the same target; (3) v0.7.0+: pass `approved=\"reason\"` kwarg on the mutation op itself (any non-empty string; presence is what matters, value not parsed semantically).",
|
|
1078
1078
|
check: (ctx) => {
|
|
1079
1079
|
// v0.4.2 — `# Autonomous: true` skills are unattended by design;
|
|
1080
1080
|
// the user-confirmation pattern doesn't apply. Silent for the
|
|
@@ -1085,19 +1085,44 @@ const UNCONFIRMED_MUTATION = {
|
|
|
1085
1085
|
for (const [targetName, target] of ctx.parsed.targets) {
|
|
1086
1086
|
let sawConfirm = false;
|
|
1087
1087
|
for (const op of target.ops) {
|
|
1088
|
+
// v0.7.0+: ask(prompt=...) parses to kind "??" in the AST, so the
|
|
1089
|
+
// existing check captures both legacy `??` and canonical `ask()`.
|
|
1088
1090
|
if (op.kind === "??")
|
|
1089
1091
|
sawConfirm = true;
|
|
1090
|
-
if (
|
|
1092
|
+
if (sawConfirm)
|
|
1093
|
+
continue;
|
|
1094
|
+
// v0.7.0+: per-op `approved="reason"` kwarg authorizes individual ops
|
|
1095
|
+
// without requiring # Autonomous: true skill-level or `??` step.
|
|
1096
|
+
const isApproved = typeof op.approved === "string" && op.approved.length > 0;
|
|
1097
|
+
if (isApproved)
|
|
1098
|
+
continue;
|
|
1099
|
+
// Class 1: `$` MCP dispatch with mutating tool name.
|
|
1100
|
+
if (op.kind === "$") {
|
|
1091
1101
|
const toolName = op.body.split(/\s+/)[0] ?? "";
|
|
1092
|
-
|
|
1102
|
+
// memory_write is explicitly a mutation tool name — flag it even
|
|
1103
|
+
// though it doesn't start with `write_` (the pattern's anchor).
|
|
1104
|
+
const isMemoryWrite = toolName === "memory_write" || /(?:^|_)memory_write(?:_|$)/.test(toolName);
|
|
1105
|
+
if (MUTATING_TOOL_PATTERN.test(toolName) || isMemoryWrite) {
|
|
1093
1106
|
findings.push({
|
|
1094
1107
|
rule: "unconfirmed-mutation",
|
|
1095
1108
|
severity: "warning",
|
|
1096
|
-
message: `\`$\` op in target '${targetName}' invokes '${toolName}' (mutating shape) without
|
|
1109
|
+
message: `\`$\` op in target '${targetName}' invokes '${toolName}' (mutating shape) without authorization. Add \`approved="..."\` kwarg, precede with \`ask(...)\`, or declare \`# Autonomous: true\`.`,
|
|
1097
1110
|
block: targetName,
|
|
1111
|
+
extras: { tool_name: toolName },
|
|
1098
1112
|
});
|
|
1099
1113
|
}
|
|
1100
1114
|
}
|
|
1115
|
+
// Class 2: file_write runtime-intrinsic op (v0.7.0).
|
|
1116
|
+
if (op.kind === "file_write") {
|
|
1117
|
+
const path = op.fileParams?.path ?? "";
|
|
1118
|
+
findings.push({
|
|
1119
|
+
rule: "unconfirmed-mutation",
|
|
1120
|
+
severity: "warning",
|
|
1121
|
+
message: `\`file_write(path="${path}")\` in target '${targetName}' is a mutation op without authorization. Add \`approved="..."\` kwarg, precede with \`ask(...)\`, or declare \`# Autonomous: true\`.`,
|
|
1122
|
+
block: targetName,
|
|
1123
|
+
extras: { op_kind: "file_write", path },
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1101
1126
|
}
|
|
1102
1127
|
}
|
|
1103
1128
|
return findings;
|
|
@@ -1530,51 +1555,84 @@ function isOriginSuspect(origin) {
|
|
|
1530
1555
|
const UNQUOTED_SUBSTITUTION_IN_KWARG_VALUE = {
|
|
1531
1556
|
id: "unquoted-substitution-in-kwarg-value",
|
|
1532
1557
|
severity: "warning",
|
|
1533
|
-
description: "A `$ tool key=$(VAR)` op kwarg has an unquoted `$(VAR)` substitution where VAR may resolve to a value containing whitespace. Runtime renders into `key=value with spaces` then re-tokenizes on whitespace — only the first chunk binds to `key
|
|
1534
|
-
remediation: "Wrap the substitution in quotes: `key=\"$(VAR)\"
|
|
1558
|
+
description: "A `$ tool key=$(VAR)` op kwarg OR a legacy `@ cmd ... $(VAR)` shell arg has an unquoted `$(VAR)` / `${VAR}` substitution where VAR may resolve to a value containing whitespace. Runtime renders into `key=value with spaces` then re-tokenizes on whitespace — only the first chunk binds to `key` (MCP) or first arg (shell). Silent arg truncation. v0.7.2 extends coverage from `$` ops to `@` ops per R4 minion 4 finding.",
|
|
1559
|
+
remediation: "Wrap the substitution in quotes: `key=\"$(VAR)\"` for MCP kwargs, `\"$(VAR)\"` for shell args. The arg tokenizer respects quoted regions, preventing the re-tokenization split.",
|
|
1535
1560
|
check: (ctx) => {
|
|
1536
1561
|
const findings = [];
|
|
1537
1562
|
const origins = buildBindingOrigins(ctx.parsed);
|
|
1538
1563
|
const reported = new Set();
|
|
1564
|
+
// v0.7.2: shared pattern matches both legacy `$(VAR)` and canonical
|
|
1565
|
+
// `${VAR}` substitution forms. Only the opening delimiter + var-name
|
|
1566
|
+
// are required to match (no closing `)`/`}`) so filter chains like
|
|
1567
|
+
// `$(VAR|trim)` and `${VAR|filter:"x"}` parse cleanly. The capture
|
|
1568
|
+
// groups (1 = paren-form name, 2 = brace-form name) get coalesced.
|
|
1569
|
+
const subStPattern = /\$(?:\(([^|)\s]+)|\{([^|}\s]+))/;
|
|
1539
1570
|
for (const [targetName, target] of ctx.parsed.targets) {
|
|
1540
1571
|
walkOps(target.ops, (op) => {
|
|
1541
|
-
if (op.kind
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
})
|
|
1572
|
+
if (op.kind === "$") {
|
|
1573
|
+
// $ MCP dispatch — scan kwarg values for unquoted substitutions.
|
|
1574
|
+
const tokens = tokenizeKeywordArgs(op.body);
|
|
1575
|
+
for (const tok of tokens) {
|
|
1576
|
+
const eq = tok.indexOf("=");
|
|
1577
|
+
if (eq === -1)
|
|
1578
|
+
continue;
|
|
1579
|
+
const key = tok.slice(0, eq);
|
|
1580
|
+
const value = tok.slice(eq + 1);
|
|
1581
|
+
if (!(value.startsWith("$(") || value.startsWith("${")))
|
|
1582
|
+
continue;
|
|
1583
|
+
const m = subStPattern.exec(value);
|
|
1584
|
+
if (m === null)
|
|
1585
|
+
continue;
|
|
1586
|
+
const varName = (m[1] ?? m[2]);
|
|
1587
|
+
const rootVar = varName.split(".")[0];
|
|
1588
|
+
const origin = origins.get(rootVar);
|
|
1589
|
+
if (!isOriginSuspect(origin))
|
|
1590
|
+
continue;
|
|
1591
|
+
const dedupKey = `${targetName}:$:${key}:${varName}`;
|
|
1592
|
+
if (reported.has(dedupKey))
|
|
1593
|
+
continue;
|
|
1594
|
+
reported.add(dedupKey);
|
|
1595
|
+
findings.push({
|
|
1596
|
+
rule: "unquoted-substitution-in-kwarg-value",
|
|
1597
|
+
severity: "warning",
|
|
1598
|
+
message: `\`$ ... ${key}=\${${varName}}\` in target '${targetName}': unquoted substitution. ${describeOriginRisk(origin)} Wrap as \`${key}="\${${varName}}"\` to prevent silent arg truncation if the value contains whitespace.`,
|
|
1599
|
+
block: targetName,
|
|
1600
|
+
extras: { kwarg: key, var_name: varName, origin: origin.kind, op: "$" },
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
else if (op.kind === "@") {
|
|
1605
|
+
// v0.7.2 — legacy @ shell op. Tokenize the body the same way the
|
|
1606
|
+
// runtime would (whitespace-separated, quotes respected), then
|
|
1607
|
+
// flag any token that is a bare unquoted substitution. Quoted
|
|
1608
|
+
// tokens (`"${VAR}"` / `'${VAR}'`) are safe.
|
|
1609
|
+
const tokens = tokenizeKeywordArgs(op.body);
|
|
1610
|
+
for (const tok of tokens) {
|
|
1611
|
+
// Skip quoted tokens — the quotes protect against whitespace split.
|
|
1612
|
+
if ((tok.startsWith('"') && tok.endsWith('"')) || (tok.startsWith("'") && tok.endsWith("'")))
|
|
1613
|
+
continue;
|
|
1614
|
+
if (!(tok.startsWith("$(") || tok.startsWith("${")))
|
|
1615
|
+
continue;
|
|
1616
|
+
const m = subStPattern.exec(tok);
|
|
1617
|
+
if (m === null)
|
|
1618
|
+
continue;
|
|
1619
|
+
const varName = (m[1] ?? m[2]);
|
|
1620
|
+
const rootVar = varName.split(".")[0];
|
|
1621
|
+
const origin = origins.get(rootVar);
|
|
1622
|
+
if (!isOriginSuspect(origin))
|
|
1623
|
+
continue;
|
|
1624
|
+
const dedupKey = `${targetName}:@:${varName}`;
|
|
1625
|
+
if (reported.has(dedupKey))
|
|
1626
|
+
continue;
|
|
1627
|
+
reported.add(dedupKey);
|
|
1628
|
+
findings.push({
|
|
1629
|
+
rule: "unquoted-substitution-in-kwarg-value",
|
|
1630
|
+
severity: "warning",
|
|
1631
|
+
message: `\`@ ... \${${varName}}\` shell arg in target '${targetName}': unquoted substitution. ${describeOriginRisk(origin)} Wrap as \`"\${${varName}}"\` to prevent silent word-splitting if the value contains whitespace.`,
|
|
1632
|
+
block: targetName,
|
|
1633
|
+
extras: { var_name: varName, origin: origin.kind, op: "@" },
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1578
1636
|
}
|
|
1579
1637
|
});
|
|
1580
1638
|
}
|
|
@@ -1595,6 +1653,183 @@ function describeOriginRisk(origin) {
|
|
|
1595
1653
|
return `Variable is a \`foreach\` iterator — element type unknown statically.`;
|
|
1596
1654
|
}
|
|
1597
1655
|
}
|
|
1656
|
+
/**
|
|
1657
|
+
* v0.7.1 — tier-2 visibility nudge for legacy symbol-form ops (`~`, `>`,
|
|
1658
|
+
* `@`, `!`, `??`, `&`). The parser still accepts these during the v0.7.x
|
|
1659
|
+
* grace period; the runtime dispatches them as before. This rule surfaces
|
|
1660
|
+
* the canonical replacement so authors editing skills see the migration
|
|
1661
|
+
* path. Tier-1 promotion (refuse-to-compile) lands in v0.8 or v0.9 once
|
|
1662
|
+
* the adopter ecosystem confirms migration is settled.
|
|
1663
|
+
*
|
|
1664
|
+
* The `$ tool` op is NOT flagged — that shape is canonical (MCP dispatch
|
|
1665
|
+
* marker). Only the 6 symbol ops that became function-call ops in v0.7.0.
|
|
1666
|
+
*/
|
|
1667
|
+
const DEPRECATED_SYMBOL_OP_REPLACEMENT = {
|
|
1668
|
+
// v0.7.2: bridge classes ship default-wired so `$ llm` / `$ memory` work
|
|
1669
|
+
// out of the box in default deployments. Suggestions are load-bearing
|
|
1670
|
+
// (no more "(or your wired connector name)" caveat).
|
|
1671
|
+
"~": "$ llm prompt=\"...\" [maxTokens=N] [model=\"...\"] -> R",
|
|
1672
|
+
">": "$ memory mode=\"fts|semantic|rerank\" query=\"...\" limit=N -> R",
|
|
1673
|
+
"@": "shell(command=\"...\") [-> R]",
|
|
1674
|
+
"!": "emit(text=\"...\")",
|
|
1675
|
+
"??": "ask(prompt=\"...\") -> R",
|
|
1676
|
+
"&": "inline(skill=\"...\") (or execute_skill(skill_name=\"...\", ...) -> R for procedural composition)",
|
|
1677
|
+
};
|
|
1678
|
+
const DEPRECATED_SYMBOL_OP = {
|
|
1679
|
+
id: "deprecated-symbol-op",
|
|
1680
|
+
severity: "warning",
|
|
1681
|
+
description: "An op uses the legacy symbol form deprecated in v0.7.0.",
|
|
1682
|
+
remediation: "Rewrite to the canonical v0.7.0 form (see message). All legacy ops continue to compile during the grace period; tier-1 promotion (refuse-to-compile) lands in v0.8/v0.9. See CHANGELOG.md `## 0.7.0 — Migration` for the full rewrite rules.",
|
|
1683
|
+
check: (ctx) => {
|
|
1684
|
+
const findings = [];
|
|
1685
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
1686
|
+
const reported = new Set();
|
|
1687
|
+
walkOps(target.ops, (op) => {
|
|
1688
|
+
const replacement = DEPRECATED_SYMBOL_OP_REPLACEMENT[op.kind];
|
|
1689
|
+
if (replacement === undefined)
|
|
1690
|
+
return;
|
|
1691
|
+
// v0.7.1: skip ops authored in canonical function-call form. The
|
|
1692
|
+
// parser collapses both `! x` and `emit(text="x")` to kind "!",
|
|
1693
|
+
// so without the sourceForm marker the lint would fire on
|
|
1694
|
+
// canonical code. Source-form marker preserves which surface
|
|
1695
|
+
// the author wrote.
|
|
1696
|
+
if (op.sourceForm === "function-call")
|
|
1697
|
+
return;
|
|
1698
|
+
// Dedupe per-kind-per-target — one nudge per legacy op type per
|
|
1699
|
+
// target is plenty; further occurrences don't add signal.
|
|
1700
|
+
const key = `${targetName}:${op.kind}`;
|
|
1701
|
+
if (reported.has(key))
|
|
1702
|
+
return;
|
|
1703
|
+
reported.add(key);
|
|
1704
|
+
findings.push({
|
|
1705
|
+
rule: "deprecated-symbol-op",
|
|
1706
|
+
severity: "warning",
|
|
1707
|
+
message: `Op '${op.kind}' in target '${targetName}' is deprecated in v0.7.0. Rewrite as: \`${replacement}\``,
|
|
1708
|
+
block: targetName,
|
|
1709
|
+
extras: { legacy_op: op.kind, canonical_replacement: replacement },
|
|
1710
|
+
});
|
|
1711
|
+
});
|
|
1712
|
+
if (target.elseBlock !== undefined) {
|
|
1713
|
+
walkOps(target.elseBlock, (op) => {
|
|
1714
|
+
const replacement = DEPRECATED_SYMBOL_OP_REPLACEMENT[op.kind];
|
|
1715
|
+
if (replacement === undefined)
|
|
1716
|
+
return;
|
|
1717
|
+
if (op.sourceForm === "function-call")
|
|
1718
|
+
return;
|
|
1719
|
+
const key = `${targetName}:else:${op.kind}`;
|
|
1720
|
+
if (reported.has(key))
|
|
1721
|
+
return;
|
|
1722
|
+
reported.add(key);
|
|
1723
|
+
findings.push({
|
|
1724
|
+
rule: "deprecated-symbol-op",
|
|
1725
|
+
severity: "warning",
|
|
1726
|
+
message: `Op '${op.kind}' in target '${targetName}' (else block) is deprecated in v0.7.0. Rewrite as: \`${replacement}\``,
|
|
1727
|
+
block: targetName,
|
|
1728
|
+
extras: { legacy_op: op.kind, canonical_replacement: replacement },
|
|
1729
|
+
});
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
return findings;
|
|
1734
|
+
},
|
|
1735
|
+
};
|
|
1736
|
+
/**
|
|
1737
|
+
* v0.7.1 — tier-2 visibility nudge for legacy `$(VAR)` substitution form.
|
|
1738
|
+
* Canonical v0.7.0+ form is `${VAR}`. Parser/runtime accept both during
|
|
1739
|
+
* grace period. Dedupes per-var-per-target; one nudge per `$(VAR)` form
|
|
1740
|
+
* per scope.
|
|
1741
|
+
*
|
|
1742
|
+
* Skips the `$$(...)` escape (used in `@ unsafe` op bodies for shell
|
|
1743
|
+
* literal pass-through). Skips ops where the body is `$set` source
|
|
1744
|
+
* (because $set's RHS is its own substitution context and the lint would
|
|
1745
|
+
* double-fire).
|
|
1746
|
+
*/
|
|
1747
|
+
const DEPRECATED_SUBSTITUTION_SHAPE = {
|
|
1748
|
+
id: "deprecated-substitution-shape",
|
|
1749
|
+
severity: "warning",
|
|
1750
|
+
description: "A `$(VAR)` substitution uses the legacy v0.6.x form deprecated in v0.7.0.",
|
|
1751
|
+
remediation: "Rewrite to `${VAR}` canonical form. Both forms produce identical results during the v0.7.x grace period; tier-1 promotion lands in v0.8/v0.9. The `$$(VAR)` escape (for `@ unsafe` shell literal pass-through) is unchanged.",
|
|
1752
|
+
check: (ctx) => {
|
|
1753
|
+
const findings = [];
|
|
1754
|
+
// Negative lookbehind blocks `$$(VAR)` escape form so the lint doesn't
|
|
1755
|
+
// fire on shell-escape sites authors deliberately wrote.
|
|
1756
|
+
const legacyRe = /(?<!\$)\$\(([^|)\s]+)/g;
|
|
1757
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
1758
|
+
const reported = new Set();
|
|
1759
|
+
const scanOp = (op, scope) => {
|
|
1760
|
+
const text = collectOpText(op);
|
|
1761
|
+
let m;
|
|
1762
|
+
while ((m = legacyRe.exec(text)) !== null) {
|
|
1763
|
+
const varName = m[1];
|
|
1764
|
+
const key = `${targetName}:${scope}:${varName}`;
|
|
1765
|
+
if (reported.has(key))
|
|
1766
|
+
continue;
|
|
1767
|
+
reported.add(key);
|
|
1768
|
+
findings.push({
|
|
1769
|
+
rule: "deprecated-substitution-shape",
|
|
1770
|
+
severity: "warning",
|
|
1771
|
+
message: `Substitution '$(${varName})' in target '${targetName}'${scope === "else" ? " (else block)" : ""} uses the legacy v0.6.x form. Rewrite as '\${${varName}}'.`,
|
|
1772
|
+
block: targetName,
|
|
1773
|
+
extras: { var_name: varName, legacy_form: `$(${varName})`, canonical_form: `\${${varName}}` },
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
legacyRe.lastIndex = 0;
|
|
1777
|
+
};
|
|
1778
|
+
walkOps(target.ops, (op) => scanOp(op, "main"));
|
|
1779
|
+
if (target.elseBlock !== undefined) {
|
|
1780
|
+
walkOps(target.elseBlock, (op) => scanOp(op, "else"));
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return findings;
|
|
1784
|
+
},
|
|
1785
|
+
};
|
|
1786
|
+
/**
|
|
1787
|
+
* v0.7.2 — tier-3 advisory for the R4 cold-author footgun (4 of 5 minions).
|
|
1788
|
+
* `foreach IT in ${VAR}` where VAR's binding origin is a `$` MCP tool output
|
|
1789
|
+
* (and the iteration expression has no `.field` accessor). MCP tools commonly
|
|
1790
|
+
* wrap arrays in an envelope object (e.g., `{issuesPage: [...], hasNextPage}`,
|
|
1791
|
+
* `{items: [...]}`, `{results: [...]}`) — cold authors iterating the bare
|
|
1792
|
+
* bound var get silent stringification + a single-iteration loop with the
|
|
1793
|
+
* stringification as the iterator value. Downstream `${IT.field}` errors.
|
|
1794
|
+
*
|
|
1795
|
+
* Placeholder for the v0.8 tool-schema-introspection solution that catches
|
|
1796
|
+
* this precisely. Advisory hints at the common envelope-field names.
|
|
1797
|
+
*/
|
|
1798
|
+
const OBJECT_ITERATION_ADVISORY = {
|
|
1799
|
+
id: "object-iteration-advisory",
|
|
1800
|
+
severity: "info",
|
|
1801
|
+
description: "A `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.",
|
|
1802
|
+
remediation: "Check the tool's response shape — most MCP services wrap arrays under fields like `.items`, `.results`, `.issuesPage`, `.data`, `.records`. Rewrite as `foreach IT in ${VAR.items}` (or the correct field) once you know the shape. v0.8 tool-schema introspection will catch this precisely; today the advisory is a soft nudge.",
|
|
1803
|
+
check: (ctx) => {
|
|
1804
|
+
const findings = [];
|
|
1805
|
+
const origins = buildBindingOrigins(ctx.parsed);
|
|
1806
|
+
// Bare var ref pattern: `$(VAR)` or `${VAR}` — no dotted accessor, no filter chain.
|
|
1807
|
+
const bareRef = /^\s*\$(?:\(([A-Za-z_]\w*)\)|\{([A-Za-z_]\w*)\})\s*$/;
|
|
1808
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
1809
|
+
walkOps(target.ops, (op) => {
|
|
1810
|
+
if (op.kind !== "foreach" || op.foreachList === undefined)
|
|
1811
|
+
return;
|
|
1812
|
+
const m = bareRef.exec(op.foreachList);
|
|
1813
|
+
if (m === null)
|
|
1814
|
+
return;
|
|
1815
|
+
const varName = (m[1] ?? m[2]);
|
|
1816
|
+
const origin = origins.get(varName);
|
|
1817
|
+
if (origin === undefined)
|
|
1818
|
+
return;
|
|
1819
|
+
if (origin.kind !== "op-output" || origin.op !== "$")
|
|
1820
|
+
return;
|
|
1821
|
+
findings.push({
|
|
1822
|
+
rule: "object-iteration-advisory",
|
|
1823
|
+
severity: "info",
|
|
1824
|
+
message: `In target '${targetName}': \`foreach ${op.foreachIter} in \${${varName}}\` iterates a bare \`$\` op output without a \`.field\` accessor. Most MCP tools wrap arrays in an envelope (e.g., \`.items\`, \`.results\`, \`.issuesPage\`, \`.data\`). Check the tool's response shape; rewrite as \`foreach ${op.foreachIter} in \${${varName}.items}\` (or the actual array field) if so.`,
|
|
1825
|
+
block: targetName,
|
|
1826
|
+
extras: { var_name: varName, foreach_iter: op.foreachIter },
|
|
1827
|
+
});
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
return findings;
|
|
1831
|
+
},
|
|
1832
|
+
};
|
|
1598
1833
|
const RULES = [
|
|
1599
1834
|
// Tier-1 (error)
|
|
1600
1835
|
PARSE_ERROR,
|
|
@@ -1628,6 +1863,8 @@ const RULES = [
|
|
|
1628
1863
|
MISSING_SKILLSTORE_FOR_DATA_REF,
|
|
1629
1864
|
// Tier-2 (warning)
|
|
1630
1865
|
DEPRECATED_QUESTION,
|
|
1866
|
+
DEPRECATED_SYMBOL_OP,
|
|
1867
|
+
DEPRECATED_SUBSTITUTION_SHAPE,
|
|
1631
1868
|
UNSAFE_SHELL_AMBIGUOUS_SUBST,
|
|
1632
1869
|
UNSAFE_SHELL_OP,
|
|
1633
1870
|
UNSAFE_SHELL_DISABLED,
|
|
@@ -1642,6 +1879,7 @@ const RULES = [
|
|
|
1642
1879
|
DUPLICATE_SKILL_NAME,
|
|
1643
1880
|
PLUGIN_COLLISION,
|
|
1644
1881
|
UNPARSED_JSON_FIELD_ACCESS,
|
|
1882
|
+
OBJECT_ITERATION_ADVISORY,
|
|
1645
1883
|
];
|
|
1646
1884
|
/** Read-only view of the rule registry — for tooling that introspects v1 rules. */
|
|
1647
1885
|
export function listRules() {
|