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.
Files changed (61) hide show
  1. package/dist/src/cli/commands/core-artifact-commands.js +6 -3
  2. package/dist/src/cli/commands/hook-handle.d.ts +2 -2
  3. package/dist/src/cli/commands/hook-handle.js +5 -10
  4. package/dist/src/cli/commands/hooks-commands.js +44 -29
  5. package/dist/src/cli/commands/project-commands.js +15 -5
  6. package/dist/src/cli/commands/workflow-commands.js +2 -1
  7. package/dist/src/cli/commands/workspace-commands.js +1 -2
  8. package/dist/src/cli/program.js +3 -2
  9. package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
  10. package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
  11. package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +45 -40
  12. package/dist/src/services/dispatch/sub-agent-dispatcher.js +25 -20
  13. package/dist/src/services/ide/adapters/claude-code-adapter.js +27 -2
  14. package/dist/src/services/ide/adapters/trae-adapter.d.ts +19 -11
  15. package/dist/src/services/ide/adapters/trae-adapter.js +45 -19
  16. package/dist/src/services/ide/hook-protocol.d.ts +7 -4
  17. package/dist/src/services/ide/hook-protocol.js +7 -4
  18. package/dist/src/services/ide/ide-types.d.ts +61 -16
  19. package/dist/src/services/ide/resource-profile.d.ts +52 -0
  20. package/dist/src/services/ide/resource-profile.js +33 -0
  21. package/dist/src/services/memory/project-context-service.js +2 -1
  22. package/dist/src/services/memory/project-memory-service.js +4 -3
  23. package/dist/src/services/perf/perf-baseline-service.js +2 -1
  24. package/dist/src/services/progress/progress-service.d.ts +23 -103
  25. package/dist/src/services/progress/progress-service.js +24 -137
  26. package/dist/src/services/scan/file-size-scan.d.ts +4 -0
  27. package/dist/src/services/scan/file-size-scan.js +32 -3
  28. package/dist/src/services/session/getSessionDir.d.ts +1 -0
  29. package/dist/src/services/session/getSessionDir.js +27 -0
  30. package/dist/src/services/session/index.d.ts +1 -0
  31. package/dist/src/services/session/index.js +1 -0
  32. package/dist/src/services/skills/hooks-settings-service.d.ts +57 -5
  33. package/dist/src/services/skills/hooks-settings-service.js +153 -28
  34. package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
  35. package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
  36. package/dist/src/services/standards/project-standards-service.d.ts +1 -2
  37. package/dist/src/shared/incrementing-number.d.ts +0 -8
  38. package/dist/src/shared/incrementing-number.js +11 -1
  39. package/dist/src/shared/version.d.ts +1 -1
  40. package/dist/src/shared/version.js +1 -1
  41. package/package.json +1 -1
  42. package/scripts/install-skills.mjs +112 -2
  43. package/skills/peaks-ide/SKILL.md +1 -1
  44. package/skills/peaks-ide/references/audit-log-helper.md +52 -0
  45. package/skills/peaks-qa/SKILL.md +104 -62
  46. package/skills/peaks-qa/references/qa-fanout-contract.md +6 -6
  47. package/skills/peaks-rd/SKILL.md +88 -73
  48. package/skills/peaks-solo/SKILL.md +52 -22
  49. package/skills/peaks-solo/references/browser-workflow.md +22 -20
  50. package/skills/peaks-solo/references/runbook.md +21 -21
  51. package/skills/peaks-solo/references/sub-agent-dispatch.md +44 -1
  52. package/skills/peaks-solo/references/swarm-dispatch-contract.md +9 -9
  53. package/skills/peaks-ui/SKILL.md +18 -9
  54. package/dist/src/cli/commands/progress-close-kill.d.ts +0 -51
  55. package/dist/src/cli/commands/progress-close-kill.js +0 -152
  56. package/dist/src/cli/commands/progress-commands.d.ts +0 -3
  57. package/dist/src/cli/commands/progress-commands.js +0 -379
  58. package/dist/src/cli/commands/progress-start-spawn.d.ts +0 -59
  59. package/dist/src/cli/commands/progress-start-spawn.js +0 -140
  60. package/dist/src/cli/commands/progress-watch-render.d.ts +0 -80
  61. 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 { executeProjectStandardsInit, executeProjectStandardsUpdate, summarizeProjectStandardsInitResult, summarizeProjectStandardsUpdateResult } from '../../services/standards/project-standards-service.js';
