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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/dist/chunk-34O5KJWR.js +81 -0
  4. package/dist/chunk-34O5KJWR.js.map +1 -0
  5. package/dist/chunk-55AP34JO.js +116 -0
  6. package/dist/chunk-55AP34JO.js.map +1 -0
  7. package/dist/chunk-6MDHM2B4.js +17 -0
  8. package/dist/chunk-6MDHM2B4.js.map +1 -0
  9. package/dist/chunk-GU2R4KLP.js +43 -0
  10. package/dist/chunk-GU2R4KLP.js.map +1 -0
  11. package/dist/chunk-H7WC3NXZ.js +39 -0
  12. package/dist/chunk-H7WC3NXZ.js.map +1 -0
  13. package/dist/chunk-P33CQFMY.js +329 -0
  14. package/dist/chunk-P33CQFMY.js.map +1 -0
  15. package/dist/chunk-P6X7T4KA.js +200 -0
  16. package/dist/chunk-P6X7T4KA.js.map +1 -0
  17. package/dist/chunk-PLQJM2KT.js +9 -0
  18. package/dist/chunk-PLQJM2KT.js.map +1 -0
  19. package/dist/chunk-RS2IEGW3.js +10 -0
  20. package/dist/chunk-RS2IEGW3.js.map +1 -0
  21. package/dist/chunk-S6Z5G5DB.js +84 -0
  22. package/dist/chunk-S6Z5G5DB.js.map +1 -0
  23. package/dist/chunk-UVUDQ4XP.js +259 -0
  24. package/dist/chunk-UVUDQ4XP.js.map +1 -0
  25. package/dist/chunk-UWVZQSP4.js +597 -0
  26. package/dist/chunk-UWVZQSP4.js.map +1 -0
  27. package/dist/chunk-YKJO3ZFY.js +308 -0
  28. package/dist/chunk-YKJO3ZFY.js.map +1 -0
  29. package/dist/cli.d.ts +1 -0
  30. package/dist/cli.js +152 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/create-atom-AXPDBYQL.js +153 -0
  33. package/dist/create-atom-AXPDBYQL.js.map +1 -0
  34. package/dist/escalate-BTEJT5NL.js +211 -0
  35. package/dist/escalate-BTEJT5NL.js.map +1 -0
  36. package/dist/extract-RPKCTINT.js +514 -0
  37. package/dist/extract-RPKCTINT.js.map +1 -0
  38. package/dist/graduate-453M7ZRQ.js +222 -0
  39. package/dist/graduate-453M7ZRQ.js.map +1 -0
  40. package/dist/helpers-PJPFPYBQ.js +11 -0
  41. package/dist/helpers-PJPFPYBQ.js.map +1 -0
  42. package/dist/history-OPD7NLZW.js +258 -0
  43. package/dist/history-OPD7NLZW.js.map +1 -0
  44. package/dist/import-generator-4CKRBMTE.js +1864 -0
  45. package/dist/import-generator-4CKRBMTE.js.map +1 -0
  46. package/dist/index.d.ts +230 -0
  47. package/dist/index.js +41 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/init-2FINDMYK.js +741 -0
  50. package/dist/init-2FINDMYK.js.map +1 -0
  51. package/dist/list-NEBVBGG3.js +71 -0
  52. package/dist/list-NEBVBGG3.js.map +1 -0
  53. package/dist/parser-3BILOSOO.js +157 -0
  54. package/dist/parser-3BILOSOO.js.map +1 -0
  55. package/dist/plan-DNVARHWH.js +249 -0
  56. package/dist/plan-DNVARHWH.js.map +1 -0
  57. package/dist/register-XTRMSH7Y.js +91 -0
  58. package/dist/register-XTRMSH7Y.js.map +1 -0
  59. package/dist/revert-J4CRDE2K.js +87 -0
  60. package/dist/revert-J4CRDE2K.js.map +1 -0
  61. package/dist/run-3GI3SBYL.js +188 -0
  62. package/dist/run-3GI3SBYL.js.map +1 -0
  63. package/dist/scan-generators-ST4TBEY7.js +375 -0
  64. package/dist/scan-generators-ST4TBEY7.js.map +1 -0
  65. package/dist/signatures-K5QIL4WG.js +258 -0
  66. package/dist/signatures-K5QIL4WG.js.map +1 -0
  67. package/dist/skills-assign-IHOXX4AI.js +182 -0
  68. package/dist/skills-assign-IHOXX4AI.js.map +1 -0
  69. package/dist/skills-load-JSD5UG2K.js +20 -0
  70. package/dist/skills-load-JSD5UG2K.js.map +1 -0
  71. package/dist/skills-scan-WACJFRJN.js +25 -0
  72. package/dist/skills-scan-WACJFRJN.js.map +1 -0
  73. package/dist/skills-suggest-JFI2NUJI.js +269 -0
  74. package/dist/skills-suggest-JFI2NUJI.js.map +1 -0
  75. package/dist/status-KQVSAZFR.js +111 -0
  76. package/dist/status-KQVSAZFR.js.map +1 -0
  77. package/dist/suggest-IFFJQFIW.js +183 -0
  78. package/dist/suggest-IFFJQFIW.js.map +1 -0
  79. package/dist/test-HP3FG3MO.js +152 -0
  80. package/dist/test-HP3FG3MO.js.map +1 -0
  81. package/dist/test-gen-2ZGPOP35.js +347 -0
  82. package/dist/test-gen-2ZGPOP35.js.map +1 -0
  83. package/dist/trust-4R26DULG.js +248 -0
  84. package/dist/trust-4R26DULG.js.map +1 -0
  85. package/dist/validate-generator-46H2LYYQ.js +410 -0
  86. package/dist/validate-generator-46H2LYYQ.js.map +1 -0
  87. package/dist/workflow-5UVLBS7J.js +655 -0
  88. package/dist/workflow-5UVLBS7J.js.map +1 -0
  89. package/package.json +84 -0
