peaks-cli 1.3.8 → 1.4.0

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 (130) hide show
  1. package/dist/src/cli/commands/core-artifact-commands.js +27 -0
  2. package/dist/src/cli/commands/project-commands.js +58 -1
  3. package/dist/src/cli/commands/request-commands.js +93 -3
  4. package/dist/src/cli/commands/retrospective-commands.d.ts +3 -0
  5. package/dist/src/cli/commands/retrospective-commands.js +113 -0
  6. package/dist/src/cli/commands/skill-scope-commands.d.ts +49 -0
  7. package/dist/src/cli/commands/skill-scope-commands.js +305 -0
  8. package/dist/src/cli/commands/workflow-commands.js +1 -1
  9. package/dist/src/cli/commands/workflow-plan-commands.d.ts +39 -0
  10. package/dist/src/cli/commands/workflow-plan-commands.js +163 -0
  11. package/dist/src/cli/program.js +8 -0
  12. package/dist/src/services/doctor/doctor-service.d.ts +40 -0
  13. package/dist/src/services/doctor/doctor-service.js +160 -0
  14. package/dist/src/services/hooks/presence-marker-detector.d.ts +16 -0
  15. package/dist/src/services/hooks/presence-marker-detector.js +105 -0
  16. package/dist/src/services/memory/project-memory-service.d.ts +19 -0
  17. package/dist/src/services/memory/project-memory-service.js +33 -0
  18. package/dist/src/services/retrospective/migrate-from-md.d.ts +37 -0
  19. package/dist/src/services/retrospective/migrate-from-md.js +528 -0
  20. package/dist/src/services/retrospective/retrospective-index.d.ts +37 -0
  21. package/dist/src/services/retrospective/retrospective-index.js +110 -0
  22. package/dist/src/services/retrospective/retrospective-show.d.ts +40 -0
  23. package/dist/src/services/retrospective/retrospective-show.js +109 -0
  24. package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +39 -0
  25. package/dist/src/services/skill-scope/adapters/_stub-helper.js +98 -0
  26. package/dist/src/services/skill-scope/adapters/claude-code.d.ts +59 -0
  27. package/dist/src/services/skill-scope/adapters/claude-code.js +304 -0
  28. package/dist/src/services/skill-scope/adapters/codex.d.ts +2 -0
  29. package/dist/src/services/skill-scope/adapters/codex.js +12 -0
  30. package/dist/src/services/skill-scope/adapters/cursor.d.ts +2 -0
  31. package/dist/src/services/skill-scope/adapters/cursor.js +13 -0
  32. package/dist/src/services/skill-scope/adapters/qoder.d.ts +2 -0
  33. package/dist/src/services/skill-scope/adapters/qoder.js +13 -0
  34. package/dist/src/services/skill-scope/adapters/tongyi.d.ts +2 -0
  35. package/dist/src/services/skill-scope/adapters/tongyi.js +13 -0
  36. package/dist/src/services/skill-scope/adapters/trae.d.ts +2 -0
  37. package/dist/src/services/skill-scope/adapters/trae.js +12 -0
  38. package/dist/src/services/skill-scope/detect.d.ts +75 -0
  39. package/dist/src/services/skill-scope/detect.js +480 -0
  40. package/dist/src/services/skill-scope/registry.d.ts +41 -0
  41. package/dist/src/services/skill-scope/registry.js +83 -0
  42. package/dist/src/services/skill-scope/source-of-truth.d.ts +44 -0
  43. package/dist/src/services/skill-scope/source-of-truth.js +118 -0
  44. package/dist/src/services/skill-scope/types.d.ts +176 -0
  45. package/dist/src/services/skill-scope/types.js +74 -0
  46. package/dist/src/services/standards/migrate-service.d.ts +63 -0
  47. package/dist/src/services/standards/migrate-service.js +193 -0
  48. package/dist/src/services/standards/project-standards-service.js +1 -23
  49. package/dist/src/services/workflow/artifact-paths.d.ts +59 -0
  50. package/dist/src/services/workflow/artifact-paths.js +127 -0
  51. package/dist/src/services/workflow/pipeline-verify-service.d.ts +6 -0
  52. package/dist/src/services/workflow/pipeline-verify-service.js +49 -4
  53. package/dist/src/services/workflow/plan-reader.d.ts +29 -0
  54. package/dist/src/services/workflow/plan-reader.js +158 -0
  55. package/dist/src/services/workflow/plan-refresher.d.ts +32 -0
  56. package/dist/src/services/workflow/plan-refresher.js +353 -0
  57. package/dist/src/services/workflow/plan-trigger-detector.d.ts +55 -0
  58. package/dist/src/services/workflow/plan-trigger-detector.js +142 -0
  59. package/dist/src/shared/format-md-compact.d.ts +32 -0
  60. package/dist/src/shared/format-md-compact.js +297 -0
  61. package/dist/src/shared/stale-policy.d.ts +67 -0
  62. package/dist/src/shared/stale-policy.js +85 -0
  63. package/dist/src/shared/version.d.ts +1 -1
  64. package/dist/src/shared/version.js +1 -1
  65. package/package.json +3 -2
  66. package/schemas/doctor-report.schema.json +2 -2
  67. package/skills/peaks-qa/SKILL.md +103 -507
  68. package/skills/peaks-qa/references/artifact-per-request.md +7 -79
  69. package/skills/peaks-qa/references/browser-validation-contracts.md +51 -0
  70. package/skills/peaks-qa/references/codegraph-regression-focus.md +5 -0
  71. package/skills/peaks-qa/references/external-capability-guidance.md +9 -0
  72. package/skills/peaks-qa/references/qa-compact-handoff.md +3 -0
  73. package/skills/peaks-qa/references/qa-context-governance.md +24 -0
  74. package/skills/peaks-qa/references/qa-fanout-contract.md +8 -0
  75. package/skills/peaks-qa/references/qa-gstack-integration.md +7 -0
  76. package/skills/peaks-qa/references/qa-local-artifacts.md +3 -0
  77. package/skills/peaks-qa/references/qa-matt-pocock-integration.md +9 -0
  78. package/skills/peaks-qa/references/qa-perf-test-plan.md +67 -0
  79. package/skills/peaks-qa/references/qa-refactor-role.md +3 -0
  80. package/skills/peaks-qa/references/qa-runbook.md +74 -0
  81. package/skills/peaks-qa/references/qa-security-test-plan.md +73 -0
  82. package/skills/peaks-qa/references/qa-skill-presence.md +22 -0
  83. package/skills/peaks-qa/references/qa-standards-preflight.md +8 -0
  84. package/skills/peaks-qa/references/qa-sub-agent-dispatch.md +38 -0
  85. package/skills/peaks-qa/references/qa-transition-gates.md +83 -0
  86. package/skills/peaks-qa/references/requirement-boundary-recheck.md +9 -0
  87. package/skills/peaks-qa/references/test-case-generation.md +27 -0
  88. package/skills/peaks-qa/references/test-report-output.md +14 -0
  89. package/skills/peaks-rd/SKILL.md +85 -612
  90. package/skills/peaks-rd/references/artifact-and-standards-output.md +9 -0
  91. package/skills/peaks-rd/references/artifact-per-request.md +20 -0
  92. package/skills/peaks-rd/references/browser-self-test-contracts.md +29 -0
  93. package/skills/peaks-rd/references/codegraph-project-analysis.md +5 -0
  94. package/skills/peaks-rd/references/compact-handoff.md +3 -0
  95. package/skills/peaks-rd/references/external-references.md +11 -0
  96. package/skills/peaks-rd/references/frontend-project-generation.md +11 -0
  97. package/skills/peaks-rd/references/library-version-awareness.md +30 -0
  98. package/skills/peaks-rd/references/mandatory-perf-baseline.md +42 -0
  99. package/skills/peaks-rd/references/mandatory-tech-doc.md +18 -0
  100. package/skills/peaks-rd/references/matt-pocock-integration.md +11 -0
  101. package/skills/peaks-rd/references/mock-data-placement.md +40 -0
  102. package/skills/peaks-rd/references/parallel-review-fanout.md +81 -0
  103. package/skills/peaks-rd/references/rd-context-governance.md +36 -0
  104. package/skills/peaks-rd/references/rd-gstack-integration.md +16 -0
  105. package/skills/peaks-rd/references/rd-runbook.md +125 -0
  106. package/skills/peaks-rd/references/rd-standards-preflight.md +8 -0
  107. package/skills/peaks-rd/references/rd-sub-agent-dispatch.md +39 -0
  108. package/skills/peaks-rd/references/rd-transition-gates.md +1 -1
  109. package/skills/peaks-rd/references/skill-presence-and-title.md +22 -0
  110. package/skills/peaks-solo/SKILL.md +87 -595
  111. package/skills/peaks-solo/references/anchoring-and-session-info.md +25 -0
  112. package/skills/peaks-solo/references/boundaries.md +21 -0
  113. package/skills/peaks-solo/references/codegraph-orchestration.md +5 -0
  114. package/skills/peaks-solo/references/completion-handoff.md +16 -0
  115. package/skills/peaks-solo/references/context-governance.md +51 -0
  116. package/skills/peaks-solo/references/external-references.md +17 -0
  117. package/skills/peaks-solo/references/frontend-only-mode.md +14 -0
  118. package/skills/peaks-solo/references/gstack-integration.md +7 -0
  119. package/skills/peaks-solo/references/local-artifact-workspace.md +79 -0
  120. package/skills/peaks-solo/references/micro-cycle.md +68 -0
  121. package/skills/peaks-solo/references/mode-selection.md +21 -0
  122. package/skills/peaks-solo/references/openspec-workflow.md +43 -0
  123. package/skills/peaks-solo/references/project-memory-loading.md +17 -0
  124. package/skills/peaks-solo/references/quality-gate-cheatsheet.md +13 -0
  125. package/skills/peaks-solo/references/resume-detection.md +63 -0
  126. package/skills/peaks-solo/references/runbook.md +1 -1
  127. package/skills/peaks-solo/references/skill-presence-and-title.md +31 -0
  128. package/skills/peaks-solo/references/standards-preflight.md +23 -0
  129. package/skills/peaks-solo/references/sub-agent-dispatch.md +46 -0
  130. package/skills/peaks-solo/references/swarm-dispatch-contract.md +56 -0
