peaks-cli 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +6 -2
  2. package/dist/src/cli/commands/core-artifact-commands.js +6 -3
  3. package/dist/src/cli/commands/gate-commands.js +28 -19
  4. package/dist/src/cli/commands/hook-handle.d.ts +17 -0
  5. package/dist/src/cli/commands/hook-handle.js +111 -0
  6. package/dist/src/cli/commands/hooks-commands.js +72 -21
  7. package/dist/src/cli/commands/progress-commands.js +9 -2
  8. package/dist/src/cli/commands/progress-start-spawn.js +30 -4
  9. package/dist/src/cli/commands/project-commands.js +8 -4
  10. package/dist/src/cli/commands/statusline-commands.js +75 -17
  11. package/dist/src/cli/commands/sub-agent-commands.d.ts +5 -0
  12. package/dist/src/cli/commands/sub-agent-commands.js +488 -0
  13. package/dist/src/cli/commands/sub-agent-dispatch-guard.d.ts +55 -0
  14. package/dist/src/cli/commands/sub-agent-dispatch-guard.js +57 -0
  15. package/dist/src/cli/commands/workflow-commands.js +2 -1
  16. package/dist/src/cli/commands/workspace-commands.js +3 -0
  17. package/dist/src/cli/program.js +9 -0
  18. package/dist/src/hooks/pre-tool-use-sub-agent.d.ts +28 -0
  19. package/dist/src/hooks/pre-tool-use-sub-agent.js +105 -0
  20. package/dist/src/services/config/config-types.d.ts +1 -1
  21. package/dist/src/services/context/artifact-meta.d.ts +72 -0
  22. package/dist/src/services/context/artifact-meta.js +105 -0
  23. package/dist/src/services/context/context-guard.d.ts +49 -0
  24. package/dist/src/services/context/context-guard.js +91 -0
  25. package/dist/src/services/context/dispatch-context-guard.d.ts +27 -0
  26. package/dist/src/services/context/dispatch-context-guard.js +192 -0
  27. package/dist/src/services/context/headroom-client.d.ts +34 -0
  28. package/dist/src/services/context/headroom-client.js +117 -0
  29. package/dist/src/services/context/shared-channel.d.ts +92 -0
  30. package/dist/src/services/context/shared-channel.js +285 -0
  31. package/dist/src/services/context/threshold.d.ts +35 -0
  32. package/dist/src/services/context/threshold.js +76 -0
  33. package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
  34. package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
  35. package/dist/src/services/dispatch/batch-counter.d.ts +27 -0
  36. package/dist/src/services/dispatch/batch-counter.js +85 -0
  37. package/dist/src/services/dispatch/dispatch-record-writer.d.ts +93 -0
  38. package/dist/src/services/dispatch/dispatch-record-writer.js +261 -0
  39. package/dist/src/services/dispatch/heartbeat-truncator.d.ts +26 -0
  40. package/dist/src/services/dispatch/heartbeat-truncator.js +13 -0
  41. package/dist/src/services/dispatch/leak-detector.d.ts +11 -0
  42. package/dist/src/services/dispatch/leak-detector.js +72 -0
  43. package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +127 -0
  44. package/dist/src/services/dispatch/sub-agent-dispatcher.js +98 -0
  45. package/dist/src/services/ide/adapters/claude-code-adapter.d.ts +18 -0
  46. package/dist/src/services/ide/adapters/claude-code-adapter.js +80 -0
  47. package/dist/src/services/ide/adapters/trae-adapter.d.ts +42 -0
  48. package/dist/src/services/ide/adapters/trae-adapter.js +98 -0
  49. package/dist/src/services/ide/hook-protocol.d.ts +47 -0
  50. package/dist/src/services/ide/hook-protocol.js +74 -0
  51. package/dist/src/services/ide/hook-translator.d.ts +72 -0
  52. package/dist/src/services/ide/hook-translator.js +128 -0
  53. package/dist/src/services/ide/ide-detector.d.ts +10 -0
  54. package/dist/src/services/ide/ide-detector.js +19 -0
  55. package/dist/src/services/ide/ide-registry.d.ts +14 -0
  56. package/dist/src/services/ide/ide-registry.js +45 -0
  57. package/dist/src/services/ide/ide-types.d.ts +180 -0
  58. package/dist/src/services/ide/ide-types.js +2 -0
  59. package/dist/src/services/ide/resource-profile.d.ts +52 -0
  60. package/dist/src/services/ide/resource-profile.js +33 -0
  61. package/dist/src/services/ide/shared/atomic-json.d.ts +15 -0
  62. package/dist/src/services/ide/shared/atomic-json.js +58 -0
  63. package/dist/src/services/ide/shared/safe-path.d.ts +11 -0
  64. package/dist/src/services/ide/shared/safe-path.js +29 -0
  65. package/dist/src/services/memory/project-context-service.js +2 -1
  66. package/dist/src/services/memory/project-memory-service.js +4 -3
  67. package/dist/src/services/perf/perf-baseline-service.js +2 -1
  68. package/dist/src/services/progress/progress-service.d.ts +1 -1
  69. package/dist/src/services/progress/progress-service.js +18 -14
  70. package/dist/src/services/security/safe-settings-path.d.ts +12 -0
  71. package/dist/src/services/security/safe-settings-path.js +104 -0
  72. package/dist/src/services/session/getSessionDir.d.ts +1 -0
  73. package/dist/src/services/session/getSessionDir.js +27 -0
  74. package/dist/src/services/session/index.d.ts +1 -0
  75. package/dist/src/services/session/index.js +1 -0
  76. package/dist/src/services/signal/cancel-handler.d.ts +14 -0
  77. package/dist/src/services/signal/cancel-handler.js +76 -0
  78. package/dist/src/services/skill/resume-detector.d.ts +54 -0
  79. package/dist/src/services/skill/resume-detector.js +334 -0
  80. package/dist/src/services/skill/skill-scheduler.d.ts +40 -0
  81. package/dist/src/services/skill/skill-scheduler.js +53 -0
  82. package/dist/src/services/skills/hooks-settings-service.d.ts +47 -29
  83. package/dist/src/services/skills/hooks-settings-service.js +190 -144
  84. package/dist/src/services/skills/statusline-settings-service.d.ts +33 -6
  85. package/dist/src/services/skills/statusline-settings-service.js +31 -34
  86. package/dist/src/services/slice/slice-archive-service.d.ts +20 -0
  87. package/dist/src/services/slice/slice-archive-service.js +111 -0
  88. package/dist/src/services/solo/batch-heartbeat-poller.d.ts +51 -0
  89. package/dist/src/services/solo/batch-heartbeat-poller.js +88 -0
  90. package/dist/src/services/solo/status-line-renderer.d.ts +34 -0
  91. package/dist/src/services/solo/status-line-renderer.js +55 -0
  92. package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
  93. package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
  94. package/dist/src/services/standards/project-standards-service.d.ts +1 -2
  95. package/dist/src/services/workspace/reconcile-service.d.ts +36 -0
  96. package/dist/src/services/workspace/reconcile-service.js +107 -6
  97. package/dist/src/services/workspace/reconcile-types.d.ts +12 -0
  98. package/dist/src/shared/version.d.ts +1 -1
  99. package/dist/src/shared/version.js +1 -1
  100. package/package.json +2 -1
  101. package/scripts/install-skills.mjs +112 -2
  102. package/skills/peaks-ide/SKILL.md +159 -0
  103. package/skills/peaks-ide/references/audit-log-helper.md +52 -0
  104. package/skills/peaks-qa/SKILL.md +153 -55
  105. package/skills/peaks-qa/references/qa-fanout-contract.md +150 -0
  106. package/skills/peaks-rd/SKILL.md +134 -62
  107. package/skills/peaks-solo/SKILL.md +124 -37
  108. package/skills/peaks-solo/references/browser-workflow.md +22 -20
  109. package/skills/peaks-solo/references/context-governance.md +144 -0
  110. package/skills/peaks-solo/references/headroom-integration.md +107 -0
  111. package/skills/peaks-solo/references/runbook.md +3 -3
  112. package/skills/peaks-solo/references/sub-agent-dispatch.md +261 -0
  113. package/skills/peaks-solo/references/swarm-dispatch-contract.md +3 -37
  114. package/skills/peaks-txt/SKILL.md +17 -0
  115. package/skills/peaks-ui/SKILL.md +45 -10
