peaks-cli 1.4.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/.claude-plugin/marketplace.json +51 -0
  2. package/CHANGELOG.md +238 -0
  3. package/README-en.md +226 -0
  4. package/README.md +152 -122
  5. package/dist/src/cli/commands/agent-commands.d.ts +20 -0
  6. package/dist/src/cli/commands/agent-commands.js +48 -0
  7. package/dist/src/cli/commands/audit-commands.d.ts +18 -0
  8. package/dist/src/cli/commands/audit-commands.js +138 -0
  9. package/dist/src/cli/commands/classify-classify-commands.d.ts +19 -0
  10. package/dist/src/cli/commands/classify-classify-commands.js +151 -0
  11. package/dist/src/cli/commands/code-review-commands.d.ts +34 -0
  12. package/dist/src/cli/commands/code-review-commands.js +83 -0
  13. package/dist/src/cli/commands/config-commands.js +90 -0
  14. package/dist/src/cli/commands/context-commands.d.ts +21 -0
  15. package/dist/src/cli/commands/context-commands.js +167 -0
  16. package/dist/src/cli/commands/core-artifact-commands.js +60 -2
  17. package/dist/src/cli/commands/hook-handle.js +50 -0
  18. package/dist/src/cli/commands/loop-commands.d.ts +21 -0
  19. package/dist/src/cli/commands/loop-commands.js +128 -0
  20. package/dist/src/cli/commands/openspec-commands.js +37 -0
  21. package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
  22. package/dist/src/cli/commands/preferences-commands.js +147 -0
  23. package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
  24. package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
  25. package/dist/src/cli/commands/understand-commands.js +34 -0
  26. package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
  27. package/dist/src/cli/commands/upgrade-commands.js +57 -0
  28. package/dist/src/cli/commands/workflow-commands.js +70 -0
  29. package/dist/src/cli/commands/workspace-commands.js +86 -0
  30. package/dist/src/cli/program.js +30 -0
  31. package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
  32. package/dist/src/services/agent/ecc-agent-service.js +143 -0
  33. package/dist/src/services/artifacts/request-artifact-service.js +14 -0
  34. package/dist/src/services/audit/backing-detector.d.ts +24 -0
  35. package/dist/src/services/audit/backing-detector.js +59 -0
  36. package/dist/src/services/audit/classifier.d.ts +38 -0
  37. package/dist/src/services/audit/classifier.js +127 -0
  38. package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
  39. package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
  40. package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
  41. package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
  42. package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
  43. package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
  44. package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
  45. package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
  46. package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
  47. package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
  48. package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
  49. package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
  50. package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
  51. package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
  52. package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
  53. package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
  54. package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
  55. package/dist/src/services/audit/enforcers/lint-style.js +173 -0
  56. package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
  57. package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
  58. package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
  59. package/dist/src/services/audit/enforcers/login-gate.js +40 -0
  60. package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
  61. package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
  62. package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
  63. package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
  64. package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
  65. package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
  66. package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
  67. package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
  68. package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
  69. package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
  70. package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
  71. package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
  72. package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
  73. package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
  74. package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
  75. package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
  76. package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
  77. package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
  78. package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
  79. package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
  80. package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
  81. package/dist/src/services/audit/red-line-catalog.js +210 -0
  82. package/dist/src/services/audit/red-lines-service.d.ts +23 -0
  83. package/dist/src/services/audit/red-lines-service.js +486 -0
  84. package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
  85. package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
  86. package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
  87. package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
  88. package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
  89. package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
  90. package/dist/src/services/audit/static-service.d.ts +57 -0
  91. package/dist/src/services/audit/static-service.js +125 -0
  92. package/dist/src/services/audit/types.d.ts +69 -0
  93. package/dist/src/services/audit/types.js +13 -0
  94. package/dist/src/services/classify/classify-service.d.ts +42 -0
  95. package/dist/src/services/classify/classify-service.js +122 -0
  96. package/dist/src/services/classify/classify-types.d.ts +79 -0
  97. package/dist/src/services/classify/classify-types.js +90 -0
  98. package/dist/src/services/code-review/ocr-service.d.ts +129 -0
  99. package/dist/src/services/code-review/ocr-service.js +362 -0
  100. package/dist/src/services/config/config-migration.d.ts +32 -0
  101. package/dist/src/services/config/config-migration.js +92 -0
  102. package/dist/src/services/config/config-restore.d.ts +10 -0
  103. package/dist/src/services/config/config-restore.js +47 -0
  104. package/dist/src/services/config/config-rollback.d.ts +13 -0
  105. package/dist/src/services/config/config-rollback.js +26 -0
  106. package/dist/src/services/config/config-service.d.ts +35 -2
  107. package/dist/src/services/config/config-service.js +81 -0
  108. package/dist/src/services/config/config-types.d.ts +58 -0
  109. package/dist/src/services/config/config-types.js +6 -0
  110. package/dist/src/services/doctor/doctor-service.js +96 -0
  111. package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
  112. package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
  113. package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
  114. package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
  115. package/dist/src/services/ide/ide-registry.js +7 -0
  116. package/dist/src/services/ide/ide-types.d.ts +1 -1
  117. package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
  118. package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
  119. package/dist/src/services/preferences/preferences-service.d.ts +6 -0
  120. package/dist/src/services/preferences/preferences-service.js +43 -0
  121. package/dist/src/services/preferences/preferences-types.d.ts +90 -0
  122. package/dist/src/services/preferences/preferences-types.js +38 -0
  123. package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
  124. package/dist/src/services/skills/skill-conformance-service.js +136 -0
  125. package/dist/src/services/skills/skill-runbook-service.js +44 -10
  126. package/dist/src/services/skills/sync-service.d.ts +43 -0
  127. package/dist/src/services/skills/sync-service.js +99 -0
  128. package/dist/src/services/slice/slice-check-service.js +166 -13
  129. package/dist/src/services/slice/slice-check-types.d.ts +1 -1
  130. package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
  131. package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
  132. package/dist/src/services/understand/understand-scan-service.js +15 -2
  133. package/dist/src/services/understand/understand-types.d.ts +26 -0
  134. package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
  135. package/dist/src/services/upgrade/1x-detector-service.js +94 -0
  136. package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
  137. package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
  138. package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
  139. package/dist/src/services/upgrade/upgrade-service.js +381 -0
  140. package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
  141. package/dist/src/services/workspace/sid-naming-guard.js +31 -0
  142. package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
  143. package/dist/src/services/workspace/workspace-archive-service.js +32 -0
  144. package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
  145. package/dist/src/services/workspace/workspace-clean-service.js +86 -0
  146. package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
  147. package/dist/src/services/workspace/workspace-state-service.js +43 -0
  148. package/dist/src/shared/change-id.js +4 -1
  149. package/dist/src/shared/version.d.ts +1 -1
  150. package/dist/src/shared/version.js +1 -1
  151. package/package.json +8 -2
  152. package/schemas/doctor-report.schema.json +1 -1
  153. package/scripts/install-skills.mjs +296 -12
  154. package/skills/peaks-doctor/SKILL.md +59 -0
  155. package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
  156. package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
  157. package/skills/peaks-doctor/test_prompts.json +17 -0
  158. package/skills/peaks-ide/SKILL.md +2 -0
  159. package/skills/peaks-qa/SKILL.md +9 -7
  160. package/skills/peaks-qa/references/artifact-per-request.md +19 -5
  161. package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
  162. package/skills/peaks-qa/references/qa-runbook.md +1 -1
  163. package/skills/peaks-rd/SKILL.md +25 -10
  164. package/skills/peaks-rd/references/ocr-integration.md +214 -0
  165. package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
  166. package/skills/peaks-rd/references/rd-runbook.md +1 -1
  167. package/skills/peaks-solo/SKILL.md +10 -4
  168. package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
  169. package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * peaks classify CLI (Slice L1a + L1b).
