daedalion 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Daedalion Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # Daedalion
2
+
3
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![npm version](https://img.shields.io/npm/v/daedalion)](https://www.npmjs.com/package/daedalion)
6
+
7
+ Spec-to-Agent compiler for GitHub Copilot. Write specs; get agents, skills, and prompts — all kept in sync with your source of truth.
8
+
9
+ > *"Write specs, get agents automatically."*
10
+
11
+ ![daedalion-init](docs/assets/daedalion-init.png)
12
+
13
+ ## Overview
14
+
15
+ Daedalion turns your `openspec/` specifications into native GitHub Copilot artifacts — agents, skills, prompts, and instructions. Your specs stay the source of truth; agents stay in sync automatically.
16
+
17
+ ```
18
+ OpenSpec (what) ───▶ Daedalion ───▶ GitHub Copilot (how)
19
+ ```
20
+
21
+ ## Core Goal: Spec-Driven Development with AI
22
+
23
+ **Daedalion's primary mission:** Ensure humans and AIs agree on **what to build** *before* any code is written.
24
+
25
+ When you run `daedalion build`, it generates **`.github/prompts/daedalion-openspec-cycle.prompt.md`** — a guided workflow that:
26
+
27
+ 1. Routes AI to draft **proposals** (why, goals, scope) + **spec deltas** (what needs to change)
28
+ 2. Blocks coding until the **human explicitly approves specs**
29
+ 3. Guides implementation via a clear **task checklist**
30
+ 4. Archives completed changes and **merges specs into canonical truth**
31
+
32
+ This prompt is your **AI code coordinator** — it prevents costly rework by enforcing spec clarity and human review at every phase.
33
+
34
+ **The result:** Predictable, spec-aligned development where both humans and AIs follow the same playbook.
35
+
36
+ ## Why Daedalion + OpenSpec
37
+
38
+ - Aligns humans and AIs on the “what” before any code.
39
+ - Enforces approval gates with an AI coordinator prompt.
40
+ - Generates native Copilot artifacts from `openspec/` automatically.
41
+ - Prevents drift with `validate` and supports CI.
42
+
43
+ ## The Core Idea
44
+
45
+ `daedalion build` generates `.github/prompts/daedalion-openspec-cycle.prompt.md` — your AI code coordinator that:
46
+ - Routes work: proposal → review → implementation → archive
47
+ - Blocks coding until specs are explicitly approved
48
+ - Keeps tasks and deltas in lockstep with human review
49
+
50
+ ```mermaid
51
+ flowchart LR
52
+ D[Draft: proposal + spec deltas] --> E{Approved?}
53
+ E -- no --> D
54
+ E -- yes --> F[Implement: tasks]
55
+ F --> G{All tasks complete?}
56
+ G -- no --> F
57
+ G -- yes --> H[Archive: merge into openspec/specs]
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ ```bash
63
+ npm install -g openspec daedalion
64
+ openspec init # creates openspec/ + AGENTS.md
65
+ daedalion init # adds daedalion.yaml + example specs
66
+ daedalion build # generates Copilot artifacts (agents, skills, prompts)
67
+ daedalion validate # checks specs ↔ artifacts are in sync
68
+ ```
69
+
70
+ Use the coordinator by asking your AI: “Help me work through the OpenSpec cycle.”
71
+
72
+ ## What It Generates
73
+
74
+ ```
75
+ .github/
76
+ ├── prompts/
77
+ │ ├── daedalion-openspec-cycle.prompt.md # AI coordinator (spec-driven workflow)
78
+ │ └── {change}.prompt.md # Slash prompts for active changes
79
+ ├── agents/{domain}.agent.md # Domain personas
80
+ ├── skills/{domain}/SKILL.md # Auto-loaded skills from specs
81
+ ├── copilot-instructions.md # Project context from openspec/project.md
82
+ └── workflows/daedalion.yml # Optional CI (validate / auto-regenerate)
83
+ ```
84
+
85
+ ## Commands
86
+
87
+ - `daedalion init` - Scaffolds a new project with example specs:
88
+
89
+ ```
90
+ project/
91
+ ├── daedalion.yaml
92
+ └── openspec/
93
+ ├── project.md
94
+ ├── specs/
95
+ │ └── example/
96
+ │ └── spec.md
97
+ └── changes/
98
+ └── example-feature/
99
+ ├── proposal.md
100
+ └── tasks.md
101
+ ```
102
+
103
+ - `daedalion build` - Generates GitHub Copilot artifacts from your specs:
104
+
105
+ ```
106
+ .github/
107
+ ├── skills/
108
+ │ └── {domain}/
109
+ │ └── SKILL.md
110
+ ├── agents/
111
+ │ └── {domain}.agent.md
112
+ ├── prompts/
113
+ │ └── {change-name}.prompt.md
114
+ ├── workflows/
115
+ │ └── daedalion.yml
116
+ └── copilot-instructions.md
117
+ ```
118
+
119
+ - `daedalion validate` — Verify specs and artifacts are in sync
120
+ - `daedalion clean` — Remove only Daedalion-generated files
121
+
122
+
123
+ ## Learn More
124
+
125
+ - OpenSpec format and conventions: [docs/openspec-format.md](docs/openspec-format.md)
126
+ - Generated output details: [docs/generated-output.md](docs/generated-output.md)
127
+ - CI setup and options: [docs/ci-workflow.md](docs/ci-workflow.md)
128
+ - Developer notes: [docs/DEVELOPER.md](docs/DEVELOPER.md)
129
+
130
+ ## License
131
+
132
+ MIT
133
+
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { init } from '../src/commands/init.js';
6
+ import { build } from '../src/commands/build.js';
7
+ import { validate } from '../src/commands/validate.js';
8
+ import { clean } from '../src/commands/clean.js';
9
+ import { VERSION, getVersionString } from '../src/version.js';
10
+
11
+ function displayLogo() {
12
+ const logo = `
13
+ ██████╗ █████╗ ███████╗██████╗ █████╗ ██╗ ██╗ ██████╗ ███╗ ██╗
14
+ ██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██║ ██║██╔═══██╗████╗ ██║
15
+ ██║ ██║███████║█████╗ ██║ ██║███████║██║ ██║██║ ██║██╔██╗ ██║
16
+ ██║ ██║██╔══██║██╔══╝ ██║ ██║██╔══██║██║ ██║██║ ██║██║╚██╗██║
17
+ ██████╔╝██║ ██║███████╗██████╔╝██║ ██║███████╗██║╚██████╔╝██║ ╚████║
18
+ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
19
+ `;
20
+
21
+ console.log(chalk.cyan(logo));
22
+ console.log(chalk.gray(`Author: Henry Bravo`));
23
+ console.log(chalk.gray(`Email: info@henrybravo.nl`));
24
+ console.log(chalk.gray(`Version: ${getVersionString()}`));
25
+ console.log();
26
+ }
27
+
28
+ // Display logo unless help is requested
29
+ if (!process.argv.includes('--help') && !process.argv.includes('-h')) {
30
+ displayLogo();
31
+ }
32
+
33
+ program
34
+ .name('daedalion')
35
+ .description('OpenSpec-to-Agent compiler for GitHub Copilot')
36
+ .version(VERSION)
37
+ .helpOption('-h, --help', 'display help for command');
38
+
39
+ program
40
+ .command('init')
41
+ .description('Scaffold config + example spec')
42
+ .option('--target <mode>', 'Agent target mode: ide or sdk')
43
+ .action(async (options) => {
44
+ try {
45
+ await init(process.cwd(), options);
46
+ } catch (error) {
47
+ console.error(chalk.red(`Error: ${error.message}`));
48
+ process.exit(2);
49
+ }
50
+ });
51
+
52
+ program
53
+ .command('build')
54
+ .description('Generate .github/ from openspec/')
55
+ .option('--dry-run', 'Preview changes without writing files')
56
+ .option('--verbose', 'Detailed output for debugging')
57
+ .option('--force', 'Overwrite without confirmation')
58
+ .option('--with-tools', 'Generate tool stub files from specs')
59
+ .action(async (options) => {
60
+ try {
61
+ await build(process.cwd(), options);
62
+ } catch (error) {
63
+ console.error(chalk.red(`Error: ${error.message}`));
64
+ process.exit(1);
65
+ }
66
+ });
67
+
68
+ program
69
+ .command('validate')
70
+ .description('Check specs and generated output')
71
+ .action(async () => {
72
+ try {
73
+ const valid = await validate(process.cwd());
74
+ if (!valid) {
75
+ process.exit(1);
76
+ }
77
+ } catch (error) {
78
+ console.error(chalk.red(`Error: ${error.message}`));
79
+ process.exit(1);
80
+ }
81
+ });
82
+
83
+ program
84
+ .command('clean')
85
+ .description('Remove generated files')
86
+ .action(async () => {
87
+ try {
88
+ await clean(process.cwd());
89
+ } catch (error) {
90
+ console.error(chalk.red(`Error: ${error.message}`));
91
+ process.exit(1);
92
+ }
93
+ });
94
+
95
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "daedalion",
3
+ "version": "0.0.1",
4
+ "description": "OpenSpec-to-Agent compiler for GitHub Copilot",
5
+ "bin": {
6
+ "daedalion": "./bin/daedalion.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "src/",
11
+ "templates/"
12
+ ],
13
+ "type": "module",
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "scripts": {
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "test:coverage": "vitest run --coverage"
21
+ },
22
+ "keywords": [
23
+ "openspec",
24
+ "github-copilot",
25
+ "agents",
26
+ "skills",
27
+ "cli"
28
+ ],
29
+ "author": "Henry Bravo <info@henrybravo.nl>",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "chalk": "^5.3.0",
33
+ "commander": "^12.0.0",
34
+ "glob": "^10.3.0",
35
+ "gray-matter": "^4.0.3",
36
+ "yaml": "^2.3.0"
37
+ },
38
+ "devDependencies": {
39
+ "vitest": "^1.0.0"
40
+ }
41
+ }
@@ -0,0 +1,198 @@
1
+ import { existsSync, readdirSync, statSync, writeFileSync } from 'fs';
2
+ import { join, relative } from 'path';
3
+ import chalk from 'chalk';
4
+ import { VERSION } from '../version.js';
5
+ import { glob } from 'glob';
6
+ import { loadConfig, resolveOpenspecPath, resolveOutputPath } from '../config.js';
7
+ import { parseSpec } from '../parsers/spec.js';
8
+ import { parseProposal } from '../parsers/proposal.js';
9
+ import { parseTasks } from '../parsers/tasks.js';
10
+ import { generateSkill } from '../generators/skill.js';
11
+ import { generateAgent } from '../generators/agent.js';
12
+ import { generatePrompt, generateCyclePrompt } from '../generators/prompt.js';
13
+ import { generateWorkflow } from '../generators/workflow.js';
14
+ import { generateInstructions } from '../generators/instructions.js';
15
+ import { generateTools } from '../generators/tools.js';
16
+ import { ensureDir } from '../utils.js';
17
+
18
+ const MANIFEST_FILENAME = '.daedalion-manifest.json';
19
+
20
+ export async function build(cwd, options = {}) {
21
+ console.log();
22
+ console.log(chalk.bold(` Daedalion v${VERSION}`));
23
+ console.log();
24
+
25
+ const config = loadConfig(cwd);
26
+ const openspecDir = resolveOpenspecPath(cwd, config);
27
+ const outputDir = resolveOutputPath(cwd, config);
28
+
29
+ if (!existsSync(openspecDir)) {
30
+ throw new Error(`OpenSpec directory not found: ${openspecDir}\n Run 'daedalion init' first.`);
31
+ }
32
+
33
+ const generatedFiles = [];
34
+
35
+ // Parse specs
36
+ console.log(' Parsing specs...');
37
+ const specs = await findAndParseSpecs(openspecDir, options);
38
+ for (const spec of specs) {
39
+ console.log(chalk.gray(` ✓ ${relative(cwd, spec.path)}`));
40
+ }
41
+
42
+ // Parse changes
43
+ console.log();
44
+ console.log(' Parsing changes...');
45
+ const changes = await findAndParseChanges(openspecDir, options);
46
+ for (const change of changes) {
47
+ console.log(chalk.gray(` ✓ ${relative(cwd, change.proposal.path)}`));
48
+ }
49
+
50
+ // Aggregate tasks from all changes for skills
51
+ const allTasks = aggregateTasks(changes);
52
+
53
+ // Generate outputs
54
+ console.log();
55
+ console.log(' Generating...');
56
+
57
+ // Generate skills and agents from specs
58
+ for (const spec of specs) {
59
+ const tasks = allTasks[spec.domain] || null;
60
+ const skillResult = generateSkill(spec, tasks, outputDir, options);
61
+ generatedFiles.push(skillResult);
62
+ logGenerated(skillResult.path, cwd, options);
63
+
64
+ const agentResult = generateAgent(spec, outputDir, options, config);
65
+ generatedFiles.push(agentResult);
66
+ logGenerated(agentResult.path, cwd, options);
67
+ }
68
+
69
+ // Generate tool stubs if --with-tools flag is set
70
+ if (options.withTools) {
71
+ console.log(chalk.gray(` (generating tool stubs)`));
72
+ for (const spec of specs) {
73
+ const toolResults = generateTools(spec, outputDir, config, options);
74
+ for (const toolResult of toolResults) {
75
+ generatedFiles.push(toolResult);
76
+ logGenerated(toolResult.path, cwd, options);
77
+ }
78
+ }
79
+ }
80
+
81
+ // Generate prompts from changes
82
+ for (const change of changes) {
83
+ const domain = findDomainForChange(change, specs);
84
+ const promptResult = generatePrompt(change.proposal, change.tasks, domain, outputDir, options);
85
+ generatedFiles.push(promptResult);
86
+ logGenerated(promptResult.path, cwd, options);
87
+ }
88
+
89
+ // Generate OpenSpec cycle prompt
90
+ const cyclePromptResult = generateCyclePrompt(outputDir, options);
91
+ generatedFiles.push(cyclePromptResult);
92
+ logGenerated(cyclePromptResult.path, cwd, options);
93
+
94
+ // Generate workflow
95
+ const workflowResult = generateWorkflow(config, outputDir, options);
96
+ generatedFiles.push(workflowResult);
97
+ logGenerated(workflowResult.path, cwd, options);
98
+
99
+ // Generate copilot-instructions.md
100
+ const instructionsResult = generateInstructions(openspecDir, outputDir, options);
101
+ generatedFiles.push(instructionsResult);
102
+ logGenerated(instructionsResult.path, cwd, options);
103
+
104
+ // Write manifest of generated files (for clean command)
105
+ if (!options.dryRun) {
106
+ writeManifest(outputDir, generatedFiles, cwd);
107
+ }
108
+
109
+ console.log();
110
+ if (options.dryRun) {
111
+ console.log(chalk.yellow(` Dry run complete. ${generatedFiles.length} files would be generated.`));
112
+ } else {
113
+ console.log(chalk.green(` Done. ${generatedFiles.length} files generated.`));
114
+ }
115
+ console.log();
116
+
117
+ return generatedFiles;
118
+ }
119
+
120
+ function writeManifest(outputDir, generatedFiles, cwd) {
121
+ const manifestPath = join(outputDir, MANIFEST_FILENAME);
122
+ const manifest = {
123
+ version: 1,
124
+ generatedAt: new Date().toISOString(),
125
+ files: generatedFiles.map(f => relative(cwd, f.path))
126
+ };
127
+ ensureDir(manifestPath);
128
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
129
+ }
130
+
131
+ async function findAndParseSpecs(openspecDir, options) {
132
+ const specsDir = join(openspecDir, 'specs');
133
+ if (!existsSync(specsDir)) {
134
+ return [];
135
+ }
136
+
137
+ const specFiles = await glob('*/spec.md', { cwd: specsDir });
138
+ return specFiles.map(file => parseSpec(join(specsDir, file)));
139
+ }
140
+
141
+ async function findAndParseChanges(openspecDir, options) {
142
+ const changesDir = join(openspecDir, 'changes');
143
+ if (!existsSync(changesDir)) {
144
+ return [];
145
+ }
146
+
147
+ const changes = [];
148
+ const changeDirs = readdirSync(changesDir).filter(name => {
149
+ const fullPath = join(changesDir, name);
150
+ return statSync(fullPath).isDirectory();
151
+ });
152
+
153
+ for (const changeDir of changeDirs) {
154
+ const proposalPath = join(changesDir, changeDir, 'proposal.md');
155
+ const tasksPath = join(changesDir, changeDir, 'tasks.md');
156
+
157
+ if (existsSync(proposalPath)) {
158
+ changes.push({
159
+ proposal: parseProposal(proposalPath),
160
+ tasks: parseTasks(tasksPath)
161
+ });
162
+ }
163
+ }
164
+
165
+ return changes;
166
+ }
167
+
168
+ function aggregateTasks(changes) {
169
+ const tasksByDomain = {};
170
+ for (const change of changes) {
171
+ // For now, associate all tasks with a generic key
172
+ // In a real implementation, you might parse domain from proposal
173
+ if (!tasksByDomain['default']) {
174
+ tasksByDomain['default'] = { groups: [], items: [], hasMore: false };
175
+ }
176
+ tasksByDomain['default'].items.push(...change.tasks.items);
177
+ tasksByDomain['default'].hasMore = tasksByDomain['default'].hasMore || change.tasks.hasMore;
178
+ }
179
+ return tasksByDomain;
180
+ }
181
+
182
+ function findDomainForChange(change, specs) {
183
+ // Try to match change to a spec domain
184
+ // For now, use the first spec's domain or 'default'
185
+ if (specs.length > 0) {
186
+ return specs[0].domain;
187
+ }
188
+ return 'default';
189
+ }
190
+
191
+ function logGenerated(filePath, cwd, options) {
192
+ const relativePath = relative(cwd, filePath);
193
+ if (options.dryRun) {
194
+ console.log(chalk.yellow(` → ${relativePath}`));
195
+ } else {
196
+ console.log(chalk.green(` ✓ ${relativePath}`));
197
+ }
198
+ }
@@ -0,0 +1,85 @@
1
+ import { existsSync, rmSync, readdirSync, readFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import chalk from 'chalk';
4
+ import { VERSION } from '../version.js';
5
+ import { loadConfig, resolveOutputPath } from '../config.js';
6
+
7
+ const MANIFEST_FILENAME = '.daedalion-manifest.json';
8
+
9
+ export async function clean(cwd) {
10
+ console.log();
11
+ console.log(chalk.bold(` Daedalion v${VERSION} - Clean`));
12
+ console.log();
13
+
14
+ const config = loadConfig(cwd);
15
+ const outputDir = resolveOutputPath(cwd, config);
16
+
17
+ if (!existsSync(outputDir)) {
18
+ console.log(chalk.yellow(' No output directory found. Nothing to clean.'));
19
+ console.log();
20
+ return;
21
+ }
22
+
23
+ const manifestPath = join(outputDir, MANIFEST_FILENAME);
24
+
25
+ if (!existsSync(manifestPath)) {
26
+ console.log(chalk.yellow(' No manifest found. Run `daedalion build` first to generate a manifest.'));
27
+ console.log(chalk.gray(' (Clean requires a manifest to avoid deleting non-Daedalion files)'));
28
+ console.log();
29
+ return;
30
+ }
31
+
32
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
33
+ const filesToRemove = manifest.files || [];
34
+
35
+ let removed = 0;
36
+ const cleanedDirs = new Set();
37
+
38
+ for (const relativePath of filesToRemove) {
39
+ const fullPath = join(cwd, relativePath);
40
+
41
+ if (existsSync(fullPath)) {
42
+ rmSync(fullPath, { force: true });
43
+ console.log(chalk.green(` ✓ Removed ${relativePath}`));
44
+ removed++;
45
+
46
+ // Track parent directories for cleanup
47
+ cleanedDirs.add(dirname(fullPath));
48
+ }
49
+ }
50
+
51
+ // Remove the manifest itself
52
+ rmSync(manifestPath, { force: true });
53
+ console.log(chalk.green(` ✓ Removed .github/${MANIFEST_FILENAME}`));
54
+
55
+ // Clean up empty directories (skills/domain/, agents/, etc.)
56
+ for (const dir of cleanedDirs) {
57
+ cleanEmptyDirs(dir, outputDir);
58
+ }
59
+
60
+ console.log();
61
+ if (removed > 0) {
62
+ console.log(chalk.green(` Done. ${removed} files removed.`));
63
+ } else {
64
+ console.log(chalk.yellow(' No generated files found to clean.'));
65
+ }
66
+ console.log();
67
+ }
68
+
69
+ // Recursively remove empty directories up to (but not including) the output dir
70
+ function cleanEmptyDirs(dir, stopAt) {
71
+ if (dir === stopAt || !existsSync(dir)) {
72
+ return;
73
+ }
74
+
75
+ try {
76
+ const contents = readdirSync(dir);
77
+ if (contents.length === 0) {
78
+ rmSync(dir, { recursive: true, force: true });
79
+ // Check parent directory too
80
+ cleanEmptyDirs(dirname(dir), stopAt);
81
+ }
82
+ } catch {
83
+ // Directory might have been removed already
84
+ }
85
+ }
@@ -0,0 +1,88 @@
1
+ import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, readFileSync, writeFileSync } from 'fs';
2
+ import { join, dirname, relative } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import chalk from 'chalk';
5
+ import { VERSION } from '../version.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ export async function init(cwd, options = {}) {
11
+ console.log();
12
+ console.log(chalk.bold(` Daedalion v${VERSION}`));
13
+ console.log();
14
+
15
+ const templatesDir = join(__dirname, '../../templates/init');
16
+ const files = getAllFiles(templatesDir);
17
+ const { agentTarget } = options;
18
+
19
+ let created = 0;
20
+ let skipped = 0;
21
+
22
+ for (const file of files) {
23
+ const relativePath = relative(templatesDir, file);
24
+ const targetPath = join(cwd, relativePath);
25
+
26
+ if (existsSync(targetPath)) {
27
+ console.log(chalk.yellow(` ⚠ ${relativePath} already exists, skipping`));
28
+ skipped++;
29
+ continue;
30
+ }
31
+
32
+ const dir = dirname(targetPath);
33
+ if (!existsSync(dir)) {
34
+ mkdirSync(dir, { recursive: true });
35
+ }
36
+
37
+ copyFileSync(file, targetPath);
38
+ console.log(chalk.green(` ✓ ${relativePath}`));
39
+ created++;
40
+ }
41
+
42
+ if (agentTarget === 'sdk') {
43
+ const configPath = join(cwd, 'daedalion.yaml');
44
+ if (existsSync(configPath)) {
45
+ let config = readFileSync(configPath, 'utf-8');
46
+ config = config.replace(/agents:\s*\n\s*target:\s*ide/, 'agents:\n target: sdk');
47
+ writeFileSync(configPath, config);
48
+ console.log(chalk.cyan(` ✓ Updated daedalion.yaml with SDK target`));
49
+ }
50
+ }
51
+
52
+ console.log();
53
+ if (created > 0) {
54
+ console.log(chalk.green(` Done. ${created} files created.`));
55
+ }
56
+ if (skipped > 0) {
57
+ console.log(chalk.yellow(` ${skipped} files skipped (already exist).`));
58
+ }
59
+
60
+ if (agentTarget === 'sdk') {
61
+ console.log();
62
+ console.log(chalk.cyan(` Agent target set to SDK (for CI pipelines)`));
63
+ console.log(chalk.gray(` Edit daedalion.yaml to add custom tool names under agents.tools`));
64
+ }
65
+
66
+ console.log();
67
+ console.log(' Next steps:');
68
+ console.log(' 1. Edit openspec/specs/example/spec.md with your specifications');
69
+ console.log(' 2. Run `daedalion build` to generate GitHub Copilot artifacts');
70
+ console.log();
71
+ }
72
+
73
+ function getAllFiles(dir, files = []) {
74
+ const entries = readdirSync(dir);
75
+
76
+ for (const entry of entries) {
77
+ const fullPath = join(dir, entry);
78
+ const stat = statSync(fullPath);
79
+
80
+ if (stat.isDirectory()) {
81
+ getAllFiles(fullPath, files);
82
+ } else {
83
+ files.push(fullPath);
84
+ }
85
+ }
86
+
87
+ return files;
88
+ }