@@ -144,6 +144,42 @@ export declare function migrateOldRuntimeState(projectRoot: string): {
144
144
  message: string;
145
145
  }>;
146
146
  };
147
+ /**
148
+ * One-time sub-agent state migration (slice 2026-06-06-sub-agent-spawn-bug-and-decouple).
149
+ *
150
+ * Move the legacy per-session sub-agent state files at:
151
+ * - `.peaks/<sid>/system/subagent-progress.json`
152
+ * - `.peaks/<sid>/system/progress-spawn.json`
153
+ * into the new canonical home at:
154
+ * - `.peaks/_sub_agents/<sid>/subagent-progress.json`
155
+ * - `.peaks/_sub_agents/<sid>/progress-spawn.json`
156
+ *
157
+ * Behavior:
158
+ * - Idempotent: re-running on a tree that is already on the new layout
159
+ * produces `migratedFiles: []`.
160
+ * - Best-effort: uses `fs.renameSync` and falls back to `copyFileSync +
161
+ * unlinkSync` if rename throws (e.g. cross-device move on Windows).
162
+ * - Empty `<sid>/system/` dir removal (R-2 guard): the legacy `system/`
163
+ * subdir is only removed when it has zero other files, so a tree where
164
+ * the user had unrelated content in `system/` is left untouched.
165
+ * - New-path-wins: when both old and new files exist, the old file is
166
+ * removed (the new path is authoritative).
167
+ *
168
+ * Walks every discovered session — not just the canonical one — so a user
169
+ * with 6 pre-migration sessions gets all of them migrated in one reconcile
170
+ * pass.
171
+ *
172
+ * @returns `{ migratedFiles, errors }`. `migratedFiles` lists the *old*
173
+ * relative paths (e.g. `.peaks/<sid>/system/subagent-progress.json`) that
174
+ * were successfully moved. `errors` lists per-file failures.
175
+ */
176
+ export declare function migrateSubAgentState(projectRoot: string): {
177
+ migratedFiles: string[];
178
+ errors: Array<{
179
+ path: string;
180
+ message: string;
181
+ }>;
182
+ };
147
183
  /**
148
184
  * Top-level orchestrator. Wires migration (added in slice
149
185
  * 2026-06-05-peaks-runtime-layer), discovery, canonical pick, re-point,
@@ -16,11 +16,20 @@
16
16
  * Pure hand-rolled; uses only node:fs, node:path, and the existing
17
17
  * session-manager helper for writing the binding. No new dependencies.
18
18
  */
