opencastle 0.20.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.
Files changed (146) hide show
  1. package/dist/cli/adapters/cursor.d.ts.map +1 -1
  2. package/dist/cli/adapters/cursor.js +3 -12
  3. package/dist/cli/adapters/cursor.js.map +1 -1
  4. package/dist/cli/adapters/single-file-base.d.ts.map +1 -1
  5. package/dist/cli/adapters/single-file-base.js +3 -12
  6. package/dist/cli/adapters/single-file-base.js.map +1 -1
  7. package/dist/cli/adapters/vscode.d.ts +1 -1
  8. package/dist/cli/adapters/vscode.d.ts.map +1 -1
  9. package/dist/cli/adapters/vscode.js +4 -16
  10. package/dist/cli/adapters/vscode.js.map +1 -1
  11. package/dist/cli/dashboard.js +1 -1
  12. package/dist/cli/dashboard.js.map +1 -1
  13. package/dist/cli/doctor.js +7 -7
  14. package/dist/cli/doctor.js.map +1 -1
  15. package/dist/cli/eject.js +2 -2
  16. package/dist/cli/eject.js.map +1 -1
  17. package/dist/cli/init.d.ts.map +1 -1
  18. package/dist/cli/init.js +13 -4
  19. package/dist/cli/init.js.map +1 -1
  20. package/dist/cli/init.test.js +27 -15
  21. package/dist/cli/init.test.js.map +1 -1
  22. package/dist/cli/lesson.js +5 -5
  23. package/dist/cli/lesson.js.map +1 -1
  24. package/dist/cli/log.d.ts +1 -1
  25. package/dist/cli/log.d.ts.map +1 -1
  26. package/dist/cli/log.js +5 -5
  27. package/dist/cli/log.js.map +1 -1
  28. package/dist/cli/manifest.d.ts +4 -1
  29. package/dist/cli/manifest.d.ts.map +1 -1
  30. package/dist/cli/manifest.js +16 -5
  31. package/dist/cli/manifest.js.map +1 -1
  32. package/dist/cli/stack-config.d.ts.map +1 -1
  33. package/dist/cli/stack-config.js +2 -14
  34. package/dist/cli/stack-config.js.map +1 -1
  35. package/dist/cli/update.d.ts.map +1 -1
  36. package/dist/cli/update.js +87 -34
  37. package/dist/cli/update.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/cli/adapters/cursor.ts +2 -12
  40. package/src/cli/adapters/single-file-base.ts +2 -12
  41. package/src/cli/adapters/vscode.ts +4 -16
  42. package/src/cli/dashboard.ts +1 -1
  43. package/src/cli/doctor.ts +7 -7
  44. package/src/cli/eject.ts +2 -2
  45. package/src/cli/init.test.ts +28 -15
  46. package/src/cli/init.ts +14 -4
  47. package/src/cli/lesson.ts +5 -5
  48. package/src/cli/log.ts +5 -5
  49. package/src/cli/manifest.ts +18 -5
  50. package/src/cli/stack-config.ts +2 -14
  51. package/src/cli/update.ts +95 -36
  52. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  53. package/src/orchestrator/agent-workflows/README.md +1 -1
  54. package/src/orchestrator/agent-workflows/bug-fix.md +4 -4
  55. package/src/orchestrator/agent-workflows/data-pipeline.md +1 -1
  56. package/src/orchestrator/agent-workflows/database-migration.md +4 -4
  57. package/src/orchestrator/agent-workflows/feature-implementation.md +3 -3
  58. package/src/orchestrator/agent-workflows/performance-optimization.md +1 -1
  59. package/src/orchestrator/agent-workflows/refactoring.md +1 -1
  60. package/src/orchestrator/agent-workflows/schema-changes.md +2 -2
  61. package/src/orchestrator/agent-workflows/security-audit.md +4 -4
  62. package/src/orchestrator/agent-workflows/shared-delivery-phase.md +1 -1
  63. package/src/orchestrator/agents/api-designer.agent.md +2 -2
  64. package/src/orchestrator/agents/architect.agent.md +2 -2
  65. package/src/orchestrator/agents/content-engineer.agent.md +2 -2
  66. package/src/orchestrator/agents/copywriter.agent.md +2 -2
  67. package/src/orchestrator/agents/data-expert.agent.md +2 -2
  68. package/src/orchestrator/agents/database-engineer.agent.md +2 -2
  69. package/src/orchestrator/agents/developer.agent.md +2 -2
  70. package/src/orchestrator/agents/devops-expert.agent.md +2 -2
  71. package/src/orchestrator/agents/documentation-writer.agent.md +2 -2
  72. package/src/orchestrator/agents/performance-expert.agent.md +2 -2
  73. package/src/orchestrator/agents/release-manager.agent.md +2 -2
  74. package/src/orchestrator/agents/researcher.agent.md +4 -4
  75. package/src/orchestrator/agents/reviewer.agent.md +1 -1
  76. package/src/orchestrator/agents/security-expert.agent.md +2 -2
  77. package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
  78. package/src/orchestrator/agents/session-guard.agent.md +10 -10
  79. package/src/orchestrator/agents/team-lead.agent.md +3 -3
  80. package/src/orchestrator/agents/testing-expert.agent.md +2 -2
  81. package/src/orchestrator/agents/ui-ux-expert.agent.md +2 -2
  82. package/src/orchestrator/copilot-instructions.md +1 -1
  83. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +11 -11
  84. package/src/orchestrator/customizations/DISPUTES.md +2 -2
  85. package/src/orchestrator/customizations/README.md +1 -1
  86. package/src/orchestrator/customizations/logs/README.md +1 -1
  87. package/src/orchestrator/customizations/project/docs-structure.md +6 -6
  88. package/src/orchestrator/instructions/ai-optimization.instructions.md +1 -1
  89. package/src/orchestrator/instructions/general.instructions.md +6 -6
  90. package/src/orchestrator/plugins/astro/SKILL.md +1 -1
  91. package/src/orchestrator/plugins/chrome-devtools/SKILL.md +4 -4
  92. package/src/orchestrator/plugins/contentful/SKILL.md +2 -2
  93. package/src/orchestrator/plugins/convex/SKILL.md +2 -2
  94. package/src/orchestrator/plugins/cypress/SKILL.md +2 -2
  95. package/src/orchestrator/plugins/figma/SKILL.md +1 -1
  96. package/src/orchestrator/plugins/jira/SKILL.md +3 -3
  97. package/src/orchestrator/plugins/linear/SKILL.md +2 -2
  98. package/src/orchestrator/plugins/netlify/SKILL.md +2 -2
  99. package/src/orchestrator/plugins/nextjs/SKILL.md +1 -1
  100. package/src/orchestrator/plugins/nx/SKILL.md +1 -1
  101. package/src/orchestrator/plugins/playwright/SKILL.md +2 -2
  102. package/src/orchestrator/plugins/prisma/SKILL.md +2 -2
  103. package/src/orchestrator/plugins/resend/SKILL.md +1 -1
  104. package/src/orchestrator/plugins/sanity/SKILL.md +2 -2
  105. package/src/orchestrator/plugins/slack/SKILL.md +2 -2
  106. package/src/orchestrator/plugins/strapi/SKILL.md +2 -2
  107. package/src/orchestrator/plugins/supabase/SKILL.md +2 -2
  108. package/src/orchestrator/plugins/teams/SKILL.md +1 -1
  109. package/src/orchestrator/plugins/turborepo/SKILL.md +1 -1
  110. package/src/orchestrator/plugins/vercel/SKILL.md +2 -2
  111. package/src/orchestrator/plugins/vitest/SKILL.md +2 -2
  112. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +8 -8
  113. package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
  114. package/src/orchestrator/prompts/bug-fix.prompt.md +4 -4
  115. package/src/orchestrator/prompts/create-skill.prompt.md +3 -3
  116. package/src/orchestrator/prompts/generate-convoy.prompt.md +1 -1
  117. package/src/orchestrator/prompts/implement-feature.prompt.md +10 -10
  118. package/src/orchestrator/prompts/quick-refinement.prompt.md +3 -3
  119. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +1 -1
  120. package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
  121. package/src/orchestrator/skills/agent-hooks/SKILL.md +9 -9
  122. package/src/orchestrator/skills/agent-memory/SKILL.md +4 -4
  123. package/src/orchestrator/skills/api-patterns/SKILL.md +2 -2
  124. package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
  125. package/src/orchestrator/skills/context-map/SKILL.md +1 -1
  126. package/src/orchestrator/skills/data-engineering/SKILL.md +2 -2
  127. package/src/orchestrator/skills/decomposition/SKILL.md +1 -1
  128. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
  129. package/src/orchestrator/skills/documentation-standards/SKILL.md +2 -2
  130. package/src/orchestrator/skills/fast-review/SKILL.md +2 -2
  131. package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
  132. package/src/orchestrator/skills/git-workflow/SKILL.md +2 -2
  133. package/src/orchestrator/skills/memory-merger/SKILL.md +3 -3
  134. package/src/orchestrator/skills/observability-logging/SKILL.md +10 -10
  135. package/src/orchestrator/skills/orchestration-protocols/SKILL.md +1 -1
  136. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
  137. package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
  138. package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
  139. package/src/orchestrator/skills/react-development/SKILL.md +1 -1
  140. package/src/orchestrator/skills/security-hardening/SKILL.md +1 -1
  141. package/src/orchestrator/skills/self-improvement/SKILL.md +2 -2
  142. package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
  143. package/src/orchestrator/skills/session-checkpoints/SKILL.md +5 -5
  144. package/src/orchestrator/skills/team-lead-reference/SKILL.md +6 -6
  145. package/src/orchestrator/skills/testing-workflow/SKILL.md +3 -3
  146. package/src/orchestrator/skills/validation-gates/SKILL.md +1 -1
