opencastle 0.19.0 → 0.21.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/README.md +18 -9
- package/dist/cli/adapters/claude-code.d.ts +2 -2
- package/dist/cli/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/adapters/claude-code.js +2 -2
- package/dist/cli/adapters/claude-code.js.map +1 -1
- package/dist/cli/adapters/cursor.d.ts +2 -1
- package/dist/cli/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/adapters/cursor.js +13 -12
- package/dist/cli/adapters/cursor.js.map +1 -1
- package/dist/cli/adapters/opencode.d.ts +2 -2
- package/dist/cli/adapters/opencode.d.ts.map +1 -1
- package/dist/cli/adapters/opencode.js +2 -2
- package/dist/cli/adapters/opencode.js.map +1 -1
- package/dist/cli/adapters/single-file-base.d.ts.map +1 -1
- package/dist/cli/adapters/single-file-base.js +19 -13
- package/dist/cli/adapters/single-file-base.js.map +1 -1
- package/dist/cli/adapters/vscode.d.ts +3 -2
- package/dist/cli/adapters/vscode.d.ts.map +1 -1
- package/dist/cli/adapters/vscode.js +14 -16
- package/dist/cli/adapters/vscode.js.map +1 -1
- package/dist/cli/dashboard.js +1 -1
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/doctor.d.ts +12 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +90 -100
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/doctor.test.d.ts +2 -0
- package/dist/cli/doctor.test.d.ts.map +1 -0
- package/dist/cli/doctor.test.js +213 -0
- package/dist/cli/doctor.test.js.map +1 -0
- package/dist/cli/eject.js +2 -2
- package/dist/cli/eject.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +13 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/init.test.js +27 -15
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/lesson.js +5 -5
- package/dist/cli/lesson.js.map +1 -1
- package/dist/cli/log.d.ts +1 -1
- package/dist/cli/log.d.ts.map +1 -1
- package/dist/cli/log.js +5 -5
- package/dist/cli/log.js.map +1 -1
- package/dist/cli/manifest.d.ts +4 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +16 -5
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/stack-config.d.ts.map +1 -1
- package/dist/cli/stack-config.js +2 -14
- package/dist/cli/stack-config.js.map +1 -1
- package/dist/cli/types.d.ts +14 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +87 -34
- package/dist/cli/update.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/adapters/claude-code.ts +2 -2
- package/src/cli/adapters/cursor.ts +14 -13
- package/src/cli/adapters/opencode.ts +2 -2
- package/src/cli/adapters/single-file-base.ts +19 -14
- package/src/cli/adapters/vscode.ts +16 -17
- package/src/cli/dashboard.ts +1 -1
- package/src/cli/doctor.test.ts +245 -0
- package/src/cli/doctor.ts +101 -104
- package/src/cli/eject.ts +2 -2
- package/src/cli/init.test.ts +28 -15
- package/src/cli/init.ts +14 -4
- package/src/cli/lesson.ts +5 -5
- package/src/cli/log.ts +5 -5
- package/src/cli/manifest.ts +18 -5
- package/src/cli/stack-config.ts +2 -14
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +95 -36
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/scripts/generate-seed-data.ts +4 -4
- package/src/dashboard/seed-data/delegations.ndjson +15 -15
- package/src/dashboard/seed-data/events.ndjson +15 -15
- package/src/orchestrator/agent-workflows/README.md +1 -1
- package/src/orchestrator/agent-workflows/bug-fix.md +4 -4
- package/src/orchestrator/agent-workflows/data-pipeline.md +1 -1
- package/src/orchestrator/agent-workflows/database-migration.md +4 -4
- package/src/orchestrator/agent-workflows/feature-implementation.md +3 -3
- package/src/orchestrator/agent-workflows/performance-optimization.md +1 -1
- package/src/orchestrator/agent-workflows/refactoring.md +1 -1
- package/src/orchestrator/agent-workflows/schema-changes.md +2 -2
- package/src/orchestrator/agent-workflows/security-audit.md +4 -4
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +1 -1
- package/src/orchestrator/agents/api-designer.agent.md +2 -2
- package/src/orchestrator/agents/architect.agent.md +2 -2
- package/src/orchestrator/agents/content-engineer.agent.md +2 -2
- package/src/orchestrator/agents/copywriter.agent.md +2 -2
- package/src/orchestrator/agents/data-expert.agent.md +2 -2
- package/src/orchestrator/agents/database-engineer.agent.md +2 -2
- package/src/orchestrator/agents/developer.agent.md +2 -2
- package/src/orchestrator/agents/devops-expert.agent.md +2 -2
- package/src/orchestrator/agents/documentation-writer.agent.md +2 -2
- package/src/orchestrator/agents/performance-expert.agent.md +2 -2
- package/src/orchestrator/agents/release-manager.agent.md +2 -2
- package/src/orchestrator/agents/researcher.agent.md +4 -4
- package/src/orchestrator/agents/reviewer.agent.md +1 -1
- package/src/orchestrator/agents/security-expert.agent.md +2 -2
- package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
- package/src/orchestrator/agents/session-guard.agent.md +10 -10
- package/src/orchestrator/agents/team-lead.agent.md +8 -3
- package/src/orchestrator/agents/testing-expert.agent.md +2 -2
- package/src/orchestrator/agents/ui-ux-expert.agent.md +2 -2
- package/src/orchestrator/copilot-instructions.md +1 -1
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +11 -11
- package/src/orchestrator/customizations/DISPUTES.md +2 -2
- package/src/orchestrator/customizations/README.md +1 -1
- package/src/orchestrator/customizations/logs/README.md +1 -1
- package/src/orchestrator/customizations/project/docs-structure.md +6 -6
- package/src/orchestrator/instructions/ai-optimization.instructions.md +1 -1
- package/src/orchestrator/instructions/general.instructions.md +6 -6
- package/src/orchestrator/plugins/astro/SKILL.md +1 -1
- package/src/orchestrator/plugins/chrome-devtools/SKILL.md +4 -4
- package/src/orchestrator/plugins/contentful/SKILL.md +2 -2
- package/src/orchestrator/plugins/convex/SKILL.md +2 -2
- package/src/orchestrator/plugins/cypress/SKILL.md +2 -2
- package/src/orchestrator/plugins/figma/SKILL.md +1 -1
- package/src/orchestrator/plugins/jira/SKILL.md +3 -3
- package/src/orchestrator/plugins/linear/SKILL.md +2 -2
- package/src/orchestrator/plugins/netlify/SKILL.md +2 -2
- package/src/orchestrator/plugins/nextjs/SKILL.md +1 -1
- package/src/orchestrator/plugins/nx/SKILL.md +1 -1
- package/src/orchestrator/plugins/playwright/SKILL.md +2 -2
- package/src/orchestrator/plugins/prisma/SKILL.md +2 -2
- package/src/orchestrator/plugins/resend/SKILL.md +1 -1
- package/src/orchestrator/plugins/sanity/SKILL.md +2 -2
- package/src/orchestrator/plugins/slack/SKILL.md +2 -2
- package/src/orchestrator/plugins/strapi/SKILL.md +2 -2
- package/src/orchestrator/plugins/supabase/SKILL.md +2 -2
- package/src/orchestrator/plugins/teams/SKILL.md +1 -1
- package/src/orchestrator/plugins/turborepo/SKILL.md +1 -1
- package/src/orchestrator/plugins/vercel/SKILL.md +2 -2
- package/src/orchestrator/plugins/vitest/SKILL.md +2 -2
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +8 -8
- package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
- package/src/orchestrator/prompts/bug-fix.prompt.md +4 -4
- package/src/orchestrator/prompts/create-skill.prompt.md +3 -3
- package/src/orchestrator/prompts/generate-convoy.prompt.md +1 -1
- package/src/orchestrator/prompts/implement-feature.prompt.md +11 -11
- package/src/orchestrator/prompts/quick-refinement.prompt.md +3 -3
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +1 -1
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +9 -9
- package/src/orchestrator/skills/agent-memory/SKILL.md +5 -5
- package/src/orchestrator/skills/api-patterns/SKILL.md +2 -2
- package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
- package/src/orchestrator/skills/context-map/SKILL.md +4 -4
- package/src/orchestrator/skills/data-engineering/SKILL.md +2 -2
- package/src/orchestrator/skills/decomposition/SKILL.md +1 -1
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
- package/src/orchestrator/skills/documentation-standards/SKILL.md +2 -2
- package/src/orchestrator/skills/fast-review/SKILL.md +2 -2
- package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
- package/src/orchestrator/skills/git-workflow/SKILL.md +2 -2
- package/src/orchestrator/skills/memory-merger/SKILL.md +3 -3
- package/src/orchestrator/skills/observability-logging/SKILL.md +10 -10
- package/src/orchestrator/skills/orchestration-protocols/SKILL.md +1 -1
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
- package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
- package/src/orchestrator/skills/react-development/SKILL.md +1 -1
- package/src/orchestrator/skills/security-hardening/SKILL.md +1 -1
- package/src/orchestrator/skills/self-improvement/SKILL.md +2 -2
- package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +5 -5
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +6 -6
- package/src/orchestrator/skills/testing-workflow/SKILL.md +3 -3
- package/src/orchestrator/skills/validation-gates/SKILL.md +1 -1
|
@@ -3,8 +3,8 @@ import { mkdir, readFile, writeFile, copyFile } from 'node:fs/promises'
|
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
4
|
import { copyDir, getOrchestratorRoot, removeDirIfExists, getPluginsRoot, getPluginSkillEntries } from '../copy.js'
|
|
5
5
|
import { scaffoldMcpConfig } from '../mcp.js'
|
|
6
|
-
import { getExcludedSkills, getExcludedAgents,
|
|
7
|
-
import type { CopyResults, CopyDirOptions, ManagedPaths, RepoInfo, StackConfig } from '../types.js'
|
|
6
|
+
import { getExcludedSkills, getExcludedAgents, getIncludedPluginIds, getAgentTransform } from '../stack-config.js'
|
|
7
|
+
import type { CopyResults, CopyDirOptions, DoctorCheck, ManagedPaths, RepoInfo, StackConfig } from '../types.js'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* VS Code / GitHub Copilot adapter.
|
|
@@ -17,7 +17,7 @@ import type { CopyResults, CopyDirOptions, ManagedPaths, RepoInfo, StackConfig }
|
|
|
17
17
|
* skills/ → .github/skills/
|
|
18
18
|
* agent-workflows/ → .github/agent-workflows/
|
|
19
19
|
* prompts/ → .github/prompts/
|
|
20
|
-
* customizations/ → .
|
|
20
|
+
* customizations/ → .opencastle/ (scaffolded once)
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
export const IDE_ID = 'vscode'
|
|
@@ -33,7 +33,7 @@ const FRAMEWORK_DIRS = [
|
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
/** Directories scaffolded once and never overwritten. */
|
|
36
|
-
const CUSTOMIZABLE_DIRS = [
|
|
36
|
+
const CUSTOMIZABLE_DIRS: string[] = []
|
|
37
37
|
|
|
38
38
|
export async function install(
|
|
39
39
|
pkgRoot: string,
|
|
@@ -99,18 +99,6 @@ export async function install(
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
// Customization templates (scaffold once)
|
|
103
|
-
const custTransform = stack ? getCustomizationsTransform(stack) : undefined
|
|
104
|
-
for (const dir of CUSTOMIZABLE_DIRS) {
|
|
105
|
-
const srcDir = resolve(srcRoot, dir)
|
|
106
|
-
if (!existsSync(srcDir)) continue
|
|
107
|
-
const destDir = resolve(destRoot, dir)
|
|
108
|
-
const sub = await copyDir(srcDir, destDir, { transform: custTransform })
|
|
109
|
-
results.copied.push(...sub.copied)
|
|
110
|
-
results.skipped.push(...sub.skipped)
|
|
111
|
-
results.created.push(...sub.created)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
102
|
// MCP server config → .vscode/mcp.json (scaffold once)
|
|
115
103
|
const mcpResult = await scaffoldMcpConfig(
|
|
116
104
|
projectRoot,
|
|
@@ -195,8 +183,19 @@ export function getManagedPaths(): ManagedPaths {
|
|
|
195
183
|
...FRAMEWORK_DIRS.map((d) => `.github/${d}/`),
|
|
196
184
|
],
|
|
197
185
|
customizable: [
|
|
198
|
-
|
|
186
|
+
'.opencastle/',
|
|
199
187
|
'.vscode/mcp.json',
|
|
200
188
|
],
|
|
201
189
|
}
|
|
202
190
|
}
|
|
191
|
+
|
|
192
|
+
export function getDoctorChecks(): DoctorCheck[] {
|
|
193
|
+
return [
|
|
194
|
+
{ label: 'Copilot instructions', path: '.github/copilot-instructions.md', type: 'file' },
|
|
195
|
+
{ label: 'Instruction files', path: '.github/instructions/', type: 'dir', countContents: true, countFilter: '.md' },
|
|
196
|
+
{ label: 'Agent definitions', path: '.github/agents/', type: 'dir', countContents: true, countFilter: '.agent.md' },
|
|
197
|
+
{ label: 'Skills directory', path: '.github/skills/', type: 'dir', countContents: true },
|
|
198
|
+
{ label: 'Agent workflows', path: '.github/agent-workflows/', type: 'dir', countContents: true },
|
|
199
|
+
{ label: 'Prompts directory', path: '.github/prompts/', type: 'dir', countContents: true },
|
|
200
|
+
]
|
|
201
|
+
}
|
package/src/cli/dashboard.ts
CHANGED
|
@@ -260,7 +260,7 @@ export default async function dashboard({
|
|
|
260
260
|
' \u{1F4C2} Showing demo data (use without --seed to read project logs)'
|
|
261
261
|
)
|
|
262
262
|
} else if (hasLogs) {
|
|
263
|
-
console.log(' \u{1F4C2} Reading logs from .
|
|
263
|
+
console.log(' \u{1F4C2} Reading logs from .opencastle/logs/')
|
|
264
264
|
} else {
|
|
265
265
|
console.log(
|
|
266
266
|
' \u{1F4A1} No agent logs found. Run agents with OpenCastle to generate data, or use --seed for demo data.'
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { mkdtempSync, realpathSync } from 'node:fs'
|
|
3
|
+
import { mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
import { runDoctorCheck, checkMcpFromPaths } from './doctor.js'
|
|
7
|
+
import { IDE_ADAPTERS } from './adapters/index.js'
|
|
8
|
+
import type { DoctorCheck } from './types.js'
|
|
9
|
+
|
|
10
|
+
// ── Test helper ───────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
function makeTempDir(): string {
|
|
13
|
+
return realpathSync(mkdtempSync(join(tmpdir(), 'doctor-test-')))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ── runDoctorCheck ────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
describe('runDoctorCheck — file checks', () => {
|
|
19
|
+
let tmpDir: string
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tmpDir = makeTempDir()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('passes when file exists', async () => {
|
|
30
|
+
writeFileSync(join(tmpDir, 'CLAUDE.md'), '# Instructions')
|
|
31
|
+
const check: DoctorCheck = { label: 'Root file', path: 'CLAUDE.md', type: 'file' }
|
|
32
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
33
|
+
expect(result.ok).toBe(true)
|
|
34
|
+
expect(result.label).toBe('Root file')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('fails when file does not exist', async () => {
|
|
38
|
+
const check: DoctorCheck = { label: 'Root file', path: 'CLAUDE.md', type: 'file' }
|
|
39
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
40
|
+
expect(result.ok).toBe(false)
|
|
41
|
+
expect(result.detail).toContain('CLAUDE.md not found')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('passes for nested path when file exists', async () => {
|
|
45
|
+
mkdirSync(join(tmpDir, '.github'), { recursive: true })
|
|
46
|
+
writeFileSync(join(tmpDir, '.github', 'copilot-instructions.md'), '# Instructions')
|
|
47
|
+
const check: DoctorCheck = { label: 'Copilot instructions', path: '.github/copilot-instructions.md', type: 'file' }
|
|
48
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
49
|
+
expect(result.ok).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('runDoctorCheck — dir checks', () => {
|
|
54
|
+
let tmpDir: string
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
tmpDir = makeTempDir()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('fails when directory does not exist', async () => {
|
|
65
|
+
const check: DoctorCheck = { label: 'Skills', path: '.claude/skills/', type: 'dir' }
|
|
66
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
67
|
+
expect(result.ok).toBe(false)
|
|
68
|
+
expect(result.detail).toContain('.claude/skills/ not found')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('passes when directory exists (no countContents)', async () => {
|
|
72
|
+
mkdirSync(join(tmpDir, '.claude', 'skills'), { recursive: true })
|
|
73
|
+
const check: DoctorCheck = { label: 'Skills', path: '.claude/skills/', type: 'dir' }
|
|
74
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
75
|
+
expect(result.ok).toBe(true)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('fails when dir exists but no files match countFilter', async () => {
|
|
79
|
+
mkdirSync(join(tmpDir, '.github', 'agents'), { recursive: true })
|
|
80
|
+
writeFileSync(join(tmpDir, '.github', 'agents', 'notes.txt'), 'not an agent')
|
|
81
|
+
const check: DoctorCheck = {
|
|
82
|
+
label: 'Agent definitions',
|
|
83
|
+
path: '.github/agents/',
|
|
84
|
+
type: 'dir',
|
|
85
|
+
countContents: true,
|
|
86
|
+
countFilter: '.agent.md',
|
|
87
|
+
}
|
|
88
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
89
|
+
expect(result.ok).toBe(false)
|
|
90
|
+
expect(result.detail).toContain('No files found')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('passes and reports count when files match countFilter', async () => {
|
|
94
|
+
mkdirSync(join(tmpDir, '.github', 'agents'), { recursive: true })
|
|
95
|
+
writeFileSync(join(tmpDir, '.github', 'agents', 'developer.agent.md'), '# Developer')
|
|
96
|
+
writeFileSync(join(tmpDir, '.github', 'agents', 'reviewer.agent.md'), '# Reviewer')
|
|
97
|
+
const check: DoctorCheck = {
|
|
98
|
+
label: 'Agent definitions',
|
|
99
|
+
path: '.github/agents/',
|
|
100
|
+
type: 'dir',
|
|
101
|
+
countContents: true,
|
|
102
|
+
countFilter: '.agent.md',
|
|
103
|
+
}
|
|
104
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
105
|
+
expect(result.ok).toBe(true)
|
|
106
|
+
expect(result.detail).toBe('2 file(s)')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('counts all files when no countFilter specified', async () => {
|
|
110
|
+
mkdirSync(join(tmpDir, '.claude', 'skills'), { recursive: true })
|
|
111
|
+
writeFileSync(join(tmpDir, '.claude', 'skills', 'git-workflow.md'), '# Skill')
|
|
112
|
+
writeFileSync(join(tmpDir, '.claude', 'skills', 'testing.md'), '# Skill')
|
|
113
|
+
writeFileSync(join(tmpDir, '.claude', 'skills', 'unused.txt'), 'other')
|
|
114
|
+
const check: DoctorCheck = {
|
|
115
|
+
label: 'Skills',
|
|
116
|
+
path: '.claude/skills/',
|
|
117
|
+
type: 'dir',
|
|
118
|
+
countContents: true,
|
|
119
|
+
}
|
|
120
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
121
|
+
expect(result.ok).toBe(true)
|
|
122
|
+
expect(result.detail).toBe('3 file(s)')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('fails when directory exists but is empty (countContents)', async () => {
|
|
126
|
+
mkdirSync(join(tmpDir, '.github', 'instructions'), { recursive: true })
|
|
127
|
+
const check: DoctorCheck = {
|
|
128
|
+
label: 'Instruction files',
|
|
129
|
+
path: '.github/instructions/',
|
|
130
|
+
type: 'dir',
|
|
131
|
+
countContents: true,
|
|
132
|
+
countFilter: '.md',
|
|
133
|
+
}
|
|
134
|
+
const result = await runDoctorCheck(tmpDir, check)
|
|
135
|
+
expect(result.ok).toBe(false)
|
|
136
|
+
expect(result.detail).toContain('No files found')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// ── checkMcpFromPaths ─────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
describe('checkMcpFromPaths', () => {
|
|
143
|
+
let tmpDir: string
|
|
144
|
+
|
|
145
|
+
beforeEach(() => {
|
|
146
|
+
tmpDir = makeTempDir()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
afterEach(() => {
|
|
150
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('returns ok when mcp config exists', () => {
|
|
154
|
+
mkdirSync(join(tmpDir, '.vscode'), { recursive: true })
|
|
155
|
+
writeFileSync(join(tmpDir, '.vscode', 'mcp.json'), '{}')
|
|
156
|
+
const result = checkMcpFromPaths(tmpDir, ['.vscode/mcp.json'])
|
|
157
|
+
expect(result.ok).toBe(true)
|
|
158
|
+
expect(result.warning).toBeFalsy()
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('returns warning when mcp config does not exist', () => {
|
|
162
|
+
const result = checkMcpFromPaths(tmpDir, ['.vscode/mcp.json'])
|
|
163
|
+
expect(result.ok).toBe(true)
|
|
164
|
+
expect(result.warning).toBe(true)
|
|
165
|
+
expect(result.detail).toContain('MCP tools unavailable')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('returns ok with no warning when paths is empty', () => {
|
|
169
|
+
const result = checkMcpFromPaths(tmpDir, [])
|
|
170
|
+
expect(result.ok).toBe(true)
|
|
171
|
+
expect(result.warning).toBeFalsy()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// ── adapter getDoctorChecks ───────────────────────────────────
|
|
176
|
+
|
|
177
|
+
describe('vscode adapter getDoctorChecks', () => {
|
|
178
|
+
it('returns expected checks', async () => {
|
|
179
|
+
const adapter = await IDE_ADAPTERS['vscode']()
|
|
180
|
+
const checks = adapter.getDoctorChecks()
|
|
181
|
+
expect(checks.length).toBeGreaterThan(0)
|
|
182
|
+
expect(checks.find((c) => c.path === '.github/copilot-instructions.md')?.type).toBe('file')
|
|
183
|
+
expect(checks.find((c) => c.path === '.github/agents/')?.countFilter).toBe('.agent.md')
|
|
184
|
+
expect(checks.find((c) => c.path === '.github/instructions/')?.countFilter).toBe('.md')
|
|
185
|
+
expect(checks.find((c) => c.path === '.github/skills/')).toBeDefined()
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('cursor adapter getDoctorChecks', () => {
|
|
190
|
+
it('returns expected checks', async () => {
|
|
191
|
+
const adapter = await IDE_ADAPTERS['cursor']()
|
|
192
|
+
const checks = adapter.getDoctorChecks()
|
|
193
|
+
expect(checks.length).toBeGreaterThan(0)
|
|
194
|
+
expect(checks.find((c) => c.path === '.cursorrules')?.type).toBe('file')
|
|
195
|
+
expect(checks.find((c) => c.path === '.cursor/rules/agents/')?.countFilter).toBe('.mdc')
|
|
196
|
+
expect(checks.find((c) => c.path === '.cursor/rules/skills/')?.countFilter).toBe('.mdc')
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe('claude-code adapter getDoctorChecks', () => {
|
|
201
|
+
it('returns expected checks', async () => {
|
|
202
|
+
const adapter = await IDE_ADAPTERS['claude-code']()
|
|
203
|
+
const checks = adapter.getDoctorChecks()
|
|
204
|
+
expect(checks.length).toBeGreaterThan(0)
|
|
205
|
+
expect(checks.find((c) => c.path === 'CLAUDE.md')?.type).toBe('file')
|
|
206
|
+
expect(checks.find((c) => c.path === '.claude/agents/')).toBeDefined()
|
|
207
|
+
expect(checks.find((c) => c.path === '.claude/skills/')).toBeDefined()
|
|
208
|
+
// claude-code: prompts and workflows share 'commands' dir
|
|
209
|
+
expect(checks.find((c) => c.path === '.claude/commands/')).toBeDefined()
|
|
210
|
+
expect(checks.find((c) => c.label === 'Commands directory')).toBeDefined()
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('opencode adapter getDoctorChecks', () => {
|
|
215
|
+
it('returns expected checks', async () => {
|
|
216
|
+
const adapter = await IDE_ADAPTERS['opencode']()
|
|
217
|
+
const checks = adapter.getDoctorChecks()
|
|
218
|
+
expect(checks.length).toBeGreaterThan(0)
|
|
219
|
+
expect(checks.find((c) => c.path === 'AGENTS.md')?.type).toBe('file')
|
|
220
|
+
expect(checks.find((c) => c.path === '.opencode/agents/')).toBeDefined()
|
|
221
|
+
expect(checks.find((c) => c.path === '.opencode/skills/')).toBeDefined()
|
|
222
|
+
// opencode: separate prompts and workflows dirs
|
|
223
|
+
expect(checks.find((c) => c.path === '.opencode/prompts/')).toBeDefined()
|
|
224
|
+
expect(checks.find((c) => c.path === '.opencode/workflows/')).toBeDefined()
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
describe('all adapters satisfy getDoctorChecks interface', () => {
|
|
229
|
+
const ideIds = ['vscode', 'cursor', 'claude-code', 'opencode']
|
|
230
|
+
|
|
231
|
+
for (const ide of ideIds) {
|
|
232
|
+
it(`${ide}: every check has label, path, and type`, async () => {
|
|
233
|
+
const adapter = await IDE_ADAPTERS[ide]()
|
|
234
|
+
const checks = adapter.getDoctorChecks()
|
|
235
|
+
expect(Array.isArray(checks)).toBe(true)
|
|
236
|
+
for (const c of checks) {
|
|
237
|
+
expect(typeof c.label).toBe('string')
|
|
238
|
+
expect(c.label.length).toBeGreaterThan(0)
|
|
239
|
+
expect(typeof c.path).toBe('string')
|
|
240
|
+
expect(c.path.length).toBeGreaterThan(0)
|
|
241
|
+
expect(['file', 'dir']).toContain(c.type)
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
})
|
package/src/cli/doctor.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { readdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { readManifest } from './manifest.js';
|
|
5
5
|
import { getRequiredMcpEnvVars } from './stack-config.js';
|
|
6
|
-
import
|
|
6
|
+
import { IDE_ADAPTERS } from './adapters/index.js';
|
|
7
|
+
import type { CliContext, DoctorCheck, IdeChoice, Manifest } from './types.js';
|
|
8
|
+
import { IDE_LABELS } from './types.js';
|
|
7
9
|
|
|
8
10
|
// ── Styled output helpers ─────────────────────────────────────
|
|
9
11
|
|
|
@@ -24,25 +26,26 @@ interface CheckResult {
|
|
|
24
26
|
|
|
25
27
|
function checkManifest(manifest: Manifest | null): CheckResult {
|
|
26
28
|
if (!manifest) {
|
|
27
|
-
return { ok: false, label: 'OpenCastle manifest (.opencastle.json)', detail: 'Not found. Run "npx opencastle init" first.' };
|
|
29
|
+
return { ok: false, label: 'OpenCastle manifest (.opencastle/manifest.json)', detail: 'Not found. Run "npx opencastle init" first.' };
|
|
28
30
|
}
|
|
29
|
-
return { ok: true, label: 'OpenCastle manifest (.opencastle.json)', detail: `v${manifest.version}, IDE: ${manifest.ides?.join(', ') ?? manifest.ide}` };
|
|
31
|
+
return { ok: true, label: 'OpenCastle manifest (.opencastle/manifest.json)', detail: `v${manifest.version}, IDE: ${manifest.ides?.join(', ') ?? manifest.ide}` };
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
async function checkCustomizations(projectRoot: string): Promise<CheckResult> {
|
|
33
|
-
const dir = resolve(projectRoot, '.
|
|
35
|
+
const dir = resolve(projectRoot, '.opencastle');
|
|
34
36
|
if (!existsSync(dir)) {
|
|
35
|
-
return { ok: false, label: 'Customizations directory', detail: '.
|
|
37
|
+
return { ok: false, label: 'Customizations directory', detail: '.opencastle/ not found' };
|
|
36
38
|
}
|
|
37
39
|
const files = await readdir(dir).catch(() => []);
|
|
38
40
|
return { ok: true, label: 'Customizations directory', detail: `${files.length} entries` };
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
async function checkSkillMatrix(projectRoot: string): Promise<CheckResult> {
|
|
42
|
-
const path = resolve(projectRoot, '.
|
|
44
|
+
const path = resolve(projectRoot, '.opencastle', 'agents', 'skill-matrix.json');
|
|
43
45
|
if (!existsSync(path)) {
|
|
44
|
-
return { ok: false, label: 'Skill matrix', detail: 'File not found at .
|
|
46
|
+
return { ok: false, label: 'Skill matrix', detail: 'File not found at .opencastle/agents/skill-matrix.json' };
|
|
45
47
|
}
|
|
48
|
+
const { readFile } = await import('node:fs/promises');
|
|
46
49
|
const content = await readFile(path, 'utf8');
|
|
47
50
|
try {
|
|
48
51
|
const data = JSON.parse(content);
|
|
@@ -59,50 +62,14 @@ async function checkSkillMatrix(projectRoot: string): Promise<CheckResult> {
|
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
async function checkInstructions(projectRoot: string): Promise<CheckResult> {
|
|
63
|
-
const dir = resolve(projectRoot, '.github', 'instructions');
|
|
64
|
-
if (!existsSync(dir)) {
|
|
65
|
-
return { ok: false, label: 'Instruction files', detail: '.github/instructions/ not found' };
|
|
66
|
-
}
|
|
67
|
-
const files = await readdir(dir).catch(() => []);
|
|
68
|
-
const mdFiles = files.filter((f) => f.endsWith('.md'));
|
|
69
|
-
if (mdFiles.length === 0) {
|
|
70
|
-
return { ok: false, label: 'Instruction files', detail: 'No .md files in .github/instructions/' };
|
|
71
|
-
}
|
|
72
|
-
return { ok: true, label: 'Instruction files', detail: `${mdFiles.length} instruction files` };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function checkAgents(projectRoot: string): Promise<CheckResult> {
|
|
76
|
-
const dir = resolve(projectRoot, '.github', 'agents');
|
|
77
|
-
if (!existsSync(dir)) {
|
|
78
|
-
return { ok: false, label: 'Agent definitions', detail: '.github/agents/ directory not found' };
|
|
79
|
-
}
|
|
80
|
-
const files = await readdir(dir).catch(() => []);
|
|
81
|
-
const agentFiles = files.filter((f) => f.endsWith('.agent.md'));
|
|
82
|
-
if (agentFiles.length === 0) {
|
|
83
|
-
return { ok: false, label: 'Agent definitions', detail: 'No .agent.md files found' };
|
|
84
|
-
}
|
|
85
|
-
return { ok: true, label: 'Agent definitions', detail: `${agentFiles.length} agents` };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function checkSkills(projectRoot: string): Promise<CheckResult> {
|
|
89
|
-
const dir = resolve(projectRoot, '.github', 'skills');
|
|
90
|
-
if (!existsSync(dir)) {
|
|
91
|
-
return { ok: false, label: 'Skills directory', detail: '.github/skills/ not found' };
|
|
92
|
-
}
|
|
93
|
-
const entries = await readdir(dir).catch(() => []);
|
|
94
|
-
return { ok: true, label: 'Skills directory', detail: `${entries.length} skills` };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
65
|
async function checkLogs(projectRoot: string): Promise<CheckResult> {
|
|
98
|
-
const dir = resolve(projectRoot, '.
|
|
66
|
+
const dir = resolve(projectRoot, '.opencastle', 'logs');
|
|
99
67
|
if (!existsSync(dir)) {
|
|
100
68
|
return { ok: false, label: 'Observability logs', detail: 'logs/ directory not found — dashboard will be empty' };
|
|
101
69
|
}
|
|
102
70
|
const required = ['sessions.ndjson', 'delegations.ndjson', 'reviews.ndjson', 'panels.ndjson', 'disputes.ndjson'];
|
|
103
71
|
const missing = required.filter((f) => !existsSync(resolve(dir, f)));
|
|
104
72
|
if (missing.length > 0) {
|
|
105
|
-
// Auto-create missing log files — they're empty NDJSON stubs
|
|
106
73
|
for (const file of missing) {
|
|
107
74
|
await writeFile(resolve(dir, file), '', { flag: 'wx' }).catch(() => {/* already exists */});
|
|
108
75
|
}
|
|
@@ -141,62 +108,51 @@ async function checkDotEnv(projectRoot: string, manifest: Manifest | null): Prom
|
|
|
141
108
|
return { ok: true, label: '.env file', detail: 'Present' };
|
|
142
109
|
}
|
|
143
110
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
switch (ide as IdeChoice) {
|
|
154
|
-
case 'vscode':
|
|
155
|
-
configFile = '.github/copilot-instructions.md';
|
|
156
|
-
break;
|
|
157
|
-
case 'cursor':
|
|
158
|
-
configFile = '.cursor/rules/opencastle.mdc';
|
|
159
|
-
break;
|
|
160
|
-
case 'claude-code':
|
|
161
|
-
configFile = '.claude/settings.json';
|
|
162
|
-
break;
|
|
163
|
-
case 'opencode':
|
|
164
|
-
configFile = '.opencode/agents.md';
|
|
165
|
-
break;
|
|
166
|
-
default:
|
|
167
|
-
continue;
|
|
111
|
+
// ── Generic adapter-driven checks ────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/** Run a single DoctorCheck against the filesystem. */
|
|
114
|
+
export async function runDoctorCheck(projectRoot: string, check: DoctorCheck): Promise<CheckResult> {
|
|
115
|
+
const fullPath = resolve(projectRoot, check.path);
|
|
116
|
+
|
|
117
|
+
if (check.type === 'file') {
|
|
118
|
+
if (!existsSync(fullPath)) {
|
|
119
|
+
return { ok: false, label: check.label, detail: `${check.path} not found` };
|
|
168
120
|
}
|
|
169
|
-
|
|
121
|
+
return { ok: true, label: check.label };
|
|
170
122
|
}
|
|
171
123
|
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
return { ok: false, label:
|
|
124
|
+
// type === 'dir'
|
|
125
|
+
if (!existsSync(fullPath)) {
|
|
126
|
+
return { ok: false, label: check.label, detail: `${check.path} not found` };
|
|
175
127
|
}
|
|
176
|
-
return { ok: true, label: 'IDE configuration files', detail: `${checks.length} IDE(s) configured` };
|
|
177
|
-
}
|
|
178
128
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
cursor: '.cursor/mcp.json',
|
|
187
|
-
'claude-code': '.claude/mcp.json',
|
|
188
|
-
opencode: 'mcp.json',
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const found: string[] = [];
|
|
192
|
-
for (const ide of ides) {
|
|
193
|
-
const path = mcpPaths[ide as string];
|
|
194
|
-
if (path && existsSync(resolve(projectRoot, path))) {
|
|
195
|
-
found.push(ide as string);
|
|
129
|
+
if (check.countContents) {
|
|
130
|
+
const entries = await readdir(fullPath).catch(() => [] as string[]);
|
|
131
|
+
const filtered = check.countFilter
|
|
132
|
+
? entries.filter((e) => e.endsWith(check.countFilter!))
|
|
133
|
+
: entries;
|
|
134
|
+
if (filtered.length === 0) {
|
|
135
|
+
return { ok: false, label: check.label, detail: `No files found in ${check.path}` };
|
|
196
136
|
}
|
|
137
|
+
return { ok: true, label: check.label, detail: `${filtered.length} file(s)` };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { ok: true, label: check.label };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Check MCP config presence from the adapter's customizable paths. */
|
|
144
|
+
export function checkMcpFromPaths(projectRoot: string, mcpPaths: string[]): CheckResult {
|
|
145
|
+
if (mcpPaths.length === 0) {
|
|
146
|
+
return { ok: true, label: 'MCP configuration', detail: 'No MCP config path configured' };
|
|
197
147
|
}
|
|
198
|
-
|
|
199
|
-
|
|
148
|
+
const found = mcpPaths.filter((p) => existsSync(resolve(projectRoot, p)));
|
|
149
|
+
if (found.length === 0) {
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
label: 'MCP configuration',
|
|
153
|
+
detail: `No MCP config found (${mcpPaths.join(', ')}) — MCP tools unavailable`,
|
|
154
|
+
warning: true,
|
|
155
|
+
};
|
|
200
156
|
}
|
|
201
157
|
return { ok: true, label: 'MCP configuration', detail: `${found.length} MCP config(s)` };
|
|
202
158
|
}
|
|
@@ -211,30 +167,71 @@ export default async function doctor({ args: _args }: CliContext): Promise<void>
|
|
|
211
167
|
|
|
212
168
|
const manifest = await readManifest(projectRoot);
|
|
213
169
|
|
|
214
|
-
|
|
170
|
+
// Shared checks (not IDE-specific)
|
|
171
|
+
const sharedResults: CheckResult[] = [
|
|
215
172
|
checkManifest(manifest),
|
|
216
173
|
await checkCustomizations(projectRoot),
|
|
217
|
-
await checkInstructions(projectRoot),
|
|
218
|
-
await checkAgents(projectRoot),
|
|
219
|
-
await checkSkills(projectRoot),
|
|
220
174
|
await checkSkillMatrix(projectRoot),
|
|
221
175
|
await checkLogs(projectRoot),
|
|
222
|
-
await checkIdeConfigs(projectRoot, manifest),
|
|
223
|
-
await checkMcpConfig(projectRoot, manifest),
|
|
224
176
|
checkMcpEnvVars(manifest),
|
|
225
177
|
await checkDotEnv(projectRoot, manifest),
|
|
226
178
|
];
|
|
227
179
|
|
|
228
|
-
|
|
180
|
+
// IDE-specific checks derived from each adapter
|
|
181
|
+
type IdeGroup = { label: string; results: CheckResult[] };
|
|
182
|
+
const ideGroups: IdeGroup[] = [];
|
|
183
|
+
|
|
184
|
+
if (manifest) {
|
|
185
|
+
const ides = manifest.ides ?? (manifest.ide ? [manifest.ide] : []);
|
|
186
|
+
for (const ide of ides) {
|
|
187
|
+
const loader = IDE_ADAPTERS[ide];
|
|
188
|
+
if (!loader) continue;
|
|
189
|
+
const adapter = await loader();
|
|
190
|
+
const doctorChecks = adapter.getDoctorChecks();
|
|
191
|
+
const managedPaths = adapter.getManagedPaths();
|
|
192
|
+
|
|
193
|
+
const checkResults = await Promise.all(
|
|
194
|
+
doctorChecks.map((c) => runDoctorCheck(projectRoot, c))
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// MCP config check — non-directory entries in the adapter's customizable paths
|
|
198
|
+
const mcpPaths = managedPaths.customizable.filter((p) => !p.endsWith('/'));
|
|
199
|
+
checkResults.push(checkMcpFromPaths(projectRoot, mcpPaths));
|
|
200
|
+
|
|
201
|
+
ideGroups.push({
|
|
202
|
+
label: IDE_LABELS[ide as IdeChoice] ?? ide,
|
|
203
|
+
results: checkResults,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Print shared results
|
|
209
|
+
for (const r of sharedResults) {
|
|
229
210
|
const icon = r.ok ? (r.warning ? WARN : PASS) : FAIL;
|
|
230
211
|
const detail = r.detail ? ` ${DIM(r.detail)}` : '';
|
|
231
212
|
console.log(` ${icon} ${r.label}${detail}`);
|
|
232
213
|
}
|
|
233
214
|
|
|
234
|
-
|
|
235
|
-
|
|
215
|
+
// Print IDE-specific results, grouped with a header when multiple IDEs are configured
|
|
216
|
+
if (ideGroups.length > 0) {
|
|
217
|
+
console.log();
|
|
218
|
+
for (const group of ideGroups) {
|
|
219
|
+
if (ideGroups.length > 1) {
|
|
220
|
+
console.log(` ${BOLD(`[${group.label}]`)}`);
|
|
221
|
+
}
|
|
222
|
+
for (const r of group.results) {
|
|
223
|
+
const icon = r.ok ? (r.warning ? WARN : PASS) : FAIL;
|
|
224
|
+
const detail = r.detail ? ` ${DIM(r.detail)}` : '';
|
|
225
|
+
console.log(` ${icon} ${r.label}${detail}`);
|
|
226
|
+
}
|
|
227
|
+
if (ideGroups.length > 1) console.log();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const allResults = [...sharedResults, ...ideGroups.flatMap((g) => g.results)];
|
|
232
|
+
const failures = allResults.filter((r) => !r.ok);
|
|
233
|
+
const warnings = allResults.filter((r) => r.ok && r.warning);
|
|
236
234
|
|
|
237
|
-
console.log();
|
|
238
235
|
if (failures.length > 0) {
|
|
239
236
|
console.log(` ${BOLD(`${failures.length} issue(s) found.`)} Run "npx opencastle init" to fix.\n`);
|
|
240
237
|
process.exit(1);
|
package/src/cli/eject.ts
CHANGED
|
@@ -19,7 +19,7 @@ export default async function eject({
|
|
|
19
19
|
|
|
20
20
|
console.log(`\n 🏰 OpenCastle eject\n`)
|
|
21
21
|
console.log(' This will:')
|
|
22
|
-
console.log(' • Remove .opencastle.json (manifest)')
|
|
22
|
+
console.log(' • Remove .opencastle/manifest.json (manifest)')
|
|
23
23
|
console.log(' • Keep ALL generated files as standalone')
|
|
24
24
|
console.log(
|
|
25
25
|
' • You can safely uninstall the opencastle package after this\n'
|
|
@@ -36,7 +36,7 @@ export default async function eject({
|
|
|
36
36
|
return
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
await unlink(resolve(projectRoot, '.opencastle.json'))
|
|
39
|
+
await unlink(resolve(projectRoot, '.opencastle', 'manifest.json'))
|
|
40
40
|
|
|
41
41
|
console.log('\n ✓ Ejected. Files are now standalone.')
|
|
42
42
|
console.log(' You can uninstall: npm uninstall opencastle\n')
|