@sweny-ai/core 0.1.77 → 0.1.78
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/dist/browser.d.ts +11 -5
- package/dist/browser.js +11 -11
- package/dist/claude.d.ts +16 -0
- package/dist/claude.js +98 -9
- package/dist/cli/config.d.ts +22 -0
- package/dist/cli/config.js +56 -9
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +56 -57
- package/dist/cli/marketplace.js +35 -9
- package/dist/cli/publish.d.ts +10 -0
- package/dist/cli/publish.js +22 -14
- package/dist/loader.d.ts +40 -0
- package/dist/loader.js +73 -0
- package/dist/mcp-catalog.d.ts +47 -0
- package/dist/mcp-catalog.js +304 -0
- package/dist/mcp.d.ts +6 -9
- package/dist/mcp.js +24 -314
- package/dist/schema.d.ts +183 -151
- package/dist/schema.js +49 -7
- package/dist/skills/custom-loader.d.ts +40 -0
- package/dist/skills/custom-loader.js +107 -32
- package/dist/skills/index.d.ts +13 -1
- package/dist/skills/index.js +12 -3
- package/dist/studio.js +4 -1
- package/dist/templates.d.ts +29 -8
- package/dist/templates.js +30 -20
- package/dist/types.d.ts +12 -0
- package/dist/verify.js +42 -4
- package/package.json +8 -4
- package/dist/__tests__/cascade.test.d.ts +0 -11
- package/dist/__tests__/cascade.test.js +0 -452
- package/dist/__tests__/claude.test.d.ts +0 -1
- package/dist/__tests__/claude.test.js +0 -427
- package/dist/__tests__/custom-loader.test.d.ts +0 -1
- package/dist/__tests__/custom-loader.test.js +0 -116
- package/dist/__tests__/executor.test.d.ts +0 -1
- package/dist/__tests__/executor.test.js +0 -1635
- package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/datadog.integration.test.js +0 -23
- package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
- package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/github.integration.test.js +0 -37
- package/dist/__tests__/integration/harness.d.ts +0 -24
- package/dist/__tests__/integration/harness.js +0 -34
- package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/linear.integration.test.js +0 -15
- package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sentry.integration.test.js +0 -20
- package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/slack.integration.test.js +0 -22
- package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sources.integration.test.js +0 -123
- package/dist/__tests__/publish.test.d.ts +0 -1
- package/dist/__tests__/publish.test.js +0 -180
- package/dist/__tests__/requires.test.d.ts +0 -1
- package/dist/__tests__/requires.test.js +0 -82
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -213
- package/dist/__tests__/schema.test.d.ts +0 -1
- package/dist/__tests__/schema.test.js +0 -600
- package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
- package/dist/__tests__/skill-config-integration.test.js +0 -204
- package/dist/__tests__/skills-index.test.d.ts +0 -1
- package/dist/__tests__/skills-index.test.js +0 -119
- package/dist/__tests__/skills.test.d.ts +0 -1
- package/dist/__tests__/skills.test.js +0 -565
- package/dist/__tests__/spec-conformance.test.d.ts +0 -18
- package/dist/__tests__/spec-conformance.test.js +0 -1084
- package/dist/__tests__/studio.test.d.ts +0 -1
- package/dist/__tests__/studio.test.js +0 -403
- package/dist/__tests__/templates.test.d.ts +0 -1
- package/dist/__tests__/templates.test.js +0 -49
- package/dist/__tests__/testing.test.d.ts +0 -1
- package/dist/__tests__/testing.test.js +0 -242
- package/dist/__tests__/verify.test.d.ts +0 -1
- package/dist/__tests__/verify.test.js +0 -517
- package/dist/__tests__/version-consistency.test.d.ts +0 -1
- package/dist/__tests__/version-consistency.test.js +0 -312
- package/dist/__tests__/workflow-builder.test.d.ts +0 -1
- package/dist/__tests__/workflow-builder.test.js +0 -110
- package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
- package/dist/__tests__/workflow-yaml.test.js +0 -201
- package/dist/cli/__tests__/diagram.test.d.ts +0 -1
- package/dist/cli/__tests__/diagram.test.js +0 -225
- package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
- package/dist/cli/__tests__/report-cloud.test.js +0 -165
- package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
- package/dist/cli/__tests__/upgrade.test.js +0 -317
- package/dist/cli/__tests__/version-check.test.d.ts +0 -1
- package/dist/cli/__tests__/version-check.test.js +0 -209
- package/dist/cli/config.test.d.ts +0 -1
- package/dist/cli/config.test.js +0 -200
- package/dist/cli/credentials.test.d.ts +0 -1
- package/dist/cli/credentials.test.js +0 -172
- package/dist/cli/e2e.test.d.ts +0 -1
- package/dist/cli/e2e.test.js +0 -690
- package/dist/cli/marketplace.test.d.ts +0 -1
- package/dist/cli/marketplace.test.js +0 -355
- package/dist/cli/new.test.d.ts +0 -1
- package/dist/cli/new.test.js +0 -1043
- package/dist/cli/output.test.d.ts +0 -1
- package/dist/cli/output.test.js +0 -180
- package/dist/cli/publish.test.d.ts +0 -1
- package/dist/cli/publish.test.js +0 -610
- package/dist/cli/renderer.test.d.ts +0 -1
- package/dist/cli/renderer.test.js +0 -302
- package/dist/executor.test.d.ts +0 -1
- package/dist/executor.test.js +0 -618
- package/dist/mcp.test.d.ts +0 -1
- package/dist/mcp.test.js +0 -735
- package/dist/mermaid.test.d.ts +0 -1
- package/dist/mermaid.test.js +0 -256
- package/dist/sources.test.d.ts +0 -1
- package/dist/sources.test.js +0 -265
- package/dist/sources.validation.test.d.ts +0 -12
- package/dist/sources.validation.test.js +0 -380
- package/dist/workflow-builder.test.d.ts +0 -1
- package/dist/workflow-builder.test.js +0 -123
package/dist/browser.d.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser-safe entry point for @sweny-ai/core
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Exports only APIs that bundle cleanly in browser environments (Vite,
|
|
5
|
+
* webpack, Astro, etc.). Anything that pulls `node:fs`, `node:path`, or the
|
|
6
|
+
* Claude Code SDK lives in the Node entry (`@sweny-ai/core`).
|
|
7
|
+
*
|
|
8
|
+
* Not exported from here:
|
|
9
|
+
* - `ClaudeClient` — depends on `@anthropic-ai/claude-agent-sdk` (Node-only).
|
|
10
|
+
* - `execute` / `ExecuteOptions` — `executor.ts` → `source-resolver.ts`
|
|
11
|
+
* imports `node:fs/promises` for URL/file Source fetching. Import from
|
|
12
|
+
* the Node entry when you need to run a workflow.
|
|
13
|
+
* - `buildWorkflow` / `refineWorkflow` — depend on the Claude interface.
|
|
14
|
+
* - Testing helpers — `MockClaude`, `createFileSkill` use `node:fs`.
|
|
7
15
|
*/
|
|
8
16
|
export type { Skill, SkillCategory, Tool, ToolContext, ConfigField, JSONSchema, Workflow, Node, Edge, NodeResult, ToolCall, ExecutionEvent, Observer, Claude, Logger, } from "./types.js";
|
|
9
17
|
export { consoleLogger } from "./types.js";
|
|
10
|
-
export { execute } from "./executor.js";
|
|
11
|
-
export type { ExecuteOptions } from "./executor.js";
|
|
12
18
|
export { github, linear, slack, sentry, datadog, notification, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredBuiltinSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
13
19
|
export type { SkillValidationResult } from "./skills/index.js";
|
|
14
20
|
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
package/dist/browser.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser-safe entry point for @sweny-ai/core
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Exports only APIs that bundle cleanly in browser environments (Vite,
|
|
5
|
+
* webpack, Astro, etc.). Anything that pulls `node:fs`, `node:path`, or the
|
|
6
|
+
* Claude Code SDK lives in the Node entry (`@sweny-ai/core`).
|
|
7
|
+
*
|
|
8
|
+
* Not exported from here:
|
|
9
|
+
* - `ClaudeClient` — depends on `@anthropic-ai/claude-agent-sdk` (Node-only).
|
|
10
|
+
* - `execute` / `ExecuteOptions` — `executor.ts` → `source-resolver.ts`
|
|
11
|
+
* imports `node:fs/promises` for URL/file Source fetching. Import from
|
|
12
|
+
* the Node entry when you need to run a workflow.
|
|
13
|
+
* - `buildWorkflow` / `refineWorkflow` — depend on the Claude interface.
|
|
14
|
+
* - Testing helpers — `MockClaude`, `createFileSkill` use `node:fs`.
|
|
7
15
|
*/
|
|
8
16
|
export { consoleLogger } from "./types.js";
|
|
9
|
-
// Executor
|
|
10
|
-
export { execute } from "./executor.js";
|
|
11
17
|
// Skills (browser-safe — no filesystem access, no `process.env` reads)
|
|
12
18
|
export { github, linear, slack, sentry, datadog, notification, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredBuiltinSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
13
19
|
// Schema & validation
|
|
14
20
|
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
|
15
21
|
// Studio adapter
|
|
16
22
|
export { workflowToFlow, flowToWorkflow, applyExecutionEvent, exportAsTypescript, getSkillCatalog } from "./studio.js";
|
|
17
|
-
// Workflow builder — NOT included in browser entry.
|
|
18
|
-
// buildWorkflow/refineWorkflow depend on the Claude interface (Node-only).
|
|
19
|
-
// Import from "@sweny-ai/core" in Node environments.
|
|
20
|
-
// Testing utilities — NOT included in browser entry.
|
|
21
|
-
// MockClaude and createFileSkill depend on node:fs.
|
|
22
|
-
// Import from "@sweny-ai/core/testing" in Node environments.
|
package/dist/claude.d.ts
CHANGED
|
@@ -58,3 +58,19 @@ export declare class ClaudeClient implements Claude {
|
|
|
58
58
|
context: Record<string, unknown>;
|
|
59
59
|
}): Promise<string>;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Best-effort recovery of typed output from a tool_result's `content`.
|
|
63
|
+
*
|
|
64
|
+
* The MCP protocol sends tool results as string content. Our in-process
|
|
65
|
+
* wrapper JSON-stringifies structured output before returning; external
|
|
66
|
+
* MCP servers generally do the same for JSON payloads. If the string
|
|
67
|
+
* looks like a JSON object or array and parses, return the parsed value
|
|
68
|
+
* so verify's output-path walks work against typed data.
|
|
69
|
+
*
|
|
70
|
+
* Raw strings are preserved verbatim. JSON-primitive strings (e.g. the
|
|
71
|
+
* literal four characters `"42"`) are intentionally NOT parsed — we
|
|
72
|
+
* cannot distinguish a tool that returned the number 42 (wrapper sends
|
|
73
|
+
* `"42"`) from a tool that returned the string "42" (wrapper also sends
|
|
74
|
+
* `"42"`). Preserving the string is safer than guessing.
|
|
75
|
+
*/
|
|
76
|
+
export declare function parseToolResultContent(content: unknown): unknown;
|
package/dist/claude.js
CHANGED
|
@@ -41,9 +41,28 @@ export class ClaudeClient {
|
|
|
41
41
|
}
|
|
42
42
|
async run(opts) {
|
|
43
43
|
const { instruction, context, tools, outputSchema, onProgress, maxTurns } = opts;
|
|
44
|
+
// Tool-call accounting (Fix #1).
|
|
45
|
+
//
|
|
46
|
+
// Stream-driven recording, correlated by tool_use_id:
|
|
47
|
+
// - `assistant` message with tool_use block → create a pending ToolCall
|
|
48
|
+
// and register it in `pendingByUseId` under its tool_use_id.
|
|
49
|
+
// - `user` message with tool_result block → look up by tool_use_id, set
|
|
50
|
+
// `status` from `is_error`, and recover the typed output by parsing
|
|
51
|
+
// the stringified `content` field (our wrapper JSON-stringifies it
|
|
52
|
+
// before returning; external MCP servers also return structured
|
|
53
|
+
// content).
|
|
54
|
+
//
|
|
55
|
+
// Keying by tool_use_id (not by tool name) avoids mis-pairing when
|
|
56
|
+
// Claude invokes the same tool in parallel — the self-review test
|
|
57
|
+
// `pairs parallel same-named in-process tool outputs correctly` shows
|
|
58
|
+
// why FIFO-by-name is wrong.
|
|
44
59
|
const toolCalls = [];
|
|
45
|
-
|
|
46
|
-
|
|
60
|
+
const pendingByUseId = new Map();
|
|
61
|
+
// Convert core tools to SDK MCP tools. The wrapper invokes the user's
|
|
62
|
+
// handler and returns `content` via the MCP transport. We do not push
|
|
63
|
+
// to toolCalls from the wrapper — the stream's tool_result is the sole
|
|
64
|
+
// signal of completion, which keeps pairing correct under parallelism.
|
|
65
|
+
const sdkTools = tools.map((t) => coreToolToSdkTool(t, this.defaultContext));
|
|
47
66
|
// Create in-process MCP server
|
|
48
67
|
const mcpServer = createSdkMcpServer({
|
|
49
68
|
name: "sweny-core",
|
|
@@ -96,19 +115,51 @@ export class ClaudeClient {
|
|
|
96
115
|
}
|
|
97
116
|
}
|
|
98
117
|
else if (message.type === "assistant") {
|
|
99
|
-
//
|
|
118
|
+
// Tool_use blocks start a ToolCall record. Status + output are
|
|
119
|
+
// filled in when the matching user tool_result arrives.
|
|
100
120
|
const am = message;
|
|
101
121
|
if (am.message?.content && Array.isArray(am.message.content)) {
|
|
102
122
|
for (const block of am.message.content) {
|
|
103
123
|
if (block.type === "tool_use") {
|
|
104
|
-
|
|
124
|
+
const call = {
|
|
105
125
|
tool: stripMcpPrefix(block.name ?? ""),
|
|
106
126
|
input: block.input,
|
|
107
|
-
}
|
|
127
|
+
};
|
|
128
|
+
toolCalls.push(call);
|
|
129
|
+
if (typeof block.id === "string")
|
|
130
|
+
pendingByUseId.set(block.id, call);
|
|
108
131
|
}
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
134
|
}
|
|
135
|
+
else if (message.type === "user") {
|
|
136
|
+
// Tool_result blocks close the loop. Pair to the pending ToolCall
|
|
137
|
+
// by tool_use_id — NOT by name, which would break parallel calls
|
|
138
|
+
// of the same tool (see regression test).
|
|
139
|
+
//
|
|
140
|
+
// Output handling is unified across in-process and external tools:
|
|
141
|
+
// our wrapper JSON-stringifies structured output before returning,
|
|
142
|
+
// and external MCP servers also send structured content as string.
|
|
143
|
+
// We parse the content back into a value so verify's output-path
|
|
144
|
+
// checks work against typed data.
|
|
145
|
+
const um = message;
|
|
146
|
+
if (um.message?.content && Array.isArray(um.message.content)) {
|
|
147
|
+
for (const block of um.message.content) {
|
|
148
|
+
if (block.type !== "tool_result" || typeof block.tool_use_id !== "string")
|
|
149
|
+
continue;
|
|
150
|
+
const call = pendingByUseId.get(block.tool_use_id);
|
|
151
|
+
if (!call)
|
|
152
|
+
continue;
|
|
153
|
+
// Delete after first match so a duplicate tool_result (SDK
|
|
154
|
+
// retry, malformed stream) cannot overwrite a completed call.
|
|
155
|
+
pendingByUseId.delete(block.tool_use_id);
|
|
156
|
+
const isError = block.is_error === true;
|
|
157
|
+
call.status = isError ? "error" : "success";
|
|
158
|
+
const parsed = parseToolResultContent(block.content);
|
|
159
|
+
call.output = isError ? { error: parsed } : parsed;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
112
163
|
else if (message.type === "result") {
|
|
113
164
|
const resultMsg = message;
|
|
114
165
|
if (resultMsg.subtype === "success" && "result" in resultMsg) {
|
|
@@ -247,22 +298,27 @@ export class ClaudeClient {
|
|
|
247
298
|
// ─── Tool conversion ────────────────────────────────────────────
|
|
248
299
|
/**
|
|
249
300
|
* Convert a core Tool to an SDK MCP tool definition.
|
|
250
|
-
*
|
|
301
|
+
*
|
|
302
|
+
* The wrapper runs the user's handler and returns the MCP `CallToolResult`.
|
|
303
|
+
* Output bookkeeping happens on the stream side (see the tool_result
|
|
304
|
+
* handler in `run()`), keyed by `tool_use_id` — never from inside the
|
|
305
|
+
* wrapper. Keeping the wrapper stateless means parallel calls to the same
|
|
306
|
+
* tool cannot mis-pair outputs.
|
|
251
307
|
*/
|
|
252
|
-
function coreToolToSdkTool(coreTool, defaultCtx
|
|
308
|
+
function coreToolToSdkTool(coreTool, defaultCtx) {
|
|
253
309
|
const zodShape = jsonSchemaToZodShape(coreTool.input_schema);
|
|
254
310
|
return sdkTool(coreTool.name, coreTool.description, zodShape, async (args) => {
|
|
255
311
|
try {
|
|
256
312
|
// The executor wraps handlers to inject ToolContext.
|
|
257
313
|
// When used standalone, defaultCtx is the fallback.
|
|
258
314
|
const output = await coreTool.handler(args, defaultCtx);
|
|
259
|
-
|
|
315
|
+
// JSON-stringify structured output so the tool_result handler can
|
|
316
|
+
// recover it via parseToolResultContent. Strings pass through.
|
|
260
317
|
return {
|
|
261
318
|
content: [{ type: "text", text: typeof output === "string" ? output : JSON.stringify(output) }],
|
|
262
319
|
};
|
|
263
320
|
}
|
|
264
321
|
catch (err) {
|
|
265
|
-
toolCalls.push({ tool: coreTool.name, input: args, output: { error: err.message } });
|
|
266
322
|
return {
|
|
267
323
|
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
268
324
|
isError: true,
|
|
@@ -270,6 +326,39 @@ function coreToolToSdkTool(coreTool, defaultCtx, toolCalls) {
|
|
|
270
326
|
}
|
|
271
327
|
});
|
|
272
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Best-effort recovery of typed output from a tool_result's `content`.
|
|
331
|
+
*
|
|
332
|
+
* The MCP protocol sends tool results as string content. Our in-process
|
|
333
|
+
* wrapper JSON-stringifies structured output before returning; external
|
|
334
|
+
* MCP servers generally do the same for JSON payloads. If the string
|
|
335
|
+
* looks like a JSON object or array and parses, return the parsed value
|
|
336
|
+
* so verify's output-path walks work against typed data.
|
|
337
|
+
*
|
|
338
|
+
* Raw strings are preserved verbatim. JSON-primitive strings (e.g. the
|
|
339
|
+
* literal four characters `"42"`) are intentionally NOT parsed — we
|
|
340
|
+
* cannot distinguish a tool that returned the number 42 (wrapper sends
|
|
341
|
+
* `"42"`) from a tool that returned the string "42" (wrapper also sends
|
|
342
|
+
* `"42"`). Preserving the string is safer than guessing.
|
|
343
|
+
*/
|
|
344
|
+
export function parseToolResultContent(content) {
|
|
345
|
+
if (typeof content !== "string")
|
|
346
|
+
return content;
|
|
347
|
+
const trimmed = content.trim();
|
|
348
|
+
if (trimmed.length === 0)
|
|
349
|
+
return content;
|
|
350
|
+
const first = trimmed[0];
|
|
351
|
+
// Only objects and arrays are unambiguous to parse. Strings, numbers,
|
|
352
|
+
// booleans, and null would all corrupt or discard information.
|
|
353
|
+
if (first !== "{" && first !== "[")
|
|
354
|
+
return content;
|
|
355
|
+
try {
|
|
356
|
+
return JSON.parse(trimmed);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return content;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
273
362
|
// ─── JSON Schema → Zod conversion ───────────────────────────────
|
|
274
363
|
/**
|
|
275
364
|
* Convert a JSON Schema object to a Zod raw shape for the agent SDK.
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -76,6 +76,28 @@ export declare function parseCliInputs(options: Record<string, unknown>, fileCon
|
|
|
76
76
|
*/
|
|
77
77
|
export declare const SUPPORTED_WORKSPACE_TOOLS: Set<string>;
|
|
78
78
|
export declare function validateInputs(config: CliConfig): string[];
|
|
79
|
+
/**
|
|
80
|
+
* Parse a value as a positive integer (>= 1).
|
|
81
|
+
*
|
|
82
|
+
* Returns:
|
|
83
|
+
* - the parsed integer when the input represents a positive finite one
|
|
84
|
+
* - the fallback when the input is null/undefined/empty (unset flag)
|
|
85
|
+
* - NaN for any malformed or non-positive input, so validateInputs can
|
|
86
|
+
* surface a field-specific error
|
|
87
|
+
*
|
|
88
|
+
* Name enforces the contract: non-positive values (0, -5) return NaN so
|
|
89
|
+
* validation rejects them loudly rather than quietly passing through as
|
|
90
|
+
* `0` and breaking downstream invariants.
|
|
91
|
+
*
|
|
92
|
+
* Why zero is rejected: every current caller (max-investigate-turns,
|
|
93
|
+
* max-implement-turns, cache-ttl) uses the result as a positive count or
|
|
94
|
+
* timeout. There is no "zero means disabled" semantics anywhere — a
|
|
95
|
+
* cache-ttl of 0 wouldn't disable cache, it would expire entries
|
|
96
|
+
* immediately, which is broken. If a future flag wants "0 = disabled",
|
|
97
|
+
* use a sibling helper (parseNonNegativeInt) rather than weakening this
|
|
98
|
+
* one's contract.
|
|
99
|
+
*/
|
|
100
|
+
export declare function parsePositiveInt(raw: unknown, fallback: number): number;
|
|
79
101
|
/**
|
|
80
102
|
* Returns non-fatal warnings about the configuration.
|
|
81
103
|
* These are surfaced before the spinner starts but do not abort the run.
|
package/dist/cli/config.js
CHANGED
|
@@ -116,8 +116,8 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
116
116
|
severityFocus: options.severityFocus || f("severity-focus") || "errors",
|
|
117
117
|
serviceFilter: options.serviceFilter || f("service-filter") || "*",
|
|
118
118
|
investigationDepth: options.investigationDepth || f("investigation-depth") || "standard",
|
|
119
|
-
maxInvestigateTurns:
|
|
120
|
-
maxImplementTurns:
|
|
119
|
+
maxInvestigateTurns: parsePositiveInt(options.maxInvestigateTurns ?? f("max-investigate-turns"), 50),
|
|
120
|
+
maxImplementTurns: parsePositiveInt(options.maxImplementTurns ?? f("max-implement-turns"), 30),
|
|
121
121
|
baseBranch: options.baseBranch || f("base-branch") || "main",
|
|
122
122
|
prLabels: (options.prLabels || f("pr-labels") || "agent,triage,needs-review")
|
|
123
123
|
.split(",")
|
|
@@ -165,7 +165,7 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
165
165
|
stream: Boolean(options.stream),
|
|
166
166
|
bell: Boolean(options.bell),
|
|
167
167
|
cacheDir: options.cacheDir || env.SWENY_CACHE_DIR || f("cache-dir") || ".sweny/cache",
|
|
168
|
-
cacheTtl:
|
|
168
|
+
cacheTtl: parsePositiveInt(options.cacheTtl ?? f("cache-ttl"), 86400),
|
|
169
169
|
noCache: options.cache === false,
|
|
170
170
|
outputDir: options.outputDir || env.SWENY_OUTPUT_DIR || f("output-dir") || ".sweny/output",
|
|
171
171
|
mcpServers: parseMcpServers(env.SWENY_MCP_SERVERS || f("mcp-servers-json") || ""),
|
|
@@ -424,14 +424,61 @@ export function validateInputs(config) {
|
|
|
424
424
|
if (!["auto", "review"].includes(config.reviewMode)) {
|
|
425
425
|
errors.push("--review-mode must be one of: auto, review");
|
|
426
426
|
}
|
|
427
|
-
// Integer bounds
|
|
428
|
-
|
|
429
|
-
|
|
427
|
+
// Integer bounds — reject NaN and Infinity first (parseInt can yield NaN
|
|
428
|
+
// silently and NaN compares false to both < min and > max, so the old
|
|
429
|
+
// bounds check let junk through).
|
|
430
|
+
validateIntegerBound(errors, "--max-investigate-turns", config.maxInvestigateTurns, 1, 500);
|
|
431
|
+
validateIntegerBound(errors, "--max-implement-turns", config.maxImplementTurns, 1, 500);
|
|
432
|
+
return errors;
|
|
433
|
+
}
|
|
434
|
+
function validateIntegerBound(errors, flag, value, min, max) {
|
|
435
|
+
if (!Number.isFinite(value)) {
|
|
436
|
+
errors.push(`${flag} must be a finite integer between ${min} and ${max} (got "${value}")`);
|
|
437
|
+
return;
|
|
430
438
|
}
|
|
431
|
-
if (
|
|
432
|
-
errors.push(
|
|
439
|
+
if (value < min || value > max) {
|
|
440
|
+
errors.push(`${flag} must be between ${min} and ${max}`);
|
|
433
441
|
}
|
|
434
|
-
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Parse a value as a positive integer (>= 1).
|
|
445
|
+
*
|
|
446
|
+
* Returns:
|
|
447
|
+
* - the parsed integer when the input represents a positive finite one
|
|
448
|
+
* - the fallback when the input is null/undefined/empty (unset flag)
|
|
449
|
+
* - NaN for any malformed or non-positive input, so validateInputs can
|
|
450
|
+
* surface a field-specific error
|
|
451
|
+
*
|
|
452
|
+
* Name enforces the contract: non-positive values (0, -5) return NaN so
|
|
453
|
+
* validation rejects them loudly rather than quietly passing through as
|
|
454
|
+
* `0` and breaking downstream invariants.
|
|
455
|
+
*
|
|
456
|
+
* Why zero is rejected: every current caller (max-investigate-turns,
|
|
457
|
+
* max-implement-turns, cache-ttl) uses the result as a positive count or
|
|
458
|
+
* timeout. There is no "zero means disabled" semantics anywhere — a
|
|
459
|
+
* cache-ttl of 0 wouldn't disable cache, it would expire entries
|
|
460
|
+
* immediately, which is broken. If a future flag wants "0 = disabled",
|
|
461
|
+
* use a sibling helper (parseNonNegativeInt) rather than weakening this
|
|
462
|
+
* one's contract.
|
|
463
|
+
*/
|
|
464
|
+
export function parsePositiveInt(raw, fallback) {
|
|
465
|
+
if (raw === undefined || raw === null || raw === "")
|
|
466
|
+
return fallback;
|
|
467
|
+
if (typeof raw === "number") {
|
|
468
|
+
if (!Number.isFinite(raw))
|
|
469
|
+
return Number.NaN;
|
|
470
|
+
const truncated = Math.trunc(raw);
|
|
471
|
+
return truncated >= 1 ? truncated : Number.NaN;
|
|
472
|
+
}
|
|
473
|
+
const asString = String(raw).trim();
|
|
474
|
+
if (asString.length === 0)
|
|
475
|
+
return fallback;
|
|
476
|
+
// Number() is stricter than parseInt — "5o" → NaN rather than 5.
|
|
477
|
+
const parsed = Number(asString);
|
|
478
|
+
if (!Number.isFinite(parsed))
|
|
479
|
+
return Number.NaN;
|
|
480
|
+
const truncated = Math.trunc(parsed);
|
|
481
|
+
return truncated >= 1 ? truncated : Number.NaN;
|
|
435
482
|
}
|
|
436
483
|
const DEFAULT_SERVICE_MAP_PATH = ".github/service-map.yml";
|
|
437
484
|
/**
|
package/dist/cli/main.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import type { Workflow } from "../types.js";
|
|
3
|
-
export declare function loadWorkflowFile(filePath: string): Workflow;
|
|
3
|
+
export declare function loadWorkflowFile(filePath: string, knownSkills?: Set<string>): Workflow;
|
|
4
4
|
export declare function workflowRunAction(file: string, options: Record<string, unknown> & {
|
|
5
5
|
json?: boolean;
|
|
6
6
|
stream?: boolean;
|
package/dist/cli/main.js
CHANGED
|
@@ -11,11 +11,11 @@ import { triageWorkflow, implementWorkflow, seedContentWorkflow } from "../workf
|
|
|
11
11
|
import { consoleLogger } from "../types.js";
|
|
12
12
|
import { ClaudeClient } from "../claude.js";
|
|
13
13
|
import { createSkillMap, validateWorkflowSkills } from "../skills/index.js";
|
|
14
|
-
import { configuredSkills } from "../skills/custom-loader.js";
|
|
14
|
+
import { configuredSkills, configuredSkillsWithDiagnostics } from "../skills/custom-loader.js";
|
|
15
15
|
import { buildAutoMcpServers, buildSkillMcpServers, buildProviderContext } from "../mcp.js";
|
|
16
16
|
import { loadAdditionalContext } from "../templates.js";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { loadAndValidateWorkflow } from "../loader.js";
|
|
18
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
19
19
|
import { buildWorkflow, refineWorkflow } from "../workflow-builder.js";
|
|
20
20
|
import { toMermaidBlock } from "../mermaid.js";
|
|
21
21
|
import { runWorkflowDiagram } from "./diagram.js";
|
|
@@ -140,11 +140,20 @@ function buildProviderCtx(config, mcpServers) {
|
|
|
140
140
|
/**
|
|
141
141
|
* Resolve rules and context from config into structured workflow input fields.
|
|
142
142
|
* All source kinds (inline, file, URL) are resolved eagerly.
|
|
143
|
+
*
|
|
144
|
+
* `offline` and `fetchAuth` are threaded through so CLI-preloaded rules/context
|
|
145
|
+
* honor the same policy as Sources resolved later by the executor (Fix #16).
|
|
143
146
|
*/
|
|
144
147
|
async function resolveRulesAndContext(config) {
|
|
148
|
+
const loadOptions = {
|
|
149
|
+
cwd: process.cwd(),
|
|
150
|
+
offline: config.offline,
|
|
151
|
+
fetchAuth: config.fetchAuth,
|
|
152
|
+
env: process.env,
|
|
153
|
+
};
|
|
145
154
|
const [rulesResult, contextResult] = await Promise.all([
|
|
146
|
-
loadAdditionalContext(config.rules),
|
|
147
|
-
loadAdditionalContext(config.context),
|
|
155
|
+
loadAdditionalContext(config.rules, loadOptions),
|
|
156
|
+
loadAdditionalContext(config.context, loadOptions),
|
|
148
157
|
]);
|
|
149
158
|
return {
|
|
150
159
|
rules: rulesResult.resolved,
|
|
@@ -172,7 +181,11 @@ triageCmd.action(async (options) => {
|
|
|
172
181
|
console.log(formatBanner(config, version));
|
|
173
182
|
}
|
|
174
183
|
// ── Build skill map + MCP servers + Claude client ──────────
|
|
175
|
-
const
|
|
184
|
+
const triageSkillDiscovery = configuredSkillsWithDiagnostics(process.env, process.cwd());
|
|
185
|
+
for (const w of triageSkillDiscovery.warnings) {
|
|
186
|
+
console.error(chalk.yellow(` ⚠ ${w.message}`));
|
|
187
|
+
}
|
|
188
|
+
const skills = createSkillMap(triageSkillDiscovery.skills);
|
|
176
189
|
const mcpAutoConfig = buildMcpAutoConfig(config);
|
|
177
190
|
const mcpServers = buildAutoMcpServers(mcpAutoConfig);
|
|
178
191
|
const claude = new ClaudeClient({
|
|
@@ -423,7 +436,11 @@ implementCmd.action(async (issueId, options) => {
|
|
|
423
436
|
fileConfig["output-dir"] ||
|
|
424
437
|
".sweny/output",
|
|
425
438
|
};
|
|
426
|
-
const
|
|
439
|
+
const implementSkillDiscovery = configuredSkillsWithDiagnostics(process.env, process.cwd());
|
|
440
|
+
for (const w of implementSkillDiscovery.warnings) {
|
|
441
|
+
console.error(chalk.yellow(` ⚠ ${w.message}`));
|
|
442
|
+
}
|
|
443
|
+
const skills = createSkillMap(implementSkillDiscovery.skills);
|
|
427
444
|
const mcpAutoConfig = buildMcpAutoConfig(config);
|
|
428
445
|
const mcpServers = buildAutoMcpServers(mcpAutoConfig);
|
|
429
446
|
const claude = new ClaudeClient({
|
|
@@ -532,31 +549,36 @@ function promptUser(question) {
|
|
|
532
549
|
});
|
|
533
550
|
}
|
|
534
551
|
const workflowCmd = program.command("workflow").description("Manage and run workflow files");
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return ext === ".yaml" || ext === ".yml" ? parseYaml(content) : JSON.parse(content);
|
|
540
|
-
}
|
|
541
|
-
export function loadWorkflowFile(filePath) {
|
|
542
|
-
const raw = parseWorkflowFileContent(filePath);
|
|
543
|
-
const errors = validateWorkflowSchema(raw);
|
|
544
|
-
if (errors.length > 0) {
|
|
545
|
-
throw new Error(`Invalid workflow file:\n${errors.map((e) => ` ${e.message}`).join("\n")}`);
|
|
552
|
+
export function loadWorkflowFile(filePath, knownSkills) {
|
|
553
|
+
const result = loadAndValidateWorkflow(filePath, { knownSkills });
|
|
554
|
+
if (!result.ok) {
|
|
555
|
+
throw new Error(`Invalid workflow file:\n${result.errors.map((e) => ` ${e.message}`).join("\n")}`);
|
|
546
556
|
}
|
|
547
|
-
return
|
|
557
|
+
return result.workflow;
|
|
548
558
|
}
|
|
549
559
|
export async function workflowRunAction(file, options) {
|
|
560
|
+
// Discover skills first so the loader can flag UNKNOWN_SKILL at parse
|
|
561
|
+
// time. validateWorkflowSkills below still runs for richer category /
|
|
562
|
+
// env-var diagnostics, but the loader's structural check fires earlier
|
|
563
|
+
// and gives users a clear "you typed `gtihub`" pointer before any other
|
|
564
|
+
// validation noise.
|
|
565
|
+
const earlySkillDiscovery = configuredSkillsWithDiagnostics(process.env, process.cwd());
|
|
566
|
+
for (const w of earlySkillDiscovery.warnings) {
|
|
567
|
+
console.error(chalk.yellow(` ⚠ ${w.message}`));
|
|
568
|
+
}
|
|
569
|
+
const knownSkillIds = new Set(earlySkillDiscovery.skills.map((s) => s.id));
|
|
550
570
|
let workflow;
|
|
551
571
|
try {
|
|
552
|
-
workflow = loadWorkflowFile(file);
|
|
572
|
+
workflow = loadWorkflowFile(file, knownSkillIds);
|
|
553
573
|
}
|
|
554
574
|
catch (err) {
|
|
555
575
|
console.error(chalk.red(` Error loading workflow file: ${err instanceof Error ? err.message : String(err)}`));
|
|
556
576
|
process.exit(1);
|
|
557
577
|
return;
|
|
558
578
|
}
|
|
559
|
-
|
|
579
|
+
// --list-nodes: lightweight static inspection, no execution. Used to be
|
|
580
|
+
// the behavior of --dry-run before Fix #6.
|
|
581
|
+
if (options.listNodes) {
|
|
560
582
|
console.log(chalk.green(` Workflow "${workflow.name}" is valid (${Object.keys(workflow.nodes).length} nodes)`));
|
|
561
583
|
for (const [id, node] of Object.entries(workflow.nodes)) {
|
|
562
584
|
console.log(chalk.dim(` ${id}: ${node.name}${node.skills.length ? ` skills=[${node.skills.join(",")}]` : ""}`));
|
|
@@ -568,11 +590,13 @@ export async function workflowRunAction(file, options) {
|
|
|
568
590
|
const config = parseCliInputs(options, fileConfig);
|
|
569
591
|
const isJson = Boolean(options.json);
|
|
570
592
|
const isTTY = !isJson && (process.stderr.isTTY ?? false);
|
|
571
|
-
|
|
593
|
+
// Reuse the discovery pulled before workflow load (above). Diagnostics
|
|
594
|
+
// already surfaced; just turn the skill list into a map for the executor.
|
|
595
|
+
const skills = createSkillMap(earlySkillDiscovery.skills);
|
|
572
596
|
// Hard-fail at startup if the workflow references skills that aren't available.
|
|
573
597
|
// Each node lists alternatives by category (e.g. [sentry, datadog, betterstack]);
|
|
574
598
|
// we require at least one configured skill per category.
|
|
575
|
-
const validation = validateWorkflowSkills(workflow, skills);
|
|
599
|
+
const validation = validateWorkflowSkills(workflow, skills, workflow.skills);
|
|
576
600
|
if (validation.errors.length > 0) {
|
|
577
601
|
console.error(chalk.red(`\n Workflow cannot run — missing required skills:\n`));
|
|
578
602
|
for (const err of validation.errors)
|
|
@@ -743,47 +767,21 @@ export function workflowExportAction(name) {
|
|
|
743
767
|
process.stdout.write(stringifyYaml(workflow, { indent: 2, lineWidth: 120 }));
|
|
744
768
|
}
|
|
745
769
|
export function workflowValidateAction(file, options) {
|
|
746
|
-
|
|
747
|
-
try {
|
|
748
|
-
raw = parseWorkflowFileContent(file);
|
|
749
|
-
}
|
|
750
|
-
catch (err) {
|
|
751
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
752
|
-
if (options.json) {
|
|
753
|
-
process.stderr.write(JSON.stringify({ valid: false, errors: [{ message }] }, null, 2) + "\n");
|
|
754
|
-
}
|
|
755
|
-
else {
|
|
756
|
-
console.error(chalk.red(` Cannot read "${file}": ${message}`));
|
|
757
|
-
}
|
|
758
|
-
process.exit(1);
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
762
|
-
const kind = raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw;
|
|
763
|
-
const message = `Expected a YAML/JSON object, got ${kind}`;
|
|
764
|
-
if (options.json) {
|
|
765
|
-
process.stderr.write(JSON.stringify({ valid: false, errors: [{ message }] }, null, 2) + "\n");
|
|
766
|
-
}
|
|
767
|
-
else {
|
|
768
|
-
console.error(chalk.red(` \u2717 ${file}: ${message}`));
|
|
769
|
-
}
|
|
770
|
-
process.exit(1);
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
const errors = validateWorkflowSchema(raw);
|
|
770
|
+
const result = loadAndValidateWorkflow(file);
|
|
774
771
|
if (options.json) {
|
|
775
|
-
|
|
772
|
+
const errs = result.ok ? [] : result.errors;
|
|
773
|
+
process.stdout.write(JSON.stringify({ valid: result.ok, errors: errs }, null, 2) + "\n");
|
|
776
774
|
}
|
|
777
|
-
else if (
|
|
775
|
+
else if (result.ok) {
|
|
778
776
|
console.log(chalk.green(` \u2713 ${file} is valid`));
|
|
779
777
|
}
|
|
780
778
|
else {
|
|
781
|
-
console.error(chalk.red(` \u2717 ${file} has ${errors.length} validation error${errors.length > 1 ? "s" : ""}:`));
|
|
782
|
-
for (const err of errors) {
|
|
779
|
+
console.error(chalk.red(` \u2717 ${file} has ${result.errors.length} validation error${result.errors.length > 1 ? "s" : ""}:`));
|
|
780
|
+
for (const err of result.errors) {
|
|
783
781
|
console.error(chalk.dim(` ${err.message}`));
|
|
784
782
|
}
|
|
785
783
|
}
|
|
786
|
-
process.exit(
|
|
784
|
+
process.exit(result.ok ? 0 : 1);
|
|
787
785
|
}
|
|
788
786
|
workflowCmd
|
|
789
787
|
.command("validate <file>")
|
|
@@ -793,7 +791,8 @@ workflowCmd
|
|
|
793
791
|
workflowCmd
|
|
794
792
|
.command("run <file>")
|
|
795
793
|
.description("Run a workflow from a YAML or JSON file")
|
|
796
|
-
.option("--dry-run", "
|
|
794
|
+
.option("--dry-run", "Execute until the first conditional routing decision, then stop (no side effects past that point). NOTE: behavior changed in this release — previously --dry-run only printed the node list. For that behavior use --list-nodes.")
|
|
795
|
+
.option("--list-nodes", "Validate, print nodes/skills, and exit without running. (Replaces the pre-Fix-#6 --dry-run behavior.)")
|
|
797
796
|
.option("--json", "Output result as JSON on stdout; suppress progress output")
|
|
798
797
|
.option("--stream", "Stream NDJSON events to stdout (for Studio / automation)")
|
|
799
798
|
.option("--mermaid", "Output a Mermaid diagram with execution state after run")
|
package/dist/cli/marketplace.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as p from "@clack/prompts";
|
|
|
10
10
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
11
11
|
import { refineWorkflow } from "../workflow-builder.js";
|
|
12
12
|
import { DagRenderer } from "./renderer.js";
|
|
13
|
-
import {
|
|
13
|
+
import { validateParsed } from "../loader.js";
|
|
14
14
|
import { loadConfigFile } from "./config-file.js";
|
|
15
15
|
import { extractSkillsFromYaml, collectCredentialsForSkills, writeSwenyYmlIfMissing, appendMissingEnvKeys, writeWorkflowFile, } from "./new.js";
|
|
16
16
|
export const MARKETPLACE_REPO = "swenyai/workflows";
|
|
@@ -194,20 +194,36 @@ export async function installMarketplaceWorkflow(id, options) {
|
|
|
194
194
|
const overwrite = options.overwrite ?? false;
|
|
195
195
|
// 1. Fetch
|
|
196
196
|
const fetched = await fetchMarketplaceWorkflow(id);
|
|
197
|
-
// 2.
|
|
197
|
+
// 2. Validate fetched content BEFORE any local side effects (Fix #3).
|
|
198
|
+
// Previously, an invalid workflow could still get written to disk and
|
|
199
|
+
// .sweny.yml / .env polluted with keys inferred from broken content.
|
|
200
|
+
let parsed;
|
|
201
|
+
{
|
|
202
|
+
let yamlParsed;
|
|
203
|
+
try {
|
|
204
|
+
yamlParsed = parseYaml(fetched.yaml);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
208
|
+
throw new Error(`Marketplace workflow "${id}" is not valid YAML: ${msg}`);
|
|
209
|
+
}
|
|
210
|
+
const validated = validateParsed(yamlParsed);
|
|
211
|
+
if (!validated.ok) {
|
|
212
|
+
throw new Error(`Marketplace workflow "${id}" failed validation:\n${validated.errors.map((e) => ` ${e.message}`).join("\n")}`);
|
|
213
|
+
}
|
|
214
|
+
parsed = validated.workflow;
|
|
215
|
+
}
|
|
216
|
+
// 3. Load existing config to detect mismatches
|
|
198
217
|
const fileConfig = loadConfigFile(cwd);
|
|
199
218
|
const workflowSkills = extractSkillsFromYaml(fetched.yaml);
|
|
200
219
|
const mismatches = computeProviderMismatch(workflowSkills, fileConfig, availableSkills);
|
|
201
|
-
//
|
|
220
|
+
// 4. Adapt if needed + possible. Schema was already validated above, so
|
|
221
|
+
// adaptation failure no longer hides a schema failure — it falls back to
|
|
222
|
+
// installing the validated-original.
|
|
202
223
|
let finalYaml = fetched.yaml;
|
|
203
224
|
let adapted = false;
|
|
204
225
|
if (mismatches.length > 0 && claude) {
|
|
205
226
|
try {
|
|
206
|
-
const parsed = workflowZ.parse(parseYaml(fetched.yaml));
|
|
207
|
-
const errs = validateWorkflow(parsed);
|
|
208
|
-
if (errs.length > 0) {
|
|
209
|
-
throw new Error(`Marketplace workflow has schema errors: ${errs.map((e) => e.message).join("; ")}`);
|
|
210
|
-
}
|
|
211
227
|
const refined = await adaptWorkflowInteractive(parsed, {
|
|
212
228
|
claude,
|
|
213
229
|
skills: availableSkills,
|
|
@@ -224,7 +240,17 @@ export async function installMarketplaceWorkflow(id, options) {
|
|
|
224
240
|
addedEnvKeys: 0,
|
|
225
241
|
};
|
|
226
242
|
}
|
|
227
|
-
|
|
243
|
+
// Re-validate the refined output before writing. refineWorkflow runs
|
|
244
|
+
// an LLM that produces a new Workflow object — it could drift away
|
|
245
|
+
// from the upfront-validated parse (renamed required fields, dropped
|
|
246
|
+
// edges). Reparsing the stringified YAML closes that loop so we
|
|
247
|
+
// never write content that hasn't been schema-checked.
|
|
248
|
+
const refinedYaml = stringifyYaml(refined, { indent: 2, lineWidth: 120 });
|
|
249
|
+
const revalidated = validateParsed(parseYaml(refinedYaml));
|
|
250
|
+
if (!revalidated.ok) {
|
|
251
|
+
throw new Error(`Adapted workflow failed re-validation:\n${revalidated.errors.map((e) => ` ${e.message}`).join("\n")}`);
|
|
252
|
+
}
|
|
253
|
+
finalYaml = refinedYaml;
|
|
228
254
|
adapted = true;
|
|
229
255
|
}
|
|
230
256
|
catch (err) {
|
package/dist/cli/publish.d.ts
CHANGED
|
@@ -32,4 +32,14 @@ export declare function validateSkillDir(dirPath: string): {
|
|
|
32
32
|
errors: string[];
|
|
33
33
|
};
|
|
34
34
|
export declare function runPublish(): Promise<PublishResult | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Recursively copy a skill source directory to its destination (Fix #17).
|
|
37
|
+
*
|
|
38
|
+
* Previously a flat `fs.readdirSync` + `fs.copyFileSync` loop: skills with
|
|
39
|
+
* nested directories (scripts/, references/, assets/) either silently
|
|
40
|
+
* shipped incomplete or — more often — threw EISDIR on the first subdir.
|
|
41
|
+
*
|
|
42
|
+
* Exported for tests; node:fs cp's recursive mode preserves structure.
|
|
43
|
+
*/
|
|
44
|
+
export declare function copySkillDir(sourceDir: string, destDir: string): void;
|
|
35
45
|
export declare function registerPublishCommand(program: Command): void;
|