19
- import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, unlinkSync } from 'node:fs';
19
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync } from 'node:fs';
20
20
  import { dirname, join, resolve } from 'node:path';
21
21
  import { getSessionIdCanonical, setCurrentSessionBinding } from '../session/session-manager.js';
22
22
  const SESSION_ID_PATTERN = /^\d{4}-\d{2}-\d{2}-session-[a-f0-9]+$/;
23
23
  const META_FILE = 'session.json';
24
+ // Sub-agent state file basenames (slice 2026-06-06-sub-agent-spawn-bug-and-decouple).
25
+ // The legacy location was `.peaks/<sid>/system/<filename>`; the canonical new
26
+ // location is `.peaks/_sub_agents/<sid>/<filename>`. `migrateSubAgentState`
27
+ // moves the two files between these homes on every `reconcileWorkspace` run.
28
+ const SUB_AGENT_MIGRATION_FILES = [
29
+ 'subagent-progress.json',
30
+ 'progress-spawn.json'
31
+ ];
32
+ const SUB_AGENTS_DIR = '_sub_agents';
24
33
  // As of slice 2026-06-05-peaks-runtime-layer these old paths are the
25
34
  // back-compat read-only fallbacks; the canonical new home is
26
35
  // `.peaks/_runtime/`. `migrateOldRuntimeState` moves them to the new
@@ -488,6 +497,89 @@ function copyDirRecursiveSync(src, dest) {
488
497
  }
489
498
  }
490
499
  }
500
+ /**
501
+ * One-time sub-agent state migration (slice 2026-06-06-sub-agent-spawn-bug-and-decouple).
502
+ *
503
+ * Move the legacy per-session sub-agent state files at:
504
+ * - `.peaks/<sid>/system/subagent-progress.json`
505
+ * - `.peaks/<sid>/system/progress-spawn.json`
506
+ * into the new canonical home at:
507
+ * - `.peaks/_sub_agents/<sid>/subagent-progress.json`
508
+ * - `.peaks/_sub_agents/<sid>/progress-spawn.json`
509
+ *
510
+ * Behavior:
511
+ * - Idempotent: re-running on a tree that is already on the new layout
512
+ * produces `migratedFiles: []`.
513
+ * - Best-effort: uses `fs.renameSync` and falls back to `copyFileSync +
514
+ * unlinkSync` if rename throws (e.g. cross-device move on Windows).
515
+ * - Empty `<sid>/system/` dir removal (R-2 guard): the legacy `system/`
516
+ * subdir is only removed when it has zero other files, so a tree where
517
+ * the user had unrelated content in `system/` is left untouched.
518
+ * - New-path-wins: when both old and new files exist, the old file is
519
+ * removed (the new path is authoritative).
520
+ *
521
+ * Walks every discovered session — not just the canonical one — so a user
522
+ * with 6 pre-migration sessions gets all of them migrated in one reconcile
523
+ * pass.
524
+ *
525
+ * @returns `{ migratedFiles, errors }`. `migratedFiles` lists the *old*
526
+ * relative paths (e.g. `.peaks/<sid>/system/subagent-progress.json`) that
527
+ * were successfully moved. `errors` lists per-file failures.
528
+ */
529
+ export function migrateSubAgentState(projectRoot) {
530
+ const root = resolve(projectRoot);
531
+ const newDir = join(root, '.peaks', SUB_AGENTS_DIR);
532
+ const migratedFiles = [];
533
+ const errors = [];
534
+ for (const session of discoverSessions(projectRoot)) {
535
+ const oldSystemDir = join(session.path, 'system');
536
+ if (!existsSync(oldSystemDir))
537
+ continue;
538
+ const newSessionDir = join(newDir, session.sessionId);
539
+ mkdirSync(newSessionDir, { recursive: true });
540
+ for (const fname of SUB_AGENT_MIGRATION_FILES) {
541
+ const oldPath = join(oldSystemDir, fname);
542
+ const newPath = join(newSessionDir, fname);
543
+ if (!existsSync(oldPath))
544
+ continue;
545
+ if (existsSync(newPath)) {
546
+ // New path is authoritative; remove stale old file.
547
+ try {
548
+ rmSync(oldPath, { force: true });
549
+ }
550
+ catch { /* best effort */ }
551
+ continue;
552
+ }
553
+ try {
554
+ try {
555
+ renameSync(oldPath, newPath);
556
+ }
557
+ catch (renameError) {
558
+ // Cross-device or locked-file fallback: copy + unlink.
559
+ copyFileSync(oldPath, newPath);
560
+ unlinkSync(oldPath);
561
+ }
562
+ migratedFiles.push(join('.peaks', session.sessionId, 'system', fname));
563
+ }
564
+ catch (error) {
565
+ errors.push({
566
+ path: oldPath,
567
+ message: error instanceof Error ? error.message : String(error)
568
+ });
569
+ }
570
+ }
571
+ // R-2 guard: only remove the legacy system/ dir when it has zero
572
+ // remaining files (the user might have unrelated content there).
573
+ try {
574
+ const remaining = readdirSync(oldSystemDir);
575
+ if (remaining.length === 0) {
576
+ rmdirSync(oldSystemDir);
577
+ }
578
+ }
579
+ catch { /* best effort */ }
580
+ }
581
+ return { migratedFiles, errors };
582
+ }
491
583
  /**
492
584
  * Top-level orchestrator. Wires migration (added in slice
493
585
  * 2026-06-05-peaks-runtime-layer), discovery, canonical pick, re-point,
@@ -503,11 +595,19 @@ export function reconcileWorkspace(options) {
503
595
  // before that read means the new path is the only path observed by
504
596
  // `getSessionIdCanonical` after this call returns.
505
597
  const migration = migrateOldRuntimeState(projectRoot);
506
- const migrateErrors = migration.errors.map((e) => ({
507
- kind: 'migrate',
508
- path: e.path,
509
- message: e.message
510
- }));
598
+ const subAgentMigration = migrateSubAgentState(projectRoot);
599
+ const migrateErrors = [
600
+ ...migration.errors.map((e) => ({
601
+ kind: 'migrate',
602
+ path: e.path,
603
+ message: e.message
604
+ })),
605
+ ...subAgentMigration.errors.map((e) => ({
606
+ kind: 'migrate',
607
+ path: e.path,
608
+ message: e.message
609
+ }))
610
+ ];
511
611
  const sessions = discoverSessions(projectRoot);
512
612
  const activeSkillSessionId = readActiveSkillSessionId(projectRoot);
513
613
  const canonical = pickCanonicalSession(sessions, activeSkillSessionId);
@@ -575,6 +675,7 @@ export function reconcileWorkspace(options) {
575
675
  apply,
576
676
  repointed,
577
677
  migratedFiles: migration.migratedFiles,
678
+ subAgentStateMigrated: subAgentMigration.migratedFiles.length,
578
679
  errors: [...migrateErrors, ...deletionResult.errors],
579
680
  changeMarker,
580
681
  systemCleaned
@@ -65,6 +65,18 @@ export type ReconcileResult = {
65
65
  * consumers can ignore this field.
66
66
  */
