prizmkit 1.1.57 → 1.1.60

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 (188) hide show
  1. package/bin/create-prizmkit.js +8 -6
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/codex/agent-adapter.js +38 -0
  4. package/bundled/adapters/codex/paths.js +27 -0
  5. package/bundled/adapters/codex/rules-adapter.js +30 -0
  6. package/bundled/adapters/codex/settings-adapter.js +27 -0
  7. package/bundled/adapters/codex/skill-adapter.js +65 -0
  8. package/bundled/adapters/codex/team-adapter.js +37 -0
  9. package/bundled/dev-pipeline/.env.example +2 -1
  10. package/bundled/dev-pipeline/README.md +10 -7
  11. package/bundled/dev-pipeline/lib/common.sh +278 -37
  12. package/bundled/dev-pipeline/run-bugfix.sh +10 -61
  13. package/bundled/dev-pipeline/run-feature.sh +10 -78
  14. package/bundled/dev-pipeline/run-recovery.sh +10 -46
  15. package/bundled/dev-pipeline/run-refactor.sh +10 -61
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
  19. package/bundled/dev-pipeline/scripts/utils.py +6 -4
  20. package/bundled/dev-pipeline-windows/.env.example +28 -0
  21. package/bundled/dev-pipeline-windows/README.md +30 -0
  22. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
  23. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
  24. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
  25. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
  26. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
  27. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
  28. package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
  29. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
  30. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
  31. package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
  32. package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
  33. package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
  34. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
  35. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
  36. package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
  37. package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
  38. package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
  39. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
  40. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
  41. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
  42. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
  43. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
  44. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
  45. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
  46. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
  47. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
  48. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
  49. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
  50. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
  51. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
  52. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
  53. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
  54. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
  55. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
  56. package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
  57. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
  59. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
  60. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
  61. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
  62. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
  63. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
  64. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
  65. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
  66. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
  67. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
  68. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
  69. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
  70. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
  71. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
  72. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
  73. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
  74. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
  75. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
  76. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
  77. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
  78. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
  79. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
  80. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
  81. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
  82. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
  83. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
  84. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
  85. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
  86. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
  87. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
  88. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
  89. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
  90. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
  91. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
  92. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
  93. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
  94. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
  95. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
  96. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
  97. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
  98. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
  99. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
  100. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
  101. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
  102. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
  103. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
  104. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
  105. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
  106. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
  107. package/bundled/skills/_metadata.json +1 -1
  108. package/bundled/skills/app-planner/SKILL.md +26 -18
  109. package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
  110. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  111. package/bundled/skills/feature-planner/SKILL.md +9 -2
  112. package/bundled/skills/prizmkit-init/SKILL.md +7 -6
  113. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
  114. package/bundled/skills-windows/app-planner/SKILL.md +639 -0
  115. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
  116. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
  117. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
  118. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
  119. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
  120. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
  121. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
  122. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
  123. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
  124. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
  125. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
  126. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
  127. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
  128. package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
  129. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
  130. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
  131. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
  132. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
  133. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
  134. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
  135. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
  136. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
  137. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
  138. package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
  139. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
  140. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
  141. package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
  142. package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
  143. package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
  144. package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
  145. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
  146. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
  147. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
  148. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
  149. package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
  150. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
  151. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
  152. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
  153. package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
  154. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
  155. package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
  156. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
  157. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
  158. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
  159. package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
  160. package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
  161. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
  162. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +68 -0
  163. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
  164. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
  165. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
  166. package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
  167. package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
  168. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
  169. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
  170. package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
  171. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
  172. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
  173. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
  174. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
  175. package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
  176. package/package.json +3 -2
  177. package/src/clean.js +73 -2
  178. package/src/config.js +159 -50
  179. package/src/detect-platform.js +16 -8
  180. package/src/external-skills.js +26 -19
  181. package/src/index.js +31 -9
  182. package/src/manifest.js +6 -2
  183. package/src/metadata.js +43 -5
  184. package/src/platforms.js +36 -0
  185. package/src/prompts.js +31 -6
  186. package/src/runtimes.js +20 -0
  187. package/src/scaffold.js +314 -110
  188. package/src/upgrade.js +81 -41
package/src/config.js CHANGED
@@ -31,8 +31,11 @@ import {
31
31
  installProjectMemory,
32
32
  installPipeline,
33
33
  installGitignore,
34
+ installGitHook,
35
+ installPrizmkitScripts,
34
36
  resolveSkillList,
35
37
  resolvePipelineFileList,
38
+ resolveRuleNamesForRuntime,
36
39
  } from './scaffold.js';
