peaks-cli 1.3.3 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/src/cli/commands/core-artifact-commands.js +6 -3
  2. package/dist/src/cli/commands/project-commands.js +8 -4
  3. package/dist/src/cli/commands/workflow-commands.js +2 -1
  4. package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
  5. package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
  6. package/dist/src/services/ide/adapters/claude-code-adapter.js +27 -0
  7. package/dist/src/services/ide/adapters/trae-adapter.d.ts +19 -11
  8. package/dist/src/services/ide/adapters/trae-adapter.js +43 -15
  9. package/dist/src/services/ide/hook-protocol.d.ts +7 -4
  10. package/dist/src/services/ide/hook-protocol.js +7 -4
  11. package/dist/src/services/ide/ide-types.d.ts +60 -0
  12. package/dist/src/services/ide/resource-profile.d.ts +52 -0
  13. package/dist/src/services/ide/resource-profile.js +33 -0
  14. package/dist/src/services/memory/project-context-service.js +2 -1
  15. package/dist/src/services/memory/project-memory-service.js +4 -3
  16. package/dist/src/services/perf/perf-baseline-service.js +2 -1
  17. package/dist/src/services/session/getSessionDir.d.ts +1 -0
  18. package/dist/src/services/session/getSessionDir.js +27 -0
  19. package/dist/src/services/session/index.d.ts +1 -0
  20. package/dist/src/services/session/index.js +1 -0
  21. package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
  22. package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
  23. package/dist/src/services/standards/project-standards-service.d.ts +1 -2
  24. package/dist/src/shared/version.d.ts +1 -1
  25. package/dist/src/shared/version.js +1 -1
  26. package/package.json +1 -1
  27. package/scripts/install-skills.mjs +112 -2
  28. package/skills/peaks-ide/SKILL.md +1 -1
  29. package/skills/peaks-ide/references/audit-log-helper.md +52 -0
  30. package/skills/peaks-qa/SKILL.md +104 -62
  31. package/skills/peaks-rd/SKILL.md +88 -58
  32. package/skills/peaks-solo/SKILL.md +52 -22
  33. package/skills/peaks-solo/references/browser-workflow.md +22 -20
  34. package/skills/peaks-solo/references/sub-agent-dispatch.md +44 -1
  35. package/skills/peaks-ui/SKILL.md +18 -9
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Canonical session-directory resolver.
3
+ *
4
+ * As of slice 2026-06-05-peaks-runtime-layer the per-session workspace
5
+ * lives at `<root>/.peaks/_runtime/<sessionId>/` (NOT at the legacy
6
+ * `<root>/.peaks/<sessionId>/` location). All **write** paths MUST route
7
+ * through this helper. The legacy top-level path is preserved as a
8
+ * back-compat **read** fallback only (see
9
+ * `src/services/artifacts/request-artifact-service.ts:662` etc.).
10
+ *
11
+ * The corresponding test in
12
+ * `tests/unit/services/session/session-dir-canonical.test.ts` enforces
13
+ * two invariants:
14
+ *
15
+ * (a) `getSessionDir(root, sid)` returns `<root>/.peaks/_runtime/<sid>`.
16
+ * (b) A static scan of `src/` flags any direct join of `.peaks` +
17
+ * `sessionId` that does NOT route through this resolver. The
18
+ * back-compat **read** sites are excluded by explicit allow-list.
19
+ *
20
+ * @param projectRoot - Absolute path to the project root.
21
+ * @param sessionId - The session identifier (e.g. `2026-06-06-session-5b1095`).
22
+ * @returns Absolute path to the canonical session directory.
23
+ */
24
+ import { join } from 'node:path';
25
+ export function getSessionDir(projectRoot, sessionId) {
26
+ return join(projectRoot, '.peaks', '_runtime', sessionId);
27
+ }
@@ -1 +1,2 @@
1
1
  export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas, getProjectScanPath, hasProjectScan, setCurrentSessionBinding, rotateSessionBinding, type SessionInfo, type SessionMeta } from './session-manager.js';
