prizmkit 1.1.58 → 1.1.61

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 (205) 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/agents/prizm-dev-team-dev.md +6 -5
  10. package/bundled/dev-pipeline/.env.example +2 -1
  11. package/bundled/dev-pipeline/README.md +10 -7
  12. package/bundled/dev-pipeline/lib/common.sh +278 -37
  13. package/bundled/dev-pipeline/run-bugfix.sh +10 -61
  14. package/bundled/dev-pipeline/run-feature.sh +10 -78
  15. package/bundled/dev-pipeline/run-recovery.sh +10 -46
  16. package/bundled/dev-pipeline/run-refactor.sh +10 -61
  17. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
  18. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
  19. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
  20. package/bundled/dev-pipeline/scripts/utils.py +6 -4
  21. package/bundled/dev-pipeline-windows/.env.example +28 -0
  22. package/bundled/dev-pipeline-windows/README.md +30 -0
  23. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
  24. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
  25. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
  26. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
  27. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
  28. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
  29. package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
  30. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
  31. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
  32. package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
  33. package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
  34. package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
  35. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
  36. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
  37. package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
  38. package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
  39. package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
  40. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
  41. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
  42. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
  43. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
  44. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
  45. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
  46. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
  47. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
  48. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
  49. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
  50. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
  51. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
  52. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
  53. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
  54. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
  55. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
  56. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
  57. package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
  59. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
  60. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
  61. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
  62. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
  63. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
  64. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
  65. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
  66. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
  67. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
  68. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
  69. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
  70. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
  71. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
  72. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
  73. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
  74. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
  75. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
  76. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
  77. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
  78. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
  79. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
  80. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
  81. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
  82. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
  83. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
  84. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
  85. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
  86. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
  87. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
  88. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
  89. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
  90. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
  91. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
  92. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
  93. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
  94. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
  95. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
  96. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
  97. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
  98. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
  99. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
  100. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
  101. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
  102. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
  103. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
  104. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
  105. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
  106. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
  107. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
  108. package/bundled/rules/prizm/prizm-documentation.md +2 -4
  109. package/bundled/rules/prizm/prizm-progressive-loading.md +2 -1
  110. package/bundled/skills/_metadata.json +1 -1
  111. package/bundled/skills/app-planner/SKILL.md +24 -16
  112. package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
  113. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  114. package/bundled/skills/feature-planner/SKILL.md +9 -2
  115. package/bundled/skills/prizmkit-implement/SKILL.md +1 -0
  116. package/bundled/skills/prizmkit-init/SKILL.md +8 -7
  117. package/bundled/skills/prizmkit-init/references/config-schema.md +3 -1
  118. package/bundled/skills/prizmkit-plan/SKILL.md +2 -0
  119. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +6 -1
  120. package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +2 -2
  121. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +2 -1
  122. package/bundled/skills/prizmkit-prizm-docs/references/op-validate.md +2 -2
  123. package/bundled/skills/prizmkit-retrospective/SKILL.md +2 -0
  124. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +2 -0
  125. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +2 -0
  126. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
  127. package/bundled/skills-windows/app-planner/SKILL.md +639 -0
  128. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
  129. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
  130. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
  131. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
  132. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
  133. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
  134. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
  135. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
  136. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
  137. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
  138. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
  139. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
  140. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
  141. package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
  142. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
  143. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
  144. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
  145. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
  146. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
  147. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
  148. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
  149. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
  150. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
  151. package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
  152. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
  153. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
  154. package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
  155. package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
  156. package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
  157. package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
  158. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
  159. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
  160. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
  161. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
  162. package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
  163. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
  164. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
  165. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
  166. package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
  167. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
  168. package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
  169. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
  170. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
  171. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
  172. package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
  173. package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
  174. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
  175. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +70 -0
  176. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
  177. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
  178. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
  179. package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
  180. package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
  181. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
  182. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
  183. package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
  184. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
  185. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
  186. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
  187. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
  188. package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
  189. package/bundled/templates/hooks/diff-prizm-docs.sh +1 -1
  190. package/bundled/templates/hooks/prizm-pre-commit.sh +53 -4
  191. package/bundled/templates/hooks/validate-prizm-docs.sh +62 -7
  192. package/bundled/templates/project-memory-template.md +2 -1
  193. package/package.json +3 -2
  194. package/src/clean.js +73 -2
  195. package/src/config.js +167 -50
  196. package/src/detect-platform.js +15 -9
  197. package/src/external-skills.js +26 -19
  198. package/src/index.js +28 -10
  199. package/src/manifest.js +6 -2
  200. package/src/metadata.js +43 -5
  201. package/src/platforms.js +38 -0
  202. package/src/prompts.js +50 -9
  203. package/src/runtimes.js +20 -0
  204. package/src/scaffold.js +447 -114
  205. package/src/upgrade.js +87 -44
package/src/scaffold.js CHANGED
@@ -10,13 +10,12 @@
10
10
  import chalk from 'chalk';
11
11
  import fs from 'fs-extra';
12
12
  import path from 'path';
13
- import { readFileSync, existsSync } from 'fs';
14
- import { fileURLToPath } from 'url';
13
+ import { readFileSync } from 'fs';
14
+ import { fileURLToPath, pathToFileURL } from 'url';
15
15
  import { dirname, join } from 'path';
16
16
  import { execSync } from 'child_process';
