@shirayner/ace 0.1.0-snapshot.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.
Files changed (58) hide show
  1. package/bin/ace.js +39 -0
  2. package/package.json +42 -0
  3. package/src/commands/doctor.js +86 -0
  4. package/src/commands/init.js +98 -0
  5. package/src/commands/list.js +67 -0
  6. package/src/core/constants.js +106 -0
  7. package/src/core/installer.js +206 -0
  8. package/src/core/merger.js +103 -0
  9. package/templates/CLAUDE.md +16 -0
  10. package/templates/commands/report.md +63 -0
  11. package/templates/hookify/hookify.block-dangerous-ops.local.md +16 -0
  12. package/templates/hookify/hookify.protect-secrets.local.md +17 -0
  13. package/templates/hookify/hookify.require-verification.local.md +13 -0
  14. package/templates/hooks/java-compile-check.sh +106 -0
  15. package/templates/memory/MEMORY.md +4 -0
  16. package/templates/memory/roles/backend.md +11 -0
  17. package/templates/memory/roles/client.md +11 -0
  18. package/templates/memory/roles/frontend.md +11 -0
  19. package/templates/memory/roles/fullstack.md +11 -0
  20. package/templates/rules/clean-code.md +33 -0
  21. package/templates/rules/code-quality.md +74 -0
  22. package/templates/rules/context-hygiene.md +29 -0
  23. package/templates/rules/memory-policy.md +30 -0
  24. package/templates/rules/reporting.md +9 -0
  25. package/templates/rules/task-recovery.md +13 -0
  26. package/templates/rules/thinking.md +19 -0
  27. package/templates/settings.json +11 -0
  28. package/templates/skills/auto-goal/SKILL.md +188 -0
  29. package/templates/skills/coding/SKILL.md +251 -0
  30. package/templates/skills/coding/references/code-review-guide.md +137 -0
  31. package/templates/skills/coding/references/code-smells.md +201 -0
  32. package/templates/skills/coding/references/implement-guide.md +123 -0
  33. package/templates/skills/coding/references/unit-test-guide.md +211 -0
  34. package/templates/skills/skill-creator/LICENSE.txt +202 -0
  35. package/templates/skills/skill-creator/SKILL.md +479 -0
  36. package/templates/skills/skill-creator/agents/analyzer.md +274 -0
  37. package/templates/skills/skill-creator/agents/comparator.md +202 -0
  38. package/templates/skills/skill-creator/agents/grader.md +223 -0
  39. package/templates/skills/skill-creator/assets/eval_review.html +146 -0
  40. package/templates/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  41. package/templates/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  42. package/templates/skills/skill-creator/references/schemas.md +430 -0
  43. package/templates/skills/skill-creator/scripts/__init__.py +0 -0
  44. package/templates/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  45. package/templates/skills/skill-creator/scripts/generate_report.py +326 -0
  46. package/templates/skills/skill-creator/scripts/improve_description.py +248 -0
  47. package/templates/skills/skill-creator/scripts/package_skill.py +136 -0
  48. package/templates/skills/skill-creator/scripts/quick_validate.py +103 -0
  49. package/templates/skills/skill-creator/scripts/run_eval.py +310 -0
  50. package/templates/skills/skill-creator/scripts/run_loop.py +332 -0
  51. package/templates/skills/skill-creator/scripts/utils.py +47 -0
  52. package/templates/skills/skill-optimize/SKILL.md +287 -0
  53. package/templates/skills/skill-optimize/references/.claude/settings.local.json +7 -0
  54. package/templates/skills/skill-optimize/references/anthropic-design-philosophy.md +250 -0
  55. package/templates/skills/skill-optimize/references/auto-goal-optimization-directions.md +130 -0
  56. package/templates/skills/skill-optimize/references/cross-disciplinary-insights.md +211 -0
  57. package/templates/skills/skill-optimize/references/quality-checklist.md +170 -0
  58. package/templates/skills/skill-optimize/references/theory-foundations.md +201 -0
