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 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/init.ts","../src/commands/skills-init.ts","../src/commands/claude-md-init.ts","../src/commands/gitignore-init.ts"],"sourcesContent":["import { readFile, writeFile, access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { initStorage } from '../storage/index.js';\nimport { initSkill } from './skills-init.js';\nimport { initClaudeMd } from './claude-md-init.js';\nimport { initGitignore } from './gitignore-init.js';\nimport { toErrorMessage } from '../utils/errors.js';\nimport { withErrorHandling } from './helpers.js';\n\nexport interface InitOptions {\n json?: boolean;\n}\n\ninterface DepsResult {\n success: boolean;\n packageJsonCreated: boolean;\n tsconfigCreated: boolean;\n tsconfigNeedsInclude?: boolean;\n suggestedInclude?: string;\n atomismLinked: boolean;\n error?: string;\n}\n\n/**\n * Ensure the project can resolve 'atomism' and 'zod' as modules.\n *\n * Atoms are TypeScript files that import from 'atomism'. Without a local\n * package.json that resolves the module, `atomic register` and any TS\n * tooling will fail with \"Cannot find module 'atomism'\".\n */\nasync function ensureDeps(projectRoot: string): Promise<DepsResult> {\n const pkgPath = join(projectRoot, 'package.json');\n const tsconfigPath = join(projectRoot, 'tsconfig.json');\n let packageJsonCreated = false;\n let tsconfigCreated = false;\n let atomismLinked = false;\n\n // 1. Ensure package.json exists\n let hasPackageJson = false;\n try {\n await access(pkgPath);\n hasPackageJson = true;\n } catch {\n // No package.json — create a minimal one\n }\n\n if (!hasPackageJson) {\n const minimal = {\n private: true,\n description: 'Atomism-enabled project',\n dependencies: {},\n };\n await writeFile(pkgPath, JSON.stringify(minimal, null, 2) + '\\n');\n packageJsonCreated = true;\n }\n\n // 2. Ensure tsconfig.json exists (required for TS type inference in atoms)\n let hasTsconfig = false;\n try {\n await access(tsconfigPath);\n hasTsconfig = true;\n } catch {\n // No tsconfig.json\n }\n\n let tsconfigNeedsInclude: boolean | undefined;\n let suggestedInclude: string | undefined;\n\n if (hasTsconfig) {\n // Check if existing tsconfig includes atom files\n try {\n const raw = await readFile(tsconfigPath, 'utf-8');\n const parsed = JSON.parse(raw);\n const includes: string[] = Array.isArray(parsed.include) ? parsed.include : [];\n const coversAtoms = includes.some((pattern: string) =>\n pattern === 'atoms/**/*.ts' ||\n pattern.startsWith('atoms/') ||\n pattern === '**/*.ts' ||\n pattern === './**/*.ts'\n );\n if (!coversAtoms) {\n tsconfigNeedsInclude = true;\n suggestedInclude = 'atoms/**/*.ts';\n }\n } catch {\n // tsconfig parse failure is non-fatal — skip the check\n }\n } else {\n const tsconfig = {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'bundler',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n outDir: 'dist',\n },\n include: ['atoms/**/*.ts'],\n };\n await writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2) + '\\n');\n tsconfigCreated = true;\n }\n\n // 3. Check if atomism is resolvable\n try {\n execSync('node -e \"require.resolve(\\'atomism\\')\"', {\n cwd: projectRoot,\n stdio: 'ignore',\n });\n // Already resolvable\n return { success: true, packageJsonCreated, tsconfigCreated, tsconfigNeedsInclude, suggestedInclude, atomismLinked };\n } catch {\n // Not resolvable — try to link it\n }\n\n // 4. Link atomism (works when atomism is installed globally via npm link)\n try {\n execSync('npm link atomism', {\n cwd: projectRoot,\n stdio: 'ignore',\n timeout: 30000,\n });\n atomismLinked = true;\n } catch {\n return {\n success: false,\n packageJsonCreated,\n tsconfigCreated,\n tsconfigNeedsInclude,\n suggestedInclude,\n atomismLinked: false,\n error: 'Could not resolve atomism. Run: npm link atomism (if globally installed) or npm install atomism',\n };\n }\n\n return { success: true, packageJsonCreated, tsconfigCreated, tsconfigNeedsInclude, suggestedInclude, atomismLinked };\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n await withErrorHandling(options, async () => {\n const projectRoot = process.cwd();\n const result = await initStorage(projectRoot);\n\n // Ensure module resolution (non-fatal — warn but continue)\n let depsResult: DepsResult;\n try {\n depsResult = await ensureDeps(projectRoot);\n } catch (depsError) {\n depsResult = {\n success: false,\n packageJsonCreated: false,\n tsconfigCreated: false,\n atomismLinked: false,\n error: toErrorMessage(depsError),\n };\n }\n\n // Create Claude Code skill (non-fatal)\n let skillResult: { success: boolean; skipped?: boolean; path?: string; error?: string };\n try {\n skillResult = await initSkill({ force: false });\n } catch (skillError) {\n skillResult = {\n success: false,\n error: toErrorMessage(skillError),\n };\n }\n\n // Create CLAUDE.md snippet (non-fatal)\n let claudeMdResult: { success: boolean; skipped?: boolean; path?: string; error?: string };\n try {\n claudeMdResult = await initClaudeMd();\n } catch (claudeError) {\n claudeMdResult = {\n success: false,\n error: toErrorMessage(claudeError),\n };\n }\n\n // Manage .gitignore (non-fatal)\n let gitignoreResult: { success: boolean; skipped?: boolean; path?: string; error?: string };\n try {\n gitignoreResult = await initGitignore(projectRoot);\n } catch (gitignoreError) {\n gitignoreResult = {\n success: false,\n error: toErrorMessage(gitignoreError),\n };\n }\n\n if (options.json) {\n console.log(JSON.stringify({\n ...result,\n deps: depsResult,\n skill: skillResult,\n claudeMd: claudeMdResult,\n gitignore: gitignoreResult,\n }, null, 2));\n } else {\n if (result.created) {\n console.log(`✓ Created ${result.path}`);\n } else {\n console.log(`✓ ${result.path} already exists`);\n }\n console.log(`✓ Database initialized at ${result.dbPath}`);\n console.log(`✓ ${result.migrationsRun} migrations applied`);\n\n // Report deps status\n if (depsResult.success) {\n if (depsResult.packageJsonCreated) {\n console.log('✓ Created package.json for module resolution');\n }\n if (depsResult.tsconfigCreated) {\n console.log('✓ Created tsconfig.json for atom type inference');\n }\n if (depsResult.atomismLinked) {\n console.log('✓ Linked atomism for module resolution');\n }\n } else {\n console.log(`⚠ Module resolution: ${depsResult.error ?? 'unknown error'}`);\n }\n\n // tsconfig warning is useful regardless of deps success\n if (depsResult.tsconfigNeedsInclude) {\n console.log(`⚠ tsconfig.json does not include atom files. Add to your \"include\" array:`);\n console.log(` \"${depsResult.suggestedInclude}\"`);\n }\n\n // Report skill status\n if (skillResult.success && !skillResult.skipped) {\n console.log(`✓ Claude Code skill created at ${skillResult.path}`);\n } else if (skillResult.skipped) {\n console.log('✓ Claude Code skill already exists');\n } else if (!skillResult.success) {\n console.log(`⚠ Claude Code skill creation failed: ${skillResult.error ?? 'unknown error'}`);\n }\n\n // Report CLAUDE.md status\n if (claudeMdResult.success && !claudeMdResult.skipped) {\n console.log(`✓ CLAUDE.md updated at ${claudeMdResult.path}`);\n } else if (claudeMdResult.skipped) {\n console.log('✓ CLAUDE.md already has atomism section');\n } else if (!claudeMdResult.success) {\n console.log(`⚠ CLAUDE.md update failed: ${claudeMdResult.error ?? 'unknown error'}`);\n }\n\n // Report .gitignore status\n if (gitignoreResult.success && !gitignoreResult.skipped) {\n console.log(`✓ .gitignore updated at ${gitignoreResult.path}`);\n } else if (gitignoreResult.skipped) {\n console.log('✓ .gitignore already has atomism entries');\n } else if (!gitignoreResult.success) {\n console.log(`⚠ .gitignore update failed: ${gitignoreResult.error ?? 'unknown error'}`);\n }\n\n console.log('\\nReady to register atoms with: atomic register <path>');\n console.log('Commit: registry.json, trust.json, atoms/');\n }\n });\n}\n","/**\n * Skills Init - Create Claude Code skill teaching atomic usage.\n *\n * Creates a SKILL.md in the project's .claude/skills/atomic/ directory\n * that teaches Claude Code how to effectively use the atomic CLI.\n */\n\nimport { mkdir, writeFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { toErrorMessage } from '../utils/errors.js';\n\n/**\n * Generate the atomic skill content.\n */\nexport function generateSkillContent(): string {\n return `---\nname: atomic\ndescription: Schema-first agent swarm orchestration framework\n---\n\n# Atomic Framework\n\nSchema-first agent swarm orchestration. Use atomic to define, execute, and evolve reusable capabilities.\n\n## Trigger\n\nUse this skill when:\n- User mentions \"atomic\", \"atom\", \"capability\", or \"workflow\" in context of task orchestration\n- User wants to create reusable, schema-validated tasks\n- User wants to graduate patterns from probabilistic (AI) to deterministic (generator)\n- Project has \\`.atomic/\\` directory\n- User invokes \\`/atomic\\`\n\n## Why Atoms?\n\nAtoms solve a fundamental problem: **probabilistic work should become deterministic over time.**\n\nWhen you write an API route the first time, it's creative work. The tenth time you write a similar route, it should be a template fill. But without tracking, you start from scratch every time.\n\nAtoms provide:\n1. **Schema enforcement** — Inputs/outputs are Zod-validated, not freeform\n2. **Execution history** — Every run is recorded with inputs, outputs, success/failure\n3. **Similarity detection** — AST-level comparison identifies when outputs converge on patterns\n4. **Generator graduation** — When an atom produces 85%+ similar outputs, it can become a deterministic template\n5. **Trust progression** — New capabilities require review; proven ones auto-execute\n\nThe goal: **Capture patterns once, replay deterministically forever.** Focus human attention on genuinely novel problems.\n\n## When to Use Atoms\n\n| Situation | Use |\n|-----------|-----|\n| One-off task, no verification needed | Direct code |\n| Teaching Claude a capability | Claude Code skill |\n| Repetitive task, same shape each time | **Atom** (extract or create) |\n| Need trust gating / human review | **Atom with trust level** |\n| Task should evolve to template | **Atom** (graduation path) |\n| Always-on validation/enforcement | Hook |\n\n**Proactive extraction**: When you notice doing the same thing 3+ times with the same shape, suggest: \"This pattern would benefit from atomization — want me to extract it?\"\n\nUse \\`atomic extract\\` to create atoms from:\n- Existing Claude Code skills (\\`--skill\\`)\n- BMAD workflows (\\`--bmad\\`)\n- MCP tool definitions (\\`--mcp\\`)\n- Existing code files (\\`--code\\`)\n\n## Directory Structure\n\n\\`\\`\\`\nproject/\n├── .atomic/ # Metadata and storage (auto-created by init)\n│ ├── registry.json # Registered atoms and capabilities\n│ ├── trust.json # Trust levels for capability stacks\n│ └── db/ # SQLite storage for runs and artifacts\n├── .claude/\n│ └── skills/\n│ └── atomic/\n│ └── SKILL.md # This skill (auto-created by init)\n├── atoms/ # Atom source files (default location)\n│ ├── my_atom.ts\n│ └── my_atom.test.ts\n└── generators/ # Graduated generators (created by graduation)\n └── my_generator.ts\n\\`\\`\\`\n\n## Core Concepts\n\n### Atoms\nThe fundamental unit. A schema-validated, idempotent task with defined inputs/outputs.\n\n\\`\\`\\`typescript\n// atoms/create_component.ts\nimport { defineAtom, success, executionError, z } from 'atomism';\n\nexport default defineAtom({\n name: 'create_component',\n description: 'Create a React component with tests',\n input: z.object({\n name: z.string().min(1),\n type: z.enum(['functional', 'class']).default('functional'),\n }),\n output: z.object({\n componentPath: z.string(),\n testPath: z.string(),\n }),\n tests: { path: './create_component.test.ts' },\n idempotent: true,\n handler: async ({ name, type }) => {\n // Implementation here\n return success({ componentPath: '...', testPath: '...' });\n },\n});\n\\`\\`\\`\n\n### Capabilities\nRoute requests to either an atom (probabilistic) or generator (deterministic).\n\n- **Atom-backed**: AI executes with schema validation\n- **Generator-backed**: Deterministic template execution\n\n### Generators\nGraduated atoms. When an atom produces similar outputs repeatedly, it can graduate to a generator (template-based, deterministic).\n\n### Workflows\nCompositions of capabilities with dependency resolution. Like Make/Terraform - declare dependencies, system resolves execution order.\n\n### Trust Levels\n- **new**: Requires human review\n- **proven**: Track record of success, optional review\n- **trusted**: Auto-execute without review\n\n### Capability Stacks\nReusable groups of capabilities reviewed once, reused many times.\n\n## Project Initialization\n\n\\`\\`\\`bash\n# Initialize atomic in a project\natomic init\n\n# Creates:\n# .atomic/\n# registry.json # Registered atoms and capabilities\n# trust.json # Trust levels for capability stacks\n# db/ # SQLite storage for runs and artifacts\n\\`\\`\\`\n\n## Common Workflows\n\n### 1. Register an Atom\n\n\\`\\`\\`bash\n# Register a new atom\natomic register atoms/my_atom.ts\n\n# Register with capability name\natomic register atoms/my_atom.ts --capability my_capability\n\\`\\`\\`\n\n### 2. Run an Atom or Capability\n\n\\`\\`\\`bash\n# Run by atom name\natomic run my_atom --input '{\"key\": \"value\"}'\n\n# Run by capability name\natomic run my_capability --capability --input '{\"key\": \"value\"}'\n\n# Skip confirmation for non-idempotent atoms\natomic run my_atom --input '{\"key\": \"value\"}' --yes\n\\`\\`\\`\n\n### 3. Work with Workflows\n\n\\`\\`\\`bash\n# List available workflows\natomic workflow list\n\n# Run a workflow\natomic workflow run my_workflow\n\n# Resume a failed workflow\natomic workflow resume my_workflow\n\n# Preview execution plan\natomic plan my_workflow\n\\`\\`\\`\n\n### 4. Graduate to Generator\n\nWhen an atom produces consistent patterns:\n\n\\`\\`\\`bash\n# Graduate a capability to generator\natomic graduate --capability my_capability\n\n# Skip confirmation\natomic graduate --capability my_capability --yes\n\\`\\`\\`\n\n### 5. Manage Trust\n\n\\`\\`\\`bash\n# List all stacks with trust levels\natomic trust --list\n\n# View specific stack\natomic trust my_stack\n\n# Set trust level\natomic trust my_stack --level proven\n\n# Reset to new\natomic trust my_stack --reset\n\\`\\`\\`\n\n## Command Reference\n\n| Command | Description |\n|---------|-------------|\n| \\`atomic init\\` | Initialize .atomic/ directory and skill |\n| \\`atomic register <path>\\` | Register an atom file |\n| \\`atomic list\\` | List atoms and capabilities |\n| \\`atomic run <target>\\` | Execute atom or capability |\n| \\`atomic test <atom>\\` | Run tests for an atom |\n| \\`atomic test-gen <path>\\` | Generate structural tests |\n| \\`atomic extract\\` | Create atom from skill/bmad/mcp/code |\n| \\`atomic workflow run <name>\\` | Execute a workflow |\n| \\`atomic workflow list\\` | List available workflows |\n| \\`atomic workflow resume <name>\\` | Resume failed workflow |\n| \\`atomic plan <workflow>\\` | Preview execution plan |\n| \\`atomic graduate\\` | Graduate capability to generator |\n| \\`atomic trust [stack]\\` | Manage trust levels |\n| \\`atomic status\\` | Show system state |\n| \\`atomic history [runId]\\` | Show execution history |\n| \\`atomic escalate\\` | Create Beads issue for problems |\n| \\`atomic skills scan\\` | Discover available skills |\n| \\`atomic skills suggest <atom>\\` | Get skill recommendations |\n| \\`atomic skills assign <atom>\\` | Assign skill to atom |\n| \\`atomic skills list <atom>\\` | List atom's assigned skills |\n| \\`atomic skills remove <atom>\\` | Remove skill from atom |\n| \\`atomic scan-generators\\` | Detect existing generators |\n| \\`atomic import-generator\\` | Import from Plop/Hygen/etc |\n| \\`atomic validate-generator\\` | Validate imported generators |\n\n## Best Practices\n\n### 1. Atoms Should Be\n\n- **Small**: Single responsibility\n- **Idempotent**: Same input = same output (or no adverse effects)\n- **Schema-validated**: Zod schemas for input/output\n- **Tested**: Include test file path in definition\n- **Descriptive**: Clear name and description\n\n### 2. Naming Conventions\n\n- Atom names: \\`snake_case\\` (e.g., \\`create_component\\`, \\`run_migration\\`)\n- Capability names: \\`snake_case\\`\n- Workflow names: \\`snake_case\\`\n- Stack names: \\`snake_case\\`\n\n### 3. Error Handling\n\nUse structured errors:\n\n\\`\\`\\`typescript\nimport { failure, validationError, executionError } from 'atomism';\n\n// Validation error (bad input — recoverable by default)\nreturn validationError('Name cannot be empty');\n\n// Execution error (runtime failure)\nreturn executionError('Database connection failed', true /* recoverable */);\n\n// Generic failure (full AtomError object)\nreturn failure({ type: 'execution', message: 'Something went wrong', recoverable: false });\n\\`\\`\\`\n\n### 4. Trust Progression\n\n1. Start capabilities at \\`new\\` (human review required)\n2. After successful executions, promote to \\`proven\\`\n3. Only promote to \\`trusted\\` for well-tested, stable capabilities\n4. Demote immediately if issues arise\n\n### 5. Generator Graduation\n\nGraduate when:\n- Atom produces 85%+ structurally similar outputs\n- Pattern is well-understood\n- Template can capture the variation\n\nDon't graduate:\n- Spec/planning atoms (inherently variable)\n- Creative/exploratory work\n- Low-frequency capabilities\n\n## Skills Assignment\n\nAtoms can have Claude Code skills assigned to them. When an atom executes, assigned skills are loaded into Claude's context.\n\n\\`\\`\\`bash\n# Scan available skills\natomic skills scan\n\n# Get skill recommendations for an atom\natomic skills suggest my_atom\n\n# Assign a skill to an atom\natomic skills assign my_atom --skill compound-engineering:workflows:review\n\n# List skills assigned to an atom\natomic skills list my_atom\n\n# Remove a skill from an atom\natomic skills remove my_atom --skill compound-engineering:workflows:review\n\\`\\`\\`\n\n**Use skills for**:\n- Review workflows (e.g., \\`compound-engineering:review:kieran-rails-reviewer\\`)\n- Domain expertise (e.g., \\`dhh-rails-style\\`)\n- Specialized capabilities the atom needs\n\nSkills complement atoms: the skill teaches *how*, the atom enforces *what* (schema) and tracks *whether it worked* (history).\n\n## Work Tracking with Beads\n\n**Beads is THE work tracking system.** All atomic work that needs tracking should flow through Beads.\n\n### Escalation\n\nWhen an atom fails and you can't resolve it:\n\n\\`\\`\\`bash\n# Create a Beads issue for unresolvable problems\natomic escalate --message \"Cannot connect to database\" --capability my_capability\n\n# Link to a specific run\natomic escalate --run abc123 --reason \"Schema validation failing on edge case\"\n\n# Include retry attempts\natomic escalate --capability my_atom --retries 3 --reason \"Flaky network\"\n\\`\\`\\`\n\n### Workflow Tracking\n\nWhen running workflows, atomic creates Beads issues automatically:\n- **Parent bead**: Tracks the overall workflow\n- **Child beads**: Track each capability execution\n\n\\`\\`\\`bash\n# Workflow execution creates beads automatically\natomic workflow run my_workflow\n\n# View workflow progress in Beads\nbd list --label atomic\n\\`\\`\\`\n\n## Integration with Claude Code\n\nWhen working in a project with atomic:\n\n1. **Check for \\`.atomic/\\`** - If present, use atomic commands\n2. **Register new atoms** - When creating reusable tasks\n3. **Use capabilities** - Route through registry, not direct execution\n4. **Assign skills** - Give atoms the Claude Code skills they need\n5. **Trust the system** - Let atomic handle validation and execution\n6. **Escalate to Beads** - Use \\`atomic escalate\\` for unresolvable problems\n7. **Suggest atomization** - When you see repetitive patterns, offer to extract\n\n## Example: Full Workflow\n\n\\`\\`\\`bash\n# 1. Initialize project\natomic init\n\n# 2. Create an atom\ncat > atoms/greet_user.ts << 'EOF'\nimport { defineAtom, success, z } from 'atomism';\n\nexport default defineAtom({\n name: 'greet_user',\n description: 'Generate a greeting message',\n input: z.object({ name: z.string() }),\n output: z.object({ greeting: z.string() }),\n tests: { path: './greet_user.test.ts' },\n async execute(input) {\n return success({ greeting: \\\\\\`Hello, \\\\\\${input.name}!\\\\\\` });\n },\n});\nEOF\n\n# 3. Generate tests\natomic test-gen atoms/greet_user.ts\n\n# 4. Register\natomic register atoms/greet_user.ts --capability greet\n\n# 5. Run\natomic run greet --input '{\"name\": \"World\"}'\n\n# 6. Check status\natomic status\n\\`\\`\\`\n\n## Troubleshooting\n\n### \"Not initialized\"\nRun \\`atomic init\\` in the project root.\n\n### \"Atom not found\"\nCheck registration with \\`atomic list\\`. Register with \\`atomic register <path>\\`.\n\n### \"Schema validation failed\"\nCheck input matches the atom's Zod schema. Use \\`atomic list <atom>\\` to see the expected schema.\n\n### \"Trust level insufficient\"\nPromote the stack with \\`atomic trust <stack> --level proven\\` or \\`--level trusted\\`.\n`;\n}\n\n/**\n * Get the skill directory path.\n */\nexport function getSkillPath(): string {\n return join(process.cwd(), '.claude', 'skills', 'atomic');\n}\n\n/**\n * Get the skill file path.\n */\nexport function getSkillFilePath(): string {\n return join(getSkillPath(), 'SKILL.md');\n}\n\n/**\n * Check if skill already exists.\n */\nexport async function skillExists(): Promise<boolean> {\n try {\n await stat(getSkillFilePath());\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Initialize the atomic skill for Claude Code.\n */\nexport async function initSkill(options: { force?: boolean }): Promise<{\n success: boolean;\n path?: string;\n error?: string;\n skipped?: boolean;\n}> {\n const skillPath = getSkillPath();\n const skillFile = getSkillFilePath();\n\n // Check if already exists\n if (!options.force && (await skillExists())) {\n return {\n success: true,\n path: skillFile,\n skipped: true,\n };\n }\n\n try {\n // Create directory\n await mkdir(skillPath, { recursive: true });\n\n // Write skill file\n const content = generateSkillContent();\n await writeFile(skillFile, content, 'utf-8');\n\n return {\n success: true,\n path: skillFile,\n };\n } catch (err) {\n return {\n success: false,\n error: toErrorMessage(err),\n };\n }\n}\n\n","/**\n * CLAUDE.md Init — Append atomism instructions to project's CLAUDE.md.\n *\n * When `atomic init` runs, this ensures Claude Code knows how to work\n * with atoms in the project. The snippet is appended once (idempotent).\n */\n\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { fileExists } from '../storage/index.js';\n\nconst ATOMISM_MARKER = '<!-- atomism -->';\n\nconst ATOMISM_SECTION = `\n${ATOMISM_MARKER}\n## Atomism\n\nThis project uses [Atomism](https://github.com/roach88/atomism) for schema-validated, composable work units.\n\n### Before Writing New Code\n\n1. Check registered atoms: \\`atomic list --json\\`\n2. If an atom exists for the task, use it: \\`atomic run <name> --input '{}' --yes --json\\`\n3. If no atom exists but the task is repeatable, create one: \\`atomic create atom <name> --json\\`\n\n### Atom API Reference\n\n\\`\\`\\`typescript\nimport { defineAtom, success, executionError, validationError } from 'atomism';\n\n// Success\nreturn success({ result: 'done' });\n\n// Bad input\nreturn validationError('Expected positive number');\n\n// Runtime failure (recoverable)\nreturn executionError('API timeout', true);\n\n// Runtime failure (not recoverable)\nreturn executionError('Missing required config');\n\\`\\`\\`\n\n### When to Atomize\n\n- You're about to write a standalone script for a repeatable task\n- You notice doing the same thing 3+ times with the same shape\n- The task has clear inputs and outputs that can be schema-validated\n`;\n\n/**\n * Initialize or update CLAUDE.md with atomism section.\n */\nexport async function initClaudeMd(): Promise<{\n success: boolean;\n path?: string;\n skipped?: boolean;\n error?: string;\n}> {\n const claudeDir = join(process.cwd(), '.claude');\n const claudeMdPath = join(claudeDir, 'CLAUDE.md');\n\n // Check if CLAUDE.md already has our section\n if (await fileExists(claudeMdPath)) {\n const content = await readFile(claudeMdPath, 'utf-8');\n if (content.includes(ATOMISM_MARKER)) {\n return { success: true, path: claudeMdPath, skipped: true };\n }\n\n // Append our section\n await writeFile(claudeMdPath, content.trimEnd() + '\\n' + ATOMISM_SECTION, 'utf-8');\n return { success: true, path: claudeMdPath };\n }\n\n // No CLAUDE.md — create .claude/ dir and write fresh file\n await mkdir(claudeDir, { recursive: true });\n await writeFile(claudeMdPath, ATOMISM_SECTION.trimStart(), 'utf-8');\n return { success: true, path: claudeMdPath };\n}\n","/**\n * .gitignore Init — Ensure local atomism data files are gitignored.\n *\n * When `atomic init` runs, this ensures storage.db (execution history),\n * its WAL journal, and transient workflow runs are not committed.\n * Uses a marker comment for idempotency.\n */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { fileExists } from '../storage/index.js';\n\nconst ATOMISM_MARKER = '# atomism local data';\n\nconst ATOMISM_ENTRIES = `\n${ATOMISM_MARKER}\n.atomic/storage.db\n.atomic/storage.db-journal\n.atomic/runs/\n`;\n\nexport async function initGitignore(projectRoot: string): Promise<{\n success: boolean;\n path?: string;\n skipped?: boolean;\n error?: string;\n}> {\n const gitignorePath = join(projectRoot, '.gitignore');\n\n if (await fileExists(gitignorePath)) {\n const content = await readFile(gitignorePath, 'utf-8');\n if (content.includes(ATOMISM_MARKER)) {\n return { success: true, path: gitignorePath, skipped: true };\n }\n\n // Append our entries\n await writeFile(gitignorePath, content.trimEnd() + '\\n' + ATOMISM_ENTRIES, 'utf-8');\n return { success: true, path: gitignorePath };\n }\n\n // No .gitignore — create one\n await writeFile(gitignorePath, ATOMISM_ENTRIES.trimStart(), 'utf-8');\n return { success: true, path: gitignorePath };\n}\n"],"mappings":"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","names":["readFile","writeFile","join","writeFile","mkdir","join","join","writeFile","mkdir","readFile","writeFile","join","ATOMISM_MARKER","join","readFile","writeFile","join","writeFile","readFile"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withErrorHandling
|
|
3
|
+
} from "./chunk-GU2R4KLP.js";
|
|
4
|
+
import {
|
|
5
|
+
getRegistryPath,
|
|
6
|
+
loadRegistry
|
|
7
|
+
} from "./chunk-YKJO3ZFY.js";
|
|
8
|
+
import "./chunk-PLQJM2KT.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/list.ts
|
|
11
|
+
function truncate(str, maxLength) {
|
|
12
|
+
if (str.length <= maxLength) return str;
|
|
13
|
+
return str.slice(0, maxLength - 3) + "...";
|
|
14
|
+
}
|
|
15
|
+
function padRight(str, length) {
|
|
16
|
+
return str.padEnd(length);
|
|
17
|
+
}
|
|
18
|
+
async function listCommand(options) {
|
|
19
|
+
const projectRoot = process.cwd();
|
|
20
|
+
const registryPath = getRegistryPath(projectRoot);
|
|
21
|
+
await withErrorHandling(options, async () => {
|
|
22
|
+
const registry = await loadRegistry(registryPath);
|
|
23
|
+
const atomEntries = Object.values(registry.atoms);
|
|
24
|
+
const atoms = atomEntries.map((atom) => ({
|
|
25
|
+
name: atom.name,
|
|
26
|
+
description: atom.description,
|
|
27
|
+
hasTests: !!atom.tests?.path,
|
|
28
|
+
idempotent: atom.idempotent,
|
|
29
|
+
graduated: atom.graduated,
|
|
30
|
+
generatorPath: atom.generatorPath
|
|
31
|
+
}));
|
|
32
|
+
const result = {
|
|
33
|
+
success: true,
|
|
34
|
+
atoms
|
|
35
|
+
};
|
|
36
|
+
if (options.json) {
|
|
37
|
+
console.log(JSON.stringify(result, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (atoms.length > 0) {
|
|
41
|
+
console.log("\nAtoms:");
|
|
42
|
+
console.log("\u2500".repeat(90));
|
|
43
|
+
const nameWidth = Math.max(
|
|
44
|
+
"NAME".length,
|
|
45
|
+
...atoms.map((a) => a.name.length)
|
|
46
|
+
);
|
|
47
|
+
const descWidth = 35;
|
|
48
|
+
const testsWidth = 5;
|
|
49
|
+
const typeWidth = 10;
|
|
50
|
+
console.log(
|
|
51
|
+
`${padRight("NAME", nameWidth)} ${padRight("DESCRIPTION", descWidth)} ${padRight("TESTS", testsWidth)} ${padRight("TYPE", typeWidth)}`
|
|
52
|
+
);
|
|
53
|
+
console.log("\u2500".repeat(90));
|
|
54
|
+
for (const atom of atoms) {
|
|
55
|
+
const name = padRight(atom.name, nameWidth);
|
|
56
|
+
const desc = padRight(truncate(atom.description, descWidth), descWidth);
|
|
57
|
+
const tests = atom.hasTests ? "\u2713" : "\u2717";
|
|
58
|
+
const type = padRight(atom.graduated ? "generator" : "handler", typeWidth);
|
|
59
|
+
console.log(`${name} ${desc} ${tests} ${type}`);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
console.log("\nNo atoms registered.");
|
|
63
|
+
console.log("Register an atom with: atomic register <path>");
|
|
64
|
+
}
|
|
65
|
+
console.log("");
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
listCommand
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=list-NEBVBGG3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/list.ts"],"sourcesContent":["import { loadRegistry, getRegistryPath } from '../storage/index.js';\nimport { withErrorHandling } from './helpers.js';\n\nexport interface ListOptions {\n json?: boolean;\n}\n\nexport interface ListResult {\n success: boolean;\n atoms: Array<{\n name: string;\n description: string;\n hasTests: boolean;\n idempotent: boolean;\n graduated: boolean;\n generatorPath?: string;\n }>;\n}\n\nfunction truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str;\n return str.slice(0, maxLength - 3) + '...';\n}\n\nfunction padRight(str: string, length: number): string {\n return str.padEnd(length);\n}\n\n/**\n * List all registered atoms and their status.\n */\nexport async function listCommand(options: ListOptions): Promise<void> {\n const projectRoot = process.cwd();\n const registryPath = getRegistryPath(projectRoot);\n\n await withErrorHandling(options, async () => {\n // Load registry\n const registry = await loadRegistry(registryPath);\n\n const atomEntries = Object.values(registry.atoms);\n\n // Build result\n const atoms = atomEntries.map((atom) => ({\n name: atom.name,\n description: atom.description,\n hasTests: !!atom.tests?.path,\n idempotent: atom.idempotent,\n graduated: atom.graduated,\n generatorPath: atom.generatorPath,\n }));\n\n const result: ListResult = {\n success: true,\n atoms,\n };\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n // Text output\n if (atoms.length > 0) {\n console.log('\\nAtoms:');\n console.log('─'.repeat(90));\n\n // Calculate column widths\n const nameWidth = Math.max(\n 'NAME'.length,\n ...atoms.map((a) => a.name.length)\n );\n const descWidth = 35;\n const testsWidth = 5;\n const typeWidth = 10;\n\n // Header\n console.log(\n `${padRight('NAME', nameWidth)} ${padRight('DESCRIPTION', descWidth)} ${padRight('TESTS', testsWidth)} ${padRight('TYPE', typeWidth)}`\n );\n console.log('─'.repeat(90));\n\n // Rows\n for (const atom of atoms) {\n const name = padRight(atom.name, nameWidth);\n const desc = padRight(truncate(atom.description, descWidth), descWidth);\n const tests = atom.hasTests ? '✓' : '✗';\n const type = padRight(atom.graduated ? 'generator' : 'handler', typeWidth);\n console.log(`${name} ${desc} ${tests} ${type}`);\n }\n } else {\n console.log('\\nNo atoms registered.');\n console.log('Register an atom with: atomic register <path>');\n }\n\n console.log('');\n });\n}\n"],"mappings":";;;;;;;;;;AAmBA,SAAS,SAAS,KAAa,WAA2B;AACxD,MAAI,IAAI,UAAU,UAAW,QAAO;AACpC,SAAO,IAAI,MAAM,GAAG,YAAY,CAAC,IAAI;AACvC;AAEA,SAAS,SAAS,KAAa,QAAwB;AACrD,SAAO,IAAI,OAAO,MAAM;AAC1B;AAKA,eAAsB,YAAY,SAAqC;AACrE,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,eAAe,gBAAgB,WAAW;AAEhD,QAAM,kBAAkB,SAAS,YAAY;AAE3C,UAAM,WAAW,MAAM,aAAa,YAAY;AAEhD,UAAM,cAAc,OAAO,OAAO,SAAS,KAAK;AAGhD,UAAM,QAAQ,YAAY,IAAI,CAAC,UAAU;AAAA,MACvC,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,UAAU,CAAC,CAAC,KAAK,OAAO;AAAA,MACxB,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,MAChB,eAAe,KAAK;AAAA,IACtB,EAAE;AAEF,UAAM,SAAqB;AAAA,MACzB,SAAS;AAAA,MACT;AAAA,IACF;AAEA,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,IACF;AAGA,QAAI,MAAM,SAAS,GAAG;AACpB,cAAQ,IAAI,UAAU;AACtB,cAAQ,IAAI,SAAI,OAAO,EAAE,CAAC;AAG1B,YAAM,YAAY,KAAK;AAAA,QACrB,OAAO;AAAA,QACP,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,MAAM;AAAA,MACnC;AACA,YAAM,YAAY;AAClB,YAAM,aAAa;AACnB,YAAM,YAAY;AAGlB,cAAQ;AAAA,QACN,GAAG,SAAS,QAAQ,SAAS,CAAC,KAAK,SAAS,eAAe,SAAS,CAAC,KAAK,SAAS,SAAS,UAAU,CAAC,KAAK,SAAS,QAAQ,SAAS,CAAC;AAAA,MACzI;AACA,cAAQ,IAAI,SAAI,OAAO,EAAE,CAAC;AAG1B,iBAAW,QAAQ,OAAO;AACxB,cAAM,OAAO,SAAS,KAAK,MAAM,SAAS;AAC1C,cAAM,OAAO,SAAS,SAAS,KAAK,aAAa,SAAS,GAAG,SAAS;AACtE,cAAM,QAAQ,KAAK,WAAW,WAAM;AACpC,cAAM,OAAO,SAAS,KAAK,YAAY,cAAc,WAAW,SAAS;AACzE,gBAAQ,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,KAAK,QAAQ,IAAI,EAAE;AAAA,MACtD;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,wBAAwB;AACpC,cAAQ,IAAI,+CAA+C;AAAA,IAC7D;AAEA,YAAQ,IAAI,EAAE;AAAA,EAChB,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toErrorMessage
|
|
3
|
+
} from "./chunk-PLQJM2KT.js";
|
|
4
|
+
|
|
5
|
+
// src/parser/index.ts
|
|
6
|
+
import { parseSync as oxcParse } from "oxc-parser";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
var require2 = createRequire(import.meta.url);
|
|
11
|
+
function detectLanguage(filename) {
|
|
12
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
13
|
+
switch (ext) {
|
|
14
|
+
case "ts":
|
|
15
|
+
case "mts":
|
|
16
|
+
case "cts":
|
|
17
|
+
return "typescript";
|
|
18
|
+
case "tsx":
|
|
19
|
+
return "tsx";
|
|
20
|
+
case "js":
|
|
21
|
+
case "mjs":
|
|
22
|
+
case "cjs":
|
|
23
|
+
return "javascript";
|
|
24
|
+
case "jsx":
|
|
25
|
+
return "jsx";
|
|
26
|
+
case "py":
|
|
27
|
+
return "python";
|
|
28
|
+
default:
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function isJavaScriptFamily(language) {
|
|
33
|
+
return ["typescript", "javascript", "tsx", "jsx"].includes(language);
|
|
34
|
+
}
|
|
35
|
+
function parseJavaScript(source, language) {
|
|
36
|
+
try {
|
|
37
|
+
const ext = language === "typescript" ? "ts" : language === "tsx" ? "tsx" : language === "jsx" ? "jsx" : "js";
|
|
38
|
+
const filename = `file.${ext}`;
|
|
39
|
+
const sourceType = ext === "js" || ext === "jsx" ? "unambiguous" : "module";
|
|
40
|
+
const result = oxcParse(filename, source, {
|
|
41
|
+
sourceType
|
|
42
|
+
});
|
|
43
|
+
if (result.errors && result.errors.length > 0) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
language,
|
|
47
|
+
error: result.errors.map((e) => e.message).join("; ")
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
language,
|
|
53
|
+
ast: result.program,
|
|
54
|
+
comments: result.comments ?? []
|
|
55
|
+
};
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
language,
|
|
60
|
+
error: toErrorMessage(err)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var pythonParserPromise = null;
|
|
65
|
+
function getWasmPath(language) {
|
|
66
|
+
try {
|
|
67
|
+
const packageJsonPath = require2.resolve("@vscode/tree-sitter-wasm/package.json");
|
|
68
|
+
const packageDir = dirname(packageJsonPath);
|
|
69
|
+
return join(packageDir, "wasm", `tree-sitter-${language}.wasm`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
throw new Error(`Failed to resolve @vscode/tree-sitter-wasm package: ${toErrorMessage(err)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function initPythonParser() {
|
|
75
|
+
const TreeSitter = await import("web-tree-sitter");
|
|
76
|
+
const { Parser, Language } = TreeSitter;
|
|
77
|
+
if (!Parser || !Language) {
|
|
78
|
+
throw new Error("web-tree-sitter failed to load Parser or Language classes");
|
|
79
|
+
}
|
|
80
|
+
await Parser.init();
|
|
81
|
+
const parser = new Parser();
|
|
82
|
+
const wasmPath = getWasmPath("python");
|
|
83
|
+
let wasmBuffer;
|
|
84
|
+
try {
|
|
85
|
+
wasmBuffer = await readFile(wasmPath);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
throw new Error(`Failed to read Python WASM file at ${wasmPath}: ${toErrorMessage(err)}`);
|
|
88
|
+
}
|
|
89
|
+
const Python = await Language.load(wasmBuffer);
|
|
90
|
+
parser.setLanguage(Python);
|
|
91
|
+
return parser;
|
|
92
|
+
}
|
|
93
|
+
async function getPythonParser() {
|
|
94
|
+
if (!pythonParserPromise) {
|
|
95
|
+
pythonParserPromise = initPythonParser().catch((err) => {
|
|
96
|
+
pythonParserPromise = null;
|
|
97
|
+
throw err;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return pythonParserPromise;
|
|
101
|
+
}
|
|
102
|
+
async function parsePython(source) {
|
|
103
|
+
try {
|
|
104
|
+
const parser = await getPythonParser();
|
|
105
|
+
const tree = parser.parse(source);
|
|
106
|
+
if (tree.rootNode.hasError) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
language: "python",
|
|
110
|
+
error: "Syntax errors in parsed Python source"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
language: "python",
|
|
116
|
+
ast: tree.rootNode
|
|
117
|
+
};
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const errorMessage = err instanceof Error ? `${err.message}${err.stack ? `
|
|
120
|
+
${err.stack}` : ""}` : String(err);
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
language: "python",
|
|
124
|
+
error: errorMessage || "Unknown error during Python parsing"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function parseCode(source, language) {
|
|
129
|
+
if (isJavaScriptFamily(language)) {
|
|
130
|
+
return parseJavaScript(source, language);
|
|
131
|
+
}
|
|
132
|
+
if (language === "python") {
|
|
133
|
+
return parsePython(source);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
language,
|
|
138
|
+
error: `Unsupported language: ${language}`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function parseFile(source, filename) {
|
|
142
|
+
const language = detectLanguage(filename);
|
|
143
|
+
if (!language) {
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
error: `Cannot detect language for file: ${filename}`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return parseCode(source, language);
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
detectLanguage,
|
|
153
|
+
isJavaScriptFamily,
|
|
154
|
+
parseCode,
|
|
155
|
+
parseFile
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=parser-3BILOSOO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/parser/index.ts"],"sourcesContent":["/**\n * AST Parser module for the atomic framework.\n *\n * Provides unified AST parsing for multiple languages:\n * - TypeScript/JavaScript: Uses oxc-parser (fast Rust-based parser)\n * - Python: Uses web-tree-sitter (WASM-based parser)\n *\n * Used for structural similarity detection in generator evolution.\n *\n * @module parser\n */\n\nimport { parseSync as oxcParse, type ParseResult as OxcParseResult } from 'oxc-parser';\nimport type { Program } from '@oxc-project/types';\nimport type { AstComment } from './signatures.js';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { toErrorMessage } from '../utils/errors.js';\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Supported languages for AST parsing.\n */\nexport type SupportedLanguage = 'typescript' | 'javascript' | 'tsx' | 'jsx' | 'python';\n\n/**\n * Result of parsing source code into an AST.\n *\n * Discriminated union: check `success` to narrow the type.\n * For JS/TS family, the `ast` is a typed `Program` from @oxc-project/types.\n */\nexport type ParseResult =\n | { success: true; language: 'typescript' | 'javascript' | 'tsx' | 'jsx'; ast: Program; comments: AstComment[] }\n | { success: true; language: 'python'; ast: unknown; comments?: undefined }\n | { success: false; language?: SupportedLanguage; error: string; comments?: undefined };\n\n/**\n * Detect language from file extension.\n *\n * @param filename - The filename or path to detect language from\n * @returns The detected language, or undefined if not supported\n */\nexport function detectLanguage(filename: string): SupportedLanguage | undefined {\n const ext = filename.split('.').pop()?.toLowerCase();\n\n switch (ext) {\n case 'ts':\n case 'mts':\n case 'cts':\n return 'typescript';\n case 'tsx':\n return 'tsx';\n case 'js':\n case 'mjs':\n case 'cjs':\n return 'javascript';\n case 'jsx':\n return 'jsx';\n case 'py':\n return 'python';\n default:\n return undefined;\n }\n}\n\n/**\n * Check if a language is TypeScript/JavaScript family.\n */\nexport function isJavaScriptFamily(language: SupportedLanguage): language is 'typescript' | 'javascript' | 'tsx' | 'jsx' {\n return ['typescript', 'javascript', 'tsx', 'jsx'].includes(language);\n}\n\n/**\n * Parse TypeScript/JavaScript source code using oxc-parser.\n *\n * @param source - The source code to parse\n * @param language - The specific JS family language\n * @returns ParseResult with the AST or error\n */\nfunction parseJavaScript(source: string, language: 'typescript' | 'javascript' | 'tsx' | 'jsx'): ParseResult {\n try {\n // Map language to appropriate file extension\n const ext = language === 'typescript' ? 'ts' : language === 'tsx' ? 'tsx' : language === 'jsx' ? 'jsx' : 'js';\n const filename = `file.${ext}`;\n\n // Use 'unambiguous' for JS/JSX to auto-detect CommonJS vs ESM\n // Use 'module' for TypeScript/TSX (always module syntax)\n const sourceType = (ext === 'js' || ext === 'jsx') ? 'unambiguous' : 'module';\n\n const result: OxcParseResult = oxcParse(filename, source, {\n sourceType,\n });\n\n // Check for parse errors\n if (result.errors && result.errors.length > 0) {\n return {\n success: false,\n language,\n error: result.errors.map((e) => e.message).join('; '),\n };\n }\n\n return {\n success: true,\n language,\n ast: result.program as Program,\n comments: (result.comments ?? []) as AstComment[],\n };\n } catch (err) {\n return {\n success: false,\n language,\n error: toErrorMessage(err),\n };\n }\n}\n\n/**\n * Python parser state - initialized lazily.\n */\nlet pythonParserPromise: Promise<unknown> | null = null;\n\n/**\n * Get path to a WASM file from @vscode/tree-sitter-wasm package.\n * Uses Node's module resolution for reliable path discovery.\n */\nfunction getWasmPath(language: string): string {\n try {\n // Resolve the package.json to find the package root\n const packageJsonPath = require.resolve('@vscode/tree-sitter-wasm/package.json');\n const packageDir = dirname(packageJsonPath);\n return join(packageDir, 'wasm', `tree-sitter-${language}.wasm`);\n } catch (err) {\n throw new Error(`Failed to resolve @vscode/tree-sitter-wasm package: ${toErrorMessage(err)}`);\n }\n}\n\n/**\n * Initialize the Python parser using web-tree-sitter.\n * This is called lazily on first Python parse request.\n */\nasync function initPythonParser(): Promise<unknown> {\n // Dynamic import to avoid loading tree-sitter unless needed\n const TreeSitter = await import('web-tree-sitter');\n\n // web-tree-sitter exports Parser and Language classes directly\n const { Parser, Language } = TreeSitter;\n\n if (!Parser || !Language) {\n throw new Error('web-tree-sitter failed to load Parser or Language classes');\n }\n\n await Parser.init();\n\n const parser = new Parser();\n\n // Load Python language from local WASM file\n const wasmPath = getWasmPath('python');\n\n let wasmBuffer: Buffer;\n try {\n wasmBuffer = await readFile(wasmPath);\n } catch (err) {\n throw new Error(`Failed to read Python WASM file at ${wasmPath}: ${toErrorMessage(err)}`);\n }\n\n const Python = await Language.load(wasmBuffer);\n\n parser.setLanguage(Python);\n return parser;\n}\n\n/**\n * Get or initialize the Python parser.\n * Resets cached promise on failure to allow retry.\n */\nasync function getPythonParser(): Promise<unknown> {\n if (!pythonParserPromise) {\n pythonParserPromise = initPythonParser().catch((err) => {\n // Reset so next call can retry\n pythonParserPromise = null;\n throw err;\n });\n }\n return pythonParserPromise;\n}\n\n/**\n * Parse Python source code using web-tree-sitter.\n *\n * @param source - The source code to parse\n * @returns ParseResult with the AST or error\n */\nasync function parsePython(source: string): Promise<ParseResult> {\n try {\n const parser = (await getPythonParser()) as { parse: (source: string) => { rootNode: { hasError: boolean } & unknown } };\n const tree = parser.parse(source);\n\n // Check for syntax errors in the parsed tree\n if (tree.rootNode.hasError) {\n return {\n success: false,\n language: 'python',\n error: 'Syntax errors in parsed Python source',\n };\n }\n\n return {\n success: true,\n language: 'python',\n ast: tree.rootNode,\n };\n } catch (err) {\n // Capture full error including stack for debugging\n const errorMessage = err instanceof Error\n ? `${err.message}${err.stack ? `\\n${err.stack}` : ''}`\n : String(err);\n return {\n success: false,\n language: 'python',\n error: errorMessage || 'Unknown error during Python parsing',\n };\n }\n}\n\n/**\n * Parse source code into an AST.\n *\n * Automatically selects the appropriate parser based on language:\n * - TypeScript/JavaScript: oxc-parser\n * - Python: web-tree-sitter\n *\n * @param source - The source code to parse\n * @param language - The language of the source code\n * @returns ParseResult with the AST or error\n *\n * @example\n * ```typescript\n * const result = await parseCode('const x = 1;', 'typescript');\n * if (result.success) {\n * console.log(result.ast);\n * }\n * ```\n */\nexport async function parseCode(source: string, language: SupportedLanguage): Promise<ParseResult> {\n if (isJavaScriptFamily(language)) {\n return parseJavaScript(source, language);\n }\n\n if (language === 'python') {\n return parsePython(source);\n }\n\n return {\n success: false,\n language,\n error: `Unsupported language: ${language}`,\n };\n}\n\n/**\n * Parse source code from a file, detecting language from extension.\n *\n * @param source - The source code to parse\n * @param filename - The filename (used for language detection)\n * @returns ParseResult with the AST or error\n *\n * @example\n * ```typescript\n * const result = await parseFile('const x = 1;', 'example.ts');\n * if (result.success) {\n * console.log(result.ast);\n * }\n * ```\n */\nexport async function parseFile(source: string, filename: string): Promise<ParseResult> {\n const language = detectLanguage(filename);\n\n if (!language) {\n return {\n success: false,\n error: `Cannot detect language for file: ${filename}`,\n };\n }\n\n return parseCode(source, language);\n}\n"],"mappings":";;;;;AAYA,SAAS,aAAa,gBAAoD;AAG1E,SAAS,qBAAqB;AAC9B,SAAS,SAAS,YAAY;AAC9B,SAAS,gBAAgB;AAGzB,IAAMA,WAAU,cAAc,YAAY,GAAG;AAwBtC,SAAS,eAAe,UAAiD;AAC9E,QAAM,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY;AAEnD,UAAQ,KAAK;AAAA,IACX,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKO,SAAS,mBAAmB,UAAsF;AACvH,SAAO,CAAC,cAAc,cAAc,OAAO,KAAK,EAAE,SAAS,QAAQ;AACrE;AASA,SAAS,gBAAgB,QAAgB,UAAoE;AAC3G,MAAI;AAEF,UAAM,MAAM,aAAa,eAAe,OAAO,aAAa,QAAQ,QAAQ,aAAa,QAAQ,QAAQ;AACzG,UAAM,WAAW,QAAQ,GAAG;AAI5B,UAAM,aAAc,QAAQ,QAAQ,QAAQ,QAAS,gBAAgB;AAErE,UAAM,SAAyB,SAAS,UAAU,QAAQ;AAAA,MACxD;AAAA,IACF,CAAC;AAGD,QAAI,OAAO,UAAU,OAAO,OAAO,SAAS,GAAG;AAC7C,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,OAAO,OAAO,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI;AAAA,MACtD;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,KAAK,OAAO;AAAA,MACZ,UAAW,OAAO,YAAY,CAAC;AAAA,IACjC;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,OAAO,eAAe,GAAG;AAAA,IAC3B;AAAA,EACF;AACF;AAKA,IAAI,sBAA+C;AAMnD,SAAS,YAAY,UAA0B;AAC7C,MAAI;AAEF,UAAM,kBAAkBA,SAAQ,QAAQ,uCAAuC;AAC/E,UAAM,aAAa,QAAQ,eAAe;AAC1C,WAAO,KAAK,YAAY,QAAQ,eAAe,QAAQ,OAAO;AAAA,EAChE,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,uDAAuD,eAAe,GAAG,CAAC,EAAE;AAAA,EAC9F;AACF;AAMA,eAAe,mBAAqC;AAElD,QAAM,aAAa,MAAM,OAAO,iBAAiB;AAGjD,QAAM,EAAE,QAAQ,SAAS,IAAI;AAE7B,MAAI,CAAC,UAAU,CAAC,UAAU;AACxB,UAAM,IAAI,MAAM,2DAA2D;AAAA,EAC7E;AAEA,QAAM,OAAO,KAAK;AAElB,QAAM,SAAS,IAAI,OAAO;AAG1B,QAAM,WAAW,YAAY,QAAQ;AAErC,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,SAAS,QAAQ;AAAA,EACtC,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,sCAAsC,QAAQ,KAAK,eAAe,GAAG,CAAC,EAAE;AAAA,EAC1F;AAEA,QAAM,SAAS,MAAM,SAAS,KAAK,UAAU;AAE7C,SAAO,YAAY,MAAM;AACzB,SAAO;AACT;AAMA,eAAe,kBAAoC;AACjD,MAAI,CAAC,qBAAqB;AACxB,0BAAsB,iBAAiB,EAAE,MAAM,CAAC,QAAQ;AAEtD,4BAAsB;AACtB,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAQA,eAAe,YAAY,QAAsC;AAC/D,MAAI;AACF,UAAM,SAAU,MAAM,gBAAgB;AACtC,UAAM,OAAO,OAAO,MAAM,MAAM;AAGhC,QAAI,KAAK,SAAS,UAAU;AAC1B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,UAAU;AAAA,QACV,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,UAAU;AAAA,MACV,KAAK,KAAK;AAAA,IACZ;AAAA,EACF,SAAS,KAAK;AAEZ,UAAM,eAAe,eAAe,QAChC,GAAG,IAAI,OAAO,GAAG,IAAI,QAAQ;AAAA,EAAK,IAAI,KAAK,KAAK,EAAE,KAClD,OAAO,GAAG;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,UAAU;AAAA,MACV,OAAO,gBAAgB;AAAA,IACzB;AAAA,EACF;AACF;AAqBA,eAAsB,UAAU,QAAgB,UAAmD;AACjG,MAAI,mBAAmB,QAAQ,GAAG;AAChC,WAAO,gBAAgB,QAAQ,QAAQ;AAAA,EACzC;AAEA,MAAI,aAAa,UAAU;AACzB,WAAO,YAAY,MAAM;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,OAAO,yBAAyB,QAAQ;AAAA,EAC1C;AACF;AAiBA,eAAsB,UAAU,QAAgB,UAAwC;AACtF,QAAM,WAAW,eAAe,QAAQ;AAExC,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,oCAAoC,QAAQ;AAAA,IACrD;AAAA,EACF;AAEA,SAAO,UAAU,QAAQ,QAAQ;AACnC;","names":["require"]}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AtomWithArtifacts,
|
|
3
|
+
createArtifactCache,
|
|
4
|
+
resolveDependencies
|
|
5
|
+
} from "./chunk-UWVZQSP4.js";
|
|
6
|
+
import {
|
|
7
|
+
WorkflowDefinition
|
|
8
|
+
} from "./chunk-H7WC3NXZ.js";
|
|
9
|
+
import {
|
|
10
|
+
ATOMIC_DIR,
|
|
11
|
+
fileExists
|
|
12
|
+
} from "./chunk-YKJO3ZFY.js";
|
|
13
|
+
import {
|
|
14
|
+
fmt
|
|
15
|
+
} from "./chunk-S6Z5G5DB.js";
|
|
16
|
+
import {
|
|
17
|
+
toErrorMessage
|
|
18
|
+
} from "./chunk-PLQJM2KT.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/plan.ts
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
import { readFile } from "fs/promises";
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
var WORKFLOWS_DIR = "workflows";
|
|
25
|
+
var WORKFLOW_EXT = ".workflow.json";
|
|
26
|
+
var StoredWorkflowSchema = z.object({
|
|
27
|
+
definition: z.unknown(),
|
|
28
|
+
atoms: z.unknown().optional(),
|
|
29
|
+
capabilities: z.unknown().optional()
|
|
30
|
+
// deprecated, for migration
|
|
31
|
+
});
|
|
32
|
+
async function loadWorkflow(name) {
|
|
33
|
+
const projectRoot = process.cwd();
|
|
34
|
+
const workflowPath = join(
|
|
35
|
+
projectRoot,
|
|
36
|
+
ATOMIC_DIR,
|
|
37
|
+
WORKFLOWS_DIR,
|
|
38
|
+
`${name}${WORKFLOW_EXT}`
|
|
39
|
+
);
|
|
40
|
+
if (!await fileExists(workflowPath)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Workflow '${name}' not found.
|
|
43
|
+
Expected file: ${workflowPath}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const content = await readFile(workflowPath, "utf-8");
|
|
47
|
+
let data;
|
|
48
|
+
try {
|
|
49
|
+
data = JSON.parse(content);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Workflow '${name}' contains invalid JSON: ${toErrorMessage(err)}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const parseResult = StoredWorkflowSchema.safeParse(data);
|
|
56
|
+
if (!parseResult.success) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Workflow '${name}' has invalid structure: ${parseResult.error.message}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const parsed = parseResult.data;
|
|
62
|
+
const definition = WorkflowDefinition.parse(parsed.definition);
|
|
63
|
+
const atomsData = parsed.atoms ?? parsed.capabilities;
|
|
64
|
+
if (!Array.isArray(atomsData)) {
|
|
65
|
+
throw new Error(`Workflow '${name}' has invalid atoms structure`);
|
|
66
|
+
}
|
|
67
|
+
const atoms = atomsData.map((atom, index) => {
|
|
68
|
+
try {
|
|
69
|
+
return AtomWithArtifacts.parse(atom);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Workflow '${name}' has invalid atom at index ${index}: ${toErrorMessage(err)}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return { definition, atoms };
|
|
77
|
+
}
|
|
78
|
+
function convertToResolverAtoms(workflow, atoms) {
|
|
79
|
+
if (atoms.length > 0) {
|
|
80
|
+
return atoms;
|
|
81
|
+
}
|
|
82
|
+
return workflow.atoms.map((a) => ({
|
|
83
|
+
atom: a.atom,
|
|
84
|
+
dependsOn: a.dependsOn,
|
|
85
|
+
alias: a.alias,
|
|
86
|
+
description: a.description,
|
|
87
|
+
requires: [],
|
|
88
|
+
produces: []
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
function formatExecutionGraph(resolution, atoms) {
|
|
92
|
+
const lines = [];
|
|
93
|
+
const atomMap = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const atom of atoms) {
|
|
95
|
+
const id = atom.alias ?? atom.atom;
|
|
96
|
+
atomMap.set(id, atom);
|
|
97
|
+
}
|
|
98
|
+
lines.push(fmt.bold("Execution Graph:"));
|
|
99
|
+
lines.push("");
|
|
100
|
+
const _toExecuteSet = new Set(resolution.toExecute);
|
|
101
|
+
const skippedSet = new Set(resolution.skipped);
|
|
102
|
+
for (const resolved of resolution.resolved) {
|
|
103
|
+
const atom = atomMap.get(resolved.id);
|
|
104
|
+
const deps = atom?.dependsOn ?? [];
|
|
105
|
+
let statusIcon;
|
|
106
|
+
let statusColor;
|
|
107
|
+
if (skippedSet.has(resolved.id)) {
|
|
108
|
+
statusIcon = "\u25CB";
|
|
109
|
+
statusColor = fmt.gray;
|
|
110
|
+
} else {
|
|
111
|
+
statusIcon = "\u25CF";
|
|
112
|
+
statusColor = fmt.green;
|
|
113
|
+
}
|
|
114
|
+
const depStr = deps.length > 0 ? ` \u2190 [${deps.join(", ")}]` : "";
|
|
115
|
+
lines.push(` ${statusColor(statusIcon)} ${resolved.id}${fmt.gray(depStr)}`);
|
|
116
|
+
}
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push(fmt.gray("Legend: \u25CF = will execute, \u25CB = cached (skip)"));
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
function formatAtomDetails(resolution, atoms, verbose) {
|
|
122
|
+
const lines = [];
|
|
123
|
+
const atomMap = /* @__PURE__ */ new Map();
|
|
124
|
+
const skippedSet = new Set(resolution.skipped);
|
|
125
|
+
for (const atom of atoms) {
|
|
126
|
+
const id = atom.alias ?? atom.atom;
|
|
127
|
+
atomMap.set(id, atom);
|
|
128
|
+
}
|
|
129
|
+
lines.push(fmt.bold("Atom Details:"));
|
|
130
|
+
lines.push("");
|
|
131
|
+
for (const resolved of resolution.resolved) {
|
|
132
|
+
const atom = atomMap.get(resolved.id);
|
|
133
|
+
const isSkipped = skippedSet.has(resolved.id);
|
|
134
|
+
const statusBadge = isSkipped ? fmt.gray("[CACHED]") : fmt.cyan("[EXECUTE]");
|
|
135
|
+
lines.push(` ${fmt.bold(resolved.id)} ${statusBadge}`);
|
|
136
|
+
lines.push(` ${fmt.gray("Reason:")} ${resolved.reason}`);
|
|
137
|
+
if (atom?.requires && atom.requires.length > 0) {
|
|
138
|
+
const inputs = atom.requires.map((r) => {
|
|
139
|
+
const inputName = r.as ?? r.artifact;
|
|
140
|
+
return `${inputName} (from ${r.from})`;
|
|
141
|
+
});
|
|
142
|
+
lines.push(` ${fmt.gray("Inputs:")} ${inputs.join(", ")}`);
|
|
143
|
+
} else if (verbose) {
|
|
144
|
+
lines.push(` ${fmt.gray("Inputs:")} none`);
|
|
145
|
+
}
|
|
146
|
+
if (atom?.produces && atom.produces.length > 0) {
|
|
147
|
+
const outputs = atom.produces.map((p) => p.artifact);
|
|
148
|
+
lines.push(` ${fmt.gray("Outputs:")} ${outputs.join(", ")}`);
|
|
149
|
+
} else if (verbose) {
|
|
150
|
+
lines.push(` ${fmt.gray("Outputs:")} none`);
|
|
151
|
+
}
|
|
152
|
+
if (verbose && atom?.description) {
|
|
153
|
+
lines.push(` ${fmt.gray("Description:")} ${atom.description}`);
|
|
154
|
+
}
|
|
155
|
+
lines.push("");
|
|
156
|
+
}
|
|
157
|
+
return lines.join("\n");
|
|
158
|
+
}
|
|
159
|
+
function formatSummary(workflow, description, resolution) {
|
|
160
|
+
const lines = [];
|
|
161
|
+
const total = resolution.resolved.length;
|
|
162
|
+
const toExecute = resolution.toExecute.length;
|
|
163
|
+
const cached = resolution.skipped.length;
|
|
164
|
+
lines.push(fmt.bold("Plan Summary:"));
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(` Workflow: ${fmt.cyan(workflow)}`);
|
|
167
|
+
lines.push(` Description: ${description}`);
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push(` Total atoms: ${total}`);
|
|
170
|
+
lines.push(` To execute: ${fmt.green(String(toExecute))}`);
|
|
171
|
+
lines.push(` Cached (skip): ${fmt.gray(String(cached))}`);
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
}
|
|
174
|
+
function buildPlanResult(name, definition, resolution, atoms) {
|
|
175
|
+
const atomMap = /* @__PURE__ */ new Map();
|
|
176
|
+
const skippedSet = new Set(resolution.skipped);
|
|
177
|
+
for (const atom of atoms) {
|
|
178
|
+
const id = atom.alias ?? atom.atom;
|
|
179
|
+
atomMap.set(id, atom);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
workflow: name,
|
|
183
|
+
description: definition.description,
|
|
184
|
+
executionOrder: resolution.resolved.map((r) => r.id),
|
|
185
|
+
toExecute: resolution.toExecute,
|
|
186
|
+
skipped: resolution.skipped,
|
|
187
|
+
atoms: resolution.resolved.map((r) => {
|
|
188
|
+
const atom = atomMap.get(r.id);
|
|
189
|
+
return {
|
|
190
|
+
id: r.id,
|
|
191
|
+
atom: atom?.atom ?? r.id,
|
|
192
|
+
status: skippedSet.has(r.id) ? "skip" : "execute",
|
|
193
|
+
reason: r.reason,
|
|
194
|
+
inputs: atom?.requires?.map((req) => req.as ?? req.artifact) ?? [],
|
|
195
|
+
outputs: atom?.produces?.map((p) => p.artifact) ?? [],
|
|
196
|
+
dependsOn: atom?.dependsOn ?? []
|
|
197
|
+
};
|
|
198
|
+
}),
|
|
199
|
+
summary: {
|
|
200
|
+
total: resolution.resolved.length,
|
|
201
|
+
toExecute: resolution.toExecute.length,
|
|
202
|
+
cached: resolution.skipped.length
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async function planCommand(name, options) {
|
|
207
|
+
try {
|
|
208
|
+
const { definition, atoms: storedAtoms } = await loadWorkflow(name);
|
|
209
|
+
const atoms = convertToResolverAtoms(
|
|
210
|
+
definition,
|
|
211
|
+
storedAtoms
|
|
212
|
+
);
|
|
213
|
+
const cache = createArtifactCache();
|
|
214
|
+
const resolution = resolveDependencies(atoms, { cache });
|
|
215
|
+
if (options.json) {
|
|
216
|
+
const result = buildPlanResult(name, definition, resolution, atoms);
|
|
217
|
+
console.log(JSON.stringify(result, null, 2));
|
|
218
|
+
} else {
|
|
219
|
+
console.log("");
|
|
220
|
+
console.log(formatSummary(name, definition.description, resolution));
|
|
221
|
+
console.log("");
|
|
222
|
+
console.log(formatExecutionGraph(resolution, atoms));
|
|
223
|
+
console.log("");
|
|
224
|
+
console.log(formatAtomDetails(resolution, atoms, options.verbose ?? false));
|
|
225
|
+
}
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (options.json) {
|
|
228
|
+
console.log(
|
|
229
|
+
JSON.stringify(
|
|
230
|
+
{
|
|
231
|
+
error: toErrorMessage(err)
|
|
232
|
+
},
|
|
233
|
+
null,
|
|
234
|
+
2
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
} else {
|
|
238
|
+
console.error(
|
|
239
|
+
fmt.red("Error:"),
|
|
240
|
+
toErrorMessage(err)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export {
|
|
247
|
+
planCommand
|
|
248
|
+
};
|
|
249
|
+
//# sourceMappingURL=plan-DNVARHWH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/plan.ts"],"sourcesContent":["/**\n * Plan command implementation for the atomic CLI.\n *\n * This module implements workflow planning similar to `terraform plan`,\n * showing what would execute without actually running anything.\n *\n * @module commands/plan\n */\n\nimport { join } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { z } from 'zod';\nimport { ATOMIC_DIR, fileExists } from '../storage/index.js';\nimport { toErrorMessage } from '../utils/errors.js';\nimport {\n AtomWithArtifacts,\n createArtifactCache,\n resolveDependencies,\n type ResolutionResult,\n} from '../workflow/index.js';\nimport { WorkflowDefinition } from '../schemas/workflow.js';\nimport { fmt } from '../cli/format.js';\n\ntype AtomWithArtifactsType = z.infer<typeof AtomWithArtifacts>;\ntype WorkflowDefinitionType = z.infer<typeof WorkflowDefinition>;\n\n/**\n * Options for the plan command.\n */\nexport interface PlanOptions {\n json?: boolean;\n verbose?: boolean;\n}\n\n/**\n * Workflow file stored in .atomic/workflows/\n */\ninterface StoredWorkflow {\n definition: WorkflowDefinitionType;\n atoms: AtomWithArtifactsType[];\n}\n\n/**\n * Plan result for JSON output.\n */\nexport interface PlanResult {\n workflow: string;\n description: string;\n executionOrder: string[];\n toExecute: string[];\n skipped: string[];\n atoms: Array<{\n id: string;\n atom: string;\n status: 'execute' | 'skip';\n reason: string;\n inputs: string[];\n outputs: string[];\n dependsOn: string[];\n }>;\n summary: {\n total: number;\n toExecute: number;\n cached: number;\n };\n}\n\nconst WORKFLOWS_DIR = 'workflows';\nconst WORKFLOW_EXT = '.workflow.json';\n\n/**\n * Schema for validating stored workflow structure.\n */\nconst StoredWorkflowSchema = z.object({\n definition: z.unknown(),\n atoms: z.unknown().optional(),\n capabilities: z.unknown().optional(), // deprecated, for migration\n});\n\n/**\n * Load a workflow from the .atomic/workflows/ directory.\n */\nasync function loadWorkflow(name: string): Promise<StoredWorkflow> {\n const projectRoot = process.cwd();\n const workflowPath = join(\n projectRoot,\n ATOMIC_DIR,\n WORKFLOWS_DIR,\n `${name}${WORKFLOW_EXT}`\n );\n\n if (!(await fileExists(workflowPath))) {\n throw new Error(\n `Workflow '${name}' not found.\\n` +\n `Expected file: ${workflowPath}`\n );\n }\n\n const content = await readFile(workflowPath, 'utf-8');\n\n // Parse JSON with clear error message\n let data: unknown;\n try {\n data = JSON.parse(content);\n } catch (err) {\n throw new Error(\n `Workflow '${name}' contains invalid JSON: ${toErrorMessage(err)}`\n );\n }\n\n // Validate the stored workflow structure\n const parseResult = StoredWorkflowSchema.safeParse(data);\n if (!parseResult.success) {\n throw new Error(\n `Workflow '${name}' has invalid structure: ${parseResult.error.message}`\n );\n }\n const parsed = parseResult.data;\n\n // Validate the workflow definition\n const definition = WorkflowDefinition.parse(parsed.definition);\n\n // Support both 'atoms' (new) and 'capabilities' (deprecated)\n const atomsData = parsed.atoms ?? parsed.capabilities;\n\n // Validate atoms array exists\n if (!Array.isArray(atomsData)) {\n throw new Error(`Workflow '${name}' has invalid atoms structure`);\n }\n\n // Validate each atom using the schema\n const atoms = atomsData.map((atom: unknown, index: number) => {\n try {\n return AtomWithArtifacts.parse(atom);\n } catch (err) {\n throw new Error(\n `Workflow '${name}' has invalid atom at index ${index}: ${toErrorMessage(err)}`\n );\n }\n });\n\n return { definition, atoms };\n}\n\n/**\n * Convert workflow atoms to resolver format.\n */\nfunction convertToResolverAtoms(\n workflow: WorkflowDefinitionType,\n atoms: AtomWithArtifactsType[]\n): AtomWithArtifactsType[] {\n // If atoms are provided, use them directly\n if (atoms.length > 0) {\n return atoms;\n }\n\n // Otherwise, convert from WorkflowDefinition format\n return workflow.atoms.map((a) => ({\n atom: a.atom,\n dependsOn: a.dependsOn,\n alias: a.alias,\n description: a.description,\n requires: [],\n produces: [],\n }));\n}\n\n/**\n * Format execution graph as ASCII art.\n */\nfunction formatExecutionGraph(\n resolution: ResolutionResult,\n atoms: AtomWithArtifactsType[]\n): string {\n const lines: string[] = [];\n const atomMap = new Map<string, AtomWithArtifactsType>();\n\n for (const atom of atoms) {\n const id = atom.alias ?? atom.atom;\n atomMap.set(id, atom);\n }\n\n // Build dependency graph visualization\n lines.push(fmt.bold('Execution Graph:'));\n lines.push('');\n\n const _toExecuteSet = new Set(resolution.toExecute);\n const skippedSet = new Set(resolution.skipped);\n\n for (const resolved of resolution.resolved) {\n const atom = atomMap.get(resolved.id);\n const deps = atom?.dependsOn ?? [];\n\n // Status indicator\n let statusIcon: string;\n let statusColor: (s: string) => string;\n if (skippedSet.has(resolved.id)) {\n statusIcon = '○';\n statusColor = fmt.gray;\n } else {\n statusIcon = '●';\n statusColor = fmt.green;\n }\n\n // Build the line\n const depStr = deps.length > 0 ? ` ← [${deps.join(', ')}]` : '';\n lines.push(` ${statusColor(statusIcon)} ${resolved.id}${fmt.gray(depStr)}`);\n }\n\n lines.push('');\n lines.push(fmt.gray('Legend: ● = will execute, ○ = cached (skip)'));\n\n return lines.join('\\n');\n}\n\n/**\n * Format atom details.\n */\nfunction formatAtomDetails(\n resolution: ResolutionResult,\n atoms: AtomWithArtifactsType[],\n verbose: boolean\n): string {\n const lines: string[] = [];\n const atomMap = new Map<string, AtomWithArtifactsType>();\n const skippedSet = new Set(resolution.skipped);\n\n for (const atom of atoms) {\n const id = atom.alias ?? atom.atom;\n atomMap.set(id, atom);\n }\n\n lines.push(fmt.bold('Atom Details:'));\n lines.push('');\n\n for (const resolved of resolution.resolved) {\n const atom = atomMap.get(resolved.id);\n const isSkipped = skippedSet.has(resolved.id);\n\n // Header with status\n const statusBadge = isSkipped\n ? fmt.gray('[CACHED]')\n : fmt.cyan('[EXECUTE]');\n lines.push(` ${fmt.bold(resolved.id)} ${statusBadge}`);\n\n // Show reason\n lines.push(` ${fmt.gray('Reason:')} ${resolved.reason}`);\n\n // Show inputs\n if (atom?.requires && atom.requires.length > 0) {\n const inputs = atom.requires.map((r) => {\n const inputName = r.as ?? r.artifact;\n return `${inputName} (from ${r.from})`;\n });\n lines.push(` ${fmt.gray('Inputs:')} ${inputs.join(', ')}`);\n } else if (verbose) {\n lines.push(` ${fmt.gray('Inputs:')} none`);\n }\n\n // Show outputs\n if (atom?.produces && atom.produces.length > 0) {\n const outputs = atom.produces.map((p) => p.artifact);\n lines.push(` ${fmt.gray('Outputs:')} ${outputs.join(', ')}`);\n } else if (verbose) {\n lines.push(` ${fmt.gray('Outputs:')} none`);\n }\n\n // Show description if verbose\n if (verbose && atom?.description) {\n lines.push(` ${fmt.gray('Description:')} ${atom.description}`);\n }\n\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format execution summary.\n */\nfunction formatSummary(\n workflow: string,\n description: string,\n resolution: ResolutionResult\n): string {\n const lines: string[] = [];\n const total = resolution.resolved.length;\n const toExecute = resolution.toExecute.length;\n const cached = resolution.skipped.length;\n\n lines.push(fmt.bold('Plan Summary:'));\n lines.push('');\n lines.push(` Workflow: ${fmt.cyan(workflow)}`);\n lines.push(` Description: ${description}`);\n lines.push('');\n lines.push(` Total atoms: ${total}`);\n lines.push(` To execute: ${fmt.green(String(toExecute))}`);\n lines.push(` Cached (skip): ${fmt.gray(String(cached))}`);\n\n return lines.join('\\n');\n}\n\n/**\n * Build plan result for JSON output.\n */\nfunction buildPlanResult(\n name: string,\n definition: WorkflowDefinitionType,\n resolution: ResolutionResult,\n atoms: AtomWithArtifactsType[]\n): PlanResult {\n const atomMap = new Map<string, AtomWithArtifactsType>();\n const skippedSet = new Set(resolution.skipped);\n\n for (const atom of atoms) {\n const id = atom.alias ?? atom.atom;\n atomMap.set(id, atom);\n }\n\n return {\n workflow: name,\n description: definition.description,\n executionOrder: resolution.resolved.map((r) => r.id),\n toExecute: resolution.toExecute,\n skipped: resolution.skipped,\n atoms: resolution.resolved.map((r) => {\n const atom = atomMap.get(r.id);\n return {\n id: r.id,\n atom: atom?.atom ?? r.id,\n status: skippedSet.has(r.id) ? 'skip' : 'execute',\n reason: r.reason,\n inputs: atom?.requires?.map((req) => req.as ?? req.artifact) ?? [],\n outputs: atom?.produces?.map((p) => p.artifact) ?? [],\n dependsOn: atom?.dependsOn ?? [],\n };\n }),\n summary: {\n total: resolution.resolved.length,\n toExecute: resolution.toExecute.length,\n cached: resolution.skipped.length,\n },\n };\n}\n\n/**\n * Execute the plan command.\n *\n * @param name - The workflow name to plan\n * @param options - Plan options\n */\nexport async function planCommand(\n name: string,\n options: PlanOptions\n): Promise<void> {\n try {\n // Load workflow\n const { definition, atoms: storedAtoms } =\n await loadWorkflow(name);\n const atoms = convertToResolverAtoms(\n definition,\n storedAtoms\n );\n\n // Create cache (in-memory for now)\n const cache = createArtifactCache();\n\n // Resolve dependencies\n const resolution = resolveDependencies(atoms, { cache });\n\n // Output result\n if (options.json) {\n const result = buildPlanResult(name, definition, resolution, atoms);\n console.log(JSON.stringify(result, null, 2));\n } else {\n // Text output\n console.log('');\n console.log(formatSummary(name, definition.description, resolution));\n console.log('');\n console.log(formatExecutionGraph(resolution, atoms));\n console.log('');\n console.log(formatAtomDetails(resolution, atoms, options.verbose ?? false));\n }\n } catch (err) {\n if (options.json) {\n console.log(\n JSON.stringify(\n {\n error: toErrorMessage(err),\n },\n null,\n 2\n )\n );\n } else {\n console.error(\n fmt.red('Error:'),\n toErrorMessage(err)\n );\n }\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AASA,SAAS,YAAY;AACrB,SAAS,gBAAgB;AACzB,SAAS,SAAS;AAwDlB,IAAM,gBAAgB;AACtB,IAAM,eAAe;AAKrB,IAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,YAAY,EAAE,QAAQ;AAAA,EACtB,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC5B,cAAc,EAAE,QAAQ,EAAE,SAAS;AAAA;AACrC,CAAC;AAKD,eAAe,aAAa,MAAuC;AACjE,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,IAAI,GAAG,YAAY;AAAA,EACxB;AAEA,MAAI,CAAE,MAAM,WAAW,YAAY,GAAI;AACrC,UAAM,IAAI;AAAA,MACR,aAAa,IAAI;AAAA,iBACG,YAAY;AAAA,IAClC;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AAGpD,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,aAAa,IAAI,4BAA4B,eAAe,GAAG,CAAC;AAAA,IAClE;AAAA,EACF;AAGA,QAAM,cAAc,qBAAqB,UAAU,IAAI;AACvD,MAAI,CAAC,YAAY,SAAS;AACxB,UAAM,IAAI;AAAA,MACR,aAAa,IAAI,4BAA4B,YAAY,MAAM,OAAO;AAAA,IACxE;AAAA,EACF;AACA,QAAM,SAAS,YAAY;AAG3B,QAAM,aAAa,mBAAmB,MAAM,OAAO,UAAU;AAG7D,QAAM,YAAY,OAAO,SAAS,OAAO;AAGzC,MAAI,CAAC,MAAM,QAAQ,SAAS,GAAG;AAC7B,UAAM,IAAI,MAAM,aAAa,IAAI,+BAA+B;AAAA,EAClE;AAGA,QAAM,QAAQ,UAAU,IAAI,CAAC,MAAe,UAAkB;AAC5D,QAAI;AACF,aAAO,kBAAkB,MAAM,IAAI;AAAA,IACrC,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,aAAa,IAAI,+BAA+B,KAAK,KAAK,eAAe,GAAG,CAAC;AAAA,MAC/E;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,YAAY,MAAM;AAC7B;AAKA,SAAS,uBACP,UACA,OACyB;AAEzB,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO;AAAA,EACT;AAGA,SAAO,SAAS,MAAM,IAAI,CAAC,OAAO;AAAA,IAChC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,OAAO,EAAE;AAAA,IACT,aAAa,EAAE;AAAA,IACf,UAAU,CAAC;AAAA,IACX,UAAU,CAAC;AAAA,EACb,EAAE;AACJ;AAKA,SAAS,qBACP,YACA,OACQ;AACR,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAU,oBAAI,IAAmC;AAEvD,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,KAAK,SAAS,KAAK;AAC9B,YAAQ,IAAI,IAAI,IAAI;AAAA,EACtB;AAGA,QAAM,KAAK,IAAI,KAAK,kBAAkB,CAAC;AACvC,QAAM,KAAK,EAAE;AAEb,QAAM,gBAAgB,IAAI,IAAI,WAAW,SAAS;AAClD,QAAM,aAAa,IAAI,IAAI,WAAW,OAAO;AAE7C,aAAW,YAAY,WAAW,UAAU;AAC1C,UAAM,OAAO,QAAQ,IAAI,SAAS,EAAE;AACpC,UAAM,OAAO,MAAM,aAAa,CAAC;AAGjC,QAAI;AACJ,QAAI;AACJ,QAAI,WAAW,IAAI,SAAS,EAAE,GAAG;AAC/B,mBAAa;AACb,oBAAc,IAAI;AAAA,IACpB,OAAO;AACL,mBAAa;AACb,oBAAc,IAAI;AAAA,IACpB;AAGA,UAAM,SAAS,KAAK,SAAS,IAAI,YAAO,KAAK,KAAK,IAAI,CAAC,MAAM;AAC7D,UAAM,KAAK,KAAK,YAAY,UAAU,CAAC,IAAI,SAAS,EAAE,GAAG,IAAI,KAAK,MAAM,CAAC,EAAE;AAAA,EAC7E;AAEA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,IAAI,KAAK,uDAA6C,CAAC;AAElE,SAAO,MAAM,KAAK,IAAI;AACxB;AAKA,SAAS,kBACP,YACA,OACA,SACQ;AACR,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAU,oBAAI,IAAmC;AACvD,QAAM,aAAa,IAAI,IAAI,WAAW,OAAO;AAE7C,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,KAAK,SAAS,KAAK;AAC9B,YAAQ,IAAI,IAAI,IAAI;AAAA,EACtB;AAEA,QAAM,KAAK,IAAI,KAAK,eAAe,CAAC;AACpC,QAAM,KAAK,EAAE;AAEb,aAAW,YAAY,WAAW,UAAU;AAC1C,UAAM,OAAO,QAAQ,IAAI,SAAS,EAAE;AACpC,UAAM,YAAY,WAAW,IAAI,SAAS,EAAE;AAG5C,UAAM,cAAc,YAChB,IAAI,KAAK,UAAU,IACnB,IAAI,KAAK,WAAW;AACxB,UAAM,KAAK,KAAK,IAAI,KAAK,SAAS,EAAE,CAAC,IAAI,WAAW,EAAE;AAGtD,UAAM,KAAK,OAAO,IAAI,KAAK,SAAS,CAAC,IAAI,SAAS,MAAM,EAAE;AAG1D,QAAI,MAAM,YAAY,KAAK,SAAS,SAAS,GAAG;AAC9C,YAAM,SAAS,KAAK,SAAS,IAAI,CAAC,MAAM;AACtC,cAAM,YAAY,EAAE,MAAM,EAAE;AAC5B,eAAO,GAAG,SAAS,UAAU,EAAE,IAAI;AAAA,MACrC,CAAC;AACD,YAAM,KAAK,OAAO,IAAI,KAAK,SAAS,CAAC,IAAI,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,IAC9D,WAAW,SAAS;AAClB,YAAM,KAAK,OAAO,IAAI,KAAK,SAAS,CAAC,OAAO;AAAA,IAC9C;AAGA,QAAI,MAAM,YAAY,KAAK,SAAS,SAAS,GAAG;AAC9C,YAAM,UAAU,KAAK,SAAS,IAAI,CAAC,MAAM,EAAE,QAAQ;AACnD,YAAM,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC,IAAI,QAAQ,KAAK,IAAI,CAAC,EAAE;AAAA,IAChE,WAAW,SAAS;AAClB,YAAM,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC,OAAO;AAAA,IAC/C;AAGA,QAAI,WAAW,MAAM,aAAa;AAChC,YAAM,KAAK,OAAO,IAAI,KAAK,cAAc,CAAC,IAAI,KAAK,WAAW,EAAE;AAAA,IAClE;AAEA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAKA,SAAS,cACP,UACA,aACA,YACQ;AACR,QAAM,QAAkB,CAAC;AACzB,QAAM,QAAQ,WAAW,SAAS;AAClC,QAAM,YAAY,WAAW,UAAU;AACvC,QAAM,SAAS,WAAW,QAAQ;AAElC,QAAM,KAAK,IAAI,KAAK,eAAe,CAAC;AACpC,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,kBAAkB,IAAI,KAAK,QAAQ,CAAC,EAAE;AACjD,QAAM,KAAK,kBAAkB,WAAW,EAAE;AAC1C,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,kBAAkB,KAAK,EAAE;AACpC,QAAM,KAAK,kBAAkB,IAAI,MAAM,OAAO,SAAS,CAAC,CAAC,EAAE;AAC3D,QAAM,KAAK,oBAAoB,IAAI,KAAK,OAAO,MAAM,CAAC,CAAC,EAAE;AAEzD,SAAO,MAAM,KAAK,IAAI;AACxB;AAKA,SAAS,gBACP,MACA,YACA,YACA,OACY;AACZ,QAAM,UAAU,oBAAI,IAAmC;AACvD,QAAM,aAAa,IAAI,IAAI,WAAW,OAAO;AAE7C,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,KAAK,SAAS,KAAK;AAC9B,YAAQ,IAAI,IAAI,IAAI;AAAA,EACtB;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,aAAa,WAAW;AAAA,IACxB,gBAAgB,WAAW,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,IACnD,WAAW,WAAW;AAAA,IACtB,SAAS,WAAW;AAAA,IACpB,OAAO,WAAW,SAAS,IAAI,CAAC,MAAM;AACpC,YAAM,OAAO,QAAQ,IAAI,EAAE,EAAE;AAC7B,aAAO;AAAA,QACL,IAAI,EAAE;AAAA,QACN,MAAM,MAAM,QAAQ,EAAE;AAAA,QACtB,QAAQ,WAAW,IAAI,EAAE,EAAE,IAAI,SAAS;AAAA,QACxC,QAAQ,EAAE;AAAA,QACV,QAAQ,MAAM,UAAU,IAAI,CAAC,QAAQ,IAAI,MAAM,IAAI,QAAQ,KAAK,CAAC;AAAA,QACjE,SAAS,MAAM,UAAU,IAAI,CAAC,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,QACpD,WAAW,MAAM,aAAa,CAAC;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,IACD,SAAS;AAAA,MACP,OAAO,WAAW,SAAS;AAAA,MAC3B,WAAW,WAAW,UAAU;AAAA,MAChC,QAAQ,WAAW,QAAQ;AAAA,IAC7B;AAAA,EACF;AACF;AAQA,eAAsB,YACpB,MACA,SACe;AACf,MAAI;AAEF,UAAM,EAAE,YAAY,OAAO,YAAY,IACrC,MAAM,aAAa,IAAI;AACzB,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA;AAAA,IACF;AAGA,UAAM,QAAQ,oBAAoB;AAGlC,UAAM,aAAa,oBAAoB,OAAO,EAAE,MAAM,CAAC;AAGvD,QAAI,QAAQ,MAAM;AAChB,YAAM,SAAS,gBAAgB,MAAM,YAAY,YAAY,KAAK;AAClE,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,OAAO;AAEL,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,cAAc,MAAM,WAAW,aAAa,UAAU,CAAC;AACnE,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,qBAAqB,YAAY,KAAK,CAAC;AACnD,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,kBAAkB,YAAY,OAAO,QAAQ,WAAW,KAAK,CAAC;AAAA,IAC5E;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,QAAQ,MAAM;AAChB,cAAQ;AAAA,QACN,KAAK;AAAA,UACH;AAAA,YACE,OAAO,eAAe,GAAG;AAAA,UAC3B;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,IAAI,IAAI,QAAQ;AAAA,QAChB,eAAe,GAAG;AAAA,MACpB;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
|