17
17
  import {
18
18
  loadMetadata,
19
- getSkillsDir,
20
19
  getAgentsDir,
21
20
  getTeamDir,
22
21
  getTemplatesDir,
@@ -24,12 +23,16 @@ import {
24
23
  getAdaptersDir,
25
24
  getRulesDir,
26
25
  loadRulesMetadata,
26
+ getSkillCandidatePaths,
27
27
  } from './metadata.js';
28
28
  import { generateGitignore } from './gitignore-template.js';
29
29
  import { buildManifest, writeManifest } from './manifest.js';
30
+ import { defaultCliForPlatform, expandPlatforms, isKnownPlatform, platformLabel, projectMemoryFile } from './platforms.js';
31
+ import { normalizeRuntime, runtimeLabel, RUNTIME_UNIX, RUNTIME_WINDOWS } from './runtimes.js';
30
32
 
31
33
  const __scaffoldDirname = dirname(fileURLToPath(import.meta.url));
32
34
  const scaffoldPkg = JSON.parse(readFileSync(join(__scaffoldDirname, '..', 'package.json'), 'utf-8'));
35
+ const PIPELINE_INSTALL_EXCLUDE = new Set(['tests', 'docs', '__pycache__', 'node_modules', '.DS_Store']);
33
36
 
34
37
  // ============================================================
35
38
  // Adapter 动态加载
@@ -42,12 +45,12 @@ const scaffoldPkg = JSON.parse(readFileSync(join(__scaffoldDirname, '..', 'packa
42
45
  async function loadAdapter(platform, adapterName) {
43
46
  const adaptersDir = getAdaptersDir();
44
47
  const adapterPath = path.join(adaptersDir, platform, adapterName);
45
- return import(adapterPath);
48
+ return import(pathToFileURL(adapterPath).href);
46
49
  }
47
50
 
48
51
  async function loadSharedFrontmatter() {
49
52
  const adaptersDir = getAdaptersDir();
50
- return import(path.join(adaptersDir, 'shared', 'frontmatter.js'));
53
+ return import(pathToFileURL(path.join(adaptersDir, 'shared', 'frontmatter.js')).href);
51
54
  }
52
55
 
53
56
  // ============================================================
@@ -81,6 +84,13 @@ export async function resolveSkillList(suite) {
81
84
  return suiteDef.skills;
82
85
  }
83
86
 
87
+ export function resolveRuleNamesForRuntime(rulesMeta, rulesPreset = 'recommended', runtime = 'unix') {
88
+ const preset = rulesMeta.presets[rulesPreset] || rulesMeta.presets.recommended;
89
+ const ruleNames = preset?.rules || [];
90
+ if (normalizeRuntime(runtime) !== RUNTIME_WINDOWS) return ruleNames;
91
+ return ruleNames.filter(name => name !== 'general/prefer-linux-commands');
92
+ }
93
+
84
94
  // ============================================================
85
95
  // 安装函数
86
96
  // ============================================================
@@ -88,8 +98,7 @@ export async function resolveSkillList(suite) {
88
98
  /**
89
99
  * 安装 Skills(纯 Copy 模式)
90
100
  */
91
- export async function installSkills(platform, skills, projectRoot, dryRun) {
92
- const skillsDir = getSkillsDir();
101
+ export async function installSkills(platform, skills, projectRoot, dryRun, runtime = 'unix') {
93
102
  const { parseFrontmatter, buildMarkdown } = await loadSharedFrontmatter();
94
103
 
95
104
  // Load metadata to get skill -> category + subcategory mapping
@@ -103,9 +112,13 @@ export async function installSkills(platform, skills, projectRoot, dryRun) {
103
112
 
104
113
  // Load Claude command adapter for skill conversion
105
114
  let convertSkillToCommand;
115
+ let convertSkillToCodex;
106
116
  if (platform === 'claude') {
107
117
  const commandAdapter = await loadAdapter('claude', 'command-adapter.js');
108
118
  convertSkillToCommand = commandAdapter.convertSkillToCommand;
119
+ } else if (platform === 'codex') {
120
+ const codexSkillAdapter = await loadAdapter('codex', 'skill-adapter.js');
121
+ convertSkillToCodex = codexSkillAdapter.convertSkill;
109
122
  }
110
123
 
111
124
  for (const skillName of skills) {
@@ -128,21 +141,9 @@ export async function installSkills(platform, skills, projectRoot, dryRun) {
128
141
  continue;
129
142
  }
130
143
 
131
- // Build path: prefer hierarchical layout (<skillsDir>/<category>/[<subcategory>/]/<skillName>),
132
- // fallback to flat bundled layout (<skillsDir>/<skillName>) for npm package compatibility.
133
- let corePath = subcategory
134
- ? path.resolve(skillsDir, category, subcategory, skillName)
135
- : path.resolve(skillsDir, category, skillName);
136
- // Ensure resolved path stays within skillsDir
137
- if (!corePath.startsWith(path.resolve(skillsDir))) {
138
- console.warn(` ⚠ Skill ${skillName} path escapes skills directory, skipping`);
139
- continue;
140
- }
141
- if (!await fs.pathExists(corePath)) {
142
- corePath = path.resolve(skillsDir, skillName);
143
- if (!corePath.startsWith(path.resolve(skillsDir))) continue;
144
- }
145
- if (!await fs.pathExists(corePath)) continue;
144
+ const corePath = getSkillCandidatePaths(skillName, metadata.skills[skillName], runtime)
145
+ .find(candidatePath => fs.pathExistsSync(candidatePath));
146
+ if (!corePath) continue;
146
147
 
147
148
  const stat = await fs.stat(corePath);
148
149
  if (!stat.isDirectory()) continue;
@@ -205,6 +206,26 @@ export async function installSkills(platform, skills, projectRoot, dryRun) {
205
206
  }
206
207
  }
207
208
  console.log(chalk.green(` ✓ .claude/commands/${skillName}.md`));
209
+ } else if (platform === 'codex') {
210
+ const content = await fs.readFile(skillMdPath, 'utf8');
211
+ const converted = convertSkillToCodex(content, skillName);
212
+ const targetDir = path.join(projectRoot, '.agents', 'skills', skillName);
213
+
214
+ if (dryRun) {
215
+ console.log(chalk.gray(` [dry-run] .agents/skills/${skillName}/`));
216
+ continue;
217
+ }
218
+
219
+ await fs.ensureDir(targetDir);
220
+ await fs.writeFile(path.join(targetDir, 'SKILL.md'), converted);
221
+
222
+ const codexEntries = await fs.readdir(corePath, { withFileTypes: true });
223
+ for (const entry of codexEntries) {
224
+ if (entry.isDirectory()) {
225
+ await fs.copy(path.join(corePath, entry.name), path.join(targetDir, entry.name));
226
+ }
227
+ }
228
+ console.log(chalk.green(` ✓ .agents/skills/${skillName}/`));
208
229
  }
209
230
  }
210
231
  }
@@ -221,6 +242,9 @@ export async function installAgents(platform, projectRoot, dryRun) {
221
242
  if (platform === 'claude') {
222
243
  const agentAdapter = await loadAdapter('claude', 'agent-adapter.js');
223
244
  convertAgentFn = agentAdapter.convertAgent;
245
+ } else if (platform === 'codex') {
246
+ const agentAdapter = await loadAdapter('codex', 'agent-adapter.js');
247
+ convertAgentFn = agentAdapter.convertAgent;
224
248
  }
225
249
 
226
250
  for (const file of agentFiles) {
@@ -247,6 +271,19 @@ export async function installAgents(platform, projectRoot, dryRun) {
247
271
  await fs.ensureDir(targetDir);
248
272
  await fs.writeFile(path.join(targetDir, file), converted);
249
273
  console.log(chalk.green(` ✓ .claude/agents/${file}`));
274
+ } else if (platform === 'codex') {
275
+ const baseName = path.basename(file, '.md');
276
+ const targetFile = `${baseName}.toml`;
277
+ const converted = convertAgentFn(content, { name: baseName });
278
+ const targetDir = path.join(projectRoot, '.codex', 'agents');
279
+ if (dryRun) {
280
+ console.log(chalk.gray(` [dry-run] .codex/agents/${targetFile}`));
281
+ continue;
282
+ }
283
+ await fs.ensureDir(targetDir);
284
+ await fs.writeFile(path.join(targetDir, targetFile), converted);
285
+ await fs.remove(path.join(targetDir, file));
286
+ console.log(chalk.green(` ✓ .codex/agents/${targetFile}`));
250
287
  }
251
288
  }
252
289
  }
@@ -340,18 +377,50 @@ export async function installTeamConfig(platform, projectRoot, dryRun) {
340
377
  );
341
378
 
342
379
  console.log(chalk.green(` ✓ .claude/team-info.json`));
380
+ } else if (platform === 'codex') {
381
+ const codexDir = path.join(projectRoot, '.codex');
382
+
383
+ if (dryRun) {
384
+ console.log(chalk.gray(` [dry-run] .codex/team-info.json`));
385
+ return;
386
+ }
387
+
388
+ await fs.ensureDir(codexDir);
389
+
390
+ const teamConfig = {
391
+ name: teamDef.name,
392
+ description: teamDef.description,
393
+ platform: 'codex',
394
+ orchestrationMode: 'codex-subagents',
395
+ agents: teamDef.members
396
+ .filter(m => m.role !== 'lead')
397
+ .map(m => ({
398
+ name: m.name,
399
+ role: m.role,
400
+ agentFile: `.codex/agents/${m.agentDefinition}.toml`,
401
+ prompt: m.prompt,
402
+ subscriptions: m.subscriptions || [],
403
+ })),
404
+ };
405
+
406
+ await fs.writeFile(
407
+ path.join(codexDir, 'team-info.json'),
408
+ JSON.stringify(teamConfig, null, 2)
409
+ );
410
+
411
+ console.log(chalk.green(` ✓ .codex/team-info.json`));
343
412
  }
344
413
  }
345
414
 
346
415
  /**
347
416
  * 安装平台配置文件(settings/hooks/rules)
348
417
  */
349
- export async function installSettings(platform, projectRoot, options, dryRun) {
418
+ export async function installSettings(platform, projectRoot, options, dryRun, runtime = 'unix') {
419
+ const normalizedRuntime = normalizeRuntime(runtime);
350
420
  // 从文件加载 rules
351
421
  const rulesMeta = await loadRulesMetadata();
352
422
  const rulesPreset = options.rules || 'recommended';
353
- const preset = rulesMeta.presets[rulesPreset] || rulesMeta.presets.recommended;
354
- const ruleNames = preset?.rules || [];
423
+ const ruleNames = resolveRuleNamesForRuntime(rulesMeta, rulesPreset, runtime);
355
424
  const rulesDir = getRulesDir();
356
425
  const ruleFiles = [];
357
426
  for (const name of ruleNames) {
@@ -374,9 +443,12 @@ export async function installSettings(platform, projectRoot, options, dryRun) {
374
443
 
375
444
  await fs.ensureDir(path.dirname(settingsPath));
376
445
 
377
- // Read hook definition from unified template
446
+ // Read hook definition from unified template.
447
+ // Windows runtime intentionally omits POSIX shell hook commands.
378
448
  const templatesDir = getTemplatesDir();
379
- const hookTemplate = await fs.readJSON(path.join(templatesDir, 'hooks', 'commit-intent.json'));
449
+ const hookTemplate = normalizedRuntime === RUNTIME_WINDOWS
450
+ ? { hooks: {} }
451
+ : await fs.readJSON(path.join(templatesDir, 'hooks', 'commit-intent.json'));
380
452
 
381
453
  await fs.writeFile(settingsPath, JSON.stringify(hookTemplate, null, 2));
382
454
  console.log(chalk.green(` ✓ .codebuddy/settings.json`));
@@ -399,13 +471,18 @@ export async function installSettings(platform, projectRoot, options, dryRun) {
399
471
 
400
472
  await fs.ensureDir(claudeDir);
401
473
 
402
- // Read hook definition from unified template
474
+ // Read hook definition from unified template.
475
+ // Windows runtime intentionally omits POSIX shell hook commands.
403
476
  const templatesDir = getTemplatesDir();
404
- const hookTemplate = await fs.readJSON(path.join(templatesDir, 'hooks', 'commit-intent.json'));
477
+ const hookTemplate = normalizedRuntime === RUNTIME_WINDOWS
478
+ ? { hooks: {} }
479
+ : await fs.readJSON(path.join(templatesDir, 'hooks', 'commit-intent.json'));
405
480
 
406
481
  // Settings
407
- const permissions = [
482
+ const permissions = normalizedRuntime === RUNTIME_WINDOWS ? [] : [
408
483
  'Bash(python3 .prizmkit/dev-pipeline/scripts/*)',
484
+ 'Bash(python .prizmkit/dev-pipeline/scripts/*)',
485
+ 'Bash(py -3 .prizmkit/dev-pipeline/scripts/*)',
409
486
  'Bash(git add *)',
410
487
  'Bash(git commit *)',
411
488
  'Bash(git diff *)',
@@ -413,12 +490,15 @@ export async function installSettings(platform, projectRoot, options, dryRun) {
413
490
  'Bash(git log *)',
414
491
  'Bash(jq *)',
415
492
  ];
416
- if (options.pipeline) {
417
- // 动态扫描 pipeline 目录中的 .sh 入口脚本(run-*、launch-*),自动添加执行权限
418
- const pipelineDir = getPipelineDir();
493
+ if (options.pipeline && normalizedRuntime !== RUNTIME_WINDOWS) {
494
+ // 动态扫描 pipeline 目录中的入口脚本,自动添加执行权限
495
+ const pipelineDir = getPipelineDir(runtime);
419
496
  try {
420
497
  const pipelineEntries = await fs.readdir(pipelineDir);
421
- const entryScripts = pipelineEntries.filter(f => /^(run-|launch-|retry-|reset-).*\.sh$/.test(f));
498
+ const entryRe = normalizedRuntime === RUNTIME_WINDOWS
499
+ ? /^(run-|launch-|retry-|reset-).*\.ps1$/i
500
+ : /^(run-|launch-|retry-|reset-).*\.sh$/;
501
+ const entryScripts = pipelineEntries.filter(f => entryRe.test(f));
422
502
  for (const script of entryScripts) {
423
503
  permissions.push(`Bash(./.prizmkit/dev-pipeline/${script} *)`);
424
504
  }
@@ -429,7 +509,7 @@ export async function installSettings(platform, projectRoot, options, dryRun) {
429
509
 
430
510
  const settings = {
431
511
  permissions: { allow: permissions },
432
- hooks: {
512
+ hooks: normalizedRuntime === RUNTIME_WINDOWS ? {} : {
433
513
  ...hookTemplate.hooks,
434
514
  SessionStart: [
435
515
  {
@@ -455,17 +535,183 @@ export async function installSettings(platform, projectRoot, options, dryRun) {
455
535
  const { installRules: installClaudeRules } = await loadAdapter('claude', 'rules-adapter.js');
456
536
  await installClaudeRules(projectRoot, ruleFiles);
457
537
  console.log(chalk.green(` ✓ .claude/rules/ (${ruleFiles.length} 条规则)`));
538
+ } else if (platform === 'codex') {
539
+ const configPath = path.join(projectRoot, '.codex', 'config.toml');
540
+ const legacySettingsPath = path.join(projectRoot, '.codex', 'prizmkit-settings.json');
541
+ const allRuleFileNames = Object.keys(rulesMeta.rules || {})
542
+ .map(name => `${name.includes('/') ? name.split('/').pop() : name}.md`);
543
+
544
+ if (dryRun) {
545
+ console.log(chalk.gray(` [dry-run] .codex/config.toml`));
546
+ console.log(chalk.gray(` [dry-run] .agents/rules/ (${ruleFiles.length} 条规则)`));
547
+ return;
548
+ }
549
+
550
+ await fs.ensureDir(path.dirname(configPath));
551
+ const configToml = `# Generated by PrizmKit for Codex CLI project configuration.
552
+ # Keep project guidance in AGENTS.md; repository skills in .agents/skills/;
553
+ # PrizmKit behavioral rules live in .agents/rules/; native Codex subagents live in .codex/agents/.
554
+
555
+ project_doc_fallback_filenames = ["CLAUDE.md", "CODEBUDDY.md"]
556
+
557
+ [agents]
558
+ max_depth = 1
559
+ `;
560
+ await fs.writeFile(configPath, configToml);
561
+ await fs.remove(legacySettingsPath);
562
+ console.log(chalk.green(` ✓ .codex/config.toml`));
563
+
564
+ const {
565
+ installRules: installCodexRules,
566
+ removeLegacyCodexRules,
567
+ } = await loadAdapter('codex', 'rules-adapter.js');
568
+ await installCodexRules(projectRoot, ruleFiles);
569
+ removeLegacyCodexRules(projectRoot, allRuleFileNames);
570
+ console.log(chalk.green(` ✓ .agents/rules/ (${ruleFiles.length} 条规则)`));
458
571
  }
459
572
  }
460
573
 
461
574
  /**
462
575
  * 安装 git pre-commit hook(prizm 格式校验)
463
576
  */
464
- export async function installGitHook(projectRoot, dryRun) {
577
+ const PRIZMKIT_HOOK_MARKER_START = '# PRIZMKIT_PRE_COMMIT_HOOK_START';
578
+ const PRIZMKIT_HOOK_MARKER_END = '# PRIZMKIT_PRE_COMMIT_HOOK_END';
579
+
580
+ function escapeRegExp(value) {
581
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
582
+ }
583
+
584
+ function ensureTrailingNewline(value) {
585
+ return value.endsWith('\n') ? value : `${value}\n`;
586
+ }
587
+
588
+ function buildStandalonePrizmHook(templateContent) {
589
+ const content = templateContent.trimEnd();
590
+ const firstNewline = content.indexOf('\n');
591
+
592
+ if (content.startsWith('#!') && firstNewline !== -1) {
593
+ return [
594
+ content.slice(0, firstNewline),
595
+ PRIZMKIT_HOOK_MARKER_START,
596
+ content.slice(firstNewline + 1),
597
+ PRIZMKIT_HOOK_MARKER_END,
598
+ '',
599
+ ].join('\n');
600
+ }
601
+
602
+ return [
603
+ PRIZMKIT_HOOK_MARKER_START,
604
+ content,
605
+ PRIZMKIT_HOOK_MARKER_END,
606
+ '',
607
+ ].join('\n');
608
+ }
609
+
610
+ function buildAppendedPrizmHook(templateContent) {
611
+ return [
612
+ PRIZMKIT_HOOK_MARKER_START,
613
+ templateContent.trimEnd(),
614
+ PRIZMKIT_HOOK_MARKER_END,
615
+ '',
616
+ ].join('\n');
617
+ }
618
+
619
+ function insertPrizmHookIntoUserHook(userHookContent, prizmHookBlock) {
620
+ const content = userHookContent.trimEnd();
621
+ const terminalSuccessExitPattern = /(^|\n)([ \t]*exit[ \t]+0[ \t]*(?:#.*)?)[ \t]*$/;
622
+ const match = content.match(terminalSuccessExitPattern);
623
+
624
+ if (!match) {
625
+ return `${content}\n\n${prizmHookBlock}`;
626
+ }
627
+
628
+ const exitLineStart = match.index + match[1].length;
629
+ const beforeExit = content.slice(0, exitLineStart).trimEnd();
630
+ const exitLine = match[2];
631
+
632
+ return [
633
+ beforeExit,
634
+ prizmHookBlock.trimEnd(),
635
+ exitLine,
636
+ '',
637
+ ].filter((part, index) => index > 0 || part.length > 0).join('\n\n');
638
+ }
639
+
640
+ function findLegacyPrizmHookEnd(content, markerIndex) {
641
+ const afterMarker = content.slice(markerIndex);
642
+ const exitMatch = afterMarker.match(/(^|\n)[ \t]*exit[ \t]+0[ \t]*(\r?\n|$)/);
643
+ if (exitMatch) {
644
+ return markerIndex + exitMatch.index + exitMatch[0].length;
645
+ }
646
+
647
+ const markerLineEnd = content.indexOf('\n', markerIndex);
648
+ const searchStart = markerLineEnd === -1 ? content.length : markerLineEnd + 1;
649
+ const blankLineIndex = content.slice(searchStart).search(/\r?\n[ \t]*(\r?\n|$)/);
650
+ if (blankLineIndex !== -1) {
651
+ return searchStart + blankLineIndex + 1;
652
+ }
653
+
654
+ return content.length;
655
+ }
656
+
657
+ function stripLegacyPrizmHookBlocks(existing) {
658
+ const legacyMarker = '# prizm-pre-commit.sh';
659
+ let cleaned = '';
660
+ let cursor = 0;
661
+
662
+ while (cursor < existing.length) {
663
+ const markerIndex = existing.indexOf(legacyMarker, cursor);
664
+ if (markerIndex === -1) {
665
+ cleaned += existing.slice(cursor);
666
+ break;
667
+ }
668
+
669
+ const markerLineStart = existing.lastIndexOf('\n', markerIndex) + 1;
670
+ let blockStart = markerLineStart;
671
+ if (markerLineStart > 0) {
672
+ const previousLineEnd = markerLineStart - 1;
673
+ const previousLineStart = existing.lastIndexOf('\n', previousLineEnd - 1) + 1;
674
+ const previousLine = existing.slice(previousLineStart, previousLineEnd).replace(/\r$/, '');
675
+ if (previousLine.startsWith('#!')) {
676
+ blockStart = previousLineStart;
677
+ }
678
+ }
679
+
680
+ cleaned += existing.slice(cursor, blockStart);
681
+ cursor = findLegacyPrizmHookEnd(existing, markerIndex);
682
+ }
683
+
684
+ return cleaned;
685
+ }
686
+
687
+ function stripManagedPrizmHook(existing) {
688
+ const markedBlockPattern = new RegExp(
689
+ `(^|\\n)${escapeRegExp(PRIZMKIT_HOOK_MARKER_START)}[\\s\\S]*?${escapeRegExp(PRIZMKIT_HOOK_MARKER_END)}\\n?`,
690
+ 'g',
691
+ );
692
+
693
+ let cleaned = existing.replace(markedBlockPattern, '\n');
694
+ cleaned = stripLegacyPrizmHookBlocks(cleaned);
695
+
696
+ return cleaned.replace(/\n{3,}/g, '\n\n').trimEnd();
697
+ }
698
+
699
+ function hasUserHookContent(content) {
700
+ return content
701
+ .replace(/^#!.*(\r?\n|$)/, '')
702
+ .trim()
703
+ .length > 0;
704
+ }
705
+
706
+ export async function installGitHook(projectRoot, dryRun, runtime = 'unix') {
465
707
  const gitDir = path.join(projectRoot, '.git');
708
+ const normalizedRuntime = normalizeRuntime(runtime);
466
709
 
467
710
  if (dryRun) {
468
- console.log(chalk.gray(' [dry-run] .git/hooks/pre-commit (prizm format check)'));
711
+ const hookLabel = normalizedRuntime === RUNTIME_WINDOWS
712
+ ? '.git/hooks/pre-commit skipped (Windows runtime: no shell hook)'
713
+ : '.git/hooks/pre-commit (prizm format check)';
714
+ console.log(chalk.gray(` [dry-run] ${hookLabel}`));
469
715
  return;
470
716
  }
471
717
 
@@ -474,16 +720,37 @@ export async function installGitHook(projectRoot, dryRun) {
474
720
  const templatePath = path.join(getTemplatesDir(), 'hooks', 'prizm-pre-commit.sh');
475
721
  const hooksDir = path.join(gitDir, 'hooks');
476
722
  const preCommitPath = path.join(hooksDir, 'pre-commit');
723
+ const hookContent = fs.readFileSync(templatePath, 'utf8');
724
+
725
+ if (normalizedRuntime === RUNTIME_WINDOWS) {
726
+ if (fs.existsSync(preCommitPath)) {
727
+ const existing = fs.readFileSync(preCommitPath, 'utf8');
728
+ const cleaned = stripManagedPrizmHook(existing);
729
+ if (!hasUserHookContent(cleaned)) {
730
+ await fs.remove(preCommitPath);
731
+ console.log(chalk.gray(' • .git/hooks/pre-commit shell hook removed for Windows runtime'));
732
+ } else if (cleaned !== existing.trimEnd()) {
733
+ fs.writeFileSync(preCommitPath, ensureTrailingNewline(cleaned));
734
+ console.log(chalk.gray(' • PrizmKit shell hook block removed for Windows runtime'));
735
+ }
736
+ }
737
+ console.log(chalk.gray(' • Git hook skipped (Windows runtime uses no shell hook)'));
738
+ return;
739
+ }
477
740
 
478
741
  fs.mkdirSync(hooksDir, { recursive: true });
479
742
 
480
743
  if (fs.existsSync(preCommitPath)) {
481
744
  const existing = fs.readFileSync(preCommitPath, 'utf8');
482
- if (existing.includes('PrizmKit')) return;
483
- const hookContent = fs.readFileSync(templatePath, 'utf8');
484
- fs.writeFileSync(preCommitPath, existing + '\n\n' + hookContent);
745
+ const cleaned = stripManagedPrizmHook(existing);
746
+ if (hasUserHookContent(cleaned)) {
747
+ fs.writeFileSync(preCommitPath, insertPrizmHookIntoUserHook(cleaned, buildAppendedPrizmHook(hookContent)));
748
+ } else {
749
+ fs.writeFileSync(preCommitPath, buildStandalonePrizmHook(hookContent));
750
+ }
751
+ fs.chmodSync(preCommitPath, 0o755);
485
752
  } else {
486
- await fs.copy(templatePath, preCommitPath);
753
+ fs.writeFileSync(preCommitPath, buildStandalonePrizmHook(hookContent));
487
754
  fs.chmodSync(preCommitPath, 0o755);
488
755
  }
489
756
 
@@ -491,11 +758,12 @@ export async function installGitHook(projectRoot, dryRun) {
491
758
  }
492
759
 
493
760
  /**
494
- * 安装 PrizmKit 脚本到 .prizmkit/scripts/(始终安装)
761
+ * 安装 PrizmKit 脚本到 .prizmkit/scripts/(Windows runtime 跳过 shell-only 脚本)
495
762
  */
496
- export async function installPrizmkitScripts(projectRoot, dryRun) {
763
+ export async function installPrizmkitScripts(projectRoot, dryRun, runtime = 'unix') {
497
764
  const scriptsDir = path.join(projectRoot, '.prizmkit', 'scripts');
498
765
  const templateHooksDir = path.join(getTemplatesDir(), 'hooks');
766
+ const normalizedRuntime = normalizeRuntime(runtime);
499
767
 
500
768
  const scripts = [
501
769
  'validate-prizm-docs.sh',
@@ -504,8 +772,17 @@ export async function installPrizmkitScripts(projectRoot, dryRun) {
504
772
 
505
773
  if (dryRun) {
506
774
  for (const s of scripts) {
507
- console.log(chalk.gray(` [dry-run] .prizmkit/scripts/${s}`));
775
+ const action = normalizedRuntime === RUNTIME_WINDOWS ? 'skip shell script' : `.prizmkit/scripts/${s}`;
776
+ console.log(chalk.gray(` [dry-run] ${action}`));
777
+ }
778
+ return;
779
+ }
780
+
781
+ if (normalizedRuntime === RUNTIME_WINDOWS) {
782
+ for (const s of scripts) {
783
+ await fs.remove(path.join(scriptsDir, s));
508
784
  }
785
+ console.log(chalk.gray(' • shell-only PrizmKit scripts skipped for Windows runtime'));
509
786
  return;
510
787
  }
511
788
 
@@ -525,16 +802,16 @@ const PRIZMKIT_MARKER_START = '<!-- PRIZMKIT:START';
525
802
  const PRIZMKIT_MARKER_END = '<!-- PRIZMKIT:END -->';
526
803
 
527
804
  /**
528
- * Merge PrizmKit template content into a project memory file (CLAUDE.md / CODEBUDDY.md).
805
+ * Merge PrizmKit template content into a project memory file.
529
806
  * - File missing → create with template content
530
807
  * - File exists with markers → replace content between markers
531
808
  * - File exists without markers → append template content
532
809
  */
533
810
  export async function installProjectMemory(platform, projectRoot, dryRun) {
534
811
  const templatesDir = getTemplatesDir();
535
- const skillsDir = getSkillsDir();
536
812
  const templateName = 'project-memory-template.md';
537
- const targetName = platform === 'claude' ? 'CLAUDE.md' : 'CODEBUDDY.md';
813
+ const targetName = projectMemoryFile(platform);
814
+ if (!targetName) return;
538
815
  const targetPath = path.join(projectRoot, targetName);
539
816
 
540
817
  // Template lives in core/templates/ (bundled as templates/).
@@ -548,7 +825,19 @@ export async function installProjectMemory(platform, projectRoot, dryRun) {
548
825
  return;
549
826
  }
550
827
 
551
- const templateContent = (await fs.readFile(templatePath, 'utf-8')).trimEnd();
828
+ let templateContent = (await fs.readFile(templatePath, 'utf-8')).trimEnd();
829
+ if (platform === 'codex') {
830
+ templateContent = templateContent.replace(
831
+ '### Available Commands\nRun `/prizm-kit` to see all available PrizmKit commands.',
832
+ `### Available PrizmKit Skills
833
+ Codex discovers repository skills from \`.agents/skills/\`. When a user or prompt mentions \`/prizmkit-xxx\`, use the matching skill at \`.agents/skills/prizmkit-xxx/SKILL.md\`.
834
+
835
+ - Start with \`.agents/skills/prizm-kit/SKILL.md\` to see all available PrizmKit skills.
836
+ - Skill assets and references live inside each \`.agents/skills/<skill>/\` directory.
837
+ - PrizmKit behavioral rules live in \`.agents/rules/\`; read the relevant Markdown guidance before commit, documentation, or context-loading work.
838
+ - Native Codex subagents live in \`.codex/agents/*.toml\`; use them when spawning or coordinating subagents.`
839
+ );
840
+ }
552
841
 
553
842
  if (dryRun) {
554
843
  console.log(chalk.gray(` [dry-run] ${targetName}`));
@@ -599,25 +888,15 @@ function collectFilesRecursive(dir, base = '') {
599
888
  return files;
600
889
  }
601
890
 
602
- /**
603
- * Resolve the list of pipeline files from the source (bundled) pipeline directory.
604
- * Used by upgrade to compute diff before actual installation.
605
- * @returns {string[]} relative file paths
606
- */
607
- export function resolvePipelineFileList() {
608
- const pipelineSource = getPipelineDir();
609
- if (!pipelineSource || !fs.pathExistsSync(pipelineSource)) return [];
610
-
611
- // 动态扫描,与 installPipeline() 保持一致的排除规则
612
- const EXCLUDE = new Set(['tests', 'docs', '__pycache__', 'node_modules', '.DS_Store']);
891
+ function collectInstallablePipelineFiles(pipelineSource) {
613
892
  let allEntries;
614
893
  try {
615
894
  allEntries = fs.readdirSync(pipelineSource);
616
895
  } catch {
617
896
  return [];
618
897
  }
619
- const items = allEntries.filter(name => !EXCLUDE.has(name) && !name.startsWith('.'));
620
898
 
899
+ const items = allEntries.filter(name => !PIPELINE_INSTALL_EXCLUDE.has(name) && !name.startsWith('.'));
621
900
  const files = [];
622
901
  for (const item of items) {
623
902
  const src = path.join(pipelineSource, item);
@@ -632,13 +911,67 @@ export function resolvePipelineFileList() {
632
911
  return files;
633
912
  }
634
913
 
914
+ /**
915
+ * Resolve the list of pipeline files from the source (bundled) pipeline directory.
916
+ * Used by upgrade to compute diff before actual installation.
917
+ * @returns {string[]} relative file paths
918
+ */
919
+ export function resolvePipelineFileList(runtime = 'unix') {
920
+ const pipelineSource = getPipelineDir(runtime);
921
+ if (!pipelineSource || !fs.pathExistsSync(pipelineSource)) return [];
922
+ return collectInstallablePipelineFiles(pipelineSource);
923
+ }
924
+
925
+ async function pruneStalePipelineRuntimeFiles(pipelineTarget, runtime) {
926
+ if (!await fs.pathExists(pipelineTarget)) return false;
927
+
928
+ const staleRuntime = runtime === RUNTIME_WINDOWS ? RUNTIME_UNIX : RUNTIME_WINDOWS;
929
+ const staleFiles = resolvePipelineFileList(staleRuntime);
930
+ const currentFiles = new Set(resolvePipelineFileList(runtime));
931
+ const staleDirs = new Set();
932
+ let prunedRuntimeSpecificFiles = false;
933
+
934
+ for (const relativeFile of staleFiles) {
935
+ if (currentFiles.has(relativeFile)) continue;
936
+
937
+ const targetFile = path.join(pipelineTarget, relativeFile);
938
+ if (await fs.pathExists(targetFile)) {
939
+ const stat = await fs.stat(targetFile);
940
+ if (stat.isFile()) {
941
+ await fs.remove(targetFile);
942
+ prunedRuntimeSpecificFiles = true;
943
+ }
944
+ }
945
+
946
+ const relativeDir = path.dirname(relativeFile);
947
+ if (relativeDir && relativeDir !== '.') staleDirs.add(relativeDir);
948
+ }
949
+
950
+ const dirsByDepth = [...staleDirs].sort((a, b) => b.split(path.sep).length - a.split(path.sep).length);
951
+ for (const relativeDir of dirsByDepth) {
952
+ const targetDir = path.join(pipelineTarget, relativeDir);
953
+ if (!await fs.pathExists(targetDir)) continue;
954
+ const entries = await fs.readdir(targetDir);
955
+ if (entries.length === 0) {
956
+ await fs.remove(targetDir);
957
+ }
958
+ }
959
+
960
+ return prunedRuntimeSpecificFiles;
961
+ }
962
+
635
963
  /**
636
964
  * 安装 dev-pipeline(纯 Copy 模式)
637
965
  * 安装目标:.prizmkit/dev-pipeline/
638
966
  * Returns an array of installed file relative paths (relative to .prizmkit/dev-pipeline/).
639
967
  */
640
- export async function installPipeline(projectRoot, dryRun, { forceOverwrite = false } = {}) {
641
- const pipelineSource = getPipelineDir();
968
+ export async function installPipeline(projectRoot, dryRun, options = {}) {
969
+ const runtime = normalizeRuntime(options.runtime);
970
+ const previousRuntime = options.previousRuntime === undefined
971
+ ? runtime
972
+ : normalizeRuntime(options.previousRuntime);
973
+ const forceOverwrite = Boolean(options.forceOverwrite);
974
+ const pipelineSource = getPipelineDir(runtime);
642
975
  const pipelineTarget = path.join(projectRoot, '.prizmkit', 'dev-pipeline');
643
976
 
644
977
  if (dryRun) {
@@ -651,51 +984,41 @@ export async function installPipeline(projectRoot, dryRun, { forceOverwrite = fa
651
984
  await fs.ensureDir(path.join(projectRoot, '.prizmkit', 'state', 'bugfix'));
652
985
  await fs.ensureDir(path.join(projectRoot, '.prizmkit', 'state', 'refactor'));
653
986
 
654
- // 动态扫描 bundled dev-pipeline 目录,排除不应安装到用户项目的内容
655
- const EXCLUDE = new Set(['tests', 'docs', '__pycache__', 'node_modules', '.DS_Store']);
656
- const allEntries = await fs.readdir(pipelineSource);
657
- const items = allEntries.filter(name => !EXCLUDE.has(name) && !name.startsWith('.'));
987
+ const runtimeSwitched = previousRuntime !== runtime;
988
+ const prunedStaleRuntimeFiles = await pruneStalePipelineRuntimeFiles(pipelineTarget, runtime);
989
+ const overwriteExisting = forceOverwrite || runtimeSwitched || prunedStaleRuntimeFiles;
658
990
 
659
- let installedCount = 0;
660
- const installedFiles = [];
661
- for (const item of items) {
662
- const src = path.join(pipelineSource, item);
663
- const tgt = path.join(pipelineTarget, item);
991
+ const installedFiles = collectInstallablePipelineFiles(pipelineSource);
992
+ let copiedCount = 0;
993
+ let skippedCount = 0;
664
994
 
665
- if (!await fs.pathExists(src)) continue;
995
+ for (const relativeFile of installedFiles) {
996
+ const src = path.join(pipelineSource, relativeFile);
997
+ const tgt = path.join(pipelineTarget, relativeFile);
666
998
 
667
- const srcStat = await fs.stat(src);
668
-
669
- if (await fs.pathExists(tgt) && !forceOverwrite) {
670
- console.log(chalk.yellow(` ⚠ .prizmkit/dev-pipeline/${item} 已存在,跳过`));
671
- // Still collect existing files for manifest tracking
672
- if (srcStat.isDirectory()) {
673
- installedFiles.push(...collectFilesRecursive(src, item));
674
- } else {
675
- installedFiles.push(item);
676
- }
999
+ if (await fs.pathExists(tgt) && !overwriteExisting) {
1000
+ skippedCount++;
677
1001
  continue;
678
1002
  }
679
1003
 
680
- await fs.copy(src, tgt);
681
- installedCount++;
682
-
683
- // Collect installed file paths
684
- if (srcStat.isDirectory()) {
685
- installedFiles.push(...collectFilesRecursive(src, item));
686
- } else {
687
- installedFiles.push(item);
688
- }
1004
+ await fs.ensureDir(path.dirname(tgt));
1005
+ await fs.copy(src, tgt, { overwrite: true, errorOnExist: false });
1006
+ copiedCount++;
689
1007
  }
690
1008
 
691
- console.log(chalk.green(` ✓ .prizmkit/dev-pipeline/ (${installedCount} 项)`));
1009
+ const skipSuffix = skippedCount ? `, preserved ${skippedCount} existing` : '';
1010
+ console.log(chalk.green(` ✓ .prizmkit/dev-pipeline/ (${copiedCount} files copied${skipSuffix})`));
692
1011
 
693
1012
  // Copy .env.example to .prizmkit/.env.example if it exists in pipeline source
694
1013
  const envExampleSrc = path.join(pipelineSource, '.env.example');
695
1014
  const envExampleTgt = path.join(projectRoot, '.prizmkit', '.env.example');
696
- if (await fs.pathExists(envExampleSrc) && !await fs.pathExists(envExampleTgt)) {
697
- await fs.copy(envExampleSrc, envExampleTgt);
698
- console.log(chalk.green(` .prizmkit/.env.example (pipeline environment configuration)`));
1015
+ if (await fs.pathExists(envExampleSrc)) {
1016
+ if (overwriteExisting || !(await fs.pathExists(envExampleTgt))) {
1017
+ await fs.copy(envExampleSrc, envExampleTgt, { overwrite: true, errorOnExist: false });
1018
+ console.log(chalk.green(` ✓ .prizmkit/.env.example (pipeline environment configuration)`));
1019
+ } else {
1020
+ console.log(chalk.gray(` • .prizmkit/.env.example exists, preserved`));
1021
+ }
699
1022
  }
700
1023
 
701
1024
  return installedFiles;
@@ -839,7 +1162,7 @@ export async function installOpenCli(projectRoot, dryRun, autoDownload = true) {
839
1162
  console.log(chalk.yellow(' ⚠ Chrome Browser Bridge extension is NOT connected'));
840
1163
 
841
1164
  if (autoDownload) {
842
- await downloadOpenCliExtension(projectRoot);
1165
+ await downloadOpenCliExtension();
843
1166
  } else {
844
1167
  printOpenCliManualSteps();
845
1168
  }
@@ -859,7 +1182,7 @@ export async function installOpenCli(projectRoot, dryRun, autoDownload = true) {
859
1182
  * 从 GitHub releases 下载并解压 opencli Chrome 扩展。
860
1183
  * 省去用户手动下载解压步骤,但仍需在 Chrome 中手动加载。
861
1184
  */
862
- async function downloadOpenCliExtension(projectRoot) {
1185
+ async function downloadOpenCliExtension() {
863
1186
  const extDir = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.opencli', 'extension');
864
1187
 
865
1188
  // Check if already downloaded
@@ -959,7 +1282,8 @@ export const EXTRAS_REGISTRY = {
959
1282
  /**
960
1283
  * 执行纯净安装
961
1284
  * @param {Object} config
962
- * @param {string} config.platform - 'codebuddy' | 'claude' | 'both'
1285
+ * @param {string} config.platform - 'codebuddy' | 'claude' | 'codex' | 'all'
1286
+ * @param {string} [config.runtime] - 'unix' | 'windows'
963
1287
  * @param {string} config.skills - 'core' | 'minimal' | 'recommended:<type>'
964
1288
  * @param {boolean} config.team - 是否启用团队模式
965
1289
  * @param {boolean} config.pipeline - 是否安装 dev-pipeline
@@ -974,7 +1298,11 @@ export const EXTRAS_REGISTRY = {
974
1298
  */
975
1299
  export async function scaffold(config) {
976
1300
  const { platform, skills, team, pipeline, rules, aiCli, externalSkills, playwrightCli, openCli, openCliAutoDownload, projectRoot, dryRun } = config;
977
- const platforms = platform === 'both' ? ['codebuddy', 'claude'] : [platform];
1301
+ const runtime = normalizeRuntime(config.runtime);
1302
+ if (!isKnownPlatform(platform)) {
1303
+ throw new Error(`Unknown platform "${platform}". Expected codebuddy, claude, codex, or all.`);
1304
+ }
1305
+ const platforms = expandPlatforms(platform);
978
1306
 
979
1307
  if (dryRun) {
980
1308
  console.log(chalk.yellow('\n [DRY RUN] 预览将要安装的内容:\n'));
@@ -986,12 +1314,11 @@ export async function scaffold(config) {
986
1314
  console.log('');
987
1315
 
988
1316
  for (const p of platforms) {
989
- const platformLabel = p === 'codebuddy' ? 'CodeBuddy' : 'Claude Code';
990
- console.log(chalk.bold(` 正在安装 ${platformLabel} 环境...\n`));
1317
+ console.log(chalk.bold(` 正在安装 ${platformLabel(p)} 环境...\n`));
991
1318
 
992
1319
  // 1. Skills
993
1320
  console.log(chalk.blue(' 技能文件:'));
994
- await installSkills(p, skillList, projectRoot, dryRun);
1321
+ await installSkills(p, skillList, projectRoot, dryRun, runtime);
995
1322
 
996
1323
  // 2. Agents
997
1324
  console.log(chalk.blue('\n Agent 定义:'));
@@ -1005,7 +1332,7 @@ export async function scaffold(config) {
1005
1332
 
1006
1333
  // 4. Settings/Hooks/Rules
1007
1334
  console.log(chalk.blue('\n 平台配置:'));
1008
- await installSettings(p, projectRoot, { pipeline, rules }, dryRun);
1335
+ await installSettings(p, projectRoot, { pipeline, rules }, dryRun, runtime);
1009
1336
 
1010
1337
  // 5. Project Memory
1011
1338
  console.log(chalk.blue('\n 项目记忆文件:'));
@@ -1018,7 +1345,7 @@ export async function scaffold(config) {
1018
1345
  let installedPipelineFiles = [];
1019
1346
  if (pipeline) {
1020
1347
  console.log(chalk.blue(' 自动化流水线:'));
1021
- installedPipelineFiles = await installPipeline(projectRoot, dryRun) || [];
1348
+ installedPipelineFiles = await installPipeline(projectRoot, dryRun, { runtime }) || [];
1022
1349
  console.log('');
1023
1350
  }
1024
1351
 
@@ -1097,11 +1424,11 @@ export async function scaffold(config) {
1097
1424
 
1098
1425
  // 12. Git pre-commit hook
1099
1426
  console.log(chalk.blue(' Git Hook:'));
1100
- await installGitHook(projectRoot, dryRun);
1427
+ await installGitHook(projectRoot, dryRun, runtime);
1101
1428
 
1102
- // 13. PrizmKit scripts (always installed)
1429
+ // 13. PrizmKit scripts
1103
1430
  console.log(chalk.blue(' PrizmKit 脚本:'));
1104
- await installPrizmkitScripts(projectRoot, dryRun);
1431
+ await installPrizmkitScripts(projectRoot, dryRun, runtime);
1105
1432
 
1106
1433
  // 14. Write installation manifest
1107
1434
  if (!dryRun) {
@@ -1109,12 +1436,12 @@ export async function scaffold(config) {
1109
1436
  const agentFileNames = (await fs.readdir(agentsDir)).filter(f => f.endsWith('.md'));
1110
1437
  const rulesMeta = await loadRulesMetadata();
1111
1438
  const rulesPresetName = rules || 'recommended';
1112
- const rulesPresetDef = rulesMeta.presets[rulesPresetName] || rulesMeta.presets.recommended;
1113
- const ruleFileNames = (rulesPresetDef?.rules || []).map(name => `${name}.md`);
1439
+ const ruleFileNames = resolveRuleNamesForRuntime(rulesMeta, rulesPresetName, runtime).map(name => `${name}.md`);
1114
1440
 
1115
1441
  const manifest = buildManifest({
1116
1442
  version: scaffoldPkg.version,
1117
1443
  platform,
1444
+ runtime,
1118
1445
  suite: skills,
1119
1446
  skills: skillList,
1120
1447
  agents: agentFileNames,
@@ -1142,15 +1469,20 @@ export async function scaffold(config) {
1142
1469
 
1143
1470
  if (!dryRun) {
1144
1471
  // 打印下一步提示
1145
- const mainPlatform = platforms.includes('claude') ? 'claude' : 'codebuddy';
1146
- const defaultCli = mainPlatform === 'claude' ? 'claude' : 'cbc';
1472
+ const mainPlatform = platforms.includes('claude') ? 'claude'
1473
+ : platforms.includes('codebuddy') ? 'codebuddy'
1474
+ : platforms[0];
1475
+ const defaultCli = defaultCliForPlatform(mainPlatform);
1147
1476
  const cli = aiCli || defaultCli;
1148
1477
 
1149
1478
  console.log('');
1150
1479
  console.log(' 下一步:');
1151
1480
  console.log(` 1. ${chalk.cyan('cd ' + projectRoot)}`);
1152
1481
  console.log(` 2. ${chalk.cyan(cli)} ${chalk.gray('# 启动 AI 对话')}`);
1153
- console.log(` 3. ${chalk.cyan('说 "/prizmkit-init"')} ${chalk.gray('# 初始化项目上下文')}`);
1482
+ const initInstruction = mainPlatform === 'codex'
1483
+ ? '说 "运行 prizmkit-init"'
1484
+ : '说 "/prizmkit-init"';
1485
+ console.log(` 3. ${chalk.cyan(initInstruction)} ${chalk.gray('# 初始化项目上下文')}`);
1154
1486
 
1155
1487
  if (pipeline) {
1156
1488
  console.log(` 4. ${chalk.cyan('说 "规划一个应用"')} ${chalk.gray('# 生成 feature-list.json')}`);
@@ -1166,7 +1498,8 @@ export async function scaffold(config) {
1166
1498
 
1167
1499
  console.log(chalk.gray(` 安装统计:`));
1168
1500
  console.log(chalk.gray(` 技能: ${suiteLabel}`));
1169
- console.log(chalk.gray(` 平台: ${platforms.map(p => p === 'codebuddy' ? 'CodeBuddy' : 'Claude Code').join(' + ')}`));
1501
+ console.log(chalk.gray(` 平台: ${platforms.map(platformLabel).join(' + ')}`));
1502
+ console.log(chalk.gray(` 运行系统: ${runtimeLabel(runtime)}`));
1170
1503
  console.log(chalk.gray(` 团队: ${team ? '已启用' : '未启用'}`));
1171
1504
  console.log(chalk.gray(` 流水线: ${pipeline ? '已安装' : '未安装'}`));
1172
1505
  const browserTools = [playwrightCli && 'playwright-cli', openCli && 'opencli'].filter(Boolean);
@@ -1175,4 +1508,4 @@ export async function scaffold(config) {
1175
1508
  if (aiCli) console.log(chalk.gray(` AI CLI: ${aiCli}`));
1176
1509
  console.log('');
1177
1510
  }
1178
- }
1511
+ }