@@ -5,12 +5,14 @@ import { getArtifactWorkspaceStatus, planArtifactSync } from '../../services/art
5
5
  import { executeProjectMemoryBackup, executeProjectMemoryExtract, summarizeProjectMemoryBackupResult, summarizeProjectMemoryExtractResult } from '../../services/memory/project-memory-service.js';
6
6
  import { summarizeProjectStandardsInitResult, summarizeProjectStandardsUpdateResult } from '../../services/standards/project-standards-service.js';
7
7
  import { executeProjectStandardsInitIdeAware, executeProjectStandardsUpdateIdeAware } from '../../services/standards/ide-aware-standards-service.js';
8
+ import { migrateStandards } from '../../services/standards/migrate-service.js';
8
9
  import { listProfiles } from '../../services/profiles/profile-service.js';
9
10
  import { planProxyTest } from '../../services/proxy/proxy-service.js';
10
11
  import { runDoctor } from '../../services/doctor/doctor-service.js';
11
12
  import { listSkills } from '../../services/skills/skill-registry.js';
12
13
  import { inspectSkillRunbook } from '../../services/skills/skill-runbook-service.js';
13
14
  import { setSkillPresence, clearSkillPresence, getSkillPresence, isSkillPresenceMode, touchSkillHeartbeat } from '../../services/skills/skill-presence-service.js';