3
+ *
4
+ * Subcommands:
5
+ * - peaks classify run --project <path> [--override <level> --reason "<text>"]
6
+ * Classify the current diff via the heuristic + return a JSON envelope
7
+ * with the chosen level, gate set, and audit log.
8
+ * - peaks classify override --level <level> --reason "<text>" --project <path>
9
+ * Force a level; writes the override to the audit log.
10
+ * - peaks classify upgrade --level <level> --reason "<text>" --project <path>
11
+ * Same as override but explicitly framed as an upgrade (audit log
12
+ * records the upgrade event separately from override).
13
+ *
14
+ * Downgrade is REFUSED (per spec §4: "peaks classify downgrade" always
15
+ * errors out). LLM may ask; the CLI never grants.
16
+ */
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { classifyTask } from '../../services/classify/classify-service.js';
20
+ import { TASK_LEVELS } from '../../services/classify/classify-types.js';
21
+ import { loadPreferences } from '../../services/preferences/preferences-service.js';
22
+ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
23
+ import { fail, ok } from '../../shared/result.js';
24
+ const CLASSIFY_AUDIT_FILE = 'classify-audit.jsonl';
25
+ function getSignalsFromGitDiff(projectRoot) {
26
+ // Use git diff --stat to extract file count + line count. Fall back to
27
+ // zeros if git is unavailable (e.g. fresh repo with no commits).
28
+ let stdout;
29
+ try {
30
+ const { execFileSync } = require('node:child_process');
31
+ stdout = execFileSync('git', ['diff', '--shortstat', 'HEAD'], {
32
+ cwd: projectRoot,
33
+ stdio: ['ignore', 'pipe', 'ignore'],
34
+ maxBuffer: 32 * 1024 * 1024,
35
+ }).toString('utf8');
36
+ }
37
+ catch {
38
+ return { filesChanged: 0, linesChanged: 0, touchesDependencies: false, touchesMigrationScripts: false, isPureRefactor: true, keywords: [] };
39
+ }
40
+ const lines = stdout.split('\n').filter((l) => l.trim().length > 0);
41
+ const filesChanged = lines.length;
42
+ let added = 0;
43
+ let removed = 0;
44
+ let touchesDependencies = false;
45
+ let touchesMigrationScripts = false;
46
+ for (const line of lines) {
47
+ const match = /(\d+)\s+insertion.*?(\d+)\s+deletion/.exec(line);
48
+ if (match) {
49
+ added += Number(match[1]);
50
+ removed += Number(match[2]);
51
+ }
52
+ if (/(package\.json|pnpm-lock\.yaml|yarn\.lock|requirements\.txt|go\.mod)/.test(line)) {
53
+ touchesDependencies = true;
54
+ }
55
+ if (/(migrate|codemod|backfill|schema)/.test(line)) {
56
+ touchesMigrationScripts = true;
57
+ }
58
+ }
59
+ // isPureRefactor: heuristic — if added lines / removed lines < 0.1 OR
60
+ // no new exports were added, treat as refactor. For L2.2 the signal is
61
+ // binary (true/false). Default: true (no behavior change is the safe
62
+ // assumption; flip to false when keyword 'add' / 'new' / 'feature' present).
63
+ const isPureRefactor = true;
64
+ return {
65
+ filesChanged,
66
+ linesChanged: added + removed,
67
+ touchesDependencies,
68
+ touchesMigrationScripts,
69
+ isPureRefactor,
70
+ keywords: [],
71
+ };
72
+ }
73
+ function appendAuditEntry(projectRoot, entry) {
74
+ const auditDir = join(projectRoot, '.peaks/_runtime');
75
+ if (!existsSync(auditDir)) {
76
+ try {
77
+ mkdirSync(auditDir, { recursive: true });
78
+ }
79
+ catch { /* ignore */ }
80
+ }
81
+ const auditPath = join(auditDir, CLASSIFY_AUDIT_FILE);
82
+ let body = '';
83
+ try {
84
+ if (existsSync(auditPath)) {
85
+ body = readFileSync(auditPath, 'utf8');
86
+ }
87
+ }
88
+ catch { /* ignore */ }
89
+ body += JSON.stringify(entry) + '\n';
90
+ try {
91
+ writeFileSync(auditPath, body);
92
+ }
93
+ catch { /* best-effort */ }
94
+ }
95
+ function isTaskLevel(value) {
96
+ return TASK_LEVELS.includes(value);
97
+ }
98
+ export function registerClassifyCommands(program, io) {
99
+ const classify = program
100
+ .command('classify')
101
+ .description('L1a task classification: 5-level heuristic (typo/bug/feature/refactor/migration) + override/upgrade + audit log');
102
+ addJsonOption(classify
103
+ .command('run')
104
+ .description('Classify the current diff (git diff HEAD) into one of 5 task levels')
105
+ .requiredOption('--project <path>', 'target project root')
106
+ .option('--override <level>', 'force a level (one of typo|bug|feature|refactor|migration); requires --reason')
107
+ .option('--reason <text>', 'reason for the override (mandatory when --override is set)')).action(async (options) => {
108
+ try {
109
+ const prefs = await loadPreferences(options.project);
110
+ const signals = getSignalsFromGitDiff(options.project);
111
+ let override;
112
+ if (options.override !== undefined) {
113
+ if (!isTaskLevel(options.override)) {
114
+ printResult(io, fail('classify.run', 'INVALID_LEVEL', `level must be one of: ${TASK_LEVELS.join(', ')}`, { provided: options.override }, ['Pass one of typo, bug, feature, refactor, migration']), options.json);
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+ if (options.reason === undefined || options.reason.length === 0) {
119
+ printResult(io, fail('classify.run', 'REASON_REQUIRED', '--reason is required when --override is set', {}, ['Provide a non-empty reason for the override']), options.json);
120
+ process.exitCode = 1;
121
+ return;
122
+ }
123
+ override = { level: options.override, reason: options.reason };
124
+ }
125
+ const result = classifyTask(override !== undefined
126
+ ? { signals, conservatism: prefs.classifyConservatism, override }
127
+ : { signals, conservatism: prefs.classifyConservatism }, prefs.classifyRules.feature_threshold_files, prefs.classifyRules.feature_threshold_lines);
128
+ appendAuditEntry(options.project, result.audit);
129
+ printResult(io, ok('classify.run', result, [], [
130
+ `gate set for level "${result.level}": ${result.gateSet.stages.join(', ')}`,
131
+ `audit log: .peaks/_runtime/${CLASSIFY_AUDIT_FILE}`,
132
+ ]), options.json);
133
+ }
134
+ catch (error) {
135
+ printResult(io, fail('classify.run', 'CLASSIFY_RUN_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Run peaks classify --help for usage']), options.json);
136
+ process.exitCode = 1;
137
+ }
138
+ });
139
+ // Downgrade is REFUSED per spec §4. Surface this as a hard fail.
140
+ classify
141
+ .command('downgrade')
142
+ .description('REFUSED per spec §4 — peaks-cli never downgrades a classification; ask the user to override explicitly')
143
+ .requiredOption('--level <level>', 'attempted level')
144
+ .requiredOption('--reason <text>', 'reason for the attempt (always rejected)')
145
+ .requiredOption('--project <path>', 'target project root')
146
+ .option('--json', 'print machine-readable JSON envelope')
147
+ .action(async (options) => {
148
+ printResult(io, fail('classify.downgrade', 'DOWNGRADE_REFUSED', 'peaks classify downgrade is refused per spec §4. Use --override (with reason) on `classify run` to force a level; the CLI never downgrades a classification unilaterally.', { attemptedLevel: options.level, reason: options.reason }, ['Use `peaks classify run --override <level> --reason "<text>"` instead']), options.json);
149
+ process.exitCode = 2;
150
+ });
151
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * peaks code-review * CLI surface — soft-optional ocr integration
3
+ * for peaks-rd Gate B3.
4
+ *
5
+ * Per the "skill-first / CLI-auxiliary" tenet, peaks-rd SKILL.md
6
+ * is the primary surface; this CLI returns structured JSON the
7
+ * skill consumes to produce a second-opinion code review.
8
+ *
9
+ * Subcommands:
10
+ * - `peaks code-review detect-ocr` — JSON envelope describing the
11
+ * current ocr install + config state (5 reasons: ready /
12
+ * package-missing / binary-missing / config-missing /
13
+ * detection-failed). Source of truth for the LLM endpoint
14
+ * config is `peaksConfig.ocr.llm` in the user's peaks-cli
15
+ * config (NOT `~/.opencodereview/config.json`).
16
+ * - `peaks code-review run-ocr [--from --to --commit]` — invokes
17
+ * `ocr review --format json` and wraps the result in a peaks
18
+ * ResultEnvelope. Soft-fails when ocr isn't ready so peaks-rd
19
+ * can continue without the second-opinion review. Injects the
20
+ * LLM endpoint config as env vars (OCR_LLM_URL / OCR_LLM_TOKEN
21
+ * / OCR_LLM_MODEL / OCR_USE_ANTHROPIC / OCR_LLM_AUTH_HEADER)
22
+ * so the ocr subprocess never has to read from a file the
23
+ * user did not set up themselves.
24
+ * - `peaks code-review config-template` — prints the JSON snippet
25
+ * the user should paste into their peaks-cli config.json. It
26
+ * does NOT write anything. The user is in control of their
27
+ * LLM token / URL / model. No `peaks ocr config set`; the user
28
+ * either edits the JSON directly or uses `peaks config set
29
+ * --key ocr.llm.url --value '...'` (a peaks-cli command, not
30
+ * an ocr command).
31
+ */
32
+ import { Command } from 'commander';
33
+ import { type ProgramIO } from '../cli-helpers.js';
34
+ export declare function registerCodeReviewCommands(program: Command, io: ProgramIO): void;
@@ -0,0 +1,83 @@
1
+ import { getOcrConfigTemplate, detectOcr, runOcrReview } from '../../services/code-review/ocr-service.js';
2
+ import { getOcrLlmConfig, getUserConfigPath, redactConfigSecrets } from '../../services/config/config-service.js';
3
+ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
4
+ import { fail, ok } from '../../shared/result.js';
5
+ export function registerCodeReviewCommands(program, io) {
6
+ const codeReview = program
7
+ .command('code-review')
8
+ .description('Code-review primitives for peaks-rd Gate B3. Wraps the soft-optional `@alibaba-group/open-code-review` (ocr) tool when it is installed + configured; peaks-rd uses the structured JSON output as a second-opinion review alongside its own LLM review. ocr ships as a peaks-cli dependency (not optional). LLM endpoint config lives under `peaksConfig.ocr.llm` in the user config — run `peaks code-review config-template` to see the JSON snippet to paste.');
9
+ addJsonOption(codeReview
10
+ .command('detect-ocr')
11
+ .description('Read-only probe: returns the ocr install + config state as a JSON envelope (5 reasons: ready / package-missing / binary-missing / config-missing / detection-failed). peaks-rd calls this first to decide whether to invoke `run-ocr`. Reads the LLM endpoint from `peaksConfig.ocr.llm` (not from ~/.opencodereview/config.json).')
12
+ .option('--project <path>', 'project root (default: cwd)')).action((options) => {
13
+ const projectRoot = options.project ?? process.cwd();
14
+ try {
15
+ const detect = detectOcr({
16
+ cwd: projectRoot,
17
+ peaksConfigPath: getUserConfigPath(),
18
+ peaksOcrConfig: getOcrLlmConfig(),
19
+ });
20
+ const envelope = detect.state === 'ready'
21
+ ? ok('code-review.detect-ocr', detect, [...detect.warnings], [...detect.nextActions])
22
+ : fail('code-review.detect-ocr', detect.state.toUpperCase().replace(/-/g, '_'), `ocr is not ready: ${detect.state}`, detect, [...detect.nextActions]);
23
+ printResult(io, envelope, options.json);
24
+ if (detect.state !== 'ready') {
25
+ process.exitCode = 1;
26
+ }
27
+ }
28
+ catch (error) {
29
+ printResult(io, fail('code-review.detect-ocr', 'DETECT_OCR_FAILED', getErrorMessage(error), { state: 'detection-failed', packageInstalled: false, binaryPath: null, version: null, configPath: '', configValid: false, missingKeys: [] }, ['Re-run with `--project <path>` pointing at a known-good project root.']), options.json);
30
+ process.exitCode = 1;
31
+ }
32
+ });
33
+ addJsonOption(codeReview
34
+ .command('run-ocr')
35
+ .description('Run `ocr review --format json` and return the parsed JSON envelope. Soft-fails (exit 0, ok=false) when ocr is not ready, so peaks-rd can continue without the second-opinion review and surface the install/config nextActions to the user. The peaks-cli `peaksConfig.ocr.llm` block is injected as OCR_LLM_URL / OCR_LLM_TOKEN / ... env vars so the ocr subprocess never has to read ~/.opencodereview/config.json.')
36
+ .option('--project <path>', 'project root (default: cwd)')
37
+ .option('--from <ref>', 'git ref to diff from (e.g. main, origin/main)')
38
+ .option('--to <ref>', 'git ref to diff to (e.g. HEAD, feature-branch)')
39
+ .option('--commit <sha>', 'specific commit SHA to review')).action((options) => {
40
+ const projectRoot = options.project ?? process.cwd();
41
+ try {
42
+ const result = runOcrReview({
43
+ cwd: projectRoot,
44
+ peaksConfigPath: getUserConfigPath(),
45
+ peaksOcrConfig: getOcrLlmConfig(),
46
+ input: { projectRoot, ...(options.from !== undefined && { from: options.from }), ...(options.to !== undefined && { to: options.to }), ...(options.commit !== undefined && { commit: options.commit }) },
47
+ });
48
+ // Soft-fail policy: when ocr is not ready or the subprocess
49
+ // failed, we still return a JSON envelope (ok=false) but
50
+ // do NOT set process.exitCode — the caller is expected to
51
+ // pattern-match on the state and proceed without ocr.
52
+ const envelope = result.spawned && result.exitCode === 0
53
+ ? ok('code-review.run-ocr', result, [...result.warnings], [...result.nextActions])
54
+ : fail('code-review.run-ocr', result.state.toUpperCase().replace(/-/g, '_'), result.spawned ? `ocr review exited ${result.exitCode}` : `ocr is not ready: ${result.state}`, result, [...result.nextActions]);
55
+ printResult(io, envelope, options.json);
56
+ // Intentionally do NOT set process.exitCode here — soft-fail.
57
+ }
58
+ catch (error) {
59
+ printResult(io, fail('code-review.run-ocr', 'RUN_OCR_FAILED', getErrorMessage(error), { spawned: false, state: 'detection-failed', exitCode: null, stdout: '', stderr: '', durationMs: 0, parsed: null }, ['Run `peaks code-review detect-ocr --json` to inspect ocr install state.']), options.json);
60
+ // Soft-fail on exception too — peaks-rd should continue.
61
+ }
62
+ });
63
+ addJsonOption(codeReview
64
+ .command('config-template')
65
+ .description('Print the JSON snippet the user should paste into their peaks-cli config (`peaksConfig.ocr.llm`). This command does NOT write anything. The user is the only party that touches the LLM token / URL / model — peaks-cli never auto-configures the endpoint. Token value is shown as the placeholder "<your-api-key>"; replace it before pasting.')).action((options) => {
66
+ const template = getOcrConfigTemplate();
67
+ const targetPath = getUserConfigPath();
68
+ const currentConfig = getOcrLlmConfig();
69
+ const currentRedacted = currentConfig === null ? null : redactConfigSecrets(currentConfig, 'ocr.llm');
70
+ const payload = {
71
+ targetPath,
72
+ currentConfig: currentRedacted,
73
+ template,
74
+ nextActions: [
75
+ `Edit ${targetPath} and add the "ocr" block from the template above.`,
76
+ 'OR use peaks-cli config set with one key at a time: `peaks config set --key ocr.llm.url --value \'<url>\'` etc.',
77
+ 'The authToken field is sensitive and is stored in the user layer (`~/.peaks/config.json`); peaks-cli will not commit it.',
78
+ 'Re-run `peaks code-review detect-ocr --json` to verify the new state is `ready`.',
79
+ ],
80
+ };
81
+ printResult(io, ok('code-review.config-template', payload), options.json);
82
+ });
83
+ }
@@ -1,10 +1,14 @@
1
+ import { executeMigration, planMigration } from '../../services/config/config-migration.js';
1
2
  import { getConfig, getMiniMaxProviderConfig, getMiniMaxProviderStatus, isSensitiveConfigPath, redactConfigSecrets, setConfig, setMiniMaxProviderConfig } from '../../services/config/config-service.js';
3
+ import { listAvailableFields, restoreField } from '../../services/config/config-restore.js';
4
+ import { executeRollback, planRollback } from '../../services/config/config-rollback.js';
2
5
  import { testMiniMaxProvider } from '../../services/providers/minimax-provider-service.js';
3
6
  import { fail, ok } from '../../shared/result.js';
4
7
  import { addJsonOption, getErrorMessage, isMiniMaxHttpsUrl, parseConfigLayer, printInvalidConfigLayer, printResult, redactSensitiveErrorMessage, summarizeMiniMaxSmokeResult } from '../cli-helpers.js';
5
8
  export function registerConfigCommands(program, io) {
6
9
  const config = program.command('config').description('Manage Peaks configuration');
7
10
  registerConfigGetSetCommands(config, io);
11
+ registerConfigMigrationCommands(config, io);
8
12
  registerMiniMaxProviderCommands(config, io);
9
13
  }
10
14
  function registerConfigGetSetCommands(config, io) {
@@ -44,6 +48,92 @@ function registerConfigGetSetCommands(config, io) {
44
48
  }
45
49
  });
