atomism 0.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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/chunk-34O5KJWR.js +81 -0
- package/dist/chunk-34O5KJWR.js.map +1 -0
- package/dist/chunk-55AP34JO.js +116 -0
- package/dist/chunk-55AP34JO.js.map +1 -0
- package/dist/chunk-6MDHM2B4.js +17 -0
- package/dist/chunk-6MDHM2B4.js.map +1 -0
- package/dist/chunk-GU2R4KLP.js +43 -0
- package/dist/chunk-GU2R4KLP.js.map +1 -0
- package/dist/chunk-H7WC3NXZ.js +39 -0
- package/dist/chunk-H7WC3NXZ.js.map +1 -0
- package/dist/chunk-P33CQFMY.js +329 -0
- package/dist/chunk-P33CQFMY.js.map +1 -0
- package/dist/chunk-P6X7T4KA.js +200 -0
- package/dist/chunk-P6X7T4KA.js.map +1 -0
- package/dist/chunk-PLQJM2KT.js +9 -0
- package/dist/chunk-PLQJM2KT.js.map +1 -0
- package/dist/chunk-RS2IEGW3.js +10 -0
- package/dist/chunk-RS2IEGW3.js.map +1 -0
- package/dist/chunk-S6Z5G5DB.js +84 -0
- package/dist/chunk-S6Z5G5DB.js.map +1 -0
- package/dist/chunk-UVUDQ4XP.js +259 -0
- package/dist/chunk-UVUDQ4XP.js.map +1 -0
- package/dist/chunk-UWVZQSP4.js +597 -0
- package/dist/chunk-UWVZQSP4.js.map +1 -0
- package/dist/chunk-YKJO3ZFY.js +308 -0
- package/dist/chunk-YKJO3ZFY.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +152 -0
- package/dist/cli.js.map +1 -0
- package/dist/create-atom-AXPDBYQL.js +153 -0
- package/dist/create-atom-AXPDBYQL.js.map +1 -0
- package/dist/escalate-BTEJT5NL.js +211 -0
- package/dist/escalate-BTEJT5NL.js.map +1 -0
- package/dist/extract-RPKCTINT.js +514 -0
- package/dist/extract-RPKCTINT.js.map +1 -0
- package/dist/graduate-453M7ZRQ.js +222 -0
- package/dist/graduate-453M7ZRQ.js.map +1 -0
- package/dist/helpers-PJPFPYBQ.js +11 -0
- package/dist/helpers-PJPFPYBQ.js.map +1 -0
- package/dist/history-OPD7NLZW.js +258 -0
- package/dist/history-OPD7NLZW.js.map +1 -0
- package/dist/import-generator-4CKRBMTE.js +1864 -0
- package/dist/import-generator-4CKRBMTE.js.map +1 -0
- package/dist/index.d.ts +230 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-2FINDMYK.js +741 -0
- package/dist/init-2FINDMYK.js.map +1 -0
- package/dist/list-NEBVBGG3.js +71 -0
- package/dist/list-NEBVBGG3.js.map +1 -0
- package/dist/parser-3BILOSOO.js +157 -0
- package/dist/parser-3BILOSOO.js.map +1 -0
- package/dist/plan-DNVARHWH.js +249 -0
- package/dist/plan-DNVARHWH.js.map +1 -0
- package/dist/register-XTRMSH7Y.js +91 -0
- package/dist/register-XTRMSH7Y.js.map +1 -0
- package/dist/revert-J4CRDE2K.js +87 -0
- package/dist/revert-J4CRDE2K.js.map +1 -0
- package/dist/run-3GI3SBYL.js +188 -0
- package/dist/run-3GI3SBYL.js.map +1 -0
- package/dist/scan-generators-ST4TBEY7.js +375 -0
- package/dist/scan-generators-ST4TBEY7.js.map +1 -0
- package/dist/signatures-K5QIL4WG.js +258 -0
- package/dist/signatures-K5QIL4WG.js.map +1 -0
- package/dist/skills-assign-IHOXX4AI.js +182 -0
- package/dist/skills-assign-IHOXX4AI.js.map +1 -0
- package/dist/skills-load-JSD5UG2K.js +20 -0
- package/dist/skills-load-JSD5UG2K.js.map +1 -0
- package/dist/skills-scan-WACJFRJN.js +25 -0
- package/dist/skills-scan-WACJFRJN.js.map +1 -0
- package/dist/skills-suggest-JFI2NUJI.js +269 -0
- package/dist/skills-suggest-JFI2NUJI.js.map +1 -0
- package/dist/status-KQVSAZFR.js +111 -0
- package/dist/status-KQVSAZFR.js.map +1 -0
- package/dist/suggest-IFFJQFIW.js +183 -0
- package/dist/suggest-IFFJQFIW.js.map +1 -0
- package/dist/test-HP3FG3MO.js +152 -0
- package/dist/test-HP3FG3MO.js.map +1 -0
- package/dist/test-gen-2ZGPOP35.js +347 -0
- package/dist/test-gen-2ZGPOP35.js.map +1 -0
- package/dist/trust-4R26DULG.js +248 -0
- package/dist/trust-4R26DULG.js.map +1 -0
- package/dist/validate-generator-46H2LYYQ.js +410 -0
- package/dist/validate-generator-46H2LYYQ.js.map +1 -0
- package/dist/workflow-5UVLBS7J.js +655 -0
- package/dist/workflow-5UVLBS7J.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeGenerator,
|
|
3
|
+
loadAtomFromFile,
|
|
4
|
+
loadGeneratorFromFile
|
|
5
|
+
} from "./chunk-P6X7T4KA.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveAtomPath
|
|
8
|
+
} from "./chunk-RS2IEGW3.js";
|
|
9
|
+
import {
|
|
10
|
+
ATOMIC_DIR,
|
|
11
|
+
REGISTRY_FILE,
|
|
12
|
+
RegistryFile,
|
|
13
|
+
completeRun,
|
|
14
|
+
createRun,
|
|
15
|
+
fileExists
|
|
16
|
+
} from "./chunk-YKJO3ZFY.js";
|
|
17
|
+
import {
|
|
18
|
+
buildSkillContext
|
|
19
|
+
} from "./chunk-6MDHM2B4.js";
|
|
20
|
+
import {
|
|
21
|
+
toErrorMessage
|
|
22
|
+
} from "./chunk-PLQJM2KT.js";
|
|
23
|
+
|
|
24
|
+
// src/atom/execute.ts
|
|
25
|
+
import { join } from "path";
|
|
26
|
+
import { readFile } from "fs/promises";
|
|
27
|
+
|
|
28
|
+
// src/atom/context.ts
|
|
29
|
+
import { randomUUID } from "crypto";
|
|
30
|
+
import { createHash } from "crypto";
|
|
31
|
+
function createAtomContext(options = {}) {
|
|
32
|
+
const runId = options.runId ?? randomUUID();
|
|
33
|
+
const logs = [];
|
|
34
|
+
const artifacts = /* @__PURE__ */ new Map();
|
|
35
|
+
const pathToHash = /* @__PURE__ */ new Map();
|
|
36
|
+
const wrapError = (message, opts = {}) => {
|
|
37
|
+
const error = {
|
|
38
|
+
type: opts.type ?? "execution",
|
|
39
|
+
message,
|
|
40
|
+
recoverable: opts.recoverable ?? false,
|
|
41
|
+
suggestion: opts.suggestion,
|
|
42
|
+
context: opts.context
|
|
43
|
+
};
|
|
44
|
+
if (opts.cause !== void 0) {
|
|
45
|
+
error.context = {
|
|
46
|
+
...error.context,
|
|
47
|
+
cause: toErrorMessage(opts.cause)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return error;
|
|
51
|
+
};
|
|
52
|
+
const log = (level, message, data) => {
|
|
53
|
+
logs.push({
|
|
54
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
55
|
+
level,
|
|
56
|
+
message,
|
|
57
|
+
data
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const artifactRefs = /* @__PURE__ */ new Map();
|
|
61
|
+
const textHashes = /* @__PURE__ */ new Set();
|
|
62
|
+
const storeArtifact = async (path, content, contentType) => {
|
|
63
|
+
const buffer = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
64
|
+
const hash = createHash("sha256").update(buffer).digest("hex");
|
|
65
|
+
const ref = {
|
|
66
|
+
hash,
|
|
67
|
+
path,
|
|
68
|
+
contentType: contentType ?? (typeof content === "string" ? "text/plain" : "application/octet-stream"),
|
|
69
|
+
size: buffer.length,
|
|
70
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
71
|
+
};
|
|
72
|
+
if (typeof content === "string") {
|
|
73
|
+
textHashes.add(hash);
|
|
74
|
+
}
|
|
75
|
+
artifactRefs.set(hash, ref);
|
|
76
|
+
if (options.storageBackend) {
|
|
77
|
+
await options.storageBackend.store(hash, buffer);
|
|
78
|
+
} else {
|
|
79
|
+
artifacts.set(hash, { content: buffer, ref });
|
|
80
|
+
}
|
|
81
|
+
pathToHash.set(path, hash);
|
|
82
|
+
return ref;
|
|
83
|
+
};
|
|
84
|
+
const getArtifact = async (hashOrPath) => {
|
|
85
|
+
const hash = pathToHash.get(hashOrPath) ?? hashOrPath;
|
|
86
|
+
if (options.storageBackend) {
|
|
87
|
+
const content = await options.storageBackend.get(hash);
|
|
88
|
+
if (!content) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const ref = artifactRefs.get(hash);
|
|
92
|
+
if (textHashes.has(hash) || ref?.contentType.startsWith("text/")) {
|
|
93
|
+
return content.toString("utf-8");
|
|
94
|
+
}
|
|
95
|
+
return content;
|
|
96
|
+
}
|
|
97
|
+
const artifact = artifacts.get(hash);
|
|
98
|
+
if (!artifact) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
if (textHashes.has(hash) || artifact.ref.contentType.startsWith("text/")) {
|
|
102
|
+
return artifact.content.toString("utf-8");
|
|
103
|
+
}
|
|
104
|
+
return artifact.content;
|
|
105
|
+
};
|
|
106
|
+
const getLogs = () => [...logs];
|
|
107
|
+
const skills = options.skills ?? [];
|
|
108
|
+
const skillContext = buildSkillContext(skills);
|
|
109
|
+
return {
|
|
110
|
+
runId,
|
|
111
|
+
workflowId: options.workflowId,
|
|
112
|
+
signal: options.signal,
|
|
113
|
+
skills,
|
|
114
|
+
skillContext,
|
|
115
|
+
wrapError,
|
|
116
|
+
log,
|
|
117
|
+
storeArtifact,
|
|
118
|
+
getArtifact,
|
|
119
|
+
getLogs
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/atom/execute.ts
|
|
124
|
+
function getAtomSkillNames(atomEntry) {
|
|
125
|
+
const skills = atomEntry.skills ? [...atomEntry.skills] : [];
|
|
126
|
+
if (atomEntry.skill && !skills.includes(atomEntry.skill)) {
|
|
127
|
+
skills.push(atomEntry.skill);
|
|
128
|
+
}
|
|
129
|
+
return skills;
|
|
130
|
+
}
|
|
131
|
+
async function executeAtom(target, input = {}, projectRoot = process.cwd(), skillLoader) {
|
|
132
|
+
const registryPath = join(projectRoot, ATOMIC_DIR, REGISTRY_FILE);
|
|
133
|
+
if (!await fileExists(registryPath)) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
atomName: target,
|
|
137
|
+
error: "Storage not initialized. Run `atomic init` first."
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
let registry;
|
|
141
|
+
try {
|
|
142
|
+
const registryContent = await readFile(registryPath, "utf-8");
|
|
143
|
+
const registryData = JSON.parse(registryContent);
|
|
144
|
+
if ("capabilities" in registryData) {
|
|
145
|
+
delete registryData["capabilities"];
|
|
146
|
+
}
|
|
147
|
+
registry = RegistryFile.parse(registryData);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
atomName: target,
|
|
152
|
+
error: `Failed to load registry: ${toErrorMessage(err)}`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const atomEntry = registry.atoms[target];
|
|
156
|
+
if (!atomEntry) {
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
atomName: target,
|
|
160
|
+
error: `Atom '${target}' not found in registry.`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (atomEntry.graduated && atomEntry.generatorPath) {
|
|
164
|
+
return executeGeneratorAtom(target, atomEntry, input, projectRoot);
|
|
165
|
+
}
|
|
166
|
+
return executeHandlerAtom(target, atomEntry, input, projectRoot, skillLoader);
|
|
167
|
+
}
|
|
168
|
+
async function executeGeneratorAtom(target, atomEntry, input, projectRoot) {
|
|
169
|
+
const generatorPath = atomEntry.generatorPath;
|
|
170
|
+
const generatorName = `${target}Generator`;
|
|
171
|
+
const fullGeneratorPath = resolveAtomPath(generatorPath, projectRoot);
|
|
172
|
+
if (!await fileExists(fullGeneratorPath)) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
atomName: target,
|
|
176
|
+
error: `Generator file not found: ${fullGeneratorPath}`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
let generator;
|
|
180
|
+
try {
|
|
181
|
+
generator = await loadGeneratorFromFile(fullGeneratorPath, generatorName);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return {
|
|
184
|
+
success: false,
|
|
185
|
+
atomName: target,
|
|
186
|
+
error: toErrorMessage(err)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
let runId;
|
|
190
|
+
try {
|
|
191
|
+
runId = await createRun(projectRoot, target, input);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
atomName: target,
|
|
196
|
+
error: `Failed to create run record: ${toErrorMessage(err)}`
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const genResult = executeGenerator(generator, input);
|
|
201
|
+
if (genResult.success) {
|
|
202
|
+
await completeRun(projectRoot, runId, "success");
|
|
203
|
+
return {
|
|
204
|
+
success: true,
|
|
205
|
+
atomName: `${target} (generator: ${generator.name})`,
|
|
206
|
+
output: genResult.output
|
|
207
|
+
};
|
|
208
|
+
} else {
|
|
209
|
+
const errorMsg = genResult.validation.errors.join("; ") || genResult.error || "Unknown error";
|
|
210
|
+
await completeRun(projectRoot, runId, "error", errorMsg);
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
atomName: `${target} (generator: ${generator.name})`,
|
|
214
|
+
error: errorMsg
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
const errorMsg = toErrorMessage(err);
|
|
219
|
+
await completeRun(projectRoot, runId, "error", errorMsg);
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
atomName: target,
|
|
223
|
+
error: errorMsg
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function executeHandlerAtom(target, atomEntry, input, projectRoot, skillLoader) {
|
|
228
|
+
const atomPath = resolveAtomPath(atomEntry.path, projectRoot);
|
|
229
|
+
if (!await fileExists(atomPath)) {
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
atomName: target,
|
|
233
|
+
error: `Atom file not found: ${atomPath}`
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
let atom;
|
|
237
|
+
try {
|
|
238
|
+
atom = await loadAtomFromFile(atomPath);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
atomName: target,
|
|
243
|
+
error: toErrorMessage(err)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const inputResult = atom.input.safeParse(input);
|
|
247
|
+
if (!inputResult.success) {
|
|
248
|
+
const issues = inputResult.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
atomName: atom.name,
|
|
252
|
+
error: `Input validation failed:
|
|
253
|
+
${issues}`
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const skillNames = getAtomSkillNames(atomEntry);
|
|
257
|
+
const loadSkills = skillLoader ?? (async (names) => {
|
|
258
|
+
const mod = await import("./skills-load-JSD5UG2K.js");
|
|
259
|
+
return mod.loadSkillsForAtom(names);
|
|
260
|
+
});
|
|
261
|
+
const skillLoadResult = await loadSkills(skillNames);
|
|
262
|
+
const skillContexts = skillLoadResult.loaded.map(
|
|
263
|
+
(skill) => ({
|
|
264
|
+
name: skill.name,
|
|
265
|
+
content: skill.content,
|
|
266
|
+
source: skill.source
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
const context = createAtomContext({ skills: skillContexts });
|
|
270
|
+
let runId;
|
|
271
|
+
try {
|
|
272
|
+
runId = await createRun(projectRoot, atom.name, inputResult.data);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
atomName: atom.name,
|
|
277
|
+
error: `Failed to create run record: ${toErrorMessage(err)}`
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const result = await atom.handler(inputResult.data, context);
|
|
282
|
+
if (result.success) {
|
|
283
|
+
const outputResult = atom.output.safeParse(result.data);
|
|
284
|
+
if (!outputResult.success) {
|
|
285
|
+
const issues = outputResult.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
286
|
+
await completeRun(projectRoot, runId, "error", `Output validation failed:
|
|
287
|
+
${issues}`);
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
atomName: atom.name,
|
|
291
|
+
error: `Output validation failed:
|
|
292
|
+
${issues}`,
|
|
293
|
+
skillWarnings: skillLoadResult.warnings
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
await completeRun(projectRoot, runId, "success");
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
atomName: atom.name,
|
|
300
|
+
output: result.data,
|
|
301
|
+
skillWarnings: skillLoadResult.warnings.length > 0 ? skillLoadResult.warnings : void 0
|
|
302
|
+
};
|
|
303
|
+
} else {
|
|
304
|
+
const errorMsg = result.error.message;
|
|
305
|
+
await completeRun(projectRoot, runId, "error", errorMsg);
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
atomName: atom.name,
|
|
309
|
+
error: errorMsg,
|
|
310
|
+
skillWarnings: skillLoadResult.warnings.length > 0 ? skillLoadResult.warnings : void 0
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
} catch (err) {
|
|
314
|
+
const errorMsg = toErrorMessage(err);
|
|
315
|
+
await completeRun(projectRoot, runId, "error", errorMsg);
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
atomName: atom.name,
|
|
319
|
+
error: errorMsg,
|
|
320
|
+
skillWarnings: skillLoadResult.warnings.length > 0 ? skillLoadResult.warnings : void 0
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export {
|
|
326
|
+
getAtomSkillNames,
|
|
327
|
+
executeAtom
|
|
328
|
+
};
|
|
329
|
+
//# sourceMappingURL=chunk-P33CQFMY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/atom/execute.ts","../src/atom/context.ts"],"sourcesContent":["/**\n * Shared atom execution logic.\n *\n * Extracts the core execution mechanics from commands/run.ts into a\n * reusable function for both direct `atomic run` and workflow execution.\n *\n * @module atom/execute\n */\n\nimport { join } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { ATOMIC_DIR, REGISTRY_FILE, fileExists, createRun, completeRun } from '../storage/index.js';\nimport { RegistryFile, type RegistryAtom } from '../schemas/registry.js';\nimport { createAtomContext, type SkillContext } from './context.js';\nimport type { AtomResult } from '../schemas/index.js';\nimport { executeGenerator } from '../generator/index.js';\nimport { loadAtomFromFile, loadGeneratorFromFile } from './loader.js';\nimport type { LoadedSkill, SkillLoadResult, SkillLoader } from './skills-load.js';\nimport { toErrorMessage } from '../utils/errors.js';\nimport { resolveAtomPath } from '../utils/paths.js';\n\n/**\n * Result of executing a single atom.\n */\nexport interface AtomExecutionResult {\n success: boolean;\n atomName: string;\n output?: unknown;\n error?: string;\n artifacts?: string[];\n /** Skill loading warnings. */\n skillWarnings?: string[];\n}\n\n/**\n * Get skill names from an atom registry entry.\n * Handles both legacy 'skill' field and new 'skills' array.\n */\nexport function getAtomSkillNames(atomEntry: RegistryAtom): string[] {\n const skills: string[] = atomEntry.skills ? [...atomEntry.skills] : [];\n if (atomEntry.skill && !skills.includes(atomEntry.skill)) {\n skills.push(atomEntry.skill);\n }\n return skills;\n}\n\n/**\n * Execute a single atom by name.\n *\n * Loads the atom from the registry, creates execution context with skills,\n * runs the handler (or generator if graduated), and returns the result.\n *\n * @param target - Atom name to execute\n * @param input - Input data (already parsed JSON)\n * @param projectRoot - Project root directory (defaults to cwd)\n * @param skillLoader - Optional skill loader (defaults to commands/skills-load)\n */\nexport async function executeAtom(\n target: string,\n input: unknown = {},\n projectRoot: string = process.cwd(),\n skillLoader?: SkillLoader\n): Promise<AtomExecutionResult> {\n const registryPath = join(projectRoot, ATOMIC_DIR, REGISTRY_FILE);\n\n // Check if initialized\n if (!(await fileExists(registryPath))) {\n return {\n success: false,\n atomName: target,\n error: 'Storage not initialized. Run `atomic init` first.',\n };\n }\n\n // Load registry\n let registry: RegistryFile;\n try {\n const registryContent = await readFile(registryPath, 'utf-8');\n const registryData = JSON.parse(registryContent) as Record<string, unknown>;\n\n // Handle migration: remove capabilities if present (old format)\n if ('capabilities' in registryData) {\n delete registryData['capabilities'];\n }\n\n registry = RegistryFile.parse(registryData);\n } catch (err) {\n return {\n success: false,\n atomName: target,\n error: `Failed to load registry: ${toErrorMessage(err)}`,\n };\n }\n\n // Look up atom\n const atomEntry = registry.atoms[target];\n if (!atomEntry) {\n return {\n success: false,\n atomName: target,\n error: `Atom '${target}' not found in registry.`,\n };\n }\n\n // Generator-based execution (graduated atoms)\n if (atomEntry.graduated && atomEntry.generatorPath) {\n return executeGeneratorAtom(target, atomEntry, input, projectRoot);\n }\n\n // Handler-based execution\n return executeHandlerAtom(target, atomEntry, input, projectRoot, skillLoader);\n}\n\n/**\n * Execute a graduated atom using its generator.\n */\nasync function executeGeneratorAtom(\n target: string,\n atomEntry: RegistryAtom,\n input: unknown,\n projectRoot: string\n): Promise<AtomExecutionResult> {\n const generatorPath = atomEntry.generatorPath!;\n const generatorName = `${target}Generator`;\n\n const fullGeneratorPath = resolveAtomPath(generatorPath, projectRoot);\n\n if (!(await fileExists(fullGeneratorPath))) {\n return {\n success: false,\n atomName: target,\n error: `Generator file not found: ${fullGeneratorPath}`,\n };\n }\n\n let generator;\n try {\n generator = await loadGeneratorFromFile(fullGeneratorPath, generatorName);\n } catch (err) {\n return {\n success: false,\n atomName: target,\n error: toErrorMessage(err),\n };\n }\n\n // Create run record\n let runId: string;\n try {\n runId = await createRun(projectRoot, target, input);\n } catch (err) {\n return {\n success: false,\n atomName: target,\n error: `Failed to create run record: ${toErrorMessage(err)}`,\n };\n }\n\n try {\n const genResult = executeGenerator(generator, input);\n\n if (genResult.success) {\n await completeRun(projectRoot, runId, 'success');\n return {\n success: true,\n atomName: `${target} (generator: ${generator.name})`,\n output: genResult.output,\n };\n } else {\n const errorMsg = genResult.validation.errors.join('; ') || genResult.error || 'Unknown error';\n await completeRun(projectRoot, runId, 'error', errorMsg);\n return {\n success: false,\n atomName: `${target} (generator: ${generator.name})`,\n error: errorMsg,\n };\n }\n } catch (err) {\n const errorMsg = toErrorMessage(err);\n await completeRun(projectRoot, runId, 'error', errorMsg);\n return {\n success: false,\n atomName: target,\n error: errorMsg,\n };\n }\n}\n\n/**\n * Execute a handler-based atom.\n */\nasync function executeHandlerAtom(\n target: string,\n atomEntry: RegistryAtom,\n input: unknown,\n projectRoot: string,\n skillLoader?: SkillLoader\n): Promise<AtomExecutionResult> {\n const atomPath = resolveAtomPath(atomEntry.path, projectRoot);\n\n if (!(await fileExists(atomPath))) {\n return {\n success: false,\n atomName: target,\n error: `Atom file not found: ${atomPath}`,\n };\n }\n\n let atom;\n try {\n atom = await loadAtomFromFile(atomPath);\n } catch (err) {\n return {\n success: false,\n atomName: target,\n error: toErrorMessage(err),\n };\n }\n\n // Validate input against schema\n const inputResult = atom.input.safeParse(input);\n if (!inputResult.success) {\n const issues = inputResult.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n return {\n success: false,\n atomName: atom.name,\n error: `Input validation failed:\\n${issues}`,\n };\n }\n\n // Load skills (use injected loader, or default to commands/skills-load)\n const skillNames = getAtomSkillNames(atomEntry);\n const loadSkills = skillLoader ?? (async (names: string[]): Promise<SkillLoadResult> => {\n const mod = await import('../commands/skills-load.js');\n return mod.loadSkillsForAtom(names);\n });\n const skillLoadResult = await loadSkills(skillNames);\n\n const skillContexts: SkillContext[] = skillLoadResult.loaded.map(\n (skill: LoadedSkill) => ({\n name: skill.name,\n content: skill.content,\n source: skill.source,\n })\n );\n\n // Create execution context\n const context = createAtomContext({ skills: skillContexts });\n\n // Create run record\n let runId: string;\n try {\n runId = await createRun(projectRoot, atom.name, inputResult.data);\n } catch (err) {\n return {\n success: false,\n atomName: atom.name,\n error: `Failed to create run record: ${toErrorMessage(err)}`,\n };\n }\n\n try {\n const result = await atom.handler(inputResult.data, context) as AtomResult<unknown>;\n\n // Validate output if successful\n if (result.success) {\n const outputResult = atom.output.safeParse(result.data);\n if (!outputResult.success) {\n const issues = outputResult.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n await completeRun(projectRoot, runId, 'error', `Output validation failed:\\n${issues}`);\n return {\n success: false,\n atomName: atom.name,\n error: `Output validation failed:\\n${issues}`,\n skillWarnings: skillLoadResult.warnings,\n };\n }\n\n await completeRun(projectRoot, runId, 'success');\n return {\n success: true,\n atomName: atom.name,\n output: result.data,\n skillWarnings: skillLoadResult.warnings.length > 0 ? skillLoadResult.warnings : undefined,\n };\n } else {\n const errorMsg = result.error.message;\n await completeRun(projectRoot, runId, 'error', errorMsg);\n return {\n success: false,\n atomName: atom.name,\n error: errorMsg,\n skillWarnings: skillLoadResult.warnings.length > 0 ? skillLoadResult.warnings : undefined,\n };\n }\n } catch (err) {\n const errorMsg = toErrorMessage(err);\n await completeRun(projectRoot, runId, 'error', errorMsg);\n return {\n success: false,\n atomName: atom.name,\n error: errorMsg,\n skillWarnings: skillLoadResult.warnings.length > 0 ? skillLoadResult.warnings : undefined,\n };\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport { createHash } from 'node:crypto';\nimport type { AtomError } from '../schemas/atom.js';\nimport { buildSkillContext } from './skill-context.js';\nimport { toErrorMessage } from '../utils/errors.js';\n\n/**\n * Context module for atom execution.\n *\n * Provides a factory function to create enhanced runtime contexts for atom\n * handlers with utilities for error handling, logging, and artifact storage.\n *\n * @module context\n */\n\n/**\n * Log entry for run logs\n */\nexport interface LogEntry {\n timestamp: string;\n level: 'debug' | 'info' | 'warn' | 'error';\n message: string;\n data?: Record<string, unknown>;\n}\n\n/**\n * Artifact reference stored in content-addressed storage\n */\nexport interface ArtifactRef {\n hash: string;\n path: string;\n contentType: string;\n size: number;\n createdAt: string;\n}\n\n/**\n * Skill context available during execution.\n */\nexport interface SkillContext {\n /** Name of the skill */\n name: string;\n /** Skill instructions/content */\n content: string;\n /** Where the skill was loaded from */\n source: 'project' | 'global' | 'mcp';\n}\n\n/**\n * Enhanced context provided to atom handlers during execution.\n *\n * This context provides utilities for:\n * - Error wrapping with structured AtomError\n * - Logging to run logs\n * - Storing and retrieving artifacts\n * - Accessing run metadata\n * - Accessing loaded skills\n */\nexport interface AtomContext {\n /** Unique identifier for this run */\n runId: string;\n\n /** Workflow ID if this atom is part of a workflow */\n workflowId?: string;\n\n /** Abort signal for cancellation support */\n signal?: AbortSignal;\n\n /** Loaded skills available for this execution */\n skills: SkillContext[];\n\n /** Combined skill instructions as a single context string */\n skillContext: string;\n\n /**\n * Wrap an error in a structured AtomError format\n */\n wrapError: (\n message: string,\n options?: {\n type?: AtomError['type'];\n recoverable?: boolean;\n suggestion?: string;\n context?: Record<string, unknown>;\n cause?: unknown;\n }\n ) => AtomError;\n\n /**\n * Log a message to the run log\n */\n log: (\n level: LogEntry['level'],\n message: string,\n data?: Record<string, unknown>\n ) => void;\n\n /**\n * Store an artifact in content-addressed storage\n */\n storeArtifact: (\n path: string,\n content: string | Buffer,\n contentType?: string\n ) => Promise<ArtifactRef>;\n\n /**\n * Retrieve an artifact from content-addressed storage\n */\n getArtifact: (hashOrPath: string) => Promise<string | Buffer | null>;\n\n /**\n * Access the run logs\n */\n getLogs: () => LogEntry[];\n}\n\n/**\n * Configuration for creating an atom context\n */\nexport interface CreateContextOptions {\n runId?: string;\n workflowId?: string;\n signal?: AbortSignal;\n /** Storage backend for artifacts (in-memory if not provided) */\n storageBackend?: {\n store: (hash: string, content: Buffer) => Promise<void>;\n get: (hash: string) => Promise<Buffer | null>;\n };\n /** Loaded skills to include in context */\n skills?: SkillContext[];\n}\n\n/**\n * Create an enhanced atom context with all utilities.\n *\n * Creates a runtime context for atom handlers with:\n * - Unique run ID for tracking execution\n * - Error wrapping with structured AtomError format\n * - Run logging at debug/info/warn/error levels\n * - Content-addressed artifact storage (in-memory or pluggable backend)\n *\n * @param options - Configuration options for the context\n * @param options.runId - Custom run ID (auto-generated UUID if not provided)\n * @param options.workflowId - Optional workflow ID if atom is part of a workflow\n * @param options.signal - Optional AbortSignal for cancellation support\n * @param options.storageBackend - Optional external storage for artifacts\n * @returns AtomContext with all utilities initialized\n *\n * @example\n * ```typescript\n * const ctx = createAtomContext({ runId: 'my-run-123' });\n * ctx.log('info', 'Starting execution');\n * const ref = await ctx.storeArtifact('output.txt', 'Hello, World!');\n * ```\n */\nexport function createAtomContext(options: CreateContextOptions = {}): AtomContext {\n const runId = options.runId ?? randomUUID();\n const logs: LogEntry[] = [];\n const artifacts: Map<string, { content: Buffer; ref: ArtifactRef }> = new Map();\n const pathToHash: Map<string, string> = new Map();\n\n /**\n * Wraps an error message in a structured AtomError format.\n */\n const wrapError: AtomContext['wrapError'] = (message, opts = {}) => {\n const error: AtomError = {\n type: opts.type ?? 'execution',\n message,\n recoverable: opts.recoverable ?? false,\n suggestion: opts.suggestion,\n context: opts.context,\n };\n\n // Include cause in context if provided\n if (opts.cause !== undefined) {\n error.context = {\n ...error.context,\n cause: toErrorMessage(opts.cause),\n };\n }\n\n return error;\n };\n\n /**\n * Logs a message with optional structured data.\n */\n const log: AtomContext['log'] = (level, message, data) => {\n logs.push({\n timestamp: new Date().toISOString(),\n level,\n message,\n data,\n });\n };\n\n // Track artifact metadata for consistent text retrieval across backends\n const artifactRefs: Map<string, ArtifactRef> = new Map();\n const textHashes: Set<string> = new Set();\n\n /**\n * Stores an artifact with content-addressed hashing.\n * Text content is tracked for consistent string retrieval.\n */\n const storeArtifact: AtomContext['storeArtifact'] = async (path, content, contentType) => {\n const buffer = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content;\n const hash = createHash('sha256').update(buffer).digest('hex');\n\n const ref: ArtifactRef = {\n hash,\n path,\n contentType: contentType ?? (typeof content === 'string' ? 'text/plain' : 'application/octet-stream'),\n size: buffer.length,\n createdAt: new Date().toISOString(),\n };\n\n // Track text artifacts for consistent retrieval\n if (typeof content === 'string') {\n textHashes.add(hash);\n }\n artifactRefs.set(hash, ref);\n\n // Store in backend if provided, otherwise in-memory\n if (options.storageBackend) {\n await options.storageBackend.store(hash, buffer);\n } else {\n artifacts.set(hash, { content: buffer, ref });\n }\n\n pathToHash.set(path, hash);\n\n return ref;\n };\n\n /**\n * Retrieves an artifact by hash or path.\n * Returns string for text artifacts, Buffer for binary.\n */\n const getArtifact: AtomContext['getArtifact'] = async (hashOrPath) => {\n // Check if it's a path first\n const hash = pathToHash.get(hashOrPath) ?? hashOrPath;\n\n // Try backend first\n if (options.storageBackend) {\n const content = await options.storageBackend.get(hash);\n if (!content) {\n return null;\n }\n // Convert to string if originally stored as text\n const ref = artifactRefs.get(hash);\n if (textHashes.has(hash) || ref?.contentType.startsWith('text/')) {\n return content.toString('utf-8');\n }\n return content;\n }\n\n // Fall back to in-memory\n const artifact = artifacts.get(hash);\n if (!artifact) {\n return null;\n }\n\n // Return as string if it's text, otherwise as Buffer\n if (textHashes.has(hash) || artifact.ref.contentType.startsWith('text/')) {\n return artifact.content.toString('utf-8');\n }\n return artifact.content;\n };\n\n /**\n * Returns a copy of all log entries.\n */\n const getLogs: AtomContext['getLogs'] = () => [...logs];\n\n // Build skill context from loaded skills\n const skills = options.skills ?? [];\n const skillContext = buildSkillContext(skills);\n\n return {\n runId,\n workflowId: options.workflowId,\n signal: options.signal,\n skills,\n skillContext,\n wrapError,\n log,\n storeArtifact,\n getArtifact,\n getLogs,\n };\n}\n\n// Re-export the simplified AtomContext type for the define module\nexport type { AtomContext as ContextType };\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toErrorMessage
|
|
3
|
+
} from "./chunk-PLQJM2KT.js";
|
|
4
|
+
|
|
5
|
+
// src/atom/define.ts
|
|
6
|
+
function isZodSchema(value) {
|
|
7
|
+
if (value === null || typeof value !== "object") {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const obj = value;
|
|
11
|
+
return "safeParse" in obj && typeof obj["safeParse"] === "function";
|
|
12
|
+
}
|
|
13
|
+
var SNAKE_CASE_REGEX = /^[a-z][a-z0-9_]*$/;
|
|
14
|
+
function defineAtom(config) {
|
|
15
|
+
if (!SNAKE_CASE_REGEX.test(config.name)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Atom name "${config.name}" must be snake_case (lowercase letters, numbers, underscores, starting with a letter)`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (!config.description || config.description.trim().length === 0) {
|
|
21
|
+
throw new Error("Atom description is required and cannot be empty");
|
|
22
|
+
}
|
|
23
|
+
if (!config.tests?.path || config.tests.path.trim().length === 0) {
|
|
24
|
+
throw new Error("Atom tests.path is required");
|
|
25
|
+
}
|
|
26
|
+
if (!isZodSchema(config.input)) {
|
|
27
|
+
throw new Error("Atom input must be a Zod schema");
|
|
28
|
+
}
|
|
29
|
+
if (!isZodSchema(config.output)) {
|
|
30
|
+
throw new Error("Atom output must be a Zod schema");
|
|
31
|
+
}
|
|
32
|
+
if (config.tests.coverage_threshold !== void 0 && (!Number.isFinite(config.tests.coverage_threshold) || config.tests.coverage_threshold < 0 || config.tests.coverage_threshold > 100)) {
|
|
33
|
+
throw new Error("Atom tests.coverage_threshold must be between 0 and 100");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
name: config.name,
|
|
37
|
+
description: config.description.trim(),
|
|
38
|
+
input: config.input,
|
|
39
|
+
output: config.output,
|
|
40
|
+
idempotent: config.idempotent ?? true,
|
|
41
|
+
tests: {
|
|
42
|
+
path: config.tests.path.trim(),
|
|
43
|
+
coverage_threshold: config.tests.coverage_threshold
|
|
44
|
+
},
|
|
45
|
+
agent: config.agent,
|
|
46
|
+
skill: config.skill,
|
|
47
|
+
handler: config.handler
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function isAtom(value) {
|
|
51
|
+
if (value === null || typeof value !== "object") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const obj = value;
|
|
55
|
+
return typeof obj["name"] === "string" && typeof obj["description"] === "string" && typeof obj["idempotent"] === "boolean" && typeof obj["handler"] === "function" && isZodSchema(obj["input"]) && isZodSchema(obj["output"]) && obj["tests"] !== null && typeof obj["tests"] === "object" && typeof obj["tests"]["path"] === "string";
|
|
56
|
+
}
|
|
57
|
+
function success(data) {
|
|
58
|
+
return { success: true, data };
|
|
59
|
+
}
|
|
60
|
+
function failure(error) {
|
|
61
|
+
return { success: false, error };
|
|
62
|
+
}
|
|
63
|
+
function validationError(message, context) {
|
|
64
|
+
return failure({
|
|
65
|
+
type: "validation",
|
|
66
|
+
message,
|
|
67
|
+
recoverable: true,
|
|
68
|
+
suggestion: "Check input data and try again",
|
|
69
|
+
context
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function executionError(message, recoverable = false, suggestion, context) {
|
|
73
|
+
return failure({
|
|
74
|
+
type: "execution",
|
|
75
|
+
message,
|
|
76
|
+
recoverable,
|
|
77
|
+
suggestion,
|
|
78
|
+
context
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/atom/loader.ts
|
|
83
|
+
import { createJiti } from "jiti";
|
|
84
|
+
|
|
85
|
+
// src/generator/index.ts
|
|
86
|
+
import { z } from "zod";
|
|
87
|
+
function invalidResult(errors, warnings = []) {
|
|
88
|
+
return { valid: false, errors, warnings };
|
|
89
|
+
}
|
|
90
|
+
function executeGenerator(generator, params) {
|
|
91
|
+
const parseResult = generator.params.safeParse(params);
|
|
92
|
+
if (!parseResult.success) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
validation: invalidResult(
|
|
96
|
+
parseResult.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
97
|
+
),
|
|
98
|
+
error: "Parameter validation failed"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
let output;
|
|
102
|
+
try {
|
|
103
|
+
output = generator.template(parseResult.data);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
validation: invalidResult([`Template execution failed: ${err}`]),
|
|
108
|
+
error: "Template execution failed"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const validation = generator.validate(output);
|
|
112
|
+
return {
|
|
113
|
+
success: validation.valid,
|
|
114
|
+
output,
|
|
115
|
+
validation,
|
|
116
|
+
error: validation.valid ? void 0 : "Output validation failed"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
var GeneratorSchema = z.object({
|
|
120
|
+
name: z.string().min(1, "Generator name is required"),
|
|
121
|
+
description: z.string().optional(),
|
|
122
|
+
params: z.any(),
|
|
123
|
+
// Zod schemas can't be validated by Zod
|
|
124
|
+
template: z.any(),
|
|
125
|
+
// Functions validated at runtime by validateGenerator
|
|
126
|
+
validate: z.any(),
|
|
127
|
+
// Functions validated at runtime by validateGenerator
|
|
128
|
+
language: z.enum(["typescript", "python", "sql", "markdown"]),
|
|
129
|
+
graduatedFrom: z.string().optional(),
|
|
130
|
+
createdAt: z.date().optional()
|
|
131
|
+
});
|
|
132
|
+
function isGenerator(value) {
|
|
133
|
+
if (!value || typeof value !== "object") return false;
|
|
134
|
+
const g = value;
|
|
135
|
+
return typeof g["name"] === "string" && typeof g["template"] === "function" && typeof g["validate"] === "function" && g["params"] !== null && typeof g["params"] === "object" && typeof g["language"] === "string";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/atom/loader.ts
|
|
139
|
+
async function loadAtomFromFile(filePath) {
|
|
140
|
+
let module;
|
|
141
|
+
try {
|
|
142
|
+
const jiti = createJiti(import.meta.url);
|
|
143
|
+
module = await jiti.import(filePath);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Failed to load atom module: ${toErrorMessage(err)}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (module["default"] && isAtom(module["default"])) {
|
|
150
|
+
return module["default"];
|
|
151
|
+
}
|
|
152
|
+
for (const key of Object.keys(module)) {
|
|
153
|
+
if (isAtom(module[key])) {
|
|
154
|
+
return module[key];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw new Error(
|
|
158
|
+
`No atom found in file: ${filePath}
|
|
159
|
+
The file must export an atom created with defineAtom().`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
async function loadGeneratorFromFile(filePath, name) {
|
|
163
|
+
let genModule;
|
|
164
|
+
try {
|
|
165
|
+
const jiti = createJiti(import.meta.url);
|
|
166
|
+
genModule = await jiti.import(filePath);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Failed to load generator module: ${toErrorMessage(err)}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (genModule[name] && isGenerator(genModule[name])) {
|
|
173
|
+
return genModule[name];
|
|
174
|
+
}
|
|
175
|
+
if (genModule["default"] && isGenerator(genModule["default"])) {
|
|
176
|
+
return genModule["default"];
|
|
177
|
+
}
|
|
178
|
+
for (const key of Object.keys(genModule)) {
|
|
179
|
+
if (isGenerator(genModule[key])) {
|
|
180
|
+
return genModule[key];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw new Error(
|
|
184
|
+
`No generator '${name}' found in file: ${filePath}
|
|
185
|
+
The file must export a generator created with createGenerator().`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export {
|
|
190
|
+
defineAtom,
|
|
191
|
+
isAtom,
|
|
192
|
+
success,
|
|
193
|
+
failure,
|
|
194
|
+
validationError,
|
|
195
|
+
executionError,
|
|
196
|
+
executeGenerator,
|
|
197
|
+
loadAtomFromFile,
|
|
198
|
+
loadGeneratorFromFile
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=chunk-P6X7T4KA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/atom/define.ts","../src/atom/loader.ts","../src/generator/index.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { AtomResult, AtomError } from '../schemas/atom.js';\n\n// Duck-type check for Zod schemas (works across different Zod instances)\nfunction isZodSchema(value: unknown): value is z.ZodType {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n const obj = value as Record<string, unknown>;\n return 'safeParse' in obj && typeof obj['safeParse'] === 'function';\n}\n\n// Re-export the enhanced context type from context.ts\nexport type { AtomContext } from './context.js';\n\n// Import the actual type for use in this module\nimport type { AtomContext } from './context.js';\n\n// Configuration for defining an atom\nexport interface AtomConfig<\n TInput extends z.ZodType,\n TOutput extends z.ZodType,\n> {\n name: string;\n description: string;\n input: TInput;\n output: TOutput;\n idempotent?: boolean;\n tests: {\n path: string;\n coverage_threshold?: number;\n };\n agent?: string;\n skill?: string;\n handler: (\n input: z.infer<TInput>,\n context: AtomContext\n ) => Promise<AtomResult<z.infer<TOutput>>>;\n}\n\n// The fully defined atom with validated configuration\nexport interface Atom<\n TInput extends z.ZodType,\n TOutput extends z.ZodType,\n> {\n name: string;\n description: string;\n input: TInput;\n output: TOutput;\n idempotent: boolean;\n tests: {\n path: string;\n coverage_threshold?: number;\n };\n agent?: string;\n skill?: string;\n handler: (\n input: z.infer<TInput>,\n context: AtomContext\n ) => Promise<AtomResult<z.infer<TOutput>>>;\n}\n\n// Validation regex for snake_case names\nconst SNAKE_CASE_REGEX = /^[a-z][a-z0-9_]*$/;\n\n/**\n * Define a type-safe atom with full TypeScript inference.\n *\n * @example\n * ```ts\n * const greetAtom = defineAtom({\n * name: 'greet_user',\n * description: 'Greets a user by name',\n * input: z.object({ name: z.string() }),\n * output: z.object({ greeting: z.string() }),\n * tests: { path: './tests/greet.test.ts' },\n * handler: async (input, ctx) => {\n * return {\n * success: true,\n * data: { greeting: `Hello, ${input.name}!` }\n * };\n * }\n * });\n * ```\n */\nexport function defineAtom<\n TInput extends z.ZodType,\n TOutput extends z.ZodType,\n>(config: AtomConfig<TInput, TOutput>): Atom<TInput, TOutput> {\n // Validate name is snake_case\n if (!SNAKE_CASE_REGEX.test(config.name)) {\n throw new Error(\n `Atom name \"${config.name}\" must be snake_case (lowercase letters, numbers, underscores, starting with a letter)`\n );\n }\n\n // Validate description is non-empty\n if (!config.description || config.description.trim().length === 0) {\n throw new Error('Atom description is required and cannot be empty');\n }\n\n // Validate tests.path is provided and not whitespace-only\n if (!config.tests?.path || config.tests.path.trim().length === 0) {\n throw new Error('Atom tests.path is required');\n }\n\n // Validate input is a Zod schema (duck-type check for cross-instance compatibility)\n if (!isZodSchema(config.input)) {\n throw new Error('Atom input must be a Zod schema');\n }\n\n // Validate output is a Zod schema (duck-type check for cross-instance compatibility)\n if (!isZodSchema(config.output)) {\n throw new Error('Atom output must be a Zod schema');\n }\n\n // Validate coverage_threshold if provided (handle NaN and out-of-bounds)\n if (\n config.tests.coverage_threshold !== undefined &&\n (!Number.isFinite(config.tests.coverage_threshold) ||\n config.tests.coverage_threshold < 0 ||\n config.tests.coverage_threshold > 100)\n ) {\n throw new Error('Atom tests.coverage_threshold must be between 0 and 100');\n }\n\n return {\n name: config.name,\n description: config.description.trim(),\n input: config.input,\n output: config.output,\n idempotent: config.idempotent ?? true,\n tests: {\n path: config.tests.path.trim(),\n coverage_threshold: config.tests.coverage_threshold,\n },\n agent: config.agent,\n skill: config.skill,\n handler: config.handler,\n };\n}\n\n/**\n * Duck-type check for the atom shape returned by defineAtom.\n * Verifies input/output schemas since execute.ts calls safeParse immediately.\n */\nexport function isAtom(value: unknown): value is Atom<z.ZodType, z.ZodType> {\n if (value === null || typeof value !== 'object') {\n return false;\n }\n const obj = value as Record<string, unknown>;\n return (\n typeof obj['name'] === 'string' &&\n typeof obj['description'] === 'string' &&\n typeof obj['idempotent'] === 'boolean' &&\n typeof obj['handler'] === 'function' &&\n isZodSchema(obj['input']) &&\n isZodSchema(obj['output']) &&\n obj['tests'] !== null &&\n typeof obj['tests'] === 'object' &&\n typeof (obj['tests'] as Record<string, unknown>)['path'] === 'string'\n );\n}\n\n// Helper to create success result\nexport function success<T>(data: T): AtomResult<T> {\n return { success: true, data };\n}\n\n// Helper to create error result\nexport function failure(error: AtomError): AtomResult<never> {\n return { success: false, error };\n}\n\n// Helper to create a validation error\nexport function validationError(\n message: string,\n context?: Record<string, unknown>\n): AtomResult<never> {\n return failure({\n type: 'validation',\n message,\n recoverable: true,\n suggestion: 'Check input data and try again',\n context,\n });\n}\n\n// Helper to create an execution error\nexport function executionError(\n message: string,\n recoverable = false,\n suggestion?: string,\n context?: Record<string, unknown>\n): AtomResult<never> {\n return failure({\n type: 'execution',\n message,\n recoverable,\n suggestion,\n context,\n });\n}\n","/**\n * Shared utilities for loading atoms and generators from files via jiti.\n *\n * Extracts the duplicated \"create jiti, import module, duck-type search\n * for atom/generator export\" pattern that was copy-pasted across\n * run.ts, register.ts, test-gen.ts, and execute.ts.\n *\n * @module atom/loader\n */\n\nimport { createJiti } from 'jiti';\nimport { z } from 'zod';\nimport { isAtom, type Atom } from './define.js';\nimport { isGenerator, type Generator } from '../generator/index.js';\nimport { toErrorMessage } from '../utils/errors.js';\n\n/**\n * Load an atom from a file path using jiti.\n *\n * Imports the module and searches for an atom export:\n * 1. Checks the default export\n * 2. Iterates named exports for the first value passing isAtom()\n *\n * @param filePath - Absolute path to the atom file\n * @returns The loaded Atom\n * @throws Error if the module cannot be imported or no atom export is found\n */\nexport async function loadAtomFromFile(\n filePath: string\n): Promise<Atom<z.ZodType, z.ZodType>> {\n let module: Record<string, unknown>;\n try {\n const jiti = createJiti(import.meta.url);\n module = (await jiti.import(filePath)) as Record<string, unknown>;\n } catch (err) {\n throw new Error(\n `Failed to load atom module: ${toErrorMessage(err)}`\n );\n }\n\n // Check default export first\n if (module['default'] && isAtom(module['default'])) {\n return module['default'] as Atom<z.ZodType, z.ZodType>;\n }\n\n // Search named exports\n for (const key of Object.keys(module)) {\n if (isAtom(module[key])) {\n return module[key] as Atom<z.ZodType, z.ZodType>;\n }\n }\n\n throw new Error(\n `No atom found in file: ${filePath}\\n` +\n `The file must export an atom created with defineAtom().`\n );\n}\n\n/**\n * Load a generator from a file path using jiti.\n *\n * Imports the module and searches for a generator export:\n * 1. Checks a named export matching the given name\n * 2. Checks the default export\n * 3. Iterates named exports for the first value passing isGenerator()\n *\n * @param filePath - Absolute path to the generator file\n * @param name - Expected generator export name (e.g., \"myAtomGenerator\")\n * @returns The loaded Generator\n * @throws Error if the module cannot be imported or no generator export is found\n */\nexport async function loadGeneratorFromFile(\n filePath: string,\n name: string\n): Promise<Generator> {\n let genModule: Record<string, unknown>;\n try {\n const jiti = createJiti(import.meta.url);\n genModule = (await jiti.import(filePath)) as Record<string, unknown>;\n } catch (err) {\n throw new Error(\n `Failed to load generator module: ${toErrorMessage(err)}`\n );\n }\n\n // Check named export matching the expected name first\n if (genModule[name] && isGenerator(genModule[name])) {\n return genModule[name] as Generator;\n }\n\n // Check default export\n if (genModule['default'] && isGenerator(genModule['default'])) {\n return genModule['default'] as Generator;\n }\n\n // Search all named exports\n for (const key of Object.keys(genModule)) {\n if (isGenerator(genModule[key])) {\n return genModule[key] as Generator;\n }\n }\n\n throw new Error(\n `No generator '${name}' found in file: ${filePath}\\n` +\n `The file must export a generator created with createGenerator().`\n );\n}\n","/**\n * Generator module for the atomic framework.\n *\n * Generators are deterministic code producers that replace probabilistic atoms\n * once patterns have been detected and validated.\n *\n * @module generator\n */\n\nimport { z } from 'zod';\n\n/**\n * Supported languages for generator output validation.\n */\nexport type GeneratorLanguage = 'typescript' | 'python' | 'sql' | 'markdown';\n\n/**\n * A generator that produces deterministic output from validated parameters.\n *\n * Generators are the \"graduated\" form of atoms - once an atom's outputs\n * converge on a pattern, it can be replaced by a generator that produces\n * the same output deterministically.\n *\n * @template T - The type of the params schema\n */\nexport interface Generator<T extends z.ZodType = z.ZodType> {\n /** Unique name for this generator */\n name: string;\n /** Description of what this generator produces */\n description?: string;\n /** Zod schema for validating input parameters */\n params: T;\n /** Template function that produces output from validated params */\n template: (params: z.infer<T>) => string;\n /** Validation function that checks the generated output */\n validate: (output: string) => ValidationResult;\n /** Language of the generated output (determines parser for validation) */\n language: GeneratorLanguage;\n /** Original atom name this generator was graduated from */\n graduatedFrom?: string;\n /** Timestamp when this generator was created */\n createdAt?: Date;\n}\n\n/**\n * Result of validating generated output.\n */\nexport interface ValidationResult {\n /** Whether the output is valid */\n valid: boolean;\n /** Error messages if validation failed */\n errors: string[];\n /** Warnings that don't prevent validity */\n warnings: string[];\n}\n\n/**\n * Result of executing a generator.\n */\nexport interface GeneratorResult {\n /** Whether generation succeeded */\n success: boolean;\n /** The generated output (if successful) */\n output?: string;\n /** Validation result */\n validation: ValidationResult;\n /** Error message if generation failed */\n error?: string;\n}\n\n/**\n * Create a valid ValidationResult indicating success.\n */\nexport function validResult(warnings: string[] = []): ValidationResult {\n return { valid: true, errors: [], warnings };\n}\n\n/**\n * Create a ValidationResult indicating failure.\n */\nexport function invalidResult(errors: string[], warnings: string[] = []): ValidationResult {\n return { valid: false, errors, warnings };\n}\n\n/**\n * Validate TypeScript code using basic syntax checks.\n *\n * Note: For full AST validation, ts-morph would be used in production.\n * This implementation provides basic structural validation by counting\n * brackets/braces/parens. It cannot detect ordering issues (e.g., `}{`\n * would pass) or other syntax errors that require actual parsing.\n * This is sufficient for catching common template errors but is not\n * a substitute for TypeScript compilation.\n *\n * @param code - TypeScript code to validate\n * @returns ValidationResult\n */\nexport function validateTypeScript(code: string): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Basic bracket/brace/paren matching\n const openBrackets = (code.match(/\\{/g) || []).length;\n const closeBrackets = (code.match(/\\}/g) || []).length;\n if (openBrackets !== closeBrackets) {\n errors.push(`Mismatched braces: ${openBrackets} open, ${closeBrackets} close`);\n }\n\n const openParens = (code.match(/\\(/g) || []).length;\n const closeParens = (code.match(/\\)/g) || []).length;\n if (openParens !== closeParens) {\n errors.push(`Mismatched parentheses: ${openParens} open, ${closeParens} close`);\n }\n\n const openSquare = (code.match(/\\[/g) || []).length;\n const closeSquare = (code.match(/\\]/g) || []).length;\n if (openSquare !== closeSquare) {\n errors.push(`Mismatched brackets: ${openSquare} open, ${closeSquare} close`);\n }\n\n // Check for unclosed string literals (simplified)\n const lines = code.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n // Skip lines that are likely multiline strings or comments\n if (line.includes('`') || line.trim().startsWith('//') || line.trim().startsWith('*')) {\n continue;\n }\n // Count unescaped quotes\n const singleQuotes = (line.match(/(?<!\\\\)'/g) || []).length;\n const doubleQuotes = (line.match(/(?<!\\\\)\"/g) || []).length;\n if (singleQuotes % 2 !== 0) {\n warnings.push(`Line ${i + 1}: Possibly unclosed single quote`);\n }\n if (doubleQuotes % 2 !== 0) {\n warnings.push(`Line ${i + 1}: Possibly unclosed double quote`);\n }\n }\n\n return errors.length > 0 ? invalidResult(errors, warnings) : validResult(warnings);\n}\n\n/**\n * Validate Python code using basic syntax checks.\n *\n * @param code - Python code to validate\n * @returns ValidationResult\n */\nexport function validatePython(code: string): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Basic bracket/paren matching\n const openParens = (code.match(/\\(/g) || []).length;\n const closeParens = (code.match(/\\)/g) || []).length;\n if (openParens !== closeParens) {\n errors.push(`Mismatched parentheses: ${openParens} open, ${closeParens} close`);\n }\n\n const openSquare = (code.match(/\\[/g) || []).length;\n const closeSquare = (code.match(/\\]/g) || []).length;\n if (openSquare !== closeSquare) {\n errors.push(`Mismatched brackets: ${openSquare} open, ${closeSquare} close`);\n }\n\n const openBraces = (code.match(/\\{/g) || []).length;\n const closeBraces = (code.match(/\\}/g) || []).length;\n if (openBraces !== closeBraces) {\n errors.push(`Mismatched braces: ${openBraces} open, ${closeBraces} close`);\n }\n\n // Check for common Python syntax issues\n const lines = code.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n const trimmed = line.trim();\n\n // Check for missing colons after control structures\n if (/^(if|elif|else|for|while|def|class|try|except|finally|with)\\b/.test(trimmed)) {\n if (!trimmed.endsWith(':') && !trimmed.includes('#')) {\n warnings.push(`Line ${i + 1}: Control structure may be missing colon`);\n }\n }\n }\n\n return errors.length > 0 ? invalidResult(errors, warnings) : validResult(warnings);\n}\n\n/**\n * Validate SQL code using basic syntax checks.\n *\n * @param code - SQL code to validate\n * @returns ValidationResult\n */\nexport function validateSQL(code: string): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Basic paren matching\n const openParens = (code.match(/\\(/g) || []).length;\n const closeParens = (code.match(/\\)/g) || []).length;\n if (openParens !== closeParens) {\n errors.push(`Mismatched parentheses: ${openParens} open, ${closeParens} close`);\n }\n\n // Check for common SQL keywords\n const upperCode = code.toUpperCase();\n const hasSelect = upperCode.includes('SELECT');\n const hasInsert = upperCode.includes('INSERT');\n const hasUpdate = upperCode.includes('UPDATE');\n const hasDelete = upperCode.includes('DELETE');\n const hasCreate = upperCode.includes('CREATE');\n const hasAlter = upperCode.includes('ALTER');\n const hasDrop = upperCode.includes('DROP');\n\n // If it has none of these, it might not be SQL\n if (!hasSelect && !hasInsert && !hasUpdate && !hasDelete && !hasCreate && !hasAlter && !hasDrop) {\n warnings.push('No common SQL keywords found (SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP)');\n }\n\n // Check for unterminated statements (simplified)\n const statements = code.split(';').filter((s) => s.trim().length > 0);\n if (statements.length > 0 && !code.trim().endsWith(';')) {\n warnings.push('SQL statement may be missing semicolon terminator');\n }\n\n return errors.length > 0 ? invalidResult(errors, warnings) : validResult(warnings);\n}\n\n/**\n * Validate Markdown using basic structure checks.\n *\n * @param code - Markdown content to validate\n * @returns ValidationResult\n */\nexport function validateMarkdown(code: string): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Check for unclosed code blocks\n const codeBlockMarkers = (code.match(/```/g) || []).length;\n if (codeBlockMarkers % 2 !== 0) {\n errors.push('Unclosed code block (odd number of ``` markers)');\n }\n\n // Check for broken links (simplified - just check syntax)\n const linkPattern = /\\[([^\\]]*)\\]\\(([^)]*)\\)/g;\n let match;\n while ((match = linkPattern.exec(code)) !== null) {\n const [, , url] = match;\n if (!url || url.trim().length === 0) {\n warnings.push(`Empty link URL found`);\n }\n }\n\n // Check for heading structure\n const lines = code.split('\\n');\n let lastHeadingLevel = 0;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n const headingMatch = line.match(/^(#{1,6})\\s/);\n if (headingMatch) {\n const level = headingMatch[1]!.length;\n if (level > lastHeadingLevel + 1 && lastHeadingLevel > 0) {\n warnings.push(`Line ${i + 1}: Heading level jumps from H${lastHeadingLevel} to H${level}`);\n }\n lastHeadingLevel = level;\n }\n }\n\n return errors.length > 0 ? invalidResult(errors, warnings) : validResult(warnings);\n}\n\n/**\n * Get the appropriate validator for a language.\n *\n * @param language - The language to validate\n * @returns Validation function\n */\nexport function getValidator(language: GeneratorLanguage): (code: string) => ValidationResult {\n switch (language) {\n case 'typescript':\n return validateTypeScript;\n case 'python':\n return validatePython;\n case 'sql':\n return validateSQL;\n case 'markdown':\n return validateMarkdown;\n }\n}\n\n/**\n * Execute a generator with the given parameters.\n *\n * @param generator - The generator to execute\n * @param params - Parameters to pass to the generator\n * @returns GeneratorResult\n *\n * @example\n * ```typescript\n * const result = executeGenerator(apiRouteGenerator, {\n * resourceName: 'users',\n * fields: ['id', 'name', 'email'],\n * });\n *\n * if (result.success) {\n * console.log(result.output);\n * }\n * ```\n */\nexport function executeGenerator<T extends z.ZodType>(\n generator: Generator<T>,\n params: unknown\n): GeneratorResult {\n // Validate params against schema\n const parseResult = generator.params.safeParse(params);\n if (!parseResult.success) {\n return {\n success: false,\n validation: invalidResult(\n parseResult.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`)\n ),\n error: 'Parameter validation failed',\n };\n }\n\n // Generate output\n let output: string;\n try {\n output = generator.template(parseResult.data);\n } catch (err) {\n return {\n success: false,\n validation: invalidResult([`Template execution failed: ${err}`]),\n error: 'Template execution failed',\n };\n }\n\n // Validate output\n const validation = generator.validate(output);\n\n return {\n success: validation.valid,\n output,\n validation,\n error: validation.valid ? undefined : 'Output validation failed',\n };\n}\n\n/**\n * Create a generator with automatic validation based on language.\n *\n * This is a convenience function that sets up the validate function\n * based on the specified language.\n *\n * @param config - Generator configuration without validate function\n * @returns Complete Generator with validation\n *\n * @example\n * ```typescript\n * const apiRouteGenerator = createGenerator({\n * name: 'api-route',\n * params: z.object({\n * resourceName: z.string(),\n * fields: z.array(z.string()),\n * }),\n * template: ({ resourceName, fields }) => `\n * export async function get${resourceName}() {\n * return { ${fields.join(', ')} };\n * }\n * `,\n * language: 'typescript',\n * });\n * ```\n */\nexport function createGenerator<T extends z.ZodType>(\n config: Omit<Generator<T>, 'validate'> & { validate?: Generator<T>['validate'] }\n): Generator<T> {\n const validate = config.validate ?? getValidator(config.language);\n return {\n ...config,\n validate,\n createdAt: config.createdAt ?? new Date(),\n };\n}\n\n/**\n * Zod schema for validating generator definitions.\n *\n * Note: Zod schemas and functions are validated as z.any() since\n * Zod cannot introspect these types. Use validateGenerator() for\n * more thorough runtime validation.\n */\nexport const GeneratorSchema = z.object({\n name: z.string().min(1, 'Generator name is required'),\n description: z.string().optional(),\n params: z.any(), // Zod schemas can't be validated by Zod\n template: z.any(), // Functions validated at runtime by validateGenerator\n validate: z.any(), // Functions validated at runtime by validateGenerator\n language: z.enum(['typescript', 'python', 'sql', 'markdown']),\n graduatedFrom: z.string().optional(),\n createdAt: z.date().optional(),\n});\n\n/**\n * Check if a value looks like a Generator.\n */\nexport function isGenerator(value: unknown): value is Generator {\n if (!value || typeof value !== 'object') return false;\n const g = value as Record<string, unknown>;\n return (\n typeof g['name'] === 'string' &&\n typeof g['template'] === 'function' &&\n typeof g['validate'] === 'function' &&\n g['params'] !== null && typeof g['params'] === 'object' &&\n typeof g['language'] === 'string'\n );\n}\n\n/**\n * Validate a generator definition.\n *\n * @param generator - Generator to validate\n * @returns Validation result\n */\nexport function validateGenerator(generator: unknown): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!generator || typeof generator !== 'object') {\n return invalidResult(['Generator must be an object']);\n }\n\n const g = generator as Record<string, unknown>;\n\n // Required fields\n if (!g['name'] || typeof g['name'] !== 'string') {\n errors.push('name: must be a non-empty string');\n }\n\n if (!g['params']) {\n errors.push('params: Zod schema is required');\n } else if (\n typeof g['params'] !== 'object' ||\n typeof (g['params'] as Record<string, unknown>)['safeParse'] !== 'function'\n ) {\n errors.push('params: must be a Zod schema');\n }\n\n if (typeof g['template'] !== 'function') {\n errors.push('template: must be a function');\n }\n\n if (typeof g['validate'] !== 'function') {\n errors.push('validate: must be a function');\n }\n\n const validLanguages = ['typescript', 'python', 'sql', 'markdown'];\n if (!validLanguages.includes(g['language'] as string)) {\n errors.push(`language: must be one of ${validLanguages.join(', ')}`);\n }\n\n // Optional fields with type checks\n if (g['description'] !== undefined && typeof g['description'] !== 'string') {\n warnings.push('description: should be a string if provided');\n }\n\n if (g['graduatedFrom'] !== undefined && typeof g['graduatedFrom'] !== 'string') {\n warnings.push('graduatedFrom: should be a string if provided');\n }\n\n if (g['createdAt'] !== undefined && !(g['createdAt'] instanceof Date)) {\n warnings.push('createdAt: should be a Date if provided');\n }\n\n return errors.length > 0 ? invalidResult(errors, warnings) : validResult(warnings);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/errors.ts"],"sourcesContent":["/**\n * Convert an unknown caught value to an error message string.\n *\n * Replaces the common pattern: `err instanceof Error ? err.message : String(err)`\n */\nexport function toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n"],"mappings":";AAKO,SAAS,eAAe,KAAsB;AACnD,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;","names":[]}
|