67
67
  migratedFiles: string[];
68
+ /**
69
+ * Count of legacy per-session sub-agent state files moved from
70
+ * `.peaks/<sid>/system/{subagent-progress,progress-spawn}.json` into
71
+ * `.peaks/_sub_agents/<sid>/` during this reconcile run.
72
+ *
73
+ * Added in slice 2026-06-06-sub-agent-spawn-bug-and-decouple. The
74
+ * detailed list of moved files is not surfaced here (the count is
75
+ * what the CLI summary and QA test assert on); the underlying
76
+ * `migrateSubAgentState` helper returns the full path list for
77
+ * forensics. Additive — older consumers can ignore this field.
78
+ */
79
+ subAgentStateMigrated: number;
68
80
  /**
69
81
  * Errors encountered during the migration step. Each entry has a
70
82
  * `kind: 'migrate'` discriminator so consumers can tell migration
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "1.3.2";
1
+ export declare const CLI_VERSION = "1.3.4";
@@ -1 +1 @@
1
- export const CLI_VERSION = "1.3.2";
1
+ export const CLI_VERSION = "1.3.4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Cross-AI-IDE workflow-gating CLI + skill family (Claude Code shipped, Trae in progress; Codex / Cursor / Qoder / Tongyi Lingma on the roadmap).",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",
@@ -56,6 +56,7 @@
56
56
  "@colbymchenry/codegraph": "0.7.10",
57
57
  "chalk": "^5.6.2",
58
58
  "commander": "^12.1.0",
59
+ "headroom-ai": "0.22.4",
59
60
  "ora": "^8.2.0",
60
61
  "shadcn": "4.7.0",
61
62
  "terminal-kit": "^3.1.2"
@@ -363,6 +363,78 @@ function resolveProjectRoot(options) {
363
363
  return projectRoot ? resolve(projectRoot) : null;
364
364
  }
365
365
 
366
+ /**
367
+ * Slice #011: Detect the installed IDE for the postinstall dispatch layer.
368
+ *
369
+ * Mirrors `src/services/ide/ide-detector.ts:detectInstalledIde` (cwd heuristic)
370
+ * but inlined here because `install-skills.mjs` is a plain `.mjs` script and
371
+ * cannot import the TS service at runtime. The dispatch:
372
+ * - Look for `.claude`, `.trae`, `.codex`, `.cursor`, `.qoder`,
373
+ * `.tongyi-lingma` in the project root in that insertion order
374
+ * (matches the registry's adapter order in `src/services/ide/ide-registry.ts`).
375
+ * - Returns the first match, or `null` if no adapter's directory is present.
376
+ *
377
+ * If the resolved IDE has no `skillInstall` declared (Trae in slice 1.3.2),
378
+ * the caller falls back to the legacy `~/.claude/{skills,output-styles}` path
379
+ * + emits a stderr warning. The dispatch is conservative: the env-var
380
+ * overrides `PEAKS_CLAUDE_SKILLS_DIR` / `PEAKS_CLAUDE_OUTPUT_STYLES_DIR`
381
+ * continue to work, and the legacy default is preserved.
382
+ */
383
+ const IDE_DETECTION_DIRS = [
384
+ { id: 'claude-code', dir: '.claude' },
385
+ { id: 'trae', dir: '.trae' },
386
+ { id: 'codex', dir: '.codex' },
387
+ { id: 'cursor', dir: '.cursor' },
388
+ { id: 'qoder', dir: '.qoder' },
389
+ { id: 'tongyi-lingma', dir: '.tongyi-lingma' },
390
+ ];
391
+
392
+ const IDE_SKILL_INSTALL_PROFILES = {
393
+ 'claude-code': {
394
+ skillsDir: join(homedir(), '.claude', 'skills'),
395
+ outputStylesDir: join(homedir(), '.claude', 'output-styles'),
396
+ envVar: 'PEAKS_CLAUDE_SKILLS_DIR',
397
+ outputStylesEnvVar: 'PEAKS_CLAUDE_OUTPUT_STYLES_DIR',
398
+ },
399
+ 'trae': null,
400
+ 'codex': null,
401
+ 'cursor': null,
402
+ 'qoder': null,
403
+ 'tongyi-lingma': null,
404
+ };
405
+
406
+ function detectInstalledIdeId(projectRoot) {
407
+ if (!projectRoot) return null;
408
+ for (const { id, dir } of IDE_DETECTION_DIRS) {
409
+ if (existsSync(join(projectRoot, dir))) {
410
+ return id;
411
+ }
412
+ }
413
+ return null;
414
+ }
415
+
416
+ function resolveIdeSkillInstallProfile(ideId) {
417
+ if (ideId === null) return null;
418
+ return IDE_SKILL_INSTALL_PROFILES[ideId] ?? null;
419
+ }
420
+
421
+ function warnUnverifiedIde(ideId, projectRoot) {
422
+ process.stderr.write(
423
+ `peaks install-skills: IDE '${ideId}' has no skillInstall profile declared; ` +
424
+ `falling back to the legacy Claude Code path (~/.claude/skills + ~/.claude/output-styles) ` +
425
+ `for project '${projectRoot}'. This is a slice #011 follow-up gap; ` +
426
+ `see .peaks/memory/ide-adapter-resource-profile-framework.md.\n`
427
+ );
428
+ }
429
+
430
+ function warnNoIdeDetected(projectRoot) {
431
+ process.stderr.write(
432
+ `peaks install-skills: no IDE detected in '${projectRoot ?? '(project root unknown)'}'; ` +
433
+ `installing to the legacy Claude Code path (~/.claude/skills + ~/.claude/output-styles). ` +
434
+ `Set PEAKS_CLAUDE_SKILLS_DIR / PEAKS_CLAUDE_OUTPUT_STYLES_DIR to override.\n`
435
+ );
436
+ }
437
+
366
438
  function writeMergedConfig(configPath, label, defaults, writeConfig) {
367
439
  const existing = readConfigFile(configPath, label);
368
440
  const next = { ...(existing === null ? defaults : mergeMissingConfigValues(existing, defaults)), version: defaults.version };
@@ -424,12 +496,37 @@ export function installProjectConfig(options = {}) {
424
496
  export function installBundledSkills(options = {}) {
425
497
  const packageRoot = resolvePackageRoot(options);
426
498
  const skillsRoot = join(packageRoot, 'skills');
427
- const targetRoot = resolve(options.targetRoot ?? process.env.PEAKS_CLAUDE_SKILLS_DIR ?? join(homedir(), '.claude', 'skills'));
428
499
 
429
500
  if (process.env.PEAKS_SKIP_SKILL_INSTALL === '1' || !existsSync(skillsRoot)) {
430
501
  return createInstallResult();
431
502
  }
432
503
 
504
+ // Slice #011: IDE-aware dispatch. Precedence (highest first):
505
+ // 1. explicit options.targetRoot (test / hook override)
506
+ // 2. options.ideId skillInstall.skillsDir (per-IDE dispatch)
507
+ // 3. PEAKS_CLAUDE_SKILLS_DIR env var (legacy back-compat)
508
+ // 4. detected IDE's skillInstall.skillsDir (auto-detect from projectRoot)
509
+ // 5. legacy default (~/.claude/skills) (no-IDE fallback)
510
+ const projectRoot = resolveProjectRoot(options);
511
+ const detectedIdeId = detectInstalledIdeId(projectRoot);
512
+ const detectedProfile = resolveIdeSkillInstallProfile(detectedIdeId);
513
+
514
+ if (options.targetRoot === undefined && options.ideId === undefined && detectedProfile === null && detectedIdeId !== null) {
515
+ warnUnverifiedIde(detectedIdeId, projectRoot ?? '(project root unknown)');
516
+ }
517
+ if (options.targetRoot === undefined && options.ideId === undefined && detectedIdeId === null && projectRoot !== null) {
518
+ warnNoIdeDetected(projectRoot);
519
+ }
520
+
521
+ const profileSkillsDir = detectedProfile?.skillsDir ?? null;
522
+ const targetRoot = resolve(
523
+ options.targetRoot
524
+ ?? (options.ideId !== undefined ? resolveIdeSkillInstallProfile(options.ideId)?.skillsDir ?? null : null)
525
+ ?? process.env.PEAKS_CLAUDE_SKILLS_DIR
526
+ ?? profileSkillsDir
527
+ ?? join(homedir(), '.claude', 'skills')
528
+ );
529
+
433
530
  const installed = [];
434
531
  const skipped = [];
435
532
  mkdirSync(targetRoot, { recursive: true });
@@ -485,12 +582,25 @@ export function installBundledSkills(options = {}) {
485
582
  export function installBundledOutputStyles(options = {}) {
486
583
  const packageRoot = resolvePackageRoot(options);
487
584
  const outputStylesRoot = join(packageRoot, 'output-styles');
488
- const targetRoot = resolve(options.targetRoot ?? process.env.PEAKS_CLAUDE_OUTPUT_STYLES_DIR ?? join(homedir(), '.claude', 'output-styles'));
489
585
 
490
586
  if (process.env.PEAKS_SKIP_SKILL_INSTALL === '1' || !existsSync(outputStylesRoot)) {
491
587
  return createInstallResult();
492
588
  }
493
589
 
590
+ // Slice #011: IDE-aware dispatch. Same precedence as installBundledSkills.
591
+ const projectRoot = resolveProjectRoot(options);
592
+ const detectedIdeId = detectInstalledIdeId(projectRoot);
593
+ const detectedProfile = resolveIdeSkillInstallProfile(detectedIdeId);
594
+
595
+ const profileOutputStylesDir = detectedProfile?.outputStylesDir ?? null;
596
+ const targetRoot = resolve(
597
+ options.targetRoot
598
+ ?? (options.ideId !== undefined ? resolveIdeSkillInstallProfile(options.ideId)?.outputStylesDir ?? null : null)
599
+ ?? process.env.PEAKS_CLAUDE_OUTPUT_STYLES_DIR
600
+ ?? profileOutputStylesDir
601
+ ?? join(homedir(), '.claude', 'output-styles')
602
+ );
603
+
494
604
  const installed = [];
495
605
  const skipped = [];
496
606
  mkdirSync(targetRoot, { recursive: true });
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: peaks-ide
3
+ description: Orchestrate peaks-cli's IDE-aware behavior (hooks + statusline + handle) for a user's specific IDE. Detects the current state (which IDE the user is on, what peaks has already installed), plans the install / switch / status / uninstall actions, and invokes the existing peaks CLI primitives. Triggers on `/peaks-ide`, "set up peaks for my IDE", "switch peaks to Trae", "what did peaks install", "uninstall peaks hooks". Sits between the user and `peaks hooks install` / `peaks statusline install` / `peaks hook handle` — those are the CLI primitives; this skill is the user-facing surface.
4
+ ---
5
+
6
+ # Peaks-Cli IDE Setup (peaks-ide)
7
+
8
+ `peaks-ide` is the **user-facing surface** for everything peaks-cli does that's IDE-aware. It does NOT introduce new CLI commands. It orchestrates the existing CLI primitives — `peaks hooks install`, `peaks statusline install`, `peaks hook handle` — based on the user's intent and the IDE the user is on.
9
+
10
+ **Why this exists (dev-preference red line):** "skill is primary, CLI is auxiliary." The behavior that only an LLM in a skill prompt would use ("detect which IDE the user is on", "plan the migration steps", "ask the user before destructive actions") lives in this SKILL.md, not in a new `peaks <cmd>`. The CLI commands stay as atomic primitives the skill composes.
11
+
12
+ **Slice #2 scope:** the first version supports Trae (alongside Claude Code, the only other adapter in slice #2's registry). Cursor / Codex / Qoder / Tongyi Lingma will land in slice #3+ — the skill will pick them up automatically because the underlying auto-detect (the `peaks project dashboard --json` flow internally calls `listAdapterIds()` via the `IdeRegistry`) registers them as the registry grows.
13
+
14
+ ## Skill presence (MANDATORY first action)
15
+
16
+ ```bash
17
+ peaks skill presence:set peaks-ide --project <repo> --mode <mode> --gate startup
18
+ peaks project memories --project <repo> --json # load durable memory
19
+ ```
20
+
21
+ The presence marker tells the global peaks status line that peaks-ide is orchestrating. The memory read pulls forward the slice #1 contract: `peaks <cmd> --project <path>` is the canonical project-root source; `process.env[adapter.envVar]` (e.g. `CLAUDE_PROJECT_DIR`, `TRAE_PROJECT_DIR`) is the env-var override for auto-detect.
22
+
23
+ ## Step 1: detect current state
24
+
25
+ The skill's first move is to **read**, not to ask. Build a complete picture of the user's IDE environment before any AskUserQuestion.
26
+
27
+ ```bash
28
+ # 1. What adapters are registered? (slice #2 ships with claude-code + trae)
29
+ peaks project dashboard --project <repo> --json
30
+
31
+ # 2. Is the user on Claude Code, Trae, or something else?
32
+ # Check the cwd for adapter-specific directories:
33
+ ls -la <repo>/.claude 2>/dev/null && echo "claude-code detected"
34
+ ls -la <repo>/.trae 2>/dev/null && echo "trae detected"
35
+
36
+ # 3. Are peaks hooks already installed? For each candidate, read settings.json:
37
+ # claude: <root>/.claude/settings.json
38
+ # trae: <root>/.trae/settings.json
39
+ peaks hooks status --project <repo> --json 2>/dev/null
40
+ peaks statusline status --project <repo> --json 2>/dev/null
41
+ ```
42
+
43
+ The skill composes a 1-2 sentence **state summary** before asking anything:
44
+
45
+ > "I see you're on **Trae** (`.trae/` exists in the project root). peaks hooks are **not installed** yet. peaks statusline is **not installed** either. The `peaks hook handle` runtime is available as a CLI primitive — you don't need to install it separately; what needs installing is the settings.json entries that point to it."
46
+
47
+ The user gets a complete picture without having to answer any question. The next step only fires AskUserQuestion if intent is genuinely ambiguous.
48
+
49
+ ## Step 2: AskUserQuestion (only if intent is ambiguous)
50
+
51
+ If the user typed `/peaks-ide` without context, the skill's intent is genuinely ambiguous. The four canonical intents are:
52
+
53
+ | Option | What it does |
54
+ |---|---|
55
+ | First-time install | Run `peaks hooks install` + `peaks statusline install` for the detected IDE |
56
+ | Switch to a different IDE | Detect current install, uninstall from old IDE, install on new IDE (e.g. Claude → Trae) |
57
+ | Show current status | Just print the state summary from Step 1; no side effects |
58
+ | Uninstall peaks hooks | Run `peaks hooks uninstall` + `peaks statusline uninstall` for the detected IDE |
59
+
60
+ **Default option is "Show current status"** — the cheapest action, and the user gets useful output even if they didn't know what they wanted. AskUserQuestion is the ONLY place this skill asks anything. All destructive paths (Switch, Uninstall) gate on user confirmation here.
61
+
62
+ ## Step 3: plan & preview (dry-run)
63
+
64
+ Before any side effect, the skill prints the exact CLI invocations it will run. This is the "see before you leap" step.
65
+
66
+ ```
67
+ Plan: First-time install for Trae (.trae/ detected in project root)
68
+
69
+ 1. peaks hooks install --project <repo>
70
+ → writes a beforeToolCall hook entry to <root>/.trae/settings.json
71
+ that points to `peaks hook handle --project "${TRAE_PROJECT_DIR}"`
72
+
73
+ 2. peaks statusline install --project <repo>
74
+ → writes a statusLine field with command `peaks statusline`
75
+
76
+ 3. (no third command — `peaks hook handle` is the runtime; once the
77
+ settings.json entries point to it, the user's Trae will invoke it
78
+ on every PreToolUse event)
79
+
80
+ Verification: after install, re-run `peaks hooks status --project <repo>`
81
+ to confirm the entries are present.
82
+
83
+ Proceed? (Y/n)
84
+ ```
85
+
86
+ The plan MUST be human-readable. Don't run a side effect until the user confirms.
87
+
88
+ ## Step 4: execute
89
+
90
+ For each step in the plan, invoke the CLI primitive. The skill does NOT call out to a hidden script — it runs the actual peaks CLI commands, so the user sees the same output they'd see typing the command themselves.
91
+
92
+ ```bash
93
+ # Example: first-time Trae install
94
+ peaks hooks install --project <repo>
95
+ peaks statusline install --project <repo>
96
+
97
+ # After each command, check the exit code. If non-zero, STOP and report
98
+ # the failure to the user. The skill does NOT auto-rollback; the user
99
+ # decides what to do next.
100
+ ```
101
+
102
+ For destructive paths (Switch, Uninstall), the skill uses a **transactional pattern**: it captures the current state, runs the destructive CLI, then runs the install CLI; if any step fails, it reports the partial state and lets the user decide.
103
+
104
+ ## Step 5: audit log
105
+
106
+ Every successful execution writes one JSON line to `.peaks/_runtime/<sid>/ide-onboard.log`:
107
+
108
+ ```json
109
+ {"timestamp":"2026-06-06T19:55:00Z","intent":"first-time-install","detected_ide":"trae","cli_invocations":["peaks hooks install --project <repo>","peaks statusline install --project <repo>"],"outcome":"success","session_id":"2026-06-06-session-22f08c"}
110
+ ```
111
+
112
+ The audit log is **machine-readable** (so `peaks project dashboard` can read it and surface "you installed peaks for Trae on 2026-06-06") and **human-readable** (so the user can `cat` it to see the install history). The skill does NOT write the log file itself — it delegates to `peaks project dashboard` (the canonical log writer, per the dev-preference red line "skill-first for workflow, CLI-backed for gates / side effects"). The audit log writer is a CLI primitive, not a per-skill helper; the skill body MUST NOT introduce a new `peaks <cmd>` for the log writer. The thin helper that writes the log is `scripts/peaks-ide-audit-log.mjs`; see [audit-log-helper.md](references/audit-log-helper.md) for its CLI surface and contract.
113
+
114
+ ## Boundaries
115
+
116
+ `peaks-ide` may:
117
+
118
+ - detect the current IDE (cwd + env var + settings.json)
119
+ - ask the user via AskUserQuestion (Step 2 only)
120
+ - preview the CLI invocations (Step 3)
121
+ - execute existing peaks CLI commands (Step 4)
122
+ - write a single line to the audit log (Step 5)
123
+
124
+ `peaks-ide` must NOT:
125
+
126
+ - introduce new `peaks <cmd>` CLI commands (dev-preference red line: "Default-no on new CLI commands")
127
+ - bypass the user's confirmation on destructive paths (Switch / Uninstall)
128
+ - write the settings.json directly (the CLI primitives own that)
129
+ - run other peaks skills (peaks-solo, peaks-qa, etc.) — those are separate skills with their own scopes
130
+ - handle multi-IDE scenarios in slice #2 (e.g. "I use Claude at work and Trae at home" — the registry is single-IDE per session; a future slice could add multi-IDE)
131
+
132
+ ## Reference: the CLI primitives the skill composes
133
+
134
+ - `peaks hooks install` / `peaks hooks uninstall` / `peaks hooks status` — adapter-driven; auto-detects IDE from env / stdin / cwd
135
+ - `peaks statusline install` / `peaks statusline uninstall` / `peaks statusline status` — same
136
+ - `peaks hook handle` — the runtime handler; not installed, just invoked
137
+ - `peaks project dashboard --json` — surfaces the current state summary
138
+ - `peaks skill runbook` — surfaces the peaks-ide skill body for inspection
139
+
140
+ ## Next-step references
141
+
142
+ - The slice #1 RD artifact at `.peaks/_runtime/<sid>/rd/requests/002-2026-06-06-peaks-ide-skeleton.md` documents the slim `IdeAdapter` shape that the skill is built on.
143
+ - The slice #2 PRD at `.peaks/_runtime/<sid>/prd/requests/002-2026-06-06-trae-adapter-and-peaks-ide-skill.md` documents the 13 ACs this skill is part of.
144
+ - The slice #2 RD artifact (in flight) documents the implementation contract.
145
+
146
+ ## Default runbook
147
+
148
+ The skill is invoked as `/peaks-ide` or via the natural-language triggers listed in the frontmatter. The runbook is the body of this SKILL.md (steps 1-5 plus the boundaries and reference sections above); the runbook-service extracts the section between this `## Default runbook` heading and the next `##` heading.
149
+
150
+ When the user types `/peaks-ide` (or "set up peaks for my IDE" / "switch peaks to Trae" / "what did peaks install" / "uninstall peaks hooks"), execute Steps 1 → 5 in order:
151
+
152
+ 1. **Skill presence (MANDATORY first action)**: `peaks skill presence:set peaks-ide --project <repo> --gate startup` and `peaks project memories --project <repo> --json` (load durable memory).
153
+ 2. **Detect current state** (Step 1 above): `peaks project dashboard --project <repo> --json` + `peaks hooks status --project <repo> --json` + `peaks statusline status --project <repo> --json` + `ls -la <repo>/.claude 2>/dev/null` + `ls -la <repo>/.trae 2>/dev/null`. Build a 1-2 sentence state summary.
154
+ 3. **AskUserQuestion** (Step 2 above, only if intent is ambiguous). Default option: "Show current status". Destructive paths (Switch, Uninstall) gate on user confirmation here.
155
+ 4. **Plan & preview** (Step 3 above): print the exact `peaks hooks install` / `peaks statusline install` invocations before running them. Wait for "Proceed? (Y/n)".
156
+ 5. **Execute** (Step 4 above): run the `peaks hooks install` / `peaks statusline install` (or uninstall / status) commands. Stop on non-zero exit. Do not auto-rollback.
157
+ 6. **Audit log** (Step 5 above): delegate the JSONL write to `peaks project dashboard` (the canonical log writer; per the dev-preference red line, the skill MUST NOT introduce a new CLI primitive for the log writer).
158
+
159
+ CLI primitives the skill composes (per the "Reference" section above): `peaks skill presence:set`, `peaks project memories`, `peaks project dashboard`, `peaks hooks install` / `uninstall` / `status`, `peaks statusline install` / `uninstall` / `status`, `peaks hook handle`, `peaks skill runbook`. The skill does NOT introduce any new `peaks <cmd>` command.
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: peaks-ide-audit-log-helper
3
+ description: Thin Node helper that writes a single JSONL line to `.peaks/audit/peaks-ide-<UTC-date>.log`. Reachable from the peaks-ide SKILL.md Step 5 escape hatch. NOT a `peaks <cmd>` CLI primitive (slice #2 closeout + dev-preference red line).
4
+ ---
5
+
6
+ # peaks-ide audit log helper
7
+
8
+ The peaks-ide skill's Step 5 (`audit log`) delegates the JSONL write to
9
+ `scripts/peaks-ide-audit-log.mjs`. The helper is a thin Node script, not a
10
+ new `peaks <cmd>`, and is the single source of truth for the audit log
11
+ shape (machine + human readable, one JSON object per line).
12
+
13
+ ## Why a thin helper, not a CLI primitive
14
+
15
+ Per `.peaks/memory/peaks-ide-skill-ac-10-audit-log-writer-is-a-thin-helper-not-a-separate-cli-primitive.md`
16
+ (slice #2 closeout), the audit log writer is reachable from the skill's
17
+ Step 5 escape hatch but is NOT a new top-level `peaks <cmd>`. The
18
+ `peaks project dashboard` command reads the log via the `audit` scan; the
19
+ helper writes the log. The dev-preference red line "Default-no on new CLI
20
+ commands" still applies — if the audit-trail becomes critical in a future
21
+ slice, the helper can be promoted to a CLI primitive at that point.
22
+
23
+ ## CLI surface
24
+
25
+ ```
26
+ node scripts/peaks-ide-audit-log.mjs --project <repo> --event <name> --adapter <id> [--ok true|false] [--detail <json>] [--dry-run]
27
+ ```
28
+
29
+ | Flag | Required | Description |
30
+ |---|---|---|
31
+ | `--project <path>` | yes | Target project root (parent of `.peaks/`) |
32
+ | `--event <name>` | yes | Event identifier (`install`, `statusline`, `hook-handle`, ...) |
33
+ | `--adapter <id>` | yes | Adapter id (`claude-code`, `trae`, ...) |
34
+ | `--ok <bool>` | no, default `true` | Outcome flag — `false` to record a failure |
35
+ | `--detail <json>` | no | Free-form JSON object attached to the entry |
36
+ | `--dry-run` | no | Print the would-be line; do not write |
37
+
38
+ ## Log line shape
39
+
40
+ ```json
41
+ {"timestamp":"2026-06-07T16:00:00.000Z","event":"install","adapter":"trae","ok":true}
42
+ ```
43
+
44
+ The log file path is `<projectRoot>/.peaks/audit/peaks-ide-<UTC-date>.log`
45
+ and is gitignored per the repo root `.gitignore` (`.peaks/audit/`).
46
+
47
+ ## Contract pinned by tests
48
+
49
+ `tests/unit/skills/peaks-ide/audit-log-helper.test.ts` pins 4 sub-cases
50
+ (per AC-5): helper is at the documented path, write emits one JSONL line
51
+ with `timestamp + event + adapter + ok`, the log path is in `.gitignore`,
52
+ and `--dry-run` returns the would-be line without writing.