46
50
  }
51
+ function registerConfigMigrationCommands(config, io) {
52
+ // Slice 0.5 Task 14 — peaks config {migrate,rollback,restore} subcommands.
53
+ // Wires up the config-migration / config-rollback / config-restore services
54
+ // from Tasks 10-12. The default mode is dry-run; --apply writes.
55
+ config
56
+ .command('migrate')
57
+ .description('Migrate global config from 1.x to 2.0 (YAGNI slim + per-project fields)')
58
+ .option('--project <path>', 'current project root (for migrating per-project fields)', process.cwd())
59
+ .option('--apply', 'actually write changes (default is dry-run)')
60
+ .option('--dry-run', 'plan only, do not write (default)')
61
+ .option('--json', 'JSON envelope output')
62
+ .action((options) => {
63
+ try {
64
+ const apply = options.apply === true;
65
+ if (apply) {
66
+ const result = executeMigration({ currentProjectRoot: options.project, apply: true });
67
+ printResult(io, ok('config.migrate', result), options.json);
68
+ return;
69
+ }
70
+ const plan = planMigration({ currentProjectRoot: options.project });
71
+ printResult(io, ok('config.migrate', { ...plan, applied: false }), options.json);
72
+ }
73
+ catch (error) {
74
+ printResult(io, fail('config.migrate', 'CONFIG_MIGRATE_FAILED', getErrorMessage(error), {}, ['Inspect ~/.peaks/config.json and re-run with --apply']), options.json);
75
+ process.exitCode = 1;
76
+ }
77
+ });
78
+ config
79
+ .command('rollback')
80
+ .description('Rollback global config to 1.x from .bak')
81
+ .option('--apply', 'actually write changes (default is dry-run)')
82
+ .option('--dry-run', 'plan only, do not write (default)')
83
+ .option('--json', 'JSON envelope output')
84
+ .action((options) => {
85
+ try {
86
+ const apply = options.apply === true;
87
+ if (apply) {
88
+ const result = executeRollback({ apply: true });
89
+ printResult(io, ok('config.rollback', result), options.json);
90
+ return;
91
+ }
92
+ const plan = planRollback();
93
+ printResult(io, ok('config.rollback', { ...plan, applied: false }), options.json);
94
+ }
95
+ catch (error) {
96
+ const message = getErrorMessage(error);
97
+ const code = message.startsWith('NO_BACKUP') ? 'NO_BACKUP' : 'CONFIG_ROLLBACK_FAILED';
98
+ io.stderr(`${code}: ${message}`);
99
+ printResult(io, fail('config.rollback', code, message, {}, ['Re-run peaks config migrate --apply to recreate the .bak']), options.json);
100
+ process.exitCode = 1;
101
+ }
102
+ });
103
+ config
104
+ .command('restore')
105
+ .description('Restore a single archived field from .bak to a sidecar file')
106
+ .option('--field <name>', 'field name to restore (e.g. language, currentWorkspace)')
107
+ .option('--list', 'list all fields available in .bak')
108
+ .option('--apply', 'actually write sidecar (default is dry-run)')
109
+ .option('--dry-run', 'plan only, do not write (default)')
110
+ .option('--json', 'JSON envelope output')
111
+ .action((options) => {
112
+ try {
113
+ if (options.list === true || !options.field) {
114
+ const fields = listAvailableFields();
115
+ printResult(io, ok('config.restore', { fields, applied: false }), options.json);
116
+ return;
117
+ }
118
+ const apply = options.apply === true;
119
+ const result = restoreField({ field: options.field, apply });
120
+ printResult(io, ok('config.restore', result), options.json);
121
+ }
122
+ catch (error) {
123
+ const message = getErrorMessage(error);
124
+ let code = 'CONFIG_RESTORE_FAILED';
125
+ if (message.startsWith('NO_BACKUP'))
126
+ code = 'NO_BACKUP';
127
+ else if (message.startsWith('RESTORE_GUARDED'))
128
+ code = 'RESTORE_GUARDED';
129
+ else if (message.startsWith('FIELD_NOT_FOUND'))
130
+ code = 'FIELD_NOT_FOUND';
131
+ io.stderr(`${code}: ${message}`);
132
+ printResult(io, fail('config.restore', code, message, {}, ['Use --list to see available fields']), options.json);
133
+ process.exitCode = 1;
134
+ }
135
+ });
136
+ }
47
137
  function printConfigSetError(io, error, asJson) {
48
138
  const message = getErrorMessage(error);
49
139
  if (message === 'Sensitive config keys must be stored in the user config layer') {
@@ -0,0 +1,21 @@
1
+ /**
2
+ * peaks context * CLI (Slice #3 L1c) — context 4-layer loader.
3
+ *
4
+ * Per docs/superpowers/specs/2026-06-11-peaks-cli-l1-l2-l3-redesign.md §4 L1c:
5
+ * - L0: full read (every SKILL.md in full)
6
+ * - L1: summary (one paragraph per skill + the skill list)
7
+ * - L2: index (just names + paths)
8
+ * - L3: fuzzy search by query (delegates to peaks memory search)
9
+ *
10
+ * The LLM-side UX layer (peaks-solo / peaks-ide) picks the layer based
11
+ * on the L1a task level:
12
+ * - typo: L0 only (a few lines)
13
+ * - bug: L0 + L1
14
+ * - feature: L0 + L1 + L2 + L3(按需)
15
+ * - refactor: L0 + L1 + L2 + L3
16
+ * - migration: L0 + L1 + L2 + L3 + codegraph
17
+ */
18
+ import { Command } from 'commander';
19
+ import { type ProgramIO } from '../cli-helpers.js';
20
+ export type ContextLayer = 'L0' | 'L1' | 'L2' | 'L3';
21
+ export declare function registerContextCommands(program: Command, io: ProgramIO): void;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * peaks context * CLI (Slice #3 L1c) — context 4-layer loader.
3
+ *
4
+ * Per docs/superpowers/specs/2026-06-11-peaks-cli-l1-l2-l3-redesign.md §4 L1c:
5
+ * - L0: full read (every SKILL.md in full)
6
+ * - L1: summary (one paragraph per skill + the skill list)
7
+ * - L2: index (just names + paths)
8
+ * - L3: fuzzy search by query (delegates to peaks memory search)
9
+ *
10
+ * The LLM-side UX layer (peaks-solo / peaks-ide) picks the layer based
11
+ * on the L1a task level:
12
+ * - typo: L0 only (a few lines)
13
+ * - bug: L0 + L1
14
+ * - feature: L0 + L1 + L2 + L3(按需)
15
+ * - refactor: L0 + L1 + L2 + L3
16
+ * - migration: L0 + L1 + L2 + L3 + codegraph
17
+ */
18
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { searchMemory } from '../../services/memory/memory-search-service.js';
21
+ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
22
+ import { fail, ok } from '../../shared/result.js';
23
+ const SKILLS_DIR = 'skills';
24
+ function readSkillsDir(projectRoot) {
25
+ const skillsRoot = join(projectRoot, SKILLS_DIR);
26
+ if (!existsSync(skillsRoot))
27
+ return [];
28
+ const out = [];
29
+ for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
30
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
31
+ continue;
32
+ const skillMd = join(skillsRoot, entry.name, 'SKILL.md');
33
+ if (existsSync(skillMd)) {
34
+ out.push(`skills/${entry.name}/SKILL.md`);
35
+ }
36
+ }
37
+ return out;
38
+ }
39
+ function readSkillFile(projectRoot, relPath) {
40
+ const abs = join(projectRoot, relPath);
41
+ try {
42
+ return readFileSync(abs, 'utf-8');
43
+ }
44
+ catch {
45
+ return '';
46
+ }
47
+ }
48
+ async function fetchL0(projectRoot) {
49
+ const files = readSkillsDir(projectRoot);
50
+ const warnings = [];
51
+ let content = '';
52
+ let byteSize = 0;
53
+ const sizes = [];
54
+ for (const rel of files) {
55
+ const body = readSkillFile(projectRoot, rel);
56
+ if (body.length === 0) {
57
+ warnings.push(`failed to read ${rel}`);
58
+ continue;
59
+ }
60
+ content += `## ${rel}\n\n${body}\n\n`;
61
+ sizes.push({ path: rel, bytes: body.length });
62
+ byteSize += body.length;
63
+ }
64
+ return { layer: 'L0', description: 'full read: every SKILL.md concatenated', files: sizes, content, byteSize, warnings };
65
+ }
66
+ async function fetchL1(projectRoot) {
67
+ const files = readSkillsDir(projectRoot);
68
+ const warnings = [];
69
+ let content = '';
70
+ let byteSize = 0;
71
+ const sizes = [];
72
+ for (const rel of files) {
73
+ const body = readSkillFile(projectRoot, rel);
74
+ if (body.length === 0) {
75
+ warnings.push(`failed to read ${rel}`);
76
+ continue;
77
+ }
78
+ const firstPara = body.split(/\r?\n\r?\n/, 2)[0] ?? body.slice(0, 400);
79
+ content += `## ${rel}\n\n${firstPara}\n\n`;
80
+ sizes.push({ path: rel, bytes: firstPara.length });
81
+ byteSize += firstPara.length;
82
+ }
83
+ return { layer: 'L1', description: 'summary: first paragraph per skill', files: sizes, content, byteSize, warnings };
84
+ }
85
+ async function fetchL2(projectRoot) {
86
+ const files = readSkillsDir(projectRoot);
87
+ const content = files.map((f) => `- ${f}`).join('\n') + '\n';
88
+ const byteSize = content.length;
89
+ return {
90
+ layer: 'L2',
91
+ description: 'index: skill paths only (no body)',
92
+ files: files.map((f) => ({ path: f, bytes: 0 })),
93
+ content,
94
+ byteSize,
95
+ warnings: [],
96
+ };
97
+ }
98
+ async function fetchL3(projectRoot, query) {
99
+ const warnings = [];
100
+ if (query.trim().length === 0) {
101
+ warnings.push('empty query; L3 requires a --query string for fuzzy search');
102
+ return { layer: 'L3', description: 'fuzzy search (empty query)', files: [], content: '', byteSize: 0, warnings };
103
+ }
104
+ try {
105
+ const hits = searchMemory({ projectRoot, query, limit: 10 });
106
+ const lines = [];
107
+ for (const hit of hits) {
108
+ lines.push(`- [${hit.score.toFixed(2)}] ${hit.sourcePath}: ${hit.description.slice(0, 120)}`);
109
+ }
110
+ const content = lines.join('\n') + '\n';
111
+ return {
112
+ layer: 'L3',
113
+ description: 'fuzzy search: top 10 hits from memory index',
114
+ files: hits.map((h) => ({ path: h.sourcePath, bytes: h.description.length })),
115
+ content,
116
+ byteSize: content.length,
117
+ warnings,
118
+ };
119
+ }
120
+ catch (error) {
121
+ return {
122
+ layer: 'L3',
123
+ description: 'fuzzy search (failed)',
124
+ files: [],
125
+ content: '',
126
+ byteSize: 0,
127
+ warnings: [`L3 search failed: ${getErrorMessage(error)}`],
128
+ };
129
+ }
130
+ }
131
+ export function registerContextCommands(program, io) {
132
+ const context = program
133
+ .command('context')
134
+ .description('Slice L1c: context 4-layer loader (L0 full / L1 summary / L2 index / L3 fuzzy)');
135
+ addJsonOption(context
136
+ .command('layer')
137
+ .description('Fetch a context layer (L0 = full SKILL.md; L1 = first paragraph; L2 = index; L3 = fuzzy search by --query)')
138
+ .requiredOption('--project <path>', 'target project root')
139
+ .requiredOption('--level <L0|L1|L2|L3>', 'context layer to load')
140
+ .option('--query <text>', 'fuzzy search query (required for L3)')).action(async (options) => {
141
+ try {
142
+ const level = options.level;
143
+ if (level !== 'L0' && level !== 'L1' && level !== 'L2' && level !== 'L3') {
144
+ printResult(io, fail('context.layer', 'INVALID_LEVEL', `level must be one of L0, L1, L2, L3 (got ${level})`, { provided: level }, ['Pass --level L0|L1|L2|L3']), options.json);
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ let payload;
149
+ if (level === 'L0')
150
+ payload = await fetchL0(options.project);
151
+ else if (level === 'L1')
152
+ payload = await fetchL1(options.project);
153
+ else if (level === 'L2')
154
+ payload = await fetchL2(options.project);
155
+ else
156
+ payload = await fetchL3(options.project, options.query ?? '');
157
+ printResult(io, ok('context.layer', payload, [], [
158
+ `${payload.layer} loaded: ${payload.byteSize} bytes across ${payload.files.length} file(s)`,
159
+ payload.warnings.length > 0 ? `${payload.warnings.length} warning(s); see envelope.warnings` : null
160
+ ].filter((x) => typeof x === 'string')), options.json);
161
+ }
162
+ catch (error) {
163
+ printResult(io, fail('context.layer', 'CONTEXT_LAYER_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Verify the project path and --level value']), options.json);
164
+ process.exitCode = 1;
165
+ }
166
+ });
167
+ }
@@ -6,10 +6,12 @@ import { executeProjectMemoryBackup, executeProjectMemoryExtract, summarizeProje
6
6
  import { summarizeProjectStandardsInitResult, summarizeProjectStandardsUpdateResult } from '../../services/standards/project-standards-service.js';
7
7
  import { executeProjectStandardsInitIdeAware, executeProjectStandardsUpdateIdeAware } from '../../services/standards/ide-aware-standards-service.js';
8
8
  import { migrateStandards } from '../../services/standards/migrate-service.js';
9
+ import { migrateClaudeRules } from '../../services/standards/migrate-claude-rules-service.js';
9
10
  import { listProfiles } from '../../services/profiles/profile-service.js';
10
11
  import { planProxyTest } from '../../services/proxy/proxy-service.js';
11
12
  import { runDoctor } from '../../services/doctor/doctor-service.js';
12
13
  import { listSkills } from '../../services/skills/skill-registry.js';
14
+ import { runSkillSync, SYNC_PLATFORMS } from '../../services/skills/sync-service.js';
13
15
  import { inspectSkillRunbook } from '../../services/skills/skill-runbook-service.js';
14
16
  import { setSkillPresence, clearSkillPresence, getSkillPresence, isSkillPresenceMode, touchSkillHeartbeat } from '../../services/skills/skill-presence-service.js';
15
17
  import { detectPresenceMarker } from '../../services/hooks/presence-marker-detector.js';
@@ -88,6 +90,42 @@ export function registerCoreAndArtifactCommands(program, io) {
88
90
  process.exitCode = 1;
89
91
  }
90
92
  });
93
+ // Slice #12 final piece (per spec §9 line 1105):
94
+ // `peaks skills sync 8 平台分发`. Idempotent: re-running is a
95
+ // no-op when the symlinks are already correct.
96
+ addJsonOption(skill
97
+ .command('sync')
98
+ .description(`Sync the peaks-* skill family to one or all of the 8 supported LLM-CLI platforms (${SYNC_PLATFORMS.join(', ')}). Idempotent.`)
99
+ .option('--platform <id>', `sync only one platform (default: --all). Valid: ${SYNC_PLATFORMS.join(', ')}`)
100
+ .option('--all', 'sync all 8 platforms (default if --platform is omitted)')
101
+ .option('--dry-run', 'do not write; emit the same shape with applied=false')
102
+ .option('--project <path>', 'project root (default: cwd)')).action(async (options) => {
103
+ try {
104
+ const projectRoot = options.project ?? process.cwd();
105
+ const platforms = options.platform !== undefined ? [options.platform] : undefined;
106
+ const result = await runSkillSync({
107
+ projectRoot,
108
+ ...(platforms !== undefined ? { platforms } : {}),
109
+ ...(options.dryRun === true ? { dryRun: true } : {}),
110
+ });
111
+ const envelope = ok('skill.sync', result, [], [
112
+ `syncedCount: ${result.syncedCount}/${result.perPlatform.length} platforms`,
113
+ `totalInstalled: ${result.totalInstalled} skill symlinks`,
114
+ result.failedCount > 0
115
+ ? `failedCount: ${result.failedCount} (run \`peaks skill status\` for details)`
116
+ : 'no failures',
117
+ ]);
118
+ printResult(io, envelope, options.json);
119
+ if (result.failedCount > 0) {
120
+ process.exitCode = 1;
121
+ }
122
+ }
123
+ catch (error) {
124
+ const message = getErrorMessage(error);
125
+ printResult(io, fail('skill.sync', 'SKILL_SYNC_FAILED', message, { applied: false }, [message]), options.json);
126
+ process.exitCode = 1;
127
+ }
128
+ });
91
129
  addJsonOption(skill
92
130
  .command('runbook <name>')
93
131
  .description('Inspect a skill Default runbook section and its --apply authorization-note status')).action(async (name, options) => {
@@ -463,10 +501,30 @@ export function registerCoreAndArtifactCommands(program, io) {
463
501
  });
464
502
  addJsonOption(standards
465
503
  .command('migrate')
466
- .description('Rewrite a consumer project CLAUDE.md to drop the legacy heartbeat block (slice 028). Dry-run by default; pass --apply to write.')
504
+ .description('Rewrite a consumer project CLAUDE.md to drop the legacy heartbeat block (slice 028). Dry-run by default; pass --apply to write. With --from-claude-rules, thins the 1.x .claude/rules/ tree to 2-line pointers and scaffolds .peaks/standards/ (slice 2026-06-12-standards-migrate-claude-rules).')
467
505
  .option('--project <path>', 'target project root')
468
- .option('--apply', 'rewrite the legacy block in place; default is dry-run')).action((options) => {
506
+ .option('--apply', 'rewrite the legacy block in place; default is dry-run')
507
+ .option('--from-claude-rules', 'thin .claude/rules/ to pointers and scaffold .peaks/standards/ (used by `peaks upgrade --to 2.0`)')).action((options) => {
469
508
  const projectRoot = options.project ?? process.cwd();
509
+ if (options.fromClaudeRules === true) {
510
+ try {
511
+ const result = migrateClaudeRules({ projectRoot, apply: options.apply === true });
512
+ printResult(io, ok('standards.migrate', result.data, [], [...result.data.nextActions]), options.json);
513
+ }
514
+ catch (error) {
515
+ printResult(io, fail('standards.migrate', 'STANDARDS_MIGRATE_FAILED', getErrorMessage(error), {
516
+ backupPath: null,
517
+ thinnedFiles: [],
518
+ scaffoldedFiles: [],
519
+ preservedFiles: [],
520
+ wouldChange: false,
521
+ applied: false,
522
+ nextActions: [],
523
+ }, [getErrorMessage(error)]), options.json);
524
+ process.exitCode = 1;
525
+ }
526
+ return;
527
+ }
470
528
  try {
471
529
  const result = migrateStandards({ project: projectRoot, apply: options.apply === true });
472
530
  printResult(io, ok('standards.migrate', result.data, [], result.data.nextActions), options.json);