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
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
|
+
}
|
package/src/cli/types.ts
ADDED
|
@@ -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
|
+
}
|