4
+ import { summarizeProjectStandardsInitResult, summarizeProjectStandardsUpdateResult } from '../../services/standards/project-standards-service.js';
5
+ import { executeProjectStandardsInitIdeAware, executeProjectStandardsUpdateIdeAware } from '../../services/standards/ide-aware-standards-service.js';
5
6
  import { listProfiles } from '../../services/profiles/profile-service.js';
6
7
  import { planProxyTest } from '../../services/proxy/proxy-service.js';
7
8
  import { runDoctor } from '../../services/doctor/doctor-service.js';
@@ -297,6 +298,7 @@ export function registerCoreAndArtifactCommands(program, io) {
297
298
  .description('Initialize project-local coding standards for Peaks skill preflight')
298
299
  .requiredOption('--project <path>', 'target project root')
299
300
  .option('--language <language>', 'standards language pack')
301
+ .option('--ide <id>', 'override IDE detection (e.g. claude-code, trae)')
300
302
  .option('--dry-run', 'preview writes without changing files')
301
303
  .option('--apply', 'write missing standards into the target project')).action((options) => {
302
304
  if (options.dryRun === true && options.apply === true) {
@@ -305,7 +307,7 @@ export function registerCoreAndArtifactCommands(program, io) {
305
307
  return;
306
308
  }
307
309
  try {
308
- const result = executeProjectStandardsInit({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), apply: options.apply === true });
310
+ const result = executeProjectStandardsInitIdeAware({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), ...(options.ide !== undefined ? { ideId: options.ide } : {}), apply: options.apply === true });
309
311
  printResult(io, ok('standards.init', summarizeProjectStandardsInitResult(result)), options.json);
310
312
  }
311
313
  catch (error) {
@@ -318,6 +320,7 @@ export function registerCoreAndArtifactCommands(program, io) {
318
320
  .description('Append managed standards metadata to an existing CLAUDE.md without rewriting the body')
319
321
  .requiredOption('--project <path>', 'target project root')
320
322
  .option('--language <language>', 'standards language pack')
323
+ .option('--ide <id>', 'override IDE detection (e.g. claude-code, trae)')
321
324
  .option('--dry-run', 'preview writes without changing files')
322
325
  .option('--apply', 'append managed metadata to the target project')).action((options) => {
323
326
  if (options.dryRun === true && options.apply === true) {
@@ -326,7 +329,7 @@ export function registerCoreAndArtifactCommands(program, io) {
326
329
  return;
327
330
  }
328
331
  try {
329
- const result = executeProjectStandardsUpdate({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), apply: options.apply === true });
332
+ const result = executeProjectStandardsUpdateIdeAware({ projectRoot: options.project, ...(options.language !== undefined ? { language: options.language } : {}), ...(options.ide !== undefined ? { ideId: options.ide } : {}), apply: options.apply === true });
330
333
  const summary = summarizeProjectStandardsUpdateResult(result);
331
334
  const response = summary.reviewSuggestions.length > 0
332
335
  ? fail('standards.update', 'STANDARDS_UPDATE_REVIEW_REQUIRED', 'Standards update requires manual review', summary, summary.reviewSuggestions)
@@ -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 或 progress start)
10
+ * 4. dispatch 到内部 peaks 逻辑(目前:gate enforce)
11
11
  * 5. 用 IDE 期望的格式发回决策
12
12
  *
13
- * Slice #1 阶段:peaks hook handle 与 peaks gate enforce / peaks progress start
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 或 progress start)
33
+ * 4. dispatch 到内部 peaks 逻辑(目前:gate enforce)
34
34
  * 5. 用 IDE 期望的格式发回决策
35
35
  *
36
- * Slice #1 阶段:peaks hook handle 与 peaks gate enforce / peaks progress start
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 / progress start)');
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/progress logic, output IDE-formatted decision')
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, we only handle Bash and Task.
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
- // Slice #3: compute the per-IDE peaks hook entries for the CLI response
27
- // summary. Replaces the slice #1 PEAKS_HOOK_ENTRIES constant which was
28
- // hardcoded to claude-code values. Slice 2026-06-06-sub-agent-spawn-bug-
29
- // and-decouple: the sub-agent progress matcher now reads from
30
- // `adapter.subAgentToolMatcher` instead of being hardcoded to 'Task', so
31
- // every IDE self-reports its sub-agent tool name (claude-code: 'Task',
32
- // future adapters: whatever the adapter declares).
33
- function listInstalledEntriesForIde(ide) {
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
- // Default (claude-code) and any future registered adapters.
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 entries in the adapter's settings.json (default: .claude/settings.json for Claude, .trae/settings.json for Trae): (1) gate-enforce hook (SOP gate), (2) progress-start hook (auto-spawn sub-agent progress terminal). Both are installed / removed together. The IDE is auto-detected from env / cwd; override with --ide <id>.");
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 all peaks-managed hook entries into the adapter's settings.json. Idempotent: re-runs are no-ops. Project scope by default.`)
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')).action((options) => {
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 = listInstalledEntriesForIde(ide);
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
- const installedEntries = listInstalledEntriesForIde(ide);
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 all peaks-managed hook entries (gate-enforce + progress-start) from the target settings.json. Third-party hooks are preserved.")
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: listInstalledEntriesForIde(ide)
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')).action(async (options) => {
11
+ .requiredOption('--project <path>', 'target project root')
12
+ .option('--strict', 'ok follows the doctor aggregate (legacy semantics). Default: workspace-only (ok tracks the runbook health)', false)).action(async (options) => {
12
13
  try {
13
- const dashboard = await loadProjectDashboard({ projectRoot: options.project });
14
+ const dashboard = await loadProjectDashboard({
15
+ projectRoot: options.project,
16
+ okPolicy: options.strict === true ? 'strict' : 'workspace-only'
17
+ });
14
18
  if (!dashboard.runbookHealth.ok) {
15
19
  const suggestions = [
16
20
  dashboard.runbookHealth.missingRunbook.length > 0
@@ -29,8 +33,8 @@ export function registerProjectCommands(program, io) {
29
33
  process.exitCode = 1;
30
34
  return;
31
35
  }
32
- if (!dashboard.doctor.ok) {
33
- printResult(io, fail('project.dashboard', 'PROJECT_DASHBOARD_DOCTOR_FAILED', `Doctor reports ${dashboard.doctor.failed} failed check(s) (${dashboard.doctor.passed} passed)`, dashboard, ['Run `peaks doctor --json` and resolve the failing checks before re-running the dashboard']), options.json);
36
+ if (!dashboard.doctor.ok && options.strict === true) {
37
+ printResult(io, fail('project.dashboard', 'PROJECT_DASHBOARD_DOCTOR_STRICT_FAIL', `Doctor reports ${dashboard.doctor.failed} failed check(s) (${dashboard.doctor.passed} passed) — --strict mode requires the doctor aggregate to pass`, dashboard, ['Run `peaks doctor --json` and resolve the failing checks, or drop --strict to use the workspace-only policy']), options.json);
34
38
  process.exitCode = 1;
35
39
  return;
36
40
  }
@@ -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
- .option('--dry-run', 'preview writes without changing files', true)
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: `.peaks/${sessionId}` } : {};
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
- ' Task matcher: `peaks progress start` (auto-spawn sub-agent progress terminal)\n' +
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]: ');
@@ -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
- import { registerProgressCommands } from './commands/progress-commands.js';
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
- * - 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.
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): 3 top
26
- * roles + 3 sub-roles + arbitrary business subdivisions:
25
+ * specific role names. Empirically observed (peaks-qa SKILL.md):3 top
26
+ * roles +3 sub-roles + arbitrary business subdivisions:
27
27
  *
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 | ...
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
- * Short label used in envelope `ide` field and CLI help text.
68
- * e.g. "claude-code" / "trae" / "null".
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
- * 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
- */
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
- * 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
- */
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
- * - `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.
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
- * 1. The slice #008 `subAgentToolMatcher: 'Task'` install entry
99
- * stays byte-stable (the generated `.trae/settings.json` does
100
- * not change).
101
- * 2. The dispatcher's return shape is uniform across both
102
- * adapters a single byte-equality test can verify the
103
- * placeholder contract.
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
- * - 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.
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
- * - `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.
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
- * 1. The slice #008 `subAgentToolMatcher: 'Task'` install entry
48
- * stays byte-stable (the generated `.trae/settings.json` does
49
- * not change).
50
- * 2. The dispatcher's return shape is uniform across both
51
- * adapters a single byte-equality test can verify the
52
- * placeholder contract.
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