@sweny-ai/core 0.1.76 → 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 +20 -0
- package/dist/claude.js +139 -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/executor.js +104 -43
- 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/requires.d.ts +7 -0
- package/dist/requires.js +34 -0
- package/dist/retry.d.ts +23 -0
- package/dist/retry.js +72 -0
- package/dist/schema.d.ts +667 -80
- package/dist/schema.js +144 -5
- 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/testing.d.ts +7 -0
- package/dist/testing.js +7 -0
- package/dist/types.d.ts +76 -0
- package/dist/verify.d.ts +1 -0
- package/dist/verify.js +43 -5
- package/package.json +8 -4
- package/dist/__tests__/cascade.test.d.ts +0 -11
- package/dist/__tests__/cascade.test.js +0 -449
- 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 -736
- 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__/schema.test.d.ts +0 -1
- package/dist/__tests__/schema.test.js +0 -526
- 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 -1074
- 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 -224
- 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 -603
- 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 -117
package/dist/schema.js
CHANGED
|
@@ -36,6 +36,7 @@ export const mcpServerConfigZ = z
|
|
|
36
36
|
headers: z.record(z.string()).optional(),
|
|
37
37
|
env: z.record(z.string()).optional(),
|
|
38
38
|
})
|
|
39
|
+
.strict()
|
|
39
40
|
.refine((c) => c.command || c.url, {
|
|
40
41
|
message: "MCP server must have either command (stdio) or url (HTTP)",
|
|
41
42
|
});
|
|
@@ -46,6 +47,7 @@ export const skillDefinitionZ = z
|
|
|
46
47
|
instruction: z.string().optional(),
|
|
47
48
|
mcp: mcpServerConfigZ.optional(),
|
|
48
49
|
})
|
|
50
|
+
.strict()
|
|
49
51
|
.refine((s) => s.instruction || s.mcp, {
|
|
50
52
|
message: "Inline skill must provide instruction, mcp, or both",
|
|
51
53
|
});
|
|
@@ -82,6 +84,7 @@ export const outputMatchZ = z
|
|
|
82
84
|
in: z.array(z.unknown()).optional(),
|
|
83
85
|
matches: z.string().min(1).optional(),
|
|
84
86
|
})
|
|
87
|
+
.strict()
|
|
85
88
|
.refine((m) => {
|
|
86
89
|
const operators = [m.equals !== undefined, m.in !== undefined, m.matches !== undefined];
|
|
87
90
|
return operators.filter(Boolean).length === 1;
|
|
@@ -94,6 +97,12 @@ export const nodeVerifyZ = z
|
|
|
94
97
|
output_required: z.array(z.string().min(1)).min(1).optional(),
|
|
95
98
|
output_matches: z.array(outputMatchZ).min(1).optional(),
|
|
96
99
|
})
|
|
100
|
+
// Fix #4 gap: the exported JSON Schema sets additionalProperties: false.
|
|
101
|
+
// Without .strict() here Zod would silently drop unknown keys and then
|
|
102
|
+
// the refine would run on the stripped object, producing a different
|
|
103
|
+
// verdict than ajv (which rejects the unknown key directly). Keep them
|
|
104
|
+
// in sync.
|
|
105
|
+
.strict()
|
|
97
106
|
.refine((v) => v.any_tool_called !== undefined ||
|
|
98
107
|
v.all_tools_called !== undefined ||
|
|
99
108
|
v.no_tool_called !== undefined ||
|
|
@@ -101,7 +110,26 @@ export const nodeVerifyZ = z
|
|
|
101
110
|
v.output_matches !== undefined, {
|
|
102
111
|
message: "verify must declare at least one check (any_tool_called, all_tools_called, no_tool_called, output_required, or output_matches)",
|
|
103
112
|
});
|
|
104
|
-
export const
|
|
113
|
+
export const nodeRequiresZ = z
|
|
114
|
+
.object({
|
|
115
|
+
output_required: z.array(z.string().min(1)).min(1).optional(),
|
|
116
|
+
output_matches: z.array(outputMatchZ).min(1).optional(),
|
|
117
|
+
on_fail: z.enum(["fail", "skip"]).optional(),
|
|
118
|
+
})
|
|
119
|
+
.strict()
|
|
120
|
+
.refine((r) => r.output_required !== undefined || r.output_matches !== undefined, {
|
|
121
|
+
message: "requires must declare at least one check (output_required or output_matches)",
|
|
122
|
+
});
|
|
123
|
+
const retryInstructionAutoZ = z.object({ auto: z.literal(true) }).strict();
|
|
124
|
+
const retryInstructionReflectZ = z.object({ reflect: z.string().min(1) }).strict();
|
|
125
|
+
export const nodeRetryZ = z
|
|
126
|
+
.object({
|
|
127
|
+
max: z.number().int().min(1),
|
|
128
|
+
instruction: z.union([z.string().min(1), retryInstructionAutoZ, retryInstructionReflectZ]).optional(),
|
|
129
|
+
})
|
|
130
|
+
.strict();
|
|
131
|
+
export const nodeZ = z
|
|
132
|
+
.object({
|
|
105
133
|
name: z.string().min(1),
|
|
106
134
|
instruction: sourceZ,
|
|
107
135
|
skills: z.array(z.string()).default([]),
|
|
@@ -110,13 +138,24 @@ export const nodeZ = z.object({
|
|
|
110
138
|
rules: nodeSourcesZ.optional(),
|
|
111
139
|
context: nodeSourcesZ.optional(),
|
|
112
140
|
verify: nodeVerifyZ.optional(),
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
requires: nodeRequiresZ.optional(),
|
|
142
|
+
retry: nodeRetryZ.optional(),
|
|
143
|
+
})
|
|
144
|
+
.strict();
|
|
145
|
+
export const edgeZ = z
|
|
146
|
+
.object({
|
|
115
147
|
from: z.string().min(1),
|
|
116
148
|
to: z.string().min(1),
|
|
117
149
|
when: z.string().optional(),
|
|
118
150
|
max_iterations: z.number().int().min(1).optional(),
|
|
119
|
-
})
|
|
151
|
+
})
|
|
152
|
+
.strict();
|
|
153
|
+
// Fix #4 gap — workflowZ is intentionally NOT .strict(). The published
|
|
154
|
+
// JSON Schema's `additionalProperties: false` only scopes the properties
|
|
155
|
+
// block, but marketplace workflows routinely carry top-level metadata
|
|
156
|
+
// (author, category, tags) handled by publish.ts. Striping rather than
|
|
157
|
+
// rejecting them preserves compatibility with the existing marketplace
|
|
158
|
+
// contract while the inner objects (nodes, edges, skills) stay strict.
|
|
120
159
|
export const workflowZ = z.object({
|
|
121
160
|
id: z.string().min(1),
|
|
122
161
|
name: z.string().min(1),
|
|
@@ -311,7 +350,10 @@ export const workflowJsonSchema = {
|
|
|
311
350
|
description: "A declarative YAML format for AI agent orchestration as a directed graph with natural language routing.",
|
|
312
351
|
type: "object",
|
|
313
352
|
required: ["id", "name", "nodes", "edges", "entry"],
|
|
314
|
-
|
|
353
|
+
// Top-level is intentionally open: marketplace workflows carry extra
|
|
354
|
+
// metadata (author, category, tags) that publish.ts reads. Inner
|
|
355
|
+
// objects (nodes, edges, skills, verify, requires, etc.) are strict.
|
|
356
|
+
additionalProperties: true,
|
|
315
357
|
$defs: {
|
|
316
358
|
Source: sourceJsonSchema,
|
|
317
359
|
NodeSources: {
|
|
@@ -375,6 +417,95 @@ export const workflowJsonSchema = {
|
|
|
375
417
|
$ref: "#/$defs/NodeSources",
|
|
376
418
|
description: "Per-node context. Additive by default; set { only: true } to block cascade.",
|
|
377
419
|
},
|
|
420
|
+
verify: {
|
|
421
|
+
type: "object",
|
|
422
|
+
description: "Machine-checked post-conditions evaluated after the LLM finishes.",
|
|
423
|
+
additionalProperties: false,
|
|
424
|
+
// Fix #4: at least one check must be declared.
|
|
425
|
+
minProperties: 1,
|
|
426
|
+
properties: {
|
|
427
|
+
any_tool_called: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
|
|
428
|
+
all_tools_called: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
|
|
429
|
+
no_tool_called: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
|
|
430
|
+
output_required: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 },
|
|
431
|
+
output_matches: {
|
|
432
|
+
type: "array",
|
|
433
|
+
items: {
|
|
434
|
+
type: "object",
|
|
435
|
+
required: ["path"],
|
|
436
|
+
additionalProperties: false,
|
|
437
|
+
properties: {
|
|
438
|
+
path: { type: "string", minLength: 1 },
|
|
439
|
+
equals: {},
|
|
440
|
+
in: { type: "array" },
|
|
441
|
+
matches: { type: "string", minLength: 1 },
|
|
442
|
+
},
|
|
443
|
+
// Fix #4: exactly one operator must be declared.
|
|
444
|
+
oneOf: [{ required: ["equals"] }, { required: ["in"] }, { required: ["matches"] }],
|
|
445
|
+
},
|
|
446
|
+
minItems: 1,
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
requires: {
|
|
451
|
+
type: "object",
|
|
452
|
+
description: "Pre-condition checks evaluated before the LLM runs.",
|
|
453
|
+
additionalProperties: false,
|
|
454
|
+
// Fix #4: at least one of output_required / output_matches must be declared.
|
|
455
|
+
// on_fail alone is not sufficient — it only tags how a failing check behaves.
|
|
456
|
+
anyOf: [{ required: ["output_required"] }, { required: ["output_matches"] }],
|
|
457
|
+
properties: {
|
|
458
|
+
output_required: {
|
|
459
|
+
type: "array",
|
|
460
|
+
items: { type: "string", minLength: 1 },
|
|
461
|
+
minItems: 1,
|
|
462
|
+
},
|
|
463
|
+
output_matches: {
|
|
464
|
+
type: "array",
|
|
465
|
+
items: {
|
|
466
|
+
type: "object",
|
|
467
|
+
required: ["path"],
|
|
468
|
+
additionalProperties: false,
|
|
469
|
+
properties: {
|
|
470
|
+
path: { type: "string", minLength: 1 },
|
|
471
|
+
equals: {},
|
|
472
|
+
in: { type: "array" },
|
|
473
|
+
matches: { type: "string", minLength: 1 },
|
|
474
|
+
},
|
|
475
|
+
// Fix #4: exactly one operator must be declared.
|
|
476
|
+
oneOf: [{ required: ["equals"] }, { required: ["in"] }, { required: ["matches"] }],
|
|
477
|
+
},
|
|
478
|
+
minItems: 1,
|
|
479
|
+
},
|
|
480
|
+
on_fail: { type: "string", enum: ["fail", "skip"] },
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
retry: {
|
|
484
|
+
type: "object",
|
|
485
|
+
description: "Node-local retry on verify failure.",
|
|
486
|
+
required: ["max"],
|
|
487
|
+
additionalProperties: false,
|
|
488
|
+
properties: {
|
|
489
|
+
max: { type: "integer", minimum: 1 },
|
|
490
|
+
instruction: {
|
|
491
|
+
oneOf: [
|
|
492
|
+
{ type: "string", minLength: 1 },
|
|
493
|
+
{
|
|
494
|
+
type: "object",
|
|
495
|
+
required: ["auto"],
|
|
496
|
+
additionalProperties: false,
|
|
497
|
+
properties: { auto: { const: true } },
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
type: "object",
|
|
501
|
+
required: ["reflect"],
|
|
502
|
+
additionalProperties: false,
|
|
503
|
+
properties: { reflect: { type: "string", minLength: 1 } },
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
378
509
|
},
|
|
379
510
|
},
|
|
380
511
|
},
|
|
@@ -401,6 +532,10 @@ export const workflowJsonSchema = {
|
|
|
401
532
|
description: "Inline skill definitions scoped to this workflow",
|
|
402
533
|
additionalProperties: {
|
|
403
534
|
type: "object",
|
|
535
|
+
// Fix #4: an inline skill must provide instruction, mcp, or both.
|
|
536
|
+
anyOf: [{ required: ["instruction"] }, { required: ["mcp"] }],
|
|
537
|
+
// Round 2: reject unknown keys to match Zod skillDefinitionZ.strict().
|
|
538
|
+
additionalProperties: false,
|
|
404
539
|
properties: {
|
|
405
540
|
name: { type: "string" },
|
|
406
541
|
description: { type: "string" },
|
|
@@ -408,6 +543,10 @@ export const workflowJsonSchema = {
|
|
|
408
543
|
mcp: {
|
|
409
544
|
type: "object",
|
|
410
545
|
description: "External MCP server definition",
|
|
546
|
+
// Fix #4: an MCP server must declare command (stdio) or url (http).
|
|
547
|
+
anyOf: [{ required: ["command"] }, { required: ["url"] }],
|
|
548
|
+
// Round 2: reject unknown keys to match Zod mcpServerConfigZ.strict().
|
|
549
|
+
additionalProperties: false,
|
|
411
550
|
properties: {
|
|
412
551
|
type: { type: "string", enum: ["stdio", "http"] },
|
|
413
552
|
command: { type: "string" },
|
|
@@ -9,8 +9,33 @@
|
|
|
9
9
|
* .gemini/skills/ < .agents/skills/ < .claude/skills/ < .sweny/skills/
|
|
10
10
|
*/
|
|
11
11
|
import type { Skill } from "../types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Diagnostic emitted when a SKILL.md file is skipped or overridden during
|
|
14
|
+
* discovery. CLI flows can render these as yellow warnings; library consumers
|
|
15
|
+
* can inspect `kind` to decide whether to surface, ignore, or fail.
|
|
16
|
+
*/
|
|
17
|
+
export interface SkillDiagnostic {
|
|
18
|
+
kind: "invalid-frontmatter" | "missing-name" | "invalid-id" | "unreadable-file" | "duplicate-id";
|
|
19
|
+
path: string;
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SkillDiscoveryResult {
|
|
23
|
+
skills: Skill[];
|
|
24
|
+
warnings: SkillDiagnostic[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Discover custom skills and surface diagnostics for broken ones.
|
|
28
|
+
*
|
|
29
|
+
* Prefer this over `discoverSkills` for CLI flows that can surface warnings
|
|
30
|
+
* to users. `discoverSkills` stays as a backward-compatible wrapper for
|
|
31
|
+
* callers that only want the happy-path skill list.
|
|
32
|
+
*/
|
|
33
|
+
export declare function discoverSkillsWithDiagnostics(cwd?: string): SkillDiscoveryResult;
|
|
12
34
|
/**
|
|
13
35
|
* Discover custom skills from all known harness directories.
|
|
36
|
+
*
|
|
37
|
+
* Backwards-compatible wrapper that drops diagnostics. Use
|
|
38
|
+
* `discoverSkillsWithDiagnostics` when you want to surface warnings.
|
|
14
39
|
*/
|
|
15
40
|
export declare function discoverSkills(cwd?: string): Skill[];
|
|
16
41
|
/**
|
|
@@ -18,7 +43,22 @@ export declare function discoverSkills(cwd?: string): Skill[];
|
|
|
18
43
|
* @deprecated Use `discoverSkills` for multi-directory support.
|
|
19
44
|
*/
|
|
20
45
|
export declare function loadCustomSkills(cwd?: string): Skill[];
|
|
46
|
+
/**
|
|
47
|
+
* From all builtins + custom repo skills, return only the skills that are usable
|
|
48
|
+
* alongside any custom-skill loader diagnostics (warnings for malformed or
|
|
49
|
+
* duplicate SKILL.md files).
|
|
50
|
+
*
|
|
51
|
+
* Most CLI flows should prefer this over `configuredSkills` so they can
|
|
52
|
+
* surface warnings to the user.
|
|
53
|
+
*/
|
|
54
|
+
export declare function configuredSkillsWithDiagnostics(env?: Record<string, string | undefined>, cwd?: string): {
|
|
55
|
+
skills: Skill[];
|
|
56
|
+
warnings: SkillDiagnostic[];
|
|
57
|
+
};
|
|
21
58
|
/**
|
|
22
59
|
* From all builtins + custom repo skills, return only the skills that are usable.
|
|
60
|
+
*
|
|
61
|
+
* Backwards-compatible wrapper that drops diagnostics. Prefer
|
|
62
|
+
* `configuredSkillsWithDiagnostics` in CLI flows so broken skill files surface.
|
|
23
63
|
*/
|
|
24
64
|
export declare function configuredSkills(env?: Record<string, string | undefined>, cwd?: string): Skill[];
|
|
@@ -17,10 +17,15 @@ const SKILL_DIRS = [".gemini/skills", ".agents/skills", ".claude/skills", ".swen
|
|
|
17
17
|
/** Valid skill ID: lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens, max 64 chars. */
|
|
18
18
|
const VALID_SKILL_ID = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
19
19
|
/**
|
|
20
|
-
* Discover custom skills
|
|
20
|
+
* Discover custom skills and surface diagnostics for broken ones.
|
|
21
|
+
*
|
|
22
|
+
* Prefer this over `discoverSkills` for CLI flows that can surface warnings
|
|
23
|
+
* to users. `discoverSkills` stays as a backward-compatible wrapper for
|
|
24
|
+
* callers that only want the happy-path skill list.
|
|
21
25
|
*/
|
|
22
|
-
export function
|
|
26
|
+
export function discoverSkillsWithDiagnostics(cwd = process.cwd()) {
|
|
23
27
|
const skillMap = new Map();
|
|
28
|
+
const warnings = [];
|
|
24
29
|
for (const relDir of SKILL_DIRS) {
|
|
25
30
|
const skillsDir = join(cwd, relDir);
|
|
26
31
|
if (!existsSync(skillsDir))
|
|
@@ -38,32 +43,73 @@ export function discoverSkills(cwd = process.cwd()) {
|
|
|
38
43
|
const skillFile = join(skillsDir, dirName, "SKILL.md");
|
|
39
44
|
if (!existsSync(skillFile))
|
|
40
45
|
continue;
|
|
46
|
+
let content;
|
|
41
47
|
try {
|
|
42
|
-
|
|
43
|
-
const parsed = parseSkillMd(content);
|
|
44
|
-
if (!parsed)
|
|
45
|
-
continue;
|
|
46
|
-
skillMap.set(parsed.id, parsed);
|
|
48
|
+
content = readFileSync(skillFile, "utf-8");
|
|
47
49
|
}
|
|
48
|
-
catch {
|
|
49
|
-
|
|
50
|
+
catch (err) {
|
|
51
|
+
const msg = err instanceof Error ? err.message : "unknown error";
|
|
52
|
+
warnings.push({
|
|
53
|
+
kind: "unreadable-file",
|
|
54
|
+
path: skillFile,
|
|
55
|
+
message: `Could not read ${skillFile}: ${msg}`,
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseSkillMd(content, skillFile);
|
|
60
|
+
if (parsed.kind === "ok") {
|
|
61
|
+
if (skillMap.has(parsed.skill.id)) {
|
|
62
|
+
warnings.push({
|
|
63
|
+
kind: "duplicate-id",
|
|
64
|
+
path: skillFile,
|
|
65
|
+
message: `Skill "${parsed.skill.id}" overrides an earlier definition (last-one-wins)`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
skillMap.set(parsed.skill.id, parsed.skill);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
warnings.push(parsed.diagnostic);
|
|
50
72
|
}
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
|
-
return [...skillMap.values()];
|
|
75
|
+
return { skills: [...skillMap.values()], warnings };
|
|
54
76
|
}
|
|
55
77
|
/**
|
|
56
|
-
*
|
|
78
|
+
* Discover custom skills from all known harness directories.
|
|
79
|
+
*
|
|
80
|
+
* Backwards-compatible wrapper that drops diagnostics. Use
|
|
81
|
+
* `discoverSkillsWithDiagnostics` when you want to surface warnings.
|
|
57
82
|
*/
|
|
58
|
-
function
|
|
83
|
+
export function discoverSkills(cwd = process.cwd()) {
|
|
84
|
+
return discoverSkillsWithDiagnostics(cwd).skills;
|
|
85
|
+
}
|
|
86
|
+
function parseSkillMd(content, path) {
|
|
59
87
|
const raw = parseFrontmatter(content);
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
88
|
+
if (raw === "invalid") {
|
|
89
|
+
return {
|
|
90
|
+
kind: "err",
|
|
91
|
+
diagnostic: { kind: "invalid-frontmatter", path, message: `Invalid YAML frontmatter in ${path}` },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (!raw?.name) {
|
|
95
|
+
return {
|
|
96
|
+
kind: "err",
|
|
97
|
+
diagnostic: { kind: "missing-name", path, message: `SKILL.md at ${path} has no 'name' field in frontmatter` },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
62
100
|
// Narrow the loosely-typed YAML output into the fields we expect
|
|
63
101
|
const fm = raw;
|
|
64
102
|
const id = String(fm.name);
|
|
65
|
-
if (!VALID_SKILL_ID.test(id) || id.includes("--") || id.length > 64)
|
|
66
|
-
return
|
|
103
|
+
if (!VALID_SKILL_ID.test(id) || id.includes("--") || id.length > 64) {
|
|
104
|
+
return {
|
|
105
|
+
kind: "err",
|
|
106
|
+
diagnostic: {
|
|
107
|
+
kind: "invalid-id",
|
|
108
|
+
path,
|
|
109
|
+
message: `Skill id "${id}" is invalid (lowercase alphanumeric + hyphens only, no consecutive hyphens, max 64 chars)`,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
67
113
|
const instruction = extractBody(content);
|
|
68
114
|
let mcp;
|
|
69
115
|
if (fm.mcp && typeof fm.mcp === "object") {
|
|
@@ -94,26 +140,42 @@ function parseSkillMd(content) {
|
|
|
94
140
|
}
|
|
95
141
|
}
|
|
96
142
|
return {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
143
|
+
kind: "ok",
|
|
144
|
+
skill: {
|
|
145
|
+
id,
|
|
146
|
+
name: id,
|
|
147
|
+
description: typeof fm.description === "string" ? fm.description : `Custom skill: ${id}`,
|
|
148
|
+
category: "general",
|
|
149
|
+
config,
|
|
150
|
+
tools: [],
|
|
151
|
+
instruction: instruction || undefined,
|
|
152
|
+
mcp,
|
|
153
|
+
},
|
|
105
154
|
};
|
|
106
155
|
}
|
|
107
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Extract and parse YAML frontmatter.
|
|
158
|
+
*
|
|
159
|
+
* Returns:
|
|
160
|
+
* - parsed object when frontmatter is present and valid
|
|
161
|
+
* - `null` when no frontmatter delimiters found (treat as no-op skip)
|
|
162
|
+
* - `"invalid"` when delimiters are present but the YAML body is malformed
|
|
163
|
+
* (distinct from "missing" so diagnostics can differentiate).
|
|
164
|
+
*/
|
|
108
165
|
function parseFrontmatter(content) {
|
|
109
166
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
110
167
|
if (!match)
|
|
111
168
|
return null;
|
|
112
169
|
try {
|
|
113
|
-
|
|
170
|
+
const parsed = YAML.parse(match[1]);
|
|
171
|
+
if (parsed === null || parsed === undefined)
|
|
172
|
+
return null;
|
|
173
|
+
if (typeof parsed !== "object")
|
|
174
|
+
return "invalid";
|
|
175
|
+
return parsed;
|
|
114
176
|
}
|
|
115
177
|
catch {
|
|
116
|
-
return
|
|
178
|
+
return "invalid";
|
|
117
179
|
}
|
|
118
180
|
}
|
|
119
181
|
/** Extract the markdown body (everything after closing ---). */
|
|
@@ -131,18 +193,22 @@ export function loadCustomSkills(cwd = process.cwd()) {
|
|
|
131
193
|
return discoverSkills(cwd);
|
|
132
194
|
}
|
|
133
195
|
/**
|
|
134
|
-
* From all builtins + custom repo skills, return only the skills that are usable
|
|
196
|
+
* From all builtins + custom repo skills, return only the skills that are usable
|
|
197
|
+
* alongside any custom-skill loader diagnostics (warnings for malformed or
|
|
198
|
+
* duplicate SKILL.md files).
|
|
199
|
+
*
|
|
200
|
+
* Most CLI flows should prefer this over `configuredSkills` so they can
|
|
201
|
+
* surface warnings to the user.
|
|
135
202
|
*/
|
|
136
|
-
export function
|
|
203
|
+
export function configuredSkillsWithDiagnostics(env = process.env, cwd) {
|
|
137
204
|
const configured = builtinSkills.filter((s) => isSkillConfigured(s, env));
|
|
138
|
-
const custom =
|
|
205
|
+
const { skills: custom, warnings } = discoverSkillsWithDiagnostics(cwd);
|
|
139
206
|
// Custom skills can override built-in IDs. When overriding, merge:
|
|
140
207
|
// keep the built-in's tools and config, add the custom instruction/mcp.
|
|
141
208
|
const configuredMap = new Map(configured.map((s) => [s.id, s]));
|
|
142
209
|
for (const skill of custom) {
|
|
143
210
|
const existing = configuredMap.get(skill.id);
|
|
144
211
|
if (existing) {
|
|
145
|
-
// Merge: built-in tools + config, custom instruction/mcp + extra config
|
|
146
212
|
configuredMap.set(skill.id, {
|
|
147
213
|
...existing,
|
|
148
214
|
config: { ...existing.config, ...skill.config },
|
|
@@ -154,5 +220,14 @@ export function configuredSkills(env = process.env, cwd) {
|
|
|
154
220
|
configuredMap.set(skill.id, skill);
|
|
155
221
|
}
|
|
156
222
|
}
|
|
157
|
-
return [...configuredMap.values()];
|
|
223
|
+
return { skills: [...configuredMap.values()], warnings };
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* From all builtins + custom repo skills, return only the skills that are usable.
|
|
227
|
+
*
|
|
228
|
+
* Backwards-compatible wrapper that drops diagnostics. Prefer
|
|
229
|
+
* `configuredSkillsWithDiagnostics` in CLI flows so broken skill files surface.
|
|
230
|
+
*/
|
|
231
|
+
export function configuredSkills(env = process.env, cwd) {
|
|
232
|
+
return configuredSkillsWithDiagnostics(env, cwd).skills;
|
|
158
233
|
}
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -69,8 +69,20 @@ export interface SkillValidationResult {
|
|
|
69
69
|
/** Nodes where notification category has zero configured skills (non-fatal) */
|
|
70
70
|
warnings: string[];
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Minimal shape we need off an inline skill definition. Matches
|
|
74
|
+
* `SkillDefinition` from types.ts (kept loose here to avoid a cross-import
|
|
75
|
+
* that drags the full type graph into skill-index consumers).
|
|
76
|
+
*/
|
|
77
|
+
type InlineSkillDef = {
|
|
78
|
+
name?: string;
|
|
79
|
+
description?: string;
|
|
80
|
+
instruction?: string;
|
|
81
|
+
mcp?: unknown;
|
|
82
|
+
category?: SkillCategory;
|
|
83
|
+
};
|
|
72
84
|
export declare function validateWorkflowSkills(workflow: {
|
|
73
85
|
nodes: Record<string, {
|
|
74
86
|
skills: string[];
|
|
75
87
|
}>;
|
|
76
|
-
}, available: Map<string, Skill>): SkillValidationResult;
|
|
88
|
+
}, available: Map<string, Skill>, inlineSkills?: Record<string, InlineSkillDef>): SkillValidationResult;
|
package/dist/skills/index.js
CHANGED
|
@@ -69,13 +69,18 @@ export function isSkillConfigured(skill, env = {}) {
|
|
|
69
69
|
export function configuredBuiltinSkills(env = {}) {
|
|
70
70
|
return builtinSkills.filter((s) => isSkillConfigured(s, env));
|
|
71
71
|
}
|
|
72
|
-
export function validateWorkflowSkills(workflow, available) {
|
|
72
|
+
export function validateWorkflowSkills(workflow, available, inlineSkills) {
|
|
73
73
|
const configured = [];
|
|
74
74
|
const configuredIds = new Set();
|
|
75
75
|
const missing = [];
|
|
76
76
|
const missingIds = new Set();
|
|
77
77
|
const errors = [];
|
|
78
78
|
const warnings = [];
|
|
79
|
+
// Inline workflow.skills are first-class at runtime (the executor merges
|
|
80
|
+
// them into the skill map via mergeInlineSkills). Treat them as present
|
|
81
|
+
// for validation so spec-valid workflows don't fail CLI pre-flight.
|
|
82
|
+
const inlineIds = new Set(inlineSkills ? Object.keys(inlineSkills) : []);
|
|
83
|
+
const inlineCategory = (id) => inlineSkills?.[id]?.category ?? "general";
|
|
79
84
|
// Collect all referenced skill IDs
|
|
80
85
|
const allReferencedIds = new Set();
|
|
81
86
|
for (const node of Object.values(workflow.nodes)) {
|
|
@@ -91,6 +96,10 @@ export function validateWorkflowSkills(workflow, available) {
|
|
|
91
96
|
configuredIds.add(id);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
99
|
+
else if (inlineIds.has(id)) {
|
|
100
|
+
// Inline skills need no env vars and are auto-configured by the executor.
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
94
103
|
else {
|
|
95
104
|
if (!missingIds.has(id)) {
|
|
96
105
|
// Try to find it in builtins to get its category and required env vars
|
|
@@ -114,14 +123,14 @@ export function validateWorkflowSkills(workflow, available) {
|
|
|
114
123
|
for (const id of node.skills) {
|
|
115
124
|
const skill = available.get(id);
|
|
116
125
|
const builtin = builtinSkills.find((s) => s.id === id);
|
|
117
|
-
const cat = skill?.category ?? builtin?.category ?? "unknown";
|
|
126
|
+
const cat = skill?.category ?? builtin?.category ?? (inlineIds.has(id) ? inlineCategory(id) : "unknown");
|
|
118
127
|
if (!categoriesNeeded.has(cat))
|
|
119
128
|
categoriesNeeded.set(cat, []);
|
|
120
129
|
categoriesNeeded.get(cat).push(id);
|
|
121
130
|
}
|
|
122
131
|
// Check each category has at least one configured skill
|
|
123
132
|
for (const [cat, skillIds] of categoriesNeeded) {
|
|
124
|
-
const hasAny = skillIds.some((id) => available.has(id));
|
|
133
|
+
const hasAny = skillIds.some((id) => available.has(id) || inlineIds.has(id));
|
|
125
134
|
if (!hasAny) {
|
|
126
135
|
const msg = `Node "${nodeId}" has no configured ${cat} providers (needs one of: ${skillIds.join(", ")})`;
|
|
127
136
|
if (cat === "notification") {
|
package/dist/studio.js
CHANGED
|
@@ -123,8 +123,11 @@ export function applyExecutionEvent(event, nodeDataMap) {
|
|
|
123
123
|
case "node:exit": {
|
|
124
124
|
const data = nodeDataMap.get(event.node);
|
|
125
125
|
if (data) {
|
|
126
|
+
// NodeResult.status is already one of "success" | "failed" | "skipped";
|
|
127
|
+
// pass it through so Studio can render each distinctly. Previously
|
|
128
|
+
// collapsed skipped → failed (Fix #7).
|
|
126
129
|
data.exec = {
|
|
127
|
-
status: event.result.status
|
|
130
|
+
status: event.result.status,
|
|
128
131
|
result: event.result,
|
|
129
132
|
};
|
|
130
133
|
}
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Template loading for
|
|
2
|
+
* Template + rules/context loading for the CLI.
|
|
3
3
|
*
|
|
4
|
-
* Templates can come from:
|
|
4
|
+
* Templates and additional-context documents can come from:
|
|
5
5
|
* 1. Local file path (relative to repo root)
|
|
6
6
|
* 2. URL (fetched at runtime)
|
|
7
|
-
* 3. Built-in defaults
|
|
7
|
+
* 3. Built-in defaults / inline text
|
|
8
8
|
*
|
|
9
|
-
* Delegates to
|
|
10
|
-
*
|
|
9
|
+
* Delegates to source-resolver.ts for the actual fetch. Fix #16: both
|
|
10
|
+
* `loadTemplate` and `loadAdditionalContext` now honor `offline` and
|
|
11
|
+
* `fetchAuth` the same way the executor's per-node Sources do, so CLI-level
|
|
12
|
+
* rules/context stay consistent with everything the executor touches later.
|
|
11
13
|
*/
|
|
12
14
|
export declare const DEFAULT_ISSUE_TEMPLATE = "## Summary\n<!-- One-line description of the issue -->\n\n## Root Cause\n<!-- What caused this issue -->\n\n## Impact\n- **Severity**: <!-- critical / high / medium / low -->\n- **Affected Services**: <!-- list -->\n- **User Impact**: <!-- description -->\n\n## Steps to Reproduce\n1. ...\n\n## Recommended Fix\n<!-- Proposed solution -->\n\n## Related\n- Commits: <!-- relevant commits -->\n- PRs: <!-- related PRs -->\n";
|
|
13
15
|
export declare const DEFAULT_PR_TEMPLATE = "## Summary\n<!-- What does this PR do? -->\n\n## Root Cause\n<!-- What caused the issue this fixes? -->\n\n## Changes\n<!-- Bullet list of changes -->\n\n## Testing\n- [ ] Tested locally\n- [ ] No breaking changes\n\n## Related Issues\nFixes #\n";
|
|
@@ -15,25 +17,44 @@ export interface Templates {
|
|
|
15
17
|
issueTemplate: string;
|
|
16
18
|
prTemplate: string;
|
|
17
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Options shared by `loadTemplate` and `loadAdditionalContext`.
|
|
22
|
+
*
|
|
23
|
+
* `offline` and `fetchAuth` mirror the same knobs on the executor's
|
|
24
|
+
* `ExecuteOptions`, so CLI-preloaded rules/context resolve under the same
|
|
25
|
+
* policy as Sources resolved later by the executor.
|
|
26
|
+
*/
|
|
27
|
+
export interface SourceLoadOptions {
|
|
28
|
+
cwd?: string;
|
|
29
|
+
offline?: boolean;
|
|
30
|
+
/** Host → env-var-name map for URL Source authentication (Bearer). */
|
|
31
|
+
fetchAuth?: Record<string, string>;
|
|
32
|
+
/** Environment used to resolve `fetchAuth` env-var names. Defaults to process.env. */
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
}
|
|
18
35
|
/**
|
|
19
36
|
* Load a template from a file path or URL.
|
|
20
37
|
* Returns the default if source is empty or loading fails.
|
|
38
|
+
*
|
|
39
|
+
* Accepts the legacy `cwd?: string` signature for backward compat.
|
|
21
40
|
*/
|
|
22
|
-
export declare function loadTemplate(source: string | undefined, fallback: string,
|
|
41
|
+
export declare function loadTemplate(source: string | undefined, fallback: string, options?: SourceLoadOptions | string): Promise<string>;
|
|
23
42
|
/**
|
|
24
43
|
* Resolve issue and PR templates from config.
|
|
25
44
|
*/
|
|
26
45
|
export declare function resolveTemplates(config: {
|
|
27
46
|
issueTemplate?: string;
|
|
28
47
|
prTemplate?: string;
|
|
29
|
-
},
|
|
48
|
+
}, options?: SourceLoadOptions | string): Promise<Templates>;
|
|
30
49
|
/**
|
|
31
50
|
* Load additional context documents (local files, URLs, or inline text).
|
|
32
51
|
* Each source is resolved eagerly (including URLs) and wrapped with a header.
|
|
33
52
|
* Returns { resolved, urls } — resolved text for all sources; urls is kept
|
|
34
53
|
* for API compat but will be empty since URLs now resolve eagerly.
|
|
54
|
+
*
|
|
55
|
+
* Accepts the legacy `cwd?: string` signature for backward compat.
|
|
35
56
|
*/
|
|
36
|
-
export declare function loadAdditionalContext(sources: string[],
|
|
57
|
+
export declare function loadAdditionalContext(sources: string[], options?: SourceLoadOptions | string): Promise<{
|
|
37
58
|
resolved: string;
|
|
38
59
|
urls: string[];
|
|
39
60
|
}>;
|