peaks-cli 1.3.4 → 1.3.6
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/hook-handle.d.ts +2 -2
- package/dist/src/cli/commands/hook-handle.js +5 -10
- package/dist/src/cli/commands/hooks-commands.js +44 -29
- package/dist/src/cli/commands/project-commands.js +7 -1
- package/dist/src/cli/commands/workspace-commands.js +1 -2
- package/dist/src/cli/program.js +3 -4
- package/dist/src/services/dashboard/project-dashboard-service.d.ts +0 -7
- package/dist/src/services/dashboard/project-dashboard-service.js +1 -8
- package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +45 -40
- package/dist/src/services/dispatch/sub-agent-dispatcher.js +25 -20
- package/dist/src/services/ide/adapters/claude-code-adapter.js +0 -3
- package/dist/src/services/ide/adapters/trae-adapter.js +2 -17
- package/dist/src/services/ide/ide-types.d.ts +1 -18
- package/dist/src/services/progress/progress-service.d.ts +23 -103
- package/dist/src/services/progress/progress-service.js +24 -137
- package/dist/src/services/scan/file-size-scan.d.ts +4 -0
- package/dist/src/services/scan/file-size-scan.js +32 -3
- package/dist/src/services/skills/hooks-settings-service.d.ts +57 -5
- package/dist/src/services/skills/hooks-settings-service.js +153 -28
- package/dist/src/shared/incrementing-number.d.ts +0 -8
- package/dist/src/shared/incrementing-number.js +11 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-prd/SKILL.md +16 -16
- package/skills/peaks-prd/references/workflow.md +4 -4
- package/skills/peaks-qa/SKILL.md +25 -32
- package/skills/peaks-qa/references/qa-fanout-contract.md +6 -6
- package/skills/peaks-qa/references/regression-gates.md +1 -1
- package/skills/peaks-rd/SKILL.md +8 -21
- package/skills/peaks-rd/references/{openspec-mcp-cli.md → openspec-cli.md} +11 -14
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-solo/references/a2a-artifact-mapping.md +1 -1
- package/skills/peaks-solo/references/browser-workflow.md +49 -38
- package/skills/peaks-solo/references/external-skill-invocation.md +9 -7
- package/skills/peaks-solo/references/{openspec-mcp-workflow.md → openspec-workflow.md} +5 -20
- package/skills/peaks-solo/references/runbook.md +21 -21
- package/skills/peaks-solo/references/sub-agent-dispatch.md +16 -35
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +9 -9
- package/skills/peaks-ui/SKILL.md +22 -24
- package/skills/peaks-ui/references/workflow.md +2 -2
- package/dist/src/cli/commands/mcp-commands.d.ts +0 -3
- package/dist/src/cli/commands/mcp-commands.js +0 -144
- package/dist/src/cli/commands/progress-close-kill.d.ts +0 -51
- package/dist/src/cli/commands/progress-close-kill.js +0 -152
- package/dist/src/cli/commands/progress-commands.d.ts +0 -3
- package/dist/src/cli/commands/progress-commands.js +0 -379
- package/dist/src/cli/commands/progress-start-spawn.d.ts +0 -59
- package/dist/src/cli/commands/progress-start-spawn.js +0 -140
- package/dist/src/cli/commands/progress-watch-render.d.ts +0 -80
- package/dist/src/cli/commands/progress-watch-render.js +0 -308
- package/dist/src/services/mcp/mcp-apply-service.d.ts +0 -31
- package/dist/src/services/mcp/mcp-apply-service.js +0 -112
- package/dist/src/services/mcp/mcp-call-service.d.ts +0 -17
- package/dist/src/services/mcp/mcp-call-service.js +0 -34
- package/dist/src/services/mcp/mcp-client-service.d.ts +0 -14
- package/dist/src/services/mcp/mcp-client-service.js +0 -49
- package/dist/src/services/mcp/mcp-install-registry.d.ts +0 -11
- package/dist/src/services/mcp/mcp-install-registry.js +0 -38
- package/dist/src/services/mcp/mcp-plan-service.d.ts +0 -29
- package/dist/src/services/mcp/mcp-plan-service.js +0 -109
- package/dist/src/services/mcp/mcp-protocol.d.ts +0 -24
- package/dist/src/services/mcp/mcp-protocol.js +0 -41
- package/dist/src/services/mcp/mcp-scan-service.d.ts +0 -8
- package/dist/src/services/mcp/mcp-scan-service.js +0 -214
- package/dist/src/services/mcp/mcp-stdio-transport.d.ts +0 -10
- package/dist/src/services/mcp/mcp-stdio-transport.js +0 -50
- package/dist/src/services/mcp/mcp-types.d.ts +0 -31
- package/dist/src/services/mcp/mcp-types.js +0 -1
|
@@ -7,10 +7,10 @@ import { type ProgramIO } from '../cli-helpers.js';
|
|
|
7
7
|
* 1. 读 stdin
|
|
8
8
|
* 2. auto-detect 来源 IDE(env / stdin shape / cwd)
|
|
9
9
|
* 3. 归一化到 peaks canonical schema
|
|
10
|
-
* 4. dispatch 到内部 peaks 逻辑(目前:gate enforce
|
|
10
|
+
* 4. dispatch 到内部 peaks 逻辑(目前:gate enforce)
|
|
11
11
|
* 5. 用 IDE 期望的格式发回决策
|
|
12
12
|
*
|
|
13
|
-
* Slice #1 阶段:peaks hook handle 与 peaks gate enforce
|
|
13
|
+
* Slice #1 阶段:peaks hook handle 与 peaks gate enforce
|
|
14
14
|
* 并存(后者内部走 hook-translator)。Slice #2 把 IDE settings 改成调用
|
|
15
15
|
* peaks hook handle 即可。Slice #3 删除旧命令。
|
|
16
16
|
*/
|
|
@@ -30,18 +30,18 @@ async function readStdin() {
|
|
|
30
30
|
* 1. 读 stdin
|
|
31
31
|
* 2. auto-detect 来源 IDE(env / stdin shape / cwd)
|
|
32
32
|
* 3. 归一化到 peaks canonical schema
|
|
33
|
-
* 4. dispatch 到内部 peaks 逻辑(目前:gate enforce
|
|
33
|
+
* 4. dispatch 到内部 peaks 逻辑(目前:gate enforce)
|
|
34
34
|
* 5. 用 IDE 期望的格式发回决策
|
|
35
35
|
*
|
|
36
|
-
* Slice #1 阶段:peaks hook handle 与 peaks gate enforce
|
|
36
|
+
* Slice #1 阶段:peaks hook handle 与 peaks gate enforce
|
|
37
37
|
* 并存(后者内部走 hook-translator)。Slice #2 把 IDE settings 改成调用
|
|
38
38
|
* peaks hook handle 即可。Slice #3 删除旧命令。
|
|
39
39
|
*/
|
|
40
40
|
export function registerHookHandleCommand(program, io) {
|
|
41
|
-
const hook = program.command('hook').description('Peaks 自有 hook 协议单一入口(slice #1 新增;后续 slice 将逐步替代 gate enforce
|
|
41
|
+
const hook = program.command('hook').description('Peaks 自有 hook 协议单一入口(slice #1 新增;后续 slice 将逐步替代 gate enforce)');
|
|
42
42
|
addJsonOption(hook
|
|
43
43
|
.command('handle')
|
|
44
|
-
.description('Read stdin hook payload, auto-detect IDE, dispatch to peaks gate
|
|
44
|
+
.description('Read stdin hook payload, auto-detect IDE, dispatch to peaks gate-enforce logic, output IDE-formatted decision')
|
|
45
45
|
.option('--project <path>', 'project the gates evaluate against (default: current directory)', '.')).action(async (options) => {
|
|
46
46
|
try {
|
|
47
47
|
const raw = await readStdin();
|
|
@@ -74,7 +74,7 @@ export function registerHookHandleCommand(program, io) {
|
|
|
74
74
|
rawIdeFormat: ide,
|
|
75
75
|
rawPayload: parsed
|
|
76
76
|
});
|
|
77
|
-
// Dispatch by toolName. For slice #1
|
|
77
|
+
// Dispatch by toolName. For slice #1+, we only handle Bash. Task tool sub-agent dispatch goes through `peaks sub-agent dispatch` (slice #009) and does not need a hook entry.
|
|
78
78
|
// Other tools: allow (no-op; future events will be added here).
|
|
79
79
|
if (hook.toolName === 'Bash' && typeof fallbackCommand === 'string' && fallbackCommand.trim().length > 0) {
|
|
80
80
|
// Lazy import to avoid circular: peaks gate enforce logic
|
|
@@ -89,11 +89,6 @@ export function registerHookHandleCommand(program, io) {
|
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
else if (hook.toolName === 'Task') {
|
|
93
|
-
// peaks progress start is a fire-and-forget; do not block hook.handle.
|
|
94
|
-
// Slice #1: simply acknowledge (no terminal spawn from hook handle itself;
|
|
95
|
-
// the legacy `peaks progress start` command still does that).
|
|
96
|
-
}
|
|
97
92
|
const allow = formatDecisionResponse(ide, 'allow');
|
|
98
93
|
io.stdout(allow.stdout);
|
|
99
94
|
if (options.json === true) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { fail, ok } from '../../shared/result.js';
|
|
2
3
|
import { addJsonOption, printResult, getErrorMessage } from '../cli-helpers.js';
|
|
3
4
|
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
4
|
-
import { applyHookInstall, planHookInstall, readHookStatus, removeHookInstall } from '../../services/skills/hooks-settings-service.js';
|
|
5
|
+
import { applyHookInstall, planHookInstall, readHookStatus, readInstalledEntriesFromSettings, removeHookInstall } from '../../services/skills/hooks-settings-service.js';
|
|
6
|
+
import { readJsonObjectFile } from '../../services/ide/shared/atomic-json.js';
|
|
5
7
|
import { detectIdeFromContext } from '../../services/ide/hook-translator.js';
|
|
6
8
|
import { getAdapter } from '../../services/ide/ide-registry.js';
|
|
7
9
|
function resolveScope(options) {
|
|
@@ -23,60 +25,63 @@ function resolveIdeForCommand(options, projectRoot) {
|
|
|
23
25
|
}
|
|
24
26
|
return detectIdeFromContext({ env: process.env, cwd: projectRoot ?? process.cwd(), parsedStdin: null });
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Slice #014: compute the per-IDE peaks hook entries for the install /
|
|
30
|
+
* dry-run RESPONSE SUMMARY. This is the *desired* shape (what the install
|
|
31
|
+
* WOULD write), not what is on disk. The status command uses a different
|
|
32
|
+
* helper (`readInstalledEntriesFromSettings`) that reads the actual
|
|
33
|
+
* settings.json.
|
|
34
|
+
*
|
|
35
|
+
* After slice #014 only the gate-enforce entry is ever installed
|
|
36
|
+
* (the legacy progress-start surface is gone). The summary mirrors
|
|
37
|
+
* the install shape so the JSON envelope doesn't claim a hook the
|
|
38
|
+
* service did not write.
|
|
39
|
+
*/
|
|
40
|
+
function listExpectedEntriesForIde(ide, _skipProgress = false) {
|
|
34
41
|
const adapter = getAdapter(ide);
|
|
35
42
|
if (ide === 'trae') {
|
|
36
|
-
return [
|
|
37
|
-
{ matcher: adapter.toolMatcher, sentinel: 'peaks hook handle' },
|
|
38
|
-
{ matcher: adapter.subAgentToolMatcher, sentinel: 'peaks progress start' }
|
|
39
|
-
];
|
|
43
|
+
return [{ matcher: adapter.toolMatcher, sentinel: 'peaks hook handle' }];
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
return [
|
|
43
|
-
{ matcher: adapter.toolMatcher, sentinel: 'peaks gate enforce' },
|
|
44
|
-
{ matcher: adapter.subAgentToolMatcher, sentinel: 'peaks progress start' }
|
|
45
|
-
];
|
|
45
|
+
return [{ matcher: adapter.toolMatcher, sentinel: 'peaks gate enforce' }];
|
|
46
46
|
}
|
|
47
47
|
export function registerHooksCommands(program, io) {
|
|
48
48
|
const hooks = program
|
|
49
49
|
.command('hooks')
|
|
50
|
-
.description("Manage the Peaks-managed hook
|
|
50
|
+
.description("Manage the Peaks-managed hook entry in the adapter's settings.json (default: .claude/settings.json for Claude, .trae/settings.json for Trae). Slice #014: the only installed entry is the gate-enforce hook (SOP gate). The legacy progress-start hook (auto-spawn sub-agent progress terminal) is no longer installed — sub-agent progress is now surfaced via the dispatch + heartbeat flow (`peaks sub-agent dispatch` / `peaks sub-agent heartbeat`). The IDE is auto-detected from env / cwd; override with --ide <id>.");
|
|
51
51
|
addJsonOption(hooks
|
|
52
52
|
.command('install')
|
|
53
|
-
.description(`Install
|
|
53
|
+
.description(`Install the peaks-managed gate-enforce hook entry into the adapter's settings.json. Slice #014: only the gate-enforce entry is installed; the legacy progress-start entry is no longer installed. Idempotent: re-runs are no-ops. Project scope by default.`)
|
|
54
54
|
.option('--global', 'install into the user-level ~/.claude/settings.json instead of the project')
|
|
55
55
|
.option('--project <path>', 'project root path (auto-detected from cwd when omitted)')
|
|
56
56
|
.option('--ide <id>', "target adapter id (claude-code | trae); default: auto-detect from env/cwd")
|
|
57
|
-
.option('--dry-run', 'show what would change without writing')
|
|
57
|
+
.option('--dry-run', 'show what would change without writing')
|
|
58
|
+
.option('--no-progress', 'skip the progress-start PreToolUse hook entry; install ONLY the gate-enforce entry')).action((options) => {
|
|
58
59
|
const scope = resolveScope(options);
|
|
59
60
|
const projectRoot = resolveProjectRoot(scope, options.project);
|
|
60
61
|
const ide = resolveIdeForCommand(options, projectRoot);
|
|
62
|
+
const skipProgress = options.progress === false;
|
|
61
63
|
try {
|
|
62
64
|
if (options.dryRun === true) {
|
|
63
|
-
const plan = planHookInstall(scope, projectRoot, { ide });
|
|
64
|
-
const dryRunEntries =
|
|
65
|
+
const plan = planHookInstall(scope, projectRoot, { ide, skipProgress });
|
|
66
|
+
const dryRunEntries = listExpectedEntriesForIde(ide, skipProgress);
|
|
65
67
|
printResult(io, ok('hooks.install', {
|
|
66
68
|
...plan,
|
|
67
69
|
ide,
|
|
68
70
|
applied: false,
|
|
69
71
|
dryRun: true,
|
|
72
|
+
skipProgress,
|
|
70
73
|
entries: dryRunEntries
|
|
71
74
|
}, [], [`would install ${dryRunEntries.length} peaks-managed hook entries`]), options.json);
|
|
72
75
|
return;
|
|
73
76
|
}
|
|
74
|
-
const result = applyHookInstall(scope, projectRoot, { ide });
|
|
77
|
+
const result = applyHookInstall(scope, projectRoot, { ide, skipProgress });
|
|
75
78
|
// Slice #3: build the per-IDE entries summary from the actual installed
|
|
76
79
|
// entries, not the slice #1 PEAKS_HOOK_ENTRIES constant (which is the
|
|
77
80
|
// claude-code default). The user's JSON envelope must reflect the IDE
|
|
78
|
-
// they targeted.
|
|
79
|
-
|
|
81
|
+
// they targeted. Slice #014: the install only emits the gate-enforce
|
|
82
|
+
// entry; the summary mirrors the install shape, NOT a hardcoded
|
|
83
|
+
// expected list.
|
|
84
|
+
const installedEntries = listExpectedEntriesForIde(ide, skipProgress);
|
|
80
85
|
const nextActions = result.applied
|
|
81
86
|
? [
|
|
82
87
|
'Restart the IDE (or reload the workspace) so the hook entries take effect',
|
|
@@ -87,18 +92,19 @@ export function registerHooksCommands(program, io) {
|
|
|
87
92
|
...result,
|
|
88
93
|
ide,
|
|
89
94
|
dryRun: false,
|
|
95
|
+
skipProgress,
|
|
90
96
|
entries: installedEntries.map((e) => ({ matcher: e.matcher, sentinel: e.sentinel }))
|
|
91
97
|
}, [], nextActions), options.json);
|
|
92
98
|
}
|
|
93
99
|
catch (error) {
|
|
94
100
|
const message = getErrorMessage(error);
|
|
95
|
-
printResult(io, fail('hooks.install', 'HOOKS_INSTALL_FAILED', message, { scope, ide, applied: false }, [message]), options.json);
|
|
101
|
+
printResult(io, fail('hooks.install', 'HOOKS_INSTALL_FAILED', message, { scope, ide, applied: false, skipProgress }, [message]), options.json);
|
|
96
102
|
process.exitCode = 1;
|
|
97
103
|
}
|
|
98
104
|
});
|
|
99
105
|
addJsonOption(hooks
|
|
100
106
|
.command('uninstall')
|
|
101
|
-
.description("Remove
|
|
107
|
+
.description("Remove the peaks-managed gate-enforce hook entry from the target settings.json. Any legacy progress-start entry that a pre-#014 install left behind is also removed (sentinel-based scan). Third-party hooks are preserved.")
|
|
102
108
|
.option('--global', 'remove from the user-level ~/.claude/settings.json instead of the project')
|
|
103
109
|
.option('--project <path>', 'project root path (auto-detected from cwd when omitted)')
|
|
104
110
|
.option('--ide <id>', 'target adapter id (claude-code | trae); default: auto-detect from env/cwd')).action((options) => {
|
|
@@ -126,10 +132,19 @@ export function registerHooksCommands(program, io) {
|
|
|
126
132
|
const ide = resolveIdeForCommand(options, projectRoot);
|
|
127
133
|
try {
|
|
128
134
|
const status = readHookStatus(scope, projectRoot, { ide });
|
|
135
|
+
// Slice #014: read the ACTUAL on-disk entries (post-install shape),
|
|
136
|
+
// not the IDE-EXPECTED list. Pre-#014 `listInstalledEntriesForIde`
|
|
137
|
+
// returned the expected list and reported `entries: [Bash, Task]`
|
|
138
|
+
// even when the file only had `Bash`. The new helper reads the
|
|
139
|
+
// file and reports whatever peaks-managed entries are present,
|
|
140
|
+
// including any legacy progress-start entry that a pre-#014
|
|
141
|
+
// install left behind.
|
|
142
|
+
const settingsPath = status.settingsPath;
|
|
143
|
+
const settings = existsSync(settingsPath) ? readJsonObjectFile(settingsPath) : {};
|
|
129
144
|
printResult(io, ok('hooks.status', {
|
|
130
145
|
...status,
|
|
131
146
|
ide,
|
|
132
|
-
entries:
|
|
147
|
+
entries: readInstalledEntriesFromSettings(settings, ide)
|
|
133
148
|
}), options.json);
|
|
134
149
|
}
|
|
135
150
|
catch (error) {
|
|
@@ -78,7 +78,13 @@ export function registerProjectCommands(program, io) {
|
|
|
78
78
|
.description('Scan a session artifact directory and extract <!-- peaks-memory:start --> blocks into .peaks/memory/')
|
|
79
79
|
.requiredOption('--session-id <id>', 'session id (e.g. 2026-05-29-session-89ff35)')
|
|
80
80
|
.requiredOption('--project <path>', 'target project root')
|
|
81
|
-
|
|
81
|
+
// Slice #015: drop the `--dry-run true` default. With the default
|
|
82
|
+
// set to true, `options.dryRun === true && options.apply === true`
|
|
83
|
+
// fired on every `--apply` call (because dryRun was true by
|
|
84
|
+
// default), permanently breaking `--apply`. `--dry-run` is now
|
|
85
|
+
// opt-in; the mutual-exclusion check below is correct without a
|
|
86
|
+
// special-case.
|
|
87
|
+
.option('--dry-run', 'preview writes without changing files')
|
|
82
88
|
.option('--apply', 'write extracted memories into .peaks/memory/')).action((options) => {
|
|
83
89
|
if (options.dryRun === true && options.apply === true) {
|
|
84
90
|
printResult(io, fail('project.memories:extract', 'INVALID_MEMORY_EXTRACT_FLAGS', 'Use either --dry-run or --apply, not both', { sessionId: options.sessionId, projectRoot: options.project }, ['Run without --apply to preview writes, or pass --apply to write memories']), options.json);
|
|
@@ -449,8 +449,7 @@ export async function resolveFirstTimeHooksInstall(options) {
|
|
|
449
449
|
// effectiveMode === 'ask' AND TTY: prompt once.
|
|
450
450
|
process.stderr.write('\nPeaks-Cli: install the PreToolUse hooks for this project now?\n' +
|
|
451
451
|
' → Bash matcher: `peaks gate enforce` (SOP gate enforcement)\n' +
|
|
452
|
-
'
|
|
453
|
-
'Both run on every Claude Code tool call without further prompting. The decision is sticky\n' +
|
|
452
|
+
'The gate-enforce hook runs on every Claude Code tool call without further prompting. The decision is sticky\n' +
|
|
454
453
|
'(recorded in .peaks/.peaks-init-hooks-decision.json) and re-runs of `workspace init` will\n' +
|
|
455
454
|
'honour it. Re-run with --install-hooks=skip or --install-hooks=auto to override.\n\n' +
|
|
456
455
|
'Install now? [Y/n]: ');
|
package/dist/src/cli/program.js
CHANGED
|
@@ -7,10 +7,11 @@ import { registerCoreAndArtifactCommands } from './commands/core-artifact-comman
|
|
|
7
7
|
import { registerWorkflowCommands } from './commands/workflow-commands.js';
|
|
8
8
|
import { registerCapabilityWorkerConfigAndSCCommands } from './commands/capability-worker-config-sc-commands.js';
|
|
9
9
|
import { registerCodegraphCommands } from './commands/codegraph-commands.js';
|
|
10
|
-
import { registerMcpCommands } from './commands/mcp-commands.js';
|
|
11
10
|
import { registerOpenSpecCommands } from './commands/openspec-commands.js';
|
|
12
11
|
import { registerPerfCommands } from './commands/perf-commands.js';
|
|
13
|
-
|
|
12
|
+
// Slice #014: peaks progress * CLI surface deleted (replaced by sub-agent
|
|
13
|
+
// dispatch + heartbeat, slice #009 + #010). Sub-agent progress is
|
|
14
|
+
// surfaced via `peaks sub-agent dispatch|heartbeat|share`.
|
|
14
15
|
import { registerProjectCommands } from './commands/project-commands.js';
|
|
15
16
|
import { registerRequestCommands } from './commands/request-commands.js';
|
|
16
17
|
import { registerScanCommands } from './commands/scan-commands.js';
|
|
@@ -85,10 +86,8 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
85
86
|
registerWorkflowCommands(program, io);
|
|
86
87
|
registerCapabilityWorkerConfigAndSCCommands(program, io);
|
|
87
88
|
registerCodegraphCommands(program, io);
|
|
88
|
-
registerMcpCommands(program, io);
|
|
89
89
|
registerOpenSpecCommands(program, io);
|
|
90
90
|
registerPerfCommands(program, io);
|
|
91
|
-
registerProgressCommands(program, io);
|
|
92
91
|
registerProjectCommands(program, io);
|
|
93
92
|
registerRequestCommands(program, io);
|
|
94
93
|
registerScanCommands(program, io);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type RequestArtifactRole, type RequestArtifactSummary } from '../artifacts/request-artifact-service.js';
|
|
2
2
|
import type { OpenSpecChangeSummary } from '../openspec/openspec-types.js';
|
|
3
|
-
import type { McpScanReport } from '../mcp/mcp-types.js';
|
|
4
3
|
import type { CapabilityItem } from '../recommendations/recommendation-types.js';
|
|
5
4
|
import { type SkillPresence } from '../skills/skill-presence-service.js';
|
|
6
5
|
export type ProjectDashboardRequests = {
|
|
@@ -19,10 +18,6 @@ export type ProjectDashboardUnderstand = {
|
|
|
19
18
|
graphPath: string;
|
|
20
19
|
parseError?: string;
|
|
21
20
|
};
|
|
22
|
-
export type ProjectDashboardMcp = {
|
|
23
|
-
servers: McpScanReport['servers'];
|
|
24
|
-
scopes: McpScanReport['scopes'];
|
|
25
|
-
};
|
|
26
21
|
export type ProjectDashboardDoctor = {
|
|
27
22
|
ok: boolean;
|
|
28
23
|
passed: number;
|
|
@@ -57,7 +52,6 @@ export type ProjectDashboardRunbookHealth = {
|
|
|
57
52
|
};
|
|
58
53
|
export type ProjectDashboardCapabilities = {
|
|
59
54
|
count: number;
|
|
60
|
-
mcpCount: number;
|
|
61
55
|
sample: Array<Pick<CapabilityItem, 'capabilityId' | 'name' | 'itemType' | 'category'>>;
|
|
62
56
|
};
|
|
63
57
|
export type ProjectDashboardSkillPresence = {
|
|
@@ -76,7 +70,6 @@ export type ProjectDashboard = {
|
|
|
76
70
|
requests: ProjectDashboardRequests;
|
|
77
71
|
openspec: ProjectDashboardOpenSpec;
|
|
78
72
|
understand: ProjectDashboardUnderstand;
|
|
79
|
-
mcp: ProjectDashboardMcp;
|
|
80
73
|
doctor: ProjectDashboardDoctor;
|
|
81
74
|
runbookHealth: ProjectDashboardRunbookHealth;
|
|
82
75
|
capabilities: ProjectDashboardCapabilities;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { listRequestArtifacts } from '../artifacts/request-artifact-service.js';
|
|
2
2
|
import { scanOpenSpec } from '../openspec/openspec-scan-service.js';
|
|
3
|
-
import { scanMcpServers } from '../mcp/mcp-scan-service.js';
|
|
4
3
|
import { scanUnderstandAnything } from '../understand/understand-scan-service.js';
|
|
5
4
|
import { seedCapabilityItems } from '../recommendations/capability-seed-items.js';
|
|
6
5
|
import { requiredSkillNames } from '../../shared/paths.js';
|
|
@@ -78,7 +77,6 @@ function buildCapabilitiesSummary(sampleSize) {
|
|
|
78
77
|
const items = seedCapabilityItems;
|
|
79
78
|
return {
|
|
80
79
|
count: items.length,
|
|
81
|
-
mcpCount: items.filter((item) => item.itemType === 'mcp').length,
|
|
82
80
|
sample: items.slice(0, sampleSize).map((item) => ({
|
|
83
81
|
capabilityId: item.capabilityId,
|
|
84
82
|
name: item.name,
|
|
@@ -109,10 +107,9 @@ export async function loadProjectDashboard(options) {
|
|
|
109
107
|
const clock = options.clock ?? defaultClock;
|
|
110
108
|
const sampleSize = options.sampleCapabilities ?? 8;
|
|
111
109
|
const okPolicy = options.okPolicy ?? 'workspace-only';
|
|
112
|
-
const [items, openspecReport,
|
|
110
|
+
const [items, openspecReport, understandReport, doctorAndRunbook] = await Promise.all([
|
|
113
111
|
listRequestArtifacts({ projectRoot: options.projectRoot }),
|
|
114
112
|
scanOpenSpec({ openspecRoot: `${options.projectRoot}/openspec` }),
|
|
115
|
-
scanMcpServers({ projectRoot: options.projectRoot }),
|
|
116
113
|
scanUnderstandAnything({ projectRoot: options.projectRoot }),
|
|
117
114
|
loadDoctorAndRunbookHealth(options.doctorReport, options.runbookHealth)
|
|
118
115
|
]);
|
|
@@ -142,10 +139,6 @@ export async function loadProjectDashboard(options) {
|
|
|
142
139
|
graphPath: understandReport.graph.path,
|
|
143
140
|
...(understandReport.graph.parseError !== undefined ? { parseError: understandReport.graph.parseError } : {})
|
|
144
141
|
},
|
|
145
|
-
mcp: {
|
|
146
|
-
servers: mcpReport.servers,
|
|
147
|
-
scopes: mcpReport.scopes
|
|
148
|
-
},
|
|
149
142
|
doctor: doctorAndRunbook.doctor,
|
|
150
143
|
runbookHealth: doctorAndRunbook.runbookHealth,
|
|
151
144
|
capabilities: buildCapabilitiesSummary(sampleSize),
|
|
@@ -8,29 +8,29 @@
|
|
|
8
8
|
* here, never leaked to SKILL.md.
|
|
9
9
|
*
|
|
10
10
|
* Why this exists:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
20
|
*
|
|
21
21
|
* Cross-reference: PRD #002 G1 (AC-1..AC-5); RD tech-doc-002 §2.
|
|
22
22
|
*/
|
|
23
23
|
/**
|
|
24
24
|
* Role string namespace. Soft whitelist — the CLI does NOT hard-validate
|
|
25
|
-
* specific role names. Empirically observed (peaks-qa SKILL.md):
|
|
26
|
-
* roles +
|
|
25
|
+
* specific role names. Empirically observed (peaks-qa SKILL.md):3 top
|
|
26
|
+
* roles +3 sub-roles + arbitrary business subdivisions:
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* - top: rd | qa | ui | txt | general-purpose
|
|
29
|
+
* - qa sub-roles: qa-business | qa-perf | qa-security
|
|
30
|
+
* - business细分: qa-business-regression | qa-business-api
|
|
31
|
+
* | qa-business-frontend | ...
|
|
32
|
+
* - promotable: prd-business | prd-technical | prd-ux |
|
|
33
|
+
* ui-visual | ui-flow | ui-component | ...
|
|
34
34
|
*
|
|
35
35
|
* Any non-empty string is a valid role. CLI emits a "soft whitelist"
|
|
36
36
|
* hint in --help but does not reject unknown values.
|
|
@@ -64,43 +64,48 @@ export interface SubAgentDispatchInput {
|
|
|
64
64
|
*/
|
|
65
65
|
export interface SubAgentDispatcher {
|
|
66
66
|
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
* Short label used in envelope `ide` field and CLI help text.
|
|
68
|
+
* e.g. "claude-code" / "trae" / "null".
|
|
69
|
+
*/
|
|
70
70
|
readonly label: string;
|
|
71
71
|
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
* Whether this dispatcher supports dispatching a given role.
|
|
73
|
+
* claude-code returns true for all non-empty strings; trae is
|
|
74
|
+
* byte-identical (UNVERIFIED pending real Trae dogfood);
|
|
75
|
+
* null-dispatcher always returns false.
|
|
76
|
+
*/
|
|
77
77
|
supportsRole(role: SubAgentRole): boolean;
|
|
78
78
|
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
* Build the IDE-specific tool call descriptor for a dispatch.
|
|
80
|
+
* Must be pure: no I/O, no side effects. The CLI wraps the
|
|
81
|
+
* returned descriptor in its JSON envelope.
|
|
82
|
+
*/
|
|
83
83
|
buildToolCall(input: SubAgentDispatchInput): SubAgentToolCall;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* Claude Code dispatcher. Real, byte-level implementation.
|
|
87
87
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
88
|
+
* - `supportsRole`: any non-empty string (Claude Code's
|
|
89
|
+
* `general-purpose` sub-agent accepts any prompt).
|
|
90
|
+
* - `buildToolCall`: returns `{name: 'Task', args: {subagent_type,
|
|
91
|
+
* description, prompt}}` — the exact shape the `Task` tool
|
|
92
|
+
* in Claude Code expects.
|
|
93
93
|
*/
|
|
94
94
|
export declare const claudeCodeSubAgentDispatcher: SubAgentDispatcher;
|
|
95
95
|
/**
|
|
96
96
|
* Trae dispatcher. UNVERIFIED — Trae sub-agent tool name TBD on real
|
|
97
97
|
* dogfood. Byte-level identical to claude-code by design so:
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
98
|
+
* - The dispatcher's return shape is uniform across both adapters
|
|
99
|
+
* — a single byte-equality test can verify the placeholder
|
|
100
|
+
* contract.
|
|
101
|
+
* - Future real Trae dogfood can replace the body of
|
|
102
|
+
* `buildToolCall` without breaking the adapter contract.
|
|
103
|
+
*
|
|
104
|
+
* Slice #014: the legacy `subAgentToolMatcher: 'Task'` install entry
|
|
105
|
+
* is gone — the field is removed from `IdeAdapter`. Slice #009+
|
|
106
|
+
* dispatched sub-agents directly, not via a PreToolUse hook. The Trae
|
|
107
|
+
* dispatcher remains a placeholder so the dispatch surface is uniform
|
|
108
|
+
* across adapters.
|
|
104
109
|
*
|
|
105
110
|
* When real Trae dogfood lands, replace the body of `buildToolCall`
|
|
106
111
|
* with Trae's actual sub-agent tool name + args shape. The interface
|
|
@@ -8,26 +8,26 @@
|
|
|
8
8
|
* here, never leaked to SKILL.md.
|
|
9
9
|
*
|
|
10
10
|
* Why this exists:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
20
|
*
|
|
21
21
|
* Cross-reference: PRD #002 G1 (AC-1..AC-5); RD tech-doc-002 §2.
|
|
22
22
|
*/
|
|
23
23
|
/**
|
|
24
24
|
* Claude Code dispatcher. Real, byte-level implementation.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
31
|
*/
|
|
32
32
|
export const claudeCodeSubAgentDispatcher = {
|
|
33
33
|
label: 'claude-code',
|
|
@@ -44,12 +44,17 @@ export const claudeCodeSubAgentDispatcher = {
|
|
|
44
44
|
/**
|
|
45
45
|
* Trae dispatcher. UNVERIFIED — Trae sub-agent tool name TBD on real
|
|
46
46
|
* dogfood. Byte-level identical to claude-code by design so:
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
47
|
+
* - The dispatcher's return shape is uniform across both adapters
|
|
48
|
+
* — a single byte-equality test can verify the placeholder
|
|
49
|
+
* contract.
|
|
50
|
+
* - Future real Trae dogfood can replace the body of
|
|
51
|
+
* `buildToolCall` without breaking the adapter contract.
|
|
52
|
+
*
|
|
53
|
+
* Slice #014: the legacy `subAgentToolMatcher: 'Task'` install entry
|
|
54
|
+
* is gone — the field is removed from `IdeAdapter`. Slice #009+
|
|
55
|
+
* dispatched sub-agents directly, not via a PreToolUse hook. The Trae
|
|
56
|
+
* dispatcher remains a placeholder so the dispatch surface is uniform
|
|
57
|
+
* across adapters.
|
|
53
58
|
*
|
|
54
59
|
* When real Trae dogfood lands, replace the body of `buildToolCall`
|
|
55
60
|
* with Trae's actual sub-agent tool name + args shape. The interface
|
|
@@ -32,7 +32,6 @@ export const CLAUDE_CODE_ADAPTER = {
|
|
|
32
32
|
envVar: 'CLAUDE_PROJECT_DIR',
|
|
33
33
|
hookEvent: 'PreToolUse',
|
|
34
34
|
toolMatcher: 'Bash',
|
|
35
|
-
subAgentToolMatcher: 'Task',
|
|
36
35
|
// Slice #009: Claude Code uses the `Task` tool for sub-agent dispatch.
|
|
37
36
|
// The CLI calls `claudeCodeSubAgentDispatcher.buildToolCall` to construct
|
|
38
37
|
// the exact args shape the `Task` tool expects.
|
|
@@ -46,9 +45,7 @@ export const CLAUDE_CODE_ADAPTER = {
|
|
|
46
45
|
],
|
|
47
46
|
capabilities: {
|
|
48
47
|
gateEnforce: true,
|
|
49
|
-
progressStart: true,
|
|
50
48
|
statusline: true,
|
|
51
|
-
mcpInstall: true,
|
|
52
49
|
},
|
|
53
50
|
// Slice #011: standards profile. Claude Code reads its constitution at
|
|
54
51
|
// CLAUDE.md + module-level rules under .claude/rules/**. The values mirror
|
|
@@ -56,11 +56,10 @@ export const TRAE_ADAPTER = {
|
|
|
56
56
|
envVar: 'TRAE_PROJECT_DIR',
|
|
57
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
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
59
|
// Slice #009: Trae's sub-agent dispatcher is UNVERIFIED — Trae sub-agent
|
|
61
60
|
// tool name TBD on real dogfood; byte-level identical to claude-code by
|
|
62
|
-
// design so the
|
|
63
|
-
//
|
|
61
|
+
// design so the dispatcher shape is uniform across both adapters. Awaiting
|
|
62
|
+
// real Trae 1.x dogfood to confirm/replace.
|
|
64
63
|
subAgentDispatcher: traeSubAgentDispatcher,
|
|
65
64
|
// Slice #010 G9: Trae supports `beforeToolCall` which can wrap
|
|
66
65
|
// `peaks sub-agent-dispatch-guard`. Opt in (matches the byte-stable
|
|
@@ -71,21 +70,7 @@ export const TRAE_ADAPTER = {
|
|
|
71
70
|
],
|
|
72
71
|
capabilities: {
|
|
73
72
|
gateEnforce: true,
|
|
74
|
-
progressStart: true,
|
|
75
73
|
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
74
|
}
|
|
90
75
|
// Standards: UNVERIFIED — see slice #012+ (Trae real-install dogfood for
|
|
91
76
|
// the `standardsProfile` and `skillInstall` fields). The slice #011
|
|
@@ -16,12 +16,8 @@ export type IdeId = 'claude-code' | 'trae' | 'codex' | 'cursor' | 'qoder' | 'ton
|
|
|
16
16
|
export interface IdeCapabilities {
|
|
17
17
|
/** peaks gate enforce 是否适用该 IDE(必备) */
|
|
18
18
|
readonly gateEnforce: true;
|
|
19
|
-
/** peaks progress start(sub-agent 派发)是否适用 */
|
|
20
|
-
readonly progressStart: boolean;
|
|
21
19
|
/** peaks statusline 状态栏是否适用 */
|
|
22
20
|
readonly statusline: boolean;
|
|
23
|
-
/** peaks mcp install 是否适用 */
|
|
24
|
-
readonly mcpInstall: boolean;
|
|
25
21
|
}
|
|
26
22
|
export interface IdeSettingsLocation {
|
|
27
23
|
/** 项目根下的 settings 目录名,例如 '.claude' / '.trae' / '.cursor' */
|
|
@@ -48,23 +44,10 @@ export interface IdeAdapter {
|
|
|
48
44
|
readonly hookEvent: string;
|
|
49
45
|
/** hook 数组元素的 matcher 字段(工具名匹配),例如 'Bash' / 'Task' / 'terminal' */
|
|
50
46
|
readonly toolMatcher: string;
|
|
51
|
-
/**
|
|
52
|
-
* The tool name used by this IDE to invoke a sub-agent (e.g. Claude Code
|
|
53
|
-
* uses 'Task' to dispatch a sub-agent, Trae may use a different name).
|
|
54
|
-
* Consumed by the `peaks progress start` hook entry so each IDE self-
|
|
55
|
-
* reports its sub-agent tool name. Additive on `toolMatcher`: the
|
|
56
|
-
* `toolMatcher` field still drives the gate-enforce hook entry, this
|
|
57
|
-
* one drives the sub-agent-progress hook entry.
|
|
58
|
-
*
|
|
59
|
-
* Added in slice 2026-06-06-sub-agent-spawn-bug-and-decouple.
|
|
60
|
-
*/
|
|
61
|
-
readonly subAgentToolMatcher: string;
|
|
62
47
|
/**
|
|
63
48
|
* Per-IDE sub-agent dispatcher. The `peaks sub-agent dispatch` CLI reads
|
|
64
49
|
* this field, calls `supportsRole` + `buildToolCall`, and returns the
|
|
65
|
-
* resulting tool-call descriptor in the JSON envelope.
|
|
66
|
-
* `subAgentToolMatcher`: the matcher still drives the gate-enforce hook
|
|
67
|
-
* entry; this field drives the runtime sub-agent dispatch surface.
|
|
50
|
+
* resulting tool-call descriptor in the JSON envelope. Encapsulates the per-IDE sub-agent dispatch surface (slice #009). The dispatcher's `buildToolCall` returns the IDE-native tool-call descriptor at runtime.
|
|
68
51
|
*
|
|
69
52
|
* Added in slice 2026-06-07-sub-agent-dispatch-decouple. See PRD #002
|
|
70
53
|
* G1 (AC-1, AC-2) + [[slim-ideadapter-shape-is-the-contract]].
|