peaks-cli 1.4.1 → 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 (219) 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 +142 -165
  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 +81 -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/memory-commands.d.ts +13 -0
  21. package/dist/src/cli/commands/memory-commands.js +60 -0
  22. package/dist/src/cli/commands/openspec-commands.js +37 -0
  23. package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
  24. package/dist/src/cli/commands/preferences-commands.js +147 -0
  25. package/dist/src/cli/commands/retrospective-commands.d.ts +9 -0
  26. package/dist/src/cli/commands/retrospective-commands.js +58 -0
  27. package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
  28. package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
  29. package/dist/src/cli/commands/understand-commands.js +34 -0
  30. package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
  31. package/dist/src/cli/commands/upgrade-commands.js +57 -0
  32. package/dist/src/cli/commands/workflow-commands.js +70 -0
  33. package/dist/src/cli/commands/workspace-commands.js +86 -0
  34. package/dist/src/cli/program.js +46 -22
  35. package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
  36. package/dist/src/services/agent/ecc-agent-service.js +143 -0
  37. package/dist/src/services/artifacts/request-artifact-service.js +14 -0
  38. package/dist/src/services/audit/backing-detector.d.ts +24 -0
  39. package/dist/src/services/audit/backing-detector.js +59 -0
  40. package/dist/src/services/audit/classifier.d.ts +38 -0
  41. package/dist/src/services/audit/classifier.js +127 -0
  42. package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
  43. package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
  44. package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
  45. package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
  46. package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
  47. package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
  48. package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
  49. package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
  50. package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
  51. package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
  52. package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
  53. package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
  54. package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
  55. package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
  56. package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
  57. package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
  58. package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
  59. package/dist/src/services/audit/enforcers/lint-style.js +173 -0
  60. package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
  61. package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
  62. package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
  63. package/dist/src/services/audit/enforcers/login-gate.js +40 -0
  64. package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
  65. package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
  66. package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
  67. package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
  68. package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
  69. package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
  70. package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
  71. package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
  72. package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
  73. package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
  74. package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
  75. package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
  76. package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
  77. package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
  78. package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
  79. package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
  80. package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
  81. package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
  82. package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
  83. package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
  84. package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
  85. package/dist/src/services/audit/red-line-catalog.js +210 -0
  86. package/dist/src/services/audit/red-lines-service.d.ts +23 -0
  87. package/dist/src/services/audit/red-lines-service.js +486 -0
  88. package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
  89. package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
  90. package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
  91. package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
  92. package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
  93. package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
  94. package/dist/src/services/audit/static-service.d.ts +57 -0
  95. package/dist/src/services/audit/static-service.js +125 -0
  96. package/dist/src/services/audit/types.d.ts +69 -0
  97. package/dist/src/services/audit/types.js +13 -0
  98. package/dist/src/services/classify/classify-service.d.ts +42 -0
  99. package/dist/src/services/classify/classify-service.js +122 -0
  100. package/dist/src/services/classify/classify-types.d.ts +79 -0
  101. package/dist/src/services/classify/classify-types.js +90 -0
  102. package/dist/src/services/code-review/ocr-service.d.ts +129 -0
  103. package/dist/src/services/code-review/ocr-service.js +362 -0
  104. package/dist/src/services/config/config-migration.d.ts +32 -0
  105. package/dist/src/services/config/config-migration.js +92 -0
  106. package/dist/src/services/config/config-restore.d.ts +10 -0
  107. package/dist/src/services/config/config-restore.js +47 -0
  108. package/dist/src/services/config/config-rollback.d.ts +13 -0
  109. package/dist/src/services/config/config-rollback.js +26 -0
  110. package/dist/src/services/config/config-service.d.ts +35 -2
  111. package/dist/src/services/config/config-service.js +81 -0
  112. package/dist/src/services/config/config-types.d.ts +58 -0
  113. package/dist/src/services/config/config-types.js +6 -0
  114. package/dist/src/services/doctor/doctor-service.js +96 -0
  115. package/dist/src/services/fuzzy-matching/fuzzy-match-service.d.ts +15 -0
  116. package/dist/src/services/fuzzy-matching/fuzzy-match-service.js +56 -0
  117. package/dist/src/services/fuzzy-matching/types.d.ts +20 -0
  118. package/dist/src/services/fuzzy-matching/types.js +1 -0
  119. package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
  120. package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
  121. package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
  122. package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
  123. package/dist/src/services/ide/ide-registry.js +7 -0
  124. package/dist/src/services/ide/ide-types.d.ts +1 -1
  125. package/dist/src/services/memory/memory-search-service.d.ts +61 -0
  126. package/dist/src/services/memory/memory-search-service.js +80 -0
  127. package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
  128. package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
  129. package/dist/src/services/preferences/preferences-service.d.ts +6 -0
  130. package/dist/src/services/preferences/preferences-service.js +43 -0
  131. package/dist/src/services/preferences/preferences-types.d.ts +90 -0
  132. package/dist/src/services/preferences/preferences-types.js +38 -0
  133. package/dist/src/services/recommendations/capability-seed-items.js +0 -1
  134. package/dist/src/services/recommendations/capability-seed-mappings.js +0 -1
  135. package/dist/src/services/recommendations/capability-seed-sources.js +0 -1
  136. package/dist/src/services/retrospective/retrospective-search-service.d.ts +37 -0
  137. package/dist/src/services/retrospective/retrospective-search-service.js +75 -0
  138. package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
  139. package/dist/src/services/skills/skill-conformance-service.js +136 -0
  140. package/dist/src/services/skills/skill-runbook-service.js +44 -10
  141. package/dist/src/services/skills/sync-service.d.ts +43 -0
  142. package/dist/src/services/skills/sync-service.js +99 -0
  143. package/dist/src/services/slice/slice-check-service.js +166 -13
  144. package/dist/src/services/slice/slice-check-types.d.ts +1 -1
  145. package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
  146. package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
  147. package/dist/src/services/standards/project-context.d.ts +1 -1
  148. package/dist/src/services/standards/project-context.js +0 -4
  149. package/dist/src/services/standards/project-standards-service.js +1 -3
  150. package/dist/src/services/understand/understand-scan-service.js +15 -2
  151. package/dist/src/services/understand/understand-types.d.ts +26 -0
  152. package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
  153. package/dist/src/services/upgrade/1x-detector-service.js +94 -0
  154. package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
  155. package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
  156. package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
  157. package/dist/src/services/upgrade/upgrade-service.js +381 -0
  158. package/dist/src/services/workspace/migrate-1-4-1-service.js +1 -1
  159. package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
  160. package/dist/src/services/workspace/sid-naming-guard.js +31 -0
  161. package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
  162. package/dist/src/services/workspace/workspace-archive-service.js +32 -0
  163. package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
  164. package/dist/src/services/workspace/workspace-clean-service.js +86 -0
  165. package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
  166. package/dist/src/services/workspace/workspace-state-service.js +43 -0
  167. package/dist/src/shared/change-id.js +4 -1
  168. package/dist/src/shared/version.d.ts +1 -1
  169. package/dist/src/shared/version.js +1 -1
  170. package/package.json +10 -8
  171. package/schemas/doctor-report.schema.json +1 -1
  172. package/scripts/install-skills.mjs +296 -12
  173. package/skills/peaks-doctor/SKILL.md +59 -0
  174. package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
  175. package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
  176. package/skills/peaks-doctor/test_prompts.json +17 -0
  177. package/skills/peaks-ide/SKILL.md +2 -0
  178. package/skills/peaks-qa/SKILL.md +9 -7
  179. package/skills/peaks-qa/references/artifact-per-request.md +19 -5
  180. package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
  181. package/skills/peaks-qa/references/qa-runbook.md +1 -1
  182. package/skills/peaks-rd/SKILL.md +25 -10
  183. package/skills/peaks-rd/references/ocr-integration.md +214 -0
  184. package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
  185. package/skills/peaks-rd/references/rd-runbook.md +1 -1
  186. package/skills/peaks-solo/SKILL.md +11 -5
  187. package/skills/peaks-solo/references/completion-handoff.md +3 -1
  188. package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
  189. package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
  190. package/dist/src/cli/commands/shadcn-commands.d.ts +0 -3
  191. package/dist/src/cli/commands/shadcn-commands.js +0 -35
  192. package/dist/src/cli/commands/skill-context-stats-command.d.ts +0 -40
  193. package/dist/src/cli/commands/skill-context-stats-command.js +0 -96
  194. package/dist/src/cli/commands/skill-scope-commands.d.ts +0 -51
  195. package/dist/src/cli/commands/skill-scope-commands.js +0 -310
  196. package/dist/src/services/shadcn/shadcn-service.d.ts +0 -27
  197. package/dist/src/services/shadcn/shadcn-service.js +0 -128
  198. package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +0 -39
  199. package/dist/src/services/skill-scope/adapters/_stub-helper.js +0 -98
  200. package/dist/src/services/skill-scope/adapters/claude-code.d.ts +0 -59
  201. package/dist/src/services/skill-scope/adapters/claude-code.js +0 -304
  202. package/dist/src/services/skill-scope/adapters/codex.d.ts +0 -2
  203. package/dist/src/services/skill-scope/adapters/codex.js +0 -12
  204. package/dist/src/services/skill-scope/adapters/cursor.d.ts +0 -2
  205. package/dist/src/services/skill-scope/adapters/cursor.js +0 -13
  206. package/dist/src/services/skill-scope/adapters/qoder.d.ts +0 -2
  207. package/dist/src/services/skill-scope/adapters/qoder.js +0 -13
  208. package/dist/src/services/skill-scope/adapters/tongyi.d.ts +0 -2
  209. package/dist/src/services/skill-scope/adapters/tongyi.js +0 -13
  210. package/dist/src/services/skill-scope/adapters/trae.d.ts +0 -2
  211. package/dist/src/services/skill-scope/adapters/trae.js +0 -12
  212. package/dist/src/services/skill-scope/detect.d.ts +0 -81
  213. package/dist/src/services/skill-scope/detect.js +0 -513
  214. package/dist/src/services/skill-scope/registry.d.ts +0 -41
  215. package/dist/src/services/skill-scope/registry.js +0 -83
  216. package/dist/src/services/skill-scope/source-of-truth.d.ts +0 -44
  217. package/dist/src/services/skill-scope/source-of-truth.js +0 -118
  218. package/dist/src/services/skill-scope/types.d.ts +0 -195
  219. package/dist/src/services/skill-scope/types.js +0 -97
