prizmkit 1.0.13 → 1.0.14

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 (77) hide show
  1. package/bin/create-prizmkit.js +4 -1
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/claude/command-adapter.js +35 -4
  4. package/bundled/adapters/claude/rules-adapter.js +6 -58
  5. package/bundled/adapters/claude/team-adapter.js +2 -2
  6. package/bundled/adapters/codebuddy/agent-adapter.js +0 -1
  7. package/bundled/adapters/codebuddy/rules-adapter.js +30 -0
  8. package/bundled/adapters/shared/frontmatter.js +3 -1
  9. package/bundled/dev-pipeline/README.md +13 -3
  10. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +10 -0
  11. package/bundled/dev-pipeline/launch-daemon.sh +18 -4
  12. package/bundled/dev-pipeline/lib/common.sh +105 -0
  13. package/bundled/dev-pipeline/run-bugfix.sh +57 -57
  14. package/bundled/dev-pipeline/run.sh +75 -59
  15. package/bundled/dev-pipeline/scripts/check-session-status.py +47 -2
  16. package/bundled/dev-pipeline/scripts/cleanup-logs.py +192 -0
  17. package/bundled/dev-pipeline/scripts/detect-stuck.py +15 -3
  18. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +32 -27
  19. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +23 -23
  20. package/bundled/dev-pipeline/scripts/update-feature-status.py +50 -2
  21. package/bundled/dev-pipeline/scripts/utils.py +22 -0
  22. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +18 -1
  23. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +19 -1
  24. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +18 -2
  25. package/bundled/dev-pipeline/templates/session-status-schema.json +7 -1
  26. package/bundled/dev-pipeline/tests/__init__.py +0 -0
  27. package/bundled/dev-pipeline/tests/conftest.py +133 -0
  28. package/bundled/dev-pipeline/tests/test_check_session.py +127 -0
  29. package/bundled/dev-pipeline/tests/test_cleanup_logs.py +119 -0
  30. package/bundled/dev-pipeline/tests/test_detect_stuck.py +207 -0
  31. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +181 -0
  32. package/bundled/dev-pipeline/tests/test_generate_prompt.py +190 -0
  33. package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +153 -0
  34. package/bundled/dev-pipeline/tests/test_init_pipeline.py +241 -0
  35. package/bundled/dev-pipeline/tests/test_update_bug_status.py +142 -0
  36. package/bundled/dev-pipeline/tests/test_update_feature_status.py +277 -0
  37. package/bundled/dev-pipeline/tests/test_utils.py +141 -0
  38. package/bundled/rules/USAGE.md +153 -0
  39. package/bundled/rules/_rules-metadata.json +43 -0
  40. package/bundled/rules/general/prefer-linux-commands.md +9 -0
  41. package/bundled/rules/prizm/prizm-commit-workflow.md +10 -0
  42. package/bundled/rules/prizm/prizm-documentation.md +19 -0
  43. package/bundled/rules/prizm/prizm-progressive-loading.md +11 -0
  44. package/bundled/skills/_metadata.json +130 -67
  45. package/bundled/skills/app-planner/SKILL.md +252 -499
  46. package/bundled/skills/app-planner/assets/evaluation-guide.md +44 -0
  47. package/bundled/skills/app-planner/scripts/validate-and-generate.py +143 -4
  48. package/bundled/skills/bug-planner/SKILL.md +58 -13
  49. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -7
  50. package/bundled/skills/dev-pipeline-launcher/SKILL.md +16 -7
  51. package/bundled/skills/feature-workflow/SKILL.md +175 -234
  52. package/bundled/skills/prizm-kit/SKILL.md +17 -31
  53. package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/SKILL.md +6 -7
  54. package/bundled/skills/{prizmkit-api-doc-generator → prizmkit-tool-api-doc-generator}/SKILL.md +4 -5
  55. package/bundled/skills/{prizmkit-bug-reproducer → prizmkit-tool-bug-reproducer}/SKILL.md +4 -5
  56. package/bundled/skills/{prizmkit-ci-cd-generator → prizmkit-tool-ci-cd-generator}/SKILL.md +4 -5
  57. package/bundled/skills/{prizmkit-db-migration → prizmkit-tool-db-migration}/SKILL.md +4 -5
  58. package/bundled/skills/{prizmkit-dependency-health → prizmkit-tool-dependency-health}/SKILL.md +3 -4
  59. package/bundled/skills/{prizmkit-deployment-strategy → prizmkit-tool-deployment-strategy}/SKILL.md +4 -5
  60. package/bundled/skills/{prizmkit-error-triage → prizmkit-tool-error-triage}/SKILL.md +4 -5
  61. package/bundled/skills/{prizmkit-log-analyzer → prizmkit-tool-log-analyzer}/SKILL.md +4 -5
  62. package/bundled/skills/{prizmkit-monitoring-setup → prizmkit-tool-monitoring-setup}/SKILL.md +4 -5
  63. package/bundled/skills/{prizmkit-onboarding-generator → prizmkit-tool-onboarding-generator}/SKILL.md +4 -5
  64. package/bundled/skills/{prizmkit-perf-profiler → prizmkit-tool-perf-profiler}/SKILL.md +4 -5
  65. package/bundled/skills/{prizmkit-security-audit → prizmkit-tool-security-audit}/SKILL.md +3 -4
  66. package/bundled/skills/{prizmkit-tech-debt-tracker → prizmkit-tool-tech-debt-tracker}/SKILL.md +3 -4
  67. package/bundled/skills/refactor-skill/SKILL.md +371 -0
  68. package/bundled/skills/refactor-workflow/SKILL.md +17 -119
  69. package/package.json +1 -1
  70. package/src/external-skills.js +71 -0
  71. package/src/index.js +62 -4
  72. package/src/metadata.js +36 -0
  73. package/src/scaffold.js +136 -32
  74. package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +0 -356
  75. package/bundled/templates/claude-md-template.md +0 -38
  76. package/bundled/templates/codebuddy-md-template.md +0 -35
  77. /package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/assets/adr-template.md +0 -0
