@bastani/atomic 0.5.0 → 0.5.1-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/.atomic/workflows/hello/claude/index.ts +10 -11
- package/.atomic/workflows/hello/copilot/index.ts +11 -26
- package/.atomic/workflows/hello/opencode/index.ts +11 -17
- package/.atomic/workflows/hello-parallel/claude/index.ts +20 -23
- package/.atomic/workflows/hello-parallel/copilot/index.ts +21 -42
- package/.atomic/workflows/hello-parallel/opencode/index.ts +21 -31
- package/.atomic/workflows/ralph/claude/index.ts +42 -53
- package/.atomic/workflows/ralph/copilot/index.ts +36 -79
- package/.atomic/workflows/ralph/opencode/index.ts +36 -59
- package/README.md +391 -166
- package/package.json +1 -1
- package/src/sdk/define-workflow.ts +35 -19
- package/src/sdk/index.ts +4 -0
- package/src/sdk/providers/claude.ts +103 -10
- package/src/sdk/providers/copilot.ts +16 -23
- package/src/sdk/providers/opencode.ts +15 -22
- package/src/sdk/runtime/executor.ts +138 -55
- package/src/sdk/runtime/graph-inference.ts +50 -0
- package/src/sdk/types.ts +113 -38
- package/src/sdk/workflows.ts +14 -1
- package/src/services/system/workflows.ts +136 -1
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { join, sep } from "path";
|
|
17
|
-
import { readdir, rm } from "fs/promises";
|
|
17
|
+
import { lstat, readdir, rm, symlink, unlink } from "fs/promises";
|
|
18
18
|
import { homedir } from "os";
|
|
19
19
|
import {
|
|
20
20
|
copyDir,
|
|
@@ -46,6 +46,41 @@ function packageRoot(): string {
|
|
|
46
46
|
return join(import.meta.dir, "..", "..", "..");
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Safely remove a symlink or junction before re-creating it.
|
|
51
|
+
*
|
|
52
|
+
* On Windows, Bun's `rm({ recursive: true })` follows NTFS junctions and
|
|
53
|
+
* can delete the **target directory's contents** (oven-sh/bun#27233).
|
|
54
|
+
* `unlink` is safe: it opens with `FILE_FLAG_OPEN_REPARSE_POINT` and
|
|
55
|
+
* removes only the link entry, never following it.
|
|
56
|
+
*
|
|
57
|
+
* Falls back to `rm` only when the path is a real directory (not a link),
|
|
58
|
+
* which can happen if a previous version created the path as a plain copy
|
|
59
|
+
* instead of a symlink.
|
|
60
|
+
*/
|
|
61
|
+
async function removeLinkOrDir(path: string): Promise<void> {
|
|
62
|
+
try {
|
|
63
|
+
const stats = await lstat(path);
|
|
64
|
+
if (stats.isSymbolicLink()) {
|
|
65
|
+
await unlink(path);
|
|
66
|
+
} else if (stats.isDirectory()) {
|
|
67
|
+
await rm(path, { recursive: true, force: true });
|
|
68
|
+
} else {
|
|
69
|
+
await unlink(path);
|
|
70
|
+
}
|
|
71
|
+
} catch (error: unknown) {
|
|
72
|
+
// ENOENT — path doesn't exist; nothing to remove.
|
|
73
|
+
if (
|
|
74
|
+
error instanceof Error &&
|
|
75
|
+
"code" in error &&
|
|
76
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
77
|
+
) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
49
84
|
/** Honors ATOMIC_SETTINGS_HOME so tests can point at a temp dir. */
|
|
50
85
|
function homeRoot(): string {
|
|
51
86
|
return process.env.ATOMIC_SETTINGS_HOME ?? homedir();
|
|
@@ -102,4 +137,104 @@ export async function installGlobalWorkflows(): Promise<void> {
|
|
|
102
137
|
await copyDir(src, dest);
|
|
103
138
|
}
|
|
104
139
|
}
|
|
140
|
+
|
|
141
|
+
// ── Type-resolution setup for workflow authors ─────────────────────
|
|
142
|
+
//
|
|
143
|
+
// The bundled tsconfig.json uses relative `paths` that only resolve
|
|
144
|
+
// correctly inside the package's own directory tree. Once the files
|
|
145
|
+
// are copied to `~/.atomic/workflows/`, those relative paths break.
|
|
146
|
+
//
|
|
147
|
+
// Strategy: symlink `node_modules/@bastani/atomic` in the destination
|
|
148
|
+
// back to the running package root. TypeScript's standard module
|
|
149
|
+
// resolution then finds `@bastani/atomic/workflows` (and its
|
|
150
|
+
// transitive deps) automatically — no `paths` override needed.
|
|
151
|
+
//
|
|
152
|
+
// If symlink creation fails (permissions, unsupported FS), we fall
|
|
153
|
+
// back to a tsconfig with an absolute `paths` entry pointing at the
|
|
154
|
+
// package's SDK source. Either way the workflow author gets types
|
|
155
|
+
// with zero manual configuration.
|
|
156
|
+
await setupWorkflowTypes(destRoot);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Wire up TypeScript type resolution for a global workflows directory.
|
|
161
|
+
*
|
|
162
|
+
* Creates a `node_modules/@bastani/atomic` symlink → the installed
|
|
163
|
+
* package root and generates a tsconfig.json that lets standard module
|
|
164
|
+
* resolution do the work. Falls back to absolute `paths` in the
|
|
165
|
+
* tsconfig if symlinking isn't possible.
|
|
166
|
+
*/
|
|
167
|
+
export async function setupWorkflowTypes(destRoot: string): Promise<void> {
|
|
168
|
+
const pkgRoot = packageRoot();
|
|
169
|
+
let usedSymlink = false;
|
|
170
|
+
|
|
171
|
+
// 1. Symlink the package itself
|
|
172
|
+
try {
|
|
173
|
+
const scopeDir = join(destRoot, "node_modules", "@bastani");
|
|
174
|
+
await ensureDir(scopeDir);
|
|
175
|
+
|
|
176
|
+
const link = join(scopeDir, "atomic");
|
|
177
|
+
await removeLinkOrDir(link);
|
|
178
|
+
|
|
179
|
+
// Junctions on Windows need no elevated privileges.
|
|
180
|
+
const type = process.platform === "win32" ? "junction" : "dir";
|
|
181
|
+
await symlink(pkgRoot, link, type);
|
|
182
|
+
usedSymlink = true;
|
|
183
|
+
} catch {
|
|
184
|
+
// Swallow — falls back to paths-based tsconfig below.
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 2. Symlink @types/bun so `Bun.*` APIs have types in workflows
|
|
188
|
+
try {
|
|
189
|
+
const bunTypes = join(pkgRoot, "node_modules", "@types", "bun");
|
|
190
|
+
if (await pathExists(bunTypes)) {
|
|
191
|
+
const typesDir = join(destRoot, "node_modules", "@types");
|
|
192
|
+
await ensureDir(typesDir);
|
|
193
|
+
|
|
194
|
+
const link = join(typesDir, "bun");
|
|
195
|
+
await removeLinkOrDir(link);
|
|
196
|
+
|
|
197
|
+
const type = process.platform === "win32" ? "junction" : "dir";
|
|
198
|
+
await symlink(bunTypes, link, type);
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Best effort — Bun APIs in workflows lack types but runtime is fine.
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 3. Generate a clean tsconfig for the destination
|
|
205
|
+
const compilerOptions: Record<string, unknown> = {
|
|
206
|
+
target: "ESNext",
|
|
207
|
+
module: "ESNext",
|
|
208
|
+
moduleResolution: "bundler",
|
|
209
|
+
allowImportingTsExtensions: true,
|
|
210
|
+
noEmit: true,
|
|
211
|
+
verbatimModuleSyntax: true,
|
|
212
|
+
strict: true,
|
|
213
|
+
skipLibCheck: true,
|
|
214
|
+
types: ["bun"],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (!usedSymlink) {
|
|
218
|
+
// Fallback: absolute paths so TypeScript can still resolve the SDK
|
|
219
|
+
// source from the installed package location.
|
|
220
|
+
compilerOptions.paths = {
|
|
221
|
+
"@bastani/atomic": [join(pkgRoot, "src", "sdk", "index.ts")],
|
|
222
|
+
"@bastani/atomic/workflows": [join(pkgRoot, "src", "sdk", "workflows.ts")],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const tsconfig = { compilerOptions, include: GLOBAL_TSCONFIG_INCLUDE };
|
|
227
|
+
|
|
228
|
+
await Bun.write(
|
|
229
|
+
join(destRoot, "tsconfig.json"),
|
|
230
|
+
JSON.stringify(tsconfig, null, 2) + "\n",
|
|
231
|
+
);
|
|
105
232
|
}
|
|
233
|
+
|
|
234
|
+
/** Include globs shared by every generated global workflows tsconfig. */
|
|
235
|
+
const GLOBAL_TSCONFIG_INCLUDE = [
|
|
236
|
+
"**/claude/**/*.ts",
|
|
237
|
+
"**/copilot/**/*.ts",
|
|
238
|
+
"**/opencode/**/*.ts",
|
|
239
|
+
"**/helpers/**/*.ts",
|
|
240
|
+
];
|