@bastani/atomic 0.5.34 → 0.6.0-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.
- package/README.md +329 -50
- package/dist/commands/cli/session.d.ts +67 -0
- package/dist/commands/cli/session.d.ts.map +1 -0
- package/dist/commands/cli/workflow-status.d.ts +63 -0
- package/dist/commands/cli/workflow-status.d.ts.map +1 -0
- package/dist/sdk/commander.d.ts +74 -0
- package/dist/sdk/commander.d.ts.map +1 -0
- package/dist/sdk/components/workflow-picker-panel.d.ts +14 -17
- package/dist/sdk/components/workflow-picker-panel.d.ts.map +1 -1
- package/dist/sdk/define-workflow.d.ts +18 -9
- package/dist/sdk/define-workflow.d.ts.map +1 -1
- package/dist/sdk/index.d.ts +4 -3
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/management-commands.d.ts +42 -0
- package/dist/sdk/management-commands.d.ts.map +1 -0
- package/dist/sdk/registry.d.ts +27 -0
- package/dist/sdk/registry.d.ts.map +1 -0
- package/dist/sdk/runtime/attached-footer.d.ts +1 -1
- package/dist/sdk/runtime/executor-env.d.ts +20 -0
- package/dist/sdk/runtime/executor-env.d.ts.map +1 -0
- package/dist/sdk/runtime/executor.d.ts +61 -10
- package/dist/sdk/runtime/executor.d.ts.map +1 -1
- package/dist/sdk/types.d.ts +147 -4
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/worker-shared.d.ts +42 -0
- package/dist/sdk/worker-shared.d.ts.map +1 -0
- package/dist/sdk/workflow-cli.d.ts +103 -0
- package/dist/sdk/workflow-cli.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin-registry.d.ts +113 -0
- package/dist/sdk/workflows/builtin-registry.d.ts.map +1 -0
- package/dist/sdk/workflows/index.d.ts +5 -5
- package/dist/sdk/workflows/index.d.ts.map +1 -1
- package/package.json +12 -8
- package/src/cli.ts +85 -144
- package/src/commands/cli/chat/index.ts +10 -0
- package/src/commands/cli/workflow-command.test.ts +279 -938
- package/src/commands/cli/workflow-inputs.test.ts +41 -11
- package/src/commands/cli/workflow-inputs.ts +47 -12
- package/src/commands/cli/workflow-list.test.ts +234 -0
- package/src/commands/cli/workflow-list.ts +0 -0
- package/src/commands/cli/workflow.ts +11 -798
- package/src/scripts/constants.ts +2 -1
- package/src/sdk/commander.ts +161 -0
- package/src/sdk/components/workflow-picker-panel.tsx +78 -258
- package/src/sdk/define-workflow.test.ts +104 -11
- package/src/sdk/define-workflow.ts +47 -11
- package/src/sdk/errors.test.ts +16 -0
- package/src/sdk/index.ts +8 -8
- package/src/sdk/management-commands.ts +151 -0
- package/src/sdk/registry.ts +132 -0
- package/src/sdk/runtime/attached-footer.ts +1 -1
- package/src/sdk/runtime/executor-env.ts +45 -0
- package/src/sdk/runtime/executor.test.ts +37 -0
- package/src/sdk/runtime/executor.ts +147 -68
- package/src/sdk/types.ts +169 -4
- package/src/sdk/worker-shared.test.ts +163 -0
- package/src/sdk/worker-shared.ts +155 -0
- package/src/sdk/workflow-cli.ts +409 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +1 -1
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +1 -1
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +1 -1
- package/src/sdk/workflows/builtin/open-claude-design/claude/index.ts +1 -1
- package/src/sdk/workflows/builtin/open-claude-design/copilot/index.ts +1 -1
- package/src/sdk/workflows/builtin/open-claude-design/opencode/index.ts +1 -1
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +1 -1
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +1 -1
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +1 -1
- package/src/sdk/workflows/builtin-registry.ts +23 -0
- package/src/sdk/workflows/index.ts +10 -20
- package/src/services/system/auth.test.ts +63 -1
- package/.agents/skills/workflow-creator/SKILL.md +0 -334
- package/.agents/skills/workflow-creator/references/agent-sessions.md +0 -888
- package/.agents/skills/workflow-creator/references/computation-and-validation.md +0 -201
- package/.agents/skills/workflow-creator/references/control-flow.md +0 -470
- package/.agents/skills/workflow-creator/references/discovery-and-verification.md +0 -232
- package/.agents/skills/workflow-creator/references/failure-modes.md +0 -903
- package/.agents/skills/workflow-creator/references/getting-started.md +0 -275
- package/.agents/skills/workflow-creator/references/running-workflows.md +0 -235
- package/.agents/skills/workflow-creator/references/session-config.md +0 -384
- package/.agents/skills/workflow-creator/references/state-and-data-flow.md +0 -357
- package/.agents/skills/workflow-creator/references/user-input.md +0 -234
- package/.agents/skills/workflow-creator/references/workflow-inputs.md +0 -272
- package/dist/sdk/runtime/discovery.d.ts +0 -132
- package/dist/sdk/runtime/discovery.d.ts.map +0 -1
- package/dist/sdk/runtime/executor-entry.d.ts +0 -11
- package/dist/sdk/runtime/executor-entry.d.ts.map +0 -1
- package/dist/sdk/runtime/loader.d.ts +0 -70
- package/dist/sdk/runtime/loader.d.ts.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/src/commands/cli/workflow.test.ts +0 -317
- package/src/sdk/runtime/discovery.ts +0 -368
- package/src/sdk/runtime/executor-entry.ts +0 -18
- package/src/sdk/runtime/loader.ts +0 -267
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct tests for the shared worker/dispatcher helpers. The happy paths
|
|
3
|
+
* are exercised indirectly via the workflow-command harness; this file
|
|
4
|
+
* covers the error branches (unknown input, invalid enum, integer parse
|
|
5
|
+
* failure, reserved-name use, type collision) plus `stringifyDefaults`
|
|
6
|
+
* end-to-end.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { test, expect, describe } from "bun:test";
|
|
10
|
+
import type { WorkflowInput } from "./types.ts";
|
|
11
|
+
import {
|
|
12
|
+
toCamelCase,
|
|
13
|
+
validateAndResolve,
|
|
14
|
+
stringifyDefaults,
|
|
15
|
+
buildInputUnion,
|
|
16
|
+
} from "./worker-shared.ts";
|
|
17
|
+
|
|
18
|
+
describe("toCamelCase", () => {
|
|
19
|
+
test("converts hyphenated names", () => {
|
|
20
|
+
expect(toCamelCase("output-type")).toBe("outputType");
|
|
21
|
+
expect(toCamelCase("max-loops")).toBe("maxLoops");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("leaves non-hyphenated names unchanged", () => {
|
|
25
|
+
expect(toCamelCase("simple")).toBe("simple");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("validateAndResolve", () => {
|
|
30
|
+
test("throws on an input key not declared in the schema", () => {
|
|
31
|
+
const schema: WorkflowInput[] = [{ name: "foo", type: "string" }];
|
|
32
|
+
expect(() => validateAndResolve({ bar: "1" }, schema)).toThrow(
|
|
33
|
+
/Unknown input "--bar"/,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("reports valid inputs in the error message", () => {
|
|
38
|
+
const schema: WorkflowInput[] = [
|
|
39
|
+
{ name: "foo", type: "string" },
|
|
40
|
+
{ name: "bar", type: "integer" },
|
|
41
|
+
];
|
|
42
|
+
expect(() => validateAndResolve({ baz: "1" }, schema)).toThrow(
|
|
43
|
+
/--foo, --bar/,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("reports (none) for free-form schemas", () => {
|
|
48
|
+
expect(() => validateAndResolve({ bar: "1" }, [])).toThrow(
|
|
49
|
+
/\(none — free-form workflow\)/,
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("throws on missing required input", () => {
|
|
54
|
+
const schema: WorkflowInput[] = [
|
|
55
|
+
{ name: "prompt", type: "text", required: true },
|
|
56
|
+
];
|
|
57
|
+
expect(() => validateAndResolve({}, schema)).toThrow(
|
|
58
|
+
/Missing required input "--prompt"/,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("throws on invalid enum value", () => {
|
|
63
|
+
const schema: WorkflowInput[] = [
|
|
64
|
+
{ name: "mode", type: "enum", values: ["fast", "slow"] },
|
|
65
|
+
];
|
|
66
|
+
expect(() => validateAndResolve({ mode: "medium" }, schema)).toThrow(
|
|
67
|
+
/Invalid value for "--mode": "medium".*Expected one of: fast, slow/,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("throws on non-integer value for integer fields", () => {
|
|
72
|
+
const schema: WorkflowInput[] = [{ name: "loops", type: "integer" }];
|
|
73
|
+
expect(() => validateAndResolve({ loops: "3.5" }, schema)).toThrow(
|
|
74
|
+
/Expected an integer/,
|
|
75
|
+
);
|
|
76
|
+
expect(() => validateAndResolve({ loops: "abc" }, schema)).toThrow(
|
|
77
|
+
/Expected an integer/,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("fills defaults and drops empty values", () => {
|
|
82
|
+
const schema: WorkflowInput[] = [
|
|
83
|
+
{ name: "mode", type: "enum", values: ["fast", "slow"], default: "fast" },
|
|
84
|
+
{ name: "note", type: "text" },
|
|
85
|
+
];
|
|
86
|
+
const out = validateAndResolve({}, schema);
|
|
87
|
+
expect(out).toEqual({ mode: "fast" });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("picks the first enum value when no default is declared", () => {
|
|
91
|
+
const schema: WorkflowInput[] = [
|
|
92
|
+
{ name: "mode", type: "enum", values: ["a", "b"] },
|
|
93
|
+
];
|
|
94
|
+
const out = validateAndResolve({}, schema);
|
|
95
|
+
expect(out).toEqual({ mode: "a" });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("stringifyDefaults", () => {
|
|
100
|
+
test("returns undefined when defaults is undefined", () => {
|
|
101
|
+
expect(stringifyDefaults(undefined)).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("coerces numbers to strings and passes strings through", () => {
|
|
105
|
+
const out = stringifyDefaults({ a: "hello", b: 42 });
|
|
106
|
+
expect(out).toEqual({ a: "hello", b: "42" });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("drops undefined values", () => {
|
|
110
|
+
const out = stringifyDefaults({ a: "x", b: undefined });
|
|
111
|
+
expect(out).toEqual({ a: "x" });
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("buildInputUnion", () => {
|
|
116
|
+
test("merges inputs from multiple workflows by name", () => {
|
|
117
|
+
const union = buildInputUnion([
|
|
118
|
+
{
|
|
119
|
+
agent: "claude",
|
|
120
|
+
name: "wf-a",
|
|
121
|
+
inputs: [{ name: "shared", type: "string" }],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
agent: "copilot",
|
|
125
|
+
name: "wf-b",
|
|
126
|
+
inputs: [
|
|
127
|
+
{ name: "shared", type: "string" },
|
|
128
|
+
{ name: "extra", type: "integer" },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
expect(Array.from(union.keys()).sort()).toEqual(["extra", "shared"]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("throws when a workflow uses a reserved input name", () => {
|
|
136
|
+
expect(() =>
|
|
137
|
+
buildInputUnion([
|
|
138
|
+
{
|
|
139
|
+
agent: "claude",
|
|
140
|
+
name: "wf",
|
|
141
|
+
inputs: [{ name: "session", type: "string" }],
|
|
142
|
+
},
|
|
143
|
+
]),
|
|
144
|
+
).toThrow(/reserved by the worker CLI/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("throws when the same input name has conflicting types", () => {
|
|
148
|
+
expect(() =>
|
|
149
|
+
buildInputUnion([
|
|
150
|
+
{
|
|
151
|
+
agent: "claude",
|
|
152
|
+
name: "wf-a",
|
|
153
|
+
inputs: [{ name: "loops", type: "integer" }],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
agent: "copilot",
|
|
157
|
+
name: "wf-b",
|
|
158
|
+
inputs: [{ name: "loops", type: "string" }],
|
|
159
|
+
},
|
|
160
|
+
]),
|
|
161
|
+
).toThrow(/Input name conflict.*"loops"/);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the single-definition worker (`./worker.ts`) and
|
|
3
|
+
* the multi-workflow dispatcher (`./workflow-cli.ts`). These were all
|
|
4
|
+
* inlined in the original `worker.ts` before the split.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { WorkflowInput } from "./types.ts";
|
|
8
|
+
import { RESERVED_INPUT_NAMES } from "./define-workflow.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert a hyphenated option name to the camelCase key Commander uses
|
|
12
|
+
* when storing options in `opts()`. Commander applies this transform
|
|
13
|
+
* automatically: `--output-type` → `opts.outputType`.
|
|
14
|
+
*
|
|
15
|
+
* @example toCamelCase("output-type") // → "outputType"
|
|
16
|
+
* @example toCamelCase("max-loops") // → "maxLoops"
|
|
17
|
+
* @example toCamelCase("simple") // → "simple"
|
|
18
|
+
*/
|
|
19
|
+
export function toCamelCase(name: string): string {
|
|
20
|
+
return name.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate and resolve inputs against a workflow's declared schema.
|
|
25
|
+
* Throws on unknown flags, missing required fields, invalid enum values,
|
|
26
|
+
* and non-integer values for integer fields.
|
|
27
|
+
*/
|
|
28
|
+
export function validateAndResolve(
|
|
29
|
+
inputs: Record<string, string>,
|
|
30
|
+
schema: readonly WorkflowInput[],
|
|
31
|
+
): Record<string, string> {
|
|
32
|
+
const out: Record<string, string> = {};
|
|
33
|
+
const known = new Set(schema.map((i) => i.name));
|
|
34
|
+
|
|
35
|
+
for (const key of Object.keys(inputs)) {
|
|
36
|
+
if (!known.has(key)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`[atomic/worker] Unknown input "--${key}" for this workflow. ` +
|
|
39
|
+
`Valid inputs: ${schema.length > 0 ? schema.map((i) => `--${i.name}`).join(", ") : "(none — free-form workflow)"}.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const field of schema) {
|
|
45
|
+
const raw = inputs[field.name];
|
|
46
|
+
const defaultStr = field.default !== undefined ? String(field.default) : undefined;
|
|
47
|
+
const enumFirst =
|
|
48
|
+
field.type === "enum" && field.values && field.values.length > 0
|
|
49
|
+
? field.values[0]
|
|
50
|
+
: undefined;
|
|
51
|
+
const value =
|
|
52
|
+
raw !== undefined && raw !== ""
|
|
53
|
+
? raw
|
|
54
|
+
: defaultStr ?? enumFirst ?? "";
|
|
55
|
+
|
|
56
|
+
if (field.required && value.trim() === "") {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`[atomic/worker] Missing required input "--${field.name}".`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (field.type === "enum" && value !== "") {
|
|
63
|
+
const allowed = field.values ?? [];
|
|
64
|
+
if (!allowed.includes(value)) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`[atomic/worker] Invalid value for "--${field.name}": "${value}". ` +
|
|
67
|
+
`Expected one of: ${allowed.join(", ")}.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (field.type === "integer" && value !== "") {
|
|
73
|
+
const parsed = Number.parseInt(value, 10);
|
|
74
|
+
if (
|
|
75
|
+
!Number.isFinite(parsed) ||
|
|
76
|
+
!Number.isInteger(parsed) ||
|
|
77
|
+
String(parsed) !== value.trim()
|
|
78
|
+
) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`[atomic/worker] Invalid value for "--${field.name}": "${value}". Expected an integer.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (value !== "") {
|
|
86
|
+
out[field.name] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Coerce a typed defaults map (InputsOf shape — string | number values)
|
|
94
|
+
* into the string-valued map the executor and CLI flag layer consume.
|
|
95
|
+
* Returns `undefined` when `defaults` is `undefined` so callers can
|
|
96
|
+
* skip merging entirely.
|
|
97
|
+
*/
|
|
98
|
+
export function stringifyDefaults(
|
|
99
|
+
defaults: Record<string, string | number | undefined> | undefined,
|
|
100
|
+
): Record<string, string> | undefined {
|
|
101
|
+
if (!defaults) return undefined;
|
|
102
|
+
const out: Record<string, string> = {};
|
|
103
|
+
for (const [k, v] of Object.entries(defaults)) {
|
|
104
|
+
if (v === undefined) continue;
|
|
105
|
+
out[k] = typeof v === "string" ? v : String(v);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Inspect every workflow in a registry snapshot and build a
|
|
112
|
+
* union-of-inputs map. Throws on reserved-name use and on same-name /
|
|
113
|
+
* different-type collisions. Returns the deduplicated union map
|
|
114
|
+
* (name → WorkflowInput). Used by the multi-workflow dispatcher to
|
|
115
|
+
* expose a single set of flags across heterogeneous registry entries.
|
|
116
|
+
*/
|
|
117
|
+
export function buildInputUnion(
|
|
118
|
+
workflows: readonly {
|
|
119
|
+
readonly agent: string;
|
|
120
|
+
readonly name: string;
|
|
121
|
+
readonly inputs: readonly WorkflowInput[];
|
|
122
|
+
}[],
|
|
123
|
+
): Map<string, WorkflowInput> {
|
|
124
|
+
const union = new Map<string, WorkflowInput>();
|
|
125
|
+
const origin = new Map<string, { agent: string; name: string }>();
|
|
126
|
+
|
|
127
|
+
for (const wf of workflows) {
|
|
128
|
+
for (const input of wf.inputs) {
|
|
129
|
+
if ((RESERVED_INPUT_NAMES as readonly string[]).includes(input.name)) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`[atomic/worker] Workflow "${wf.agent}/${wf.name}" declares input ` +
|
|
132
|
+
`"${input.name}" which is reserved by the worker CLI. ` +
|
|
133
|
+
`Reserved names: ${(RESERVED_INPUT_NAMES as readonly string[]).join(", ")}.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const existing = union.get(input.name);
|
|
138
|
+
if (existing) {
|
|
139
|
+
if (existing.type !== input.type) {
|
|
140
|
+
const first = origin.get(input.name)!;
|
|
141
|
+
throw new Error(
|
|
142
|
+
`[atomic/worker] Input name conflict: "${input.name}" is declared as ` +
|
|
143
|
+
`"${existing.type}" in "${first.agent}/${first.name}" but as ` +
|
|
144
|
+
`"${input.type}" in "${wf.agent}/${wf.name}". ` +
|
|
145
|
+
`Workflows sharing an input name must agree on the type.`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
union.set(input.name, input);
|
|
150
|
+
origin.set(input.name, { agent: wf.agent, name: wf.name });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return union;
|
|
155
|
+
}
|