prizmkit 1.1.142 → 1.1.144

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 (36) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/pi/paths.js +9 -0
  3. package/bundled/adapters/pi/rules-adapter.js +13 -0
  4. package/bundled/adapters/pi/settings-adapter.js +23 -0
  5. package/bundled/adapters/pi/skill-adapter.js +18 -0
  6. package/bundled/dev-pipeline/prizmkit_runtime/config.py +15 -3
  7. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +3 -1
  8. package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +10 -2
  9. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +1 -0
  10. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +1 -0
  11. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +26 -2
  12. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +61 -7
  13. package/bundled/dev-pipeline/tests/test_unified_cli.py +88 -0
  14. package/bundled/skills/_metadata.json +1 -1
  15. package/bundled/skills/prizmkit-code-review/SKILL.md +3 -3
  16. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +63 -58
  17. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +8 -1
  18. package/bundled/skills/prizmkit-plan/SKILL.md +3 -3
  19. package/bundled/skills/prizmkit-plan/assets/plan-template.md +3 -0
  20. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +55 -52
  21. package/bundled/skills/prizmkit-test/SKILL.md +3 -3
  22. package/bundled/skills/prizmkit-test/references/independent-test-review.md +68 -69
  23. package/bundled/skills/prizmkit-test/references/test-report-template.md +5 -1
  24. package/bundled/templates/hooks/commit-intent-status.py +50 -0
  25. package/bundled/templates/hooks/commit-intent.json +2 -2
  26. package/bundled/templates/hooks/post-command-prizm-drift.py +44 -0
  27. package/package.json +1 -1
  28. package/src/ai-cli-launch.js +1 -1
  29. package/src/clean.js +13 -4
  30. package/src/config.js +32 -5
  31. package/src/detect-platform.js +11 -7
  32. package/src/index.js +5 -0
  33. package/src/platforms.js +6 -2
  34. package/src/prompts.js +12 -4
  35. package/src/scaffold.js +74 -20
  36. package/src/upgrade.js +12 -5
package/src/prompts.js CHANGED
@@ -37,13 +37,13 @@ export function __resetAiCliPromptsForTests() {
37
37
  }
38
38
 
39
39
  /**
40
- * Select target platform (codebuddy, claude, codex, or all).
40
+ * Select target platform (codebuddy, claude, codex, pi, or all).
41
41
  * @param {string} currentPlatform - current platform value
42
42
  * @param {Object} detected - detected environment info (e.g., { cbc: true, claude: false })
43
43
  * @returns {Promise<string>}
44
44
  */