15
+ import { detectPresenceMarker } from '../../services/hooks/presence-marker-detector.js';
14
16
  import { getSessionId, getSessionMeta, rotateSessionBinding, setSessionMeta, setSessionTitle, listSessionMetas } from '../../services/session/session-manager.js';
15
17
  import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
16
18
  import { findProjectRoot } from '../../services/config/config-safety.js';
@@ -198,6 +200,16 @@ export function registerCoreAndArtifactCommands(program, io) {
198
200
  lastHeartbeat: updated.lastHeartbeat
199
201
  }), options.json);
200
202
  });
203
+ addJsonOption(skill
204
+ .command('detect-marker-loss')
205
+ .description('Detect whether the latest assistant message lost the Peaks-Cli status header while a peaks skill is still active (slice 028 detection primitive).')
206
+ .option('--project <path>', 'project root path (auto-detected from cwd when omitted)')
207
+ .option('--message <text>', 'latest assistant message text to scan (defaults to reading the most recent LLM response from the stdin pipe, or empty string when no pipe is attached)')).action((options) => {
208
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
209
+ const message = options.message ?? '';
210
+ const result = detectPresenceMarker({ project: projectRoot, latestAssistantMessage: message });
211
+ printResult(io, ok('skill.detect-marker-loss', result), options.json);
212
+ });
201
213
  const session = program.command('session').description('Manage Peaks session directories');