2
+ export { getSessionDir } from './getSessionDir.js';
@@ -1 +1,2 @@
1
1
  export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas, getProjectScanPath, hasProjectScan, setCurrentSessionBinding, rotateSessionBinding } from './session-manager.js';
2
+ export { getSessionDir } from './getSessionDir.js';
@@ -0,0 +1,94 @@
1
+ /**
2
+ * IDE-aware wrapper for `peaks standards init` / `peaks standards update`.
3
+ *
4
+ * Slice #011-2026-06-07-ide-adapter-resource-profile: the original
5
+ * `executeProjectStandardsInit` / `executeProjectStandardsUpdate` always
6
+ * wrote to `CLAUDE.md` + `.claude/rules/**` regardless of which IDE
7
+ * the user was running. This wrapper dispatches on the IDE detected
8
+ * (or explicitly requested via `--ide`) and falls back to the legacy
9
+ * Claude Code path with a stderr warning when the detected IDE has
10
+ * no `standardsProfile` declared (Trae in slice 1.3.2).
11
+ *
12
+ * Two entry points:
13
+ *
14
+ * - `executeProjectStandardsInitIdeAware` — same signature as the
15
+ * underlying `executeProjectStandardsInit`, plus an optional
16
+ * `ideId` override that bypasses detection.
17
+ * - `executeProjectStandardsUpdateIdeAware` — same shape, for the
18
+ * `update` flow.
19
+ *
20
+ * Detection precedence:
21
+ * 1. Explicit `options.ideId` (CLI `--ide` flag)
22
+ * 2. `IdeRegistry.detect()` from cwd (or the `projectRoot` if given)
23
+ * 3. `null` (no IDE detected) → fall back to the legacy Claude Code path
24
+ *
25
+ * Fallback behavior: when the resolved IDE has no `standardsProfile`
26
+ * declared, the wrapper STILL calls the legacy Claude Code writer
27
+ * (so the user gets the files they would have gotten before slice #011)
28
+ * and emits a stderr warning with the IDE id and the fact that the
29
+ * adapter is UNVERIFIED for the standards profile. This keeps the
30
+ * "Trae is UNVERIFIED, ship a working file tree, surface the gap"
31
+ * contract intact.
32
+ */
33
+ import type { IdeId } from '../ide/ide-types.js';
34
+ import { detectAllResourceTargets, getStandardsProfile } from '../ide/resource-profile.js';
35
+ import { type ProjectStandardsInitOptions, type ProjectStandardsInitResult, type ProjectStandardsUpdateResult } from './project-standards-service.js';
36
+ export type { ProjectStandardsInitResult, ProjectStandardsUpdateResult };
37
+ export type ProjectStandardsIdeAwareOptions = ProjectStandardsInitOptions & {
38
+ /**
39
+ * Explicit IDE override. When set, bypasses `IdeRegistry.detect()`
40
+ * (cwd + env heuristics) and uses the provided IDE id directly.
41
+ * Mirrors the `peaks hooks install --ide <id>` pattern.
42
+ */
43
+ readonly ideId?: IdeId;
44
+ };
45
+ export type ProjectStandardsUpdateIdeAwareOptions = ProjectStandardsInitOptions & {
46
+ /** Explicit IDE override. See {@link ProjectStandardsIdeAwareOptions}. */
47
+ readonly ideId?: IdeId;
48
+ };
49
+ /**
50
+ * Resolve the active IDE id for a standards call. Order of precedence:
51
+ * 1. explicit `options.ideId`
52
+ * 2. `IdeRegistry.detect()` from `options.projectRoot`
53
+ * 3. `null` (no detected IDE — caller falls back to legacy)
54
+ */
55
+ export declare function resolveStandardsIdeId(options: {
56
+ readonly projectRoot: string;
57
+ readonly ideId?: IdeId;
58
+ }): IdeId | null;
59
+ /**
60
+ * Run `peaks standards init` with IDE-aware dispatch.
61
+ *
62
+ * When the resolved IDE has a `standardsProfile`, the call still
63
+ * delegates to the existing `executeProjectStandardsInit` (the
64
+ * profile maps to the Claude Code path; future per-IDE writers
65
+ * plug in here). When the IDE is unregistered for the standards
66
+ * profile, the call delegates to the legacy path + emits a stderr
67
+ * warning.
68
+ */
69
+ export declare function executeProjectStandardsInitIdeAware(options: ProjectStandardsIdeAwareOptions): ProjectStandardsInitResult;
70
+ /**
71
+ * Run `peaks standards update` with IDE-aware dispatch.
72
+ *
73
+ * Same dispatch rules as `executeProjectStandardsInitIdeAware`.
74
+ */
75
+ export declare function executeProjectStandardsUpdateIdeAware(options: ProjectStandardsUpdateIdeAwareOptions): ProjectStandardsUpdateResult;
76
+ /**
77
+ * Test seam + integration-test helper: returns the resolved IDE id
78
+ * for the call, plus the active standards profile. Exported for
79
+ * the integration test in `tests/unit/standards/ide-aware-standards-service.test.ts`
80
+ * to assert the dispatch decision without running the full write.
81
+ */
82
+ export declare function inspectStandardsDispatch(options: {
83
+ readonly projectRoot: string;
84
+ readonly ideId?: IdeId;
85
+ }): {
86
+ readonly ideId: IdeId | null;
87
+ readonly profile: ReturnType<typeof getStandardsProfile>;
88
+ };
89
+ /**
90
+ * Test seam: the resource-profile accessor exposes
91
+ * `detectAllResourceTargets` for callers that need to enumerate
92
+ * across all registered IDEs. Re-exported here for convenience.
93
+ */
94
+ export { detectAllResourceTargets };
@@ -0,0 +1,89 @@
1
+ import { detectAllResourceTargets, getStandardsProfile, } from '../ide/resource-profile.js';
2
+ import { executeProjectStandardsInit, executeProjectStandardsUpdate, } from './project-standards-service.js';
3
+ import { detectInstalledIde } from '../ide/ide-detector.js';
4
+ function warnUnregisteredIde(ideId, projectRoot) {
5
+ process.stderr.write(`peaks standards: IDE '${ideId}' has no standardsProfile declared; ` +
6
+ `falling back to the legacy Claude Code path (CLAUDE.md + .claude/rules/**) ` +
7
+ `for project '${projectRoot}'. This is a slice #011 follow-up gap; ` +
8
+ `see .peaks/memory/ide-adapter-resource-profile-framework.md.\n`);
9
+ }
10
+ function warnNoIdeDetected(projectRoot) {
11
+ process.stderr.write(`peaks standards: no IDE detected in '${projectRoot}'; ` +
12
+ `writing to the legacy Claude Code path (CLAUDE.md + .claude/rules/**). ` +
13
+ `Pass --ide <id> to bypass detection.\n`);
14
+ }
15
+ /**
16
+ * Resolve the active IDE id for a standards call. Order of precedence:
17
+ * 1. explicit `options.ideId`
18
+ * 2. `IdeRegistry.detect()` from `options.projectRoot`
19
+ * 3. `null` (no detected IDE — caller falls back to legacy)
20
+ */
21
+ export function resolveStandardsIdeId(options) {
22
+ if (options.ideId !== undefined) {
23
+ return options.ideId;
24
+ }
25
+ return detectInstalledIde(options.projectRoot);
26
+ }
27
+ /**
28
+ * Run `peaks standards init` with IDE-aware dispatch.
29
+ *
30
+ * When the resolved IDE has a `standardsProfile`, the call still
31
+ * delegates to the existing `executeProjectStandardsInit` (the
32
+ * profile maps to the Claude Code path; future per-IDE writers
33
+ * plug in here). When the IDE is unregistered for the standards
34
+ * profile, the call delegates to the legacy path + emits a stderr
35
+ * warning.
36
+ */
37
+ export function executeProjectStandardsInitIdeAware(options) {
38
+ const ideId = resolveStandardsIdeId(options);
39
+ if (ideId === null) {
40
+ warnNoIdeDetected(options.projectRoot);
41
+ return executeProjectStandardsInit(options);
42
+ }
43
+ const profile = getStandardsProfile(ideId);
44
+ if (profile === null) {
45
+ warnUnregisteredIde(ideId, options.projectRoot);
46
+ return executeProjectStandardsInit(options);
47
+ }
48
+ // Claude Code path: profile matches the legacy writer. Future per-IDE
49
+ // writers (markdown+frontmatter, multiple rule roots, etc.) plug in
50
+ // here by branching on `profile.format` / `profile.rulesDir`.
51
+ return executeProjectStandardsInit(options);
52
+ }
53
+ /**
54
+ * Run `peaks standards update` with IDE-aware dispatch.
55
+ *
56
+ * Same dispatch rules as `executeProjectStandardsInitIdeAware`.
57
+ */
58
+ export function executeProjectStandardsUpdateIdeAware(options) {
59
+ const ideId = resolveStandardsIdeId(options);
60
+ if (ideId === null) {
61
+ warnNoIdeDetected(options.projectRoot);
62
+ return executeProjectStandardsUpdate(options);
63
+ }
64
+ const profile = getStandardsProfile(ideId);
65
+ if (profile === null) {
66
+ warnUnregisteredIde(ideId, options.projectRoot);
67
+ return executeProjectStandardsUpdate(options);
68
+ }
69
+ return executeProjectStandardsUpdate(options);
70
+ }
71
+ /**
72
+ * Test seam + integration-test helper: returns the resolved IDE id
73
+ * for the call, plus the active standards profile. Exported for
74
+ * the integration test in `tests/unit/standards/ide-aware-standards-service.test.ts`
75
+ * to assert the dispatch decision without running the full write.
76
+ */
77
+ export function inspectStandardsDispatch(options) {
78
+ const ideId = resolveStandardsIdeId(options);
79
+ if (ideId === null) {
80
+ return { ideId: null, profile: null };
81
+ }
82
+ return { ideId, profile: getStandardsProfile(ideId) };
83
+ }
84
+ /**
85
+ * Test seam: the resource-profile accessor exposes
86
+ * `detectAllResourceTargets` for callers that need to enumerate
87
+ * across all registered IDEs. Re-exported here for convenience.
88
+ */
89
+ export { detectAllResourceTargets };
@@ -68,7 +68,7 @@ export type ProjectStandardsUpdateSummary = {
68
68
  readonly reviewSuggestions: string[];
69
69
  };
70
70
  };
71
- type ProjectStandardsInitOptions = {
71
+ export type ProjectStandardsInitOptions = {
72
72
  readonly projectRoot: string;
73
73
  readonly language?: string;
74
74
  readonly apply?: boolean;
@@ -79,4 +79,3 @@ export declare function executeProjectStandardsInit(options: ProjectStandardsIni
79
79
  export declare function executeProjectStandardsUpdate(options: ProjectStandardsInitOptions): ProjectStandardsUpdateResult;
80
80
  export declare function summarizeProjectStandardsInitResult(result: ProjectStandardsInitResult): ProjectStandardsInitSummary;
81
81
  export declare function summarizeProjectStandardsUpdateResult(result: ProjectStandardsUpdateResult): ProjectStandardsUpdateSummary;
82
- export {};
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "1.3.3";
1
+ export declare const CLI_VERSION = "1.3.4";
@@ -1 +1 @@
1
- export const CLI_VERSION = "1.3.3";
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.3",
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",
@@ -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 });
@@ -109,7 +109,7 @@ Every successful execution writes one JSON line to `.peaks/_runtime/<sid>/ide-on
109
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
110
  ```
111
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.
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
113
 
114
114
  ## Boundaries
115
115
 
@@ -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.