peaks-cli 1.3.2 → 1.3.4

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 (115) hide show
  1. package/README.md +6 -2
  2. package/dist/src/cli/commands/core-artifact-commands.js +6 -3
  3. package/dist/src/cli/commands/gate-commands.js +28 -19
  4. package/dist/src/cli/commands/hook-handle.d.ts +17 -0
  5. package/dist/src/cli/commands/hook-handle.js +111 -0
  6. package/dist/src/cli/commands/hooks-commands.js +72 -21
  7. package/dist/src/cli/commands/progress-commands.js +9 -2
  8. package/dist/src/cli/commands/progress-start-spawn.js +30 -4
  9. package/dist/src/cli/commands/project-commands.js +8 -4
  10. package/dist/src/cli/commands/statusline-commands.js +75 -17
  11. package/dist/src/cli/commands/sub-agent-commands.d.ts +5 -0
  12. package/dist/src/cli/commands/sub-agent-commands.js +488 -0
  13. package/dist/src/cli/commands/sub-agent-dispatch-guard.d.ts +55 -0
  14. package/dist/src/cli/commands/sub-agent-dispatch-guard.js +57 -0
  15. package/dist/src/cli/commands/workflow-commands.js +2 -1
  16. package/dist/src/cli/commands/workspace-commands.js +3 -0
  17. package/dist/src/cli/program.js +9 -0
  18. package/dist/src/hooks/pre-tool-use-sub-agent.d.ts +28 -0
  19. package/dist/src/hooks/pre-tool-use-sub-agent.js +105 -0
  20. package/dist/src/services/config/config-types.d.ts +1 -1
  21. package/dist/src/services/context/artifact-meta.d.ts +72 -0
  22. package/dist/src/services/context/artifact-meta.js +105 -0
  23. package/dist/src/services/context/context-guard.d.ts +49 -0
  24. package/dist/src/services/context/context-guard.js +91 -0
  25. package/dist/src/services/context/dispatch-context-guard.d.ts +27 -0
  26. package/dist/src/services/context/dispatch-context-guard.js +192 -0
  27. package/dist/src/services/context/headroom-client.d.ts +34 -0
  28. package/dist/src/services/context/headroom-client.js +117 -0
  29. package/dist/src/services/context/shared-channel.d.ts +92 -0
  30. package/dist/src/services/context/shared-channel.js +285 -0
  31. package/dist/src/services/context/threshold.d.ts +35 -0
  32. package/dist/src/services/context/threshold.js +76 -0
  33. package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
  34. package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
  35. package/dist/src/services/dispatch/batch-counter.d.ts +27 -0
  36. package/dist/src/services/dispatch/batch-counter.js +85 -0
  37. package/dist/src/services/dispatch/dispatch-record-writer.d.ts +93 -0
  38. package/dist/src/services/dispatch/dispatch-record-writer.js +261 -0
  39. package/dist/src/services/dispatch/heartbeat-truncator.d.ts +26 -0
  40. package/dist/src/services/dispatch/heartbeat-truncator.js +13 -0
  41. package/dist/src/services/dispatch/leak-detector.d.ts +11 -0
  42. package/dist/src/services/dispatch/leak-detector.js +72 -0
  43. package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +127 -0
  44. package/dist/src/services/dispatch/sub-agent-dispatcher.js +98 -0
  45. package/dist/src/services/ide/adapters/claude-code-adapter.d.ts +18 -0
  46. package/dist/src/services/ide/adapters/claude-code-adapter.js +80 -0
  47. package/dist/src/services/ide/adapters/trae-adapter.d.ts +42 -0
  48. package/dist/src/services/ide/adapters/trae-adapter.js +98 -0
  49. package/dist/src/services/ide/hook-protocol.d.ts +47 -0
  50. package/dist/src/services/ide/hook-protocol.js +74 -0
  51. package/dist/src/services/ide/hook-translator.d.ts +72 -0
  52. package/dist/src/services/ide/hook-translator.js +128 -0
  53. package/dist/src/services/ide/ide-detector.d.ts +10 -0
  54. package/dist/src/services/ide/ide-detector.js +19 -0
  55. package/dist/src/services/ide/ide-registry.d.ts +14 -0
  56. package/dist/src/services/ide/ide-registry.js +45 -0
  57. package/dist/src/services/ide/ide-types.d.ts +180 -0
  58. package/dist/src/services/ide/ide-types.js +2 -0
  59. package/dist/src/services/ide/resource-profile.d.ts +52 -0
  60. package/dist/src/services/ide/resource-profile.js +33 -0
  61. package/dist/src/services/ide/shared/atomic-json.d.ts +15 -0
  62. package/dist/src/services/ide/shared/atomic-json.js +58 -0
  63. package/dist/src/services/ide/shared/safe-path.d.ts +11 -0
  64. package/dist/src/services/ide/shared/safe-path.js +29 -0
  65. package/dist/src/services/memory/project-context-service.js +2 -1
  66. package/dist/src/services/memory/project-memory-service.js +4 -3
  67. package/dist/src/services/perf/perf-baseline-service.js +2 -1
  68. package/dist/src/services/progress/progress-service.d.ts +1 -1
  69. package/dist/src/services/progress/progress-service.js +18 -14
  70. package/dist/src/services/security/safe-settings-path.d.ts +12 -0
  71. package/dist/src/services/security/safe-settings-path.js +104 -0
  72. package/dist/src/services/session/getSessionDir.d.ts +1 -0
  73. package/dist/src/services/session/getSessionDir.js +27 -0
  74. package/dist/src/services/session/index.d.ts +1 -0
  75. package/dist/src/services/session/index.js +1 -0
  76. package/dist/src/services/signal/cancel-handler.d.ts +14 -0
  77. package/dist/src/services/signal/cancel-handler.js +76 -0
  78. package/dist/src/services/skill/resume-detector.d.ts +54 -0
  79. package/dist/src/services/skill/resume-detector.js +334 -0
  80. package/dist/src/services/skill/skill-scheduler.d.ts +40 -0
  81. package/dist/src/services/skill/skill-scheduler.js +53 -0
  82. package/dist/src/services/skills/hooks-settings-service.d.ts +47 -29
  83. package/dist/src/services/skills/hooks-settings-service.js +190 -144
  84. package/dist/src/services/skills/statusline-settings-service.d.ts +33 -6
  85. package/dist/src/services/skills/statusline-settings-service.js +31 -34
  86. package/dist/src/services/slice/slice-archive-service.d.ts +20 -0
  87. package/dist/src/services/slice/slice-archive-service.js +111 -0
  88. package/dist/src/services/solo/batch-heartbeat-poller.d.ts +51 -0
  89. package/dist/src/services/solo/batch-heartbeat-poller.js +88 -0
  90. package/dist/src/services/solo/status-line-renderer.d.ts +34 -0
  91. package/dist/src/services/solo/status-line-renderer.js +55 -0
  92. package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
  93. package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
  94. package/dist/src/services/standards/project-standards-service.d.ts +1 -2
  95. package/dist/src/services/workspace/reconcile-service.d.ts +36 -0
  96. package/dist/src/services/workspace/reconcile-service.js +107 -6
  97. package/dist/src/services/workspace/reconcile-types.d.ts +12 -0
  98. package/dist/src/shared/version.d.ts +1 -1
  99. package/dist/src/shared/version.js +1 -1
  100. package/package.json +2 -1
  101. package/scripts/install-skills.mjs +112 -2
  102. package/skills/peaks-ide/SKILL.md +159 -0
  103. package/skills/peaks-ide/references/audit-log-helper.md +52 -0
  104. package/skills/peaks-qa/SKILL.md +153 -55
  105. package/skills/peaks-qa/references/qa-fanout-contract.md +150 -0
  106. package/skills/peaks-rd/SKILL.md +134 -62
  107. package/skills/peaks-solo/SKILL.md +124 -37
  108. package/skills/peaks-solo/references/browser-workflow.md +22 -20
  109. package/skills/peaks-solo/references/context-governance.md +144 -0
  110. package/skills/peaks-solo/references/headroom-integration.md +107 -0
  111. package/skills/peaks-solo/references/runbook.md +3 -3
  112. package/skills/peaks-solo/references/sub-agent-dispatch.md +261 -0
  113. package/skills/peaks-solo/references/swarm-dispatch-contract.md +3 -37
  114. package/skills/peaks-txt/SKILL.md +17 -0
  115. package/skills/peaks-ui/SKILL.md +45 -10
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Slice #009 — SubAgentDispatcher abstraction.
3
+ *
4
+ * Per-IDE contract: given a sub-agent role + prompt + request/session ids,
5
+ * return a tool-call descriptor that the calling LLM should execute in
6
+ * its native environment. The CLI is IDE-agnostic; per-IDE tool names
7
+ * (Claude Code's `Task`, Trae's UNVERIFIED placeholder) are encapsulated
8
+ * here, never leaked to SKILL.md.
9
+ *
10
+ * Why this exists:
11
+ * - Prior SKILL.md hardcoded `Task(subagent_type="general-purpose", ...)`
12
+ * which made peaks-cli depend on Claude Code's specific sub-agent
13
+ * tool name. Adding a new IDE (Trae, future Cursor, etc.) required
14
+ * editing every SKILL.md that mentioned sub-agent dispatch.
15
+ * - This file (plus the per-IDE adapter wiring) collapses all
16
+ * per-IDE sub-agent specifics to a single `SubAgentDispatcher`
17
+ * instance per adapter. SKILL.md now only references
18
+ * `peaks sub-agent dispatch <role>`, and the IDE-private tool
19
+ * name flows through the returned `data.toolCall` at runtime.
20
+ *
21
+ * Cross-reference: PRD #002 G1 (AC-1..AC-5); RD tech-doc-002 §2.
22
+ */
23
+ /**
24
+ * Claude Code dispatcher. Real, byte-level implementation.
25
+ *
26
+ * - `supportsRole`: any non-empty string (Claude Code's
27
+ * `general-purpose` sub-agent accepts any prompt).
28
+ * - `buildToolCall`: returns `{name: 'Task', args: {subagent_type,
29
+ * description, prompt}}` — the exact shape the `Task` tool
30
+ * in Claude Code expects.
31
+ */
32
+ export const claudeCodeSubAgentDispatcher = {
33
+ label: 'claude-code',
34
+ supportsRole: (role) => role.length > 0,
35
+ buildToolCall: ({ role, prompt, requestId }) => ({
36
+ name: 'Task',
37
+ args: {
38
+ subagent_type: 'general-purpose',
39
+ description: `${role} for rid=${requestId}`,
40
+ prompt,
41
+ },
42
+ }),
43
+ };
44
+ /**
45
+ * Trae dispatcher. UNVERIFIED — Trae sub-agent tool name TBD on real
46
+ * dogfood. Byte-level identical to claude-code by design so:
47
+ * 1. The slice #008 `subAgentToolMatcher: 'Task'` install entry
48
+ * stays byte-stable (the generated `.trae/settings.json` does
49
+ * not change).
50
+ * 2. The dispatcher's return shape is uniform across both
51
+ * adapters — a single byte-equality test can verify the
52
+ * placeholder contract.
53
+ *
54
+ * When real Trae dogfood lands, replace the body of `buildToolCall`
55
+ * with Trae's actual sub-agent tool name + args shape. The interface
56
+ * stays the same; only the per-IDE wiring breaks (intentionally).
57
+ */
58
+ export const traeSubAgentDispatcher = {
59
+ // UNVERIFIED — see file header
60
+ label: 'trae',
61
+ supportsRole: (role) => role.length > 0,
62
+ buildToolCall: ({ role, prompt, requestId }) => ({
63
+ name: 'Task',
64
+ args: {
65
+ subagent_type: 'general-purpose',
66
+ description: `${role} for rid=${requestId}`,
67
+ prompt,
68
+ },
69
+ }),
70
+ };
71
+ /**
72
+ * Null dispatcher for IDEs that cannot dispatch sub-agents at all
73
+ * (e.g. a CLI-only IDE that has no LLM tool surface). Used as the
74
+ * fallback by future unsupported-IDE adapters. The CLI returns
75
+ * `{ok: false, code: "IDE_NOT_SUPPORTED"}` when the dispatcher's
76
+ * `supportsRole` returns false.
77
+ */
78
+ export const nullSubAgentDispatcher = {
79
+ label: 'null',
80
+ supportsRole: () => false,
81
+ buildToolCall: ({ role }) => {
82
+ throw new SubAgentNotSupportedError(role);
83
+ },
84
+ };
85
+ /**
86
+ * Thrown by `nullSubAgentDispatcher.buildToolCall` and any future
87
+ * dispatcher that does not support a given role. The CLI catches
88
+ * this and returns the IDE_NOT_SUPPORTED error envelope.
89
+ */
90
+ export class SubAgentNotSupportedError extends Error {
91
+ role;
92
+ code = 'IDE_NOT_SUPPORTED';
93
+ constructor(role) {
94
+ super(`Sub-agent dispatch is not supported for role: ${role}`);
95
+ this.role = role;
96
+ this.name = 'SubAgentNotSupportedError';
97
+ }
98
+ }
@@ -0,0 +1,18 @@
1
+ import type { IdeAdapter } from '../ide-types.js';
2
+ /**
3
+ * Claude Code adapter —— peaks-cli 的"起源 IDE"。
4
+ *
5
+ * 该 adapter 从原 `src/services/skills/hooks-settings-service.ts` 提取,保持
6
+ * 字节级兼容:用户在 Claude Code 环境下跑 `peaks hooks install` 产出的
7
+ * `.claude/settings.json` 与 refactor 前逐字节相同。
8
+ *
9
+ * 字段解释(见 PRD AC-1):
10
+ * - dirName = '.claude' : Claude Code 项目根下的 settings 目录
11
+ * - settingsFileName = 'settings.json'
12
+ * - envVar = 'CLAUDE_PROJECT_DIR' : Claude Code 注入的 env 变量,用于 ${...} 占位
13
+ * - hookEvent = 'PreToolUse' : Claude Code hook 数组 key
14
+ * - toolMatcher = 'Bash' | 'Task' : PreToolUse 数组元素的 matcher 字段
15
+ *
16
+ * 不可消除的 per-IDE 字段(见 tech-doc.md §1.3)。
17
+ */
18
+ export declare const CLAUDE_CODE_ADAPTER: IdeAdapter;
@@ -0,0 +1,80 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, resolve } from 'node:path';
3
+ import { claudeCodeSubAgentDispatcher } from '../../dispatch/sub-agent-dispatcher.js';
4
+ /**
5
+ * Claude Code adapter —— peaks-cli 的"起源 IDE"。
6
+ *
7
+ * 该 adapter 从原 `src/services/skills/hooks-settings-service.ts` 提取,保持
8
+ * 字节级兼容:用户在 Claude Code 环境下跑 `peaks hooks install` 产出的
9
+ * `.claude/settings.json` 与 refactor 前逐字节相同。
10
+ *
11
+ * 字段解释(见 PRD AC-1):
12
+ * - dirName = '.claude' : Claude Code 项目根下的 settings 目录
13
+ * - settingsFileName = 'settings.json'
14
+ * - envVar = 'CLAUDE_PROJECT_DIR' : Claude Code 注入的 env 变量,用于 ${...} 占位
15
+ * - hookEvent = 'PreToolUse' : Claude Code hook 数组 key
16
+ * - toolMatcher = 'Bash' | 'Task' : PreToolUse 数组元素的 matcher 字段
17
+ *
18
+ * 不可消除的 per-IDE 字段(见 tech-doc.md §1.3)。
19
+ */
20
+ export const CLAUDE_CODE_ADAPTER = {
21
+ id: 'claude-code',
22
+ displayName: 'Claude Code',
23
+ settings: {
24
+ dirName: '.claude',
25
+ settingsFileName: 'settings.json',
26
+ resolveSettingsFile: (scope, projectRoot) => {
27
+ const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
28
+ return join(root, '.claude', 'settings.json');
29
+ },
30
+ supportsScope: () => true,
31
+ },
32
+ envVar: 'CLAUDE_PROJECT_DIR',
33
+ hookEvent: 'PreToolUse',
34
+ toolMatcher: 'Bash',
35
+ subAgentToolMatcher: 'Task',
36
+ // Slice #009: Claude Code uses the `Task` tool for sub-agent dispatch.
37
+ // The CLI calls `claudeCodeSubAgentDispatcher.buildToolCall` to construct
38
+ // the exact args shape the `Task` tool expects.
39
+ subAgentDispatcher: claudeCodeSubAgentDispatcher,
40
+ // Slice #010 G9: Claude Code supports the PreToolUse hook event in a
41
+ // form that can wrap `peaks sub-agent-dispatch-guard` as a sub-command.
42
+ // Opt in to the G9 hook install.
43
+ promptSizeAware: true,
44
+ installHints: [
45
+ 'Restart Claude Code (or reload the window) so the PreToolUse hooks take effect.'
46
+ ],
47
+ capabilities: {
48
+ gateEnforce: true,
49
+ progressStart: true,
50
+ statusline: true,
51
+ mcpInstall: true,
52
+ },
53
+ // Slice #011: standards profile. Claude Code reads its constitution at
54
+ // CLAUDE.md + module-level rules under .claude/rules/**. The values mirror
55
+ // the hardcoded paths in `src/services/standards/project-standards-service.ts`
56
+ // (line 147 = '.claude', line 417/421 = 'CLAUDE.md' + '.claude/rules/...')
57
+ // and the postinstall target in `scripts/install-skills.mjs` (line 427 =
58
+ // '~/.claude/skills'). Filling the profile here makes the dispatch layer
59
+ // route to the SAME paths, so byte-stability on `peaks standards init` for
60
+ // Claude Code projects is preserved.
61
+ standardsProfile: {
62
+ rootFile: 'CLAUDE.md',
63
+ rulesDir: '.claude/rules',
64
+ rulesFileGlob: '**/*.md',
65
+ autoLoaded: true,
66
+ format: 'markdown',
67
+ migrationHint: 'Standards live at CLAUDE.md + .claude/rules/** for Claude Code.',
68
+ },
69
+ // Slice #011: skill install profile. The postinstall script symlinks
70
+ // bundled skills to `~/.claude/skills` and writes output-styles to
71
+ // `~/.claude/output-styles`, matching the existing hardcoded
72
+ // install-skills.mjs lines 427 + 488. The env-var back-compat name
73
+ // matches the legacy `PEAKS_CLAUDE_SKILLS_DIR` / `PEAKS_CLAUDE_OUTPUT_STYLES_DIR`.
74
+ skillInstall: {
75
+ skillsDir: join(homedir(), '.claude', 'skills'),
76
+ outputStylesDir: join(homedir(), '.claude', 'output-styles'),
77
+ installStrategy: 'symlink',
78
+ envVarOverride: 'PEAKS_CLAUDE_SKILLS_DIR',
79
+ },
80
+ };
@@ -0,0 +1,42 @@
1
+ import type { IdeAdapter } from '../ide-types.js';
2
+ /**
3
+ * Trae IDE adapter —— peaks-cli 的第二个内置 IDE 适配器。
4
+ *
5
+ * 不可消除的 per-IDE 字段(slice #1 锁定):
6
+ * - settings.dirName = '.trae' : Trae 项目根下的配置目录
7
+ * - settings.settingsFileName = 'settings.json' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
8
+ * - envVar = 'TRAE_PROJECT_DIR' : Trae 注入的 env 变量(用于 ${...} 占位)
9
+ * - hookEvent = 'beforeToolCall' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
10
+ * - toolMatcher = 'terminal' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
11
+ *
12
+ * Slice #1 的 slim `IdeAdapter` shape 在 slice #1 RD 中被锁为"填表"模式。
13
+ * 本文件是 slice #2 第一个真实客户,验证 slice #1 抽出的形状真的可以
14
+ * 简单复制粘贴就接入新 IDE。
15
+ *
16
+ * 与 slice #1 claude-code-adapter.ts 的区别(故意):
17
+ * - Trae 的 hookEvent 名是 `beforeToolCall` 而不是 `PreToolUse`(VERIFIED)
18
+ * - Trae 的 toolMatcher 是 `terminal` 而不是 `Bash`(VERIFIED)
19
+ * - Trae 的 settings 路径是 `.trae/settings.json`(同 Claude 风格,只是目录名不同;VERIFIED)
20
+ * - Trae 的 envVar 是 `TRAE_PROJECT_DIR`
21
+ * - installHints 提示用户"重启 Trae"(同 Claude 风格)
22
+ *
23
+ * Slice #009 验证结论(2026-06-07):
24
+ * - 4 UNVERIFIED fields are all VERIFIED-AS-IS against the Trae 1.x fixture
25
+ * (tests/fixtures/trae/trae-1x-payload.json) AND the live install
26
+ * dispatch path exercised by `peaks hooks install` / `peaks statusline
27
+ * install` / `peaks hook handle`. The fixture mimics a real Trae 1.x
28
+ * install's payload shape; the dispatch path is the byte-level same path
29
+ * a real Trae install would trigger. Caveat: a follow-up slice should
30
+ * re-run the same 5+ dogfood paths on a real Trae 1.x install once one
31
+ * is available, to confirm the 1.x assumption is correct (see PRD R-1
32
+ * + the new memory at
33
+ * .peaks/memory/trae-adapter-values-verified-against-1x.md).
34
+ * - See .peaks/_runtime/2026-06-06-session-5b1095/qa/dogfood-trae-1x-2026-06-07.md
35
+ * for the full resolution table.
36
+ *
37
+ * Slice #3 refactor: the `peaks hooks install` command now dispatches on the
38
+ * IDE adapter (auto-detect from env / cwd, override with `--ide trae`). When
39
+ * a Trae install is run, the resulting `<root>/.trae/settings.json` will use
40
+ * the `beforeToolCall` event key and the `terminal` matcher from this adapter.
41
+ */
42
+ export declare const TRAE_ADAPTER: IdeAdapter;
@@ -0,0 +1,98 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, resolve } from 'node:path';
3
+ import { traeSubAgentDispatcher } from '../../dispatch/sub-agent-dispatcher.js';
4
+ /**
5
+ * Trae IDE adapter —— peaks-cli 的第二个内置 IDE 适配器。
6
+ *
7
+ * 不可消除的 per-IDE 字段(slice #1 锁定):
8
+ * - settings.dirName = '.trae' : Trae 项目根下的配置目录
9
+ * - settings.settingsFileName = 'settings.json' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
10
+ * - envVar = 'TRAE_PROJECT_DIR' : Trae 注入的 env 变量(用于 ${...} 占位)
11
+ * - hookEvent = 'beforeToolCall' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
12
+ * - toolMatcher = 'terminal' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
13
+ *
14
+ * Slice #1 的 slim `IdeAdapter` shape 在 slice #1 RD 中被锁为"填表"模式。
15
+ * 本文件是 slice #2 第一个真实客户,验证 slice #1 抽出的形状真的可以
16
+ * 简单复制粘贴就接入新 IDE。
17
+ *
18
+ * 与 slice #1 claude-code-adapter.ts 的区别(故意):
19
+ * - Trae 的 hookEvent 名是 `beforeToolCall` 而不是 `PreToolUse`(VERIFIED)
20
+ * - Trae 的 toolMatcher 是 `terminal` 而不是 `Bash`(VERIFIED)
21
+ * - Trae 的 settings 路径是 `.trae/settings.json`(同 Claude 风格,只是目录名不同;VERIFIED)
22
+ * - Trae 的 envVar 是 `TRAE_PROJECT_DIR`
23
+ * - installHints 提示用户"重启 Trae"(同 Claude 风格)
24
+ *
25
+ * Slice #009 验证结论(2026-06-07):
26
+ * - 4 UNVERIFIED fields are all VERIFIED-AS-IS against the Trae 1.x fixture
27
+ * (tests/fixtures/trae/trae-1x-payload.json) AND the live install
28
+ * dispatch path exercised by `peaks hooks install` / `peaks statusline
29
+ * install` / `peaks hook handle`. The fixture mimics a real Trae 1.x
30
+ * install's payload shape; the dispatch path is the byte-level same path
31
+ * a real Trae install would trigger. Caveat: a follow-up slice should
32
+ * re-run the same 5+ dogfood paths on a real Trae 1.x install once one
33
+ * is available, to confirm the 1.x assumption is correct (see PRD R-1
34
+ * + the new memory at
35
+ * .peaks/memory/trae-adapter-values-verified-against-1x.md).
36
+ * - See .peaks/_runtime/2026-06-06-session-5b1095/qa/dogfood-trae-1x-2026-06-07.md
37
+ * for the full resolution table.
38
+ *
39
+ * Slice #3 refactor: the `peaks hooks install` command now dispatches on the
40
+ * IDE adapter (auto-detect from env / cwd, override with `--ide trae`). When
41
+ * a Trae install is run, the resulting `<root>/.trae/settings.json` will use
42
+ * the `beforeToolCall` event key and the `terminal` matcher from this adapter.
43
+ */
44
+ export const TRAE_ADAPTER = {
45
+ id: 'trae',
46
+ displayName: 'Trae',
47
+ settings: {
48
+ dirName: '.trae',
49
+ settingsFileName: 'settings.json', // VERIFIED against Trae 1.x fixture — slice 009-009-2026-06-07-trae-dogfood (2026-06-07)
50
+ resolveSettingsFile: (scope, projectRoot) => {
51
+ const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
52
+ return join(root, '.trae', 'settings.json');
53
+ },
54
+ supportsScope: (scope) => scope === 'project' || scope === 'global'
55
+ },
56
+ envVar: 'TRAE_PROJECT_DIR',
57
+ hookEvent: 'beforeToolCall', // VERIFIED against Trae 1.x fixture — slice 009-009-2026-06-07-trae-dogfood (2026-06-07); fixture at tests/fixtures/trae/trae-1x-payload.json
58
+ toolMatcher: 'terminal', // VERIFIED against Trae 1.x fixture — slice 009-009-2026-06-07-trae-dogfood (2026-06-07); fixture pins `parameters.tool: 'terminal'`
59
+ subAgentToolMatcher: 'Task', // UNVERIFIED — Trae's sub-agent tool name is unknown; matches the prior hardcoded 'Task' literal so byte-level install output is unchanged. Will be dogfooded when a real Trae 1.x install dispatches a sub-agent.
60
+ // Slice #009: Trae's sub-agent dispatcher is UNVERIFIED — Trae sub-agent
61
+ // tool name TBD on real dogfood; byte-level identical to claude-code by
62
+ // design so the slice #008 `subAgentToolMatcher: 'Task'` install entry
63
+ // stays byte-stable. Awaiting real Trae 1.x dogfood to confirm/replace.
64
+ subAgentDispatcher: traeSubAgentDispatcher,
65
+ // Slice #010 G9: Trae supports `beforeToolCall` which can wrap
66
+ // `peaks sub-agent-dispatch-guard`. Opt in (matches the byte-stable
67
+ // slice #008 install entry shape).
68
+ promptSizeAware: true,
69
+ installHints: [
70
+ 'Restart Trae (or reload the workspace) so the beforeToolCall hooks take effect.'
71
+ ],
72
+ capabilities: {
73
+ gateEnforce: true,
74
+ progressStart: true,
75
+ statusline: true,
76
+ // Slice #007-007-2026-06-07-mcp-decouple: mcpInstall is LOAD-BEARING.
77
+ // The 4 MCP capabilities (playwright, chrome-devtools, figma, context7)
78
+ // are installed via `peaks mcp plan/apply` which writes to the global
79
+ // `~/.claude/settings.json` file. Trae 1.x's MCP integration is
80
+ // UNVERIFIED (the Trae fixture did not dogfood the MCP install path),
81
+ // so the 6 SKILL.md files must surface a Trae-specific path (manual
82
+ // install + manual tool invocation) rather than promising `peaks mcp
83
+ // apply` will work on Trae. Skill bodies consume this flag through
84
+ // the IDE adapter's `capabilities.mcpInstall`; setting it to true
85
+ // without a real Trae MCP install dogfood would be a regression.
86
+ // Cross-reference: .peaks/memory/trae-adapter-sets-mcpinstall-false-trae-mcp-integration-is-unverified.md
87
+ // and the 4 per-capability memos under .peaks/memory/mcp-decouple-*.md.
88
+ mcpInstall: false
89
+ }
90
+ // Standards: UNVERIFIED — see slice #012+ (Trae real-install dogfood for
91
+ // the `standardsProfile` and `skillInstall` fields). The slice #011
92
+ // framework lands; per-IDE values for Trae are a follow-up gated on
93
+ // the user's real Trae 1.x install. Until then, `peaks standards init`
94
+ // on a Trae-detected project falls back to the Claude Code path
95
+ // (CLAUDE.md + .claude/rules/**) with a stderr warning, and the
96
+ // postinstall script writes skills + output-styles to the legacy
97
+ // `~/.claude/{skills,output-styles}` paths with a stderr warning.
98
+ };
@@ -0,0 +1,47 @@
1
+ import { PEAKS_HOOK_SCHEMA, type IdeId, type PeaksCanonicalHook, type PeaksDecisionTransport } from './ide-types.js';
2
+ export { PEAKS_HOOK_SCHEMA };
3
+ export type { PeaksCanonicalHook, PeaksDecisionTransport };
4
+ /**
5
+ * Compute the deny decision shape for Claude Code (the only adapter registered
6
+ * in slice #1). The output is a JSON object that, when written to stdout, makes
7
+ * the Claude Code permission system block the tool call BEFORE the user's
8
+ * permission prompt — un-bypassable, even under --dangerously-skip-permissions.
9
+ */
10
+ export declare const CLAUDE_CODE_DENY_SHAPE: Record<string, unknown>;
11
+ export declare const CLAUDE_CODE_DENY_TRANSPORT: PeaksDecisionTransport;
12
+ /**
13
+ * Compute the deny decision shape for Trae (Cursor-style sibling IDE).
14
+ * VERIFIED against Trae 1.x fixture — slice 009-009-2026-06-07-trae-dogfood (2026-06-07).
15
+ * Shape is the standard Cursor/Claude sibling envelope: `hookSpecificOutput`
16
+ * with `hookEventName`, `permissionDecision`, `permissionDecisionReason`.
17
+ * The hookEventName is `'beforeToolCall'` for Trae vs `'PreToolUse'` for
18
+ * Claude Code — the only field that differs between the two siblings.
19
+ * Fixture: tests/fixtures/trae/trae-1x-payload.json. Constant NAME preserved
20
+ * (per slice 009 PRD R-3); only the shape was already correct from slice #3.
21
+ */
22
+ export declare const TRAE_DENY_SHAPE: Record<string, unknown>;
23
+ export declare const TRAE_DENY_TRANSPORT: PeaksDecisionTransport;
24
+ /**
25
+ * Format a decision response for a given IDE. Slice #1 handles Claude Code;
26
+ * slice #3 added Trae (1.x-assumption shape — see TRAE_DENY_SHAPE doc).
27
+ * Future slices will add exit-code / both variants for IDEs that don't read
28
+ * stdout.
29
+ */
30
+ export declare function formatDecisionResponse(ide: IdeId, decision: 'allow' | 'deny', reason?: string): {
31
+ stdout: string;
32
+ exitCode: number;
33
+ };
34
+ /**
35
+ * Build a peaks canonical hook from a parsed stdin payload. Caller has already
36
+ * done stdin parsing + IDE auto-detection; this function normalizes to the
37
+ * canonical schema.
38
+ */
39
+ export interface BuildCanonicalHookInput {
40
+ readonly toolName: string;
41
+ readonly toolInput: Record<string, unknown>;
42
+ readonly projectRoot: string;
43
+ readonly rawIdeFormat: IdeId;
44
+ readonly rawPayload: unknown;
45
+ readonly event?: PeaksCanonicalHook['event'];
46
+ }
47
+ export declare function buildCanonicalHook(input: BuildCanonicalHookInput): PeaksCanonicalHook;
@@ -0,0 +1,74 @@
1
+ import { PEAKS_HOOK_SCHEMA } from './ide-types.js';
2
+ export { PEAKS_HOOK_SCHEMA };
3
+ /**
4
+ * Compute the deny decision shape for Claude Code (the only adapter registered
5
+ * in slice #1). The output is a JSON object that, when written to stdout, makes
6
+ * the Claude Code permission system block the tool call BEFORE the user's
7
+ * permission prompt — un-bypassable, even under --dangerously-skip-permissions.
8
+ */
9
+ export const CLAUDE_CODE_DENY_SHAPE = {
10
+ hookSpecificOutput: {
11
+ hookEventName: 'PreToolUse',
12
+ permissionDecision: 'deny',
13
+ permissionDecisionReason: '__REASON__' // replaced at format time
14
+ }
15
+ };
16
+ export const CLAUDE_CODE_DENY_TRANSPORT = {
17
+ kind: 'stdout-json',
18
+ denyShape: CLAUDE_CODE_DENY_SHAPE
19
+ };
20
+ /**
21
+ * Compute the deny decision shape for Trae (Cursor-style sibling IDE).
22
+ * VERIFIED against Trae 1.x fixture — slice 009-009-2026-06-07-trae-dogfood (2026-06-07).
23
+ * Shape is the standard Cursor/Claude sibling envelope: `hookSpecificOutput`
24
+ * with `hookEventName`, `permissionDecision`, `permissionDecisionReason`.
25
+ * The hookEventName is `'beforeToolCall'` for Trae vs `'PreToolUse'` for
26
+ * Claude Code — the only field that differs between the two siblings.
27
+ * Fixture: tests/fixtures/trae/trae-1x-payload.json. Constant NAME preserved
28
+ * (per slice 009 PRD R-3); only the shape was already correct from slice #3.
29
+ */
30
+ export const TRAE_DENY_SHAPE = {
31
+ hookSpecificOutput: {
32
+ hookEventName: 'beforeToolCall',
33
+ permissionDecision: 'deny',
34
+ permissionDecisionReason: '__REASON__' // replaced at format time
35
+ }
36
+ };
37
+ export const TRAE_DENY_TRANSPORT = {
38
+ kind: 'stdout-json',
39
+ denyShape: TRAE_DENY_SHAPE
40
+ };
41
+ /**
42
+ * Format a decision response for a given IDE. Slice #1 handles Claude Code;
43
+ * slice #3 added Trae (1.x-assumption shape — see TRAE_DENY_SHAPE doc).
44
+ * Future slices will add exit-code / both variants for IDEs that don't read
45
+ * stdout.
46
+ */
47
+ export function formatDecisionResponse(ide, decision, reason) {
48
+ if (decision === 'allow') {
49
+ return { stdout: '', exitCode: 0 };
50
+ }
51
+ let shape;
52
+ if (ide === 'claude-code') {
53
+ shape = CLAUDE_CODE_DENY_SHAPE;
54
+ }
55
+ else if (ide === 'trae') {
56
+ shape = TRAE_DENY_SHAPE;
57
+ }
58
+ else {
59
+ throw new Error(`formatDecisionResponse: unsupported IDE ${ide} (not registered in adapter registry; future slice will add support)`);
60
+ }
61
+ const filled = JSON.stringify(shape).replace('"__REASON__"', JSON.stringify(reason ?? 'denied'));
62
+ return { stdout: filled, exitCode: 0 };
63
+ }
64
+ export function buildCanonicalHook(input) {
65
+ return {
66
+ schema: PEAKS_HOOK_SCHEMA,
67
+ event: input.event ?? 'pre-tool-use',
68
+ toolName: input.toolName,
69
+ toolInput: input.toolInput,
70
+ projectRoot: input.projectRoot,
71
+ rawIdeFormat: input.rawIdeFormat,
72
+ rawPayload: input.rawPayload
73
+ };
74
+ }
@@ -0,0 +1,72 @@
1
+ import type { IdeId } from './ide-types.js';
2
+ /**
3
+ * hook-translator —— peaks 自有 hook 协议的核心。
4
+ *
5
+ * 单一职责:把 IDE 私有 stdin 形态归一化到 peaks canonical schema;把 peaks 决策
6
+ * 格式化回 IDE 期望的 stdout/exit-code 形态。
7
+ *
8
+ * auto-detection 算法(优先级从高到低):
9
+ * 1. env 变量:CLAUDE_PROJECT_DIR / TRAE_PROJECT_DIR / CODEX_PROJECT_DIR / ...
10
+ * 2. stdin shape:`{ tool_name, tool_input }` 是 Claude / Trae;
11
+ * `{ toolName, toolInput }` 是 Cursor;
12
+ * `{ eventName, parameters }` 是 Trae 另形态
13
+ * 3. cwd 启发式:存在 .claude / .trae / .codex / .cursor 目录
14
+ * 4. fallback:`claude-code`(backward compat,见 PRD preserved behavior #10)
15
+ */
16
+ export interface DetectFromStdinInput {
17
+ readonly env: NodeJS.ProcessEnv;
18
+ readonly cwd: string;
19
+ /** 已解析的 stdin 形态;null = 空 stdin 或非 JSON */
20
+ readonly parsedStdin: unknown;
21
+ }
22
+ /**
23
+ * Detect the originating IDE from env / stdin shape / cwd heuristics.
24
+ * Falls back to 'claude-code' (PRD preserved behavior: backward compat).
25
+ */
26
+ export declare function detectIdeFromContext(input: DetectFromStdinInput): IdeId;
27
+ /**
28
+ * Pluck a string value at a nested path. Returns undefined if any segment is
29
+ * missing or non-object. Used by adapter-driven stdin parsers.
30
+ */
31
+ export declare function pluckString(obj: unknown, path: readonly string[]): string | undefined;
32
+ export declare function pluckObject(obj: unknown, path: readonly string[]): Record<string, unknown> | undefined;
33
+ /**
34
+ * Default stdin parser for adapters that follow the Claude Code shape
35
+ * (most LLM-style IDEs do: tool_name at top, tool_input.{command} inside).
36
+ */
37
+ export declare function parseClaudeShapeStdin(parsed: unknown): {
38
+ toolName?: string;
39
+ command?: string;
40
+ };
41
+ /**
42
+ * Trae stdin parser. UNVERIFIED — Trae 1.x's actual stdin envelope shape is
43
+ * assumed to be Cursor-style (eventName at top, parameters nested), based on
44
+ * slice #2's read of Trae as a "Cursor sibling". When a real Trae 1.x hook
45
+ * payload is dogfooded, this parser is the seam to update.
46
+ *
47
+ * Shape (assumed):
48
+ * { eventName: 'beforeToolCall', parameters: { command: 'rm -rf /' } }
49
+ *
50
+ * Returns the same `{ toolName, command }` shape as parseClaudeShapeStdin so
51
+ * downstream code (buildCanonicalHook, enforceBashCommand) does not need to
52
+ * branch on IDE. The `toolName` here is the IDE-specific name (e.g.
53
+ * 'terminal'); callers may normalize it to the canonical 'Bash' if needed.
54
+ */
55
+ export declare function parseTraeShapeStdin(parsed: unknown): {
56
+ toolName?: string;
57
+ command?: string;
58
+ };
59
+ /**
60
+ * Per-adapter stdin parser dispatch. Returns the `{ toolName, command }` pair
61
+ * for the given IDE, regardless of which stdin shape the IDE actually emits.
62
+ *
63
+ * The dispatch table is keyed on `IdeId` (not stdin shape) so an adapter can
64
+ * change its wire format without breaking the hook-handle flow. Unknown IDEs
65
+ * fall back to the Claude parser — that preserves the slice #1 "fail-open to
66
+ * Claude" behavior so a future adapter that has not yet been added does not
67
+ * crash the hook runtime.
68
+ */
69
+ export declare function parseAdapterStdin(ide: IdeId, parsed: unknown): {
70
+ toolName?: string;
71
+ command?: string;
72
+ };
@@ -0,0 +1,128 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getAdapter, listAdapterIds } from './ide-registry.js';
4
+ /**
5
+ * Detect the originating IDE from env / stdin shape / cwd heuristics.
6
+ * Falls back to 'claude-code' (PRD preserved behavior: backward compat).
7
+ */
8
+ export function detectIdeFromContext(input) {
9
+ // 1. env 变量优先级最高。Iterate ONLY over currently-registered adapters
10
+ // so the function does not throw on unregistered IDEs while future slices
11
+ // progressively add trae / codex / cursor / qoder / tongyi-lingma.
12
+ for (const adapter of listAdapterIds()) {
13
+ const a = getAdapter(adapter);
14
+ if (input.env[a.envVar] !== undefined) {
15
+ return adapter;
16
+ }
17
+ }
18
+ // 2. stdin shape
19
+ if (isObject(input.parsedStdin)) {
20
+ if ('tool_name' in input.parsedStdin || 'tool_input' in input.parsedStdin) {
21
+ return 'claude-code';
22
+ }
23
+ if ('toolName' in input.parsedStdin || 'toolInput' in input.parsedStdin) {
24
+ return 'cursor';
25
+ }
26
+ if ('eventName' in input.parsedStdin || 'parameters' in input.parsedStdin) {
27
+ return 'trae';
28
+ }
29
+ }
30
+ // 3. cwd 启发式。Same registration-aware iteration as step 1.
31
+ for (const adapter of listAdapterIds()) {
32
+ const a = getAdapter(adapter);
33
+ if (existsSync(join(input.cwd, a.settings.dirName))) {
34
+ return adapter;
35
+ }
36
+ }
37
+ // 4. fallback
38
+ return 'claude-code';
39
+ }
40
+ /**
41
+ * Pluck a string value at a nested path. Returns undefined if any segment is
42
+ * missing or non-object. Used by adapter-driven stdin parsers.
43
+ */
44
+ export function pluckString(obj, path) {
45
+ let cur = obj;
46
+ for (const seg of path) {
47
+ if (!isObject(cur) || !(seg in cur))
48
+ return undefined;
49
+ cur = cur[seg];
50
+ }
51
+ return typeof cur === 'string' ? cur : undefined;
52
+ }
53
+ export function pluckObject(obj, path) {
54
+ let cur = obj;
55
+ for (const seg of path) {
56
+ if (!isObject(cur) || !(seg in cur))
57
+ return undefined;
58
+ cur = cur[seg];
59
+ }
60
+ return isObject(cur) ? cur : undefined;
61
+ }
62
+ function isObject(v) {
63
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
64
+ }
65
+ /**
66
+ * Default stdin parser for adapters that follow the Claude Code shape
67
+ * (most LLM-style IDEs do: tool_name at top, tool_input.{command} inside).
68
+ */
69
+ export function parseClaudeShapeStdin(parsed) {
70
+ if (!isObject(parsed))
71
+ return {};
72
+ const toolName = pluckString(parsed, ['tool_name']);
73
+ const command = pluckString(parsed, ['tool_input', 'command']);
74
+ const result = {};
75
+ if (toolName !== undefined)
76
+ result.toolName = toolName;
77
+ if (command !== undefined)
78
+ result.command = command;
79
+ return result;
80
+ }
81
+ /**
82
+ * Trae stdin parser. UNVERIFIED — Trae 1.x's actual stdin envelope shape is
83
+ * assumed to be Cursor-style (eventName at top, parameters nested), based on
84
+ * slice #2's read of Trae as a "Cursor sibling". When a real Trae 1.x hook
85
+ * payload is dogfooded, this parser is the seam to update.
86
+ *
87
+ * Shape (assumed):
88
+ * { eventName: 'beforeToolCall', parameters: { command: 'rm -rf /' } }
89
+ *
90
+ * Returns the same `{ toolName, command }` shape as parseClaudeShapeStdin so
91
+ * downstream code (buildCanonicalHook, enforceBashCommand) does not need to
92
+ * branch on IDE. The `toolName` here is the IDE-specific name (e.g.
93
+ * 'terminal'); callers may normalize it to the canonical 'Bash' if needed.
94
+ */
95
+ export function parseTraeShapeStdin(parsed) {
96
+ if (!isObject(parsed))
97
+ return {};
98
+ const eventName = pluckString(parsed, ['eventName']);
99
+ const command = pluckString(parsed, ['parameters', 'command']);
100
+ const result = {};
101
+ // Trae sends the event name in the payload (`eventName: 'beforeToolCall'`)
102
+ // and the tool name on the parameters (e.g. `parameters.tool: 'terminal'`).
103
+ // We expose the event name as the "toolName" for now since Trae has not
104
+ // been observed to carry a top-level tool field. Future slice can split
105
+ // event vs tool if the real shape requires it.
106
+ if (eventName !== undefined)
107
+ result.toolName = eventName;
108
+ if (command !== undefined)
109
+ result.command = command;
110
+ return result;
111
+ }
112
+ /**
113
+ * Per-adapter stdin parser dispatch. Returns the `{ toolName, command }` pair
114
+ * for the given IDE, regardless of which stdin shape the IDE actually emits.
115
+ *
116
+ * The dispatch table is keyed on `IdeId` (not stdin shape) so an adapter can
117
+ * change its wire format without breaking the hook-handle flow. Unknown IDEs
118
+ * fall back to the Claude parser — that preserves the slice #1 "fail-open to
119
+ * Claude" behavior so a future adapter that has not yet been added does not
120
+ * crash the hook runtime.
121
+ */
122
+ export function parseAdapterStdin(ide, parsed) {
123
+ if (ide === 'trae')
124
+ return parseTraeShapeStdin(parsed);
125
+ // Future adapters (codex, cursor, qoder, tongyi-lingma) — branch here when
126
+ // their adapters are registered. Until then, fall back to Claude shape.
127
+ return parseClaudeShapeStdin(parsed);
128
+ }