package/bin/ace.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createRequire } from 'module';
4
+ import { Command } from 'commander';
5
+ import chalk from 'chalk';
6
+ import { initCommand } from '../src/commands/init.js';
7
+ import { doctorCommand } from '../src/commands/doctor.js';
8
+ import { listCommand } from '../src/commands/list.js';
9
+
10
+ const require = createRequire(import.meta.url);
11
+ const pkg = require('../package.json');
12
+
13
+ const program = new Command();
14
+
15
+ program
16
+ .name('ace')
17
+ .description('AI Coding Environment - One command to set up your Claude Code harness')
18
+ .version(pkg.version);
19
+
20
+ program
21
+ .command('init')
22
+ .description('Initialize AI coding environment')
23
+ .option('-p, --preset <name>', 'Installation preset: full, minimal, safe', 'full')
24
+ .option('-f, --force', 'Overwrite existing files', false)
25
+ .option('--dry-run', 'Show what would be done without making changes', false)
26
+ .option('--no-interaction', 'Skip interactive prompts, use defaults', false)
27
+ .action(initCommand);
28
+
29
+ program
30
+ .command('doctor')
31
+ .description('Verify installation integrity')
32
+ .action(doctorCommand);
33
+
34
+ program
35
+ .command('list')
36
+ .description('List installed components and their status')
37
+ .action(listCommand);
38
+
39
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@shirayner/ace",
3
+ "version": "0.1.0-snapshot.1",
4
+ "description": "AI Coding Environment - One command to set up your Claude Code harness",
5
+ "bin": {
6
+ "ace": "./bin/ace.js"
7
+ },
8
+ "main": "src/index.js",
9
+ "scripts": {
10
+ "test": "node --test tests/",
11
+ "lint": "eslint src/"
12
+ },
13
+ "keywords": [
14
+ "claude-code",
15
+ "ai",
16
+ "coding",
17
+ "environment",
18
+ "harness",
19
+ "cli"
20
+ ],
21
+ "author": "",
22
+ "license": "MIT",
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "type": "module",
27
+ "dependencies": {
28
+ "commander": "^12.0.0",
29
+ "chalk": "^5.3.0",
30
+ "inquirer": "^9.2.0",
31
+ "fs-extra": "^11.2.0",
32
+ "deepmerge": "^4.3.1",
33
+ "ora": "^8.0.0"
34
+ },
35
+ "files": [
36
+ "bin/",
37
+ "src/",
38
+ "templates/",
39
+ "README.md",
40
+ "LICENSE"
41
+ ]
42
+ }
@@ -0,0 +1,86 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { CLAUDE_DIR, COMPONENTS } from '../core/constants.js';
5
+
6
+ export async function doctorCommand() {
7
+ console.log(chalk.bold('\n ace doctor — verifying installation\n'));
8
+
9
+ const checks = [];
10
+
11
+ // 1. Check ~/.claude/ exists
12
+ checks.push(await check('~/.claude/ directory', fs.pathExists(CLAUDE_DIR)));
13
+
14
+ // 2. Check core files
15
+ checks.push(await check('CLAUDE.md', fs.pathExists(path.join(CLAUDE_DIR, 'CLAUDE.md'))));
16
+ checks.push(await check('settings.json', fs.pathExists(path.join(CLAUDE_DIR, 'settings.json'))));
17
+
18
+ // 3. Check rules
19
+ const ruleFiles = COMPONENTS.rules.files;
20
+ for (const file of ruleFiles) {
21
+ checks.push(await check(`rules/${path.basename(file.dest)}`, fs.pathExists(path.join(CLAUDE_DIR, file.dest))));
22
+ }
23
+
24
+ // 4. Check skills
25
+ const skillDirs = COMPONENTS.skills.directories;
26
+ for (const dir of skillDirs) {
27
+ const skillMd = path.join(CLAUDE_DIR, dir, 'SKILL.md');
28
+ checks.push(await check(dir, fs.pathExists(skillMd)));
29
+ }
30
+
31
+ // 5. Check memory
32
+ checks.push(await check('memory/MEMORY.md', fs.pathExists(path.join(CLAUDE_DIR, 'memory', 'MEMORY.md'))));
33
+
34
+ // 6. Validate settings.json structure
35
+ try {
36
+ const settings = await fs.readJson(path.join(CLAUDE_DIR, 'settings.json'));
37
+ checks.push({ name: 'settings.json valid JSON', ok: true });
38
+
39
+ const hasHooks = settings?.permissions?.hooks;
40
+ checks.push({ name: 'settings.json has hooks config', ok: !!hasHooks });
41
+
42
+ const hasMemoryDir = settings?.autoMemoryDirectory;
43
+ checks.push({ name: 'settings.json has autoMemoryDirectory', ok: !!hasMemoryDir });
44
+ } catch {
45
+ checks.push({ name: 'settings.json parseable', ok: false });
46
+ }
47
+
48
+ // 7. Validate CLAUDE.md @references
49
+ try {
50
+ const claudeMd = await fs.readFile(path.join(CLAUDE_DIR, 'CLAUDE.md'), 'utf-8');
51
+ const refs = claudeMd.match(/@~?\/?\.?claude\/[^\s)]+/g) || [];
52
+ for (const ref of refs) {
53
+ const refPath = ref.replace(/^@/, '').replace(/^~/, process.env.HOME || process.env.USERPROFILE);
54
+ checks.push(await check(`@ref: ${path.basename(refPath)}`, fs.pathExists(refPath)));
55
+ }
56
+ } catch {
57
+ checks.push({ name: 'CLAUDE.md readable', ok: false });
58
+ }
59
+
60
+ // Summary
61
+ const passed = checks.filter(c => c.ok).length;
62
+ const failed = checks.filter(c => !c.ok).length;
63
+
64
+ console.log();
65
+ checks.forEach(c => {
66
+ const icon = c.ok ? chalk.green(' pass') : chalk.red(' FAIL');
67
+ console.log(` ${icon} ${c.name}`);
68
+ });
69
+
70
+ console.log(chalk.bold(`\n ${passed} passed, ${failed} failed\n`));
71
+
72
+ if (failed > 0) {
73
+ console.log(chalk.yellow(' Run `ace init` to fix missing components.\n'));
74
+ } else {
75
+ console.log(chalk.green(' All checks passed. Environment is healthy.\n'));
76
+ }
77
+ }
78
+
79
+ async function check(name, promise) {
80
+ try {
81
+ const ok = await promise;
82
+ return { name, ok: !!ok };
83
+ } catch {
84
+ return { name, ok: false };
85
+ }
86
+ }
@@ -0,0 +1,98 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import { PRESETS, ROLES } from '../core/constants.js';
4
+ import { Installer } from '../core/installer.js';
5
+
6
+ export async function initCommand(options) {
7
+ console.log(chalk.bold('\n ace - AI Coding Environment\n'));
8
+
9
+ let role = 'fullstack';
10
+ let preset = options.preset;
11
+
12
+ // Interactive mode
13
+ if (options.interaction !== false) {
14
+ const answers = await inquirer.prompt([
15
+ {
16
+ type: 'list',
17
+ name: 'role',
18
+ message: 'What is your primary role?',
19
+ choices: Object.entries(ROLES).map(([key, val]) => ({
20
+ name: `${val.label} — ${val.description}`,
21
+ value: key,
22
+ })),
23
+ default: 'fullstack',
24
+ },
25
+ {
26
+ type: 'list',
27
+ name: 'preset',
28
+ message: 'Installation scope?',
29
+ choices: [
30
+ { name: 'Full — All components (rules, skills, hooks, safety guards, memory)', value: 'full' },
31
+ { name: 'Safe — Core + rules + skills + safety guards + memory', value: 'safe' },
32
+ { name: 'Minimal — Core + rules + skills only', value: 'minimal' },
33
+ ],
34
+ default: 'full',
35
+ },
36
+ ]);
37
+ role = answers.role;
38
+ preset = answers.preset;
39
+ }
40
+
41
+ const components = PRESETS[preset];
42
+ if (!components) {
43
+ console.error(chalk.red(`Unknown preset: ${preset}. Available: ${Object.keys(PRESETS).join(', ')}`));
44
+ process.exit(1);
45
+ }
46
+
47
+ console.log(chalk.dim(`\n Role: ${ROLES[role].label}`));
48
+ console.log(chalk.dim(` Preset: ${preset}`));
49
+ console.log(chalk.dim(` Components: ${components.join(', ')}`));
50
+ if (options.force) console.log(chalk.yellow(' Force mode: existing files will be overwritten'));
51
+ if (options.dryRun) console.log(chalk.cyan(' Dry-run mode: no changes will be made'));
52
+ console.log();
53
+
54
+ const installer = new Installer({
55
+ force: options.force,
56
+ dryRun: options.dryRun,
57
+ role,
58
+ components,
59
+ });
60
+
61
+ const results = installer.run();
62
+
63
+ // Wait for async
64
+ const { installed, skipped, merged, errors } = await results;
65
+
66
+ // Summary
67
+ console.log(chalk.bold('\n Installation Summary\n'));
68
+
69
+ if (installed.length > 0) {
70
+ console.log(chalk.green(` Installed (${installed.length}):`));
71
+ installed.forEach(f => console.log(chalk.green(` + ${f}`)));
72
+ }
73
+
74
+ if (merged.length > 0) {
75
+ console.log(chalk.blue(` Merged (${merged.length}):`));
76
+ merged.forEach(m => {
77
+ const detail = m.added ? ` (added ${m.added.length} refs)` : '';
78
+ console.log(chalk.blue(` ~ ${m.file}${detail}`));
79
+ });
80
+ }
81
+
82
+ if (skipped.length > 0) {
83
+ console.log(chalk.yellow(` Skipped (${skipped.length}) — already exist:`));
84
+ skipped.forEach(f => console.log(chalk.yellow(` - ${f}`)));
85
+ }
86
+
87
+ if (errors.length > 0) {
88
+ console.log(chalk.red(` Errors (${errors.length}):`));
89
+ errors.forEach(e => console.log(chalk.red(` ! ${e.file || e.component}: ${e.error}`)));
90
+ }
91
+
92
+ if (errors.length === 0) {
93
+ console.log(chalk.green.bold('\n Done! Your AI coding environment is ready.\n'));
94
+ console.log(chalk.dim(' Run `ace doctor` to verify the installation.'));
95
+ } else {
96
+ console.log(chalk.yellow('\n Completed with errors. Run `ace doctor` to diagnose.\n'));
97
+ }
98
+ }
@@ -0,0 +1,67 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { CLAUDE_DIR, COMPONENTS } from '../core/constants.js';
5
+
6
+ export async function listCommand() {
7
+ console.log(chalk.bold('\n ace list — installed components\n'));
8
+
9
+ for (const [name, component] of Object.entries(COMPONENTS)) {
10
+ const status = await getComponentStatus(component);
11
+ const icon = status === 'installed' ? chalk.green('installed')
12
+ : status === 'partial' ? chalk.yellow(' partial')
13
+ : chalk.dim(' missing');
14
+
15
+ console.log(` ${icon} ${name} — ${component.description}`);
16
+
17
+ // Show details for partial status
18
+ if (status === 'partial') {
19
+ const details = await getComponentDetails(component);
20
+ details.missing.forEach(f => console.log(chalk.red(` missing: ${f}`)));
21
+ }
22
+ }
23
+
24
+ console.log();
25
+ }
26
+
27
+ async function getComponentStatus(component) {
28
+ const allPaths = [];
29
+
30
+ if (component.files) {
31
+ allPaths.push(...component.files.map(f => path.join(CLAUDE_DIR, f.dest)));
32
+ }
33
+ if (component.directories) {
34
+ allPaths.push(...component.directories.map(d => path.join(CLAUDE_DIR, d)));
35
+ }
36
+ if (component.conditional) {
37
+ allPaths.push(...component.conditional.map(f => path.join(CLAUDE_DIR, f.dest)));
38
+ }
39
+
40
+ if (allPaths.length === 0) return 'installed';
41
+
42
+ const checks = await Promise.all(allPaths.map(p => fs.pathExists(p)));
43
+ const existCount = checks.filter(Boolean).length;
44
+
45
+ if (existCount === allPaths.length) return 'installed';
46
+ if (existCount > 0) return 'partial';
47
+ return 'missing';
48
+ }
49
+
50
+ async function getComponentDetails(component) {
51
+ const missing = [];
52
+ const installed = [];
53
+
54
+ const allFiles = [
55
+ ...(component.files || []).map(f => f.dest),
56
+ ...(component.directories || []),
57
+ ...(component.conditional || []).map(f => f.dest),
58
+ ];
59
+
60
+ for (const file of allFiles) {
61
+ const exists = await fs.pathExists(path.join(CLAUDE_DIR, file));
62
+ if (exists) installed.push(file);
63
+ else missing.push(file);
64
+ }
65
+
66
+ return { missing, installed };
67
+ }
@@ -0,0 +1,106 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export const CLAUDE_DIR = path.join(os.homedir(), '.claude');
8
+ export const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates');
9
+
10
+ export const PRESETS = {
11
+ full: ['core', 'rules', 'skills', 'hooks', 'hookify', 'memory', 'commands'],
12
+ minimal: ['core', 'rules', 'skills'],
13
+ safe: ['core', 'rules', 'skills', 'hookify', 'memory'],
14
+ };
15
+
16
+ export const ROLES = {
17
+ backend: {
18
+ label: 'Backend Developer',
19
+ description: 'Java/Go/Python backend, microservices, APIs',
20
+ primaryLang: 'Java',
21
+ hooks: ['java-compile-check.sh'],
22
+ },
23
+ frontend: {
24
+ label: 'Frontend Developer',
25
+ description: 'React/Vue/TypeScript, Web UI, SPA/SSR',
26
+ primaryLang: 'TypeScript',
27
+ hooks: [],
28
+ },
29
+ client: {
30
+ label: 'Client Developer',
31
+ description: 'iOS/Android/Flutter, mobile apps',
32
+ primaryLang: 'Kotlin/Swift',
33
+ hooks: [],
34
+ },
35
+ fullstack: {
36
+ label: 'Fullstack Developer',
37
+ description: 'Full-stack development, frontend + backend',
38
+ primaryLang: 'TypeScript + Java',
39
+ hooks: ['java-compile-check.sh'],
40
+ },
41
+ };
42
+
43
+ export const COMPONENTS = {
44
+ core: {
45
+ description: 'Core config (CLAUDE.md + settings.json)',
46
+ required: true,
47
+ files: [
48
+ { src: 'CLAUDE.md', dest: 'CLAUDE.md', merge: 'claude-md' },
49
+ { src: 'settings.json', dest: 'settings.json', merge: 'settings-json' },
50
+ ],
51
+ },
52
+ rules: {
53
+ description: 'Cognitive & code quality rules',
54
+ required: true,
55
+ files: [
56
+ { src: 'rules/thinking.md', dest: 'rules/thinking.md' },
57
+ { src: 'rules/clean-code.md', dest: 'rules/clean-code.md' },
58
+ { src: 'rules/code-quality.md', dest: 'rules/code-quality.md' },
59
+ { src: 'rules/reporting.md', dest: 'rules/reporting.md' },
60
+ { src: 'rules/task-recovery.md', dest: 'rules/task-recovery.md' },
61
+ { src: 'rules/context-hygiene.md', dest: 'rules/context-hygiene.md' },
62
+ { src: 'rules/memory-policy.md', dest: 'rules/memory-policy.md' },
63
+ ],
64
+ },
65
+ skills: {
66
+ description: 'AI skills (auto-goal, coding, skill-creator, skill-optimize)',
67
+ required: true,
68
+ directories: [
69
+ 'skills/auto-goal',
70
+ 'skills/coding',
71
+ 'skills/skill-creator',
72
+ 'skills/skill-optimize',
73
+ ],
74
+ },
75
+ hooks: {
76
+ description: 'Hook scripts (optional, role-dependent)',
77
+ required: false,
78
+ conditional: [
79
+ { src: 'hooks/java-compile-check.sh', dest: 'hooks/java-compile-check.sh', roles: ['backend', 'fullstack'] },
80
+ ],
81
+ },
82
+ hookify: {
83
+ description: 'Safety guard rules (block dangerous ops, protect secrets, require verification)',
84
+ required: false,
85
+ files: [
86
+ { src: 'hookify/hookify.block-dangerous-ops.local.md', dest: 'hookify.block-dangerous-ops.local.md' },
87
+ { src: 'hookify/hookify.protect-secrets.local.md', dest: 'hookify.protect-secrets.local.md' },
88
+ { src: 'hookify/hookify.require-verification.local.md', dest: 'hookify.require-verification.local.md' },
89
+ ],
90
+ },
91
+ memory: {
92
+ description: 'Global memory templates',
93
+ required: false,
94
+ files: [
95
+ { src: 'memory/MEMORY.md', dest: 'memory/MEMORY.md', merge: 'skip-existing' },
96
+ ],
97
+ roleTemplates: true,
98
+ },
99
+ commands: {
100
+ description: 'Custom commands (report)',
101
+ required: false,
102
+ files: [
103
+ { src: 'commands/report.md', dest: 'commands/report.md' },
104
+ ],
105
+ },
106
+ };
@@ -0,0 +1,206 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { CLAUDE_DIR, TEMPLATES_DIR, COMPONENTS } from './constants.js';
6
+ import { mergeClaudeMd, mergeSettingsJson, conflictCheck, backupFile } from './merger.js';
7
+
8
+ export class Installer {
9
+ constructor(options = {}) {
10
+ this.targetDir = CLAUDE_DIR;
11
+ this.templatesDir = TEMPLATES_DIR;
12
+ this.force = options.force || false;
13
+ this.dryRun = options.dryRun || false;
14
+ this.role = options.role || 'fullstack';
15
+ this.components = options.components || [];
16
+ this.results = { installed: [], skipped: [], merged: [], errors: [] };
17
+ }
18
+
19
+ async run() {
20
+ if (!this.dryRun) {
21
+ await fs.ensureDir(this.targetDir);
22
+ }
23
+
24
+ for (const componentName of this.components) {
25
+ const component = COMPONENTS[componentName];
26
+ if (!component) continue;
27
+
28
+ const spinner = ora(`Installing ${componentName}...`).start();
29
+
30
+ try {
31
+ await this.installComponent(componentName, component);
32
+ spinner.succeed(`${componentName} installed`);
33
+ } catch (err) {
34
+ spinner.fail(`${componentName} failed: ${err.message}`);
35
+ this.results.errors.push({ component: componentName, error: err.message });
36
+ }
37
+ }
38
+
39
+ return this.results;
40
+ }
41
+
42
+ async installComponent(name, component) {
43
+ // Install regular files
44
+ if (component.files) {
45
+ for (const file of component.files) {
46
+ await this.installFile(file);
47
+ }
48
+ }
49
+
50
+ // Install conditional files (role-dependent)
51
+ if (component.conditional) {
52
+ for (const file of component.conditional) {
53
+ if (file.roles && file.roles.includes(this.role)) {
54
+ await this.installFile(file);
55
+ }
56
+ }
57
+ }
58
+
59
+ // Install directories (e.g., skills)
60
+ if (component.directories) {
61
+ for (const dir of component.directories) {
62
+ await this.installDirectory(dir);
63
+ }
64
+ }
65
+
66
+ // Install role-specific templates (e.g., memory/user_profile.md)
67
+ if (component.roleTemplates) {
68
+ await this.installRoleTemplate();
69
+ }
70
+ }
71
+
72
+ async installFile(fileSpec) {
73
+ const srcPath = path.join(this.templatesDir, fileSpec.src);
74
+ const destPath = path.join(this.targetDir, fileSpec.dest);
75
+
76
+ if (!await fs.pathExists(srcPath)) {
77
+ this.results.errors.push({ file: fileSpec.src, error: 'Template file not found' });
78
+ return;
79
+ }
80
+
81
+ const exists = await conflictCheck(destPath);
82
+
83
+ if (exists && fileSpec.merge === 'skip-existing') {
84
+ this.results.skipped.push(fileSpec.dest);
85
+ return;
86
+ }
87
+
88
+ if (exists && !this.force) {
89
+ if (fileSpec.merge === 'claude-md') {
90
+ await this.mergeClaudeMdFile(srcPath, destPath, fileSpec);
91
+ return;
92
+ }
93
+ if (fileSpec.merge === 'settings-json') {
94
+ await this.mergeSettingsJsonFile(srcPath, destPath, fileSpec);
95
+ return;
96
+ }
97
+ // No merge strategy — skip existing
98
+ this.results.skipped.push(fileSpec.dest);
99
+ return;
100
+ }
101
+
102
+ if (this.dryRun) {
103
+ console.log(chalk.cyan(` [dry-run] Would install: ${fileSpec.dest}`));
104
+ this.results.installed.push(fileSpec.dest);
105
+ return;
106
+ }
107
+
108
+ await fs.ensureDir(path.dirname(destPath));
109
+ await fs.copy(srcPath, destPath);
110
+ this.results.installed.push(fileSpec.dest);
111
+ }
112
+
113
+ async installDirectory(dir) {
114
+ const srcPath = path.join(this.templatesDir, dir);
115
+ const destPath = path.join(this.targetDir, dir);
116
+
117
+ if (!await fs.pathExists(srcPath)) {
118
+ this.results.errors.push({ file: dir, error: 'Template directory not found' });
119
+ return;
120
+ }
121
+
122
+ const exists = await conflictCheck(destPath);
123
+
124
+ if (exists && !this.force) {
125
+ this.results.skipped.push(dir);
126
+ return;
127
+ }
128
+
129
+ if (this.dryRun) {
130
+ console.log(chalk.cyan(` [dry-run] Would install directory: ${dir}`));
131
+ this.results.installed.push(dir);
132
+ return;
133
+ }
134
+
135
+ await fs.ensureDir(path.dirname(destPath));
136
+ await fs.copy(srcPath, destPath, { overwrite: this.force });
137
+ this.results.installed.push(dir);
138
+ }
139
+
140
+ async mergeClaudeMdFile(srcPath, destPath, fileSpec) {
141
+ const existing = await fs.readFile(destPath, 'utf-8');
142
+ const template = await fs.readFile(srcPath, 'utf-8');
143
+ const { content, added } = mergeClaudeMd(existing, template);
144
+
145
+ if (added.length === 0) {
146
+ this.results.skipped.push(fileSpec.dest);
147
+ return;
148
+ }
149
+
150
+ if (this.dryRun) {
151
+ console.log(chalk.cyan(` [dry-run] Would merge CLAUDE.md, adding ${added.length} references`));
152
+ this.results.merged.push({ file: fileSpec.dest, added });
153
+ return;
154
+ }
155
+
156
+ await backupFile(destPath);
157
+ await fs.writeFile(destPath, content, 'utf-8');
158
+ this.results.merged.push({ file: fileSpec.dest, added });
159
+ }
160
+
161
+ async mergeSettingsJsonFile(srcPath, destPath, fileSpec) {
162
+ const existing = await fs.readJson(destPath);
163
+ const template = await fs.readJson(srcPath);
164
+ const merged = mergeSettingsJson(existing, template);
165
+
166
+ if (JSON.stringify(existing) === JSON.stringify(merged)) {
167
+ this.results.skipped.push(fileSpec.dest);
168
+ return;
169
+ }
170
+
171
+ if (this.dryRun) {
172
+ console.log(chalk.cyan(` [dry-run] Would merge settings.json`));
173
+ this.results.merged.push({ file: fileSpec.dest });
174
+ return;
175
+ }
176
+
177
+ await backupFile(destPath);
178
+ await fs.writeJson(destPath, merged, { spaces: 2 });
179
+ this.results.merged.push({ file: fileSpec.dest });
180
+ }
181
+
182
+ async installRoleTemplate() {
183
+ const templatePath = path.join(this.templatesDir, 'memory', 'roles', `${this.role}.md`);
184
+ const destPath = path.join(this.targetDir, 'memory', 'user_profile.md');
185
+
186
+ if (await conflictCheck(destPath)) {
187
+ this.results.skipped.push('memory/user_profile.md');
188
+ return;
189
+ }
190
+
191
+ if (!await fs.pathExists(templatePath)) {
192
+ this.results.errors.push({ file: `memory/roles/${this.role}.md`, error: 'Role template not found' });
193
+ return;
194
+ }
195
+
196
+ if (this.dryRun) {
197
+ console.log(chalk.cyan(` [dry-run] Would install user profile template for role: ${this.role}`));
198
+ this.results.installed.push('memory/user_profile.md');
199
+ return;
200
+ }
201
+
202
+ await fs.ensureDir(path.dirname(destPath));
203
+ await fs.copy(templatePath, destPath);
204
+ this.results.installed.push('memory/user_profile.md');
205
+ }
206
+ }