@@ -31,6 +31,7 @@ import {
31
31
  } from './stack-config.js'
32
32
  import { ALL_PLUGIN_SKILL_NAMES } from '../orchestrator/plugins/index.js'
33
33
  import { IDE_ADAPTERS } from './adapters/index.js'
34
+ import { copyDir, getOrchestratorRoot } from './copy.js'
34
35
 
35
36
  // ── Helpers ────────────────────────────────────────────────────
36
37
 
@@ -42,6 +43,16 @@ async function readJson<T = unknown>(path: string): Promise<T> {
42
43
  return JSON.parse(await readFile(path, 'utf8')) as T
43
44
  }
44
45
 
46
+ /** Simulate the init.ts customizations scaffold step (copies customizations to .opencastle/). */
47
+ async function scaffoldCustomizations(pkgRoot: string, projectRoot: string, stack: StackConfig): Promise<void> {
48
+ const custSrcDir = resolve(getOrchestratorRoot(pkgRoot), 'customizations')
49
+ if (existsSync(custSrcDir)) {
50
+ const custDestDir = join(projectRoot, '.opencastle')
51
+ const custTransform = getCustomizationsTransform(stack)
52
+ await copyDir(custSrcDir, custDestDir, { transform: custTransform })
53
+ }
54
+ }
55
+
45
56
  /** Recursively list all files in a directory (relative paths). */