45
45
  export async function selectPlatform(currentPlatform, detected = {}) {
46
- const selectablePlatforms = ['codebuddy', 'claude', 'codex', 'all'];
46
+ const selectablePlatforms = ['codebuddy', 'claude', 'codex', 'pi', 'all'];
47
47
  const detectedDefault = selectablePlatforms.includes(detected.suggested)
48
48
  ? detected.suggested
49
49
  : 'claude';
@@ -68,6 +68,10 @@ export async function selectPlatform(currentPlatform, detected = {}) {
68
68
  name: `Codex (AGENTS.md + .agents/skills + .codex/)${detected.codex ? chalk.green(' ← 已检测到 codex') : ''}`,
69
69
  value: 'codex',
70
70
  },
71
+ {
72
+ name: `Pi (.pi/skills + .pi/APPEND_SYSTEM.md)${detected.pi ? chalk.green(' ← 已检测到 pi') : ''}`,
73
+ value: 'pi',
74
+ },
71
75
  {
72
76
  name: `同时安装全部平台 (${platformLabel('all')})`,
73
77
  value: 'all',
@@ -79,7 +83,7 @@ export async function selectPlatform(currentPlatform, detected = {}) {
79
83
  }
80
84
 
81
85
  /**
82
- * Select AI CLI command (cbc, claude, codex, custom, or skip).
86
+ * Select AI CLI command (cbc, claude, codex, pi, custom, or skip).
83
87
  * @param {string} currentCommand - current AI CLI command
84
88
  * @param {Object} detected - detected environment info
85
89
  * @returns {Promise<string>} - known command, custom string, or ''
@@ -98,6 +102,10 @@ export async function selectAiCli(currentCommand, detected = {}) {
98
102
  name: `codex — Codex CLI${detected.codex ? chalk.green(' ✓ 已安装') : chalk.gray(' (未检测到)')}`,
99
103
  value: 'codex',
100
104
  },
105
+ {
106
+ name: `pi — Pi Coding Agent${detected.pi ? chalk.green(' ✓ 已安装') : chalk.gray(' (未检测到)')}`,
107
+ value: 'pi',
108
+ },
101
109
  {
102
110
  name: '自定义 — 输入其他命令',
103
111
  value: '__custom__',
@@ -108,7 +116,7 @@ export async function selectAiCli(currentCommand, detected = {}) {
108
116
  },
109
117
  ];
110
118
 
111
- const knownCliValues = ['cbc', 'claude', 'codex'];
119
+ const knownCliValues = ['cbc', 'claude', 'codex', 'pi'];
112
120
  const cliDefault = knownCliValues.includes(currentCommand)
113
121
  ? currentCommand
114
122
  : (currentCommand ? '__custom__' : (detected.suggestedCli || ''));
package/src/scaffold.js CHANGED
@@ -96,6 +96,7 @@ async function removeEmptyDirectory(dirPath) {
96
96
  }
97
97
 
98
98
  export async function removeRetiredAgentTeamArtifacts(platform, projectRoot, dryRun) {
99
+ if (platform === 'pi') return;
99
100
  const agentDir = platform === 'claude'
100
101
  ? path.join(projectRoot, '.claude', 'agents')
101
102
  : platform === 'codex'
@@ -231,12 +232,16 @@ export async function installSkills(platform, skills, projectRoot, dryRun, runti
231
232
  // Load Claude command adapter for skill conversion
232
233
  let convertSkillToCommand;
233
234
  let convertSkillToCodex;
235
+ let convertSkillToPi;
234
236
  if (platform === 'claude') {
235
237
  const commandAdapter = await loadAdapter('claude', 'command-adapter.js');
236
238
  convertSkillToCommand = commandAdapter.convertSkillToCommand;
237
239
  } else if (platform === 'codex') {
238
240
  const codexSkillAdapter = await loadAdapter('codex', 'skill-adapter.js');
239
241
  convertSkillToCodex = codexSkillAdapter.convertSkill;
242
+ } else if (platform === 'pi') {
243
+ const piSkillAdapter = await loadAdapter('pi', 'skill-adapter.js');
244
+ convertSkillToPi = piSkillAdapter.convertSkill;
240
245
  }
241
246
 
242
247
  for (const skillName of skills) {
@@ -347,6 +352,25 @@ export async function installSkills(platform, skills, projectRoot, dryRun, runti
347
352
  }
348
353
  }
349
354
  console.log(chalk.green(` ✓ .agents/skills/${skillName}/`));
355
+ } else if (platform === 'pi') {
356
+ const content = await fs.readFile(skillMdPath, 'utf8');
357
+ const converted = convertSkillToPi(content, skillName);
358
+ const targetDir = path.join(projectRoot, '.pi', 'skills', skillName);
359
+
360
+ if (dryRun) {
361
+ console.log(chalk.gray(` [dry-run] .pi/skills/${skillName}/`));
362
+ continue;
363
+ }
364
+
365
+ await fs.remove(targetDir);
366
+ await fs.ensureDir(targetDir);
367
+ await fs.writeFile(path.join(targetDir, 'SKILL.md'), converted);
368
+ for (const entry of await fs.readdir(corePath, { withFileTypes: true })) {
369
+ if (entry.isDirectory()) {
370
+ await fs.copy(path.join(corePath, entry.name), path.join(targetDir, entry.name));
371
+ }
372
+ }
373
+ console.log(chalk.green(` ✓ .pi/skills/${skillName}/`));
350
374
  }
351
375
  }
352
376
  }
@@ -503,6 +527,18 @@ job_max_runtime_seconds = 3300
503
527
  await installCodexRules(projectRoot, ruleFiles);
504
528
  removeLegacyCodexRules(projectRoot, allRuleFileNames);
505
529
  console.log(chalk.green(` ✓ .agents/rules/ (${ruleFiles.length} 条规则)`));
530
+ } else if (platform === 'pi') {
531
+ if (dryRun) {
532
+ console.log(chalk.gray(' [dry-run] .pi/settings.json'));
533
+ console.log(chalk.gray(` [dry-run] .pi/rules/ (${ruleFiles.length} 条规则)`));
534
+ return;
535
+ }
536
+ const { generateSettings } = await loadAdapter('pi', 'settings-adapter.js');
537
+ const { installRules } = await loadAdapter('pi', 'rules-adapter.js');
538
+ await generateSettings(projectRoot);
539
+ await installRules(projectRoot, ruleFiles);
540
+ console.log(chalk.green(' ✓ .pi/settings.json'));
541
+ console.log(chalk.green(` ✓ .pi/rules/ (${ruleFiles.length} 条规则)`));
506
542
  }
507
543
  }
508
544
 
@@ -724,6 +760,8 @@ export async function installPrizmkitScripts(projectRoot, dryRun, runtime = 'pyt
724
760
  const scripts = [
725
761
  'validate-prizm-docs.py',
726
762
  'diff-prizm-docs.py',
763
+ 'commit-intent-status.py',
764
+ 'post-command-prizm-drift.py',
727
765
  ];
728
766
  const legacyManagedScripts = [
729
767
  'validate-prizm-docs.sh',
@@ -925,11 +963,11 @@ export async function installProjectMemory(platform, projectRoot, dryRun) {
925
963
  const templateName = 'project-memory-template.md';
926
964
  const mainName = projectMemoryFile(platform);
927
965
  const privateName = privateProjectMemoryFile(platform);
928
- if (!mainName || !privateName) return;
966
+ if (!privateName) return;
929
967
 
930
- const mainPath = path.join(projectRoot, mainName);
968
+ const mainPath = mainName ? path.join(projectRoot, mainName) : null;
931
969
  const privatePath = path.join(projectRoot, privateName);
932
- const importPointer = `@./${privateName}`;
970
+ const importPointer = mainName ? `@./${privateName}` : null;
933
971
 
934
972
  const templateCandidates = [
935
973
  path.join(templatesDir, templateName),
@@ -942,7 +980,18 @@ export async function installProjectMemory(platform, projectRoot, dryRun) {
942
980
  }
943
981
 
944
982
  let templateContent = (await fs.readFile(templatePath, 'utf-8')).trimEnd();
945
- if (platform === 'codex') {
983
+ if (platform === 'pi') {
984
+ templateContent = templateContent.replace(
985
+ '### Available Commands\nRun `/prizmkit` to see all available PrizmKit commands.',
986
+ `### Available PrizmKit Skills
987
+ Pi discovers project skills from \`.pi/skills/\`. Invoke them with \`/skill:<name>\` (for example \`/skill:prizmkit-plan\`).
988
+
989
+ - Start with \`/skill:prizmkit\` to see all available PrizmKit skills.
990
+ - Skill assets and references live inside each \`.pi/skills/<skill>/\` directory.
991
+ - PrizmKit behavioral rules live in \`.pi/rules/\`; read the relevant Markdown guidance before commit, documentation, or context-loading work.
992
+ `
993
+ );
994
+ } else if (platform === 'codex') {
946
995
  templateContent = templateContent.replace(
947
996
  '### Available Commands\nRun `/prizmkit` to see all available PrizmKit commands.',
948
997
  `### Available PrizmKit Skills
@@ -957,10 +1006,11 @@ Codex discovers repository skills from \`.agents/skills/\`. When a user or promp
957
1006
 
958
1007
  if (dryRun) {
959
1008
  console.log(chalk.gray(` [dry-run] ${privateName}`));
960
- console.log(chalk.gray(` [dry-run] ${mainName} import ${importPointer}`));
1009
+ if (mainName) console.log(chalk.gray(` [dry-run] ${mainName} import ${importPointer}`));
961
1010
  return;
962
1011
  }
963
1012
 
1013
+ await fs.ensureDir(path.dirname(privatePath));
964
1014
  if (!await fs.pathExists(privatePath)) {
965
1015
  await fs.writeFile(privatePath, `${templateContent}\n`, 'utf-8');
966
1016
  console.log(chalk.green(` ✓ ${privateName} (created PrizmKit section)`));
@@ -975,18 +1025,20 @@ Codex discovers repository skills from \`.agents/skills/\`. When a user or promp
975
1025
  }
976
1026
  }
977
1027
 
978
- const existingMain = await fs.pathExists(mainPath)
979
- ? await fs.readFile(mainPath, 'utf-8')
980
- : '';
981
- const mainMerge = ensureSingleImportPointer(existingMain, importPointer);
982
- logManagedProjectMemoryWarnings(mainName, mainMerge.warnings);
983
- const nextMain = mainMerge.content;
984
- if (nextMain !== existingMain) {
985
- await fs.writeFile(mainPath, nextMain, 'utf-8');
986
- const migrationSuffix = mainMerge.removedBlocks.length ? ' and migrated managed block' : '';
987
- console.log(chalk.green(` ✓ ${mainName} (imports ${privateName}${migrationSuffix})`));
988
- } else {
989
- console.log(chalk.green(` ✓ ${mainName} (import up-to-date)`));
1028
+ if (mainPath && importPointer) {
1029
+ const existingMain = await fs.pathExists(mainPath)
1030
+ ? await fs.readFile(mainPath, 'utf-8')
1031
+ : '';
1032
+ const mainMerge = ensureSingleImportPointer(existingMain, importPointer);
1033
+ logManagedProjectMemoryWarnings(mainName, mainMerge.warnings);
1034
+ const nextMain = mainMerge.content;
1035
+ if (nextMain !== existingMain) {
1036
+ await fs.writeFile(mainPath, nextMain, 'utf-8');
1037
+ const migrationSuffix = mainMerge.removedBlocks.length ? ' and migrated managed block' : '';
1038
+ console.log(chalk.green(` ✓ ${mainName} (imports ${privateName}${migrationSuffix})`));
1039
+ } else {
1040
+ console.log(chalk.green(` ✓ ${mainName} (import up-to-date)`));
1041
+ }
990
1042
  }
991
1043
  }
992
1044
 
@@ -1475,7 +1527,7 @@ export const EXTRAS_REGISTRY = {
1475
1527
  /**
1476
1528
  * 执行纯净安装
1477
1529
  * @param {Object} config
1478
- * @param {string} config.platform - 'codebuddy' | 'claude' | 'codex' | 'all'
1530
+ * @param {string} config.platform - 'codebuddy' | 'claude' | 'codex' | 'pi' | 'all'
1479
1531
  * @param {string} [config.runtime] - 'python' (legacy aliases accepted: 'unix' | 'windows')
1480
1532
  * @param {string} config.skills - 'core' | 'minimal' | 'recommended:<type>'
1481
1533
  * @param {boolean} config.pipeline - 是否安装 dev-pipeline
@@ -1497,7 +1549,7 @@ export async function scaffold(config) {
1497
1549
  });
1498
1550
  const resolvedAiCli = launchSelection.aiCli;
1499
1551
  if (!isKnownPlatform(platform)) {
1500
- throw new Error(`Unknown platform "${platform}". Expected codebuddy, claude, codex, or all.`);
1552
+ throw new Error(`Unknown platform "${platform}". Expected codebuddy, claude, codex, pi, or all.`);
1501
1553
  }
1502
1554
  const platforms = expandPlatforms(platform);
1503
1555
  const payloadPlatforms = resolvePayloadPlatforms(platform, resolvedAiCli);
@@ -1677,7 +1729,9 @@ export async function scaffold(config) {
1677
1729
  console.log(` 2. ${chalk.cyan(cli)} ${chalk.gray('# 启动 AI 对话')}`);
1678
1730
  const initInstruction = mainPlatform === 'codex'
1679
1731
  ? '说 "运行 prizmkit-init"'
1680
- : '说 "/prizmkit-init"';
1732
+ : mainPlatform === 'pi'
1733
+ ? '输入 "/skill:prizmkit-init"'
1734
+ : '说 "/prizmkit-init"';
1681
1735
  console.log(` 3. ${chalk.cyan(initInstruction)} ${chalk.gray('# 初始化项目上下文')}`);
1682
1736
 
1683
1737
  if (pipeline) {
package/src/upgrade.js CHANGED
@@ -73,14 +73,16 @@ export async function removeSkillFiles(platform, projectRoot, skillNames, dryRun
73
73
  console.log(chalk.red(` ✗ removed .codebuddy/skills/${skillName}/`));
74
74
  }
75
75
  }
76
- } else if (platform === 'codex') {
77
- const skillDir = path.join(projectRoot, '.agents', 'skills', skillName);
76
+ } else if (platform === 'codex' || platform === 'pi') {
77
+ const skillDir = platform === 'pi'
78
+ ? path.join(projectRoot, '.pi', 'skills', skillName)
79
+ : path.join(projectRoot, '.agents', 'skills', skillName);
78
80
  if (await fs.pathExists(skillDir)) {
79
81
  if (dryRun) {
80
- console.log(chalk.gray(` [dry-run] remove .agents/skills/${skillName}/`));
82
+ console.log(chalk.gray(` [dry-run] remove ${path.relative(projectRoot, skillDir)}/`));
81
83
  } else {
82
84
  await fs.remove(skillDir);
83
- console.log(chalk.red(` ✗ removed .agents/skills/${skillName}/`));
85
+ console.log(chalk.red(` ✗ removed ${path.relative(projectRoot, skillDir)}/`));
84
86
  }
85
87
  }
86
88
  }
@@ -91,6 +93,7 @@ export async function removeSkillFiles(platform, projectRoot, skillNames, dryRun
91
93
  * Remove agent files for a specific platform.
92
94
  */
93
95
  export async function removeAgentFiles(platform, projectRoot, agentFileNames, dryRun) {
96
+ if (platform === 'pi') return;
94
97
  for (const fileName of agentFileNames) {
95
98
  const dir = platform === 'claude'
96
99
  ? path.join(projectRoot, '.claude', 'agents')
@@ -124,7 +127,9 @@ export async function removeRuleFiles(platform, projectRoot, ruleFileNames, dryR
124
127
  ? path.join(projectRoot, '.claude', 'rules')
125
128
  : platform === 'codex'
126
129
  ? path.join(projectRoot, '.agents', 'rules')
127
- : path.join(projectRoot, '.codebuddy', 'rules');
130
+ : platform === 'pi'
131
+ ? path.join(projectRoot, '.pi', 'rules')
132
+ : path.join(projectRoot, '.codebuddy', 'rules');
128
133
  // Rules are stored as .md for claude, .mdc for codebuddy
129
134
  const targetName = platform === 'codebuddy'
130
135
  ? baseName.replace(/\.md$/, '.mdc')
@@ -243,11 +248,13 @@ export async function runUpgrade(directory, options = {}) {
243
248
  const hasClaude = await hasPlatformFiles(path.join(projectRoot, '.claude', 'commands'));
244
249
  const hasCodeBuddy = await hasPlatformFiles(path.join(projectRoot, '.codebuddy', 'skills'));
245
250
  const hasCodex = await hasPlatformFiles(path.join(projectRoot, '.agents', 'skills'));
251
+ const hasPi = await hasPlatformFiles(path.join(projectRoot, '.pi', 'skills'));
246
252
 
247
253
  const detectedPlatforms = [
248
254
  hasCodeBuddy && 'codebuddy',
249
255
  hasClaude && 'claude',
250
256
  hasCodex && 'codex',
257
+ hasPi && 'pi',
251
258
  ].filter(Boolean);
252
259
 
253
260
  if (detectedPlatforms.length > 1) platform = 'all';