202
214
  addJsonOption(session
203
215
  .command('list')
@@ -449,6 +461,21 @@ export function registerCoreAndArtifactCommands(program, io) {
449
461
  process.exitCode = 1;
450
462
  }
451
463
  });
464
+ addJsonOption(standards
465
+ .command('migrate')
466
+ .description('Rewrite a consumer project CLAUDE.md to drop the legacy heartbeat block (slice 028). Dry-run by default; pass --apply to write.')
467
+ .option('--project <path>', 'target project root')
468
+ .option('--apply', 'rewrite the legacy block in place; default is dry-run')).action((options) => {
469
+ const projectRoot = options.project ?? process.cwd();
470
+ try {
471
+ const result = migrateStandards({ project: projectRoot, apply: options.apply === true });
472
+ printResult(io, ok('standards.migrate', result.data, [], result.data.nextActions), options.json);
473
+ }
474
+ catch (error) {
475
+ printResult(io, fail('standards.migrate', 'STANDARDS_MIGRATE_FAILED', getErrorMessage(error), { file: null, foundOldBlock: false, wouldChange: false, applied: false, before: null, after: null, nextActions: [] }, [getErrorMessage(error)]), options.json);
476
+ process.exitCode = 1;
477
+ }
478
+ });
452
479
  const memory = program.command('memory').description('Manage project-local Peaks memory');
453
480
  addJsonOption(memory
454
481
  .command('extract')
@@ -1,6 +1,8 @@
1
1
  import { loadProjectDashboard } from '../../services/dashboard/project-dashboard-service.js';
2
2
  import { generateProjectContext, readProjectContext } from '../../services/memory/project-context-service.js';
3
- import { extractSessionMemories, readMemoryIndex, readProjectMemories } from '../../services/memory/project-memory-service.js';
3
+ import { extractSessionMemories, readMemoryIndex, readProjectMemories, readProjectMemoryBody } from '../../services/memory/project-memory-service.js';
4
+ import { applyStalePolicy, DEFAULT_STALE_DAYS } from '../../shared/stale-policy.js';
5
+ import { formatMdCompact } from '../../shared/format-md-compact.js';
4
6
  import { fail, ok } from '../../shared/result.js';
5
7
  import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
6
8
  export function registerProjectCommands(program, io) {
@@ -158,4 +160,59 @@ export function registerProjectCommands(program, io) {
158
160
  process.exitCode = 1;
159
161
  }
160
162
  });
163
+ // --- Show one project memory's body (R3: default = compact) ---
164
+ addJsonOption(project
165
+ .command('memories:show <name>')
166
+ .description('Show one project memory body by name. Default format is `compact` (LLM-primary); pass --pretty for the disk verbatim. Stale entries (default ≥30 days) are excluded; pass --include-stale or --stale-days <N> to override.')
167
+ .requiredOption('--project <path>', 'target project root')
168
+ .option('--pretty', 'return the on-disk body verbatim; overrides the compact default')
169
+ .option('--include-stale', 'include stale entries (the default excludes them)')
170
+ .option('--stale-days <n>', 'override the 30-day stale threshold (must be > 0)', (value) => Number(value))).action((name, options) => {
171
+ try {
172
+ const memory = readProjectMemoryBody(options.project, name);
173
+ if (memory === null) {
174
+ printResult(io, fail('project.memories:show', 'MEMORY_NOT_FOUND', `memory ${name} not found in .peaks/memory`, { name, projectRoot: options.project }, ['Run `peaks project memories --json` to see available names']), options.json);
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ // Compute the stale decision. R4: stale is computed at CLI load time
179
+ // only; the source `.md` file is never modified.
180
+ const updatedAt = memory.updatedAt;
181
+ const thresholdDays = options.staleDays !== undefined && Number.isFinite(options.staleDays) && options.staleDays > 0
182
+ ? options.staleDays
183
+ : DEFAULT_STALE_DAYS;
184
+ const policy = applyStalePolicy([{ name: memory.name, updatedAt }], {
185
+ thresholdDays,
186
+ includeStale: options.includeStale === true
187
+ });
188
+ if (policy.entries.length === 0) {
189
+ const ageDays = policy.entries.length === 0 && policy.droppedCount > 0
190
+ ? applyStalePolicy([{ name: memory.name, updatedAt }], { thresholdDays, includeStale: true }).entries[0]?.ageDays ?? 0
191
+ : 0;
192
+ printResult(io, fail('project.memories:show', 'MEMORY_STALE', `memory ${name} is stale (age ${ageDays} days > ${thresholdDays} day threshold); pass --include-stale to override`, { name, ageDays, thresholdDays }, ['Pass --include-stale to load stale memories; pass --stale-days <N> to override the threshold']), options.json);
193
+ process.exitCode = 1;
194
+ return;
195
+ }
196
+ const ageDays = policy.entries[0]?.ageDays ?? 0;
197
+ const isStale = policy.entries[0]?.stale ?? false;
198
+ const format = options.pretty === true ? 'pretty' : 'compact';
199
+ const body = format === 'pretty' ? memory.body : formatMdCompact(memory.body);
200
+ printResult(io, ok('project.memories:show', {
201
+ name: memory.name,
202
+ title: memory.title,
203
+ kind: memory.kind,
204
+ sourcePath: memory.filePath,
205
+ updatedAt,
206
+ ageDays,
207
+ stale: isStale,
208
+ body,
209
+ format,
210
+ bodyBytes: Buffer.byteLength(body, 'utf8')
211
+ }), options.json);
212
+ }
213
+ catch (error) {
214
+ printResult(io, fail('project.memories:show', 'PROJECT_MEMORY_SHOW_FAILED', getErrorMessage(error), { name, projectRoot: options.project }, ['Check the project path and .peaks/memory directory']), options.json);
215
+ process.exitCode = 1;
216
+ }
217
+ });
161
218
  }