@@ -28,11 +28,14 @@ program
28
28
  .command('install [directory]')
29
29
  .description('Install PrizmKit into a project directory')
30
30
  .option('--platform <platform>', 'Target platform: codebuddy, claude, or both')
31
- .option('--skills <suite>', 'Skill suite: full, core, or minimal', 'full')
31
+ .option('--skills <suite>', 'Skill suite: full, core, minimal, or recommended:<type> (frontend/backend/fullstack/library)', 'full')
32
32
  .option('--team', 'Enable multi-agent team mode (default: true)')
33
33
  .option('--no-team', 'Disable multi-agent team mode')
34
34
  .option('--pipeline', 'Install dev-pipeline (default: true)')
35
35
  .option('--no-pipeline', 'Disable dev-pipeline')
36
+ .option('--rules <preset>', 'Rules preset: recommended, minimal, or none', 'recommended')
37
+ .option('--ai-cli <command>', 'AI CLI executable command (e.g. cbc, claude, claude-internal)')
38
+ .option('--external-skills <names>', 'Comma-separated external skill names to install (e.g. find-skill,uiuxpromax)')
36
39
  .option('--non-interactive', 'Skip interactive prompts (requires --platform)')
37
40
  .option('--dry-run', 'Show what would be created without making changes')
38
41
  .action(async (directory = '.', options) => {
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.0.13",
3
- "bundledAt": "2026-03-13T01:34:34.833Z",
4
- "bundledFrom": "cca8058"
2
+ "frameworkVersion": "1.0.14",
3
+ "bundledAt": "2026-03-15T11:21:10.029Z",
4
+ "bundledFrom": "94ac498"
5
5
  }
@@ -15,6 +15,37 @@ import { existsSync, mkdirSync, cpSync } from 'node:fs';
15
15
  import { readFile, writeFile } from 'node:fs/promises';
16
16
  import path from 'path';
17
17
 
18
+ const TOOL_SHORTHANDS = new Set([
19
+ 'tech_debt_tracker',
20
+ 'bug_reproducer',
21
+ 'adr_manager',
22
+ 'security_audit',
23
+ 'dependency_health',
24
+ 'ci_cd_generator',
25
+ 'deployment_strategy',
26
+ 'db_migration',
27
+ 'monitoring_setup',
28
+ 'error_triage',
29
+ 'log_analyzer',
30
+ 'perf_profiler',
31
+ 'onboarding_generator',
32
+ 'api_doc_generator',
33
+ ]);
34
+
35
+ function toClaudePrizmkitCommand(sub) {
36
+ const normalized = sub.replace(/_/g, '-');
37
+
38
+ if (sub.startsWith('tool_')) {
39
+ return `prizmkit-${normalized}`;
40
+ }
41
+
42
+ if (TOOL_SHORTHANDS.has(sub)) {
43
+ return `prizmkit-tool-${normalized}`;
44
+ }
45
+
46
+ return `prizmkit-${normalized}`;
47
+ }
48
+
18
49
  /**
19
50
  * Convert a core SKILL.md to Claude Code command.md format.
20
51
  * @param {string} skillContent - Content of the core SKILL.md
@@ -27,7 +58,7 @@ export function convertSkillToCommand(skillContent, skillName) {
27
58
  // Claude Code command frontmatter only uses description
28
59
  // Also convert prizmkit.xxx references in the description field
29
60
  let desc = frontmatter.description || `PrizmKit ${skillName} command`;
30
- desc = desc.replace(/prizmkit\.(\w+)/g, (_m, sub) => `/prizmkit-${sub.replace(/_/g, '-')}`);
61
+ desc = desc.replace(/prizmkit\.(\w+)/g, (_m, sub) => `/${toClaudePrizmkitCommand(sub)}`);
31
62
 
32
63
  const claudeFrontmatter = {
33
64
  description: desc,
@@ -53,9 +84,9 @@ export function convertSkillToCommand(skillContent, skillName) {
53
84
  // Replace prizmkit.xxx with /prizmkit-xxx for Claude Code
54
85
  convertedBody = convertedBody.replace(
55
86
  /prizmkit\.(\w+)/g,
56
- (match, sub) => {
57
- // Map common shorthand: prizmkit.init -> /prizmkit-init
58
- const commandName = `prizmkit-${sub.replace(/_/g, '-')}`;
87
+ (_match, sub) => {
88
+ // Map shorthand references like prizmkit.tech_debt_tracker -> /prizmkit-tool-tech-debt-tracker
89
+ const commandName = toClaudePrizmkitCommand(sub);
59
90
  return `\`/${commandName}\``;
60
91
  }
61
92
  );
@@ -8,70 +8,18 @@ import { mkdirSync } from 'node:fs';
8
8
  import { writeFile } from 'node:fs/promises';
9
9
  import path from 'path';
10
10
 
11
- const RULES = [
12
- {
13
- filename: 'prizm-documentation.md',
14
- content: `---
15
- description: "PrizmKit documentation rules"
16
- globs:
17
- - "**/*.ts"
18
- - "**/*.tsx"
19
- - "**/*.js"
20
- - "**/*.jsx"
21
- - "**/*.py"
22
- - "**/*.go"
23
- - "**/*.rs"
24
- - "**/*.java"
25
- ---
26
-
27
- When modifying source files in this project:
28
- 1. Check if \`.prizm-docs/root.prizm\` exists
29
- 2. If it does, read it before making changes to understand project structure
30
- 3. After making changes, update affected \`.prizm-docs/\` files
31
- 4. Follow the Prizm doc format (KEY: value, not prose)
32
- 5. Size limits: L0 = 4KB, L1 = 3KB, L2 = 5KB
33
- `,
34
- },
35
- {
36
- filename: 'prizm-commit-workflow.md',
37
- content: `---
38
- description: "PrizmKit commit workflow rules"
39
- ---
40
-
41
- Before any git commit in this project:
42
- 1. Update \`.prizm-docs/\` for affected modules
43
- 2. Use Conventional Commits format: type(scope): description
44
- 3. Bug fixes use \`fix()\` prefix, not \`feat()\`
45
- 4. Do NOT create REGISTRY.md entries for bug fixes
46
- 5. Use \`/prizmkit-committer\` command for the complete commit workflow
47
- `,
48
- },
49
- {
50
- filename: 'prizm-progressive-loading.md',
51
- content: `---
52
- description: "PrizmKit progressive context loading protocol"
53
- ---
54
-
55
- This project uses PrizmKit's progressive loading protocol:
56
- - ON SESSION START: Read \`.prizm-docs/root.prizm\` (L0 — project map)
57
- - ON TASK: Read L1 (\`.prizm-docs/<module>.prizm\`) for relevant modules
58
- - ON FILE EDIT: Read L2 (\`.prizm-docs/<module>/<submodule>.prizm\`) before modifying
59
- - NEVER load all .prizm docs at once
60
- - Arrow notation (->) in .prizm files indicates load pointers
61
- - DECISIONS and CHANGELOG in .prizm files are append-only
62
- `,
63
- },
64
- ];
65
-
66
11
  /**
67
12
  * Install PrizmKit rules to the target project's .claude/rules/ directory.
68
13
  * @param {string} targetRoot - Target project root
14
+ * @param {Array<{name: string, content: string}>} ruleFiles - Rules to install
69
15
  */
