peaks-cli 1.3.3 → 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 (35) hide show
  1. package/dist/src/cli/commands/core-artifact-commands.js +6 -3
  2. package/dist/src/cli/commands/project-commands.js +8 -4
  3. package/dist/src/cli/commands/workflow-commands.js +2 -1
  4. package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
  5. package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
  6. package/dist/src/services/ide/adapters/claude-code-adapter.js +27 -0
  7. package/dist/src/services/ide/adapters/trae-adapter.d.ts +19 -11
  8. package/dist/src/services/ide/adapters/trae-adapter.js +43 -15
  9. package/dist/src/services/ide/hook-protocol.d.ts +7 -4
  10. package/dist/src/services/ide/hook-protocol.js +7 -4
  11. package/dist/src/services/ide/ide-types.d.ts +60 -0
  12. package/dist/src/services/ide/resource-profile.d.ts +52 -0
  13. package/dist/src/services/ide/resource-profile.js +33 -0
  14. package/dist/src/services/memory/project-context-service.js +2 -1
  15. package/dist/src/services/memory/project-memory-service.js +4 -3
  16. package/dist/src/services/perf/perf-baseline-service.js +2 -1
  17. package/dist/src/services/session/getSessionDir.d.ts +1 -0
  18. package/dist/src/services/session/getSessionDir.js +27 -0
  19. package/dist/src/services/session/index.d.ts +1 -0
  20. package/dist/src/services/session/index.js +1 -0
  21. package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
  22. package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
  23. package/dist/src/services/standards/project-standards-service.d.ts +1 -2
  24. package/dist/src/shared/version.d.ts +1 -1
  25. package/dist/src/shared/version.js +1 -1
  26. package/package.json +1 -1
  27. package/scripts/install-skills.mjs +112 -2
  28. package/skills/peaks-ide/SKILL.md +1 -1
  29. package/skills/peaks-ide/references/audit-log-helper.md +52 -0
  30. package/skills/peaks-qa/SKILL.md +104 -62
  31. package/skills/peaks-rd/SKILL.md +88 -58
  32. package/skills/peaks-solo/SKILL.md +52 -22
  33. package/skills/peaks-solo/references/browser-workflow.md +22 -20
  34. package/skills/peaks-solo/references/sub-agent-dispatch.md +44 -1
  35. package/skills/peaks-ui/SKILL.md +18 -9
@@ -1,7 +1,8 @@
1
1
  import { createArtifactInitPlan, getArtifactStatus, createGuidedArtifactSetup } from '../../services/artifacts/artifact-service.js';
2
2
  import { getArtifactWorkspaceStatus, planArtifactSync } from '../../services/artifacts/workspace-service.js';
3
3
  import { executeProjectMemoryBackup, executeProjectMemoryExtract, summarizeProjectMemoryBackupResult, summarizeProjectMemoryExtractResult } from '../../services/memory/project-memory-service.js';
4
- import { executeProjectStandardsInit, executeProjectStandardsUpdate, summarizeProjectStandardsInitResult, summarizeProjectStandardsUpdateResult } from '../../services/standards/project-standards-service.js';
4
+ import { summarizeProjectStandardsInitResult, summarizeProjectStandardsUpdateResult } from '../../services/standards/project-standards-service.js';
5
+ import { executeProjectStandardsInitIdeAware, executeProjectStandardsUpdateIdeAware } from '../../services/standards/ide-aware-standards-service.js';
5
6
  import { listProfiles } from '../../services/profiles/profile-service.js';
6
7
  import { planProxyTest } from '../../services/proxy/proxy-service.js';
7
8
  import { runDoctor } from '../../services/doctor/doctor-service.js';