@@ -5,7 +5,82 @@ import { recordBypass, isBypassLimitReached, MAX_BYPASSES_PER_SESSION } from '..
5
5
  import { lintRequestArtifact } from '../../services/artifacts/artifact-lint-service.js';
6
6
  import { getRepairCycleStatus } from '../../services/artifacts/repair-cycle-service.js';
7
7
  import { fail, ok } from '../../shared/result.js';
8
+ import { formatMdCompact } from '../../shared/format-md-compact.js';
8
9
  import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
10
+ /**
11
+ * Per-artifact default format. Only PRD and tech-doc are user-review
12
+ * surfaces; all other RD/QA/TXT artifacts default to compact. The
13
+ * `--pretty` / `--compact` flags override uniformly. See tech-doc
14
+ * §1.3.
15
+ */
16
+ const DEFAULT_FORMAT_BY_ARTIFACT = {
17
+ prd: 'pretty',
18
+ 'tech-doc': 'pretty',
19
+ 'code-review': 'compact',
20
+ 'security-review': 'compact',
21
+ 'perf-baseline': 'compact',
22
+ 'bug-analysis': 'compact',
23
+ 'test-cases': 'compact',
24
+ 'test-reports': 'compact',
25
+ 'security-findings': 'compact',
26
+ 'performance-findings': 'compact',
27
+ handoff: 'compact'
28
+ };
29
+ function resolveDefaultFormat(artifactName) {
30
+ return DEFAULT_FORMAT_BY_ARTIFACT[artifactName] ?? 'compact';
31
+ }
32
+ function applyPerArtifactFormat(envelope, override) {
33
+ if (override === null || envelope === null || typeof envelope !== 'object')
34
+ return envelope;
35
+ // The service returns `{ id, sessionId, role, body, ... }` for a
36
+ // single-artifact show. The per-artifact `body` field is the only
37
+ // thing that changes; we attach a `format` field to surface the
38
+ // choice to the caller. Slice 023 (R3) AC6 / AC7.
39
+ const obj = envelope;
40
+ if (typeof obj.body === 'string') {
41
+ return {
42
+ ...obj,
43
+ body: override === 'compact' ? formatMdCompact(obj.body) : obj.body,
44
+ format: override
45
+ };
46
+ }
47
+ return envelope;
48
+ }
49
+ /**
50
+ * Map a request-artifact envelope to a `DEFAULT_FORMAT_BY_ARTIFACT` key.
51
+ * The service returns the path of the artifact in `path`; the role +
52
+ * filename stem gives us the artifact name. Falls back to the role
53
+ * itself when no `path` field is present.
54
+ */
55
+ function inferArtifactName(envelope, role) {
56
+ if (envelope === null || typeof envelope !== 'object')
57
+ return role;
58
+ const obj = envelope;
59
+ const pathField = typeof obj.path === 'string' ? obj.path : '';
60
+ // Path looks like `.peaks/_runtime/<sid>/<role>/requests/<file>.md`.
61
+ // The role is the second-to-last directory; the file stem is the
62
+ // last segment. For RD role, the artifact is one of {code-review,
63
+ // security-review, perf-baseline, bug-analysis, tech-doc}; the file
64
+ // stem usually carries the artifact name (e.g. `tech-doc.md`,
65
+ // `code-review-002.md`). We strip the trailing `-<digits>` suffix
66
+ // when present.
67
+ const stem = pathField.split(/[\\/]/).pop()?.replace(/\.md$/, '') ?? '';
68
+ if (stem.length > 0) {
69
+ // Try the stem verbatim first.
70
+ if (DEFAULT_FORMAT_BY_ARTIFACT[stem] !== undefined)
71
+ return stem;
72
+ // Strip a trailing -<digits> (e.g. code-review-002 -> code-review).
73
+ const trimmed = stem.replace(/-\d+$/, '');
74
+ if (DEFAULT_FORMAT_BY_ARTIFACT[trimmed] !== undefined)
75
+ return trimmed;
76
+ // Last-ditch: match by prefix.
77
+ for (const key of Object.keys(DEFAULT_FORMAT_BY_ARTIFACT)) {
78
+ if (stem.startsWith(key))
79
+ return key;
80
+ }
81
+ }
82
+ return role;
83
+ }
9
84
  const VALID_ROLES = ['prd', 'ui', 'rd', 'qa', 'sc'];
