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.
Files changed (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/bin/cli.mjs +69 -0
  4. package/dist/cli/adapters/claude-code.d.ts +22 -0
  5. package/dist/cli/adapters/claude-code.d.ts.map +1 -0
  6. package/dist/cli/adapters/claude-code.js +237 -0
  7. package/dist/cli/adapters/claude-code.js.map +1 -0
  8. package/dist/cli/adapters/cursor.d.ts +20 -0
  9. package/dist/cli/adapters/cursor.d.ts.map +1 -0
  10. package/dist/cli/adapters/cursor.js +231 -0
  11. package/dist/cli/adapters/cursor.js.map +1 -0
  12. package/dist/cli/adapters/vscode.d.ts +20 -0
  13. package/dist/cli/adapters/vscode.d.ts.map +1 -0
  14. package/dist/cli/adapters/vscode.js +132 -0
  15. package/dist/cli/adapters/vscode.js.map +1 -0
  16. package/dist/cli/copy.d.ts +14 -0
  17. package/dist/cli/copy.d.ts.map +1 -0
  18. package/dist/cli/copy.js +62 -0
  19. package/dist/cli/copy.js.map +1 -0
  20. package/dist/cli/dashboard.d.ts +3 -0
  21. package/dist/cli/dashboard.d.ts.map +1 -0
  22. package/dist/cli/dashboard.js +183 -0
  23. package/dist/cli/dashboard.js.map +1 -0
  24. package/dist/cli/diff.d.ts +3 -0
  25. package/dist/cli/diff.d.ts.map +1 -0
  26. package/dist/cli/diff.js +27 -0
  27. package/dist/cli/diff.js.map +1 -0
  28. package/dist/cli/eject.d.ts +3 -0
  29. package/dist/cli/eject.d.ts.map +1 -0
  30. package/dist/cli/eject.js +27 -0
  31. package/dist/cli/eject.js.map +1 -0
  32. package/dist/cli/init.d.ts +3 -0
  33. package/dist/cli/init.d.ts.map +1 -0
  34. package/dist/cli/init.js +92 -0
  35. package/dist/cli/init.js.map +1 -0
  36. package/dist/cli/manifest.d.ts +14 -0
  37. package/dist/cli/manifest.d.ts.map +1 -0
  38. package/dist/cli/manifest.js +34 -0
  39. package/dist/cli/manifest.js.map +1 -0
  40. package/dist/cli/mcp.d.ts +14 -0
  41. package/dist/cli/mcp.d.ts.map +1 -0
  42. package/dist/cli/mcp.js +35 -0
  43. package/dist/cli/mcp.js.map +1 -0
  44. package/dist/cli/prompt.d.ts +12 -0
  45. package/dist/cli/prompt.d.ts.map +1 -0
  46. package/dist/cli/prompt.js +104 -0
  47. package/dist/cli/prompt.js.map +1 -0
  48. package/dist/cli/run/adapters/claude-code.d.ts +16 -0
  49. package/dist/cli/run/adapters/claude-code.d.ts.map +1 -0
  50. package/dist/cli/run/adapters/claude-code.js +82 -0
  51. package/dist/cli/run/adapters/claude-code.js.map +1 -0
  52. package/dist/cli/run/adapters/copilot.d.ts +16 -0
  53. package/dist/cli/run/adapters/copilot.d.ts.map +1 -0
  54. package/dist/cli/run/adapters/copilot.js +84 -0
  55. package/dist/cli/run/adapters/copilot.js.map +1 -0
  56. package/dist/cli/run/adapters/cursor.d.ts +16 -0
  57. package/dist/cli/run/adapters/cursor.d.ts.map +1 -0
  58. package/dist/cli/run/adapters/cursor.js +81 -0
  59. package/dist/cli/run/adapters/cursor.js.map +1 -0
  60. package/dist/cli/run/adapters/index.d.ts +14 -0
  61. package/dist/cli/run/adapters/index.d.ts.map +1 -0
  62. package/dist/cli/run/adapters/index.js +35 -0
  63. package/dist/cli/run/adapters/index.js.map +1 -0
  64. package/dist/cli/run/executor.d.ts +15 -0
  65. package/dist/cli/run/executor.d.ts.map +1 -0
  66. package/dist/cli/run/executor.js +249 -0
  67. package/dist/cli/run/executor.js.map +1 -0
  68. package/dist/cli/run/reporter.d.ts +10 -0
  69. package/dist/cli/run/reporter.d.ts.map +1 -0
  70. package/dist/cli/run/reporter.js +112 -0
  71. package/dist/cli/run/reporter.js.map +1 -0
  72. package/dist/cli/run/schema.d.ts +28 -0
  73. package/dist/cli/run/schema.d.ts.map +1 -0
  74. package/dist/cli/run/schema.js +511 -0
  75. package/dist/cli/run/schema.js.map +1 -0
  76. package/dist/cli/run.d.ts +6 -0
  77. package/dist/cli/run.d.ts.map +1 -0
  78. package/dist/cli/run.js +123 -0
  79. package/dist/cli/run.js.map +1 -0
  80. package/dist/cli/stack-config.d.ts +12 -0
  81. package/dist/cli/stack-config.d.ts.map +1 -0
  82. package/dist/cli/stack-config.js +146 -0
  83. package/dist/cli/stack-config.js.map +1 -0
  84. package/dist/cli/types.d.ts +169 -0
  85. package/dist/cli/types.d.ts.map +1 -0
  86. package/dist/cli/types.js +2 -0
  87. package/dist/cli/types.js.map +1 -0
  88. package/dist/cli/update.d.ts +3 -0
  89. package/dist/cli/update.d.ts.map +1 -0
  90. package/dist/cli/update.js +50 -0
  91. package/dist/cli/update.js.map +1 -0
  92. package/package.json +48 -0
  93. package/src/cli/adapters/claude-code.ts +287 -0
  94. package/src/cli/adapters/cursor.ts +377 -0
  95. package/src/cli/adapters/vscode.ts +168 -0
  96. package/src/cli/copy.ts +79 -0
  97. package/src/cli/dashboard.ts +225 -0
  98. package/src/cli/diff.ts +44 -0
  99. package/src/cli/eject.ts +39 -0
  100. package/src/cli/init.ts +120 -0
  101. package/src/cli/manifest.ts +45 -0
  102. package/src/cli/mcp.ts +49 -0
  103. package/src/cli/prompt.ts +115 -0
  104. package/src/cli/run/adapters/claude-code.ts +95 -0
  105. package/src/cli/run/adapters/copilot.ts +97 -0
  106. package/src/cli/run/adapters/cursor.ts +94 -0
  107. package/src/cli/run/adapters/index.ts +40 -0
  108. package/src/cli/run/executor.ts +292 -0
  109. package/src/cli/run/reporter.ts +129 -0
  110. package/src/cli/run/schema.ts +595 -0
  111. package/src/cli/run.ts +137 -0
  112. package/src/cli/stack-config.ts +180 -0
  113. package/src/cli/types.ts +207 -0
  114. package/src/cli/update.ts +75 -0
  115. package/src/dashboard/astro.config.mjs +6 -0
  116. package/src/dashboard/package-lock.json +5455 -0
  117. package/src/dashboard/package.json +14 -0
  118. package/src/dashboard/public/data/delegations.ndjson +35 -0
  119. package/src/dashboard/public/data/panels.ndjson +13 -0
  120. package/src/dashboard/public/data/sessions.ndjson +50 -0
  121. package/src/dashboard/public/icon-192.png +0 -0
  122. package/src/dashboard/scripts/generate-seed-data.ts +355 -0
  123. package/src/dashboard/src/layouts/Layout.astro +25 -0
  124. package/src/dashboard/src/pages/index.astro +1070 -0
  125. package/src/dashboard/src/styles/dashboard.css +1078 -0
  126. package/src/dashboard/tsconfig.json +6 -0
  127. package/src/orchestrator/agent-workflows/README.md +22 -0
  128. package/src/orchestrator/agent-workflows/bug-fix.md +128 -0
  129. package/src/orchestrator/agent-workflows/data-pipeline.md +145 -0
  130. package/src/orchestrator/agent-workflows/database-migration.md +159 -0
  131. package/src/orchestrator/agent-workflows/feature-implementation.md +223 -0
  132. package/src/orchestrator/agent-workflows/performance-optimization.md +125 -0
  133. package/src/orchestrator/agent-workflows/refactoring.md +142 -0
  134. package/src/orchestrator/agent-workflows/schema-changes.md +164 -0
  135. package/src/orchestrator/agent-workflows/security-audit.md +148 -0
  136. package/src/orchestrator/agent-workflows/shared-delivery-phase.md +33 -0
  137. package/src/orchestrator/agents/api-designer.agent.md +68 -0
  138. package/src/orchestrator/agents/architect.agent.md +129 -0
  139. package/src/orchestrator/agents/content-engineer.agent.md +57 -0
  140. package/src/orchestrator/agents/copywriter.agent.md +95 -0
  141. package/src/orchestrator/agents/data-expert.agent.md +63 -0
  142. package/src/orchestrator/agents/database-engineer.agent.md +62 -0
  143. package/src/orchestrator/agents/developer.agent.md +66 -0
  144. package/src/orchestrator/agents/devops-expert.agent.md +57 -0
  145. package/src/orchestrator/agents/documentation-writer.agent.md +60 -0
  146. package/src/orchestrator/agents/performance-expert.agent.md +58 -0
  147. package/src/orchestrator/agents/release-manager.agent.md +72 -0
  148. package/src/orchestrator/agents/researcher.agent.md +145 -0
  149. package/src/orchestrator/agents/reviewer.agent.md +62 -0
  150. package/src/orchestrator/agents/security-expert.agent.md +64 -0
  151. package/src/orchestrator/agents/seo-specialist.agent.md +67 -0
  152. package/src/orchestrator/agents/team-lead.agent.md +644 -0
  153. package/src/orchestrator/agents/testing-expert.agent.md +85 -0
  154. package/src/orchestrator/agents/ui-ux-expert.agent.md +63 -0
  155. package/src/orchestrator/copilot-instructions.md +3 -0
  156. package/src/orchestrator/customizations/AGENT-EXPERTISE.md +325 -0
  157. package/src/orchestrator/customizations/AGENT-FAILURES.md +69 -0
  158. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +58 -0
  159. package/src/orchestrator/customizations/DISPUTES.md +162 -0
  160. package/src/orchestrator/customizations/KNOWLEDGE-GRAPH.md +10 -0
  161. package/src/orchestrator/customizations/LESSONS-LEARNED.md +70 -0
  162. package/src/orchestrator/customizations/README.md +59 -0
  163. package/src/orchestrator/customizations/agents/agent-registry.md +46 -0
  164. package/src/orchestrator/customizations/agents/skill-matrix.md +142 -0
  165. package/src/orchestrator/customizations/logs/README.md +181 -0
  166. package/src/orchestrator/customizations/logs/delegations.ndjson +1 -0
  167. package/src/orchestrator/customizations/logs/panels.ndjson +1 -0
  168. package/src/orchestrator/customizations/logs/sessions.ndjson +1 -0
  169. package/src/orchestrator/customizations/project/docs-structure.md +23 -0
  170. package/src/orchestrator/customizations/project/tracker-config.md +45 -0
  171. package/src/orchestrator/customizations/project.instructions.md +64 -0
  172. package/src/orchestrator/customizations/stack/api-config.md +37 -0
  173. package/src/orchestrator/customizations/stack/cms-config.md +26 -0
  174. package/src/orchestrator/customizations/stack/data-pipeline-config.md +41 -0
  175. package/src/orchestrator/customizations/stack/database-config.md +44 -0
  176. package/src/orchestrator/customizations/stack/deployment-config.md +45 -0
  177. package/src/orchestrator/customizations/stack/testing-config.md +56 -0
  178. package/src/orchestrator/instructions/ai-optimization.instructions.md +143 -0
  179. package/src/orchestrator/instructions/general.instructions.md +194 -0
  180. package/src/orchestrator/mcp.json +55 -0
  181. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +235 -0
  182. package/src/orchestrator/prompts/brainstorm.prompt.md +115 -0
  183. package/src/orchestrator/prompts/bug-fix.prompt.md +141 -0
  184. package/src/orchestrator/prompts/create-skill.prompt.md +103 -0
  185. package/src/orchestrator/prompts/generate-task-spec.prompt.md +154 -0
  186. package/src/orchestrator/prompts/implement-feature.prompt.md +124 -0
  187. package/src/orchestrator/prompts/metrics-report.prompt.md +142 -0
  188. package/src/orchestrator/prompts/quick-refinement.prompt.md +137 -0
  189. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +100 -0
  190. package/src/orchestrator/skills/accessibility-standards/SKILL.md +164 -0
  191. package/src/orchestrator/skills/agent-hooks/SKILL.md +147 -0
  192. package/src/orchestrator/skills/agent-memory/SKILL.md +144 -0
  193. package/src/orchestrator/skills/api-patterns/SKILL.md +106 -0
  194. package/src/orchestrator/skills/browser-testing/SKILL.md +203 -0
  195. package/src/orchestrator/skills/code-commenting/SKILL.md +133 -0
  196. package/src/orchestrator/skills/contentful-cms/SKILL.md +43 -0
  197. package/src/orchestrator/skills/context-map/SKILL.md +135 -0
  198. package/src/orchestrator/skills/convex-database/SKILL.md +80 -0
  199. package/src/orchestrator/skills/data-engineering/SKILL.md +99 -0
  200. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +49 -0
  201. package/src/orchestrator/skills/documentation-standards/SKILL.md +85 -0
  202. package/src/orchestrator/skills/fast-review/SKILL.md +327 -0
  203. package/src/orchestrator/skills/frontend-design/SKILL.md +42 -0
  204. package/src/orchestrator/skills/jira-management/SKILL.md +168 -0
  205. package/src/orchestrator/skills/memory-merger/SKILL.md +123 -0
  206. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +75 -0
  207. package/src/orchestrator/skills/nx-workspace/SKILL.md +192 -0
  208. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +184 -0
  209. package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +38 -0
  210. package/src/orchestrator/skills/performance-optimization/SKILL.md +101 -0
  211. package/src/orchestrator/skills/react-development/SKILL.md +117 -0
  212. package/src/orchestrator/skills/sanity-cms/SKILL.md +18 -0
  213. package/src/orchestrator/skills/security-hardening/SKILL.md +118 -0
  214. package/src/orchestrator/skills/self-improvement/SKILL.md +137 -0
  215. package/src/orchestrator/skills/seo-patterns/SKILL.md +40 -0
  216. package/src/orchestrator/skills/session-checkpoints/SKILL.md +205 -0
  217. package/src/orchestrator/skills/slack-notifications/SKILL.md +211 -0
  218. package/src/orchestrator/skills/strapi-cms/SKILL.md +43 -0
  219. package/src/orchestrator/skills/supabase-database/SKILL.md +24 -0
  220. package/src/orchestrator/skills/task-management/SKILL.md +143 -0
  221. package/src/orchestrator/skills/team-lead-reference/SKILL.md +317 -0
  222. package/src/orchestrator/skills/teams-notifications/SKILL.md +249 -0
  223. package/src/orchestrator/skills/testing-workflow/SKILL.md +134 -0
  224. package/src/orchestrator/skills/validation-gates/SKILL.md +100 -0
package/src/cli/run.ts ADDED
@@ -0,0 +1,137 @@
1
+ import { resolve } from 'node:path'
2
+ import { parseTaskSpec } from './run/schema.js'
3
+ import { createExecutor, buildPhases } from './run/executor.js'
4
+ import { getAdapter } from './run/adapters/index.js'
5
+ import { createReporter, printExecutionPlan } from './run/reporter.js'
6
+ import type { CliContext, RunOptions } from './types.js'
7
+
8
+ const HELP = `
9
+ opencastle run [options]
10
+
11
+ Process a task queue from a spec file, delegating to AI agents autonomously.
12
+
13
+ Options:
14
+ --file, -f <path> Task spec file (default: opencastle.tasks.yml)
15
+ --dry-run Show execution plan without running
16
+ --concurrency, -c <n> Override max parallel tasks
17
+ --adapter, -a <name> Override agent runtime adapter
18
+ --report-dir <path> Where to write run reports (default: .opencastle/runs)
19
+ --verbose Show full agent output
20
+ --help, -h Show this help
21
+ `
22
+
23
+ /**
24
+ * Parse CLI arguments for the run command.
25
+ */
26
+ function parseArgs(args: string[]): RunOptions {
27
+ const opts: RunOptions = {
28
+ file: 'opencastle.tasks.yml',
29
+ dryRun: false,
30
+ concurrency: null,
31
+ adapter: null,
32
+ reportDir: null,
33
+ verbose: false,
34
+ help: false,
35
+ }
36
+
37
+ for (let i = 0; i < args.length; i++) {
38
+ const arg = args[i]
39
+ switch (arg) {
40
+ case '--help':
41
+ case '-h':
42
+ opts.help = true
43
+ break
44
+ case '--file':
45
+ case '-f':
46
+ opts.file = args[++i]
47
+ break
48
+ case '--dry-run':
49
+ opts.dryRun = true
50
+ break
51
+ case '--concurrency':
52
+ case '-c': {
53
+ const val = parseInt(args[++i], 10)
54
+ if (!Number.isFinite(val) || val < 1) {
55
+ console.error(` ✗ --concurrency must be an integer >= 1`)
56
+ process.exit(1)
57
+ }
58
+ opts.concurrency = val
59
+ break
60
+ }
61
+ case '--adapter':
62
+ case '-a':
63
+ opts.adapter = args[++i]
64
+ break
65
+ case '--report-dir':
66
+ opts.reportDir = args[++i]
67
+ break
68
+ case '--verbose':
69
+ opts.verbose = true
70
+ break
71
+ default:
72
+ console.error(` ✗ Unknown option: ${arg}`)
73
+ console.log(HELP)
74
+ process.exit(1)
75
+ }
76
+ }
77
+
78
+ return opts
79
+ }
80
+
81
+ /**
82
+ * CLI entry point for the `run` command.
83
+ */
84
+ export default async function run({ args }: CliContext): Promise<void> {
85
+ const opts = parseArgs(args)
86
+
87
+ if (opts.help) {
88
+ console.log(HELP)
89
+ return
90
+ }
91
+
92
+ // ── Read and validate spec ────────────────────────────────────
93
+ const specPath = resolve(process.cwd(), opts.file)
94
+ const spec = await parseTaskSpec(specPath)
95
+
96
+ // Apply CLI overrides
97
+ if (opts.concurrency !== null) spec.concurrency = opts.concurrency
98
+ if (opts.adapter !== null) spec.adapter = opts.adapter
99
+ if (opts.verbose) spec._verbose = true
100
+
101
+ // ── Dry run ──────────────────────────────────────────────────
102
+ const phases = buildPhases(spec.tasks)
103
+
104
+ if (opts.dryRun) {
105
+ printExecutionPlan(spec, phases)
106
+ return
107
+ }
108
+
109
+ // ── Check adapter ────────────────────────────────────────────
110
+ const adapter = await getAdapter(spec.adapter)
111
+ const available = await adapter.isAvailable()
112
+ if (!available) {
113
+ console.error(
114
+ ` ✗ Adapter "${spec.adapter}" is not available.\n` +
115
+ ` Make sure the "${spec.adapter === 'claude-code' ? 'claude' : spec.adapter}" CLI is installed and on your PATH.`
116
+ )
117
+ process.exit(1)
118
+ }
119
+
120
+ // ── Execute ──────────────────────────────────────────────────
121
+ console.log(`\n 🏰 OpenCastle Run: ${spec.name}`)
122
+ console.log(` Adapter: ${adapter.name} | Concurrency: ${spec.concurrency} | Tasks: ${spec.tasks.length}`)
123
+
124
+ const reporter = createReporter(spec, {
125
+ reportDir: opts.reportDir
126
+ ? resolve(process.cwd(), opts.reportDir)
127
+ : undefined,
128
+ verbose: opts.verbose,
129
+ })
130
+
131
+ const executor = createExecutor(spec, adapter, reporter)
132
+ const report = await executor.run()
133
+
134
+ // ── Exit code ────────────────────────────────────────────────
135
+ const hasFailures = report.summary.failed > 0 || report.summary['timed-out'] > 0
136
+ process.exit(hasFailures ? 1 : 0)
137
+ }
@@ -0,0 +1,180 @@
1
+ import type { CmsChoice, DbChoice, PmChoice, NotifChoice, StackConfig, CopyDirOptions } from './types.js';
2
+
3
+ // ── Skill / Technology labels ─────────────────────────────────
4
+
5
+ /** Display name for each CMS choice */
6
+ const CMS_LABELS: Record<Exclude<CmsChoice, 'none'>, { tech: string; skill: string }> = {
7
+ sanity: { tech: 'Sanity', skill: 'sanity-cms' },
8
+ contentful: { tech: 'Contentful', skill: 'contentful-cms' },
9
+ strapi: { tech: 'Strapi', skill: 'strapi-cms' },
10
+ };
11
+
12
+ /** Display name for each DB choice */
13
+ const DB_LABELS: Record<Exclude<DbChoice, 'none'>, { tech: string; skill: string }> = {
14
+ supabase: { tech: 'Supabase', skill: 'supabase-database' },
15
+ convex: { tech: 'Convex', skill: 'convex-database' },
16
+ };
17
+
18
+ /** Display name for each PM choice */
19
+ const PM_LABELS: Record<Exclude<PmChoice, 'none'>, { tech: string; skill: string }> = {
20
+ linear: { tech: 'Linear', skill: 'task-management' },
21
+ jira: { tech: 'Jira', skill: 'jira-management' },
22
+ };
23
+
24
+ /** Display name for each notifications choice */
25
+ const NOTIF_LABELS: Record<Exclude<NotifChoice, 'none'>, { tech: string; skill: string }> = {
26
+ slack: { tech: 'Slack', skill: 'slack-notifications' },
27
+ teams: { tech: 'Teams', skill: 'teams-notifications' },
28
+ };
29
+
30
+ // ── Exclusion / inclusion maps ────────────────────────────────
31
+
32
+ /** Skills to EXCLUDE based on CMS choice */
33
+ const CMS_SKILL_MAP: Record<CmsChoice, string[]> = {
34
+ sanity: ['contentful-cms', 'strapi-cms'],
35
+ contentful: ['sanity-cms', 'strapi-cms'],
36
+ strapi: ['sanity-cms', 'contentful-cms'],
37
+ none: ['sanity-cms', 'contentful-cms', 'strapi-cms'],
38
+ };
39
+
40
+ /** Skills to EXCLUDE based on DB choice */
41
+ const DB_SKILL_MAP: Record<DbChoice, string[]> = {
42
+ supabase: ['convex-database'],
43
+ convex: ['supabase-database'],
44
+ none: ['supabase-database', 'convex-database'],
45
+ };
46
+
47
+ /** Skills to EXCLUDE based on PM choice */
48
+ const PM_SKILL_MAP: Record<PmChoice, string[]> = {
49
+ linear: ['jira-management'],
50
+ jira: ['task-management'],
51
+ none: ['task-management', 'jira-management'],
52
+ };
53
+
54
+ /** Agents to EXCLUDE based on CMS choice */
55
+ const CMS_AGENT_EXCLUSIONS: Record<CmsChoice, string[]> = {
56
+ sanity: [],
57
+ contentful: [],
58
+ strapi: [],
59
+ none: ['content-engineer.agent.md'],
60
+ };
61
+
62
+ /** Agents to EXCLUDE based on DB choice */
63
+ const DB_AGENT_EXCLUSIONS: Record<DbChoice, string[]> = {
64
+ supabase: [],
65
+ convex: [],
66
+ none: ['database-engineer.agent.md'],
67
+ };
68
+
69
+ /** MCP server keys to INCLUDE based on CMS choice */
70
+ const CMS_MCP_MAP: Record<CmsChoice, string[]> = {
71
+ sanity: ['Sanity'],
72
+ contentful: ['Contentful'],
73
+ strapi: ['Strapi'],
74
+ none: [],
75
+ };
76
+
77
+ /** MCP server keys to INCLUDE based on DB choice */
78
+ const DB_MCP_MAP: Record<DbChoice, string[]> = {
79
+ supabase: ['Supabase'],
80
+ convex: ['Convex'],
81
+ none: [],
82
+ };
83
+
84
+ /** MCP server keys to INCLUDE based on PM choice */
85
+ const PM_MCP_MAP: Record<PmChoice, string[]> = {
86
+ linear: ['Linear'],
87
+ jira: ['Jira'],
88
+ none: [],
89
+ };
90
+
91
+ /** Skills to EXCLUDE based on notifications choice */
92
+ const NOTIF_SKILL_MAP: Record<NotifChoice, string[]> = {
93
+ slack: ['teams-notifications'],
94
+ teams: ['slack-notifications'],
95
+ none: ['slack-notifications', 'teams-notifications'],
96
+ };
97
+
98
+ /** MCP server keys to INCLUDE based on notifications choice */
99
+ const NOTIF_MCP_MAP: Record<NotifChoice, string[]> = {
100
+ slack: ['Slack'],
101
+ teams: ['Teams'],
102
+ none: [],
103
+ };
104
+
105
+ /** Always-included MCP servers */
106
+ const CORE_MCP_SERVERS = ['chrome-devtools', 'Vercel'];
107
+
108
+ export function getExcludedSkills(stack: StackConfig): Set<string> {
109
+ return new Set([
110
+ ...CMS_SKILL_MAP[stack.cms],
111
+ ...DB_SKILL_MAP[stack.db],
112
+ ...PM_SKILL_MAP[stack.pm],
113
+ ...NOTIF_SKILL_MAP[stack.notifications],
114
+ ]);
115
+ }
116
+
117
+ export function getExcludedAgents(stack: StackConfig): Set<string> {
118
+ return new Set([
119
+ ...CMS_AGENT_EXCLUSIONS[stack.cms],
120
+ ...DB_AGENT_EXCLUSIONS[stack.db],
121
+ ]);
122
+ }
123
+
124
+ export function getIncludedMcpServers(stack: StackConfig): Set<string> {
125
+ return new Set([
126
+ ...CORE_MCP_SERVERS,
127
+ ...CMS_MCP_MAP[stack.cms],
128
+ ...DB_MCP_MAP[stack.db],
129
+ ...PM_MCP_MAP[stack.pm],
130
+ ...NOTIF_MCP_MAP[stack.notifications],
131
+ ]);
132
+ }
133
+
134
+ // ── Customization file transforms ─────────────────────────────
135
+
136
+ /**
137
+ * Return a transform callback that pre-populates customization files
138
+ * based on the user's stack selection.
139
+ *
140
+ * Used by all adapters when copying the `customizations/` directory.
141
+ */
142
+ export function getCustomizationsTransform(
143
+ stack: StackConfig
144
+ ): NonNullable<CopyDirOptions['transform']> {
145
+ return (content: string, srcPath: string) => {
146
+ // Pre-fill skill matrix with CMS and DB bindings
147
+ if (srcPath.endsWith('skill-matrix.md')) {
148
+ return transformSkillMatrix(content, stack);
149
+ }
150
+ return content;
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Fill in the `database` and `cms` rows in the skill matrix
156
+ * based on the user's stack selection.
157
+ */
158
+ function transformSkillMatrix(content: string, stack: StackConfig): string {
159
+ let result = content;
160
+
161
+ // Fill the database row
162
+ if (stack.db !== 'none') {
163
+ const { tech, skill } = DB_LABELS[stack.db];
164
+ result = result.replace(
165
+ /(\| `database`\s*\|)\s*\|(\s*\|)/,
166
+ `$1 ${tech} | \`${skill}\` $2`
167
+ );
168
+ }
169
+
170
+ // Fill the CMS row
171
+ if (stack.cms !== 'none') {
172
+ const { tech, skill } = CMS_LABELS[stack.cms];
173
+ result = result.replace(
174
+ /(\| `cms`\s*\|)\s*\|(\s*\|)/,
175
+ `$1 ${tech} | \`${skill}\` $2`
176
+ );
177
+ }
178
+
179
+ return result;
180
+ }
@@ -0,0 +1,207 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+
3
+ // ── Stack selection types ──────────────────────────────────────
4
+
5
+ export type CmsChoice = 'sanity' | 'contentful' | 'strapi' | 'none';
6
+ export type DbChoice = 'supabase' | 'convex' | 'none';
7
+ export type PmChoice = 'linear' | 'jira' | 'none';
8
+ export type NotifChoice = 'slack' | 'teams' | 'none';
9
+
10
+ export interface StackConfig {
11
+ cms: CmsChoice;
12
+ db: DbChoice;
13
+ pm: PmChoice;
14
+ notifications: NotifChoice;
15
+ }
16
+
17
+ /** Context passed from bin/cli.mjs to every command handler. */
18
+ export interface CliContext {
19
+ pkgRoot: string;
20
+ args: string[];
21
+ }
22
+
23
+ /** Results from a copy/install/update operation. */
24
+ export interface CopyResults {
25
+ copied: string[];
26
+ skipped: string[];
27
+ created: string[];
28
+ }
29
+
30
+ /** Options for the copyDir utility. */
31
+ export interface CopyDirOptions {
32
+ overwrite?: boolean;
33
+ filter?: (_name: string, _srcPath: string) => boolean;
34
+ transform?: (
35
+ _content: string,
36
+ _srcPath: string
37
+ ) => Promise<string | null> | string | null;
38
+ }
39
+
40
+ /** OpenCastle project manifest (.opencastle.json). */
41
+ export interface Manifest {
42
+ version: string;
43
+ ide: string;
44
+ installedAt: string;
45
+ updatedAt: string;
46
+ managedPaths?: ManagedPaths;
47
+ stack?: StackConfig;
48
+ }
49
+
50
+ /** Framework vs customizable file paths. */
51
+ export interface ManagedPaths {
52
+ framework: string[];
53
+ customizable: string[];
54
+ }
55
+
56
+ /** IDE adapter interface (init/update commands). */
57
+ export interface IdeAdapter {
58
+ install(_pkgRoot: string, _projectRoot: string, _stack?: StackConfig): Promise<CopyResults>;
59
+ update(_pkgRoot: string, _projectRoot: string, _stack?: StackConfig): Promise<CopyResults>;
60
+ getManagedPaths(): ManagedPaths;
61
+ }
62
+
63
+ /** Select prompt option. */
64
+ export interface SelectOption {
65
+ label: string;
66
+ hint?: string;
67
+ value: string;
68
+ }
69
+
70
+ /** Scaffold result from MCP config. */
71
+ export interface ScaffoldResult {
72
+ path: string;
73
+ action: 'created' | 'skipped';
74
+ }
75
+
76
+ // ── Run command types ──────────────────────────────────────────
77
+
78
+ /** Validated task spec from YAML. */
79
+ export interface TaskSpec {
80
+ name: string;
81
+ concurrency: number;
82
+ on_failure: 'continue' | 'stop';
83
+ adapter: string;
84
+ tasks: Task[];
85
+ _verbose?: boolean;
86
+ }
87
+
88
+ /** A single task in the spec. */
89
+ export interface Task {
90
+ id: string;
91
+ prompt: string;
92
+ agent: string;
93
+ timeout: string;
94
+ depends_on: string[];
95
+ files: string[];
96
+ description: string;
97
+ _process?: ChildProcess;
98
+ }
99
+
100
+ /** Task execution status. */
101
+ export type TaskStatus =
102
+ | 'pending'
103
+ | 'running'
104
+ | 'done'
105
+ | 'failed'
106
+ | 'skipped'
107
+ | 'timed-out';
108
+
109
+ /** Result of a single task execution. */
110
+ export interface TaskResult {
111
+ id: string;
112
+ status: TaskStatus;
113
+ duration: number;
114
+ output: string;
115
+ exitCode: number;
116
+ }
117
+
118
+ /** Final run report. */
119
+ export interface RunReport {
120
+ name: string;
121
+ startedAt: string;
122
+ completedAt: string;
123
+ duration: string;
124
+ summary: RunSummary;
125
+ tasks: TaskResult[];
126
+ }
127
+
128
+ /** Summary counts of task statuses. */
129
+ export interface RunSummary {
130
+ total: number;
131
+ done: number;
132
+ failed: number;
133
+ skipped: number;
134
+ 'timed-out': number;
135
+ }
136
+
137
+ /** Agent runtime adapter for the run command. */
138
+ export interface AgentAdapter {
139
+ name: string;
140
+ isAvailable(): Promise<boolean>;
141
+ execute(_task: Task, _options?: ExecuteOptions): Promise<ExecuteResult>;
142
+ kill?(_task: Task): void;
143
+ }
144
+
145
+ /** Options for agent execution. */
146
+ export interface ExecuteOptions {
147
+ verbose?: boolean;
148
+ }
149
+
150
+ /** Result from an agent adapter execution. */
151
+ export interface ExecuteResult {
152
+ success: boolean;
153
+ output: string;
154
+ exitCode: number;
155
+ _timedOut?: boolean;
156
+ taskId?: string;
157
+ }
158
+
159
+ /** Reporter interface for the run command. */
160
+ export interface Reporter {
161
+ onTaskStart(_task: Task): void;
162
+ onTaskDone(_task: Task, _result: TaskResult): void;
163
+ onTaskSkipped(_task: Task, _reason: string): void;
164
+ onPhaseStart(_phase: number, _tasks: Task[]): void;
165
+ onComplete(_report: RunReport): Promise<void>;
166
+ }
167
+
168
+ /** Reporter options. */
169
+ export interface ReporterOptions {
170
+ reportDir?: string;
171
+ verbose?: boolean;
172
+ }
173
+
174
+ /** Parsed CLI args for the run command. */
175
+ export interface RunOptions {
176
+ file: string;
177
+ dryRun: boolean;
178
+ concurrency: number | null;
179
+ adapter: string | null;
180
+ reportDir: string | null;
181
+ verbose: boolean;
182
+ help: boolean;
183
+ }
184
+
185
+ /** Parse result from YAML block parser. */
186
+ export interface ParseResult {
187
+ value: unknown;
188
+ nextIndex: number;
189
+ }
190
+
191
+ /** Validation result. */
192
+ export interface ValidationResult {
193
+ valid: boolean;
194
+ errors: string[];
195
+ }
196
+
197
+ /** Timeout promise with cancel ability. */
198
+ export interface TimeoutHandle {
199
+ promise: Promise<ExecuteResult>;
200
+ clear: () => void;
201
+ }
202
+
203
+ /** Executor returned by createExecutor. */
204
+ export interface Executor {
205
+ run(): Promise<RunReport>;
206
+ getPhases(): Task[][];
207
+ }
@@ -0,0 +1,75 @@
1
+ import { resolve } from 'node:path'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { readManifest, writeManifest } from './manifest.js'
4
+ import { confirm, closePrompts } from './prompt.js'
5
+ import type { CliContext, IdeAdapter } 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
+ const VALID_IDES = Object.keys(ADAPTERS)
15
+
16
+ export default async function update({
17
+ pkgRoot,
18
+ args,
19
+ }: CliContext): Promise<void> {
20
+ const projectRoot = process.cwd()
21
+
22
+ const manifest = await readManifest(projectRoot)
23
+ if (!manifest) {
24
+ console.error(
25
+ ' ✗ No OpenCastle installation found. Run "npx opencastle init" first.'
26
+ )
27
+ process.exit(1)
28
+ }
29
+
30
+ if (!manifest.ide || !VALID_IDES.includes(manifest.ide)) {
31
+ console.error(
32
+ ` ✗ Invalid IDE "${manifest.ide}" in .opencastle.json. Valid options: ${VALID_IDES.join(', ')}`
33
+ )
34
+ process.exit(1)
35
+ }
36
+
37
+ const pkg = JSON.parse(
38
+ await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
39
+ ) as { version: string }
40
+
41
+ if (manifest.version === pkg.version && !args.includes('--force')) {
42
+ console.log(` Already up to date (v${pkg.version}).`)
43
+ return
44
+ }
45
+
46
+ console.log(
47
+ `\n 🏰 OpenCastle update: v${manifest.version} → v${pkg.version}\n`
48
+ )
49
+ console.log(` IDE: ${manifest.ide}`)
50
+ console.log(' Framework files will be overwritten.')
51
+ console.log(' Customization files will be preserved.\n')
52
+
53
+ const proceed = await confirm('Proceed with update?')
54
+ if (!proceed) {
55
+ console.log(' Aborted.')
56
+ return
57
+ }
58
+
59
+ const adapter = await ADAPTERS[manifest.ide]()
60
+ const results = await adapter.update(pkgRoot, projectRoot)
61
+
62
+ // Update manifest
63
+ manifest.version = pkg.version
64
+ manifest.updatedAt = new Date().toISOString()
65
+ manifest.managedPaths = adapter.getManagedPaths()
66
+ await writeManifest(projectRoot, manifest)
67
+
68
+ console.log(`\n ✓ Updated ${results.copied.length} framework files`)
69
+ if (results.created.length > 0) {
70
+ console.log(` + Created ${results.created.length} new files`)
71
+ }
72
+ console.log()
73
+
74
+ closePrompts()
75
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'astro/config';
2
+
3
+ export default defineConfig({
4
+ site: 'https://www.opencastle.dev',
5
+ base: process.env.DASHBOARD_BASE || '/',
6
+ });