70
- export async function installRules(targetRoot) {
16
+ export async function installRules(targetRoot, ruleFiles) {
71
17
  const rulesDir = path.join(targetRoot, '.claude', 'rules');
72
18
  mkdirSync(rulesDir, { recursive: true });
73
19
 
74
- for (const rule of RULES) {
75
- await writeFile(path.join(rulesDir, rule.filename), rule.content);
20
+ for (const rule of ruleFiles) {
21
+ // Use the basename (after /) as the filename
22
+ const baseName = rule.name.includes('/') ? rule.name.split('/').pop() : rule.name;
23
+ await writeFile(path.join(rulesDir, `${baseName}.md`), rule.content);
76
24
  }
77
25
  }
@@ -15,8 +15,8 @@
15
15
  * - 支持 shutdown 消息终止 teammate
16
16
  */
17
17
 
18
- import { mkdirSync, existsSync } from 'node:fs';
19
- import { writeFile, readFile } from 'node:fs/promises';
18
+ import { mkdirSync } from 'node:fs';
19
+ import { writeFile } from 'node:fs/promises';
20
20
  import path from 'path';
21
21
 
22
22
  /**
@@ -4,7 +4,6 @@
4
4
  * For CodeBuddy, this is mostly pass-through as the core format matches.
5
5
  */
6
6
 
7
- import { parseFrontmatter, buildMarkdown } from '../shared/frontmatter.js';
8
7
  import { existsSync, mkdirSync } from 'node:fs';
9
8
  import { readFile, writeFile, symlink } from 'node:fs/promises';
10
9
  import path from 'path';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * CodeBuddy Rules Adapter
3
+ * Installs .codebuddy/rules/*.mdc files for PrizmKit-enabled projects.
4
+ */
5
+ import { mkdirSync } from 'node:fs';
6
+ import { writeFile } from 'node:fs/promises';
7
+ import path from 'path';
8
+
9
+ export async function installRules(targetRoot, ruleFiles) {
10
+ // ruleFiles: [{ name: 'prizm/prizm-documentation', content: '...' }, ...]
11
+ const rulesDir = path.join(targetRoot, '.codebuddy', 'rules');
12
+ mkdirSync(rulesDir, { recursive: true });
13
+
14
+ for (const rule of ruleFiles) {
15
+ const mdcContent = convertToMdc(rule.content, rule.name);
16
+ // Use the basename (after /) as the filename
17
+ const baseName = rule.name.includes('/') ? rule.name.split('/').pop() : rule.name;
18
+ await writeFile(path.join(rulesDir, `${baseName}.mdc`), mdcContent);
19
+ }
20
+ }
21
+
22
+ function convertToMdc(mdContent, name) {
23
+ // Strip existing frontmatter from .md, build .mdc frontmatter
24
+ const bodyMatch = mdContent.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
25
+ const descMatch = mdContent.match(/description:\s*"([^"]+)"/);
26
+ const body = bodyMatch ? bodyMatch[1] : mdContent;
27
+ const description = descMatch ? descMatch[1] : name;
28
+
29
+ return `---\ndescription: ${description}\nalwaysApply: true\nenabled: true\nupdatedAt: ${new Date().toISOString()}\nprovider: \n---\n${body}`;
30
+ }
@@ -38,9 +38,11 @@ export function parseFrontmatter(content) {
38
38
  value = value.slice(1, -1);
39
39
  }
40
40
 
41
- // Parse numbers
41
+ // Parse numbers (integer or float)
42
42
  if (/^\d+$/.test(value)) {
43
43
  value = parseInt(value, 10);
44
+ } else if (/^\d+\.\d+$/.test(value)) {
45
+ value = parseFloat(value);
44
46
  }
45
47
 
46
48
  frontmatter[key] = value;
@@ -4,7 +4,7 @@ Autonomous development pipeline that drives the `prizm-dev-team` multi-agent tea
4
4
 
5
5
  ## Prerequisites
6
6
 
7
- - Python 3.6+
7
+ - Python 3.8+
8
8
  - [jq](https://jqlang.github.io/jq/) (`brew install jq`)
9
9
  - AI CLI in PATH: CodeBuddy (`cbc`) or Claude Code (`claude`)
10
10
  - `feature-list.json` generated by the `app-planner` skill
@@ -31,13 +31,15 @@ python3 dev-pipeline/scripts/init-pipeline.py \
31
31
 
32
32
  | Command | Description |
33
33
  |---------|-------------|
34
- | `./run.sh run [feature-list.json]` | Start or resume the pipeline. Processes features sequentially by dependency order. |
34
+ | `./run.sh run [feature-list.json] [options]` | Start or resume the pipeline. Processes features sequentially by dependency order. |
35
35
  | `./run.sh status [feature-list.json]` | Display current pipeline status: completed, pending, blocked, failed features. |
36
36
  | `./run.sh reset` | Clear all runtime state in `state/`. Pipeline starts fresh on next `run`. |
37
37
  | `./run.sh help` | Show usage help. |
38
38
  | `./retry-feature.sh <feature-id> [feature-list.json]` | Retry a single failed feature. Runs one session then exits. |
39
39
  | `./reset-feature.sh <feature-id> [--clean] [--run]` | Reset a feature to pending. `--clean` deletes artifacts, `--run` auto-retries. |
40
40
 
41
+ `run.sh` 常用 options:`--resume-phase N`、`--max-retries N`、`--timeout SEC`、`--ai-cli CMD`、`--dry-run`、`--no-reset`。
42
+
41
43
  If `feature-list.json` path is omitted, defaults to `.dev-pipeline/feature-list.json` (run.sh) or `feature-list.json` (retry-feature.sh).
42
44
 
43
45
  ### Retrying a Failed Feature
@@ -95,8 +97,12 @@ What is always reset (with or without `--clean`):
95
97
  | `MAX_RETRIES` | `3` | Maximum retry attempts per feature before marking as failed. |
96
98
  | `SESSION_TIMEOUT` | `0` (no limit) | Timeout in seconds per AI CLI session. 0 = no timeout. |
97
99
  | `AI_CLI` | auto-detect | AI CLI command name. Auto-detects `cbc` or `claude`. Set to override. |
100
+ | `CODEBUDDY_CLI` | (deprecated) | Legacy alias for `AI_CLI`. Prefer `AI_CLI`. |
98
101
  | `HEARTBEAT_INTERVAL` | `30` | Seconds between heartbeat log output while a session is running. |
99
102
  | `HEARTBEAT_STALE_THRESHOLD` | `600` | Seconds before a session is considered stale/stuck. |
103
+ | `LOG_CLEANUP_ENABLED` | `1` | Run log cleanup before pipeline execution (`1`=enabled, `0`=disabled). |
104
+ | `LOG_RETENTION_DAYS` | `14` | Delete logs older than N days. |
105
+ | `LOG_MAX_TOTAL_MB` | `1024` | Keep total log size under N MB by deleting oldest logs first. |
100
106
 
101
107
  Example with custom config:
102
108
 
@@ -105,6 +111,9 @@ MAX_RETRIES=5 ./dev-pipeline/run.sh run feature-list.json
105
111
 
106
112
  # With 2-hour timeout per session
107
113
  SESSION_TIMEOUT=7200 ./dev-pipeline/run.sh run feature-list.json
114
+
115
+ # Keep only recent logs and cap total log size
116
+ LOG_RETENTION_DAYS=7 LOG_MAX_TOTAL_MB=512 ./dev-pipeline/run.sh run feature-list.json
108
117
  ```
109
118
 
110
119
  ## How It Works
@@ -215,6 +224,7 @@ cat dev-pipeline/state/features/F-003/sessions/F-003-*/logs/session.log | less
215
224
 
216
225
  - **Ctrl+C** during execution triggers graceful shutdown — current state is saved.
217
226
  - **Re-running** `./run.sh run feature-list.json` resumes from where it left off. Completed features are skipped.
227
+ - 如需强制从某一阶段继续,可使用:`./run.sh run feature-list.json --resume-phase 6`(例如直接进入实现阶段)。
218
228
 
219
229
  ### Manual Intervention
220
230
 
@@ -422,7 +432,7 @@ run-bugfix.sh main loop
422
432
 
423
433
  ├─ AI CLI session # cbc --print -y < prompt (CBC)
424
434
  │ │ # claude --print -p "$(cat prompt)" --yes (CC)
425
- │ └─ prizmkit-bug-fix-workflow # 5-phase pipeline
435
+ │ └─ bugfix 5-phase workflow
426
436
  │ ├─ Phase 1: Triage (Dev agent: classify, assess impact, write fix-plan.md)
427
437
  │ ├─ Phase 2: Reproduce (Dev agent: create failing reproduction test)
428
438
  │ ├─ Phase 3: Fix (Dev agent: TDD — make reproduction test pass)
@@ -134,6 +134,16 @@ cmd_start() {
134
134
  local start_time
135
135
  start_time=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
136
136
 
137
+ # Rotate log if over 50 MB
138
+ if [[ -f "$LOG_FILE" ]]; then
139
+ local log_bytes
140
+ log_bytes=$(wc -c < "$LOG_FILE" 2>/dev/null | tr -d ' ')
141
+ if [[ "$log_bytes" -gt 52428800 ]]; then
142
+ mv "$LOG_FILE" "${LOG_FILE}.$(date -u '+%Y%m%dT%H%M%S').bak"
143
+ log_info "Log rotated (was $((log_bytes / 1048576))MB): ${LOG_FILE}.bak"
144
+ fi
145
+ fi
146
+
137
147
  log_info "Launching bugfix pipeline..."
138
148
  log_info "Bug fix list: $bug_list"
139
149
  log_info "Log file: $LOG_FILE"
@@ -160,6 +160,16 @@ cmd_start() {
160
160
  local start_time
161
161
  start_time=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
162
162
 
163
+ # Rotate log if over 50 MB
164
+ if [[ -f "$LOG_FILE" ]]; then
165
+ local log_bytes
166
+ log_bytes=$(wc -c < "$LOG_FILE" 2>/dev/null | tr -d ' ')
167
+ if [[ "$log_bytes" -gt 52428800 ]]; then
168
+ mv "$LOG_FILE" "${LOG_FILE}.$(date -u '+%Y%m%dT%H%M%S').bak"
169
+ log_info "Log rotated (was $((log_bytes / 1048576))MB): ${LOG_FILE}.bak"
170
+ fi
171
+ fi
172
+
163
173
  # Launch run.sh in background, fully detached
164
174
  log_info "Launching pipeline..."
165
175
  log_info "Feature list: $feature_list"
@@ -190,10 +200,11 @@ cmd_start() {
190
200
  local pipeline_pid=$!
191
201
  disown "$pipeline_pid" 2>/dev/null || true
192
202
 
193
- # Write PID file
194
- echo "$pipeline_pid" > "$PID_FILE"
203
+ # Write PID file (atomic)
204
+ echo "$pipeline_pid" > "${PID_FILE}.tmp"
205
+ mv "${PID_FILE}.tmp" "$PID_FILE"
195
206
 
196
- # Write start metadata
207
+ # Write start metadata (atomic)
197
208
  python3 -c "
198
209
  import json, sys, os
199
210
  pid, started_at, feature_list, env_overrides, log_file, state_dir = int(sys.argv[1]), sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6]
@@ -204,8 +215,11 @@ data = {
204
215
  'env_overrides': env_overrides,
205
216
  'log_file': log_file
206
217
  }
207
- with open(os.path.join(state_dir, '.pipeline-meta.json'), 'w') as f:
218
+ target = os.path.join(state_dir, '.pipeline-meta.json')
219
+ tmp = target + '.tmp'
220
+ with open(tmp, 'w') as f:
208
221
  json.dump(data, f, indent=2)
222
+ os.replace(tmp, target)
209
223
  " "$pipeline_pid" "$start_time" "$feature_list" "$env_overrides" "$LOG_FILE" "$STATE_DIR" 2>/dev/null || true
210
224
 
211
225
  # Wait briefly and verify
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # dev-pipeline/lib/common.sh - Shared shell helpers
4
+ #
5
+ # Shared by feature and bugfix pipeline runners.
6
+ # Provides:
7
+ # - CLI/platform detection
8
+ # - Common color + log helpers
9
+ # - Common dependency checks
10
+ # ============================================================
11
+
12
+ # Colors
13
+ RED='\033[0;31m'
14
+ GREEN='\033[0;32m'
15
+ YELLOW='\033[1;33m'
16
+ BLUE='\033[0;34m'
17
+ MAGENTA='\033[0;35m'
18
+ BOLD='\033[1m'
19
+ NC='\033[0m'
20
+
21
+ log_info() { echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
22
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
23
+ log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
24
+ log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
25
+
26
+ # Detect AI CLI + platform.
27
+ # Priority:
28
+ # AI_CLI env > .prizmkit/config.json > CODEBUDDY_CLI > auto-detect(cbc/claude) > error
29
+ #
30
+ # Exports:
31
+ # CLI_CMD
32
+ # PLATFORM
33
+ # PRIZMKIT_PLATFORM
34
+ prizm_detect_cli_and_platform() {
35
+ if [[ -n "${AI_CLI:-}" ]]; then
36
+ CLI_CMD="$AI_CLI"
37
+ # Read from .prizmkit/config.json if present
38
+ elif [[ -f ".prizmkit/config.json" ]]; then
39
+ _config_ai_cli=$(python3 -c "
40
+ import json, sys
41
+ try:
42
+ with open('.prizmkit/config.json') as f:
43
+ d = json.load(f)
44
+ v = d.get('ai_cli', '')
45
+ if v: print(v)
46
+ except: pass
47
+ " 2>/dev/null || true)
48
+ if [[ -n "$_config_ai_cli" ]]; then
49
+ CLI_CMD="$_config_ai_cli"
50
+ elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
51
+ CLI_CMD="$CODEBUDDY_CLI"
52
+ elif command -v cbc &>/dev/null; then
53
+ CLI_CMD="cbc"
54
+ elif command -v claude &>/dev/null; then
55
+ CLI_CMD="claude"
56
+ else
57
+ echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
58
+ exit 1
59
+ fi
60
+ elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
61
+ CLI_CMD="$CODEBUDDY_CLI"
62
+ elif command -v cbc &>/dev/null; then
63
+ CLI_CMD="cbc"
64
+ elif command -v claude &>/dev/null; then
65
+ CLI_CMD="claude"
66
+ else
67
+ echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
68
+ exit 1
69
+ fi
70
+
71
+ if [[ -n "${PRIZMKIT_PLATFORM:-}" ]]; then
72
+ PLATFORM="$PRIZMKIT_PLATFORM"
73
+ elif [[ "$CLI_CMD" == *"claude"* ]]; then
74
+ PLATFORM="claude"
75
+ else
76
+ PLATFORM="codebuddy"
77
+ fi
78
+
79
+ export CLI_CMD
80
+ export PLATFORM
81
+ export PRIZMKIT_PLATFORM="$PLATFORM"
82
+ }
83
+
84
+ # Common dependency check (jq + python3 + optional CLI in PATH)
85
+ # Args:
86
+ # $1 - cli command (optional)
87
+ prizm_check_common_dependencies() {
88
+ local cli_cmd="${1:-}"
89
+
90
+ if ! command -v jq &>/dev/null; then
91
+ log_error "jq is required but not installed. Install with: brew install jq"
92
+ exit 1
93
+ fi
94
+
95
+ if ! command -v python3 &>/dev/null; then
96
+ log_error "python3 is required but not installed."
97
+ exit 1
98
+ fi
99
+
100
+ if [[ -n "$cli_cmd" ]] && ! command -v "$cli_cmd" &>/dev/null; then
101
+ log_warn "AI CLI '$cli_cmd' not found in PATH."
102
+ log_warn "Set AI_CLI environment variable to the correct command."
103
+ log_warn "Continuing anyway (will fail when spawning sessions)..."
104
+ fi
105
+ }