skillscript-runtime 0.3.2 → 0.4.0

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.
@@ -0,0 +1,147 @@
1
+ // v0.4.0 — `connectors.json` loader. Reads + validates the per-host
2
+ // connector configuration file at runtime startup and wires the
3
+ // declared instances into the Registry.
4
+ //
5
+ // Spec: ERD §3 + Perry's v0.4.0 kickoff (b3f6c5ed) + amendment (58a9d3d3).
6
+ //
7
+ // Surface shape (matches Claude Desktop's `mcp.json` convention so authors
8
+ // don't carry two mental models):
9
+ //
10
+ // {
11
+ // "youtrack": {
12
+ // "class": "RemoteMcpConnector",
13
+ // "config": {
14
+ // "command": "npx",
15
+ // "args": ["mcp-remote", "https://...", "--header", "Authorization:${AUTH_HEADER}"],
16
+ // "env": { "AUTH_HEADER": "Bearer ${YOUTRACK_TOKEN}" }
17
+ // }
18
+ // }
19
+ // }
20
+ //
21
+ // **Credential discipline (v0.4.0 hard requirement, Perry's amendment):**
22
+ // `connectors.json` is secret-bearing. Default `.gitignore` excludes it;
23
+ // `connectors.json.example` ships at repo root as the template. See README.
24
+ import { readFileSync } from "node:fs";
25
+ import { CallbackMcpConnector } from "./mcp.js";
26
+ export const KNOWN_CONNECTOR_CLASSES = new Map([
27
+ ["CallbackMcpConnector", { ctor: CallbackMcpConnector }],
28
+ ]);
29
+ /** Listable for error messages + runtime_capabilities discovery. */
30
+ export function listKnownConnectorClasses() {
31
+ return [...KNOWN_CONNECTOR_CLASSES.keys()];
32
+ }
33
+ /**
34
+ * Resolve `${NAME}` patterns in a string against the provided env.
35
+ * Missing var → throws (clear error rather than silent empty string).
36
+ * Used by the loader on every string value in the `config` block.
37
+ */
38
+ export function resolveEnvSubstitution(value, env) {
39
+ return value.replace(/\$\{([A-Z_][A-Z0-9_]*)\}/g, (_match, name) => {
40
+ const v = env[name];
41
+ if (v === undefined) {
42
+ throw new Error(`Environment variable '\${${name}}' referenced in connectors.json is not set.`);
43
+ }
44
+ return v;
45
+ });
46
+ }
47
+ /** Walk a config tree and resolve `${NAME}` substitutions on every string leaf. */
48
+ function resolveConfigEnv(value, env) {
49
+ if (typeof value === "string")
50
+ return resolveEnvSubstitution(value, env);
51
+ if (Array.isArray(value))
52
+ return value.map((v) => resolveConfigEnv(v, env));
53
+ if (value !== null && typeof value === "object") {
54
+ const out = {};
55
+ for (const [k, v] of Object.entries(value))
56
+ out[k] = resolveConfigEnv(v, env);
57
+ return out;
58
+ }
59
+ return value;
60
+ }
61
+ /**
62
+ * Read + parse `connectors.json` at the given path. Validate the top-
63
+ * level shape. Resolve `${VAR}` substitutions. Instantiate each entry
64
+ * via the closed-set class registry's `fromConfig` factory (when
65
+ * present; entries pointing at classes without one get a clear error
66
+ * and a `null` instance, surfaced via `errors`).
67
+ *
68
+ * Missing file → returns `{connectors: [], errors: []}` (graceful: not
69
+ * every deployment uses external connectors). Malformed JSON or
70
+ * structural errors → returned in `errors[]` for the bootstrap caller
71
+ * to log and refuse to start, or for the lint surface to consume.
72
+ */
73
+ export function loadConnectorsConfig(opts) {
74
+ const env = opts.env ?? process.env;
75
+ let raw;
76
+ try {
77
+ raw = readFileSync(opts.path, "utf8");
78
+ }
79
+ catch (err) {
80
+ const code = err.code;
81
+ if (code === "ENOENT")
82
+ return { connectors: [], errors: [] };
83
+ return { connectors: [], errors: [`connectors.json: failed to read '${opts.path}': ${err.message}`] };
84
+ }
85
+ let parsed;
86
+ try {
87
+ parsed = JSON.parse(raw);
88
+ }
89
+ catch (err) {
90
+ return { connectors: [], errors: [`connectors.json: malformed JSON in '${opts.path}': ${err.message}`] };
91
+ }
92
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
93
+ return { connectors: [], errors: [`connectors.json: top-level must be an object mapping connector names to {class, config}. Got: ${Array.isArray(parsed) ? "array" : typeof parsed}.`] };
94
+ }
95
+ const connectors = [];
96
+ const errors = [];
97
+ for (const [name, entry] of Object.entries(parsed)) {
98
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
99
+ errors.push(`connectors.json: entry '${name}' must be an object with {class, config}. Got: ${Array.isArray(entry) ? "array" : entry === null ? "null" : typeof entry}.`);
100
+ continue;
101
+ }
102
+ const obj = entry;
103
+ const className = obj["class"];
104
+ if (typeof className !== "string" || className === "") {
105
+ errors.push(`connectors.json: entry '${name}' is missing required string field 'class'.`);
106
+ continue;
107
+ }
108
+ const rawConfig = obj["config"] ?? {};
109
+ if (rawConfig === null || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
110
+ errors.push(`connectors.json: entry '${name}' field 'config' must be an object. Got: ${Array.isArray(rawConfig) ? "array" : typeof rawConfig}.`);
111
+ continue;
112
+ }
113
+ let resolvedConfig;
114
+ try {
115
+ resolvedConfig = resolveConfigEnv(rawConfig, env);
116
+ }
117
+ catch (err) {
118
+ errors.push(`connectors.json: entry '${name}': ${err.message}`);
119
+ continue;
120
+ }
121
+ const classEntry = KNOWN_CONNECTOR_CLASSES.get(className);
122
+ if (classEntry === undefined) {
123
+ errors.push(`connectors.json: entry '${name}' references unknown connector class '${className}'. Known classes: ${listKnownConnectorClasses().join(", ")}.`);
124
+ continue;
125
+ }
126
+ let instance;
127
+ if (classEntry.fromConfig !== undefined) {
128
+ try {
129
+ instance = classEntry.fromConfig(resolvedConfig);
130
+ }
131
+ catch (err) {
132
+ errors.push(`connectors.json: entry '${name}' failed to instantiate '${className}': ${err.message}`);
133
+ continue;
134
+ }
135
+ }
136
+ else {
137
+ // No fromConfig means the class can't be JSON-instantiated. v0.4.0
138
+ // ships with CallbackMcpConnector in this state — wire it via
139
+ // embedder code. v0.4.1 adds RemoteMcpConnector with a fromConfig.
140
+ errors.push(`connectors.json: entry '${name}' uses class '${className}' which doesn't support configuration via connectors.json. Wire this connector via embedder code instead, or use a JSON-instantiable class.`);
141
+ continue;
142
+ }
143
+ connectors.push({ name, className, config: resolvedConfig, instance });
144
+ }
145
+ return { connectors, errors };
146
+ }
147
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/connectors/config.ts"],"names":[],"mappings":"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"}
package/dist/filters.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** The names of every registered filter. Lint's `unknown-filter` rule consults this. */
2
- export declare const KNOWN_FILTERS: readonly ["url", "shell", "json", "json_parse", "trim", "length"];
2
+ export declare const KNOWN_FILTERS: readonly ["url", "shell", "json", "trim", "length"];
3
3
  export type KnownFilter = (typeof KNOWN_FILTERS)[number];