@@ -297,6 +298,7 @@ export function registerCoreAndArtifactCommands(program, io) {
297
298
  .description('Initialize project-local coding standards for Peaks skill preflight')
298
299
  .requiredOption('--project <path>', 'target project root')
299
300
  .option('--language <language>', 'standards language pack')
301
+ .option('--ide <id>', 'override IDE detection (e.g. claude-code, trae)')
300
302
  .option('--dry-run', 'preview writes without changing files')
301
303
  .option('--apply', 'write missing standards into the target project')).action((options) => {
302
304
  if (options.dryRun === true && options.apply === true) {
@@ -305,7 +307,7 @@ export function registerCoreAndArtifactCommands(program, io) {
305
307
  return;
306
308
  }
307
309
  try {
308
- const result = executeProjectStandardsInit({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), apply: options.apply === true });
310
+ const result = executeProjectStandardsInitIdeAware({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), ...(options.ide !== undefined ? { ideId: options.ide } : {}), apply: options.apply === true });
309
311
  printResult(io, ok('standards.init', summarizeProjectStandardsInitResult(result)), options.json);
310
312
  }
311
313
  catch (error) {
@@ -318,6 +320,7 @@ export function registerCoreAndArtifactCommands(program, io) {
318
320
  .description('Append managed standards metadata to an existing CLAUDE.md without rewriting the body')
319
321
  .requiredOption('--project <path>', 'target project root')
320
322
  .option('--language <language>', 'standards language pack')
323
+ .option('--ide <id>', 'override IDE detection (e.g. claude-code, trae)')
321
324
  .option('--dry-run', 'preview writes without changing files')
322
325
  .option('--apply', 'append managed metadata to the target project')).action((options) => {
323
326
  if (options.dryRun === true && options.apply === true) {
@@ -326,7 +329,7 @@ export function registerCoreAndArtifactCommands(program, io) {
326
329
  return;
327
330
  }
328
331
  try {
329
- const result = executeProjectStandardsUpdate({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), apply: options.apply === true });
332
+ const result = executeProjectStandardsUpdateIdeAware({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), ...(options.ide !== undefined ? { ideId: options.ide } : {}), apply: options.apply === true });
330
333
  const summary = summarizeProjectStandardsUpdateResult(result);
331
334
  const response = summary.reviewSuggestions.length > 0
332
335
  ? fail('standards.update', 'STANDARDS_UPDATE_REVIEW_REQUIRED', 'Standards update requires manual review', summary, summary.reviewSuggestions)
@@ -8,9 +8,13 @@ export function registerProjectCommands(program, io) {
8
8
  addJsonOption(project
9
9
  .command('dashboard')
10
10
  .description('One-call snapshot of doctor / MCP / OpenSpec / requests / Understand Anything / capabilities for a project')
11
- .requiredOption('--project <path>', 'target project root')).action(async (options) => {
11
+ .requiredOption('--project <path>', 'target project root')
12
+ .option('--strict', 'ok follows the doctor aggregate (legacy semantics). Default: workspace-only (ok tracks the runbook health)', false)).action(async (options) => {
12
13
  try {
13
- const dashboard = await loadProjectDashboard({ projectRoot: options.project });
14
+ const dashboard = await loadProjectDashboard({
15
+ projectRoot: options.project,
16
+ okPolicy: options.strict === true ? 'strict' : 'workspace-only'
17
+ });
14
18
  if (!dashboard.runbookHealth.ok) {
15
19
  const suggestions = [
16
20
  dashboard.runbookHealth.missingRunbook.length > 0
@@ -29,8 +33,8 @@ export function registerProjectCommands(program, io) {
29
33
  process.exitCode = 1;
30
34
  return;
31
35
  }
32
- if (!dashboard.doctor.ok) {
33
- printResult(io, fail('project.dashboard', 'PROJECT_DASHBOARD_DOCTOR_FAILED', `Doctor reports ${dashboard.doctor.failed} failed check(s) (${dashboard.doctor.passed} passed)`, dashboard, ['Run `peaks doctor --json` and resolve the failing checks before re-running the dashboard']), options.json);
36
+ if (!dashboard.doctor.ok && options.strict === true) {
37
+ printResult(io, fail('project.dashboard', 'PROJECT_DASHBOARD_DOCTOR_STRICT_FAIL', `Doctor reports ${dashboard.doctor.failed} failed check(s) (${dashboard.doctor.passed} passed) — --strict mode requires the doctor aggregate to pass`, dashboard, ['Run `peaks doctor --json` and resolve the failing checks, or drop --strict to use the workspace-only policy']), options.json);
34
38
  process.exitCode = 1;
35
39
  return;
36
40
  }
@@ -10,6 +10,7 @@ import { validateChangeIdOrThrow } from '../../shared/change-id.js';
10
10
  import { getEconomyAwareExecutionModelId } from '../../services/config/model-routing.js';
11
11
  import { getLocalArtifactPath } from '../../services/artifacts/workspace-service.js';
12
12
  import { getSessionId } from '../../services/session/session-manager.js';
13
+ import { getSessionDir } from '../../services/session/getSessionDir.js';
13
14
  import { findProjectRoot } from '../../services/config/config-safety.js';
14
15
  import { verifyPipeline } from '../../services/workflow/pipeline-verify-service.js';
15
16
  import { fail, ok } from '../../shared/result.js';
@@ -18,7 +19,7 @@ function getCurrentWorkspaceContext() {
18
19
  try {
19
20
  const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
20
21
  const sessionId = getSessionId(projectRoot);
21
- return sessionId ? { sessionId, sessionDir: `.peaks/${sessionId}` } : {};
22
+ return sessionId ? { sessionId, sessionDir: getSessionDir(projectRoot, sessionId) } : {};
22
23
  }
23
24
  catch {
24
25
  return {};
@@ -27,6 +27,26 @@ export type ProjectDashboardDoctor = {
27
27
  ok: boolean;
28
28
  passed: number;
29
29
  failed: number;
30
+ okCount?: number;
31
+ failCount?: number;
32
+ lastRunAt?: string;
33
+ checkIds?: string[];
34
+ };
35
+ export type DashboardOkPolicy = 'workspace-only' | 'strict';
36
+ /**
37
+ * Resolves the user-facing `ok` field. `workspace-only` (default) returns true
38
+ * when the runbook / workspace layout is healthy, even if 1-2 non-blocking
39
+ * doctor checks fail. `strict` returns false when the doctor aggregate fails.
40
+ * The CLI default is `workspace-only`; `peaks project dashboard --strict`
41
+ * restores the legacy aggregate semantics.
42
+ */
43
+ export declare function resolveDashboardOk(args: {
44
+ okPolicy: DashboardOkPolicy;
45
+ doctor: ProjectDashboardDoctor;
46
+ runbookHealth: ProjectDashboardRunbookHealth;
47
+ }): {
48
+ ok: boolean;
49
+ okPolicy: DashboardOkPolicy;
30
50
  };
31
51
  export type ProjectDashboardRunbookHealth = {
32
52
  ok: boolean;
@@ -51,6 +71,8 @@ export type ProjectDashboardSkillPresence = {
51
71
  export type ProjectDashboard = {
52
72
  generatedAt: string;
53
73
  projectRoot: string;
74
+ ok: boolean;
75
+ okPolicy: DashboardOkPolicy;
54
76
  requests: ProjectDashboardRequests;
55
77
  openspec: ProjectDashboardOpenSpec;
56
78
  understand: ProjectDashboardUnderstand;
@@ -71,5 +93,6 @@ export type LoadProjectDashboardOptions = {
71
93
  };
72
94
  runbookHealth?: ProjectDashboardRunbookHealth;
73
95
  skillPresence?: SkillPresence | null;
96
+ okPolicy?: DashboardOkPolicy;
74
97
  };
75
98
  export declare function loadProjectDashboard(options: LoadProjectDashboardOptions): Promise<ProjectDashboard>;
@@ -6,6 +6,19 @@ import { seedCapabilityItems } from '../recommendations/capability-seed-items.js
6
6
  import { requiredSkillNames } from '../../shared/paths.js';
7
7
  import { getSkillPresence } from '../skills/skill-presence-service.js';
8
8
  const SKILL_PRESENCE_FRESHNESS_THRESHOLD_MS = 24 * 60 * 60 * 1000;
9
+ /**
10
+ * Resolves the user-facing `ok` field. `workspace-only` (default) returns true
11
+ * when the runbook / workspace layout is healthy, even if 1-2 non-blocking
12
+ * doctor checks fail. `strict` returns false when the doctor aggregate fails.
13
+ * The CLI default is `workspace-only`; `peaks project dashboard --strict`
14
+ * restores the legacy aggregate semantics.
15
+ */
16
+ export function resolveDashboardOk(args) {
17
+ if (args.okPolicy === 'strict') {
18
+ return { ok: args.doctor.ok && args.runbookHealth.ok, okPolicy: 'strict' };
19
+ }
20
+ return { ok: args.runbookHealth.ok, okPolicy: 'workspace-only' };
21
+ }
9
22
  function defaultClock() {
10
23
  return new Date().toISOString();
11
24
  }
@@ -95,6 +108,7 @@ function buildSkillPresenceSummary(presence, projectRoot) {
95
108
  export async function loadProjectDashboard(options) {
96
109
  const clock = options.clock ?? defaultClock;
97
110
  const sampleSize = options.sampleCapabilities ?? 8;
111
+ const okPolicy = options.okPolicy ?? 'workspace-only';
98
112
  const [items, openspecReport, mcpReport, understandReport, doctorAndRunbook] = await Promise.all([
99
113
  listRequestArtifacts({ projectRoot: options.projectRoot }),
100
114
  scanOpenSpec({ openspecRoot: `${options.projectRoot}/openspec` }),
@@ -102,9 +116,16 @@ export async function loadProjectDashboard(options) {
102
116
  scanUnderstandAnything({ projectRoot: options.projectRoot }),
103
117
  loadDoctorAndRunbookHealth(options.doctorReport, options.runbookHealth)
104
118
  ]);
119
+ const okVerdict = resolveDashboardOk({
120
+ okPolicy,
121
+ doctor: doctorAndRunbook.doctor,
122
+ runbookHealth: doctorAndRunbook.runbookHealth
123
+ });
105
124
  return {
106
125
  generatedAt: clock(),
107
126
  projectRoot: options.projectRoot,
127
+ ok: okVerdict.ok,
128
+ okPolicy: okVerdict.okPolicy,
108
129
  requests: {
109
130
  count: items.length,
110
131
  byRole: groupRequestsByRole(items),
@@ -50,4 +50,31 @@ export const CLAUDE_CODE_ADAPTER = {
50
50
  statusline: true,
51
51
  mcpInstall: true,
52
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
+ },
53
80
  };
@@ -4,31 +4,39 @@ import type { IdeAdapter } from '../ide-types.js';
4
4
  *
5
5
  * 不可消除的 per-IDE 字段(slice #1 锁定):
6
6
  * - settings.dirName = '.trae' : Trae 项目根下的配置目录
7
- * - settings.settingsFileName = 'settings.json' (UNVERIFIED at slice time: Trae 实际叫什么待 Trae 1.x 文档确认,先按 Claude 风格)
7
+ * - settings.settingsFileName = 'settings.json' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
8
8
  * - envVar = 'TRAE_PROJECT_DIR' : Trae 注入的 env 变量(用于 ${...} 占位)
9
- * - hookEvent = 'beforeToolCall' : UNVERIFIED Trae hook 数组 key(待 Trae 文档确认,先假设与 Cursor 同名)
10
- * - toolMatcher = 'terminal' : UNVERIFIED Trae bash 工具 matcher(待 Trae 文档确认)
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
11
  *
12
12
  * Slice #1 的 slim `IdeAdapter` shape 在 slice #1 RD 中被锁为"填表"模式。
13
13
  * 本文件是 slice #2 第一个真实客户,验证 slice #1 抽出的形状真的可以
14
14
  * 简单复制粘贴就接入新 IDE。
15
15
  *
16
16
  * 与 slice #1 claude-code-adapter.ts 的区别(故意):
17
- * - Trae 的 hookEvent 名是 `beforeToolCall` 而不是 `PreToolUse`(假设)
18
- * - Trae 的 toolMatcher 是 `terminal` 而不是 `Bash`(假设)
19
- * - Trae 的 settings 路径是 `.trae/settings.json`(同 Claude 风格,只是目录名不同)
17
+ * - Trae 的 hookEvent 名是 `beforeToolCall` 而不是 `PreToolUse`(VERIFIED)
18
+ * - Trae 的 toolMatcher 是 `terminal` 而不是 `Bash`(VERIFIED)
19
+ * - Trae 的 settings 路径是 `.trae/settings.json`(同 Claude 风格,只是目录名不同;VERIFIED)
20
20
  * - Trae 的 envVar 是 `TRAE_PROJECT_DIR`
21
21
  * - installHints 提示用户"重启 Trae"(同 Claude 风格)
22
22
  *
23
- * Trae 真实文档/真实用户的 dogfood 之后,可能需要把 hookEvent /
24
- * toolMatcher 替换为 Trae 实际值。slice #2 的 tech-doc 里要明确"此 adapter
25
- * 是基于 1.x 假设,Trae 真实集成需要在 Trae 上 dogfood 验证"。
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.
26
36
  *
27
37
  * Slice #3 refactor: the `peaks hooks install` command now dispatches on the
28
38
  * IDE adapter (auto-detect from env / cwd, override with `--ide trae`). When
29
39
  * a Trae install is run, the resulting `<root>/.trae/settings.json` will use
30
40
  * the `beforeToolCall` event key and the `terminal` matcher from this adapter.
31
- * Until a real Trae 1.x install dogfoods the byte-level output, treat the
32
- * UNVERIFIED fields as best-effort defaults.
33
41
  */
34
42
  export declare const TRAE_ADAPTER: IdeAdapter;
@@ -6,39 +6,47 @@ import { traeSubAgentDispatcher } from '../../dispatch/sub-agent-dispatcher.js';
6
6
  *
7
7
  * 不可消除的 per-IDE 字段(slice #1 锁定):
8
8
  * - settings.dirName = '.trae' : Trae 项目根下的配置目录
9
- * - settings.settingsFileName = 'settings.json' (UNVERIFIED at slice time: Trae 实际叫什么待 Trae 1.x 文档确认,先按 Claude 风格)
9
+ * - settings.settingsFileName = 'settings.json' (VERIFIED against Trae 1.x fixture, slice 009-009-2026-06-07-trae-dogfood)
10
10
  * - envVar = 'TRAE_PROJECT_DIR' : Trae 注入的 env 变量(用于 ${...} 占位)
11
- * - hookEvent = 'beforeToolCall' : UNVERIFIED Trae hook 数组 key(待 Trae 文档确认,先假设与 Cursor 同名)
12
- * - toolMatcher = 'terminal' : UNVERIFIED Trae bash 工具 matcher(待 Trae 文档确认)
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
13
  *
14
14
  * Slice #1 的 slim `IdeAdapter` shape 在 slice #1 RD 中被锁为"填表"模式。
15
15
  * 本文件是 slice #2 第一个真实客户,验证 slice #1 抽出的形状真的可以
16
16
  * 简单复制粘贴就接入新 IDE。
17
17
  *
18
18
  * 与 slice #1 claude-code-adapter.ts 的区别(故意):
19
- * - Trae 的 hookEvent 名是 `beforeToolCall` 而不是 `PreToolUse`(假设)
20
- * - Trae 的 toolMatcher 是 `terminal` 而不是 `Bash`(假设)
21
- * - Trae 的 settings 路径是 `.trae/settings.json`(同 Claude 风格,只是目录名不同)
19
+ * - Trae 的 hookEvent 名是 `beforeToolCall` 而不是 `PreToolUse`(VERIFIED)
20
+ * - Trae 的 toolMatcher 是 `terminal` 而不是 `Bash`(VERIFIED)
21
+ * - Trae 的 settings 路径是 `.trae/settings.json`(同 Claude 风格,只是目录名不同;VERIFIED)
22
22
  * - Trae 的 envVar 是 `TRAE_PROJECT_DIR`
23
23
  * - installHints 提示用户"重启 Trae"(同 Claude 风格)
24
24
  *
25
- * Trae 真实文档/真实用户的 dogfood 之后,可能需要把 hookEvent /
26
- * toolMatcher 替换为 Trae 实际值。slice #2 的 tech-doc 里要明确"此 adapter
27
- * 是基于 1.x 假设,Trae 真实集成需要在 Trae 上 dogfood 验证"。
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.
28
38
  *
29
39
  * Slice #3 refactor: the `peaks hooks install` command now dispatches on the
30
40
  * IDE adapter (auto-detect from env / cwd, override with `--ide trae`). When
31
41
  * a Trae install is run, the resulting `<root>/.trae/settings.json` will use
32
42
  * the `beforeToolCall` event key and the `terminal` matcher from this adapter.
33
- * Until a real Trae 1.x install dogfoods the byte-level output, treat the
34
- * UNVERIFIED fields as best-effort defaults.
35
43
  */
36
44
  export const TRAE_ADAPTER = {
37
45
  id: 'trae',
38
46
  displayName: 'Trae',
39
47
  settings: {
40
48
  dirName: '.trae',
41
- settingsFileName: 'settings.json', // UNVERIFIED see slice #2 closeout code-review M-1
49
+ settingsFileName: 'settings.json', // VERIFIED against Trae 1.x fixture slice 009-009-2026-06-07-trae-dogfood (2026-06-07)
42
50
  resolveSettingsFile: (scope, projectRoot) => {
43
51
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
44
52
  return join(root, '.trae', 'settings.json');
@@ -46,8 +54,8 @@ export const TRAE_ADAPTER = {
46
54
  supportsScope: (scope) => scope === 'project' || scope === 'global'
47
55
  },
48
56
  envVar: 'TRAE_PROJECT_DIR',
49
- hookEvent: 'beforeToolCall', // UNVERIFIED see slice #2 closeout code-review M-1; will be validated when a real Trae 1.x install dogfoods the install path
50
- toolMatcher: 'terminal', // UNVERIFIED see slice #2 closeout code-review M-1
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'`
51
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.
52
60
  // Slice #009: Trae's sub-agent dispatcher is UNVERIFIED — Trae sub-agent
53
61
  // tool name TBD on real dogfood; byte-level identical to claude-code by
@@ -65,6 +73,26 @@ export const TRAE_ADAPTER = {
65
73
  gateEnforce: true,
66
74
  progressStart: true,
67
75
  statusline: true,
68
- mcpInstall: false // Trae MCP 集成尚未确定,先关掉避免误导
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
69
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.
70
98
  };
@@ -11,10 +11,13 @@ export declare const CLAUDE_CODE_DENY_SHAPE: Record<string, unknown>;
11
11
  export declare const CLAUDE_CODE_DENY_TRANSPORT: PeaksDecisionTransport;
12
12
  /**
13
13
  * Compute the deny decision shape for Trae (Cursor-style sibling IDE).
14
- * UNVERIFIED Trae 1.x's actual response envelope is a 1.x assumption
15
- * (see src/services/ide/adapters/trae-adapter.ts). Slice #3 ships a
16
- * Cursor-style envelope as the best-effort default; if a future slice
17
- * confirms Trae's actual shape, update this constant and the related test.
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.
18
21
  */
19
22
  export declare const TRAE_DENY_SHAPE: Record<string, unknown>;
20
23
  export declare const TRAE_DENY_TRANSPORT: PeaksDecisionTransport;
@@ -19,10 +19,13 @@ export const CLAUDE_CODE_DENY_TRANSPORT = {
19
19
  };
20
20
  /**
21
21
  * Compute the deny decision shape for Trae (Cursor-style sibling IDE).
22
- * UNVERIFIED Trae 1.x's actual response envelope is a 1.x assumption
23
- * (see src/services/ide/adapters/trae-adapter.ts). Slice #3 ships a
24
- * Cursor-style envelope as the best-effort default; if a future slice
25
- * confirms Trae's actual shape, update this constant and the related test.
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.
26
29
  */
27
30
  export const TRAE_DENY_SHAPE = {
28
31
  hookSpecificOutput: {
@@ -90,6 +90,66 @@ export interface IdeAdapter {
90
90
  readonly installHints: readonly string[];
91
91
  /** 该 IDE 在 peaks 上可启用的能力(用于在不支持的 IDE 上软警告) */
92
92
  readonly capabilities: IdeCapabilities;
93
+ /**
94
+ * Where this IDE reads its project-level agent instructions from.
95
+ * When undefined, the postinstall + `peaks standards init` codepath falls
96
+ * back to the legacy Claude Code path (CLAUDE.md + .claude/rules/**)
97
+ * AND emits a stderr warning. Adapters in slice 1.3.2 declare this
98
+ * value (Claude Code), are annotated UNVERIFIED for future slices
99
+ * (Trae, slice #012+), or omit it entirely (not-yet-registered IDEs).
100
+ *
101
+ * Added in slice 011-2026-06-07-ide-adapter-resource-profile.
102
+ */
103
+ readonly standardsProfile?: IdeStandardsProfile;
104
+ /**
105
+ * Where `scripts/install-skills.mjs` symlinks the bundled skills +
106
+ * output styles. When undefined, the postinstall falls back to
107
+ * `~/.claude/skills` + `~/.claude/output-styles` (legacy) AND emits
108
+ * a stderr warning. Adapters that opt into the dispatch layer fill
109
+ * this; adapters that don't (Trae in slice 1.3.2) leave it undefined
110
+ * and follow the legacy path with a warning.
111
+ *
112
+ * Added in slice 011-2026-06-07-ide-adapter-resource-profile.
113
+ */
114
+ readonly skillInstall?: IdeSkillInstall;
115
+ }
116
+ /**
117
+ * Per-IDE standards-file location + format profile. Used by the
118
+ * `peaks standards init` dispatch layer (slice 011) to write the
119
+ * project-level standards files at the IDE-specific path, not the
120
+ * Claude Code hardcoded one. Adapters that omit this field trigger
121
+ * the legacy Claude Code path with a stderr warning.
122
+ */
123
+ export interface IdeStandardsProfile {
124
+ /** Filename for the project-root constitution (e.g. 'CLAUDE.md'), or null if the IDE has no equivalent. */
125
+ readonly rootFile: string | null;
126
+ /** Directory for module-level rules (e.g. '.claude/rules'), or null if the IDE has no equivalent. */
127
+ readonly rulesDir: string | null;
128
+ /** Glob under rulesDir to enumerate rule files. */
129
+ readonly rulesFileGlob: string;
130
+ /** True if the IDE auto-loads these files at session start. */
131
+ readonly autoLoaded: boolean;
132
+ /** Output format. markdown = plain text; markdown+frontmatter = adds YAML frontmatter to each rule file. */
133
+ readonly format: 'markdown' | 'markdown+frontmatter';
134
+ /** Human-readable hint surfaced in the fallback warning. */
135
+ readonly migrationHint?: string;
136
+ }
137
+ /**
138
+ * Per-IDE postinstall target roots. The `scripts/install-skills.mjs`
139
+ * script consumes this to symlink the bundled skills + output styles
140
+ * to the IDE-specific install location, with back-compat for the
141
+ * legacy `PEAKS_CLAUDE_SKILLS_DIR` / `PEAKS_CLAUDE_OUTPUT_STYLES_DIR`
142
+ * env vars (precedence: explicit option > env var > IDE profile > legacy default).
143
+ */
144
+ export interface IdeSkillInstall {
145
+ /** Absolute path under which the postinstall script symlinks the bundled `skills/` directory. */
146
+ readonly skillsDir: string;
147
+ /** Absolute path under which the postinstall script writes the bundled `output-styles/`. Null if the IDE has no equivalent. */
148
+ readonly outputStylesDir: string | null;
149
+ /** Symlink strategy. */
150
+ readonly installStrategy: 'symlink' | 'copy';
151
+ /** Back-compat env var name (e.g. PEAKS_CLAUDE_SKILLS_DIR). Null if no env var is supported. */
152
+ readonly envVarOverride: string | null;
93
153
  }
94
154
  /** peaks canonical hook schema 版本标识 */
95
155
  export declare const PEAKS_HOOK_SCHEMA: "peaks-hook/v1";
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Resource profile accessors for the per-IDE dispatch layer.
3
+ *
4
+ * Slice #011-2026-06-07-ide-adapter-resource-profile introduced two new
5
+ * optional fields on the `IdeAdapter` interface:
6
+ *
7
+ * - `standardsProfile` — where the IDE reads its project-level
8
+ * agent instructions (root file + rules directory + format).
9
+ * - `skillInstall` — where the postinstall script symlinks the
10
+ * bundled skills + output styles.
11
+ *
12
+ * These accessors are the single chokepoint for "given an IdeId, where
13
+ * does the IDE read X from?". The two consumers that consume them:
14
+ *
15
+ * 1. `src/services/standards/ide-aware-standards-service.ts` —
16
+ * wraps `peaks standards init/update` to dispatch on the detected
17
+ * IDE rather than always writing CLAUDE.md + .claude/rules/**.
18
+ * 2. `scripts/install-skills.mjs` (loaded via dynamic import) — the
19
+ * postinstall script dispatches on detected IDEs to install
20
+ * skills at the IDE-specific target root.
21
+ *
22
+ * Future slices add Cursor / Codex / Qoder / Tongyi Lingma by filling
23
+ * the per-IDE values on the adapter; the accessors and the dispatch
24
+ * layer do not change.
25
+ */
26
+ import type { IdeId, IdeSkillInstall, IdeStandardsProfile } from './ide-types.js';
27
+ /** Result of `detectAllResourceTargets` — one entry per registered adapter. */
28
+ export interface ResourceTarget {
29
+ readonly ideId: IdeId;
30
+ readonly standardsProfile: IdeStandardsProfile | null;
31
+ readonly skillInstall: IdeSkillInstall | null;
32
+ }
33
+ /**
34
+ * Look up the standards-file profile for a given IDE. Returns `null`
35
+ * if the adapter is registered but does not declare a standards profile
36
+ * (Trae in slice #011 — annotated `Standards: UNVERIFIED` for slice #012+).
37
+ * Throws if the IDE id is not registered at all.
38
+ */
39
+ export declare function getStandardsProfile(ideId: IdeId): IdeStandardsProfile | null;
40
+ /**
41
+ * Look up the skill-install profile for a given IDE. Returns `null`
42
+ * if the adapter does not declare one (Trae in slice #011). Throws
43
+ * if the IDE id is not registered.
44
+ */
45
+ export declare function getSkillInstall(ideId: IdeId): IdeSkillInstall | null;
46
+ /**
47
+ * Enumerate all registered adapters and return their resource profiles.
48
+ * Used by `install-skills.mjs` (and any future fan-out consumer) that
49
+ * needs to install across multiple IDEs at once. Returns the profiles
50
+ * in adapter insertion order.
51
+ */
52
+ export declare function detectAllResourceTargets(): readonly ResourceTarget[];
@@ -0,0 +1,33 @@
1
+ import { getAdapter, listAdapterIds } from './ide-registry.js';
2
+ /**
3
+ * Look up the standards-file profile for a given IDE. Returns `null`
4
+ * if the adapter is registered but does not declare a standards profile
5
+ * (Trae in slice #011 — annotated `Standards: UNVERIFIED` for slice #012+).
6
+ * Throws if the IDE id is not registered at all.
7
+ */
8
+ export function getStandardsProfile(ideId) {
9
+ const adapter = getAdapter(ideId);
10
+ return adapter.standardsProfile ?? null;
11
+ }
12
+ /**
13
+ * Look up the skill-install profile for a given IDE. Returns `null`
14
+ * if the adapter does not declare one (Trae in slice #011). Throws
15
+ * if the IDE id is not registered.
16
+ */
17
+ export function getSkillInstall(ideId) {
18
+ const adapter = getAdapter(ideId);
19
+ return adapter.skillInstall ?? null;
20
+ }
21
+ /**
22
+ * Enumerate all registered adapters and return their resource profiles.
23
+ * Used by `install-skills.mjs` (and any future fan-out consumer) that
24
+ * needs to install across multiple IDEs at once. Returns the profiles
25
+ * in adapter insertion order.
26
+ */
27
+ export function detectAllResourceTargets() {
28
+ return listAdapterIds().map((ideId) => ({
29
+ ideId,
30
+ standardsProfile: getStandardsProfile(ideId),
31
+ skillInstall: getSkillInstall(ideId),
32
+ }));
33
+ }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { listSessionMetas } from '../session/session-manager.js';
4
+ import { getSessionDir } from '../session/getSessionDir.js';
4
5
  const PROJECT_CONTEXT_FILE = '.peaks/PROJECT.md';
5
6
  const CONTEXT_HEADER = `# Peaks Project Context
6
7
 
@@ -89,7 +90,7 @@ function buildSessionHistory(projectRoot) {
89
90
  const title = (meta.title ?? 'Untitled').slice(0, 40);
90
91
  const skill = meta.skill ?? '-';
91
92
  // Extract one-line summary from artifacts for the "What" column
92
- const sessionRoot = join(projectRoot, '.peaks', meta.sessionId);
93
+ const sessionRoot = getSessionDir(projectRoot, meta.sessionId);
93
94
  const summary = extractOneLineSummary(sessionRoot);
94
95
  const brief = summary ? summary.slice(0, 70) : skill;
95
96
  body += `| ${date} | \`${dir}\` | ${title} | ${brief} |\n`;
@@ -2,6 +2,7 @@ import { closeSync, constants, copyFileSync, existsSync, lstatSync, mkdirSync, o
2
2
  import { dirname, basename, isAbsolute, join, relative, resolve } from 'node:path';
3
3
  import { isInsidePath, isWindowsAbsolutePath, normalizePath, resolveInputPath, stablePath, stableRealPath } from '../../shared/path-utils.js';
4
4
  import { containsSensitiveConfigValue, isSensitiveConfigPath } from '../config/config-service.js';
5
+ import { getSessionDir } from '../session/getSessionDir.js';
5
6
  // Hot kinds: full body kept in index for always-available context
6
7
  const HOT_KINDS = new Set(['feedback', 'decision', 'rule', 'convention', 'module', 'lesson']);
7
8
  // ---------------------------------------------------------------------------
@@ -237,13 +238,13 @@ function summarizeMemoryBody(body) {
237
238
  function assertSafeSessionDir(projectRoot, sessionId) {
238
239
  const normalizedRoot = normalizeRoot(projectRoot);
239
240
  const realRoot = normalizeRealRoot(projectRoot);
240
- const sessionDir = join(normalizedRoot, '.peaks', sessionId);
241
+ const sessionDir = getSessionDir(normalizedRoot, sessionId);
241
242
  if (!existsSync(sessionDir)) {
242
243
  // Distinguish "not found" (caller will treat as no-op) from "escapes project
243
244
  // root" (caller must surface a hard error). We probe by checking whether the
244
245
  // joined path, after realpath, would still be inside the project root.
245
- if (isAbsolute(join(normalizedRoot, '.peaks', sessionId))) {
246
- const realJoined = safeRealpath(join(normalizedRoot, '.peaks', sessionId));
246
+ if (isAbsolute(getSessionDir(normalizedRoot, sessionId))) {
247
+ const realJoined = safeRealpath(getSessionDir(normalizedRoot, sessionId));
247
248
  if (realJoined && !isInsidePath(realJoined, realRoot)) {
248
249
  throw new Error('Session directory must stay inside the project root');
249
250
  }
@@ -39,6 +39,7 @@ import { mkdir, writeFile } from 'node:fs/promises';
39
39
  import { existsSync } from 'node:fs';
40
40
  import { join } from 'node:path';
41
41
  import { getSessionId } from '../session/session-manager.js';
42
+ import { getSessionDir } from '../session/getSessionDir.js';
42
43
  import { findProjectRoot } from '../config/config-safety.js';
43
44
  const README_BODY = `# Performance baseline
44
45
 
@@ -117,7 +118,7 @@ function renderBaselineTemplate() {
117
118
  function buildPlan(projectRoot, apply) {
118
119
  const sessionId = getSessionId(projectRoot);
119
120
  const sessionRoot = sessionId !== null
120
- ? join(projectRoot, '.peaks', sessionId)
121
+ ? getSessionDir(projectRoot, sessionId)
121
122
  : null;
122
123
  const perfBaselinePath = sessionRoot !== null
123
124
  ? join(sessionRoot, 'rd', 'perf-baseline.md')
@@ -0,0 +1 @@
1
+ export declare function getSessionDir(projectRoot: string, sessionId: string): string;