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.
- package/dist/src/cli/commands/core-artifact-commands.js +6 -3
- package/dist/src/cli/commands/project-commands.js +8 -4
- package/dist/src/cli/commands/workflow-commands.js +2 -1
- package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
- package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
- package/dist/src/services/ide/adapters/claude-code-adapter.js +27 -0
- package/dist/src/services/ide/adapters/trae-adapter.d.ts +19 -11
- package/dist/src/services/ide/adapters/trae-adapter.js +43 -15
- package/dist/src/services/ide/hook-protocol.d.ts +7 -4
- package/dist/src/services/ide/hook-protocol.js +7 -4
- package/dist/src/services/ide/ide-types.d.ts +60 -0
- package/dist/src/services/ide/resource-profile.d.ts +52 -0
- package/dist/src/services/ide/resource-profile.js +33 -0
- package/dist/src/services/memory/project-context-service.js +2 -1
- package/dist/src/services/memory/project-memory-service.js +4 -3
- package/dist/src/services/perf/perf-baseline-service.js +2 -1
- package/dist/src/services/session/getSessionDir.d.ts +1 -0
- package/dist/src/services/session/getSessionDir.js +27 -0
- package/dist/src/services/session/index.d.ts +1 -0
- package/dist/src/services/session/index.js +1 -0
- package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
- package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
- package/dist/src/services/standards/project-standards-service.d.ts +1 -2
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/scripts/install-skills.mjs +112 -2
- package/skills/peaks-ide/SKILL.md +1 -1
- package/skills/peaks-ide/references/audit-log-helper.md +52 -0
- package/skills/peaks-qa/SKILL.md +104 -62
- package/skills/peaks-rd/SKILL.md +88 -58
- package/skills/peaks-solo/SKILL.md +52 -22
- package/skills/peaks-solo/references/browser-workflow.md +22 -20
- package/skills/peaks-solo/references/sub-agent-dispatch.md +44 -1
- 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 {
|
|
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 =
|
|
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 =
|
|
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')
|
|
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({
|
|
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', '
|
|
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:
|
|
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' (
|
|
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'
|
|
10
|
-
* - toolMatcher = 'terminal'
|
|
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
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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' (
|
|
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'
|
|
12
|
-
* - toolMatcher = 'terminal'
|
|
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
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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', //
|
|
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', //
|
|
50
|
-
toolMatcher: 'terminal', //
|
|
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
|
-
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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 =
|
|
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 =
|
|
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(
|
|
246
|
-
const realJoined = safeRealpath(
|
|
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
|
-
?
|
|
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;
|