10
85
  function parseRole(value) {
11
86
  if (!VALID_ROLES.includes(value)) {
@@ -119,11 +194,13 @@ export function registerRequestCommands(program, io) {
119
194
  });
120
195
  addJsonOption(request
121
196
  .command('show')
122
- .description('Show a single per-request artifact, optionally scoped to a session')
197
+ .description('Show a single per-request artifact, optionally scoped to a session. R3: default body format is per-artifact (PRD/tech-doc pretty; everything else compact); pass --pretty or --compact to override uniformly.')
123
198
  .argument('<request-id>', 'request id, e.g. 2026-05-23-add-foo')
124
199
  .requiredOption('--role <role>', `target role (${VALID_ROLES.join(' | ')})`, parseRole)
125
200
  .requiredOption('--project <path>', 'target project root')
126
- .option('--session-id <session>', 'restrict to a specific session id')).action(async (requestId, options) => {
201
+ .option('--session-id <session>', 'restrict to a specific session id')
202
+ .option('--pretty', 'force the body to render pretty (overrides the per-artifact default)')
203
+ .option('--compact', 'force the body to render compact (overrides the per-artifact default)')).action(async (requestId, options) => {
127
204
  try {
128
205
  const showOptions = {
129
206
  projectRoot: options.project,
@@ -139,7 +216,20 @@ export function registerRequestCommands(program, io) {
139
216
  process.exitCode = 1;
140
217
  return;
141
218
  }
142
- printResult(io, ok('request.show', result), options.json);
219
+ // R3: pick the per-artifact default format and apply the override
220
+ // if either flag is set. Last-flag-wins if both are passed.
221
+ const override = options.compact === true
222
+ ? 'compact'
223
+ : options.pretty === true
224
+ ? 'pretty'
225
+ : null;
226
+ const artifactName = inferArtifactName(result, options.role);
227
+ const format = override ?? resolveDefaultFormat(artifactName);
228
+ const transformed = applyPerArtifactFormat(result, override ?? format);
229
+ const payload = transformed === result
230
+ ? { ...result, format }
231
+ : transformed;
232
+ printResult(io, ok('request.show', payload), options.json);
143
233
  }
144
234
  catch (error) {
145
235
  printResult(io, fail('request.show', 'REQUEST_SHOW_FAILED', getErrorMessage(error), { role: options.role, requestId }, ['Check role, request id, and project path before retrying']), options.json);
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import { type ProgramIO } from '../cli-helpers.js';
3
+ export declare function registerRetrospectiveCommands(program: Command, io: ProgramIO): void;
@@ -0,0 +1,113 @@
1
+ import { findProjectRoot } from '../../services/config/config-safety.js';
2
+ import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
3
+ import { loadRetrospectiveIndex } from '../../services/retrospective/retrospective-index.js';
4
+ import { showRetrospective } from '../../services/retrospective/retrospective-show.js';
5
+ import { migrateRetrospectiveFromMd } from '../../services/retrospective/migrate-from-md.js';
6
+ import { fail, ok } from '../../shared/result.js';
7
+ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
8
+ export function registerRetrospectiveCommands(program, io) {
9
+ const retrospective = program.command('retrospective').description('Read the peaks retrospective index (R3: index.json, not the legacy <id>/ MD tree)');
10
+ addJsonOption(retrospective
11
+ .command('index')
12
+ .description('List all retrospective entries from .peaks/retrospective/index.json (R3: replaces the per-workflow MD dirs)')
13
+ .option('--project <path>', 'target project root (defaults to git root or cwd)')).action((options) => {
14
+ const projectRoot = options.project !== undefined
15
+ ? resolveCanonicalProjectRoot(options.project)
16
+ : (findProjectRoot(process.cwd()) ?? process.cwd());
17
+ try {
18
+ const result = loadRetrospectiveIndex(projectRoot);
19
+ const warnings = result.warning === null ? [] : [result.warning];
20
+ printResult(io, ok('retrospective.index', {
21
+ indexPath: result.indexPath,
22
+ source: result.source,
23
+ total: result.totalCount,
24
+ entries: result.entries
25
+ }, warnings), options.json);
26
+ }
27
+ catch (error) {
28
+ printResult(io, fail('retrospective.index', 'RETROSPECTIVE_INDEX_FAILED', getErrorMessage(error), { projectRoot }, ['Check the project path and .peaks/retrospective/index.json']), options.json);
29
+ process.exitCode = 1;
30
+ }
31
+ });
32
+ addJsonOption(retrospective
33
+ .command('show <id>')
34
+ .description('Show one retrospective entry by id. Default format is `compact` (LLM-primary); pass --pretty to get the disk / re-hydrated pretty form.')
35
+ .option('--project <path>', 'target project root (defaults to git root or cwd)')
36
+ .option('--pretty', 'return the pretty form (re-hydrated from source artifacts); overrides the compact default')).action((id, options) => {
37
+ const projectRoot = options.project !== undefined
38
+ ? resolveCanonicalProjectRoot(options.project)
39
+ : (findProjectRoot(process.cwd()) ?? process.cwd());
40
+ try {
41
+ const format = options.pretty === true ? 'pretty' : 'compact';
42
+ const result = showRetrospective({ projectRoot, id, format });
43
+ if (!result.ok) {
44
+ const suggestions = [];
45
+ if (result.code === 'INDEX_MISSING')
46
+ suggestions.push('Run `peaks retrospective migrate --apply` to build the index');
47
+ if (result.code === 'NOT_FOUND')
48
+ suggestions.push('Run `peaks retrospective index --json` to see available ids');
49
+ if (result.code === 'ARTIFACT_MISSING' || result.missingArtifacts !== undefined) {
50
+ suggestions.push('Re-hydrate from the legacy archive at .peaks/_archive/retrospective-2026-06-09-pre-r3.tar.gz');
51
+ }
52
+ printResult(io, fail('retrospective.show', result.code, result.message, { id, projectRoot, ...(result.missingArtifacts !== undefined ? { missingArtifacts: result.missingArtifacts } : {}) }, suggestions), options.json);
53
+ process.exitCode = 1;
54
+ return;
55
+ }
56
+ printResult(io, ok('retrospective.show', {
57
+ id: result.entry.id,
58
+ sessionId: result.entry.sessionId,
59
+ sliceId: result.entry.sliceId ?? null,
60
+ type: result.entry.type,
61
+ title: result.entry.title,
62
+ summary: result.entry.summary,
63
+ outcome: result.entry.outcome,
64
+ keyDecisions: result.entry.keyDecisions,
65
+ lessonsLearned: result.entry.lessonsLearned,
66
+ artifactPaths: result.entry.artifactPaths,
67
+ updatedAt: result.entry.updatedAt,
68
+ body: result.body,
69
+ format: result.format
70
+ }, result.warnings), options.json);
71
+ }
72
+ catch (error) {
73
+ printResult(io, fail('retrospective.show', 'RETROSPECTIVE_SHOW_FAILED', getErrorMessage(error), { id, projectRoot }, ['Check the project path and id']), options.json);
74
+ process.exitCode = 1;
75
+ }
76
+ });
77
+ addJsonOption(retrospective
78
+ .command('migrate')
79
+ .description('One-time migration from per-workflow .peaks/retrospective/<id>/*.md dirs to a single .peaks/retrospective/index.json + .peaks/_archive/retrospective-2026-06-09-pre-r3.tar.gz archive. Dry-run by default; --apply is destructive.')
80
+ .option('--project <path>', 'target project root (defaults to git root or cwd)')
81
+ .option('--apply', 'write the index.json + archive + delete legacy MDs (default: dry-run preview)')
82
+ .option('--include-failed', 'include malformed MDs as best-effort entries; default is to skip + warn')
83
+ .option('--expected-entries <n>', 'override the default expected entry count (88) for the no-op check', (value) => Number(value))).action((options) => {
84
+ const projectRoot = options.project !== undefined
85
+ ? resolveCanonicalProjectRoot(options.project)
86
+ : (findProjectRoot(process.cwd()) ?? process.cwd());
87
+ try {
88
+ const result = migrateRetrospectiveFromMd({
89
+ projectRoot,
90
+ apply: options.apply === true,
91
+ includeFailed: options.includeFailed === true,
92
+ ...(options.expectedEntries !== undefined && Number.isFinite(options.expectedEntries) ? { expectedEntries: options.expectedEntries } : {})
93
+ });
94
+ const exitCode = result.status === 'failed' ? 1 : 0;
95
+ printResult(io, ok('retrospective.migrate', {
96
+ status: result.status,
97
+ indexPath: result.indexPath,
98
+ archivePath: result.archivePath,
99
+ totalLegacyDirs: result.totalLegacyDirs,
100
+ totalLegacyMds: result.totalLegacyMds,
101
+ parsedEntries: result.parsedEntries,
102
+ failedEntries: result.failedEntries,
103
+ archiveVerified: result.archiveVerified
104
+ }, result.warnings), options.json);
105
+ if (exitCode !== 0)
106
+ process.exitCode = exitCode;
107
+ }
108
+ catch (error) {
109
+ printResult(io, fail('retrospective.migrate', 'RETROSPECTIVE_MIGRATE_FAILED', getErrorMessage(error), { projectRoot }, ['Check the project path and .peaks/retrospective/ directory']), options.json);
110
+ process.exitCode = 1;
111
+ }
112
+ });
113
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `peaks skill scope` CLI surface (slice 025.1).
3
+ *
4
+ * Four subcommands (mutually exclusive):
5
+ * - `--detect` — dry-run; prints the relevance matrix, never touches files.
6
+ * - `--apply` — writes the source-of-truth + IDE-native config.
7
+ * - `--show` — reads the source-of-truth + native config back.
8
+ * - `--reset` — removes the source-of-truth + IDE-native config.
9
+ *
10
+ * Exit code matrix (tech-doc §6.3):
11
+ * 0 success
12
+ * 1 uncaught error
13
+ * 2 invalid usage (missing/incompatible flags)
14
+ * 3 source-of-truth written but adapter returned NOT_SUPPORTED
15
+ * 4 adapter failure other than NOT_SUPPORTED
16
+ */
17
+ import { Command } from 'commander';
18
+ import { type ResultEnvelope } from '../../shared/result.js';
19
+ import { type ProgramIO } from '../cli-helpers.js';
20
+ export type SkillScopeAction = 'detect' | 'apply' | 'show' | 'reset';
21
+ export interface RunSkillScopeInput {
22
+ readonly subcommand: SkillScopeAction;
23
+ readonly project: string;
24
+ readonly strict?: boolean;
25
+ readonly loose?: boolean;
26
+ readonly ide?: string;
27
+ readonly shadowFallback?: boolean;
28
+ readonly json?: boolean;
29
+ /** Test seam: override the detected allowlist (CLI re-adds peaks-* per G6). */
30
+ readonly overrideAllowlist?: readonly string[];
31
+ /** Test seam: force the source-of-truth write to fail (simulates atomicity test). */
32
+ readonly simulateSourceOfTruthWriteFailure?: boolean;
33
+ }
34
+ export interface RunSkillScopeResult {
35
+ readonly exitCode: number;
36
+ readonly envelope: ResultEnvelope<unknown> | null;
37
+ readonly stdout: string;
38
+ readonly stderr: string;
39
+ }
40
+ /**
41
+ * Programmatic entry point for `peaks skill scope`. Used by the CLI shim
42
+ * AND by the unit tests.
43
+ */
44
+ export declare function runSkillScopeCommand(input: RunSkillScopeInput): Promise<RunSkillScopeResult>;
45
+ /**
46
+ * Register the `peaks skill scope` subcommand on the `skill` command group.
47
+ * Mutually-exclusive flags: exactly one of --detect / --apply / --show / --reset.
48
+ */
49
+ export declare function registerSkillScopeCommands(program: Command, io: ProgramIO): void;