4
4
  /**
5
5
  * Apply a named pipe filter to a string value. Filters supported in v1:
@@ -15,6 +15,11 @@ export type KnownFilter = (typeof KNOWN_FILTERS)[number];
15
15
  *
16
16
  * Unknown filter names throw — typos are caught at compile time when the value
17
17
  * is already resolved, or at runtime for ambient refs.
18
+ *
19
+ * v0.3.3 — `|json_parse` filter removed. Use `$ json_parse $(VAR) -> P` op
20
+ * instead, which binds the parsed structure so `$(P.field)` works via
21
+ * resolveRef's dotted descent. Filter was string-in/string-out which couldn't
22
+ * propagate parsed shape through `.field` access.
18
23
  */
19
24
  export declare function applyFilter(value: string, filter: string): string;
20
25
  //# sourceMappingURL=filters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,eAAO,MAAM,aAAa,mEAAoE,CAAC;AAC/F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAYzD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAwCjE"}
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,eAAO,MAAM,aAAa,qDAAsD,CAAC;AACjF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAYzD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAwBjE"}
package/dist/filters.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Pipe-filter implementations. `$(NAME|filter)` syntax dispatches here.
2
2
  /** The names of every registered filter. Lint's `unknown-filter` rule consults this. */
3
- export const KNOWN_FILTERS = ["url", "shell", "json", "json_parse", "trim", "length"];
3
+ export const KNOWN_FILTERS = ["url", "shell", "json", "trim", "length"];
4
4
  //