46
57
  async function listFilesRecursive(dir: string, prefix = ''): Promise<string[]> {
47
58
  if (!existsSync(dir)) return []
@@ -290,7 +301,7 @@ describe('gitignore generation', () => {
290
301
  it('creates .gitignore with framework paths ignored and customizable un-ignored', async () => {
291
302
  const managed = {
292
303
  framework: ['.github/copilot-instructions.md', '.github/agents/'],
293
- customizable: ['.github/customizations/', '.vscode/mcp.json'],
304
+ customizable: ['.opencastle/', '.vscode/mcp.json'],
294
305
  }
295
306
 
296
307
  await updateGitignore(tempDir, managed)
@@ -300,7 +311,7 @@ describe('gitignore generation', () => {
300
311
  expect(content).toContain('.github/copilot-instructions.md')
301
312
  expect(content).toContain('.github/agents/')
302
313
  // Customizable paths should be un-ignored
303
- expect(content).toContain('!.github/customizations/')
314
+ expect(content).toContain('!.opencastle/')
304
315
  expect(content).toContain('!.vscode/mcp.json')
305
316
  // Markers should be present
306
317
  expect(content).toContain('# >>> OpenCastle managed (do not edit) >>>')
@@ -316,14 +327,14 @@ describe('gitignore generation', () => {
316
327
 
317
328
  const managed2 = {
318
329
  framework: ['.github/agents/', '.github/skills/'],
319
- customizable: ['.vscode/mcp.json', '.github/customizations/'],
330
+ customizable: ['.vscode/mcp.json', '.opencastle/'],
320
331
  }
321
332
  const result = await updateGitignore(tempDir, managed2)
322
333
  expect(result).toBe('updated')
323
334
 
324
335
  const content = await readFile(join(tempDir, '.gitignore'), 'utf8')
325
336
  expect(content).toContain('.github/skills/')
326
- expect(content).toContain('!.github/customizations/')
337
+ expect(content).toContain('!.opencastle/')
327
338
  // Only one managed block
328
339
  const startCount = (content.match(/>>> OpenCastle managed/g) ?? []).length
329
340
  expect(startCount).toBe(1)
@@ -356,15 +367,15 @@ describe('VS Code adapter install', () => {
356
367
  expect(existsSync(join(githubDir, 'skills'))).toBe(true)
357
368
  expect(existsSync(join(githubDir, 'agent-workflows'))).toBe(true)
358
369
  expect(existsSync(join(githubDir, 'prompts'))).toBe(true)
359
- expect(existsSync(join(githubDir, 'customizations'))).toBe(true)
360
370
  expect(existsSync(join(tempDir, '.vscode', 'mcp.json'))).toBe(true)
361
371
  })
362
372
 
363
- it('creates all observability log files in customizations/logs', async () => {
373
+ it('creates all observability log files in .opencastle/logs', async () => {
364
374
  const adapter = await IDE_ADAPTERS['vscode']()
365
375
  await adapter.install(PKG_ROOT, tempDir, STACK_EMPTY, EMPTY_REPO_INFO)
376
+ await scaffoldCustomizations(PKG_ROOT, tempDir, STACK_EMPTY)
366
377
 
367
- const logsDir = join(tempDir, '.github', 'customizations', 'logs')
378
+ const logsDir = join(tempDir, '.opencastle', 'logs')
368
379
  expect(existsSync(logsDir)).toBe(true)
369
380
  for (const file of ['events.ndjson']) {
370
381
  expect(existsSync(join(logsDir, file))).toBe(true)
@@ -516,9 +527,10 @@ describe('VS Code adapter install', () => {
516
527
  it('fills skill-matrix.json with selected DB and CMS', async () => {
517
528
  const adapter = await IDE_ADAPTERS['vscode']()
518
529
  await adapter.install(PKG_ROOT, tempDir, STACK_FULL, EMPTY_REPO_INFO)
530
+ await scaffoldCustomizations(PKG_ROOT, tempDir, STACK_FULL)
519
531
 
520
532
  const skillMatrix = await readFile(
521
- join(tempDir, '.github', 'customizations', 'agents', 'skill-matrix.json'),
533
+ join(tempDir, '.opencastle', 'agents', 'skill-matrix.json'),
522
534
  'utf8'
523
535
  )
524
536
  const data = JSON.parse(skillMatrix)
@@ -533,9 +545,10 @@ describe('VS Code adapter install', () => {
533
545
  it('leaves skill-matrix.json database/cms slots empty when none selected', async () => {
534
546
  const adapter = await IDE_ADAPTERS['vscode']()
535
547
  await adapter.install(PKG_ROOT, tempDir, STACK_EMPTY, EMPTY_REPO_INFO)
548
+ await scaffoldCustomizations(PKG_ROOT, tempDir, STACK_EMPTY)
536
549
 
537
550
  const skillMatrix = await readFile(
538
- join(tempDir, '.github', 'customizations', 'agents', 'skill-matrix.json'),
551
+ join(tempDir, '.opencastle', 'agents', 'skill-matrix.json'),
539
552
  'utf8'
540
553
  )
541
554
  const data = JSON.parse(skillMatrix)
@@ -554,7 +567,7 @@ describe('VS Code adapter install', () => {
554
567
  expect(paths.framework).toContain('.github/agent-workflows/')
555
568
  expect(paths.framework).toContain('.github/prompts/')
556
569
 
557
- expect(paths.customizable).toContain('.github/customizations/')
570
+ expect(paths.customizable).toContain('.opencastle/')
558
571
  expect(paths.customizable).toContain('.vscode/mcp.json')
559
572
  })
560
573
  })
@@ -680,7 +693,7 @@ describe('Cursor adapter install', () => {
680
693
  expect(paths.framework).toContain('.cursor/rules/general.mdc')
681
694
  expect(paths.framework).toContain('.cursor/rules/ai-optimization.mdc')
682
695
 
683
- expect(paths.customizable).toContain('.cursor/rules/customizations/')
696
+ expect(paths.customizable).toContain('.opencastle/')
684
697
  expect(paths.customizable).toContain('.cursor/mcp.json')
685
698
  })
686
699
  })
@@ -827,7 +840,7 @@ describe('Claude Code adapter install', () => {
827
840
  expect(paths.framework).toContain('.claude/skills/')
828
841
  expect(paths.framework).toContain('.claude/commands/')
829
842
 
830
- expect(paths.customizable).toContain('.claude/customizations/')
843
+ expect(paths.customizable).toContain('.opencastle/')
831
844
  expect(paths.customizable).toContain('.claude/mcp.json')
832
845
  })
833
846
  })
@@ -867,7 +880,6 @@ describe('OpenCode adapter install', () => {
867
880
  expect(existsSync(join(tempDir, '.opencode', 'skills'))).toBe(true)
868
881
  expect(existsSync(join(tempDir, '.opencode', 'prompts'))).toBe(true)
869
882
  expect(existsSync(join(tempDir, '.opencode', 'workflows'))).toBe(true)
870
- expect(existsSync(join(tempDir, '.opencode', 'customizations'))).toBe(true)
871
883
  })
872
884
 
873
885
  it('generates OpenCode MCP config with mcp format', async () => {
@@ -919,7 +931,7 @@ describe('OpenCode adapter install', () => {
919
931
  expect(paths.framework).toContain('.opencode/prompts/')
920
932
  expect(paths.framework).toContain('.opencode/workflows/')
921
933
 
922
- expect(paths.customizable).toContain('.opencode/customizations/')
934
+ expect(paths.customizable).toContain('.opencastle/')
923
935
  expect(paths.customizable).toContain('opencode.json')
924
936
  })
925
937
  })
@@ -1078,6 +1090,7 @@ describe('full stack configuration', () => {
1078
1090
 
1079
1091
  const adapter = await IDE_ADAPTERS['vscode']()
1080
1092
  const result = await adapter.install(PKG_ROOT, tempDir, stack, EMPTY_REPO_INFO)
1093
+ await scaffoldCustomizations(PKG_ROOT, tempDir, stack)
1081
1094
 
1082
1095
  expect(result.created.length).toBeGreaterThan(0)
1083
1096
 
@@ -1119,7 +1132,7 @@ describe('full stack configuration', () => {
1119
1132
 
1120
1133
  // Skill matrix should be filled
1121
1134
  const skillMatrix = await readFile(
1122
- join(tempDir, '.github', 'customizations', 'agents', 'skill-matrix.json'),
1135
+ join(tempDir, '.opencastle', 'agents', 'skill-matrix.json'),
1123
1136
  'utf8'
1124
1137
  )
1125
1138
  const matrixData = JSON.parse(skillMatrix)
package/src/cli/init.ts CHANGED
@@ -3,9 +3,9 @@ import { readFile, unlink } from 'node:fs/promises'
3
3
  import { existsSync } from 'node:fs'
4
4
  import { multiselect, confirm, closePrompts, c } from './prompt.js'
5
5
  import { readManifest, writeManifest, createManifest } from './manifest.js'
6
- import { removeDirIfExists } from './copy.js'
6
+ import { removeDirIfExists, copyDir, getOrchestratorRoot } from './copy.js'
7
7
  import { updateGitignore } from './gitignore.js'
8
- import { getRequiredMcpEnvVars } from './stack-config.js'
8
+ import { getRequiredMcpEnvVars, getCustomizationsTransform } from './stack-config.js'
9
9
  import { TECH_PLUGINS, TEAM_PLUGINS } from '../orchestrator/plugins/index.js'
10
10
  import { detectRepoInfo, mergeStackIntoRepoInfo, formatRepoInfo, buildDetectedToolsSet } from './detect.js'
11
11
  import { IDE_ADAPTERS } from './adapters/index.js'
@@ -138,7 +138,7 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
138
138
  console.log(` ${c.green('+')} ${p}`)
139
139
  }
140
140
  }
141
- console.log(` ${c.green('+')} .opencastle.json`)
141
+ console.log(` ${c.green('+')} .opencastle/manifest.json`)
142
142
  console.log(` ${c.green('+')} .gitignore (OpenCastle entries)`)
143
143
  console.log(`\n ${c.dim('No files were written.')}\n`)
144
144
  closePrompts()
@@ -217,6 +217,16 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
217
217
  }
218
218
  }
219
219
 
220
+ // ── Scaffold customizations to .opencastle/ ──────────────────────────────
221
+ const custSrcDir = resolve(getOrchestratorRoot(pkgRoot), 'customizations')
222
+ if (existsSync(custSrcDir)) {
223
+ const custDestDir = resolve(projectRoot, '.opencastle')
224
+ const custTransform = getCustomizationsTransform(stack)
225
+ const sub = await copyDir(custSrcDir, custDestDir, { transform: custTransform })
226
+ totalCreated += sub.created.length
227
+ totalSkipped += sub.skipped.length
228
+ }
229
+
220
230
  // ── Write manifest ──────────────────────────────────────────────
221
231
  const manifest = createManifest(pkg.version, ides[0], ides)
222
232
  manifest.managedPaths = allManagedPaths
@@ -311,7 +321,7 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
311
321
  ` ${step}. Run the ${c.cyan('"Bootstrap Customizations"')} prompt to configure for your project`
312
322
  )
313
323
  step++
314
- console.log(` ${step}. Commit the customizations/ folder to your repository`)
324
+ console.log(` ${step}. Commit the .opencastle/ folder to your repository`)
315
325
  console.log()
316
326
 
317
327
  closePrompts()
package/src/cli/lesson.ts CHANGED
@@ -25,7 +25,7 @@ type Severity = (typeof SEVERITIES)[number]
25
25
  const HELP = `
26
26
  opencastle lesson [options]
27
27
 
28
- Append a structured lesson to .github/customizations/LESSONS-LEARNED.md
28
+ Append a structured lesson to .opencastle/LESSONS-LEARNED.md
29
29
 
30
30
  Required flags:
31
31
  --title <text> Short descriptive title
@@ -70,16 +70,16 @@ async function resolveCustomizationsDir(override: string | null): Promise<string
70
70
  let dir = process.cwd()
71
71
  for (;;) {
72
72
  try {
73
- const s = await stat(join(dir, '.github'))
74
- if (s.isDirectory()) return join(dir, '.github', 'customizations')
73
+ const s = await stat(join(dir, '.opencastle'))
74
+ if (s.isDirectory()) return join(dir, '.opencastle')
75
75
  } catch {
76
- // .github not found here, walk up
76
+ // .opencastle not found here, walk up
77
77
  }
78
78
  const parent = dirname(dir)
79
79
  if (parent === dir) break
80
80
  dir = parent
81
81
  }
82
- return join(process.cwd(), '.github', 'customizations')
82
+ return join(process.cwd(), '.opencastle')
83
83
  }
84
84
 
85
85
  function nextLessonId(content: string): string {
package/src/cli/log.ts CHANGED
@@ -41,22 +41,22 @@ function coerceValue(key: string, raw: string): unknown {
41
41
  return raw
42
42
  }
43
43
 
44
- /** Resolve the path to the logs directory (walks up to find .github/). */
44
+ /** Resolve the path to the logs directory (walks up to find .opencastle/). */
45
45
  export async function resolveLogsDir(override?: string | null): Promise<string> {
46
46
  if (override) return override
47
47
  let dir = process.cwd()
48
48
  for (;;) {
49
49
  try {
50
- const s = await stat(join(dir, '.github'))
51
- if (s.isDirectory()) return join(dir, '.github', 'customizations', 'logs')
50
+ const s = await stat(join(dir, '.opencastle'))
51
+ if (s.isDirectory()) return join(dir, '.opencastle', 'logs')
52
52
  } catch {
53
- // .github not in this directory, walk up
53
+ // .opencastle not in this directory, walk up
54
54
  }
55
55
  const parent = dirname(dir)
56
56
  if (parent === dir) break
57
57
  dir = parent
58
58
  }
59
- return join(process.cwd(), '.github', 'customizations', 'logs')
59
+ return join(process.cwd(), '.opencastle', 'logs')
60
60
  }
61
61
 
62
62
  /** Append a structured event record to events.ndjson. */
@@ -1,11 +1,13 @@
1
- import { readFile, writeFile } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { resolve, dirname } from 'node:path';
3
3
  import type { Manifest } from './types.js';
4
4
 
5
- const MANIFEST_FILE = '.opencastle.json';
5
+ const MANIFEST_FILE = '.opencastle/manifest.json';
6
6
 
7
7
  /**
8
8
  * Read the project's OpenCastle manifest, or null if not installed.
9
+ * Tries the new location (.opencastle/manifest.json) first, then falls back
10
+ * to the legacy location (.opencastle.json) for backward compatibility.
9
11
  */
10
12
  export async function readManifest(
11
13
  projectRoot: string
@@ -17,18 +19,29 @@ export async function readManifest(
17
19
  );
18
20
  return JSON.parse(content) as Manifest;
19
21
  } catch {
20
- return null;
22
+ // Fallback to legacy location
23
+ try {
24
+ const content = await readFile(
25
+ resolve(projectRoot, '.opencastle.json'),
26
+ 'utf8'
27
+ );
28
+ return JSON.parse(content) as Manifest;
29
+ } catch {
30
+ return null;
31
+ }
21
32
  }
22
33
  }
23
34
 
24
35
  /**
25
- * Write the manifest to the project root.
36
+ * Write the manifest to .opencastle/manifest.json.
37
+ * Creates the .opencastle/ directory if it doesn't exist.
26
38
  */
27
39
  export async function writeManifest(
28
40
  projectRoot: string,
29
41
  manifest: Manifest
30
42
  ): Promise<void> {
31
43
  const path = resolve(projectRoot, MANIFEST_FILE);
44
+ await mkdir(dirname(path), { recursive: true });
32
45
  await writeFile(path, JSON.stringify(manifest, null, 2) + '\n');
33
46
  }
34
47
 
@@ -257,20 +257,8 @@ const SUBCATEGORY_TO_SLOT: Record<string, string> = {
257
257
  /**
258
258
  * Get the filesystem path to the skill matrix file for a given IDE.
259
259
  */
260
- function getSkillMatrixPath(projectRoot: string, ide: string): string {
261
- const relativePath = 'customizations/agents/skill-matrix.json';
262
- switch (ide) {
263
- case 'vscode':
264
- return resolve(projectRoot, '.github', relativePath);
265
- case 'cursor':
266
- return resolve(projectRoot, '.cursor', 'rules', relativePath);
267
- case 'claude-code':
268
- return resolve(projectRoot, '.claude', relativePath);
269
- case 'opencode':
270
- return resolve(projectRoot, '.opencode', relativePath);
271
- default:
272
- return '';
273
- }
260
+ function getSkillMatrixPath(projectRoot: string, _ide: string): string {
261
+ return resolve(projectRoot, '.opencastle', 'agents', 'skill-matrix.json');
274
262
  }
275
263
 
276
264
  /**
package/src/cli/update.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'node:path'
2
2
  import { existsSync } from 'node:fs'
3
- import { readFile, appendFile, rename } from 'node:fs/promises'
3
+ import { readFile, appendFile, rename, mkdir, writeFile, unlink, copyFile, readdir, rm } from 'node:fs/promises'
4
4
  import { readManifest, writeManifest } from './manifest.js'
5
5
  import { multiselect, confirm, closePrompts, c } from './prompt.js'
6
6
  import { isLegacyStack, migrateStackConfig, IDE_LABELS } from './types.js'
@@ -17,6 +17,8 @@ export default async function update({
17
17
  }: CliContext): Promise<void> {
18
18
  const projectRoot = process.cwd()
19
19
 
20
+ await migrateCustomizationsDir(projectRoot)
21
+
20
22
  const manifest = await readManifest(projectRoot)
21
23
  if (!manifest) {
22
24
  console.error(
@@ -326,9 +328,62 @@ export default async function update({
326
328
  closePrompts()
327
329
  }
328
330
 
331
+ async function copyDirMigrate(srcDir: string, destDir: string): Promise<void> {
332
+ await mkdir(destDir, { recursive: true })
333
+ for (const entry of await readdir(srcDir, { withFileTypes: true })) {
334
+ const srcPath = resolve(srcDir, entry.name)
335
+ const destPath = resolve(destDir, entry.name)
336
+ if (entry.isDirectory()) {
337
+ await copyDirMigrate(srcPath, destPath)
338
+ } else if (!existsSync(destPath)) {
339
+ await copyFile(srcPath, destPath)
340
+ }
341
+ }
342
+ }
343
+
344
+ async function migrateCustomizationsDir(projectRoot: string): Promise<void> {
345
+ const oldManifestPath = resolve(projectRoot, '.opencastle.json')
346
+ const newOpencastleDir = resolve(projectRoot, '.opencastle')
347
+ const newManifestPath = resolve(newOpencastleDir, 'manifest.json')
348
+
349
+ // Migrate manifest from flat location to .opencastle/manifest.json
350
+ if (existsSync(oldManifestPath) && !existsSync(newManifestPath)) {
351
+ await mkdir(newOpencastleDir, { recursive: true })
352
+ const content = await readFile(oldManifestPath, 'utf8')
353
+ await writeFile(newManifestPath, content)
354
+ await unlink(oldManifestPath)
355
+ console.log(` ${c.green('✓')} Migrated manifest to .opencastle/manifest.json`)
356
+ }
357
+
358
+ // Old customizations directory locations per IDE
359
+ const oldCustDirs = [
360
+ resolve(projectRoot, '.github', 'customizations'),
361
+ resolve(projectRoot, '.cursor', 'rules', 'customizations'),
362
+ resolve(projectRoot, '.claude', 'customizations'),
363
+ resolve(projectRoot, '.opencode', 'customizations'),
364
+ ]
365
+
366
+ // Copy from the first found old location (content is the same across IDEs)
367
+ for (const oldDir of oldCustDirs) {
368
+ if (!existsSync(oldDir)) continue
369
+ await copyDirMigrate(oldDir, newOpencastleDir)
370
+ console.log(` ${c.green('✓')} Migrated customizations to .opencastle/`)
371
+ break
372
+ }
373
+
374
+ // Remove all old customizations directories
375
+ for (const oldDir of oldCustDirs) {
376
+ if (existsSync(oldDir)) {
377
+ await rm(oldDir, { recursive: true })
378
+ }
379
+ }
380
+ }
381
+
329
382
  async function migrateLegacyLogs(projectRoot: string): Promise<void> {
330
- const logsDir = resolve(projectRoot, '.github', 'customizations', 'logs')
331
- if (!existsSync(logsDir)) return
383
+ const candidateLogsDirs = [
384
+ resolve(projectRoot, '.github', 'customizations', 'logs'),
385
+ resolve(projectRoot, '.opencastle', 'logs'),
386
+ ]
332
387
 
333
388
  const typeMap: Record<string, string> = {
334
389
  'sessions.ndjson': 'session',
@@ -338,48 +393,52 @@ async function migrateLegacyLogs(projectRoot: string): Promise<void> {
338
393
  'disputes.ndjson': 'dispute',
339
394
  }
340
395
 
341
- const eventsFile = resolve(logsDir, 'events.ndjson')
342
- let totalMigrated = 0
343
-
344
- for (const [filename, type] of Object.entries(typeMap)) {
345
- const filePath = resolve(logsDir, filename)
346
- if (!existsSync(filePath)) continue
396
+ for (const logsDir of candidateLogsDirs) {
397
+ if (!existsSync(logsDir)) continue
347
398
 
348
- let content: string
349
- try {
350
- content = await readFile(filePath, 'utf8')
351
- } catch {
352
- continue
353
- }
399
+ const eventsFile = resolve(logsDir, 'events.ndjson')
400
+ let totalMigrated = 0
354
401
 
355
- const lines = content.split('\n').filter((line) => line.trim() !== '')
356
- if (lines.length === 0) continue
402
+ for (const [filename, type] of Object.entries(typeMap)) {
403
+ const filePath = resolve(logsDir, filename)
404
+ if (!existsSync(filePath)) continue
357
405
 
358
- const migratedLines: string[] = []
359
- for (const line of lines) {
406
+ let content: string
360
407
  try {
361
- const record = JSON.parse(line) as Record<string, unknown>
362
- if (!record['type']) {
363
- record['type'] = type
364
- }
365
- migratedLines.push(JSON.stringify(record))
408
+ content = await readFile(filePath, 'utf8')
366
409
  } catch {
367
- console.warn(` ${c.yellow('⚠')} Skipping malformed JSON line in ${filename}`)
410
+ continue
368
411
  }
369
- }
370
412
 
371
- if (migratedLines.length > 0) {
372
- await appendFile(eventsFile, migratedLines.join('\n') + '\n', 'utf8')
373
- totalMigrated += migratedLines.length
374
- }
413
+ const lines = content.split('\n').filter((line) => line.trim() !== '')
414
+ if (lines.length === 0) continue
415
+
416
+ const migratedLines: string[] = []
417
+ for (const line of lines) {
418
+ try {
419
+ const record = JSON.parse(line) as Record<string, unknown>
420
+ if (!record['type']) {
421
+ record['type'] = type
422
+ }
423
+ migratedLines.push(JSON.stringify(record))
424
+ } catch {
425
+ console.warn(` ${c.yellow('⚠')} Skipping malformed JSON line in ${filename}`)
426
+ }
427
+ }
375
428
 
376
- await rename(filePath, filePath + '.migrated')
377
- }
429
+ if (migratedLines.length > 0) {
430
+ await appendFile(eventsFile, migratedLines.join('\n') + '\n', 'utf8')
431
+ totalMigrated += migratedLines.length
432
+ }
378
433
 
379
- if (totalMigrated > 0) {
380
- console.log(
381
- ` ${c.green('✓')} Migrated ${c.bold(String(totalMigrated))} records from legacy log files to events.ndjson`
382
- )
434
+ await rename(filePath, filePath + '.migrated')
435
+ }
436
+
437
+ if (totalMigrated > 0) {
438
+ console.log(
439
+ ` ${c.green('✓')} Migrated ${c.bold(String(totalMigrated))} records from legacy log files to events.ndjson`
440
+ )
441
+ }
383
442
  }
384
443
  }
385
444
 
@@ -1,25 +1,25 @@
1
1
  {
2
- "hash": "1338aa44",
2
+ "hash": "efd7e9fc",
3
3
  "configHash": "30f8ea04",
4
- "lockfileHash": "56ae5b5d",
5
- "browserHash": "aaeb6157",
4
+ "lockfileHash": "851b1664",
5
+ "browserHash": "1b9872e1",
6
6
  "optimized": {
7
7
  "astro > cssesc": {
8
8
  "src": "../../../../../node_modules/cssesc/cssesc.js",
9
9
  "file": "astro___cssesc.js",
10
- "fileHash": "2e7de42e",
10
+ "fileHash": "bd1b469f",
11
11
  "needsInterop": true
12
12
  },
13
13
  "astro > aria-query": {
14
14
  "src": "../../../../../node_modules/aria-query/lib/index.js",
15
15
  "file": "astro___aria-query.js",
16
- "fileHash": "4d71b8ca",
16
+ "fileHash": "796388ea",
17
17
  "needsInterop": true
18
18
  },
19
19
  "astro > axobject-query": {
20
20
  "src": "../../../../../node_modules/axobject-query/lib/index.js",
21
21
  "file": "astro___axobject-query.js",
22
- "fileHash": "aa82361e",
22
+ "fileHash": "deadbce3",
23
23
  "needsInterop": true
24
24
  }
25
25
  },
@@ -1,4 +1,4 @@
1
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
1
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
2
2
 
3
3
  # Workflow Templates
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
1
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
2
2
 
3
3
  # Workflow: Bug Fix
4
4
 
@@ -29,9 +29,9 @@ Follow the **Delivery Outcome** in `general.instructions.md` and the **Branch Ow
29
29
 
30
30
  ### Steps
31
31
 
32
- 1. Check `.github/customizations/KNOWN-ISSUES.md` for existing entry
32
+ 1. Check `.opencastle/KNOWN-ISSUES.md` for existing entry
33
33
  2. Check tracker for existing bug ticket
34
- 3. Read `.github/customizations/LESSONS-LEARNED.md` for related pitfalls
34
+ 3. Read `.opencastle/LESSONS-LEARNED.md` for related pitfalls
35
35
  4. **Reproduce the bug** — this is mandatory before any fix attempt:
36
36
  a. Start the dev server (see the **codebase-tool** skill for the serve command)
37
37
  b. Navigate to the affected page in Chrome
@@ -110,7 +110,7 @@ Follow the **Delivery Outcome** in `general.instructions.md` and the **Branch Ow
110
110
  4. Test adjacent features for regressions
111
111
  5. If security-related: schedule panel review
112
112
  6. Move tracker issue to Done
113
- 7. Update `.github/customizations/KNOWN-ISSUES.md` if the bug was listed there
113
+ 7. Update `.opencastle/KNOWN-ISSUES.md` if the bug was listed there
114
114
  8. Commit and push
115
115
 
116
116
  ### Exit Criteria
@@ -1,4 +1,4 @@
1
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
1
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
2
2
 
3
3
  # Workflow: Data Pipeline
4
4
 
@@ -1,10 +1,10 @@
1
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
1
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
2
2
 
3
3
  # Workflow: Database Migration
4
4
 
5
5
  Structured workflow for database schema changes, RLS policies, and data migrations.
6
6
 
7
- > **Project config:** For database-specific paths, schema details, and migration conventions, see the relevant database customization file in `customizations/stack/`.
7
+ > **Project config:** For database-specific paths, schema details, and migration conventions, see the relevant database customization file in `.opencastle/stack/`.
8
8
 
9
9
  ## Phases
10
10
 
@@ -34,8 +34,8 @@ Follow the **Delivery Outcome** in `general.instructions.md` and the **Branch Ow
34
34
 
35
35
  1. Read current schema in the migrations directory (see database customization) to understand existing tables
36
36
  2. Check existing RLS policies using the database query tool
37
- 3. Read `.github/customizations/project.instructions.md` for database architecture
38
- 4. Check `.github/customizations/KNOWN-ISSUES.md` for database-related limitations
37
+ 3. Read `.opencastle/project.instructions.md` for database architecture
38
+ 4. Check `.opencastle/KNOWN-ISSUES.md` for database-related limitations
39
39
  5. Document the migration plan: tables affected, columns added/removed, RLS changes
40
40
  6. Write rollback strategy (how to reverse the migration)
41
41
  7. Create tracker issue with migration details and rollback plan
@@ -1,4 +1,4 @@
1
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
1
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
2
2
 
3
3
  # Workflow: Feature Implementation
4
4
 
@@ -77,7 +77,7 @@ Run the `brainstorm` prompt when the task has ambiguity, multiple valid approach
77
77
 
78
78
  ### Steps
79
79
 
80
- 1. Read `.github/customizations/project.instructions.md`, `.github/customizations/KNOWN-ISSUES.md`, `.github/customizations/LESSONS-LEARNED.md`
80
+ 1. Read `.opencastle/project.instructions.md`, `.opencastle/KNOWN-ISSUES.md`, `.opencastle/LESSONS-LEARNED.md`
81
81
  2. Search codebase for existing implementations
82
82
  3. Identify affected apps, libs, and layers
83
83
  4. **Spec flow analysis** — Trace the complete user flow end-to-end and identify:
@@ -212,7 +212,7 @@ If there are no open questions, explicitly state: "No open questions — plan is
212
212
  - Final responsive sweep at all breakpoints (if UI changes)
213
213
  7. Move all issues to Done
214
214
  8. Update session checkpoint → delete checkpoint
215
- 9. Update `.github/customizations/project/roadmap.md`
215
+ 9. Update `.opencastle/project/roadmap.md`
216
216
 
217
217
  ### Exit Criteria
218
218
 
@@ -1,4 +1,4 @@
1
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
1
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
2
2
 
3
3
  # Workflow: Performance Optimization
4
4
 
@@ -1,4 +1,4 @@
1
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
1
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
2
2
 
3
3
  # Workflow: Code Refactoring
4
4