@@ -0,0 +1,222 @@
1
+ import {
2
+ fileExists,
3
+ getRegistryPath,
4
+ graduateAtom,
5
+ loadRegistry,
6
+ saveRegistry
7
+ } from "./chunk-YKJO3ZFY.js";
8
+ import {
9
+ toErrorMessage
10
+ } from "./chunk-PLQJM2KT.js";
11
+
12
+ // src/commands/graduate.ts
13
+ import { join } from "path";
14
+ import { writeFile, mkdir } from "fs/promises";
15
+ function generateTemplate(capabilityName, inputSchema, description, graduatedFrom) {
16
+ const typeName = capabilityName.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
17
+ const properties = inputSchema["properties"] || {};
18
+ const required = inputSchema["required"] || [];
19
+ const zodFields = Object.entries(properties).map(([name, prop]) => {
20
+ const p = prop;
21
+ const zodType = jsonSchemaTypeToZod(p["type"], p);
22
+ const isRequired = required.includes(name);
23
+ const desc = p["description"] ? `.describe(${JSON.stringify(p["description"])})` : "";
24
+ const key = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
25
+ return ` ${key}: ${zodType}${isRequired ? "" : ".optional()"}${desc},`;
26
+ }).join("\n");
27
+ return `/**
28
+ * Generator for ${capabilityName}
29
+ *
30
+ * ${description}
31
+ *
32
+ * This generator was graduated from an atom. Modify the template function
33
+ * to produce deterministic output based on the input parameters.
34
+ *
35
+ * @module generators/${capabilityName}
36
+ */
37
+
38
+ import { z } from 'zod';
39
+ import { createGenerator } from '../generator/index.js';
40
+
41
+ /**
42
+ * Parameters schema for ${capabilityName}
43
+ */
44
+ export const ${typeName}Params = z.object({
45
+ ${zodFields}
46
+ });
47
+
48
+ export type ${typeName}Params = z.infer<typeof ${typeName}Params>;
49
+
50
+ /**
51
+ * ${capabilityName} generator
52
+ *
53
+ * TODO: Customize the template function to produce the desired output.
54
+ * The current implementation is a placeholder.
55
+ */
56
+ export const ${capabilityName}Generator = createGenerator({
57
+ name: '${capabilityName}',
58
+ description: ${JSON.stringify(description)},
59
+ params: ${typeName}Params,
60
+ template: (params: ${typeName}Params) => {
61
+ // TODO: Implement the actual template
62
+ // This placeholder just returns a JSON representation of the params
63
+ return JSON.stringify(params, null, 2);
64
+ },
65
+ language: 'typescript',
66
+ graduatedFrom: '${graduatedFrom}',
67
+ });
68
+
69
+ export default ${capabilityName}Generator;
70
+ `;
71
+ }
72
+ function jsonSchemaTypeToZod(type, schema) {
73
+ switch (type) {
74
+ case "string":
75
+ if (schema["enum"]) {
76
+ const enumValues = schema["enum"];
77
+ const allStrings = enumValues.every((v) => typeof v === "string");
78
+ if (allStrings) {
79
+ const values = enumValues.map((v) => `'${v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`).join(", ");
80
+ return `z.enum([${values}])`;
81
+ }
82
+ const literals = enumValues.map((v) => {
83
+ if (typeof v === "string") return `z.literal('${v.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}')`;
84
+ if (typeof v === "number") return `z.literal(${v})`;
85
+ if (typeof v === "boolean") return `z.literal(${v})`;
86
+ if (v === null) return "z.literal(null)";
87
+ return "z.unknown()";
88
+ }).join(", ");
89
+ return `z.union([${literals}])`;
90
+ }
91
+ return "z.string()";
92
+ case "number":
93
+ return "z.number()";
94
+ case "integer":
95
+ return "z.number().int()";
96
+ case "boolean":
97
+ return "z.boolean()";
98
+ case "array": {
99
+ const items = schema["items"];
100
+ if (items) {
101
+ const itemType = jsonSchemaTypeToZod(items["type"], items);
102
+ return `z.array(${itemType})`;
103
+ }
104
+ return "z.array(z.unknown())";
105
+ }
106
+ case "object": {
107
+ const properties = schema["properties"];
108
+ if (properties && Object.keys(properties).length > 0) {
109
+ return "z.object({}) /* TODO: Add nested properties */";
110
+ }
111
+ return "z.record(z.string(), z.unknown())";
112
+ }
113
+ default:
114
+ return "z.unknown()";
115
+ }
116
+ }
117
+ async function graduateCommand(options) {
118
+ const projectRoot = process.cwd();
119
+ const registryPath = getRegistryPath(projectRoot);
120
+ const generatorsDir = join(projectRoot, "generators");
121
+ try {
122
+ if (!/^[a-z][a-z0-9_]*$/.test(options.atom)) {
123
+ throw new Error(
124
+ `Invalid atom name: ${options.atom}
125
+ Atom names must be snake_case`
126
+ );
127
+ }
128
+ const registry = await loadRegistry(registryPath);
129
+ const atomEntry = registry.atoms[options.atom];
130
+ if (!atomEntry) {
131
+ const availableAtoms = Object.keys(registry.atoms);
132
+ if (availableAtoms.length === 0) {
133
+ throw new Error(
134
+ `Atom '${options.atom}' not found. No atoms are registered.
135
+ Register an atom with: atomic register <path>`
136
+ );
137
+ }
138
+ throw new Error(
139
+ `Atom '${options.atom}' not found.
140
+ Available atoms: ${availableAtoms.join(", ")}`
141
+ );
142
+ }
143
+ if (atomEntry.graduated) {
144
+ throw new Error(
145
+ `Atom '${options.atom}' is already graduated.
146
+ Generator: ${atomEntry.generatorPath}`
147
+ );
148
+ }
149
+ const originalAtomPath = atomEntry.path;
150
+ const originalAtomName = atomEntry.name;
151
+ if (!await fileExists(generatorsDir)) {
152
+ await mkdir(generatorsDir, { recursive: true });
153
+ }
154
+ const generatorFileName = `${options.atom}.ts`;
155
+ const generatorPath = join(generatorsDir, generatorFileName);
156
+ const relativeGeneratorPath = `generators/${generatorFileName}`;
157
+ if (await fileExists(generatorPath)) {
158
+ throw new Error(
159
+ `Generator file already exists: ${generatorPath}
160
+ Delete it first or use a different atom name.`
161
+ );
162
+ }
163
+ const template = generateTemplate(
164
+ options.atom,
165
+ atomEntry.inputSchema,
166
+ atomEntry.description,
167
+ originalAtomName
168
+ );
169
+ if (!options.json) {
170
+ console.log(`
171
+ Graduating atom: ${options.atom}`);
172
+ console.log(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
173
+ console.log(`Atom path: ${originalAtomPath}`);
174
+ console.log(`Generator path: ${relativeGeneratorPath}`);
175
+ console.log("");
176
+ console.log("Note: Similarity analysis requires artifact storage (not yet implemented)");
177
+ console.log("");
178
+ if (!options.yes) {
179
+ console.log("Generator template will be created. Edit it to customize the output.");
180
+ console.log("Use --yes to skip this message.");
181
+ console.log("");
182
+ }
183
+ }
184
+ await writeFile(generatorPath, template);
185
+ registry.atoms[options.atom] = graduateAtom(atomEntry, relativeGeneratorPath);
186
+ await saveRegistry(registryPath, registry);
187
+ const result = {
188
+ success: true,
189
+ atom: options.atom,
190
+ generatorPath: relativeGeneratorPath
191
+ };
192
+ if (options.json) {
193
+ console.log(JSON.stringify(result, null, 2));
194
+ } else {
195
+ console.log(`\u2713 Atom '${options.atom}' graduated to generator`);
196
+ console.log(` Generator: ${relativeGeneratorPath}`);
197
+ console.log("");
198
+ console.log("Next steps:");
199
+ console.log(` 1. Edit ${relativeGeneratorPath} to customize the template`);
200
+ console.log(" 2. Run atomic test to verify the generator works");
201
+ }
202
+ } catch (err) {
203
+ const errorMessage = toErrorMessage(err);
204
+ if (options.json) {
205
+ console.log(
206
+ JSON.stringify({
207
+ success: false,
208
+ atom: options.atom,
209
+ error: errorMessage
210
+ })
211
+ );
212
+ process.exit(1);
213
+ } else {
214
+ console.error(`Error: ${errorMessage}`);
215
+ process.exit(1);
216
+ }
217
+ }
218
+ }
219
+ export {
220
+ graduateCommand
221
+ };
222
+ //# sourceMappingURL=graduate-453M7ZRQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/graduate.ts"],"sourcesContent":["/**\n * Graduate command for the atomic CLI.\n *\n * This module implements the `atomic graduate` command which promotes\n * an atom from handler-based (probabilistic) to generator-based (deterministic).\n *\n * @module graduate\n */\n\nimport { join } from 'node:path';\nimport { writeFile, mkdir } from 'node:fs/promises';\nimport { fileExists, loadRegistry, saveRegistry, getRegistryPath } from '../storage/index.js';\nimport { graduateAtom } from '../schemas/registry.js';\nimport { toErrorMessage } from '../utils/errors.js';\n\n/**\n * Options for the graduate command.\n */\nexport interface GraduateOptions {\n atom: string;\n json?: boolean;\n yes?: boolean; // Skip confirmation\n}\n\n/**\n * Result of graduation.\n */\nexport interface GraduateResult {\n success: boolean;\n atom: string;\n generatorPath?: string;\n error?: string;\n}\n\n/**\n * Generate a TypeScript generator file template.\n *\n * @param capabilityName - Name of the capability being graduated\n * @param inputSchema - JSON Schema for the input\n * @param description - Description of the capability\n * @param graduatedFrom - Original atom name this generator was graduated from\n */\nfunction generateTemplate(\n capabilityName: string,\n inputSchema: Record<string, unknown>,\n description: string,\n graduatedFrom: string\n): string {\n // Convert capability_name to PascalCase for type name\n const typeName = capabilityName\n .split('_')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n\n // Extract properties from JSON Schema for params\n const properties = (inputSchema['properties'] as Record<string, unknown>) || {};\n const required = (inputSchema['required'] as string[]) || [];\n\n // Generate Zod schema fields\n const zodFields = Object.entries(properties)\n .map(([name, prop]) => {\n const p = prop as Record<string, unknown>;\n const zodType = jsonSchemaTypeToZod(p['type'] as string, p);\n const isRequired = required.includes(name);\n const desc = p['description'] ? `.describe(${JSON.stringify(p['description'])})` : '';\n // Quote property names that aren't valid JS identifiers\n const key = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n return ` ${key}: ${zodType}${isRequired ? '' : '.optional()'}${desc},`;\n })\n .join('\\n');\n\n return `/**\n * Generator for ${capabilityName}\n *\n * ${description}\n *\n * This generator was graduated from an atom. Modify the template function\n * to produce deterministic output based on the input parameters.\n *\n * @module generators/${capabilityName}\n */\n\nimport { z } from 'zod';\nimport { createGenerator } from '../generator/index.js';\n\n/**\n * Parameters schema for ${capabilityName}\n */\nexport const ${typeName}Params = z.object({\n${zodFields}\n});\n\nexport type ${typeName}Params = z.infer<typeof ${typeName}Params>;\n\n/**\n * ${capabilityName} generator\n *\n * TODO: Customize the template function to produce the desired output.\n * The current implementation is a placeholder.\n */\nexport const ${capabilityName}Generator = createGenerator({\n name: '${capabilityName}',\n description: ${JSON.stringify(description)},\n params: ${typeName}Params,\n template: (params: ${typeName}Params) => {\n // TODO: Implement the actual template\n // This placeholder just returns a JSON representation of the params\n return JSON.stringify(params, null, 2);\n },\n language: 'typescript',\n graduatedFrom: '${graduatedFrom}',\n});\n\nexport default ${capabilityName}Generator;\n`;\n}\n\n/**\n * Convert JSON Schema type to Zod type string.\n */\nfunction jsonSchemaTypeToZod(\n type: string | undefined,\n schema: Record<string, unknown>\n): string {\n switch (type) {\n case 'string':\n if (schema['enum']) {\n const enumValues = schema['enum'] as unknown[];\n // z.enum only works with string literals; for mixed types use z.union of z.literal\n const allStrings = enumValues.every((v) => typeof v === 'string');\n if (allStrings) {\n const values = (enumValues as string[]).map((v) => `'${v.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")}'`).join(', ');\n return `z.enum([${values}])`;\n }\n // Mixed types: use z.union of z.literal\n const literals = enumValues\n .map((v) => {\n if (typeof v === 'string') return `z.literal('${v.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")}')`;\n if (typeof v === 'number') return `z.literal(${v})`;\n if (typeof v === 'boolean') return `z.literal(${v})`;\n if (v === null) return 'z.literal(null)';\n return 'z.unknown()';\n })\n .join(', ');\n return `z.union([${literals}])`;\n }\n return 'z.string()';\n case 'number':\n return 'z.number()';\n case 'integer':\n return 'z.number().int()';\n case 'boolean':\n return 'z.boolean()';\n case 'array': {\n const items = schema['items'] as Record<string, unknown> | undefined;\n if (items) {\n const itemType = jsonSchemaTypeToZod(items['type'] as string, items);\n return `z.array(${itemType})`;\n }\n return 'z.array(z.unknown())';\n }\n case 'object': {\n const properties = schema['properties'] as Record<string, unknown> | undefined;\n if (properties && Object.keys(properties).length > 0) {\n // Nested object schemas require recursion. For MVP, emit z.object({})\n // with a comment prompting manual completion. Full support would\n // recursively call jsonSchemaTypeToZod for each property.\n return 'z.object({}) /* TODO: Add nested properties */';\n }\n return 'z.record(z.string(), z.unknown())';\n }\n default:\n return 'z.unknown()';\n }\n}\n\n/**\n * Graduate an atom from handler to generator.\n *\n * @param options - Command options\n */\nexport async function graduateCommand(options: GraduateOptions): Promise<void> {\n const projectRoot = process.cwd();\n const registryPath = getRegistryPath(projectRoot);\n const generatorsDir = join(projectRoot, 'generators');\n\n try {\n // Validate atom name\n if (!/^[a-z][a-z0-9_]*$/.test(options.atom)) {\n throw new Error(\n `Invalid atom name: ${options.atom}\\n` +\n 'Atom names must be snake_case'\n );\n }\n\n // Load registry\n const registry = await loadRegistry(registryPath);\n\n // Check if atom exists\n const atomEntry = registry.atoms[options.atom];\n if (!atomEntry) {\n const availableAtoms = Object.keys(registry.atoms);\n if (availableAtoms.length === 0) {\n throw new Error(\n `Atom '${options.atom}' not found. No atoms are registered.\\n` +\n 'Register an atom with: atomic register <path>'\n );\n }\n throw new Error(\n `Atom '${options.atom}' not found.\\n` +\n `Available atoms: ${availableAtoms.join(', ')}`\n );\n }\n\n // Check if already graduated\n if (atomEntry.graduated) {\n throw new Error(\n `Atom '${options.atom}' is already graduated.\\n` +\n `Generator: ${atomEntry.generatorPath}`\n );\n }\n\n // Get atom info\n const originalAtomPath = atomEntry.path;\n const originalAtomName = atomEntry.name;\n\n // Create generators directory if needed\n if (!(await fileExists(generatorsDir))) {\n await mkdir(generatorsDir, { recursive: true });\n }\n\n // Generate the generator file\n const generatorFileName = `${options.atom}.ts`;\n const generatorPath = join(generatorsDir, generatorFileName);\n const relativeGeneratorPath = `generators/${generatorFileName}`;\n\n if (await fileExists(generatorPath)) {\n throw new Error(\n `Generator file already exists: ${generatorPath}\\n` +\n 'Delete it first or use a different atom name.'\n );\n }\n\n // Generate template\n const template = generateTemplate(\n options.atom,\n atomEntry.inputSchema,\n atomEntry.description,\n originalAtomName\n );\n\n // Show what we're about to do\n if (!options.json) {\n console.log(`\\nGraduating atom: ${options.atom}`);\n console.log(`─────────────────────────────────────────────`);\n console.log(`Atom path: ${originalAtomPath}`);\n console.log(`Generator path: ${relativeGeneratorPath}`);\n console.log('');\n\n // TODO: In future, show similarity stats from artifact analysis\n console.log('Note: Similarity analysis requires artifact storage (not yet implemented)');\n console.log('');\n\n if (!options.yes) {\n // Note: Full confirmation prompt with template preview requires artifact\n // storage to show similarity stats. For MVP, --yes just skips this message.\n // TODO: Add interactive confirmation when artifact storage is implemented.\n console.log('Generator template will be created. Edit it to customize the output.');\n console.log('Use --yes to skip this message.');\n console.log('');\n }\n }\n\n // Write the generator file\n await writeFile(generatorPath, template);\n\n // Update the atom to graduated status\n registry.atoms[options.atom] = graduateAtom(atomEntry, relativeGeneratorPath);\n\n // Save the registry\n await saveRegistry(registryPath, registry);\n\n const result: GraduateResult = {\n success: true,\n atom: options.atom,\n generatorPath: relativeGeneratorPath,\n };\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(`✓ Atom '${options.atom}' graduated to generator`);\n console.log(` Generator: ${relativeGeneratorPath}`);\n console.log('');\n console.log('Next steps:');\n console.log(` 1. Edit ${relativeGeneratorPath} to customize the template`);\n console.log(' 2. Run atomic test to verify the generator works');\n }\n } catch (err) {\n const errorMessage = toErrorMessage(err);\n\n if (options.json) {\n console.log(\n JSON.stringify({\n success: false,\n atom: options.atom,\n error: errorMessage,\n } satisfies GraduateResult)\n );\n process.exit(1);\n } else {\n console.error(`Error: ${errorMessage}`);\n process.exit(1);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,11 @@
1
+ import {
2
+ deriveErrorCode,
3
+ withErrorHandling
4
+ } from "./chunk-GU2R4KLP.js";
5
+ import "./chunk-YKJO3ZFY.js";
6
+ import "./chunk-PLQJM2KT.js";
7
+ export {
8
+ deriveErrorCode,
9
+ withErrorHandling
10
+ };
11
+ //# sourceMappingURL=helpers-PJPFPYBQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,258 @@
1
+ import {
2
+ ATOMIC_DIR,
3
+ DB_FILE,
4
+ fileExists,
5
+ getClient
6
+ } from "./chunk-YKJO3ZFY.js";
7
+ import {
8
+ fmt
9
+ } from "./chunk-S6Z5G5DB.js";
10
+ import {
11
+ toErrorMessage
12
+ } from "./chunk-PLQJM2KT.js";
13
+
14
+ // src/commands/history.ts
15
+ import { z } from "zod";
16
+ import { join } from "path";
17
+ var RunSummaryRecordSchema = z.object({
18
+ id: z.string(),
19
+ atom_name: z.string(),
20
+ started_at: z.string(),
21
+ completed_at: z.string().nullable(),
22
+ status: z.enum(["running", "success", "error"])
23
+ });
24
+ var RunRecordSchema = z.object({
25
+ id: z.string(),
26
+ atom_name: z.string(),
27
+ workflow_id: z.string().nullable(),
28
+ input_hash: z.string(),
29
+ started_at: z.string(),
30
+ completed_at: z.string().nullable(),
31
+ status: z.enum(["running", "success", "error"]),
32
+ error_message: z.string().nullable()
33
+ });
34
+ var ArtifactRecordSchema = z.object({
35
+ artifact_path: z.string(),
36
+ object_hash: z.string(),
37
+ content_type: z.string(),
38
+ size: z.number()
39
+ });
40
+ function calculateDuration(startedAt, completedAt) {
41
+ if (!completedAt) {
42
+ return null;
43
+ }
44
+ const start = new Date(startedAt).getTime();
45
+ const end = new Date(completedAt).getTime();
46
+ if (isNaN(start) || isNaN(end)) {
47
+ return null;
48
+ }
49
+ return end - start;
50
+ }
51
+ function formatDuration(ms) {
52
+ if (ms === null) {
53
+ return "running";
54
+ }
55
+ if (ms < 1e3) {
56
+ return `${ms}ms`;
57
+ }
58
+ const seconds = (ms / 1e3).toFixed(1);
59
+ return `${seconds}s`;
60
+ }
61
+ function formatTimestamp(timestamp) {
62
+ const date = new Date(timestamp);
63
+ return date.toLocaleString();
64
+ }
65
+ async function listHistory(options) {
66
+ const projectRoot = process.cwd();
67
+ const db = await getClient(projectRoot);
68
+ try {
69
+ let sql = `
70
+ SELECT id, atom_name, started_at, completed_at, status
71
+ FROM runs
72
+ `;
73
+ const args = [];
74
+ if (options.atom) {
75
+ sql += " WHERE atom_name = ?";
76
+ args.push(options.atom);
77
+ }
78
+ sql += " ORDER BY started_at DESC LIMIT ?";
79
+ args.push(options.limit);
80
+ const rows = await db.prepare(sql).all(...args);
81
+ return rows.map((row) => {
82
+ const record = RunSummaryRecordSchema.parse(row);
83
+ const duration = calculateDuration(record.started_at, record.completed_at);
84
+ return {
85
+ runId: record.id,
86
+ timestamp: record.started_at,
87
+ atomName: record.atom_name,
88
+ success: record.status === "success",
89
+ duration
90
+ };
91
+ });
92
+ } finally {
93
+ await db.close();
94
+ }
95
+ }
96
+ async function getRunDetails(runId) {
97
+ const projectRoot = process.cwd();
98
+ const db = await getClient(projectRoot);
99
+ try {
100
+ const runRow = await db.prepare("SELECT * FROM runs WHERE id = ?").get(runId);
101
+ if (!runRow) {
102
+ return null;
103
+ }
104
+ const run = RunRecordSchema.parse(runRow);
105
+ const artifactRows = await db.prepare(`
106
+ SELECT ra.artifact_path, ra.object_hash, o.content_type,
107
+ LENGTH(o.content) as size
108
+ FROM run_artifacts ra
109
+ JOIN objects o ON ra.object_hash = o.hash
110
+ WHERE ra.run_id = ?
111
+ ORDER BY ra.artifact_path
112
+ `).all(runId);
113
+ const artifacts = artifactRows.map((row) => {
114
+ const record = ArtifactRecordSchema.parse(row);
115
+ return {
116
+ path: record.artifact_path,
117
+ hash: record.object_hash,
118
+ contentType: record.content_type,
119
+ size: record.size
120
+ };
121
+ });
122
+ return {
123
+ runId: run.id,
124
+ atomName: run.atom_name,
125
+ workflowId: run.workflow_id,
126
+ inputHash: run.input_hash,
127
+ startedAt: run.started_at,
128
+ completedAt: run.completed_at,
129
+ status: run.status,
130
+ errorMessage: run.error_message,
131
+ duration: calculateDuration(run.started_at, run.completed_at),
132
+ artifacts
133
+ };
134
+ } finally {
135
+ await db.close();
136
+ }
137
+ }
138
+ var ANSI_COLOR_OFFSET = 9;
139
+ function formatRunTable(runs) {
140
+ if (runs.length === 0) {
141
+ return fmt.dim("No execution history found.");
142
+ }
143
+ const lines = [];
144
+ const header = [
145
+ "RUN ID".padEnd(12),
146
+ "TIMESTAMP".padEnd(20),
147
+ "ATOM".padEnd(25),
148
+ "STATUS".padEnd(8),
149
+ "DURATION".padEnd(10)
150
+ ].join(" ");
151
+ lines.push(fmt.bold(header));
152
+ lines.push("\u2500".repeat(80));
153
+ for (const run of runs) {
154
+ const runIdShort = run.runId.substring(0, 10) + "..";
155
+ const timestamp = formatTimestamp(run.timestamp).substring(0, 18);
156
+ const atomName = run.atomName.length > 23 ? run.atomName.substring(0, 22) + ".." : run.atomName;
157
+ const status = run.success ? fmt.green("\u2713 pass") : fmt.red("\u2717 fail");
158
+ const duration = formatDuration(run.duration);
159
+ const row = [
160
+ fmt.dim(runIdShort.padEnd(12)),
161
+ timestamp.padEnd(20),
162
+ atomName.padEnd(25),
163
+ status.padEnd(8 + ANSI_COLOR_OFFSET),
164
+ duration.padEnd(10)
165
+ ].join(" ");
166
+ lines.push(row);
167
+ }
168
+ return lines.join("\n");
169
+ }
170
+ function formatRunDetails(details) {
171
+ const lines = [];
172
+ lines.push(fmt.bold("Run Details"));
173
+ lines.push("\u2500".repeat(60));
174
+ lines.push(` ${fmt.dim("Run ID:")} ${details.runId}`);
175
+ lines.push(` ${fmt.dim("Atom:")} ${details.atomName}`);
176
+ if (details.workflowId) {
177
+ lines.push(` ${fmt.dim("Workflow:")} ${details.workflowId}`);
178
+ }
179
+ lines.push(` ${fmt.dim("Started:")} ${formatTimestamp(details.startedAt)}`);
180
+ if (details.completedAt) {
181
+ lines.push(` ${fmt.dim("Completed:")} ${formatTimestamp(details.completedAt)}`);
182
+ }
183
+ lines.push(` ${fmt.dim("Duration:")} ${formatDuration(details.duration)}`);
184
+ const statusStr = details.status === "success" ? fmt.green("\u2713 success") : details.status === "error" ? fmt.red("\u2717 error") : fmt.yellow("\u22EF running");
185
+ lines.push(` ${fmt.dim("Status:")} ${statusStr}`);
186
+ if (details.errorMessage) {
187
+ lines.push(` ${fmt.dim("Error:")} ${fmt.red(details.errorMessage)}`);
188
+ }
189
+ lines.push(` ${fmt.dim("Input Hash:")} ${details.inputHash}`);
190
+ if (details.artifacts.length > 0) {
191
+ lines.push("");
192
+ lines.push(fmt.bold("Artifacts"));
193
+ lines.push("\u2500".repeat(60));
194
+ for (const artifact of details.artifacts) {
195
+ const size = artifact.size < 1024 ? `${artifact.size}B` : `${(artifact.size / 1024).toFixed(1)}KB`;
196
+ lines.push(` ${artifact.path}`);
197
+ lines.push(` ${fmt.dim("Hash:")} ${artifact.hash.substring(0, 16)}.. ${fmt.dim("Type:")} ${artifact.contentType} ${fmt.dim("Size:")} ${size}`);
198
+ }
199
+ } else {
200
+ lines.push("");
201
+ lines.push(fmt.dim("No artifacts stored for this run."));
202
+ }
203
+ return lines.join("\n");
204
+ }
205
+ async function historyCommand(runId, options) {
206
+ const projectRoot = process.cwd();
207
+ const dbPath = join(projectRoot, ATOMIC_DIR, DB_FILE);
208
+ try {
209
+ if (!await fileExists(dbPath)) {
210
+ throw new Error("Storage not initialized. Run `atomic init` first.");
211
+ }
212
+ if (runId) {
213
+ const details = await getRunDetails(runId);
214
+ if (!details) {
215
+ throw new Error(`Run '${runId}' not found.`);
216
+ }
217
+ if (options.json) {
218
+ console.log(JSON.stringify(details, null, 2));
219
+ } else {
220
+ console.log(formatRunDetails(details));
221
+ }
222
+ } else {
223
+ const limit = parseInt(options.limit ?? "20", 10);
224
+ if (isNaN(limit) || limit < 1) {
225
+ throw new Error("--limit must be a positive integer");
226
+ }
227
+ const runs = await listHistory({
228
+ atom: options.atom,
229
+ limit
230
+ });
231
+ if (options.json) {
232
+ console.log(JSON.stringify({ runs }, null, 2));
233
+ } else {
234
+ if (options.atom) {
235
+ console.log(fmt.bold(`Execution History for "${options.atom}"`));
236
+ } else {
237
+ console.log(fmt.bold("Execution History"));
238
+ }
239
+ console.log("");
240
+ console.log(formatRunTable(runs));
241
+ }
242
+ }
243
+ } catch (err) {
244
+ const message = toErrorMessage(err);
245
+ if (options.json) {
246
+ console.log(JSON.stringify({ error: message }, null, 2));
247
+ } else {
248
+ console.error(fmt.error(`History failed: ${message}`));
249
+ }
250
+ process.exit(1);
251
+ }
252
+ }
253
+ export {
254
+ getRunDetails,
255
+ historyCommand,
256
+ listHistory
257
+ };
258
+ //# sourceMappingURL=history-OPD7NLZW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/history.ts"],"sourcesContent":["/**\n * History command - Show execution history.\n *\n * Story 2.9: AOP-1226\n *\n * Acceptance Criteria:\n * - Table shows: run_id, timestamp, atom_name, success, duration\n * - --atom <name> filters to specific atom\n * - --limit N limits results (default 20)\n * - --json flag outputs structured data\n * - atomic history <run_id> shows full run details including artifacts\n */\n\nimport { z } from 'zod';\nimport { getClient, ATOMIC_DIR, DB_FILE, fileExists } from '../storage/index.js';\nimport { fmt } from '../cli/format.js';\nimport { join } from 'node:path';\nimport { toErrorMessage } from '../utils/errors.js';\n\n\n/**\n * Options for the history command.\n */\nexport interface HistoryOptions {\n atom?: string;\n limit?: string;\n json?: boolean;\n}\n\n/**\n * Zod schema for run summary records (list query).\n * Only includes fields selected in the list query.\n */\nconst RunSummaryRecordSchema = z.object({\n id: z.string(),\n atom_name: z.string(),\n started_at: z.string(),\n completed_at: z.string().nullable(),\n status: z.enum(['running', 'success', 'error']),\n});\n\n/**\n * Zod schema for full run records (detail query).\n * Includes all fields from the runs table.\n */\nconst RunRecordSchema = z.object({\n id: z.string(),\n atom_name: z.string(),\n workflow_id: z.string().nullable(),\n input_hash: z.string(),\n started_at: z.string(),\n completed_at: z.string().nullable(),\n status: z.enum(['running', 'success', 'error']),\n error_message: z.string().nullable(),\n});\n\ntype _RunRecord = z.infer<typeof RunRecordSchema>;\n\n/**\n * Zod schema for artifact records from the database.\n * Provides runtime validation for artifact query results.\n */\nconst ArtifactRecordSchema = z.object({\n artifact_path: z.string(),\n object_hash: z.string(),\n content_type: z.string(),\n size: z.number(),\n});\n\n/**\n * Run summary for list view.\n */\nexport interface RunSummary {\n runId: string;\n timestamp: string;\n atomName: string;\n success: boolean;\n duration: number | null;\n}\n\n/**\n * Full run details for single run view.\n */\nexport interface RunDetails {\n runId: string;\n atomName: string;\n workflowId: string | null;\n inputHash: string;\n startedAt: string;\n completedAt: string | null;\n status: 'running' | 'success' | 'error';\n errorMessage: string | null;\n duration: number | null;\n artifacts: {\n path: string;\n hash: string;\n contentType: string;\n size: number;\n }[];\n}\n\n/**\n * Calculate duration in milliseconds from timestamps.\n * Returns null if timestamps are missing or malformed.\n */\nfunction calculateDuration(startedAt: string, completedAt: string | null): number | null {\n if (!completedAt) {\n return null;\n }\n const start = new Date(startedAt).getTime();\n const end = new Date(completedAt).getTime();\n\n // Guard against malformed timestamps that would produce NaN\n if (isNaN(start) || isNaN(end)) {\n return null;\n }\n\n return end - start;\n}\n\n/**\n * Format duration for display.\n */\nfunction formatDuration(ms: number | null): string {\n if (ms === null) {\n return 'running';\n }\n if (ms < 1000) {\n return `${ms}ms`;\n }\n const seconds = (ms / 1000).toFixed(1);\n return `${seconds}s`;\n}\n\n/**\n * Format timestamp for display.\n */\nfunction formatTimestamp(timestamp: string): string {\n const date = new Date(timestamp);\n return date.toLocaleString();\n}\n\n/**\n * List execution history.\n */\nexport async function listHistory(options: {\n atom?: string;\n limit: number;\n}): Promise<RunSummary[]> {\n const projectRoot = process.cwd();\n const db = await getClient(projectRoot);\n\n try {\n let sql = `\n SELECT id, atom_name, started_at, completed_at, status\n FROM runs\n `;\n const args: (string | number)[] = [];\n\n if (options.atom) {\n sql += ' WHERE atom_name = ?';\n args.push(options.atom);\n }\n\n sql += ' ORDER BY started_at DESC LIMIT ?';\n args.push(options.limit);\n\n const rows = await db.prepare(sql).all(...args);\n\n return rows.map((row) => {\n const record = RunSummaryRecordSchema.parse(row);\n const duration = calculateDuration(record.started_at, record.completed_at);\n return {\n runId: record.id,\n timestamp: record.started_at,\n atomName: record.atom_name,\n success: record.status === 'success',\n duration,\n };\n });\n } finally {\n await db.close();\n }\n}\n\n/**\n * Get full details for a specific run.\n */\nexport async function getRunDetails(runId: string): Promise<RunDetails | null> {\n const projectRoot = process.cwd();\n const db = await getClient(projectRoot);\n\n try {\n // Get run record\n const runRow = await db.prepare('SELECT * FROM runs WHERE id = ?').get(runId);\n\n if (!runRow) {\n return null;\n }\n\n const run = RunRecordSchema.parse(runRow);\n\n // Get artifacts for this run\n const artifactRows = await db.prepare(`\n SELECT ra.artifact_path, ra.object_hash, o.content_type,\n LENGTH(o.content) as size\n FROM run_artifacts ra\n JOIN objects o ON ra.object_hash = o.hash\n WHERE ra.run_id = ?\n ORDER BY ra.artifact_path\n `).all(runId);\n\n const artifacts = artifactRows.map((row) => {\n const record = ArtifactRecordSchema.parse(row);\n return {\n path: record.artifact_path,\n hash: record.object_hash,\n contentType: record.content_type,\n size: record.size,\n };\n });\n\n return {\n runId: run.id,\n atomName: run.atom_name,\n workflowId: run.workflow_id,\n inputHash: run.input_hash,\n startedAt: run.started_at,\n completedAt: run.completed_at,\n status: run.status,\n errorMessage: run.error_message,\n duration: calculateDuration(run.started_at, run.completed_at),\n artifacts,\n };\n } finally {\n await db.close();\n }\n}\n\n/**\n * ANSI escape codes add invisible characters for color formatting.\n * This offset accounts for the escape sequences in padEnd calculations.\n */\nconst ANSI_COLOR_OFFSET = 9;\n\n/**\n * Format run list as table.\n */\nfunction formatRunTable(runs: RunSummary[]): string {\n if (runs.length === 0) {\n return fmt.dim('No execution history found.');\n }\n\n const lines: string[] = [];\n\n // Header\n const header = [\n 'RUN ID'.padEnd(12),\n 'TIMESTAMP'.padEnd(20),\n 'ATOM'.padEnd(25),\n 'STATUS'.padEnd(8),\n 'DURATION'.padEnd(10),\n ].join(' ');\n lines.push(fmt.bold(header));\n lines.push('─'.repeat(80));\n\n // Rows\n for (const run of runs) {\n const runIdShort = run.runId.substring(0, 10) + '..';\n const timestamp = formatTimestamp(run.timestamp).substring(0, 18);\n const atomName = run.atomName.length > 23 ? run.atomName.substring(0, 22) + '..' : run.atomName;\n const status = run.success ? fmt.green('✓ pass') : fmt.red('✗ fail');\n const duration = formatDuration(run.duration);\n\n const row = [\n fmt.dim(runIdShort.padEnd(12)),\n timestamp.padEnd(20),\n atomName.padEnd(25),\n status.padEnd(8 + ANSI_COLOR_OFFSET),\n duration.padEnd(10),\n ].join(' ');\n lines.push(row);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format run details for display.\n */\nfunction formatRunDetails(details: RunDetails): string {\n const lines: string[] = [];\n\n lines.push(fmt.bold('Run Details'));\n lines.push('─'.repeat(60));\n lines.push(` ${fmt.dim('Run ID:')} ${details.runId}`);\n lines.push(` ${fmt.dim('Atom:')} ${details.atomName}`);\n if (details.workflowId) {\n lines.push(` ${fmt.dim('Workflow:')} ${details.workflowId}`);\n }\n lines.push(` ${fmt.dim('Started:')} ${formatTimestamp(details.startedAt)}`);\n if (details.completedAt) {\n lines.push(` ${fmt.dim('Completed:')} ${formatTimestamp(details.completedAt)}`);\n }\n lines.push(` ${fmt.dim('Duration:')} ${formatDuration(details.duration)}`);\n\n const statusStr =\n details.status === 'success'\n ? fmt.green('✓ success')\n : details.status === 'error'\n ? fmt.red('✗ error')\n : fmt.yellow('⋯ running');\n lines.push(` ${fmt.dim('Status:')} ${statusStr}`);\n\n if (details.errorMessage) {\n lines.push(` ${fmt.dim('Error:')} ${fmt.red(details.errorMessage)}`);\n }\n\n lines.push(` ${fmt.dim('Input Hash:')} ${details.inputHash}`);\n\n if (details.artifacts.length > 0) {\n lines.push('');\n lines.push(fmt.bold('Artifacts'));\n lines.push('─'.repeat(60));\n for (const artifact of details.artifacts) {\n const size = artifact.size < 1024 ? `${artifact.size}B` : `${(artifact.size / 1024).toFixed(1)}KB`;\n lines.push(` ${artifact.path}`);\n lines.push(` ${fmt.dim('Hash:')} ${artifact.hash.substring(0, 16)}.. ${fmt.dim('Type:')} ${artifact.contentType} ${fmt.dim('Size:')} ${size}`);\n }\n } else {\n lines.push('');\n lines.push(fmt.dim('No artifacts stored for this run.'));\n }\n\n return lines.join('\\n');\n}\n\n/**\n * History command handler.\n */\nexport async function historyCommand(\n runId: string | undefined,\n options: HistoryOptions\n): Promise<void> {\n const projectRoot = process.cwd();\n const dbPath = join(projectRoot, ATOMIC_DIR, DB_FILE);\n\n try {\n // Check if initialized\n if (!(await fileExists(dbPath))) {\n throw new Error('Storage not initialized. Run `atomic init` first.');\n }\n\n if (runId) {\n // Show details for specific run\n const details = await getRunDetails(runId);\n\n if (!details) {\n throw new Error(`Run '${runId}' not found.`);\n }\n\n if (options.json) {\n console.log(JSON.stringify(details, null, 2));\n } else {\n console.log(formatRunDetails(details));\n }\n } else {\n // List history\n const limit = parseInt(options.limit ?? '20', 10);\n if (isNaN(limit) || limit < 1) {\n throw new Error('--limit must be a positive integer');\n }\n\n const runs = await listHistory({\n atom: options.atom,\n limit,\n });\n\n if (options.json) {\n console.log(JSON.stringify({ runs }, null, 2));\n } else {\n if (options.atom) {\n console.log(fmt.bold(`Execution History for \"${options.atom}\"`));\n } else {\n console.log(fmt.bold('Execution History'));\n }\n console.log('');\n console.log(formatRunTable(runs));\n }\n }\n } catch (err) {\n const message = toErrorMessage(err);\n if (options.json) {\n console.log(JSON.stringify({ error: message }, null, 2));\n } else {\n console.error(fmt.error(`History failed: ${message}`));\n }\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}