@@ -0,0 +1,26 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { backupConfigPath, globalConfigPath } from './config-migration.js';
3
+ export function planRollback() {
4
+ const backup = backupConfigPath();
5
+ if (!existsSync(backup)) {
6
+ return { available: false, detectedVersion: null, backupPath: backup };
7
+ }
8
+ const raw = JSON.parse(readFileSync(backup, 'utf8'));
9
+ return {
10
+ available: true,
11
+ detectedVersion: raw.version ?? null,
12
+ backupPath: backup,
13
+ };
14
+ }
15
+ export function executeRollback(opts) {
16
+ const plan = planRollback();
17
+ if (!plan.available) {
18
+ throw new Error('NO_BACKUP: ~/.peaks/config.json.1.x.bak not found');
19
+ }
20
+ if (!opts.apply) {
21
+ return { ...plan, applied: false };
22
+ }
23
+ const restored = JSON.parse(readFileSync(plan.backupPath, 'utf8'));
24
+ writeFileSync(globalConfigPath(), JSON.stringify(restored, null, 2) + '\n', 'utf8');
25
+ return { ...plan, applied: true, restoredConfigPath: globalConfigPath() };
26
+ }
@@ -1,5 +1,19 @@
1
- import type { ConfigGetOptions, ConfigLayer, ConfigSetOptions, MiniMaxProviderConfig, PeaksConfig, TokenRef, WorkspaceConfig } from './config-types.js';
1
+ import type { ConfigGetOptions, ConfigLayer, ConfigSetOptions, ConfigV2, MiniMaxProviderConfig, OcrAuthHeader, OcrConfig, OcrLlmConfig, PeaksConfig, TokenRef, WorkspaceConfig } from './config-types.js';
2
2
  export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './config-safety.js';
