opencastle 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 +215 -0
- package/bin/cli.mjs +69 -0
- package/dist/cli/adapters/claude-code.d.ts +22 -0
- package/dist/cli/adapters/claude-code.d.ts.map +1 -0
- package/dist/cli/adapters/claude-code.js +237 -0
- package/dist/cli/adapters/claude-code.js.map +1 -0
- package/dist/cli/adapters/cursor.d.ts +20 -0
- package/dist/cli/adapters/cursor.d.ts.map +1 -0
- package/dist/cli/adapters/cursor.js +231 -0
- package/dist/cli/adapters/cursor.js.map +1 -0
- package/dist/cli/adapters/vscode.d.ts +20 -0
- package/dist/cli/adapters/vscode.d.ts.map +1 -0
- package/dist/cli/adapters/vscode.js +132 -0
- package/dist/cli/adapters/vscode.js.map +1 -0
- package/dist/cli/copy.d.ts +14 -0
- package/dist/cli/copy.d.ts.map +1 -0
- package/dist/cli/copy.js +62 -0
- package/dist/cli/copy.js.map +1 -0
- package/dist/cli/dashboard.d.ts +3 -0
- package/dist/cli/dashboard.d.ts.map +1 -0
- package/dist/cli/dashboard.js +183 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/cli/diff.d.ts +3 -0
- package/dist/cli/diff.d.ts.map +1 -0
- package/dist/cli/diff.js +27 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/eject.d.ts +3 -0
- package/dist/cli/eject.d.ts.map +1 -0
- package/dist/cli/eject.js +27 -0
- package/dist/cli/eject.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +92 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/manifest.d.ts +14 -0
- package/dist/cli/manifest.d.ts.map +1 -0
- package/dist/cli/manifest.js +34 -0
- package/dist/cli/manifest.js.map +1 -0
- package/dist/cli/mcp.d.ts +14 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +35 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/prompt.d.ts +12 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +104 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/run/adapters/claude-code.d.ts +16 -0
- package/dist/cli/run/adapters/claude-code.d.ts.map +1 -0
- package/dist/cli/run/adapters/claude-code.js +82 -0
- package/dist/cli/run/adapters/claude-code.js.map +1 -0
- package/dist/cli/run/adapters/copilot.d.ts +16 -0
- package/dist/cli/run/adapters/copilot.d.ts.map +1 -0
- package/dist/cli/run/adapters/copilot.js +84 -0
- package/dist/cli/run/adapters/copilot.js.map +1 -0
- package/dist/cli/run/adapters/cursor.d.ts +16 -0
- package/dist/cli/run/adapters/cursor.d.ts.map +1 -0
- package/dist/cli/run/adapters/cursor.js +81 -0
- package/dist/cli/run/adapters/cursor.js.map +1 -0
- package/dist/cli/run/adapters/index.d.ts +14 -0
- package/dist/cli/run/adapters/index.d.ts.map +1 -0
- package/dist/cli/run/adapters/index.js +35 -0
- package/dist/cli/run/adapters/index.js.map +1 -0
- package/dist/cli/run/executor.d.ts +15 -0
- package/dist/cli/run/executor.d.ts.map +1 -0
- package/dist/cli/run/executor.js +249 -0
- package/dist/cli/run/executor.js.map +1 -0
- package/dist/cli/run/reporter.d.ts +10 -0
- package/dist/cli/run/reporter.d.ts.map +1 -0
- package/dist/cli/run/reporter.js +112 -0
- package/dist/cli/run/reporter.js.map +1 -0
- package/dist/cli/run/schema.d.ts +28 -0
- package/dist/cli/run/schema.d.ts.map +1 -0
- package/dist/cli/run/schema.js +511 -0
- package/dist/cli/run/schema.js.map +1 -0
- package/dist/cli/run.d.ts +6 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +123 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/stack-config.d.ts +12 -0
- package/dist/cli/stack-config.d.ts.map +1 -0
- package/dist/cli/stack-config.js +146 -0
- package/dist/cli/stack-config.js.map +1 -0
- package/dist/cli/types.d.ts +169 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/update.d.ts +3 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +50 -0
- package/dist/cli/update.js.map +1 -0
- package/package.json +48 -0
- package/src/cli/adapters/claude-code.ts +287 -0
- package/src/cli/adapters/cursor.ts +377 -0
- package/src/cli/adapters/vscode.ts +168 -0
- package/src/cli/copy.ts +79 -0
- package/src/cli/dashboard.ts +225 -0
- package/src/cli/diff.ts +44 -0
- package/src/cli/eject.ts +39 -0
- package/src/cli/init.ts +120 -0
- package/src/cli/manifest.ts +45 -0
- package/src/cli/mcp.ts +49 -0
- package/src/cli/prompt.ts +115 -0
- package/src/cli/run/adapters/claude-code.ts +95 -0
- package/src/cli/run/adapters/copilot.ts +97 -0
- package/src/cli/run/adapters/cursor.ts +94 -0
- package/src/cli/run/adapters/index.ts +40 -0
- package/src/cli/run/executor.ts +292 -0
- package/src/cli/run/reporter.ts +129 -0
- package/src/cli/run/schema.ts +595 -0
- package/src/cli/run.ts +137 -0
- package/src/cli/stack-config.ts +180 -0
- package/src/cli/types.ts +207 -0
- package/src/cli/update.ts +75 -0
- package/src/dashboard/astro.config.mjs +6 -0
- package/src/dashboard/package-lock.json +5455 -0
- package/src/dashboard/package.json +14 -0
- package/src/dashboard/public/data/delegations.ndjson +35 -0
- package/src/dashboard/public/data/panels.ndjson +13 -0
- package/src/dashboard/public/data/sessions.ndjson +50 -0
- package/src/dashboard/public/icon-192.png +0 -0
- package/src/dashboard/scripts/generate-seed-data.ts +355 -0
- package/src/dashboard/src/layouts/Layout.astro +25 -0
- package/src/dashboard/src/pages/index.astro +1070 -0
- package/src/dashboard/src/styles/dashboard.css +1078 -0
- package/src/dashboard/tsconfig.json +6 -0
- package/src/orchestrator/agent-workflows/README.md +22 -0
- package/src/orchestrator/agent-workflows/bug-fix.md +128 -0
- package/src/orchestrator/agent-workflows/data-pipeline.md +145 -0
- package/src/orchestrator/agent-workflows/database-migration.md +159 -0
- package/src/orchestrator/agent-workflows/feature-implementation.md +223 -0
- package/src/orchestrator/agent-workflows/performance-optimization.md +125 -0
- package/src/orchestrator/agent-workflows/refactoring.md +142 -0
- package/src/orchestrator/agent-workflows/schema-changes.md +164 -0
- package/src/orchestrator/agent-workflows/security-audit.md +148 -0
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +33 -0
- package/src/orchestrator/agents/api-designer.agent.md +68 -0
- package/src/orchestrator/agents/architect.agent.md +129 -0
- package/src/orchestrator/agents/content-engineer.agent.md +57 -0
- package/src/orchestrator/agents/copywriter.agent.md +95 -0
- package/src/orchestrator/agents/data-expert.agent.md +63 -0
- package/src/orchestrator/agents/database-engineer.agent.md +62 -0
- package/src/orchestrator/agents/developer.agent.md +66 -0
- package/src/orchestrator/agents/devops-expert.agent.md +57 -0
- package/src/orchestrator/agents/documentation-writer.agent.md +60 -0
- package/src/orchestrator/agents/performance-expert.agent.md +58 -0
- package/src/orchestrator/agents/release-manager.agent.md +72 -0
- package/src/orchestrator/agents/researcher.agent.md +145 -0
- package/src/orchestrator/agents/reviewer.agent.md +62 -0
- package/src/orchestrator/agents/security-expert.agent.md +64 -0
- package/src/orchestrator/agents/seo-specialist.agent.md +67 -0
- package/src/orchestrator/agents/team-lead.agent.md +644 -0
- package/src/orchestrator/agents/testing-expert.agent.md +85 -0
- package/src/orchestrator/agents/ui-ux-expert.agent.md +63 -0
- package/src/orchestrator/copilot-instructions.md +3 -0
- package/src/orchestrator/customizations/AGENT-EXPERTISE.md +325 -0
- package/src/orchestrator/customizations/AGENT-FAILURES.md +69 -0
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +58 -0
- package/src/orchestrator/customizations/DISPUTES.md +162 -0
- package/src/orchestrator/customizations/KNOWLEDGE-GRAPH.md +10 -0
- package/src/orchestrator/customizations/LESSONS-LEARNED.md +70 -0
- package/src/orchestrator/customizations/README.md +59 -0
- package/src/orchestrator/customizations/agents/agent-registry.md +46 -0
- package/src/orchestrator/customizations/agents/skill-matrix.md +142 -0
- package/src/orchestrator/customizations/logs/README.md +181 -0
- package/src/orchestrator/customizations/logs/delegations.ndjson +1 -0
- package/src/orchestrator/customizations/logs/panels.ndjson +1 -0
- package/src/orchestrator/customizations/logs/sessions.ndjson +1 -0
- package/src/orchestrator/customizations/project/docs-structure.md +23 -0
- package/src/orchestrator/customizations/project/tracker-config.md +45 -0
- package/src/orchestrator/customizations/project.instructions.md +64 -0
- package/src/orchestrator/customizations/stack/api-config.md +37 -0
- package/src/orchestrator/customizations/stack/cms-config.md +26 -0
- package/src/orchestrator/customizations/stack/data-pipeline-config.md +41 -0
- package/src/orchestrator/customizations/stack/database-config.md +44 -0
- package/src/orchestrator/customizations/stack/deployment-config.md +45 -0
- package/src/orchestrator/customizations/stack/testing-config.md +56 -0
- package/src/orchestrator/instructions/ai-optimization.instructions.md +143 -0
- package/src/orchestrator/instructions/general.instructions.md +194 -0
- package/src/orchestrator/mcp.json +55 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +235 -0
- package/src/orchestrator/prompts/brainstorm.prompt.md +115 -0
- package/src/orchestrator/prompts/bug-fix.prompt.md +141 -0
- package/src/orchestrator/prompts/create-skill.prompt.md +103 -0
- package/src/orchestrator/prompts/generate-task-spec.prompt.md +154 -0
- package/src/orchestrator/prompts/implement-feature.prompt.md +124 -0
- package/src/orchestrator/prompts/metrics-report.prompt.md +142 -0
- package/src/orchestrator/prompts/quick-refinement.prompt.md +137 -0
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +100 -0
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +164 -0
- package/src/orchestrator/skills/agent-hooks/SKILL.md +147 -0
- package/src/orchestrator/skills/agent-memory/SKILL.md +144 -0
- package/src/orchestrator/skills/api-patterns/SKILL.md +106 -0
- package/src/orchestrator/skills/browser-testing/SKILL.md +203 -0
- package/src/orchestrator/skills/code-commenting/SKILL.md +133 -0
- package/src/orchestrator/skills/contentful-cms/SKILL.md +43 -0
- package/src/orchestrator/skills/context-map/SKILL.md +135 -0
- package/src/orchestrator/skills/convex-database/SKILL.md +80 -0
- package/src/orchestrator/skills/data-engineering/SKILL.md +99 -0
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +49 -0
- package/src/orchestrator/skills/documentation-standards/SKILL.md +85 -0
- package/src/orchestrator/skills/fast-review/SKILL.md +327 -0
- package/src/orchestrator/skills/frontend-design/SKILL.md +42 -0
- package/src/orchestrator/skills/jira-management/SKILL.md +168 -0
- package/src/orchestrator/skills/memory-merger/SKILL.md +123 -0
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +75 -0
- package/src/orchestrator/skills/nx-workspace/SKILL.md +192 -0
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +184 -0
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +38 -0
- package/src/orchestrator/skills/performance-optimization/SKILL.md +101 -0
- package/src/orchestrator/skills/react-development/SKILL.md +117 -0
- package/src/orchestrator/skills/sanity-cms/SKILL.md +18 -0
- package/src/orchestrator/skills/security-hardening/SKILL.md +118 -0
- package/src/orchestrator/skills/self-improvement/SKILL.md +137 -0
- package/src/orchestrator/skills/seo-patterns/SKILL.md +40 -0
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +205 -0
- package/src/orchestrator/skills/slack-notifications/SKILL.md +211 -0
- package/src/orchestrator/skills/strapi-cms/SKILL.md +43 -0
- package/src/orchestrator/skills/supabase-database/SKILL.md +24 -0
- package/src/orchestrator/skills/task-management/SKILL.md +143 -0
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +317 -0
- package/src/orchestrator/skills/teams-notifications/SKILL.md +249 -0
- package/src/orchestrator/skills/testing-workflow/SKILL.md +134 -0
- package/src/orchestrator/skills/validation-gates/SKILL.md +100 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { copyDir, getOrchestratorRoot, removeDirIfExists } from '../copy.js'
|
|
5
|
+
import { scaffoldMcpConfig } from '../mcp.js'
|
|
6
|
+
import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform } from '../stack-config.js'
|
|
7
|
+
import type { CopyResults, ManagedPaths, StackConfig } from '../types.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* VS Code / GitHub Copilot adapter.
|
|
11
|
+
*
|
|
12
|
+
* This is the **native format** — the orchestrator source files map 1:1.
|
|
13
|
+
*
|
|
14
|
+
* copilot-instructions.md → .github/copilot-instructions.md
|
|
15
|
+
* agents/ → .github/agents/
|
|
16
|
+
* instructions/ → .github/instructions/
|
|
17
|
+
* skills/ → .github/skills/
|
|
18
|
+
* agent-workflows/ → .github/agent-workflows/
|
|
19
|
+
* prompts/ → .github/prompts/
|
|
20
|
+
* customizations/ → .github/customizations/ (scaffolded once)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const IDE_ID = 'vscode'
|
|
24
|
+
export const IDE_LABEL = 'VS Code (GitHub Copilot)'
|
|
25
|
+
|
|
26
|
+
/** Directories whose contents are framework-managed (overwritten on update). */
|
|
27
|
+
const FRAMEWORK_DIRS = [
|
|
28
|
+
'agents',
|
|
29
|
+
'instructions',
|
|
30
|
+
'skills',
|
|
31
|
+
'agent-workflows',
|
|
32
|
+
'prompts',
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
/** Directories scaffolded once and never overwritten. */
|
|
36
|
+
const CUSTOMIZABLE_DIRS = ['customizations']
|
|
37
|
+
|
|
38
|
+
export async function install(
|
|
39
|
+
pkgRoot: string,
|
|
40
|
+
projectRoot: string,
|
|
41
|
+
stack?: StackConfig
|
|
42
|
+
): Promise<CopyResults> {
|
|
43
|
+
const srcRoot = getOrchestratorRoot(pkgRoot)
|
|
44
|
+
const destRoot = resolve(projectRoot, '.github')
|
|
45
|
+
|
|
46
|
+
await mkdir(destRoot, { recursive: true })
|
|
47
|
+
|
|
48
|
+
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
49
|
+
|
|
50
|
+
const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
|
|
51
|
+
const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
|
|
52
|
+
|
|
53
|
+
// copilot-instructions.md
|
|
54
|
+
const copilotSrc = resolve(srcRoot, 'copilot-instructions.md')
|
|
55
|
+
const copilotDest = resolve(destRoot, 'copilot-instructions.md')
|
|
56
|
+
if (!existsSync(copilotDest)) {
|
|
57
|
+
await writeFile(copilotDest, await readFile(copilotSrc, 'utf8'))
|
|
58
|
+
results.created.push(copilotDest)
|
|
59
|
+
} else {
|
|
60
|
+
results.skipped.push(copilotDest)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Framework directories
|
|
64
|
+
for (const dir of FRAMEWORK_DIRS) {
|
|
65
|
+
const srcDir = resolve(srcRoot, dir)
|
|
66
|
+
if (!existsSync(srcDir)) continue
|
|
67
|
+
const destDir = resolve(destRoot, dir)
|
|
68
|
+
|
|
69
|
+
// Build filter based on directory type
|
|
70
|
+
let filter: ((_name: string, _srcPath: string) => boolean) | undefined
|
|
71
|
+
if (dir === 'skills') {
|
|
72
|
+
filter = (name) => !excludedSkills.has(name)
|
|
73
|
+
} else if (dir === 'agents') {
|
|
74
|
+
filter = (name) => !excludedAgents.has(name)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sub = await copyDir(srcDir, destDir, { filter })
|
|
78
|
+
results.copied.push(...sub.copied)
|
|
79
|
+
results.skipped.push(...sub.skipped)
|
|
80
|
+
results.created.push(...sub.created)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Customization templates (scaffold once)
|
|
84
|
+
const custTransform = stack ? getCustomizationsTransform(stack) : undefined
|
|
85
|
+
for (const dir of CUSTOMIZABLE_DIRS) {
|
|
86
|
+
const srcDir = resolve(srcRoot, dir)
|
|
87
|
+
if (!existsSync(srcDir)) continue
|
|
88
|
+
const destDir = resolve(destRoot, dir)
|
|
89
|
+
const sub = await copyDir(srcDir, destDir, { transform: custTransform })
|
|
90
|
+
results.copied.push(...sub.copied)
|
|
91
|
+
results.skipped.push(...sub.skipped)
|
|
92
|
+
results.created.push(...sub.created)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// MCP server config → .vscode/mcp.json (scaffold once)
|
|
96
|
+
const mcpResult = await scaffoldMcpConfig(
|
|
97
|
+
pkgRoot,
|
|
98
|
+
projectRoot,
|
|
99
|
+
'.vscode/mcp.json',
|
|
100
|
+
stack
|
|
101
|
+
)
|
|
102
|
+
results[mcpResult.action].push(mcpResult.path)
|
|
103
|
+
|
|
104
|
+
return results
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function update(
|
|
108
|
+
pkgRoot: string,
|
|
109
|
+
projectRoot: string,
|
|
110
|
+
stack?: StackConfig
|
|
111
|
+
): Promise<CopyResults> {
|
|
112
|
+
const srcRoot = getOrchestratorRoot(pkgRoot)
|
|
113
|
+
const destRoot = resolve(projectRoot, '.github')
|
|
114
|
+
|
|
115
|
+
const results: CopyResults = { copied: [], skipped: [], created: [] }
|
|
116
|
+
|
|
117
|
+
const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
|
|
118
|
+
const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
|
|
119
|
+
|
|
120
|
+
// Overwrite copilot-instructions.md
|
|
121
|
+
const copilotDest = resolve(destRoot, 'copilot-instructions.md')
|
|
122
|
+
await writeFile(
|
|
123
|
+
copilotDest,
|
|
124
|
+
await readFile(resolve(srcRoot, 'copilot-instructions.md'), 'utf8')
|
|
125
|
+
)
|
|
126
|
+
results.copied.push(copilotDest)
|
|
127
|
+
|
|
128
|
+
// Remove existing framework directories to clear stale files
|
|
129
|
+
for (const dir of FRAMEWORK_DIRS) {
|
|
130
|
+
await removeDirIfExists(resolve(destRoot, dir))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Re-copy framework directories
|
|
134
|
+
for (const dir of FRAMEWORK_DIRS) {
|
|
135
|
+
const srcDir = resolve(srcRoot, dir)
|
|
136
|
+
if (!existsSync(srcDir)) continue
|
|
137
|
+
const destDir = resolve(destRoot, dir)
|
|
138
|
+
|
|
139
|
+
let filter: ((_name: string, _srcPath: string) => boolean) | undefined
|
|
140
|
+
if (dir === 'skills') {
|
|
141
|
+
filter = (name) => !excludedSkills.has(name)
|
|
142
|
+
} else if (dir === 'agents') {
|
|
143
|
+
filter = (name) => !excludedAgents.has(name)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sub = await copyDir(srcDir, destDir, { overwrite: true, filter })
|
|
147
|
+
// All re-installed framework files count as "updated" (copied), not "created"
|
|
148
|
+
results.copied.push(...sub.copied, ...sub.created)
|
|
149
|
+
results.skipped.push(...sub.skipped)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Customizations are NEVER overwritten during update.
|
|
153
|
+
|
|
154
|
+
return results
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function getManagedPaths(): ManagedPaths {
|
|
158
|
+
return {
|
|
159
|
+
framework: [
|
|
160
|
+
'.github/copilot-instructions.md',
|
|
161
|
+
...FRAMEWORK_DIRS.map((d) => `.github/${d}/`),
|
|
162
|
+
],
|
|
163
|
+
customizable: [
|
|
164
|
+
...CUSTOMIZABLE_DIRS.map((d) => `.github/${d}/`),
|
|
165
|
+
'.vscode/mcp.json',
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
}
|
package/src/cli/copy.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readdir,
|
|
3
|
+
readFile,
|
|
4
|
+
mkdir,
|
|
5
|
+
writeFile,
|
|
6
|
+
copyFile,
|
|
7
|
+
rm,
|
|
8
|
+
} from 'node:fs/promises';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import type { CopyResults, CopyDirOptions } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Recursively copy a directory tree.
|
|
15
|
+
*/
|
|
16
|
+
export async function copyDir(
|
|
17
|
+
src: string,
|
|
18
|
+
dest: string,
|
|
19
|
+
{ overwrite = false, filter, transform }: CopyDirOptions = {}
|
|
20
|
+
): Promise<CopyResults> {
|
|
21
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
22
|
+
await mkdir(dest, { recursive: true });
|
|
23
|
+
|
|
24
|
+
const results: CopyResults = { copied: [], skipped: [], created: [] };
|
|
25
|
+
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const srcPath = resolve(src, entry.name);
|
|
28
|
+
const destPath = resolve(dest, entry.name);
|
|
29
|
+
|
|
30
|
+
if (filter && !filter(entry.name, srcPath)) continue;
|
|
31
|
+
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
const sub = await copyDir(srcPath, destPath, {
|
|
34
|
+
overwrite,
|
|
35
|
+
filter,
|
|
36
|
+
transform,
|
|
37
|
+
});
|
|
38
|
+
results.copied.push(...sub.copied);
|
|
39
|
+
results.skipped.push(...sub.skipped);
|
|
40
|
+
results.created.push(...sub.created);
|
|
41
|
+
} else {
|
|
42
|
+
const exists = existsSync(destPath);
|
|
43
|
+
if (exists && !overwrite) {
|
|
44
|
+
results.skipped.push(destPath);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (transform) {
|
|
49
|
+
const content = await readFile(srcPath, 'utf8');
|
|
50
|
+
const transformed = await transform(content, srcPath);
|
|
51
|
+
if (transformed !== null) {
|
|
52
|
+
await writeFile(destPath, transformed);
|
|
53
|
+
results[exists ? 'copied' : 'created'].push(destPath);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
await copyFile(srcPath, destPath);
|
|
57
|
+
results[exists ? 'copied' : 'created'].push(destPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the orchestrator source directory from the CLI package root.
|
|
67
|
+
*/
|
|
68
|
+
export function getOrchestratorRoot(pkgRoot: string): string {
|
|
69
|
+
return resolve(pkgRoot, 'src', 'orchestrator');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remove a directory if it exists. No-op if it doesn't.
|
|
74
|
+
*/
|
|
75
|
+
export async function removeDirIfExists(dirPath: string): Promise<void> {
|
|
76
|
+
if (existsSync(dirPath)) {
|
|
77
|
+
await rm(dirPath, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { createServer } from 'node:http'
|
|
2
|
+
import type { IncomingMessage, ServerResponse, Server } from 'node:http'
|
|
3
|
+
import { readFile, access } from 'node:fs/promises'
|
|
4
|
+
import { resolve, join, extname } from 'node:path'
|
|
5
|
+
import { exec } from 'node:child_process'
|
|
6
|
+
import type { CliContext } from './types.js'
|
|
7
|
+
|
|
8
|
+
const MIME_TYPES: Record<string, string> = {
|
|
9
|
+
'.html': 'text/html',
|
|
10
|
+
'.css': 'text/css',
|
|
11
|
+
'.js': 'application/javascript',
|
|
12
|
+
'.json': 'application/json',
|
|
13
|
+
'.ndjson': 'application/x-ndjson',
|
|
14
|
+
'.png': 'image/png',
|
|
15
|
+
'.svg': 'image/svg+xml',
|
|
16
|
+
'.ico': 'image/x-icon',
|
|
17
|
+
'.woff': 'font/woff',
|
|
18
|
+
'.woff2': 'font/woff2',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DATA_FILES = [
|
|
22
|
+
'sessions.ndjson',
|
|
23
|
+
'delegations.ndjson',
|
|
24
|
+
'panels.ndjson',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
interface DashboardArgs {
|
|
28
|
+
port: number
|
|
29
|
+
openBrowser: boolean
|
|
30
|
+
seed: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseArgs(args: string[]): DashboardArgs {
|
|
34
|
+
let port = 4300
|
|
35
|
+
let openBrowser = true
|
|
36
|
+
let seed = false
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
if (args[i] === '--port' && args[i + 1]) {
|
|
40
|
+
port = parseInt(args[i + 1], 10)
|
|
41
|
+
i++
|
|
42
|
+
} else if (args[i] === '--no-open') {
|
|
43
|
+
openBrowser = false
|
|
44
|
+
} else if (args[i] === '--seed') {
|
|
45
|
+
seed = true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { port, openBrowser, seed }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function openUrl(url: string): void {
|
|
53
|
+
const plat = process.platform
|
|
54
|
+
const cmd =
|
|
55
|
+
plat === 'darwin'
|
|
56
|
+
? 'open'
|
|
57
|
+
: plat === 'win32'
|
|
58
|
+
? 'start'
|
|
59
|
+
: 'xdg-open'
|
|
60
|
+
exec(`${cmd} ${url}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
64
|
+
try {
|
|
65
|
+
await access(filePath)
|
|
66
|
+
return true
|
|
67
|
+
} catch {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function tryListen(
|
|
73
|
+
server: Server,
|
|
74
|
+
port: number,
|
|
75
|
+
maxAttempts = 10
|
|
76
|
+
): Promise<number> {
|
|
77
|
+
return new Promise((res, rej) => {
|
|
78
|
+
let attempt = 0
|
|
79
|
+
|
|
80
|
+
function attemptListen(): void {
|
|
81
|
+
server.listen(port + attempt, '127.0.0.1', () => {
|
|
82
|
+
res(port + attempt)
|
|
83
|
+
})
|
|
84
|
+
server.once('error', (err: Error & { code?: string }) => {
|
|
85
|
+
if (err.code === 'EADDRINUSE' && attempt < maxAttempts) {
|
|
86
|
+
attempt++
|
|
87
|
+
attemptListen()
|
|
88
|
+
} else {
|
|
89
|
+
rej(err)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
attemptListen()
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default async function dashboard({
|
|
99
|
+
pkgRoot,
|
|
100
|
+
args,
|
|
101
|
+
}: CliContext): Promise<void> {
|
|
102
|
+
const { port, openBrowser, seed } = parseArgs(args)
|
|
103
|
+
|
|
104
|
+
const distDir = resolve(pkgRoot, 'src', 'dashboard', 'dist')
|
|
105
|
+
const projectRoot = process.cwd()
|
|
106
|
+
const logsDir = resolve(projectRoot, '.github', 'customizations', 'logs')
|
|
107
|
+
|
|
108
|
+
// Check if dist exists
|
|
109
|
+
if (!(await fileExists(distDir))) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'Dashboard not built. Run "npm run build:dashboard" in the opencastle package first.'
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if any log files exist (for messaging)
|
|
116
|
+
let hasLogs = false
|
|
117
|
+
if (!seed) {
|
|
118
|
+
for (const f of DATA_FILES) {
|
|
119
|
+
if (await fileExists(join(logsDir, f))) {
|
|
120
|
+
hasLogs = true
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const server = createServer(
|
|
127
|
+
async (req: IncomingMessage, res: ServerResponse) => {
|
|
128
|
+
try {
|
|
129
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host}`)
|
|
130
|
+
let pathname = decodeURIComponent(url.pathname)
|
|
131
|
+
|
|
132
|
+
// Serve index.html for root
|
|
133
|
+
if (pathname === '/') {
|
|
134
|
+
pathname = '/index.html'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle data file requests — proxy to project logs or dist
|
|
138
|
+
const dataMatch = pathname.match(/^\/data\/(.+\.ndjson)$/)
|
|
139
|
+
if (dataMatch && DATA_FILES.includes(dataMatch[1])) {
|
|
140
|
+
const filename = dataMatch[1]
|
|
141
|
+
let filePath: string
|
|
142
|
+
|
|
143
|
+
if (seed) {
|
|
144
|
+
filePath = join(distDir, 'data', filename)
|
|
145
|
+
} else {
|
|
146
|
+
filePath = join(logsDir, filename)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (await fileExists(filePath)) {
|
|
150
|
+
const content = await readFile(filePath)
|
|
151
|
+
res.writeHead(200, { 'Content-Type': 'application/x-ndjson' })
|
|
152
|
+
res.end(content)
|
|
153
|
+
} else {
|
|
154
|
+
// Graceful fallback — empty body
|
|
155
|
+
res.writeHead(200, { 'Content-Type': 'application/x-ndjson' })
|
|
156
|
+
res.end('')
|
|
157
|
+
}
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Serve static files from dist/
|
|
162
|
+
const filePath = resolve(distDir, pathname.slice(1))
|
|
163
|
+
|
|
164
|
+
// Security: prevent path traversal
|
|
165
|
+
if (!filePath.startsWith(distDir)) {
|
|
166
|
+
res.writeHead(403)
|
|
167
|
+
res.end('Forbidden')
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (await fileExists(filePath)) {
|
|
172
|
+
const ext = extname(filePath)
|
|
173
|
+
const contentType = MIME_TYPES[ext] ?? 'application/octet-stream'
|
|
174
|
+
const content = await readFile(filePath)
|
|
175
|
+
res.writeHead(200, { 'Content-Type': contentType })
|
|
176
|
+
res.end(content)
|
|
177
|
+
} else {
|
|
178
|
+
res.writeHead(404)
|
|
179
|
+
res.end('Not Found')
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
res.writeHead(500)
|
|
183
|
+
res.end('Internal Server Error')
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
const actualPort = await tryListen(server, port)
|
|
189
|
+
const url = `http://localhost:${actualPort}`
|
|
190
|
+
|
|
191
|
+
console.log('')
|
|
192
|
+
console.log(' \u{1F3F0} OpenCastle Dashboard')
|
|
193
|
+
console.log('')
|
|
194
|
+
console.log(` \u2192 ${url}`)
|
|
195
|
+
|
|
196
|
+
if (seed) {
|
|
197
|
+
console.log(
|
|
198
|
+
' \u{1F4C2} Showing demo data (use without --seed to read project logs)'
|
|
199
|
+
)
|
|
200
|
+
} else if (hasLogs) {
|
|
201
|
+
console.log(' \u{1F4C2} Reading logs from .github/customizations/logs/')
|
|
202
|
+
} else {
|
|
203
|
+
console.log(
|
|
204
|
+
' \u{1F4A1} No agent logs found. Run agents with OpenCastle to generate data, or use --seed for demo data.'
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log('')
|
|
209
|
+
console.log(' Press Ctrl+C to stop')
|
|
210
|
+
console.log('')
|
|
211
|
+
|
|
212
|
+
if (openBrowser) {
|
|
213
|
+
openUrl(url)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Graceful shutdown
|
|
217
|
+
process.on('SIGINT', () => {
|
|
218
|
+
console.log('\n Dashboard stopped.\n')
|
|
219
|
+
server.close()
|
|
220
|
+
process.exit(0)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// Keep the process alive
|
|
224
|
+
await new Promise<never>(() => {})
|
|
225
|
+
}
|
package/src/cli/diff.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { readManifest } from './manifest.js'
|
|
4
|
+
import type { CliContext } from './types.js'
|
|
5
|
+
|
|
6
|
+
export default async function diff({ pkgRoot }: CliContext): Promise<void> {
|
|
7
|
+
const projectRoot = process.cwd()
|
|
8
|
+
|
|
9
|
+
const manifest = await readManifest(projectRoot)
|
|
10
|
+
if (!manifest) {
|
|
11
|
+
console.error(
|
|
12
|
+
' ✗ No OpenCastle installation found. Run "npx opencastle init" first.'
|
|
13
|
+
)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pkg = JSON.parse(
|
|
18
|
+
await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
|
|
19
|
+
) as { version: string }
|
|
20
|
+
|
|
21
|
+
if (manifest.version === pkg.version) {
|
|
22
|
+
console.log(
|
|
23
|
+
` No changes — installed version matches package version (v${pkg.version}).`
|
|
24
|
+
)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(
|
|
29
|
+
`\n 🏰 OpenCastle diff: v${manifest.version} → v${pkg.version}\n`
|
|
30
|
+
)
|
|
31
|
+
console.log(' Framework files that would be updated:\n')
|
|
32
|
+
|
|
33
|
+
for (const path of manifest.managedPaths?.framework ?? []) {
|
|
34
|
+
console.log(` ↻ ${path}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('\n Customization files that would be preserved:\n')
|
|
38
|
+
|
|
39
|
+
for (const path of manifest.managedPaths?.customizable ?? []) {
|
|
40
|
+
console.log(` ✓ ${path}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`\n Run "npx opencastle update" to apply.\n`)
|
|
44
|
+
}
|
package/src/cli/eject.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { unlink } from 'node:fs/promises'
|
|
3
|
+
import { readManifest } from './manifest.js'
|
|
4
|
+
import { confirm, closePrompts } from './prompt.js'
|
|
5
|
+
import type { CliContext } from './types.js'
|
|
6
|
+
|
|
7
|
+
export default async function eject({
|
|
8
|
+
pkgRoot: _pkgRoot,
|
|
9
|
+
args: _args,
|
|
10
|
+
}: CliContext): Promise<void> {
|
|
11
|
+
const projectRoot = process.cwd()
|
|
12
|
+
|
|
13
|
+
const manifest = await readManifest(projectRoot)
|
|
14
|
+
if (!manifest) {
|
|
15
|
+
console.error(' ✗ No OpenCastle installation found.')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(`\n 🏰 OpenCastle eject\n`)
|
|
20
|
+
console.log(' This will:')
|
|
21
|
+
console.log(' • Remove .opencastle.json (manifest)')
|
|
22
|
+
console.log(' • Keep ALL generated files as standalone')
|
|
23
|
+
console.log(
|
|
24
|
+
' • You can safely uninstall the opencastle package after this\n'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const proceed = await confirm('Continue?')
|
|
28
|
+
if (!proceed) {
|
|
29
|
+
console.log(' Aborted.')
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await unlink(resolve(projectRoot, '.opencastle.json'))
|
|
34
|
+
|
|
35
|
+
console.log('\n ✓ Ejected. Files are now standalone.')
|
|
36
|
+
console.log(' You can uninstall: npm uninstall opencastle\n')
|
|
37
|
+
|
|
38
|
+
closePrompts()
|
|
39
|
+
}
|
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { select, confirm, closePrompts } from './prompt.js'
|
|
4
|
+
import { readManifest, writeManifest, createManifest } from './manifest.js'
|
|
5
|
+
import type { CliContext, IdeAdapter, CmsChoice, DbChoice, PmChoice, NotifChoice, StackConfig } from './types.js'
|
|
6
|
+
|
|
7
|
+
const ADAPTERS: Record<string, () => Promise<IdeAdapter>> = {
|
|
8
|
+
vscode: () => import('./adapters/vscode.js') as Promise<IdeAdapter>,
|
|
9
|
+
cursor: () => import('./adapters/cursor.js') as Promise<IdeAdapter>,
|
|
10
|
+
'claude-code': () =>
|
|
11
|
+
import('./adapters/claude-code.js') as Promise<IdeAdapter>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default async function init({ pkgRoot }: CliContext): Promise<void> {
|
|
15
|
+
const projectRoot = process.cwd()
|
|
16
|
+
|
|
17
|
+
// Check for existing installation
|
|
18
|
+
const existing = await readManifest(projectRoot)
|
|
19
|
+
if (existing) {
|
|
20
|
+
const proceed = await confirm(
|
|
21
|
+
`OpenCastle already installed (v${existing.version}, ${existing.ide}). Re-initialize?`,
|
|
22
|
+
false
|
|
23
|
+
)
|
|
24
|
+
if (!proceed) {
|
|
25
|
+
console.log(' Aborted.')
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const pkg = JSON.parse(
|
|
31
|
+
await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
|
|
32
|
+
) as { version: string }
|
|
33
|
+
|
|
34
|
+
console.log(`\n 🏰 OpenCastle v${pkg.version}`)
|
|
35
|
+
console.log(
|
|
36
|
+
' Multi-agent orchestration framework for AI coding assistants\n'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// ── IDE selection ───────────────────────────────────────────────
|
|
40
|
+
const ide = await select('Which IDE are you using?', [
|
|
41
|
+
{
|
|
42
|
+
label: 'VS Code',
|
|
43
|
+
hint: 'GitHub Copilot — .github/ agents, instructions, skills',
|
|
44
|
+
value: 'vscode',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'Cursor',
|
|
48
|
+
hint: '.cursorrules & .cursor/rules/*.mdc',
|
|
49
|
+
value: 'cursor',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Claude Code',
|
|
53
|
+
hint: 'CLAUDE.md & .claude/ commands, skills',
|
|
54
|
+
value: 'claude-code',
|
|
55
|
+
},
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
// ── CMS selection ───────────────────────────────────────────────
|
|
59
|
+
const cms = await select('Which CMS are you using?', [
|
|
60
|
+
{ label: 'Sanity', hint: 'GROQ queries, real-time collaboration', value: 'sanity' },
|
|
61
|
+
{ label: 'Contentful', hint: 'GraphQL / REST API, structured content', value: 'contentful' },
|
|
62
|
+
{ label: 'Strapi', hint: 'Open-source headless CMS', value: 'strapi' },
|
|
63
|
+
{ label: 'None', hint: 'No CMS — skip CMS skills and agents', value: 'none' },
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
// ── Database selection ──────────────────────────────────────────
|
|
67
|
+
const db = await select('Which database are you using?', [
|
|
68
|
+
{ label: 'Supabase', hint: 'Postgres + Auth + RLS + Edge Functions', value: 'supabase' },
|
|
69
|
+
{ label: 'Convex', hint: 'Reactive backend with real-time sync', value: 'convex' },
|
|
70
|
+
{ label: 'None', hint: 'No database — skip DB skills and agents', value: 'none' },
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
// ── Project management selection ────────────────────────────────
|
|
74
|
+
const pm = await select('Which project management tool are you using?', [
|
|
75
|
+
{ label: 'Linear', hint: 'Issue tracking with MCP integration', value: 'linear' },
|
|
76
|
+
{ label: 'Jira', hint: 'Atlassian issue tracking via Rovo MCP', value: 'jira' },
|
|
77
|
+
{ label: 'None', hint: 'No project management — skip PM skills', value: 'none' },
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
// ── Notifications selection ────────────────────────────────────
|
|
81
|
+
const notifications = await select('Which notifications tool are you using?', [
|
|
82
|
+
{ label: 'Slack', hint: 'Agent notifications and bi-directional communication', value: 'slack' },
|
|
83
|
+
{ label: 'Microsoft Teams', hint: 'Agent notifications via Teams channels', value: 'teams' },
|
|
84
|
+
{ label: 'None', hint: 'No notifications — skip messaging skills', value: 'none' },
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
const stack: StackConfig = { cms: cms as CmsChoice, db: db as DbChoice, pm: pm as PmChoice, notifications: notifications as NotifChoice }
|
|
88
|
+
|
|
89
|
+
console.log(`\n Installing for ${ide}...`)
|
|
90
|
+
console.log(` Stack: CMS=${stack.cms}, DB=${stack.db}, PM=${stack.pm}, Notifications=${stack.notifications}\n`)
|
|
91
|
+
|
|
92
|
+
// ── Run adapter ─────────────────────────────────────────────────
|
|
93
|
+
const adapter = await ADAPTERS[ide]()
|
|
94
|
+
const results = await adapter.install(pkgRoot, projectRoot, stack)
|
|
95
|
+
|
|
96
|
+
// ── Write manifest ──────────────────────────────────────────────
|
|
97
|
+
const manifest = createManifest(pkg.version, ide)
|
|
98
|
+
manifest.managedPaths = adapter.getManagedPaths()
|
|
99
|
+
manifest.stack = stack
|
|
100
|
+
await writeManifest(projectRoot, manifest)
|
|
101
|
+
|
|
102
|
+
// ── Summary ─────────────────────────────────────────────────────
|
|
103
|
+
const created = results.created.length
|
|
104
|
+
const skipped = results.skipped.length
|
|
105
|
+
|
|
106
|
+
console.log(` ✓ Created ${created} files`)
|
|
107
|
+
if (skipped > 0) {
|
|
108
|
+
console.log(` → Skipped ${skipped} existing files`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(`\n Next steps:`)
|
|
112
|
+
console.log(
|
|
113
|
+
' 1. Run the "Bootstrap Customizations" prompt to configure for your project'
|
|
114
|
+
)
|
|
115
|
+
console.log(' 2. Customize agent definitions for your tech stack')
|
|
116
|
+
console.log(' 3. Commit the generated files to your repository')
|
|
117
|
+
console.log()
|
|
118
|
+
|
|
119
|
+
closePrompts()
|
|
120
|
+
}
|