peaks-cli 1.3.3 → 1.3.5
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/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 +15 -5
- package/dist/src/cli/commands/workflow-commands.js +2 -1
- package/dist/src/cli/commands/workspace-commands.js +1 -2
- package/dist/src/cli/program.js +3 -2
- 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/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 +27 -2
- package/dist/src/services/ide/adapters/trae-adapter.d.ts +19 -11
- package/dist/src/services/ide/adapters/trae-adapter.js +45 -19
- 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 +61 -16
- 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/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/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/skills/hooks-settings-service.d.ts +57 -5
- package/dist/src/services/skills/hooks-settings-service.js +153 -28
- 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/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/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-qa/references/qa-fanout-contract.md +6 -6
- package/skills/peaks-rd/SKILL.md +88 -73
- package/skills/peaks-solo/SKILL.md +52 -22
- package/skills/peaks-solo/references/browser-workflow.md +22 -20
- package/skills/peaks-solo/references/runbook.md +21 -21
- package/skills/peaks-solo/references/sub-agent-dispatch.md +44 -1
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +9 -9
- package/skills/peaks-ui/SKILL.md +18 -9
- 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
|
@@ -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)
|
|
@@ -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) {
|
|
@@ -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
|
}
|
|
@@ -74,7 +78,13 @@ export function registerProjectCommands(program, io) {
|
|
|
74
78
|
.description('Scan a session artifact directory and extract <!-- peaks-memory:start --> blocks into .peaks/memory/')
|
|
75
79
|
.requiredOption('--session-id <id>', 'session id (e.g. 2026-05-29-session-89ff35)')
|
|
76
80
|
.requiredOption('--project <path>', 'target project root')
|
|
77
|
-
|
|
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')
|
|
78
88
|
.option('--apply', 'write extracted memories into .peaks/memory/')).action((options) => {
|
|
79
89
|
if (options.dryRun === true && options.apply === true) {
|
|
80
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);
|
|
@@ -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 {};
|
|
@@ -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
|
@@ -10,7 +10,9 @@ import { registerCodegraphCommands } from './commands/codegraph-commands.js';
|
|
|
10
10
|
import { registerMcpCommands } from './commands/mcp-commands.js';
|
|
11
11
|
import { registerOpenSpecCommands } from './commands/openspec-commands.js';
|
|
12
12
|
import { registerPerfCommands } from './commands/perf-commands.js';
|
|
13
|
-
|
|
13
|
+
// Slice #014: peaks progress * CLI surface deleted (replaced by sub-agent
|
|
14
|
+
// dispatch + heartbeat, slice #009 + #010). Sub-agent progress is
|
|
15
|
+
// surfaced via `peaks sub-agent dispatch|heartbeat|share`.
|
|
14
16
|
import { registerProjectCommands } from './commands/project-commands.js';
|
|
15
17
|
import { registerRequestCommands } from './commands/request-commands.js';
|
|
16
18
|
import { registerScanCommands } from './commands/scan-commands.js';
|
|
@@ -88,7 +90,6 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
|
|
|
88
90
|
registerMcpCommands(program, io);
|
|
89
91
|
registerOpenSpecCommands(program, io);
|
|
90
92
|
registerPerfCommands(program, io);
|
|
91
|
-
registerProgressCommands(program, io);
|
|
92
93
|
registerProjectCommands(program, io);
|
|
93
94
|
registerRequestCommands(program, io);
|
|
94
95
|
registerScanCommands(program, io);
|
|
@@ -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),
|
|
@@ -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
|