3
+ /**
4
+ * Load the slim 2.0 `~/.peaks/config.json` file. Returns the parsed
5
+ * object when the file is at schema 2.0.0; returns null when the
6
+ * file is absent (fresh install, no global config yet).
7
+ *
8
+ * Throws `CONFIG_LEGACY_VERSION` when the file exists at a 1.x
9
+ * schema version — the caller is expected to run
10
+ * `peaks config migrate --apply` to bring it forward before
11
+ * continuing. This gate is intentional: a slim 2.0 reader must
12
+ * not silently pass through a 1.x shape, because every field it
13
+ * ignores is a field the caller is going to look for elsewhere
14
+ * (preferences.json, .bak, _state/).
15
+ */
16
+ export declare function loadGlobalConfig(): ConfigV2 | null;
3
17
  export declare function isConfigLayer(value: string): value is ConfigLayer;
4
18
  export declare function isSensitiveConfigPath(path: string): boolean;
5
19
  export declare function containsSensitiveConfigValue(value: unknown): boolean;
@@ -18,6 +32,23 @@ export type MiniMaxProviderStatus = {
18
32
  export declare function getMiniMaxProviderConfig(): MiniMaxProviderConfig;
19
33
  export declare function getMiniMaxProviderStatus(): MiniMaxProviderStatus;
20
34
  export declare function setMiniMaxProviderConfig(input: MiniMaxProviderConfig): MiniMaxProviderStatus;
35
+ /**
36
+ * Read the ocr LLM endpoint config from the user-layer
37
+ * `~/.peaks/config.json`. The user populates this themselves by
38
+ * pasting the `peaks code-review config-template` output (or by
39
+ * running `peaks config set --key ocr.llm.url --value '...'`).
40
+ * peaks-cli never auto-writes these values.
41
+ */
42
+ export declare function getOcrConfig(): OcrConfig;
43
+ /**
44
+ * Return the resolved `OcrLlmConfig` block (`peaksConfig.ocr.llm`)
45
+ * or `null` when the user has not populated the user config. The
46
+ * 5-state OCR detector uses this as the source of truth; when the
47
+ * returned block is missing required fields it produces a
48
+ * `config-missing` state with a templated `nextActions` payload
49
+ * the user can paste into their config.
50
+ */
51
+ export declare function getOcrLlmConfig(): OcrLlmConfig | null;
21
52
  export declare function bootstrapProjectLanguageConfig(projectRoot: string, language: string): void;
22
53
  export declare function readConfig(projectRoot?: string | null): PeaksConfig;
23
54
  export declare function writeConfig(partial: Partial<PeaksConfig>, layer?: ConfigLayer): void;
@@ -32,4 +63,6 @@ export declare function getWorkspaceConfigForPath(path?: string): WorkspaceConfi
32
63
  export declare function ensureWorkspaceConfigForPath(path?: string): WorkspaceConfig | null;
33
64
  export declare function getWorkspaceConfigForCurrentPath(): WorkspaceConfig | null;
34
65
  export declare function ensureWorkspaceConfigForCurrentPath(): WorkspaceConfig | null;
35
- export type { TokenRef, WorkspaceConfig, PeaksConfig, ConfigLayer };
66
+ export type { OcrAuthHeader, OcrConfig, OcrLlmConfig, TokenRef, WorkspaceConfig, PeaksConfig, ConfigLayer };
67
+ export { getUserConfigPath } from './config-safety.js';
68
+ export { globalConfigPath } from './config-migration.js';
@@ -3,8 +3,35 @@ import { dirname, isAbsolute, resolve } from 'node:path';
3
3
  import { DEFAULT_CONFIG } from './config-types.js';
4
4
  import { stablePath } from '../../shared/path-utils.js';
5
5
  import { findProjectRoot, getProjectBootstrapConfigPath, getProjectConfigPath, getUserConfigPath, isInsidePath, readConfigFileSafely, resolveProjectRootForConfig, validateArtifactWorkspaceMarkerPath, validateArtifactWorkspaceRoot, validateProjectBootstrapConfigPathForWrite, validateUserConfigPathForWrite, writeConfigFileSafely, writeProjectConfigFile, writeUserConfigFile } from './config-safety.js';
6
+ import { globalConfigPath, CONFIG_SCHEMA_VERSION_V2 } from './config-migration.js';
7
+ import { isConfigV2 } from './config-types.js';
6
8
  // Re-export resolveProjectRootForConfig and resolveCanonicalProjectRoot for external consumers
7
9
  export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './config-safety.js';
10
+ /**
11
+ * Load the slim 2.0 `~/.peaks/config.json` file. Returns the parsed
12
+ * object when the file is at schema 2.0.0; returns null when the
13
+ * file is absent (fresh install, no global config yet).
14
+ *
15
+ * Throws `CONFIG_LEGACY_VERSION` when the file exists at a 1.x
16
+ * schema version — the caller is expected to run
17
+ * `peaks config migrate --apply` to bring it forward before
18
+ * continuing. This gate is intentional: a slim 2.0 reader must
19
+ * not silently pass through a 1.x shape, because every field it
20
+ * ignores is a field the caller is going to look for elsewhere
21
+ * (preferences.json, .bak, _state/).
22
+ */
23
+ export function loadGlobalConfig() {
24
+ const path = globalConfigPath();
25
+ if (!existsSync(path))
26
+ return null;
27
+ const content = readConfigFileSafely(path, 'Global config path must stay inside the user root');
28
+ const raw = JSON.parse(content);
29
+ if (isConfigV2(raw)) {
30
+ return raw;
31
+ }
32
+ const detected = typeof raw.version === 'string' ? raw.version : 'unknown';
33
+ throw new Error(`CONFIG_LEGACY_VERSION: ~/.peaks/config.json is at version "${detected}", expected ${CONFIG_SCHEMA_VERSION_V2}. Run \`peaks config migrate --apply\`.`);
34
+ }
8
35
  function readJsonFile(path, validateBeforeRead, errorMessage = 'Config path must stay inside the config root') {
9
36
  if (!path || !existsSync(path))
10
37
  return null;
@@ -367,6 +394,58 @@ export function setMiniMaxProviderConfig(input) {
367
394
  writeConfig({ providers }, 'user');
368
395
  return createMiniMaxProviderStatus(providers.minimax ?? {});
369
396
  }
397
+ const OCR_AUTH_HEADERS = new Set(['authorization', 'x-api-key', 'bearer']);
398
+ function toOcrLlmConfig(value) {
399
+ if (!isRecord(value))
400
+ return {};
401
+ const url = typeof value.url === 'string' && value.url.trim().length > 0 ? value.url.trim() : undefined;
402
+ const authToken = typeof value.authToken === 'string' && value.authToken.length > 0 ? value.authToken : undefined;
403
+ const model = typeof value.model === 'string' && value.model.trim().length > 0 ? value.model.trim() : undefined;
404
+ const useAnthropic = typeof value.useAnthropic === 'boolean' ? value.useAnthropic : undefined;
405
+ const rawAuthHeader = typeof value.authHeader === 'string' ? value.authHeader : undefined;
406
+ const authHeader = rawAuthHeader !== undefined && OCR_AUTH_HEADERS.has(rawAuthHeader)
407
+ ? rawAuthHeader
408
+ : undefined;
409
+ return {
410
+ ...(url !== undefined ? { url } : {}),
411
+ ...(authToken !== undefined ? { authToken } : {}),
412
+ ...(model !== undefined ? { model } : {}),
413
+ ...(useAnthropic !== undefined ? { useAnthropic } : {}),
414
+ ...(authHeader !== undefined ? { authHeader } : {})
415
+ };
416
+ }
417
+ function toOcrConfig(value) {
418
+ if (!isRecord(value))
419
+ return {};
420
+ return {
421
+ ...(isRecord(value.llm) ? { llm: toOcrLlmConfig(value.llm) } : {})
422
+ };
423
+ }
424
+ /**
425
+ * Read the ocr LLM endpoint config from the user-layer
426
+ * `~/.peaks/config.json`. The user populates this themselves by
427
+ * pasting the `peaks code-review config-template` output (or by
428
+ * running `peaks config set --key ocr.llm.url --value '...'`).
429
+ * peaks-cli never auto-writes these values.
430
+ */
431
+ export function getOcrConfig() {
432
+ const userConfig = readUserJsonFile() ?? {};
433
+ return toOcrConfig(userConfig.ocr);
434
+ }
435
+ /**
436
+ * Return the resolved `OcrLlmConfig` block (`peaksConfig.ocr.llm`)
437
+ * or `null` when the user has not populated the user config. The
438
+ * 5-state OCR detector uses this as the source of truth; when the
439
+ * returned block is missing required fields it produces a
440
+ * `config-missing` state with a templated `nextActions` payload
441
+ * the user can paste into their config.
442
+ */
443
+ export function getOcrLlmConfig() {
444
+ const ocr = getOcrConfig();
445
+ if (!ocr.llm)
446
+ return null;
447
+ return ocr.llm;
448
+ }
370
449
  function inferHumanLanguage(value) {
371
450
  const normalized = value.trim();
372
451
  if (!normalized) {
@@ -641,3 +720,5 @@ export function getWorkspaceConfigForCurrentPath() {
641
720
  export function ensureWorkspaceConfigForCurrentPath() {
642
721
  return ensureWorkspaceConfigForPath(process.cwd());
643
722
  }
723
+ export { getUserConfigPath } from './config-safety.js';
724
+ export { globalConfigPath } from './config-migration.js';
@@ -1,3 +1,4 @@
1
+ import { CONFIG_SCHEMA_VERSION_V2 } from './config-migration.js';
1
2
  export type TokenRef = {
2
3
  env: string;
3
4
  } | {
@@ -49,6 +50,39 @@ export type WorkspaceConfig = {
49
50
  artifactStorage?: ArtifactStorageConfig;
50
51
  installedCapabilityIds: string[];
51
52
  };
53
+ /**
54
+ * Open Code Review (ocr) LLM endpoint config. Stored under
55
+ * `peaksConfig.ocr.llm` so the user has a single, discoverable
56
+ * place to declare their LLM endpoint for the ocr second-opinion
57
+ * review. peaks-cli never auto-writes these values; the user pastes
58
+ * the template (printed by `peaks code-review config-template`) into
59
+ * their `~/.peaks/config.json` themselves.
60
+ *
61
+ * The field names map onto the OCR package's own env-var surface
62
+ * (the highest-priority config path for the ocr subprocess):
63
+ *
64
+ * peaksConfig.ocr.llm.url → OCR_LLM_URL
65
+ * peaksConfig.ocr.llm.authToken → OCR_LLM_TOKEN
66
+ * peaksConfig.ocr.llm.model → OCR_LLM_MODEL
67
+ * peaksConfig.ocr.llm.useAnthropic → OCR_USE_ANTHROPIC
68
+ * peaksConfig.ocr.llm.authHeader → OCR_LLM_AUTH_HEADER
69
+ *
70
+ * All fields are optional at the type level so the user can fill
71
+ * them in one at a time; the 5-state detector treats the
72
+ * `url + authToken + model` triple as the minimum for a `ready`
73
+ * state and reports the missing keys in `nextActions`.
74
+ */
75
+ export type OcrAuthHeader = 'authorization' | 'x-api-key' | 'bearer';
76
+ export type OcrLlmConfig = {
77
+ url?: string;
78
+ authToken?: string;
79
+ model?: string;
80
+ useAnthropic?: boolean;
81
+ authHeader?: OcrAuthHeader;
82
+ };
83
+ export type OcrConfig = {
84
+ llm?: OcrLlmConfig;
85
+ };
52
86
  export type PeaksConfig = {
53
87
  version: string;
54
88
  language: string;
@@ -78,6 +112,15 @@ export type PeaksConfig = {
78
112
  enabled: boolean;
79
113
  heartbeatIntervalMs: number;
80
114
  };
115
+ /**
116
+ * Open Code Review (ocr) second-opinion config. Source of truth
117
+ * for the LLM endpoint that the ocr subprocess consumes via env
118
+ * vars (`OCR_LLM_URL` / `OCR_LLM_TOKEN` / ...). peaks-cli does
119
+ * NOT auto-write this — the user populates it by pasting the
120
+ * `peaks code-review config-template` output into their
121
+ * `~/.peaks/config.json`. See `OcrLlmConfig` for the field map.
122
+ */
123
+ ocr?: OcrConfig;
81
124
  };
82
125
  export type ConfigLayer = 'user' | 'project';
83
126
  export type ConfigGetOptions = {
@@ -90,3 +133,18 @@ export type ConfigSetOptions = {
90
133
  layer?: ConfigLayer;
91
134
  };
92
135
  export declare const DEFAULT_CONFIG: PeaksConfig;
136
+ /**
137
+ * Slim 2.0 schema for `~/.peaks/config.json`. After migration,
138
+ * the only meaningful field is `version`; everything else
139
+ * (language, model, economyMode, swarmMode, tokens, providers,
140
+ * proxy, workspaces, currentWorkspace) is stored elsewhere
141
+ * (`.peaks/preferences.json`, `.peaks/_state/`, or `.bak`).
142
+ *
143
+ * The type is intentionally minimal: extra keys are ignored at
144
+ * runtime, not rejected, so a hand-written or partially-migrated
145
+ * file does not fail the loader.
146
+ */
147
+ export interface ConfigV2 {
148
+ readonly version: typeof CONFIG_SCHEMA_VERSION_V2;
149
+ }
150
+ export declare function isConfigV2(raw: unknown): raw is ConfigV2;
@@ -1,4 +1,5 @@
1
1
  import { CLI_VERSION } from '../../shared/version.js';
2
+ import { CONFIG_SCHEMA_VERSION_V2 } from './config-migration.js';
2
3
  export const DEFAULT_CONFIG = {
3
4
  version: CLI_VERSION,
4
5
  language: 'en',
@@ -17,3 +18,8 @@ export const DEFAULT_CONFIG = {
17
18
  heartbeatIntervalMs: 60000
18
19
  }
19
20
  };
21
+ export function isConfigV2(raw) {
22
+ return (typeof raw === 'object' &&
23
+ raw !== null &&
24
+ raw.version === CONFIG_SCHEMA_VERSION_V2);
25
+ }
@@ -10,6 +10,7 @@ import { loadSkillRegistry } from '../skills/skill-registry.js';
10
10
  import { getSkillPresence } from '../skills/skill-presence-service.js';
11
11
  import { planStatusLineInstall } from '../skills/statusline-settings-service.js';
12
12
  import { findProjectRoot } from '../config/config-safety.js';
13
+ import { isValidSessionId } from '../workspace/sid-naming-guard.js';
13
14
  import { CLI_VERSION } from '../../shared/version.js';
14
15
  const CODEGRAPH_EXPECTED_VERSION = '0.7.10';
15
16
  const SKILL_PRESENCE_FRESHNESS_THRESHOLD_MS = 24 * 60 * 60 * 1000;
@@ -809,6 +810,101 @@ export async function runDoctor(options = {}) {
809
810
  message: `Failed to load doctor-report.schema.json for self-validation: ${getErrorMessage(error)}`
810
811
  });
811
812
  }
813
+ // Slice L3.2: project doctor MVP — 2 new diagnostic checks.
814
+ // 1. L3:l3-orphan-sessions — flags bare sids in .peaks/_runtime/ that
815
+ // fail isValidSessionId. Reuses the Slice 0.5 sid-naming-guard.
816
+ // 2. L3:l3-memory-health — verifies .peaks/memory/index.json is
817
+ // well-formed JSON with the expected schema_version field and
818
+ // references real files on disk.
819
+ // Slice L3.2: project doctor MVP — 2 new diagnostic checks.
820
+ // 1. L3:l3-orphan-sessions — flags bare sids in .peaks/_runtime/ that
821
+ // fail isValidSessionId. Reuses the Slice 0.5 sid-naming-guard.
822
+ // 2. L3:l3-memory-health — verifies .peaks/memory/index.json is
823
+ // well-formed JSON with the expected schema_version field and
824
+ // references real files on disk.
825
+ const l3ProjectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
826
+ try {
827
+ const runtimeDir = join(l3ProjectRoot, '.peaks/_runtime');
828
+ if (existsSync(runtimeDir)) {
829
+ const entries = readdirSync(runtimeDir, { withFileTypes: true })
830
+ .filter((e) => e.isDirectory())
831
+ .map((e) => e.name);
832
+ const validSids = entries.filter((sid) => isValidSessionId(sid));
833
+ const invalidSids = entries.filter((sid) => !isValidSessionId(sid));
834
+ checks.push({
835
+ id: 'L3:l3-orphan-sessions',
836
+ ok: invalidSids.length === 0,
837
+ message: invalidSids.length === 0
838
+ ? `All ${validSids.length} session(s) under .peaks/_runtime/ are valid (isValidSessionId)`
839
+ : `${invalidSids.length} orphan session(s) under .peaks/_runtime/ fail isValidSessionId: ${invalidSids.slice(0, 5).join(', ')}${invalidSids.length > 5 ? '...' : ''}. Run \`peaks workspace clean --project <repo>\` to archive.`
840
+ });
841
+ }
842
+ else {
843
+ checks.push({
844
+ id: 'L3:l3-orphan-sessions',
845
+ ok: true,
846
+ message: 'No .peaks/_runtime/ directory; nothing to check.'
847
+ });
848
+ }
849
+ }
850
+ catch (error) {
851
+ checks.push({
852
+ id: 'L3:l3-orphan-sessions',
853
+ ok: true,
854
+ message: `L3:l3-orphan-sessions probe failed (${getErrorMessage(error)}); skipping check`
855
+ });
856
+ }
857
+ try {
858
+ const memoryIndexPath = join(l3ProjectRoot, '.peaks/memory/index.json');
859
+ if (existsSync(memoryIndexPath)) {
860
+ const raw = readFileSync(memoryIndexPath, 'utf8');
861
+ try {
862
+ const parsed = JSON.parse(raw);
863
+ if (parsed.schema_version === undefined) {
864
+ checks.push({
865
+ id: 'L3:l3-memory-health',
866
+ ok: false,
867
+ message: '.peaks/memory/index.json missing schema_version field'
868
+ });
869
+ }
870
+ else if (!Array.isArray(parsed.entries)) {
871
+ checks.push({
872
+ id: 'L3:l3-memory-health',
873
+ ok: false,
874
+ message: '.peaks/memory/index.json entries is not an array'
875
+ });
876
+ }
877
+ else {
878
+ checks.push({
879
+ id: 'L3:l3-memory-health',
880
+ ok: true,
881
+ message: `.peaks/memory/index.json is well-formed JSON; schema_version=${parsed.schema_version}; ${parsed.entries.length} memory entries`
882
+ });
883
+ }
884
+ }
885
+ catch (parseError) {
886
+ checks.push({
887
+ id: 'L3:l3-memory-health',
888
+ ok: false,
889
+ message: `.peaks/memory/index.json is not valid JSON: ${getErrorMessage(parseError)}`
890
+ });
891
+ }
892
+ }
893
+ else {
894
+ checks.push({
895
+ id: 'L3:l3-memory-health',
896
+ ok: true,
897
+ message: 'No .peaks/memory/index.json yet (no memories extracted)'
898
+ });
899
+ }
900
+ }
901
+ catch (error) {
902
+ checks.push({
903
+ id: 'L3:l3-memory-health',
904
+ ok: true,
905
+ message: `L3:l3-memory-health probe failed (${getErrorMessage(error)}); skipping check`
906
+ });
907
+ }
812
908
  const failed = checks.filter((check) => !check.ok).length;
813
909
  return {
814
910
  checks,
@@ -0,0 +1,15 @@
1
+ import type { FuzzyMatchOptions, FuzzyMatchResult } from './types.js';
2
+ /**
3
+ * String-overload: when `items` is an array of strings, the searchable text
4
+ * is the string itself. No keyFn is required.
5
+ */
6
+ export declare function fuzzyMatch<T extends string>(query: string, items: T[], options?: FuzzyMatchOptions): FuzzyMatchResult<T>[];
7
+ /**
8
+ * Object-overload: caller provides a `keyFn` that extracts the searchable
9
+ * text from each item. The keyFn is invoked once per item per call; the
10
+ * caller is responsible for ensuring the result is stable (e.g., don't
11
+ * concatenate mutable fields).
12
+ */
13
+ export declare function fuzzyMatchWithKey<T>(query: string, items: T[], options: FuzzyMatchOptions & {
14
+ keyFn: (item: T) => string;
15
+ }): FuzzyMatchResult<T>[];
@@ -0,0 +1,56 @@
1
+ import { Fzf } from 'fzf';
2
+ /**
3
+ * Default limit for fuzzy-match. Aligned with the spec's "--limit default 6".
4
+ */
5
+ const DEFAULT_LIMIT = 6;
6
+ /**
7
+ * String-overload: when `items` is an array of strings, the searchable text
8
+ * is the string itself. No keyFn is required.
9
+ */
10
+ export function fuzzyMatch(query, items, options = {}) {
11
+ return fuzzyMatchWithKey(query, items, { ...options, keyFn: (item) => item });
12
+ }
13
+ /**
14
+ * Object-overload: caller provides a `keyFn` that extracts the searchable
15
+ * text from each item. The keyFn is invoked once per item per call; the
16
+ * caller is responsible for ensuring the result is stable (e.g., don't
17
+ * concatenate mutable fields).
18
+ */
19
+ export function fuzzyMatchWithKey(query, items, options) {
20
+ const { keyFn } = options;
21
+ const limit = options.limit ?? DEFAULT_LIMIT;
22
+ if (items.length === 0)
23
+ return [];
24
+ // Empty query: surface all items (capped at limit) with neutral score and
25
+ // empty positions. Useful for "list" or "preview" use cases where the
26
+ // caller wants a deterministic top-N without a query.
27
+ if (query === '') {
28
+ return items.slice(0, limit).map((item) => ({ item, score: 0, positions: [] }));
29
+ }
30
+ const fzf = new Fzf(items, {
31
+ selector: keyFn,
32
+ limit,
33
+ // Per spec: default is case-insensitive (NOT fzf's smart-case).
34
+ // The user explicitly opts into case-sensitive via caseSensitive:true.
35
+ casing: options.caseSensitive === true ? 'case-sensitive' : 'case-insensitive',
36
+ // normalize:true (default) strips diacritics; fzf returns more matches
37
+ // for non-ASCII text this way, which is what we want for
38
+ // bilingual (zh-CN + en) memory entries.
39
+ });
40
+ const raw = fzf.find(query);
41
+ if (raw.length === 0)
42
+ return [];
43
+ // fzf-for-js score is "higher = better". Normalize so the top of the
44
+ // current batch is exactly 1.0 and others are in [0, 1].
45
+ // When the top score is 0 (degenerate — exact-character-only query that
46
+ // still matched somehow), fall back to 1.0 to avoid divide-by-zero.
47
+ const topScore = raw[0]?.score ?? 1;
48
+ const denom = topScore > 0 ? topScore : 1;
49
+ return raw.slice(0, limit).map((entry) => {
50
+ const score = topScore > 0 ? Number((entry.score / denom).toFixed(4)) : 1;
51
+ // positions is a Set<number> in fzf-for-js; convert to a sorted array
52
+ // so the JSON envelope is stable and human-readable.
53
+ const positions = [...entry.positions].sort((a, b) => a - b);
54
+ return { item: entry.item, score, positions };
55
+ });
56
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Options for the generic fuzzy-match kernel.
3
+ */
4
+ export interface FuzzyMatchOptions {
5
+ /** Maximum number of matches to return. Default 6. */
6
+ limit?: number;
7
+ /** When true, matching is case-sensitive. Default false (smart-case). */
8
+ caseSensitive?: boolean;
9
+ }
10
+ /**
11
+ * A single fuzzy-match hit. `item` is the original entry; `score` is
12
+ * normalized to [0, 1] with the top of the current batch at 1.0;
13
+ * `positions` is the set of char indices in the searchable text that
14
+ * contributed to the match.
15
+ */
16
+ export interface FuzzyMatchResult<T> {
17
+ item: T;
18
+ score: number;
19
+ positions: number[];
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Hermes IDE adapter (Slice #0.7) — peaks-cli 的第七个内置 IDE 适配器。
3
+ *
4
+ * 不可消除的 per-IDE 字段(slice #0.7 填表):
5
+ * - settings.dirName = '.hermes' : Hermes 项目根下的配置目录
6
+ * - settings.settingsFileName = 'settings.json'
7
+ * - envVar = 'HERMES_PROJECT_DIR' : Hermes 注入的 env 变量(用于 ${...} 占位)
8
+ * - hookEvent = 'PreToolUse' : 现代 IDE 通用约定(UNVERIFIED — 待真实 Hermes 安装验证)
9
+ * - toolMatcher = 'Bash' : 同上(UNVERIFIED)
10
+ *
11
+ * Slice #0.7 状态:
12
+ * - Slim adapter shape 跟 trae-adapter.ts / claude-code-adapter.ts 同型,
13
+ * 验证 slice #1 抽出的形状在第 7 个 IDE 上仍然可以"填表"接入。
14
+ * - 4 UNVERIFIED fields (hookEvent, toolMatcher, envVar, dirName) 在
15
+ * Hermes 真实安装可用前均为占位值;待真实 Hermes fixture 验证后
16
+ * 跟 trae-adapter 一样会被 VERIFIED 标记。
17
+ * - 见 PRD §0.7 + memory trae-adapter-values-verified-against-1x.md
18
+ * 了解 UNVERIFIED → VERIFIED 的迁移路径。
19
+ */
20
+ import type { IdeAdapter } from '../ide-types.js';
21
+ export declare const HERMES_ADAPTER: IdeAdapter;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Hermes IDE adapter (Slice #0.7) — peaks-cli 的第七个内置 IDE 适配器。
3
+ *
4
+ * 不可消除的 per-IDE 字段(slice #0.7 填表):
5
+ * - settings.dirName = '.hermes' : Hermes 项目根下的配置目录
6
+ * - settings.settingsFileName = 'settings.json'
7
+ * - envVar = 'HERMES_PROJECT_DIR' : Hermes 注入的 env 变量(用于 ${...} 占位)
8
+ * - hookEvent = 'PreToolUse' : 现代 IDE 通用约定(UNVERIFIED — 待真实 Hermes 安装验证)
9
+ * - toolMatcher = 'Bash' : 同上(UNVERIFIED)
10
+ *
11
+ * Slice #0.7 状态:
12
+ * - Slim adapter shape 跟 trae-adapter.ts / claude-code-adapter.ts 同型,
13
+ * 验证 slice #1 抽出的形状在第 7 个 IDE 上仍然可以"填表"接入。
14
+ * - 4 UNVERIFIED fields (hookEvent, toolMatcher, envVar, dirName) 在
15
+ * Hermes 真实安装可用前均为占位值;待真实 Hermes fixture 验证后
16
+ * 跟 trae-adapter 一样会被 VERIFIED 标记。
17
+ * - 见 PRD §0.7 + memory trae-adapter-values-verified-against-1x.md
18
+ * 了解 UNVERIFIED → VERIFIED 的迁移路径。
19
+ */
20
+ import { homedir } from 'node:os';
21
+ import { join, resolve } from 'node:path';
22
+ import { traeSubAgentDispatcher } from '../../dispatch/sub-agent-dispatcher.js';
23
+ export const HERMES_ADAPTER = {
24
+ id: 'hermes',
25
+ displayName: 'Hermes',
26
+ settings: {
27
+ dirName: '.hermes', // UNVERIFIED — placeholder; pending real Hermes 1.x fixture
28
+ settingsFileName: 'settings.json',
29
+ resolveSettingsFile: (scope, projectRoot) => {
30
+ const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
31
+ return join(root, '.hermes', 'settings.json');
32
+ },
33
+ supportsScope: (scope) => scope === 'project' || scope === 'global'
34
+ },
35
+ envVar: 'HERMES_PROJECT_DIR', // UNVERIFIED
36
+ hookEvent: 'PreToolUse', // UNVERIFIED
37
+ toolMatcher: 'Bash', // UNVERIFIED
38
+ // Slice #0.7: Hermes sub-agent dispatcher UNVERIFIED — pending real Hermes
39
+ // dogfood. Reusing the Trae dispatcher as a uniform placeholder (byte-stable
40
+ // shape per slice #008; same rationale as Trae adapter).
41
+ subAgentDispatcher: traeSubAgentDispatcher,
42
+ // Slice #010 G9: Hermes PreToolUse is the assumed hook path. UNVERIFIED.
43
+ promptSizeAware: true,
44
+ installHints: [
45
+ 'Restart Hermes (or reload the workspace) so the PreToolUse hooks take effect.'
46
+ ],
47
+ capabilities: {
48
+ gateEnforce: true,
49
+ statusline: true
50
+ }
51
+ };
@@ -0,0 +1,14 @@
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 type { IdeAdapter } from '../ide-types.js';
14
+ export declare const OPENCLAW_ADAPTER: IdeAdapter;
@@ -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;