5
5
  // Adding a new filter:
6
6
  // 1. Add a case in `applyFilter` below.
@@ -25,6 +25,11 @@ export const KNOWN_FILTERS = ["url", "shell", "json", "json_parse", "trim", "len
25
25
  *
26
26
  * Unknown filter names throw — typos are caught at compile time when the value
27
27
  * is already resolved, or at runtime for ambient refs.
28
+ *
29
+ * v0.3.3 — `|json_parse` filter removed. Use `$ json_parse $(VAR) -> P` op
30
+ * instead, which binds the parsed structure so `$(P.field)` works via
31
+ * resolveRef's dotted descent. Filter was string-in/string-out which couldn't
32
+ * propagate parsed shape through `.field` access.
28
33
  */
29
34
  export function applyFilter(value, filter) {
30
35
  switch (filter) {
@@ -34,21 +39,6 @@ export function applyFilter(value, filter) {
34
39
  return `'${value.replace(/'/g, "'\\''")}'`;
35
40
  case "json":
36
41
  return JSON.stringify(value);
37
- case "json_parse": {
38
- // v0.3.2: sibling to `|json` (stringify). Parses the input as JSON and
39
- // re-stringifies — round-trip for valid JSON, throws for malformed.
40
- // Useful chain with `|length` for array counting + as a validation gate
41
- // before downstream string ops. Field-access on parsed structures is a
42
- // separate concern (parsed value isn't propagated through string-in/
43
- // string-out filter signature). See $ json_parse intercept (deferred).
44
- try {
45
- const parsed = JSON.parse(value);
46
- return JSON.stringify(parsed);
47
- }
48
- catch (err) {
49
- throw new Error(`\`|json_parse\` filter: input is not valid JSON. Got: '${value.slice(0, 40)}${value.length > 40 ? "..." : ""}' — ${err.message}`);
50
- }
51
- }
52
42
  case "trim":
53
43
  return value.trim();
54
44
  case "length": {
@@ -65,7 +55,7 @@ export function applyFilter(value, filter) {
65
55
  return String(value.length);
66
56
  }
67
57
  default:
68
- throw new Error(`Unknown filter '${filter}' — supported: url, shell, json, json_parse, trim, length`);
58
+ throw new Error(`Unknown filter '${filter}' — supported: url, shell, json, trim, length`);
69
59
  }
70
60
  }
71
61
  //# sourceMappingURL=filters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filters.js","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE,wFAAwF;AACxF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAE/F,EAAE;AACF,uBAAuB;AACvB,0CAA0C;AAC1C,kFAAkF;AAClF,2EAA2E;AAC3E,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AAEzE;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,MAAc;IACvD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,KAAK;YACR,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACnC,KAAK,OAAO;YACV,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;QAC7C,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,uEAAuE;YACvE,oEAAoE;YACpE,wEAAwE;YACxE,uEAAuE;YACvE,qEAAqE;YACrE,uEAAuE;YACvE,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAY,CAAC;gBAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACb,0DAA0D,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAQ,GAAa,CAAC,OAAO,EAAE,CAC7I,CAAC;YACJ,CAAC;QACH,CAAC;QACD,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,8DAA8D;YAC9D,qEAAqE;YACrE,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAY,CAAC;gBAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;oBAAE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,wDAAwD;YAC1D,CAAC;YACD,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,2DAA2D,CAAC,CAAC;IAC1G,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"filters.js","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE,wFAAwF;AACxF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAEjF,EAAE;AACF,uBAAuB;AACvB,0CAA0C;AAC1C,kFAAkF;AAClF,2EAA2E;AAC3E,gFAAgF;AAChF,iDAAiD;AACjD,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AAEzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,MAAc;IACvD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,KAAK;YACR,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACnC,KAAK,OAAO;YACV,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;QAC7C,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,8DAA8D;YAC9D,qEAAqE;YACrE,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAY,CAAC;gBAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;oBAAE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,wDAAwD;YAC1D,CAAC;YACD,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,+CAA+C,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"help-content.d.ts","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAslBzD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,cAAc,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB"}
1
+ {"version":3,"file":"help-content.d.ts","sourceRoot":"","sources":["../src/help-content.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAumBzD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,cAAc,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB"}
@@ -131,6 +131,23 @@ Calls an MCP tool. Without an explicit connector prefix, routes to the
131
131
  another stored skill end-to-end without requiring an MCP connector — pass
132
132
  input vars as additional kwargs.
133
133
 
134
+ **Built-in (v0.3.3):** \`$ json_parse $(VAR) -> P\` parses the input as JSON
135
+ and binds the structured value to \`P\`. Dotted descent via \`$(P.field)\`
136
+ works in conditions and emit — no filter+field grammar gymnastics.
137
+
138
+ \`\`\`
139
+ # Vars: PAYLOAD={"status":"ok","count":3}
140
+
141
+ read:
142
+ $ json_parse $(PAYLOAD) -> P
143
+ if $(P.status) == "ok" and $(P.count) > "0":
144
+ ! processing $(P.count) items
145
+ \`\`\`
146
+
147
+ Throws on malformed JSON (caught by \`else:\` / \`# OnError:\`). Replaces
148
+ the v0.3.2 \`|json_parse\` filter, which couldn't propagate parsed
149
+ structure through \`.field\` access.
150
+
134
151
  ### \`~\` — LocalModel call
135
152
 
136
153
  \`\`\`
@@ -222,7 +239,6 @@ Apply on \`$(VAR|filter)\` references; chain left-to-right.
222
239
  | \`url\` | encodeURIComponent |
223
240
  | \`shell\` | POSIX single-quote escape |
224
241
  | \`json\` | JSON.stringify |
225
- | \`json_parse\` | Parse JSON string; round-trips for valid JSON, throws for malformed (v0.3.2). Pairs with \`|length\` for array counts on JSON arrays. |
226
242
  | \`trim\` | Whitespace trim |
227
243
  | \`length\` | Array element count or string char count (v0.2.5) |
228
244
 
@@ -597,6 +613,7 @@ Three tiers per ERD §3:
597
613
  - \`duplicate-skill-name\` — name collides with an existing stored skill
598
614
  - \`plugin-collision\` — placeholder for v1.x plugin-loader name conflicts
599
615
  - \`deferred-skill-reference\` — composition ref (\`&\` / \`$ execute_skill\` / \`# Templates:\`) targets a skill not currently in the SkillStore; resolution deferred to execute time (v0.3.1+). Confirms the forward-reference path is engaged; clears once the target is stored.
616
+ - \`unparsed-json-field-access\` — op text contains \`$(VAR|json_parse).field\`; the \`|json_parse\` filter was removed in v0.3.3. Replace with \`$ json_parse $(VAR) -> P\` then \`$(P.field)\`.
600
617
 
601
618
  \`compile_skill({source})\` runs the full lint preflight and reports
602
619
  findings in the \`errors\` + \`warnings\` arrays. \`lint_skill({source})\`
@@ -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
@@ -78,6 +78,22 @@ export interface LintOptions {
78
78
  * warning fires.
79
79
  */
80
80
  enableUnsafeShell?: boolean;
81
+ /**
82
+ * Names of registered MCP connector instances (v0.4.0). When provided,
83
+ * `unknown-connector` lint rule fires tier-1 on `$ name.tool` refs to
84
+ * names not in the list. When undefined, the rule is silent (caller
85
+ * doesn't know what's wired). Derived from `registry` if only the
86
+ * registry is provided.
87
+ */
88
+ mcpConnectorNames?: string[];
89
+ /**
90
+ * Errors from `connectors.json` load pass (v0.4.0). When provided,
91
+ * `unknown-connector-class` lint rule re-surfaces the subset of these
92
+ * about unknown class names so cold-author tooling sees them through
93
+ * the lint API. Other config errors flow through `parse-error`-style
94
+ * surfacing in the bootstrap result.
95
+ */
96
+ connectorConfigErrors?: string[];
81
97
  }
82
98
  interface LintContext {
83
99
  parsed: ParsedSkill;
@@ -88,6 +104,8 @@ interface LintContext {
88
104
  hasSkillStore: boolean;
89
105
  callSite: "cli" | "api" | "compile-preflight";
90
106
  enableUnsafeShell: boolean | undefined;
107
+ mcpConnectorNames: string[] | undefined;
108
+ connectorConfigErrors: string[];
91
109
  }
92
110
  export interface LintRule {
93
111
  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;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;AAspCD,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;IAC5B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC;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;IACvC,iBAAiB,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACxC,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;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,CAmCrF;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,CAkC1E;AAED,6HAA6H;AAC7H,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAW3D;AA2wCD,mFAAmF;AACnF,wBAAgB,SAAS,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAElE"}
package/dist/lint.js CHANGED
@@ -10,6 +10,8 @@ export async function lint(source, options) {
10
10
  hasSkillStore: options?.skillStore !== undefined,
11
11
  callSite: options?.callSite ?? "api",
12
12
  enableUnsafeShell: options?.enableUnsafeShell,
13
+ mcpConnectorNames: options?.mcpConnectorNames ?? collectMcpConnectorNamesFromRegistry(options?.registry),
14
+ connectorConfigErrors: options?.connectorConfigErrors ?? [],
13
15
  };
14
16
  const findings = [];
15
17
  for (const rule of RULES) {
@@ -43,6 +45,8 @@ export function lintSync(source, options) {
43
45
  hasSkillStore: options?.skillStore !== undefined,
44
46
  callSite: options?.callSite ?? "api",
45
47
  enableUnsafeShell: options?.enableUnsafeShell,
48
+ mcpConnectorNames: options?.mcpConnectorNames ?? collectMcpConnectorNamesFromRegistry(options?.registry),
49
+ connectorConfigErrors: options?.connectorConfigErrors ?? [],
46
50
  };
47
51
  const findings = [];
48
52
  for (const rule of RULES) {
@@ -84,9 +88,23 @@ export function formatLintResult(result) {
84
88
  const PARSE_ERROR = {
85
89
  id: "parse-error",
86
90
  severity: "error",
87
- description: "Any syntax error collected by the parser.",
91
+ description: "Any syntax error collected by the parser (catch-all for shapes not owned by a more specific tier-1 rule).",
88
92
  remediation: "Fix the grammar error per the message. Check op syntax, header form, indent levels.",
89
- check: (ctx) => ctx.parsed.parseErrors.map((msg) => ({
93
+ check: (ctx) => ctx.parsed.parseErrors
94
+ // v0.3.4: skip messages a more specific tier-1 rule already owns —
95
+ // pre-fix both this rule and the specific one fired identical bodies,
96
+ // doubling noise. Each pattern below mirrors the corresponding tier-1
97
+ // rule's filter regex; PARSE_ERROR stays catch-all for unowned shapes
98
+ // (header issues, foreach/needs malformed, etc.).
99
+ //
100
+ // Owning rules:
101
+ // invalid-conditional-syntax → Unsupported condition
102
+ // single-equals → `=` is not valid in a condition
103
+ // malformed-op-grammar → Malformed `<op>`
104
+ // reserved-keyword → is a reserved keyword
105
+ // indentation → Tab characters / Mid-block indent change
106
+ .filter((msg) => !/Unsupported condition|`=` is not valid in a condition|Malformed `[~>&$@!?]|is a reserved keyword|Tab characters in indentation|Mid-block indent change/.test(msg))
107
+ .map((msg) => ({
90
108
  rule: "parse-error",
91
109
  severity: "error",
92
110
  message: msg,
@@ -306,8 +324,8 @@ const MALFORMED_OP_GRAMMAR = {
306
324
  const INVALID_CONDITIONAL_SYNTAX = {
307
325
  id: "invalid-conditional-syntax",
308
326
  severity: "error",
309
- description: "An `if:` / `elif:` condition uses syntax outside the v1 narrow grammar (truthy / `==` / `!=` / `in` / `not in`).",
310
- remediation: "Restructure the condition to use a supported shape. v1 explicitly excludes AND/OR, numeric comparison, and defined-checks.",
327
+ description: "An `if:` / `elif:` condition uses syntax outside the supported grammar.",
328
+ remediation: "Use a supported shape: truthy `$(REF)`; `$(REF) ==/!=/</>/<=/>= \"literal\"` or `$(REF) ==/!=/</>/<=/>= $(REF)`; `$(REF) (not) in $(REF)`; composable with `and` / `or` / `not` and parens. Filters + dotted-field allowed inside `$(REF)`. For field access on parsed JSON, use `$ json_parse $(VAR) -> P` then `$(P.field)`.",
311
329
  check: (ctx) => ctx.parsed.parseErrors
312
330
  .filter((msg) => /Unsupported condition/.test(msg))
313
331
  .map((msg) => ({
@@ -426,6 +444,62 @@ const UNKNOWN_RETRIEVAL_ARG = {
426
444
  return findings;
427
445
  },
428
446
  };
447
+ // v0.4.0 — `$ name.tool` references a connector name not registered.
448
+ // `connectorNames` is the authoritative list from the Registry (passed
449
+ // through LintOptions); when undefined (caller doesn't know what's
450
+ // wired) the rule is silent rather than risk false positives.
451
+ const UNKNOWN_CONNECTOR = {
452
+ id: "unknown-connector",
453
+ severity: "error",
454
+ description: "A `$ name.tool` op references a connector name that's not registered. Either the name is misspelled or `connectors.json` is missing an entry.",
455
+ remediation: "Check the connector name against `connectors.json` (or whatever wired the registry). Either fix the spelling or add the entry. `runtime_capabilities()` lists the names currently wired.",
456
+ check: (ctx) => {
457
+ if (ctx.mcpConnectorNames === undefined)
458
+ return [];
459
+ const known = new Set(ctx.mcpConnectorNames);
460
+ const findings = [];
461
+ const reported = new Set();
462
+ for (const [targetName, target] of ctx.parsed.targets) {
463
+ walkOps(target.ops, (op) => {
464
+ if (op.kind !== "$" || op.mcpConnector === undefined)
465
+ return;
466
+ const ref = op.mcpConnector;
467
+ if (known.has(ref))
468
+ return;
469
+ const key = `${targetName}:${ref}`;
470
+ if (reported.has(key))
471
+ return;
472
+ reported.add(key);
473
+ findings.push({
474
+ rule: "unknown-connector",
475
+ severity: "error",
476
+ message: `\`$ ${ref}.<tool>\` in target '${targetName}' references unknown connector '${ref}'. Wired connectors: ${known.size === 0 ? "(none)" : [...known].join(", ")}.`,
477
+ block: targetName,
478
+ extras: { referenced_connector: ref },
479
+ });
480
+ });
481
+ }
482
+ return findings;
483
+ },
484
+ };
485
+ // v0.4.0 — `connectors.json` declares `class: "Foo"` where `Foo` is not
486
+ // in the closed-set class registry. The loader catches this at startup
487
+ // and surfaces via `connectorConfigErrors`; this rule re-surfaces the
488
+ // subset that's class-related into the lint diagnostic stream so cold-
489
+ // author tooling (compile_skill / lint_skill MCP) sees them.
490
+ const UNKNOWN_CONNECTOR_CLASS = {
491
+ id: "unknown-connector-class",
492
+ severity: "error",
493
+ description: "`connectors.json` references a connector class that's not in the closed-set class registry (v0.4.0).",
494
+ remediation: "Use one of the known classes (see `runtime_capabilities()` for the list shipped in this runtime). Plugin-style runtime-arbitrary class loading is deliberately out of scope; future classes ship via CHANGELOG-tracked additions to the registry.",
495
+ check: (ctx) => ctx.connectorConfigErrors
496
+ .filter((msg) => /unknown connector class/.test(msg))
497
+ .map((msg) => ({
498
+ rule: "unknown-connector-class",
499
+ severity: "error",
500
+ message: msg,
501
+ })),
502
+ };
429
503
  // v0.3.1: tier-3 advisory that fires alongside the demoted tier-2
430
504
  // unknown-skill-reference / unknown-template-reference. Confirms the
431
505
  // deferred-resolution path is engaged so cold authors can distinguish
@@ -476,6 +550,57 @@ const DEFERRED_SKILL_REFERENCE = {
476
550
  return findings;
477
551
  },
478
552
  };
553
+ // v0.3.3 — `|json_parse` filter removed; the shape `$(VAR|json_parse).field`
554
+ // is statically detectable. Fire a tier-3 advisory pointing at the new
555
+ // `$ json_parse $(VAR) -> P` op so authors who carried the v0.3.2 pattern
556
+ // forward get a direct remediation instead of the generic "unknown filter"
557
+ // error from applyFilter.
558
+ const UNPARSED_JSON_FIELD_ACCESS = {
559
+ id: "unparsed-json-field-access",
560
+ severity: "info",
561
+ description: "Op text contains `$(VAR|json_parse).field` — the `|json_parse` filter was removed in v0.3.3.",
562
+ remediation: "Replace with `$ json_parse $(VAR) -> P` then access `$(P.field)`. The op binds the parsed structure so dotted descent works in conditions + emit. See help({topic: \"ops\"}).",
563
+ check: (ctx) => {
564
+ const findings = [];
565
+ const BAD = /\$\([^)]*\|\s*json_parse\s*\)\.([A-Za-z_]\w*)/;
566
+ const reportIfMatches = (text, targetName) => {
567
+ const m = BAD.exec(text);
568
+ if (m === null)
569
+ return;
570
+ findings.push({
571
+ rule: "unparsed-json-field-access",
572
+ severity: "info",
573
+ message: `In target '${targetName}': \`$(...|json_parse).${m[1]}\` — the \`|json_parse\` filter was removed in v0.3.3. Replace with \`$ json_parse $(VAR) -> P\` then \`$(P.${m[1]})\`.`,
574
+ block: targetName,
575
+ });
576
+ };
577
+ for (const [targetName, target] of ctx.parsed.targets) {
578
+ walkOps(target.ops, (op) => {
579
+ if (op.body !== undefined)
580
+ reportIfMatches(op.body, targetName);
581
+ if (op.foreachList !== undefined)
582
+ reportIfMatches(op.foreachList, targetName);
583
+ if (op.ifBranches !== undefined) {
584
+ for (const b of op.ifBranches)
585
+ reportIfMatches(b.cond, targetName);
586
+ }
587
+ if (op.retrievalParams !== undefined) {
588
+ reportIfMatches(op.retrievalParams.query, targetName);
589
+ for (const v of Object.values(op.retrievalParams.extra))
590
+ reportIfMatches(String(v), targetName);
591
+ }
592
+ if (op.localModelParams !== undefined) {
593
+ reportIfMatches(op.localModelParams.prompt, targetName);
594
+ }
595
+ if (op.ampParams !== undefined) {
596
+ for (const v of Object.values(op.ampParams.args))
597
+ reportIfMatches(v, targetName);
598
+ }
599
+ });
600
+ }
601
+ return findings;
602
+ },
603
+ };
479
604
  // v0.2.12 Bug 17. `# Templates:` refs were not lint-validated despite
480
605
  // `# OnError:` having compile-time validation (since v0.2.10).
481
606
  // v0.3.1: demoted tier-1 → tier-2 alongside unknown-skill-reference.
@@ -1244,6 +1369,8 @@ const RULES = [
1244
1369
  UNKNOWN_TEMPLATE_REFERENCE,
1245
1370
  DEFERRED_SKILL_REFERENCE,
1246
1371
  UNKNOWN_RETRIEVAL_ARG,
1372
+ UNKNOWN_CONNECTOR,
1373
+ UNKNOWN_CONNECTOR_CLASS,
1247
1374
  UNINITIALIZED_APPEND,
1248
1375
  FOREACH_LOCAL_ACCUMULATOR_TARGET,
1249
1376
  APPEND_TO_NON_LIST,
@@ -1267,6 +1394,7 @@ const RULES = [
1267
1394
  NO_DEFAULT_TARGET,
1268
1395
  DUPLICATE_SKILL_NAME,
1269
1396
  PLUGIN_COLLISION,
1397
+ UNPARSED_JSON_FIELD_ACCESS,
1270
1398
  ];
1271
1399
  /** Read-only view of the rule registry — for tooling that introspects v1 rules. */
1272
1400
  export function listRules() {
@@ -1397,6 +1525,11 @@ function collectClassesFromRegistry(registry) {
1397
1525
  ...registry.listMcpConnectorClasses(),
1398
1526
  ];
1399
1527
  }
1528
+ function collectMcpConnectorNamesFromRegistry(registry) {
1529
+ if (registry === undefined)
1530
+ return undefined;
1531
+ return registry.listMcpConnectors().map((e) => e.name);
1532
+ }
1400
1533
  function buildFeatureSet(classes) {
1401
1534
  const provided = new Set();
1402
1535
  for (const Ctor of classes) {