peaks-cli 1.4.2 → 2.0.1

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 (180) hide show
  1. package/.claude-plugin/marketplace.json +51 -0
  2. package/CHANGELOG.md +279 -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/capability-commands.js +2 -1
  10. package/dist/src/cli/commands/classify-classify-commands.d.ts +19 -0
  11. package/dist/src/cli/commands/classify-classify-commands.js +151 -0
  12. package/dist/src/cli/commands/code-review-commands.d.ts +34 -0
  13. package/dist/src/cli/commands/code-review-commands.js +83 -0
  14. package/dist/src/cli/commands/config-commands.js +90 -0
  15. package/dist/src/cli/commands/context-commands.d.ts +21 -0
  16. package/dist/src/cli/commands/context-commands.js +167 -0
  17. package/dist/src/cli/commands/core-artifact-commands.js +60 -2
  18. package/dist/src/cli/commands/hook-handle.js +50 -0
  19. package/dist/src/cli/commands/loop-commands.d.ts +21 -0
  20. package/dist/src/cli/commands/loop-commands.js +128 -0
  21. package/dist/src/cli/commands/openspec-commands.js +37 -0
  22. package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
  23. package/dist/src/cli/commands/preferences-commands.js +147 -0
  24. package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
  25. package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
  26. package/dist/src/cli/commands/understand-commands.js +34 -0
  27. package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
  28. package/dist/src/cli/commands/upgrade-commands.js +57 -0
  29. package/dist/src/cli/commands/workflow-commands.js +70 -0
  30. package/dist/src/cli/commands/workspace-commands.js +117 -2
  31. package/dist/src/cli/program.js +30 -0
  32. package/dist/src/lib/render/message-renderer.d.ts +20 -0
  33. package/dist/src/lib/render/message-renderer.js +80 -0
  34. package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
  35. package/dist/src/services/agent/ecc-agent-service.js +143 -0
  36. package/dist/src/services/artifacts/request-artifact-service.js +14 -0
  37. package/dist/src/services/audit/backing-detector.d.ts +24 -0
  38. package/dist/src/services/audit/backing-detector.js +59 -0
  39. package/dist/src/services/audit/classifier.d.ts +38 -0
  40. package/dist/src/services/audit/classifier.js +127 -0
  41. package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
  42. package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
  43. package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
  44. package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
  45. package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
  46. package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
  47. package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
  48. package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
  49. package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
  50. package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
  51. package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
  52. package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
  53. package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
  54. package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
  55. package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
  56. package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
  57. package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
  58. package/dist/src/services/audit/enforcers/lint-style.js +173 -0
  59. package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
  60. package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
  61. package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
  62. package/dist/src/services/audit/enforcers/login-gate.js +40 -0
  63. package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
  64. package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
  65. package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
  66. package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
  67. package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
  68. package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
  69. package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
  70. package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
  71. package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
  72. package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
  73. package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
  74. package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
  75. package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
  76. package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
  77. package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
  78. package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
  79. package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
  80. package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
  81. package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
  82. package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
  83. package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
  84. package/dist/src/services/audit/red-line-catalog.js +210 -0
  85. package/dist/src/services/audit/red-lines-service.d.ts +23 -0
  86. package/dist/src/services/audit/red-lines-service.js +486 -0
  87. package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
  88. package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
  89. package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
  90. package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
  91. package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
  92. package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
  93. package/dist/src/services/audit/static-service.d.ts +57 -0
  94. package/dist/src/services/audit/static-service.js +125 -0
  95. package/dist/src/services/audit/types.d.ts +69 -0
  96. package/dist/src/services/audit/types.js +13 -0
  97. package/dist/src/services/classify/classify-service.d.ts +42 -0
  98. package/dist/src/services/classify/classify-service.js +122 -0
  99. package/dist/src/services/classify/classify-types.d.ts +79 -0
  100. package/dist/src/services/classify/classify-types.js +90 -0
  101. package/dist/src/services/code-review/ocr-service.d.ts +129 -0
  102. package/dist/src/services/code-review/ocr-service.js +362 -0
  103. package/dist/src/services/config/config-migration.d.ts +32 -0
  104. package/dist/src/services/config/config-migration.js +111 -0
  105. package/dist/src/services/config/config-restore.d.ts +10 -0
  106. package/dist/src/services/config/config-restore.js +47 -0
  107. package/dist/src/services/config/config-rollback.d.ts +13 -0
  108. package/dist/src/services/config/config-rollback.js +26 -0
  109. package/dist/src/services/config/config-service.d.ts +36 -2
  110. package/dist/src/services/config/config-service.js +105 -0
  111. package/dist/src/services/config/config-types.d.ts +73 -0
  112. package/dist/src/services/config/config-types.js +28 -13
  113. package/dist/src/services/config/model-routing.js +5 -3
  114. package/dist/src/services/doctor/doctor-service.js +96 -0
  115. package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
  116. package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
  117. package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
  118. package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
  119. package/dist/src/services/ide/ide-registry.js +7 -0
  120. package/dist/src/services/ide/ide-types.d.ts +1 -1
  121. package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
  122. package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
  123. package/dist/src/services/preferences/preferences-service.d.ts +6 -0
  124. package/dist/src/services/preferences/preferences-service.js +43 -0
  125. package/dist/src/services/preferences/preferences-types.d.ts +90 -0
  126. package/dist/src/services/preferences/preferences-types.js +38 -0
  127. package/dist/src/services/rd/rd-service.js +29 -1
  128. package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
  129. package/dist/src/services/skills/skill-conformance-service.js +136 -0
  130. package/dist/src/services/skills/skill-runbook-service.js +44 -10
  131. package/dist/src/services/skills/sync-service.d.ts +86 -0
  132. package/dist/src/services/skills/sync-service.js +271 -0
  133. package/dist/src/services/slice/slice-check-service.js +166 -13
  134. package/dist/src/services/slice/slice-check-types.d.ts +1 -1
  135. package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
  136. package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
  137. package/dist/src/services/understand/understand-scan-service.js +15 -2
  138. package/dist/src/services/understand/understand-types.d.ts +26 -0
  139. package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
  140. package/dist/src/services/upgrade/1x-detector-service.js +94 -0
  141. package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
  142. package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
  143. package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
  144. package/dist/src/services/upgrade/upgrade-service.js +381 -0
  145. package/dist/src/services/workflow/workflow-router-service.js +15 -4
  146. package/dist/src/services/workspace/claude-settings-template.d.ts +53 -0
  147. package/dist/src/services/workspace/claude-settings-template.js +133 -0
  148. package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
  149. package/dist/src/services/workspace/sid-naming-guard.js +31 -0
  150. package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
  151. package/dist/src/services/workspace/workspace-archive-service.js +32 -0
  152. package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
  153. package/dist/src/services/workspace/workspace-clean-service.js +86 -0
  154. package/dist/src/services/workspace/workspace-service.d.ts +24 -0
  155. package/dist/src/services/workspace/workspace-service.js +124 -2
  156. package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
  157. package/dist/src/services/workspace/workspace-state-service.js +43 -0
  158. package/dist/src/shared/change-id.js +4 -1
  159. package/dist/src/shared/version.d.ts +1 -1
  160. package/dist/src/shared/version.js +1 -1
  161. package/package.json +8 -2
  162. package/schemas/doctor-report.schema.json +1 -1
  163. package/scripts/install-skills.mjs +296 -12
  164. package/skills/peaks-doctor/SKILL.md +59 -0
  165. package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
  166. package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
  167. package/skills/peaks-doctor/test_prompts.json +17 -0
  168. package/skills/peaks-ide/SKILL.md +2 -0
  169. package/skills/peaks-qa/SKILL.md +9 -7
  170. package/skills/peaks-qa/references/artifact-per-request.md +19 -5
  171. package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
  172. package/skills/peaks-qa/references/qa-runbook.md +1 -1
  173. package/skills/peaks-rd/SKILL.md +25 -10
  174. package/skills/peaks-rd/references/ocr-integration.md +214 -0
  175. package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
  176. package/skills/peaks-rd/references/rd-runbook.md +1 -1
  177. package/skills/peaks-solo/SKILL.md +16 -4
  178. package/skills/peaks-solo/references/anchoring-and-session-info.md +9 -0
  179. package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
  180. package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OpenClaw IDE adapter (Slice #0.7) — peaks-cli 的第八个内置 IDE 适配器。
3
+ *
4
+ * 不可消除的 per-IDE 字段(slice #0.7 填表):
5
+ * - settings.dirName = '.openclaw' : OpenClaw 项目根下的配置目录
6
+ * - settings.settingsFileName = 'settings.json'
7
+ * - envVar = 'OPENCLAW_PROJECT_DIR' : OpenClaw 注入的 env 变量
8
+ * - hookEvent = 'PreToolUse' : 现代 IDE 通用约定(UNVERIFIED)
9
+ * - toolMatcher = 'Bash' : 同上(UNVERIFIED)
10
+ *
11
+ * Slice #0.7 状态: 同 hermes-adapter.ts 的 UNVERIFIED 占位说明。
12
+ */
13
+ import { homedir } from 'node:os';
14
+ import { join, resolve } from 'node:path';
15
+ import { traeSubAgentDispatcher } from '../../dispatch/sub-agent-dispatcher.js';
16
+ export const OPENCLAW_ADAPTER = {
17
+ id: 'openclaw',
18
+ displayName: 'OpenClaw',
19
+ settings: {
20
+ dirName: '.openclaw', // UNVERIFIED
21
+ settingsFileName: 'settings.json',
22
+ resolveSettingsFile: (scope, projectRoot) => {
23
+ const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
24
+ return join(root, '.openclaw', 'settings.json');
25
+ },
26
+ supportsScope: (scope) => scope === 'project' || scope === 'global'
27
+ },
28
+ envVar: 'OPENCLAW_PROJECT_DIR', // UNVERIFIED
29
+ hookEvent: 'PreToolUse', // UNVERIFIED
30
+ toolMatcher: 'Bash', // UNVERIFIED
31
+ // Slice #0.7: OpenClaw sub-agent dispatcher UNVERIFIED. Reusing Trae
32
+ // dispatcher as uniform placeholder.
33
+ subAgentDispatcher: traeSubAgentDispatcher,
34
+ promptSizeAware: true,
35
+ installHints: [
36
+ 'Restart OpenClaw (or reload the workspace) so the PreToolUse hooks take effect.'
37
+ ],
38
+ capabilities: {
39
+ gateEnforce: true,
40
+ statusline: true
41
+ }
42
+ };
@@ -1,5 +1,7 @@
1
1
  import { CLAUDE_CODE_ADAPTER } from './adapters/claude-code-adapter.js';
2
2
  import { TRAE_ADAPTER } from './adapters/trae-adapter.js';
3
+ import { HERMES_ADAPTER } from './adapters/hermes-adapter.js';
4
+ import { OPENCLAW_ADAPTER } from './adapters/openclaw-adapter.js';
3
5
  /**
4
6
  * Built-in IDE adapter registry。Map<IdeId, IdeAdapter> 是单一来源。
5
7
  *
@@ -9,10 +11,13 @@ import { TRAE_ADAPTER } from './adapters/trae-adapter.js';
9
11
  * 后续 slice 注入 codex / cursor / qoder / tongyi-lingma 时,只需在此
10
12
  * Map 加条目 —— 所有 adapter 使用方(hook-translator、hooks install、statusline
11
13
  * install、mcp apply)通过 `getAdapter(ide)` 拿取,无需修改。
14
+ * Slice #0.7 注入 hermes + openclaw。
12
15
  */
13
16
  const ADAPTERS = new Map([
14
17
  ['claude-code', CLAUDE_CODE_ADAPTER],
15
18
  ['trae', TRAE_ADAPTER],
19
+ ['hermes', HERMES_ADAPTER],
20
+ ['openclaw', OPENCLAW_ADAPTER],
16
21
  ]);
17
22
  /** Get the adapter for a given IDE id. Throws on unsupported IDE. */
18
23
  export function getAdapter(ide) {
@@ -42,4 +47,6 @@ export function _resetAdaptersForTesting() {
42
47
  ADAPTERS.clear();
43
48
  ADAPTERS.set('claude-code', CLAUDE_CODE_ADAPTER);
44
49
  ADAPTERS.set('trae', TRAE_ADAPTER);
50
+ ADAPTERS.set('hermes', HERMES_ADAPTER);
51
+ ADAPTERS.set('openclaw', OPENCLAW_ADAPTER);
45
52
  }
@@ -12,7 +12,7 @@
12
12
  * 其他全部归一化到 peaks 内部模型(见 hook-protocol.ts)。
13
13
  */
14
14
  import type { SubAgentDispatcher } from '../dispatch/sub-agent-dispatcher.js';
15
- export type IdeId = 'claude-code' | 'trae' | 'codex' | 'cursor' | 'qoder' | 'tongyi-lingma';
15
+ export type IdeId = 'claude-code' | 'trae' | 'codex' | 'cursor' | 'qoder' | 'tongyi-lingma' | 'hermes' | 'openclaw';
16
16
  export interface IdeCapabilities {
17
17
  /** peaks gate enforce 是否适用该 IDE(必备) */
18
18
  readonly gateEnforce: true;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * openspec-propose-from-doctor-service (Slice L3.3) — generates a draft
3
+ * OpenSpec change record (proposal.md) from a doctor CRITICAL finding.
4
+ *
5
+ * Per L1+L2+L3 redesign §5.4: "CRITICAL → proposal 草稿生成". The doctor
6
+ * scans the project for issues; when a CRITICAL finding is surfaced,
7
+ * peaks-cli generates a draft `openspec/changes/<id>/proposal.md` so the
8
+ * LLM doesn't have to start from scratch.
9
+ *
10
+ * The draft proposal is INFORMATIONAL — it requires the LLM to review +
11
+ * edit before `peaks openspec validate` will accept it.
12
+ */
13
+ export interface DoctorFinding {
14
+ readonly id: string;
15
+ readonly rule: string;
16
+ readonly detail: string;
17
+ readonly severity: 'pass' | 'warn' | 'fail';
18
+ }
19
+ export interface ProposeFromDoctorInput {
20
+ readonly projectRoot: string;
21
+ readonly finding: DoctorFinding;
22
+ /** Optional clock for testability. */
23
+ readonly clock?: () => string;
24
+ }
25
+ export interface ProposeFromDoctorResult {
26
+ readonly changeId: string;
27
+ readonly changeDir: string;
28
+ readonly proposalPath: string;
29
+ readonly created: boolean;
30
+ }
31
+ export declare function proposeFromDoctor(input: ProposeFromDoctorInput): ProposeFromDoctorResult;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * openspec-propose-from-doctor-service (Slice L3.3) — generates a draft
3
+ * OpenSpec change record (proposal.md) from a doctor CRITICAL finding.
4
+ *
5
+ * Per L1+L2+L3 redesign §5.4: "CRITICAL → proposal 草稿生成". The doctor
6
+ * scans the project for issues; when a CRITICAL finding is surfaced,
7
+ * peaks-cli generates a draft `openspec/changes/<id>/proposal.md` so the
8
+ * LLM doesn't have to start from scratch.
9
+ *
10
+ * The draft proposal is INFORMATIONAL — it requires the LLM to review +
11
+ * edit before `peaks openspec validate` will accept it.
12
+ */
13
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { getErrorMessage } from '../../shared/result.js';
16
+ const SLUGIFY = /[^a-z0-9]+/g;
17
+ function slugify(text) {
18
+ return text
19
+ .toLowerCase()
20
+ .replace(SLUGIFY, '-')
21
+ .replace(/^-+|-+$/g, '')
22
+ .slice(0, 50);
23
+ }
24
+ function formatProposal(input) {
25
+ return `# Doctor Finding: ${input.finding.rule}
26
+
27
+ **Date**: ${input.date}
28
+ **Change ID**: ${input.changeId}
29
+ **Source**: \`peaks doctor\` finding (CRITICAL severity: ${input.finding.severity})
30
+
31
+ ## Why
32
+
33
+ A \`peaks doctor\` scan flagged the following issue at \`${input.finding.id}\`:
34
+
35
+ > ${input.finding.detail}
36
+
37
+ The current behavior is broken or degraded. This proposal outlines a fix.
38
+
39
+ ## What Changes
40
+
41
+ - Address the doctor finding at \`${input.finding.id}\`.
42
+ - See the Why section above for the original error message.
43
+ - Acceptance criteria below describe the success conditions.
44
+
45
+ ## Acceptance Criteria
46
+
47
+ - \`peaks doctor --json\` returns \`ok: true\` for the \`${input.finding.id}\` check.
48
+ - Re-running the audit does not regress other findings.
49
+
50
+ ## Out of Scope
51
+
52
+ - Other doctor findings (each is tracked in its own OpenSpec change).
53
+ - Refactors that don't fix this specific issue.
54
+
55
+ ## Risks
56
+
57
+ - Low: this is a doctor-flagged issue with a clear acceptance criterion.
58
+
59
+ ## Status
60
+
61
+ - created: ${input.date}
62
+ - last update: ${input.date}
63
+ - state: draft
64
+ - state reason: auto-generated from peaks doctor; LLM must review + edit before validate
65
+ `;
66
+ }
67
+ function makeChangeId(finding, date) {
68
+ return `${date}-fix-${slugify(finding.id)}`;
69
+ }
70
+ export function proposeFromDoctor(input) {
71
+ const date = (input.clock ?? (() => new Date().toISOString()))().slice(0, 10);
72
+ const changeId = makeChangeId(input.finding, date);
73
+ const changeDir = join(input.projectRoot, 'openspec/changes', changeId);
74
+ const proposalPath = join(changeDir, 'proposal.md');
75
+ let created = false;
76
+ try {
77
+ if (!existsSync(changeDir)) {
78
+ mkdirSync(changeDir, { recursive: true });
79
+ }
80
+ if (!existsSync(proposalPath)) {
81
+ const content = formatProposal({
82
+ changeId,
83
+ finding: input.finding,
84
+ title: `Fix ${input.finding.rule}`,
85
+ date,
86
+ });
87
+ writeFileSync(proposalPath, content);
88
+ created = true;
89
+ }
90
+ }
91
+ catch (error) {
92
+ throw new Error(`proposeFromDoctor: ${getErrorMessage(error)}`);
93
+ }
94
+ return { changeId, changeDir, proposalPath, created };
95
+ }
@@ -0,0 +1,6 @@
1
+ import { DEFAULT_PREFERENCES, PREFERENCES_SCHEMA_VERSION, type ProjectPreferences } from './preferences-types.js';
2
+ export { DEFAULT_PREFERENCES, PREFERENCES_SCHEMA_VERSION };
3
+ export type { ProjectPreferences } from './preferences-types.js';
4
+ export declare function preferencesPath(projectRoot: string): string;
5
+ export declare function loadPreferences(projectRoot: string): ProjectPreferences;
6
+ export declare function savePreferences(projectRoot: string, overrides: Partial<ProjectPreferences>): ProjectPreferences;
@@ -0,0 +1,43 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { DEFAULT_PREFERENCES, PREFERENCES_SCHEMA_VERSION, } from './preferences-types.js';
4
+ const PREFS_REL_PATH = '.peaks/preferences.json';
5
+ export { DEFAULT_PREFERENCES, PREFERENCES_SCHEMA_VERSION };
6
+ export function preferencesPath(projectRoot) {
7
+ return join(projectRoot, PREFS_REL_PATH);
8
+ }
9
+ export function loadPreferences(projectRoot) {
10
+ const filePath = preferencesPath(projectRoot);
11
+ if (!existsSync(filePath)) {
12
+ return structuredClone(DEFAULT_PREFERENCES);
13
+ }
14
+ let raw;
15
+ try {
16
+ raw = JSON.parse(readFileSync(filePath, 'utf8'));
17
+ }
18
+ catch (err) {
19
+ throw new Error(`PREFERENCES_JSON_INVALID: failed to parse ${filePath}: ${err.message}`);
20
+ }
21
+ if (typeof raw !== 'object' ||
22
+ raw === null ||
23
+ raw.schema_version !== PREFERENCES_SCHEMA_VERSION) {
24
+ throw new Error(`PREFERENCES_SCHEMA_MISMATCH: expected schema_version=${PREFERENCES_SCHEMA_VERSION} in ${filePath}, got ${raw?.schema_version}`);
25
+ }
26
+ return mergePreferences(DEFAULT_PREFERENCES, raw);
27
+ }
28
+ export function savePreferences(projectRoot, overrides) {
29
+ const filePath = preferencesPath(projectRoot);
30
+ const current = loadPreferences(projectRoot);
31
+ const merged = mergePreferences(current, overrides);
32
+ mkdirSync(dirname(filePath), { recursive: true });
33
+ writeFileSync(filePath, JSON.stringify(merged, null, 2) + '\n', 'utf8');
34
+ return merged;
35
+ }
36
+ function mergePreferences(base, overrides) {
37
+ const definedEntries = Object.entries(overrides).filter(([, value]) => value !== undefined);
38
+ return {
39
+ ...base,
40
+ ...Object.fromEntries(definedEntries),
41
+ schema_version: PREFERENCES_SCHEMA_VERSION,
42
+ };
43
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * peaks-cli 2.0 project-local preferences schema.
3
+ * Per spec §8.4 — per-project state lives in `.peaks/preferences.json`,
4
+ * NOT in `~/.peaks/config.json` (which is slim global).
5
+ *
6
+ * Spec reference: docs/superpowers/specs/2026-06-11-peaks-cli-l1-l2-l3-redesign.md §8.4
7
+ */
8
+ export declare const PREFERENCES_SCHEMA_VERSION = "2.0.0";
9
+ /**
10
+ * Per-task-level UA install UX prompt decision.
11
+ * Values:
12
+ * - 'unset' — ask every session (default)
13
+ * - 'skip-this-session' — skip prompt for current session only
14
+ * - 'skip-forever' — never ask, never install
15
+ */
16
+ export type UaPromptDecision = 'unset' | 'skip-this-session' | 'skip-forever';
17
+ /**
18
+ * L1a task classification conservatism.
19
+ * Values:
20
+ * - 'default' — use default signal thresholds
21
+ * - 'strict' — always upgrade to next level (slower, safer)
22
+ * - 'lax' — always downgrade to previous level (faster, riskier)
23
+ */
24
+ export type ClassifyConservatism = 'default' | 'strict' | 'lax';
25
+ /**
26
+ * Per-touchpoint headroom-AI mode override.
27
+ * Spec §7.4 — default 'balanced'.
28
+ */
29
+ export type HeadroomMode = 'balanced' | 'aggressive' | 'conservative';
30
+ export interface HeadroomPreferences {
31
+ /** Whether headroom integration is enabled globally. Default: true */
32
+ readonly enabled: boolean;
33
+ /** Default mode if a touchpoint doesn't override. Default: 'balanced' */
34
+ readonly defaultMode: HeadroomMode;
35
+ /** Per-touchpoint mode overrides */
36
+ readonly perTouchpoint: {
37
+ memorySearch: HeadroomMode;
38
+ retrospectiveSearch: HeadroomMode;
39
+ doctorScan: HeadroomMode;
40
+ doctorRoute: HeadroomMode;
41
+ };
42
+ }
43
+ export interface ClassifyRuleOverrides {
44
+ /** File count threshold above which a task is promoted to 'feature' */
45
+ readonly feature_threshold_files?: number;
46
+ /** Line count threshold above which a task is promoted to 'feature' */
47
+ readonly feature_threshold_lines?: number;
48
+ /** Whether to require a 24h grace before cleaning recently-active sessions */
49
+ readonly runtime_clean_grace_hours?: number;
50
+ }
51
+ export interface SwarmSpeculativePreferences {
52
+ /** Whether speculative dispatch is enabled. Default: true */
53
+ readonly enabled: boolean;
54
+ /** Max concurrent speculative sub-agents. Default: 2 */
55
+ readonly maxConcurrent: number;
56
+ /** Min hit rate below which speculative auto-disables. Default: 0.5 */
57
+ readonly minHitRate: number;
58
+ }
59
+ export interface ProjectPreferences {
60
+ /**
61
+ * On-disk schema version. The JSON key is `schema_version` (snake_case) — this matches the
62
+ * raw on-disk key in `.peaks/preferences.json`, NOT the camelCase used by the rest of this
63
+ * interface. preferences-service.ts validates this value against PREFERENCES_SCHEMA_VERSION
64
+ * on load and writes the current value on save. Any mismatch throws PREFERENCES_SCHEMA_MISMATCH.
65
+ */
66
+ readonly schema_version: typeof PREFERENCES_SCHEMA_VERSION;
67
+ readonly economyMode: boolean;
68
+ readonly swarmMode: boolean;
69
+ readonly uaPrompt: UaPromptDecision;
70
+ readonly agentShieldPrompt: UaPromptDecision;
71
+ readonly classifyConservatism: ClassifyConservatism;
72
+ readonly classifyRules: ClassifyRuleOverrides;
73
+ readonly headroom: HeadroomPreferences;
74
+ readonly swarmSpeculative: SwarmSpeculativePreferences;
75
+ /** Loop Autonomous (L4 14.5) toggle. Default: false — never auto-enable. */
76
+ readonly loopAutonomousEnabled: boolean;
77
+ /**
78
+ * L2.3 P2-a: ECC AgentShield subprocess toggle. Default: false.
79
+ *
80
+ * When true, `peaks audit static` spawns `npx ecc-agentshield scan --json`
81
+ * and merges its findings into the audit report. When false (default),
82
+ * the audit runs peaks-cli-only and the subprocess is never spawned.
83
+ *
84
+ * The preference is independent of whether ECC is installed — i.e.
85
+ * `agentShieldEnabled: true` with ECC missing surfaces a soft
86
+ * "ECC not installed" warning and the audit still completes.
87
+ */
88
+ readonly agentShieldEnabled: boolean;
89
+ }
90
+ export declare const DEFAULT_PREFERENCES: ProjectPreferences;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * peaks-cli 2.0 project-local preferences schema.
3
+ * Per spec §8.4 — per-project state lives in `.peaks/preferences.json`,
4
+ * NOT in `~/.peaks/config.json` (which is slim global).
5
+ *
6
+ * Spec reference: docs/superpowers/specs/2026-06-11-peaks-cli-l1-l2-l3-redesign.md §8.4
7
+ */
8
+ export const PREFERENCES_SCHEMA_VERSION = '2.0.0';
9
+ export const DEFAULT_PREFERENCES = {
10
+ schema_version: PREFERENCES_SCHEMA_VERSION,
11
+ economyMode: true,
12
+ swarmMode: true,
13
+ uaPrompt: 'unset',
14
+ agentShieldPrompt: 'unset',
15
+ classifyConservatism: 'default',
16
+ classifyRules: {
17
+ feature_threshold_files: 10,
18
+ feature_threshold_lines: 100,
19
+ runtime_clean_grace_hours: 24,
20
+ },
21
+ headroom: {
22
+ enabled: true,
23
+ defaultMode: 'balanced',
24
+ perTouchpoint: {
25
+ memorySearch: 'balanced',
26
+ retrospectiveSearch: 'balanced',
27
+ doctorScan: 'balanced',
28
+ doctorRoute: 'conservative',
29
+ },
30
+ },
31
+ swarmSpeculative: {
32
+ enabled: true,
33
+ maxConcurrent: 2,
34
+ minHitRate: 0.5,
35
+ },
36
+ loopAutonomousEnabled: false,
37
+ agentShieldEnabled: false,
38
+ };
@@ -5,6 +5,22 @@ import { validateChangeIdOrThrow, buildArtifactRelativePath } from '../../shared
5
5
  import { WORKSPACE_UNAVAILABLE_NEXT_ACTIONS } from '../../shared/planner-response.js';
6
6
  import { getLocalArtifactPath, hasValidArtifactWorkspace } from '../artifacts/workspace-service.js';
7
7
  import { getConfiguredExecutionModelId, STRONGEST_MODEL_ID } from '../config/model-routing.js';
8
+ /**
9
+ * 2.0.1-bug1: the slim 2.0 `~/.peaks/config.json` no longer carries a
10
+ * `providers` block (legacy model config lives in
11
+ * `.peaks/preferences.json` per spec §10.4). `buildPlan` is a pure
12
+ * planner function and historically took its execution model from
13
+ * `DEFAULT_CONFIG.providers.minimax.model`; with the slim default
14
+ * that field is `undefined`, so `getConfiguredExecutionModelId`
15
+ * would throw. We retain the pre-2.0 default here as a literal so
16
+ * the planner remains usable when the caller has not passed an
17
+ * explicit `executionModelId` (unit tests, dry-run previews, the
18
+ * `peaks swarm plan` onboarding path). Production callers that
19
+ * have a real `ocr.llm.model` configured pass it via
20
+ * `request.executionModelId` (or via the legacy preferences.json
21
+ * bridge) and bypass this fallback.
22
+ */
23
+ const DEFAULT_EXECUTION_MODEL_ID = 'minimax-2.7';
8
24
  import { getTechStatus, TECH_REQUIRED_ARTIFACTS } from '../tech/tech-service.js';
9
25
  function normalizeGoal(goal) {
10
26
  const normalized = goal.trim();
@@ -136,6 +152,18 @@ function readArtifactFile(rootPath, artifactWorkspacePath, artifact) {
136
152
  return null;
137
153
  }
138
154
  }
155
+ function resolveExecutionModelId() {
156
+ try {
157
+ return getConfiguredExecutionModelId(undefined);
158
+ }
159
+ catch {
160
+ // 2.0.1-bug1: with the slim `~/.peaks/config.json` the legacy
161
+ // `providers` block is gone, so the configured-model lookup is
162
+ // expected to throw. Fall back to the pre-2.0 default so the
163
+ // planner remains usable in unit tests and the dry-run path.
164
+ return DEFAULT_EXECUTION_MODEL_ID;
165
+ }
166
+ }
139
167
  function getConcreteTargetAreas(request, artifactWorkspacePath, hasApprovedTechArtifacts) {
140
168
  if (!artifactWorkspacePath || !hasApprovedTechArtifacts || !hasPlannerArtifactWorkspace(request, artifactWorkspacePath)) {
141
169
  return [];
@@ -154,7 +182,7 @@ function buildPlan(request) {
154
182
  validateChangeIdOrThrow(request.changeId);
155
183
  const goal = normalizeGoal(request.goal);
156
184
  const swarmMode = request.swarmMode ?? true;
157
- const executionModelId = request.executionModelId?.trim() || getConfiguredExecutionModelId(undefined);
185
+ const executionModelId = request.executionModelId?.trim() || resolveExecutionModelId();
158
186
  const { workerTarget, blockedReasons } = resolveWorkerTarget(request.maxWorkers);
159
187
  const artifactWorkspacePath = resolveArtifactWorkspacePath(request);
160
188
  const artifactRoot = buildArtifactRelativePath(request.changeId, 'rd', 'swarm');
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Skill-conformance audit (Slice #12) — checks every peaks-* SKILL.md
3
+ * against the 5 standards from the L1+L2+L3 redesign §5.4 Slice #12:
4
+ *
5
+ * 1. task-level frontmatter (all 12 skills must declare one)
6
+ * 2. CLI-back 注解 coverage (each skill body must document which
7
+ * `peaks <cmd>` commands it composes; the absence is a SKILL.md
8
+ * anti-pattern — it means the LLM has to discover the CLI
9
+ * primitives by accident)
10
+ * 3. loadStrategy on-demand 标注 (skills should declare when they
11
+ * load — `eager` for always-loaded, `on-demand` for invoked)
12
+ * 4. 800-line cap (Karpathy limit per spec §2.3)
13
+ * 5. outputStyle: peaks-concise-v1 frontmatter (peak-cli display
14
+ * style for the skill's user-visible output)
15
+ *
16
+ * Plus 1 derived check:
17
+ * 6. CLI primitives declared in references/audit/ (Skill must
18
+ * surface every peaks <cmd> it composes in the references/ subdir;
19
+ * this is the "CLI-back 注解 100% 覆盖" check)
20
+ */
21
+ export type LoadStrategy = 'eager' | 'on-demand';
22
+ export type ConformanceLevel = 'pass' | 'warn' | 'fail';
23
+ export interface ConformanceCheck {
24
+ readonly id: string;
25
+ readonly skill: string;
26
+ readonly level: ConformanceLevel;
27
+ readonly message: string;
28
+ }
29
+ export interface ConformanceReport {
30
+ readonly checked: number;
31
+ readonly passed: number;
32
+ readonly warned: number;
33
+ readonly failed: number;
34
+ readonly checks: readonly ConformanceCheck[];
35
+ readonly summary: string;
36
+ }
37
+ export interface SkillConformanceInput {
38
+ readonly projectRoot: string;
39
+ }
40
+ export declare function auditSkillConformance(input: SkillConformanceInput): ConformanceReport;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Skill-conformance audit (Slice #12) — checks every peaks-* SKILL.md
3
+ * against the 5 standards from the L1+L2+L3 redesign §5.4 Slice #12:
4
+ *
5
+ * 1. task-level frontmatter (all 12 skills must declare one)
6
+ * 2. CLI-back 注解 coverage (each skill body must document which
7
+ * `peaks <cmd>` commands it composes; the absence is a SKILL.md
8
+ * anti-pattern — it means the LLM has to discover the CLI
9
+ * primitives by accident)
10
+ * 3. loadStrategy on-demand 标注 (skills should declare when they
11
+ * load — `eager` for always-loaded, `on-demand` for invoked)
12
+ * 4. 800-line cap (Karpathy limit per spec §2.3)
13
+ * 5. outputStyle: peaks-concise-v1 frontmatter (peak-cli display
14
+ * style for the skill's user-visible output)
15
+ *
16
+ * Plus 1 derived check:
17
+ * 6. CLI primitives declared in references/audit/ (Skill must
18
+ * surface every peaks <cmd> it composes in the references/ subdir;
19
+ * this is the "CLI-back 注解 100% 覆盖" check)
20
+ */
21
+ import { readFileSync, statSync } from 'node:fs';
22
+ import { existsSync } from 'node:fs';
23
+ import { join } from 'node:path';
24
+ const SKILL_NAMES = [
25
+ 'peaks-ide',
26
+ 'peaks-prd',
27
+ 'peaks-ui',
28
+ 'peaks-rd',
29
+ 'peaks-qa',
30
+ 'peaks-sc',
31
+ 'peaks-solo',
32
+ 'peaks-solo-resume',
33
+ 'peaks-solo-status',
34
+ 'peaks-solo-test',
35
+ 'peaks-sop',
36
+ 'peaks-txt',
37
+ 'peaks-doctor'
38
+ ];
39
+ const REQUIRED_FRONTMATTER_FIELDS = ['name', 'description'];
40
+ const OPTIONAL_FRONTMATTER_FIELDS = ['task-level', 'loadStrategy', 'outputStyle'];
41
+ const MAX_LINE_COUNT = 800;
42
+ const CLI_BACK_PATTERNS = [
43
+ /`peaks\s+[a-z][a-z0-9-]+(?:\s+[a-z][a-z0-9-]+)*`/g,
44
+ /`peaks\s+[a-z][a-z0-9-]+/g,
45
+ ];
46
+ function readSkillFrontmatter(skillPath) {
47
+ if (!existsSync(skillPath))
48
+ return null;
49
+ const content = readFileSync(skillPath, 'utf-8');
50
+ const match = /^---\n([\s\S]*?)\n---\n/.exec(content);
51
+ if (match === null)
52
+ return { raw: content, fields: {} };
53
+ const raw = match[1] ?? '';
54
+ const fields = {};
55
+ for (const line of raw.split('\n')) {
56
+ const colonIdx = line.indexOf(':');
57
+ if (colonIdx === -1)
58
+ continue;
59
+ const key = line.slice(0, colonIdx).trim();
60
+ const value = line.slice(colonIdx + 1).trim();
61
+ if (key.length > 0)
62
+ fields[key] = value;
63
+ }
64
+ return { raw, fields };
65
+ }
66
+ function lineCount(filePath) {
67
+ try {
68
+ return statSync(filePath).size > 0 ? readFileSync(filePath, 'utf-8').split(/\r?\n/).length : 0;
69
+ }
70
+ catch {
71
+ return 0;
72
+ }
73
+ }
74
+ function countCliBackReferences(content) {
75
+ let count = 0;
76
+ for (const pattern of CLI_BACK_PATTERNS) {
77
+ pattern.lastIndex = 0;
78
+ const matches = content.match(pattern);
79
+ if (matches)
80
+ count += matches.length;
81
+ }
82
+ return count;
83
+ }
84
+ export function auditSkillConformance(input) {
85
+ const skillsDir = join(input.projectRoot, 'skills');
86
+ const checks = [];
87
+ for (const skillName of SKILL_NAMES) {
88
+ const skillPath = join(skillsDir, skillName, 'SKILL.md');
89
+ const fm = readSkillFrontmatter(skillPath);
90
+ // 1. task-level frontmatter
91
+ if (fm === null) {
92
+ checks.push({ id: 'frontmatter:present', skill: skillName, level: 'fail', message: 'SKILL.md missing' });
93
+ }
94
+ else {
95
+ for (const required of REQUIRED_FRONTMATTER_FIELDS) {
96
+ if (fm.fields[required] === undefined) {
97
+ checks.push({ id: `frontmatter:${required}`, skill: skillName, level: 'fail', message: `frontmatter missing required field "${required}"` });
98
+ }
99
+ }
100
+ }
101
+ // 2. CLI-back annotation
102
+ if (fm !== null) {
103
+ const content = readFileSync(skillPath, 'utf-8');
104
+ const cliCount = countCliBackReferences(content);
105
+ if (cliCount === 0) {
106
+ checks.push({ id: 'cli-back:present', skill: skillName, level: 'warn', message: 'no `peaks <cmd>` references in SKILL.md body; consider documenting which CLI primitives the skill composes' });
107
+ }
108
+ }
109
+ // 3. loadStrategy on-demand 标注
110
+ if (fm !== null && fm.fields['loadStrategy'] === undefined) {
111
+ checks.push({ id: 'loadStrategy:declared', skill: skillName, level: 'warn', message: 'loadStrategy not declared in frontmatter (eager | on-demand)' });
112
+ }
113
+ // 4. 800-line cap
114
+ if (fm !== null) {
115
+ const lines = lineCount(skillPath);
116
+ if (lines > MAX_LINE_COUNT) {
117
+ checks.push({ id: 'line-count:cap', skill: skillName, level: 'fail', message: `${lines} lines > ${MAX_LINE_COUNT} cap (Karpathy)` });
118
+ }
119
+ }
120
+ // 5. outputStyle: peaks-concise-v1
121
+ if (fm !== null && fm.fields['outputStyle'] === undefined) {
122
+ checks.push({ id: 'outputStyle:declared', skill: skillName, level: 'warn', message: 'outputStyle not declared in frontmatter' });
123
+ }
124
+ }
125
+ const failed = checks.filter((c) => c.level === 'fail').length;
126
+ const warned = checks.filter((c) => c.level === 'warn').length;
127
+ const passed = checks.filter((c) => c.level === 'pass').length;
128
+ return {
129
+ checked: checks.length,
130
+ passed,
131
+ warned,
132
+ failed,
133
+ checks,
134
+ summary: failed === 0 ? 'all hard checks pass; warnings are advisory' : `${failed} hard failure(s); fix before shipping`,
135
+ };
136
+ }