37
40
  import {
38
41
  removeSkillFiles,
@@ -44,11 +47,14 @@ import { detectPlatform } from './detect-platform.js';
44
47
  import {
45
48
  selectPlatform,
46
49
  selectAiCli,
50
+ selectRuntime,
47
51
  selectSkillSuite,
48
52
  selectRulesPreset,
49
53
  confirmTeamMode,
50
54
  confirmPipeline,
51
55
  } from './prompts.js';
56
+ import { expandPlatforms, platformLabel, projectMemoryFile } from './platforms.js';
57
+ import { normalizeRuntime, runtimeLabel } from './runtimes.js';
52
58
 
53
59
  const __dirname = dirname(fileURLToPath(import.meta.url));
54
60
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
@@ -57,36 +63,53 @@ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-
57
63
  // Helpers
58
64
  // ============================================================
59
65
 
66
+ async function removeGeneratedCodexConfig(projectRoot, dryRun) {
67
+ const configPath = path.join(projectRoot, '.codex', 'config.toml');
68
+ if (!await fs.pathExists(configPath)) return;
69
+
70
+ const content = await fs.readFile(configPath, 'utf8');
71
+ if (!content.includes('Generated by PrizmKit for Codex CLI project configuration')) {
72
+ return;
73
+ }
74
+
75
+ if (dryRun) {
76
+ console.log(chalk.gray(' [dry-run] remove .codex/config.toml'));
77
+ } else {
78
+ await fs.remove(configPath);
79
+ console.log(chalk.red(' ✗ removed .codex/config.toml'));
80
+ }
81
+ }
82
+
60
83
  /**
61
84
  * Detect which platforms are actually installed on filesystem.
62
85
  */
63
86
  async function detectInstalledPlatforms(projectRoot) {
64
- const hasClaude = await fs.pathExists(path.join(projectRoot, '.claude', 'commands'))
65
- || await fs.pathExists(path.join(projectRoot, '.claude', 'agents'));
66
- const hasCodeBuddy = await fs.pathExists(path.join(projectRoot, '.codebuddy', 'skills'))
67
- || await fs.pathExists(path.join(projectRoot, '.codebuddy', 'agents'));
87
+ const hasPlatformFiles = async (dir) => {
88
+ if (!await fs.pathExists(dir)) return false;
89
+ try {
90
+ return (await fs.readdir(dir)).length > 0;
91
+ } catch {
92
+ return false;
93
+ }
94
+ };
68
95
 
96
+ const hasClaude = await hasPlatformFiles(path.join(projectRoot, '.claude', 'commands'))
97
+ || await hasPlatformFiles(path.join(projectRoot, '.claude', 'agents'));
98
+ const hasCodeBuddy = await hasPlatformFiles(path.join(projectRoot, '.codebuddy', 'skills'))
99
+ || await hasPlatformFiles(path.join(projectRoot, '.codebuddy', 'agents'));
100
+ const hasCodex = await hasPlatformFiles(path.join(projectRoot, '.agents', 'skills'))
101
+ || await hasPlatformFiles(path.join(projectRoot, '.codex', 'agents'));
102
+
103
+ if (hasClaude && hasCodeBuddy && hasCodex) return 'all';
69
104
  if (hasClaude && hasCodeBuddy) return 'both';
105
+ if (hasCodex) return 'codex';
70
106
  if (hasCodeBuddy) return 'codebuddy';
71
107
  if (hasClaude) return 'claude';
72
108
  return null;
73
109
  }
74
110
 
75
- /**
76
- * Expand a platform string to an array of individual platforms.
77
- */
78
- function expandPlatforms(platform) {
79
- return platform === 'both' ? ['codebuddy', 'claude'] : [platform];
80
- }
81
-
82
111
  function getProjectMemoryFile(platform) {
83
- if (platform === 'claude') return 'CLAUDE.md';
84
- if (platform === 'codebuddy') return 'CODEBUDDY.md';
85
- return null;
86
- }
87
-
88
- function getCounterpartPlatform(platform) {
89
- return platform === 'claude' ? 'codebuddy' : platform === 'codebuddy' ? 'claude' : null;
112
+ return projectMemoryFile(platform);
90
113
  }
91
114
 
92
115
  async function migrateProjectMemoryOnPlatformDrop(droppedPlatform, newPlatforms, projectRoot, dryRun) {
@@ -96,10 +119,7 @@ async function migrateProjectMemoryOnPlatformDrop(droppedPlatform, newPlatforms,
96
119
  const sourcePath = path.join(projectRoot, sourceFile);
97
120
  if (!await fs.pathExists(sourcePath)) return;
98
121
 
99
- const counterpart = getCounterpartPlatform(droppedPlatform);
100
- const targetPlatform = (counterpart && newPlatforms.includes(counterpart))
101
- ? counterpart
102
- : newPlatforms.find(p => p !== droppedPlatform);
122
+ const targetPlatform = newPlatforms.find(p => p !== droppedPlatform) || newPlatforms[0];
103
123
 
104
124
  if (!targetPlatform || targetPlatform === droppedPlatform) return;
105
125
 
@@ -138,9 +158,6 @@ async function migrateProjectMemoryOnPlatformDrop(droppedPlatform, newPlatforms,
138
158
  console.log(chalk.green(` ✓ merged ${sourceFile} content into ${targetFile}`));
139
159
  }
140
160
 
141
- const platformLabel = (p) => p === 'both' ? 'CodeBuddy + Claude Code'
142
- : p === 'codebuddy' ? 'CodeBuddy' : 'Claude Code';
143
-
144
161
  /**
145
162
  * Remove all PrizmKit-owned platform-specific files for a single platform.
146
163
  * Selective removal — only removes known PrizmKit files, leaves user-created files intact.
@@ -166,15 +183,28 @@ async function removePlatformFiles(platform, projectRoot, manifest, dryRun) {
166
183
  }
167
184
 
168
185
  // Settings
169
- const settingsFile = platform === 'claude'
170
- ? path.join(projectRoot, '.claude', 'settings.json')
171
- : path.join(projectRoot, '.codebuddy', 'settings.json');
172
- if (await fs.pathExists(settingsFile)) {
173
- if (dryRun) {
174
- console.log(chalk.gray(` [dry-run] remove ${path.relative(projectRoot, settingsFile)}`));
175
- } else {
176
- await fs.remove(settingsFile);
177
- console.log(chalk.red(` ✗ removed ${path.relative(projectRoot, settingsFile)}`));
186
+ if (platform === 'codex') {
187
+ await removeGeneratedCodexConfig(projectRoot, dryRun);
188
+ const legacySettingsFile = path.join(projectRoot, '.codex', 'prizmkit-settings.json');
189
+ if (await fs.pathExists(legacySettingsFile)) {
190
+ if (dryRun) {
191
+ console.log(chalk.gray(' [dry-run] remove .codex/prizmkit-settings.json (legacy)'));
192
+ } else {
193
+ await fs.remove(legacySettingsFile);
194
+ console.log(chalk.red(' ✗ removed .codex/prizmkit-settings.json (legacy)'));
195
+ }
196
+ }
197
+ } else {
198
+ const settingsFile = platform === 'claude'
199
+ ? path.join(projectRoot, '.claude', 'settings.json')
200
+ : path.join(projectRoot, '.codebuddy', 'settings.json');
201
+ if (await fs.pathExists(settingsFile)) {
202
+ if (dryRun) {
203
+ console.log(chalk.gray(` [dry-run] remove ${path.relative(projectRoot, settingsFile)}`));
204
+ } else {
205
+ await fs.remove(settingsFile);
206
+ console.log(chalk.red(` ✗ removed ${path.relative(projectRoot, settingsFile)}`));
207
+ }
178
208
  }
179
209
  }
180
210
 
@@ -200,10 +230,21 @@ async function removePlatformFiles(platform, projectRoot, manifest, dryRun) {
200
230
  console.log(chalk.red(' ✗ removed ~/.codebuddy/teams/prizm-dev-team/'));
201
231
  }
202
232
  }
233
+ } else if (platform === 'codex') {
234
+ const teamInfo = path.join(projectRoot, '.codex', 'team-info.json');
235
+ if (await fs.pathExists(teamInfo)) {
236
+ if (dryRun) {
237
+ console.log(chalk.gray(' [dry-run] remove .codex/team-info.json'));
238
+ } else {
239
+ await fs.remove(teamInfo);
240
+ console.log(chalk.red(' ✗ removed .codex/team-info.json'));
241
+ }
242
+ }
203
243
  }
204
244
 
205
245
  // Project memory file
206
- const memoryFile = platform === 'claude' ? 'CLAUDE.md' : 'CODEBUDDY.md';
246
+ const memoryFile = projectMemoryFile(platform);
247
+ if (!memoryFile) return;
207
248
  const memoryPath = path.join(projectRoot, memoryFile);
208
249
  if (await fs.pathExists(memoryPath)) {
209
250
  if (dryRun) {
@@ -225,6 +266,48 @@ async function removePlatformFiles(platform, projectRoot, manifest, dryRun) {
225
266
  console.log(chalk.red(' ✗ removed .claude/command-assets/'));
226
267
  }
227
268
  }
269
+ } else if (platform === 'codex') {
270
+ const agentsSkillsDir = path.join(projectRoot, '.agents', 'skills');
271
+ if (await fs.pathExists(agentsSkillsDir)) {
272
+ if (dryRun) {
273
+ console.log(chalk.gray(' [dry-run] remove .agents/skills/ if empty'));
274
+ } else if ((await fs.readdir(agentsSkillsDir)).length === 0) {
275
+ await fs.remove(agentsSkillsDir);
276
+ console.log(chalk.red(' ✗ removed .agents/skills/'));
277
+ }
278
+ }
279
+ const agentsDir = path.join(projectRoot, '.agents');
280
+ if (await fs.pathExists(agentsDir)) {
281
+ if (dryRun) {
282
+ console.log(chalk.gray(' [dry-run] remove .agents/ if empty'));
283
+ } else if ((await fs.readdir(agentsDir)).length === 0) {
284
+ await fs.remove(agentsDir);
285
+ console.log(chalk.red(' ✗ removed .agents/'));
286
+ }
287
+ }
288
+
289
+ const codexAgentsDir = path.join(projectRoot, '.codex', 'agents');
290
+ if (await fs.pathExists(codexAgentsDir)) {
291
+ if (dryRun) {
292
+ console.log(chalk.gray(' [dry-run] remove .codex/agents/ if empty'));
293
+ } else if ((await fs.readdir(codexAgentsDir)).length === 0) {
294
+ await fs.remove(codexAgentsDir);
295
+ console.log(chalk.red(' ✗ removed .codex/agents/'));
296
+ }
297
+ }
298
+
299
+ const codexDir = path.join(projectRoot, '.codex');
300
+ if (await fs.pathExists(codexDir)) {
301
+ if (dryRun) {
302
+ console.log(chalk.gray(' [dry-run] remove .codex/ if empty'));
303
+ } else {
304
+ const remaining = await fs.readdir(codexDir);
305
+ if (remaining.length === 0) {
306
+ await fs.remove(codexDir);
307
+ console.log(chalk.red(' ✗ removed .codex/'));
308
+ }
309
+ }
310
+ }
228
311
  }
229
312
  }
230
313
 
@@ -265,11 +348,13 @@ export async function runConfig(directory, options = {}) {
265
348
  try { userConfig = await fs.readJSON(configPath); } catch { /* ignore */ }
266
349
  }
267
350
 
268
- // Filesystem-based platform detection (overrides manifest)
269
- const detectedPlatform = await detectInstalledPlatforms(projectRoot) || oldManifest.platform || 'claude';
351
+ // Existing manifests are the source of truth. Filesystem detection is only a
352
+ // legacy fallback for manifests created before platform was recorded.
353
+ const detectedPlatform = oldManifest.platform || await detectInstalledPlatforms(projectRoot) || 'claude';
270
354
 
271
355
  const currentConfig = {
272
356
  platform: detectedPlatform,
357
+ runtime: normalizeRuntime(oldManifest.runtime || 'unix'),
273
358
  suite: oldManifest.suite || 'core',
274
359
  rules: oldManifest.options?.rules || 'recommended',
275
360
  team: oldManifest.options?.team ?? true,
@@ -280,6 +365,7 @@ export async function runConfig(directory, options = {}) {
280
365
  // Display current config
281
366
  console.log(chalk.bold(' 当前配置:'));
282
367
  console.log(` 平台: ${chalk.cyan(platformLabel(currentConfig.platform))}`);
368
+ console.log(` 运行系统: ${chalk.cyan(runtimeLabel(currentConfig.runtime))}`);
283
369
  console.log(` AI CLI: ${currentConfig.aiCli ? chalk.cyan(currentConfig.aiCli) : chalk.gray('(未设置)')}`);
284
370
  console.log(` 技能套件: ${chalk.cyan(currentConfig.suite)}`);
285
371
  console.log(` 规则: ${chalk.cyan(currentConfig.rules)}`);
@@ -290,18 +376,20 @@ export async function runConfig(directory, options = {}) {
290
376
  // ── Phase 2: PROMPT for new config ───────────────────────────────────────
291
377
 
292
378
  let newConfig;
379
+ const optionRuntime = options.runtime !== undefined ? normalizeRuntime(options.runtime) : undefined;
293
380
 
294
381
  if (nonInteractive) {
295
382
  // Non-interactive: only change what's explicitly specified via CLI options
296
- if (!options.platform && !options.skills && !options.rules
383
+ if (!options.platform && !options.skills && !options.rules && options.runtime === undefined
297
384
  && options.aiCli === undefined && options.team === undefined && options.pipeline === undefined) {
298
385
  console.log(chalk.yellow(' ⚠ 非交互式模式下未指定任何变更。'));
299
- console.log(chalk.gray(' 使用 --platform, --skills, --rules, --ai-cli, --team/--no-team, --pipeline/--no-pipeline'));
386
+ console.log(chalk.gray(' 使用 --platform, --runtime, --skills, --rules, --ai-cli, --team/--no-team, --pipeline/--no-pipeline'));
300
387
  return;
301
388
  }
302
389
 
303
390
  newConfig = {
304
391
  platform: options.platform || currentConfig.platform,
392
+ runtime: optionRuntime || currentConfig.runtime,
305
393
  suite: options.skills || currentConfig.suite,
306
394
  rules: options.rules || currentConfig.rules,
307
395
  team: options.team !== undefined ? options.team : currentConfig.team,
@@ -315,6 +403,7 @@ export async function runConfig(directory, options = {}) {
315
403
  try {
316
404
  console.log(chalk.bold(' 选择新配置(按 Enter 保持当前值):\n'));
317
405
 
406
+ const newRuntime = optionRuntime || await selectRuntime(currentConfig.runtime);
318
407
  const newPlatform = await selectPlatform(currentConfig.platform, detected);
319
408
  const newAiCli = await selectAiCli(currentConfig.aiCli, detected);
320
409
 
@@ -326,6 +415,7 @@ export async function runConfig(directory, options = {}) {
326
415
 
327
416
  newConfig = {
328
417
  platform: newPlatform,
418
+ runtime: newRuntime,
329
419
  suite: newSuite,
330
420
  rules: newRules,
331
421
  team: newTeam,
@@ -347,6 +437,9 @@ export async function runConfig(directory, options = {}) {
347
437
  if (newConfig.platform !== currentConfig.platform) {
348
438
  changes.push(`平台: ${platformLabel(currentConfig.platform)} → ${platformLabel(newConfig.platform)}`);
349
439
  }
440
+ if (newConfig.runtime !== currentConfig.runtime) {
441
+ changes.push(`运行系统: ${runtimeLabel(currentConfig.runtime)} → ${runtimeLabel(newConfig.runtime)}`);
442
+ }
350
443
  if (newConfig.aiCli !== currentConfig.aiCli) {
351
444
  changes.push(`AI CLI: ${currentConfig.aiCli || '(未设置)'} → ${newConfig.aiCli || '(未设置)'}`);
352
445
  }
@@ -419,9 +512,8 @@ export async function runConfig(directory, options = {}) {
419
512
  const agentsDir = getAgentsDir();
420
513
  const newAgentFiles = (await fs.readdir(agentsDir)).filter(f => f.endsWith('.md'));
421
514
  const rulesMeta = await loadRulesMetadata();
422
- const rulesPresetDef = rulesMeta.presets[newConfig.rules] || rulesMeta.presets.recommended;
423
- const newRuleFiles = (rulesPresetDef?.rules || []).map(name => `${name}.md`);
424
- const newPipelineFiles = newConfig.pipeline ? resolvePipelineFileList() : [];
515
+ const newRuleFiles = resolveRuleNamesForRuntime(rulesMeta, newConfig.rules, newConfig.runtime).map(name => `${name}.md`);
516
+ const newPipelineFiles = newConfig.pipeline ? resolvePipelineFileList(newConfig.runtime) : [];
425
517
 
426
518
  // 5a. Migrate project memory before removing dropped platforms
427
519
  for (const p of platformsToRemove) {
@@ -434,13 +526,15 @@ export async function runConfig(directory, options = {}) {
434
526
  await removePlatformFiles(p, projectRoot, oldManifest, false);
435
527
  }
436
528
 
437
- // 5c. Handle skill/rule diff for existing platforms (suite or rules changed)
438
- if (newConfig.suite !== currentConfig.suite || newConfig.rules !== currentConfig.rules) {
529
+ // 5c. Handle skill/rule diff for existing platforms (suite, rules, or runtime changed)
530
+ if (newConfig.suite !== currentConfig.suite
531
+ || newConfig.rules !== currentConfig.rules
532
+ || newConfig.runtime !== currentConfig.runtime) {
439
533
  const newTempManifest = buildManifest({
440
534
  version: pkg.version, platform: newConfig.platform, suite: newConfig.suite,
441
535
  skills: newSkillList, agents: newAgentFiles, rules: newRuleFiles,
442
536
  pipeline: newPipelineFiles, team: newConfig.team, aiCli: newConfig.aiCli,
443
- rulesPreset: newConfig.rules, extras: oldManifest?.files?.extras || [],
537
+ runtime: newConfig.runtime, rulesPreset: newConfig.rules, extras: oldManifest?.files?.extras || [],
444
538
  });
445
539
  const diff = diffManifest(oldManifest, newTempManifest);
446
540
 
@@ -460,6 +554,7 @@ export async function runConfig(directory, options = {}) {
460
554
  // 5d. Install/update files for new + existing platforms
461
555
  const allTargetPlatforms = [...platformsToAdd, ...platformsToUpdate];
462
556
  const needsReinstall = newConfig.platform !== currentConfig.platform
557
+ || newConfig.runtime !== currentConfig.runtime
463
558
  || newConfig.suite !== currentConfig.suite
464
559
  || newConfig.rules !== currentConfig.rules
465
560
  || newConfig.team !== currentConfig.team;
@@ -471,13 +566,13 @@ export async function runConfig(directory, options = {}) {
471
566
  console.log(chalk.bold(`\n ${isNew ? '安装' : '更新'} ${platformLabel(p)} 环境...\n`));
472
567
 
473
568
  console.log(chalk.blue(' Skills:'));
474
- await installSkills(p, newSkillList, projectRoot, false);
569
+ await installSkills(p, newSkillList, projectRoot, false, newConfig.runtime);
475
570
 
476
571
  console.log(chalk.blue('\n Agents:'));
477
572
  await installAgents(p, projectRoot, false);
478
573
 
479
574
  console.log(chalk.blue('\n Settings & Rules:'));
480
- await installSettings(p, projectRoot, { pipeline: newConfig.pipeline, rules: newConfig.rules }, false);
575
+ await installSettings(p, projectRoot, { pipeline: newConfig.pipeline, rules: newConfig.rules }, false, newConfig.runtime);
481
576
 
482
577
  if (isNew) {
483
578
  console.log(chalk.blue('\n Project Memory:'));
@@ -492,10 +587,13 @@ export async function runConfig(directory, options = {}) {
492
587
  }
493
588
 
494
589
  // 5e. Pipeline changes
495
- if (newConfig.pipeline !== currentConfig.pipeline) {
590
+ if (newConfig.pipeline !== currentConfig.pipeline || (newConfig.pipeline && newConfig.runtime !== currentConfig.runtime)) {
496
591
  if (newConfig.pipeline) {
497
592
  console.log(chalk.blue('\n 安装 Pipeline:'));
498
- await installPipeline(projectRoot, false);
593
+ await installPipeline(projectRoot, false, {
594
+ runtime: newConfig.runtime,
595
+ previousRuntime: currentConfig.runtime,
596
+ });
499
597
  } else {
500
598
  console.log(chalk.blue('\n 移除 Pipeline 框架文件(保留 state/):'));
501
599
  const oldPipelineFiles = oldManifest?.files?.pipeline || [];
@@ -509,7 +607,16 @@ export async function runConfig(directory, options = {}) {
509
607
  console.log(chalk.blue('\n Gitignore:'));
510
608
  await installGitignore(projectRoot, { pipeline: newConfig.pipeline }, false);
511
609
 
512
- // 5g. Update .prizmkit/config.json (ai_cli)
610
+ // 5g. Runtime-aware git hook and PrizmKit helper scripts
611
+ if (newConfig.runtime !== currentConfig.runtime) {
612
+ console.log(chalk.blue('\n Git Hook:'));
613
+ await installGitHook(projectRoot, false, newConfig.runtime);
614
+
615
+ console.log(chalk.blue('\n PrizmKit Scripts:'));
616
+ await installPrizmkitScripts(projectRoot, false, newConfig.runtime);
617
+ }
618
+
619
+ // 5h. Update .prizmkit/config.json (ai_cli)
513
620
  if (newConfig.aiCli !== currentConfig.aiCli) {
514
621
  const prizmkitDir = path.join(projectRoot, '.prizmkit');
515
622
  await fs.ensureDir(prizmkitDir);
@@ -530,6 +637,7 @@ export async function runConfig(directory, options = {}) {
530
637
  const newManifest = buildManifest({
531
638
  version: pkg.version,
532
639
  platform: newConfig.platform,
640
+ runtime: newConfig.runtime,
533
641
  suite: newConfig.suite,
534
642
  skills: newSkillList,
535
643
  agents: newAgentFiles,
@@ -559,6 +667,7 @@ export async function runConfig(directory, options = {}) {
559
667
 
560
668
  console.log(chalk.gray(' 新配置:'));
561
669
  console.log(chalk.gray(` 平台: ${platformLabel(newConfig.platform)}`));
670
+ console.log(chalk.gray(` 运行系统: ${runtimeLabel(newConfig.runtime)}`));
562
671
  console.log(chalk.gray(` AI CLI: ${newConfig.aiCli || '(未设置)'}`));
563
672
  console.log(chalk.gray(` 技能套件: ${newConfig.suite}`));
564
673
  console.log(chalk.gray(` 规则: ${newConfig.rules}`));
@@ -4,11 +4,12 @@
4
4
  */
5
5
 
6
6
  import { execFileSync } from 'child_process';
7
+ import os from 'os';
7
8
 
8
9
  /**
9
10
  * 允许检测的命令白名单(仅用于 which 检测)
10
11
  */
11
- const ALLOWED_COMMANDS = ['cbc', 'claude'];
12
+ const ALLOWED_COMMANDS = ['cbc', 'claude', 'codex'];
12
13
 
13
14
  /**
14
15
  * 检查命令是否存在于 PATH 中
@@ -18,7 +19,8 @@ function commandExists(cmd) {
18
19
  throw new Error(`Unknown command: ${cmd}`);
19
20
  }
20
21
  try {
21
- execFileSync('which', [cmd], { stdio: 'ignore' });
22
+ const isWindows = os.platform() === 'win32';
23
+ execFileSync(isWindows ? 'where.exe' : 'which', [cmd], { stdio: 'ignore' });
22
24
  return true;
23
25
  } catch {
24
26
  return false;
@@ -31,26 +33,32 @@ function commandExists(cmd) {
31
33
  * 注意:platform 控制目录结构(.codebuddy/ 或 .claude/),
32
34
  * suggestedCli 是实际运行的可执行命令,二者相互独立。
33
35
  *
34
- * @returns {{ cbc: boolean, claude: boolean, claudeInternal: boolean, suggested: string, suggestedCli: string }}
36
+ * @returns {{ cbc: boolean, claude: boolean, codex: boolean, suggested: string, suggestedCli: string }}
35
37
  */
36
38
  export function detectPlatform() {
37
39
  const cbc = commandExists('cbc');
38
40
  const claude = commandExists('claude');
41
+ const codex = commandExists('codex');
39
42
 
40
- // platform 建议:基于目录结构,优先 both
43
+ // platform 建议:基于目录结构
41
44
  let suggested = 'codebuddy';
42
- if ((cbc) && (claude)) {
45
+ if (cbc && claude && codex) {
46
+ suggested = 'all';
47
+ } else if ((cbc) && (claude)) {
43
48
  suggested = 'both';
49
+ } else if (codex && !cbc && !claude) {
50
+ suggested = 'codex';
44
51
  } else if ((claude) && !cbc) {
45
52
  suggested = 'claude';
46
53
  } else if (cbc && !claude) {
47
54
  suggested = 'codebuddy';
48
55
  }
49
56
 
50
- // CLI 命令建议:优先 cbc,其次 claude-internal,再次 claude
57
+ // CLI 命令建议:优先 cbc,其次 claude,再次 codex
51
58
  let suggestedCli = '';
52
59
  if (cbc) suggestedCli = 'cbc';
53
60
  else if (claude) suggestedCli = 'claude';
61
+ else if (codex) suggestedCli = 'codex';
54
62
 
55
- return { cbc, claude, suggested, suggestedCli };
56
- }
63
+ return { cbc, claude, codex, suggested, suggestedCli };
64
+ }
@@ -7,20 +7,21 @@
7
7
  * - Install all: installs all skills from a repo (e.g. pbakaus/impeccable)
8
8
  *
9
9
  * When run non-interactively (stdio: 'pipe'), npx skills writes canonical
10
- * .agents/skills/<name>/SKILL.md files. We copy those to the selected
11
- * platform dir(s), then clean up .agents/ and skills-lock.json.
10
+ * .agents/skills/<name>/SKILL.md files. Codex uses that location directly;
11
+ * other platforms get copied into their platform-specific skill directories.
12
12
  */
13
13
 
14
14
  import { execSync } from 'node:child_process';
15
15
  import path from 'path';
16
16
  import fs from 'fs-extra';
17
+ import { expandPlatforms } from './platforms.js';
17
18
 
18
19
  /**
19
20
  * @param {Object} skill - Skill definition from external_skills.known
20
21
  * skill.repo {string} - GitHub short ref (e.g. "pbakaus/impeccable") or full URL
21
22
  * skill.name {string} - Skill name (used for display / single-skill mode)
22
23
  * skill.installAll {boolean} - If true, install all skills from the repo
23
- * @param {string} platform - 'claude' | 'codebuddy' | 'both'
24
+ * @param {string} platform - 'claude' | 'codebuddy' | 'codex' | 'both' | 'all'
24
25
  * @param {string} projectRoot - Target project root
25
26
  * @param {boolean} dryRun
26
27
  */
@@ -64,6 +65,9 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
64
65
  for (const skillName of skillDirs) {
65
66
  const srcDir = path.join(agentsSkillsDir, skillName);
66
67
  const destDir = path.join(targetDir, skillName);
68
+ if (path.resolve(srcDir) === path.resolve(destDir)) {
69
+ continue;
70
+ }
67
71
  await fs.remove(destDir).catch(() => {});
68
72
  await fs.copy(srcDir, destDir);
69
73
  }
@@ -79,14 +83,20 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
79
83
 
80
84
  for (const targetDir of getTargetDirs(platform, projectRoot)) {
81
85
  const skillDir = path.join(targetDir, skill.name);
86
+ if (path.resolve(skillDir) === path.resolve(path.dirname(canonicalSkillMd))) {
87
+ continue;
88
+ }
82
89
  await fs.remove(skillDir).catch(() => {});
83
90
  await fs.ensureDir(skillDir);
84
91
  await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
85
92
  }
86
93
  }
87
94
 
88
- // Clean up npx skills artifacts after copying
89
- await fs.remove(path.join(projectRoot, '.agents')).catch(() => {});
95
+ // Clean up npx skills artifacts after copying. Preserve .agents/skills when
96
+ // Codex is selected because it is the official repository skill location.
97
+ if (!expandPlatforms(platform).includes('codex')) {
98
+ await fs.remove(path.join(projectRoot, '.agents')).catch(() => {});
99
+ }
90
100
  await fs.remove(path.join(projectRoot, 'skills-lock.json')).catch(() => {});
91
101
  }
92
102
 
@@ -95,21 +105,23 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
95
105
  * Call this once after all external skills have been installed.
96
106
  *
97
107
  * @param {string} projectRoot
98
- * @param {string} platform - 'claude' | 'codebuddy' | 'both'
108
+ * @param {string} platform - 'claude' | 'codebuddy' | 'codex' | 'both' | 'all'
99
109
  */
100
110
  export async function cleanExternalSkillArtifacts(projectRoot, platform) {
101
111
  const keepDirs = new Set(
102
112
  getTargetDirs(platform, projectRoot).map(d => path.resolve(d))
103
113
  );
104
114
 
115
+ const selectedPlatforms = expandPlatforms(platform);
105
116
  const dirsToCheck = [
106
- '.agents',
117
+ selectedPlatforms.includes('codex') ? null : '.agents',
107
118
  '.agent',
108
119
  '.trae',
109
120
  'skills',
110
121
  '.claude/skills',
111
122
  '.codebuddy/skills',
112
- ];
123
+ '.codex/skills',
124
+ ].filter(Boolean);
113
125
 
114
126
  for (const dir of dirsToCheck) {
115
127
  const abs = path.join(projectRoot, dir);
@@ -135,7 +147,7 @@ export async function cleanExternalSkillArtifacts(projectRoot, platform) {
135
147
  }
136
148
 
137
149
  // Remove empty parent dirs left behind (e.g. .codebuddy/ after removing .codebuddy/skills)
138
- const parentDirs = ['.claude', '.codebuddy', '.trae', '.agent'];
150
+ const parentDirs = ['.claude', '.codebuddy', '.codex', '.trae', '.agent'];
139
151
  for (const dir of parentDirs) {
140
152
  const abs = path.join(projectRoot, dir);
141
153
  if (!await fs.pathExists(abs)) continue;
@@ -155,14 +167,9 @@ export async function cleanExternalSkillArtifacts(projectRoot, platform) {
155
167
  }
156
168
 
157
169
  function getTargetDirs(platform, projectRoot) {
158
- if (platform === 'both') {
159
- return [
160
- path.join(projectRoot, '.claude', 'skills'),
161
- path.join(projectRoot, '.codebuddy', 'skills'),
162
- ];
163
- }
164
- if (platform === 'codebuddy') {
165
- return [path.join(projectRoot, '.codebuddy', 'skills')];
166
- }
167
- return [path.join(projectRoot, '.claude', 'skills')];
170
+ return expandPlatforms(platform).map(p => {
171
+ if (p === 'codebuddy') return path.join(projectRoot, '.codebuddy', 'skills');
172
+ if (p === 'codex') return path.join(projectRoot, '.agents', 'skills');
173
+ return path.join(projectRoot, '.claude', 'skills');
174
+ });
168
175
  }