peaks-cli 1.2.9 → 1.3.1

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 (53) hide show
  1. package/README.md +62 -46
  2. package/bin/peaks.js +0 -0
  3. package/dist/src/cli/commands/hooks-commands.js +24 -9
  4. package/dist/src/cli/commands/progress-commands.js +26 -2
  5. package/dist/src/cli/commands/request-commands.js +5 -0
  6. package/dist/src/cli/commands/slice-commands.d.ts +3 -0
  7. package/dist/src/cli/commands/slice-commands.js +42 -0
  8. package/dist/src/cli/commands/workflow-commands.js +3 -3
  9. package/dist/src/cli/commands/workspace-commands.d.ts +63 -0
  10. package/dist/src/cli/commands/workspace-commands.js +347 -5
  11. package/dist/src/cli/program.js +4 -0
  12. package/dist/src/services/artifacts/artifact-prerequisites.d.ts +17 -1
  13. package/dist/src/services/artifacts/artifact-prerequisites.js +38 -5
  14. package/dist/src/services/artifacts/request-artifact-service.d.ts +22 -0
  15. package/dist/src/services/artifacts/request-artifact-service.js +172 -54
  16. package/dist/src/services/doctor/doctor-service.d.ts +7 -0
  17. package/dist/src/services/doctor/doctor-service.js +20 -2
  18. package/dist/src/services/progress/progress-service.d.ts +26 -0
  19. package/dist/src/services/progress/progress-service.js +25 -0
  20. package/dist/src/services/sc/sc-service.d.ts +52 -1
  21. package/dist/src/services/sc/sc-service.js +324 -17
  22. package/dist/src/services/scan/acceptance-coverage-service.js +6 -2
  23. package/dist/src/services/session/session-manager.d.ts +7 -5
  24. package/dist/src/services/session/session-manager.js +60 -16
  25. package/dist/src/services/skills/hooks-settings-service.d.ts +25 -3
  26. package/dist/src/services/skills/hooks-settings-service.js +57 -13
  27. package/dist/src/services/skills/skill-presence-service.js +102 -68
  28. package/dist/src/services/skills/skill-runbook-service.js +2 -1
  29. package/dist/src/services/skills/skill-statusline-service.js +13 -7
  30. package/dist/src/services/slice/slice-check-service.d.ts +2 -0
  31. package/dist/src/services/slice/slice-check-service.js +248 -0
  32. package/dist/src/services/slice/slice-check-types.d.ts +61 -0
  33. package/dist/src/services/slice/slice-check-types.js +18 -0
  34. package/dist/src/services/workflow/pipeline-verify-service.d.ts +5 -2
  35. package/dist/src/services/workflow/pipeline-verify-service.js +35 -35
  36. package/dist/src/services/workspace/migrate-service.d.ts +2 -0
  37. package/dist/src/services/workspace/migrate-service.js +484 -0
  38. package/dist/src/services/workspace/migrate-types.d.ts +84 -0
  39. package/dist/src/services/workspace/migrate-types.js +21 -0
  40. package/dist/src/services/workspace/reconcile-service.d.ts +119 -0
  41. package/dist/src/services/workspace/reconcile-service.js +464 -0
  42. package/dist/src/services/workspace/reconcile-types.d.ts +93 -0
  43. package/dist/src/services/workspace/reconcile-types.js +13 -0
  44. package/dist/src/services/workspace/workspace-service.d.ts +11 -0
  45. package/dist/src/services/workspace/workspace-service.js +87 -7
  46. package/dist/src/shared/change-id.d.ts +59 -0
  47. package/dist/src/shared/change-id.js +194 -16
  48. package/dist/src/shared/version.d.ts +1 -1
  49. package/dist/src/shared/version.js +1 -1
  50. package/package.json +13 -2
  51. package/skills/peaks-solo/SKILL.md +28 -4
  52. package/skills/peaks-solo/references/micro-cycle.md +155 -0
  53. package/skills/peaks-solo/references/runbook.md +2 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Type envelope for the `peaks slice check` CLI command.
3
+ *
4
+ * `peaks slice check` is the boundary check for the RD micro-cycle
5
+ * (see `skills/peaks-solo/references/micro-cycle.md`). It bundles the
6
+ * 4 self-checks that must pass at slice end before the slice is handed
7
+ * off to peaks-qa:
8
+ *
9
+ * 1. typecheck (`npx tsc --noEmit`)
10
+ * 2. unit tests (`npx vitest run`)
11
+ * 3. 3-way review fan-out (code-review + security-review + perf-baseline)
12
+ * 4. gate machinery (`peaks workflow verify-pipeline --rid <rid>`)
13
+ *
14
+ * The micro-cycle itself (per-bug TDD) runs OUTSIDE slice check — only
15
+ * single-test runs (`vitest -t "<name>"`) are allowed in micro-cycles.
16
+ * This command is for the BOUNDARY, not the inner loop.
17
+ */
18
+ export type SliceCheckStageStatus = 'pass' | 'fail' | 'skipped';
19
+ export type SliceCheckStage = {
20
+ /** Stable id for the stage (matches the runbook's check list). */
21
+ name: 'typecheck' | 'unit-tests' | 'review-fanout' | 'gate-verify-pipeline';
22
+ /** Human-readable description. */
23
+ description: string;
24
+ status: SliceCheckStageStatus;
25
+ /** Wall-clock duration in ms; null if skipped. */
26
+ durationMs: number | null;
27
+ /** Free-form detail (summary line + last error line). */
28
+ detail: string;
29
+ /** Optional structured data (e.g. test counts, gate counts). */
30
+ data?: Record<string, unknown>;
31
+ };
32
+ export type SliceCheckResult = {
33
+ /** Absolute project root the command operated on. */
34
+ projectRoot: string;
35
+ /** Request id the boundary check applies to; null if no slice is active. */
36
+ rid: string | null;
37
+ /** All stages in execution order. */
38
+ stages: SliceCheckStage[];
39
+ /** True iff every stage passed (or was skipped) and the boundary is OK to hand off. */
40
+ boundaryReady: boolean;
41
+ /** Total wall-clock duration in ms. */
42
+ totalDurationMs: number;
43
+ /** Next steps suggested when boundaryReady is false. */
44
+ nextActions: string[];
45
+ };
46
+ export type SliceCheckOptions = {
47
+ projectRoot: string;
48
+ /** When omitted, slice check inspects `.peaks/_runtime/current-change` to find the active rid. */
49
+ rid?: string;
50
+ /**
51
+ * When true, re-run the 3-way review fan-out (peaks-rd's code-review +
52
+ * security-review + perf-baseline sub-agents) even if the review files
53
+ * already exist. The default is to verify presence and skip if all 3 are present.
54
+ */
55
+ refreshFanout: boolean;
56
+ /**
57
+ * When true, skip the unit-test stage. Useful when a slice has no unit
58
+ * tests (e.g. a docs-only or config-only slice).
59
+ */
60
+ skipTests: boolean;
61
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Type envelope for the `peaks slice check` CLI command.
3
+ *
4
+ * `peaks slice check` is the boundary check for the RD micro-cycle
5
+ * (see `skills/peaks-solo/references/micro-cycle.md`). It bundles the
6
+ * 4 self-checks that must pass at slice end before the slice is handed
7
+ * off to peaks-qa:
8
+ *
9
+ * 1. typecheck (`npx tsc --noEmit`)
10
+ * 2. unit tests (`npx vitest run`)
11
+ * 3. 3-way review fan-out (code-review + security-review + perf-baseline)
12
+ * 4. gate machinery (`peaks workflow verify-pipeline --rid <rid>`)
13
+ *
14
+ * The micro-cycle itself (per-bug TDD) runs OUTSIDE slice check — only
15
+ * single-test runs (`vitest -t "<name>"`) are allowed in micro-cycles.
16
+ * This command is for the BOUNDARY, not the inner loop.
17
+ */
18
+ export {};
@@ -7,7 +7,7 @@ export type PipelineGate = {
7
7
  };
8
8
  export type PipelineVerification = {
9
9
  rid: string;
10
- sessionId: string;
10
+ changeId: string;
11
11
  requestType: RequestType;
12
12
  complete: boolean;
13
13
  rdPhase: {
@@ -26,6 +26,9 @@ export type PipelineVerification = {
26
26
  export declare function verifyPipeline(options: {
27
27
  projectRoot: string;
28
28
  rid: string;
29
- sessionId: string;
29
+ /** Optional explicit change-id; when omitted, the RD/QA on-disk location
30
+ * is resolved via showRequestArtifact (which scans all top-level dirs and
31
+ * returns the actual change-id the file lives in). */
32
+ changeId?: string;
30
33
  requestType?: string;
31
34
  }): Promise<PipelineVerification>;
@@ -1,15 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { readFile } from 'node:fs/promises';
4
3
  import { isRequestType } from '../artifacts/artifact-prerequisites.js';
5
- async function readFileContent(path) {
6
- try {
7
- return await readFile(path, 'utf8');
8
- }
9
- catch {
10
- return null;
11
- }
12
- }
4
+ import { showRequestArtifact } from '../artifacts/request-artifact-service.js';
13
5
  function extractState(markdown) {
14
6
  for (const rawLine of markdown.split(/\r?\n/)) {
15
7
  const match = /^-\s*state:\s*(.+?)\s*$/.exec(rawLine.trim());
@@ -18,23 +10,19 @@ function extractState(markdown) {
18
10
  }
19
11
  return 'unknown';
20
12
  }
21
- async function findRequestFile(projectRoot, sessionId, role, rid) {
22
- const dir = join(projectRoot, '.peaks', sessionId, role, 'requests');
23
- if (!existsSync(dir))
13
+ /**
14
+ * As of slice 2026-06-05-change-id-as-unit-of-work, the file's durable
15
+ * scope is the change-id (the `.peaks/<changeId>/` dir the file lives
16
+ * in), NOT the session-id. We resolve the on-disk location via
17
+ * `showRequestArtifact` (which scans all top-level dirs and returns the
18
+ * actual dir the file was found in) instead of assuming
19
+ * `.peaks/<sessionId>/<role>/requests/`.
20
+ */
21
+ async function findRequestFile(projectRoot, role, rid) {
22
+ const artifact = await showRequestArtifact({ projectRoot, role: role, requestId: rid });
23
+ if (artifact === null)
24
24
  return null;
25
- const { readdir } = await import('node:fs/promises');
26
- const entries = await readdir(dir, { withFileTypes: true });
27
- for (const entry of entries) {
28
- if (!entry.isFile() || !entry.name.endsWith('.md'))
29
- continue;
30
- if (entry.name === `${rid}.md` || (/^\d+-/.test(entry.name) && entry.name.endsWith(`-${rid}.md`))) {
31
- const path = join(dir, entry.name);
32
- const content = await readFileContent(path);
33
- if (content)
34
- return { path, content };
35
- }
36
- }
37
- return null;
25
+ return { path: artifact.path, content: artifact.content, changeId: artifact.changeId };
38
26
  }
39
27
  function rdGatesForType(requestType) {
40
28
  const gates = [
@@ -78,31 +66,42 @@ export async function verifyPipeline(options) {
78
66
  const nextActions = [];
79
67
  const rdGates = rdGatesForType(requestType);
80
68
  const qaGates = qaGatesForType(requestType);
81
- // Check RD phase
82
- const rdFile = await findRequestFile(options.projectRoot, options.sessionId, 'rd', options.rid);
69
+ // Resolve RD + QA on-disk locations via showRequestArtifact (the change-id
70
+ // is whatever dir the file actually lives in, not the caller's session-id).
71
+ const rdFile = await findRequestFile(options.projectRoot, 'rd', options.rid);
83
72
  let rdInvoked = false;
84
73
  let rdState = 'missing';
74
+ // The resolved change-id is the on-disk location the file actually
75
+ // lives in. The caller's `options.changeId` is a hint used for
76
+ // path construction (nextActions strings), NOT for the resolved
77
+ // changeId field — the on-disk location is the source of truth.
78
+ let resolvedChangeId = '';
85
79
  if (rdFile) {
86
80
  rdInvoked = true;
87
81
  rdState = extractState(rdFile.content);
88
82
  rdGates[0].passed = true;
89
83
  rdGates[0].detail = `found at ${rdFile.path}`;
84
+ resolvedChangeId = rdFile.changeId;
90
85
  }
91
86
  else {
92
87
  violations.push('RD phase skipped: peaks-rd was never invoked for this request (no RD request artifact found)');
93
88
  nextActions.push('Invoke Skill(skill="peaks-rd") with the request-id, then run unit tests + code review + security review');
94
89
  rdGates[0].detail = 'not found';
95
90
  }
96
- // Check RD evidence files
91
+ // Check RD evidence files (under the change-id dir the RD request lives in)
97
92
  const RD_EVIDENCE_FILE = {
98
93
  'tech-doc': 'tech-doc.md',
99
94
  'bug-analysis': 'bug-analysis.md',
100
95
  'code-review': 'code-review.md',
101
96
  'security-review': 'security-review.md'
102
97
  };
98
+ // The evidence dir: prefer the on-disk changeId; fall back to the
99
+ // caller's hint; final fallback to the requestId (back-compat for
100
+ // pre-1.3.0 trees where the file lived under .peaks/<rid>/).
101
+ const rdEvidenceDir = resolvedChangeId || options.changeId || options.rid;
103
102
  for (const gate of rdGates.slice(1)) {
104
103
  const fileName = RD_EVIDENCE_FILE[gate.name];
105
- const evidencePath = join(options.projectRoot, '.peaks', options.sessionId, 'rd', fileName);
104
+ const evidencePath = join(options.projectRoot, '.peaks', rdEvidenceDir, 'rd', fileName);
106
105
  if (existsSync(evidencePath)) {
107
106
  gate.passed = true;
108
107
  gate.detail = evidencePath;
@@ -110,7 +109,7 @@ export async function verifyPipeline(options) {
110
109
  else {
111
110
  gate.detail = `missing: ${evidencePath}`;
112
111
  violations.push(`RD evidence missing: ${gate.description} (${fileName})`);
113
- nextActions.push(`Create .peaks/${options.sessionId}/rd/${fileName}`);
112
+ nextActions.push(`Create .peaks/${rdEvidenceDir}/rd/${fileName}`);
114
113
  }
115
114
  }
116
115
  // Check if RD reached qa-handoff
@@ -119,7 +118,7 @@ export async function verifyPipeline(options) {
119
118
  nextActions.push(`Complete RD gates → peaks request transition ${options.rid} --role rd --state qa-handoff`);
120
119
  }
121
120
  // Check QA phase
122
- const qaFile = await findRequestFile(options.projectRoot, options.sessionId, 'qa', options.rid);
121
+ const qaFile = await findRequestFile(options.projectRoot, 'qa', options.rid);
123
122
  let qaInvoked = false;
124
123
  let qaState = 'missing';
125
124
  if (qaFile) {
@@ -127,13 +126,14 @@ export async function verifyPipeline(options) {
127
126
  qaState = extractState(qaFile.content);
128
127
  qaGates[0].passed = true;
129
128
  qaGates[0].detail = `found at ${qaFile.path}`;
129
+ resolvedChangeId = qaFile.changeId || resolvedChangeId;
130
130
  }
131
131
  else {
132
132
  violations.push('QA phase skipped: peaks-qa was never invoked for this request (no QA request artifact found)');
133
133
  nextActions.push('Invoke Skill(skill="peaks-qa") with the request-id for functional/performance/security testing');
134
134
  qaGates[0].detail = 'not found';
135
135
  }
136
- // Check QA evidence files
136
+ // Check QA evidence files (under the same change-id dir)
137
137
  const QA_EVIDENCE_FILE = {
138
138
  'test-cases': `test-cases/${options.rid}.md`,
139
139
  'test-report': `test-reports/${options.rid}.md`,
@@ -142,7 +142,7 @@ export async function verifyPipeline(options) {
142
142
  };
143
143
  for (const gate of qaGates.slice(1)) {
144
144
  const fileName = QA_EVIDENCE_FILE[gate.name];
145
- const evidencePath = join(options.projectRoot, '.peaks', options.sessionId, 'qa', fileName);
145
+ const evidencePath = join(options.projectRoot, '.peaks', rdEvidenceDir, 'qa', fileName);
146
146
  if (existsSync(evidencePath)) {
147
147
  gate.passed = true;
148
148
  gate.detail = evidencePath;
@@ -150,7 +150,7 @@ export async function verifyPipeline(options) {
150
150
  else {
151
151
  gate.detail = `missing: ${evidencePath}`;
152
152
  violations.push(`QA evidence missing: ${gate.description} (${fileName})`);
153
- nextActions.push(`Create .peaks/${options.sessionId}/qa/${fileName}`);
153
+ nextActions.push(`Create .peaks/${rdEvidenceDir}/qa/${fileName}`);
154
154
  }
155
155
  }
156
156
  // Check if QA reached verdict-issued
@@ -169,7 +169,7 @@ export async function verifyPipeline(options) {
169
169
  && RD_QA_HANDOFF_STATES.has(rdState) && QA_COMPLETE_STATES.has(qaState);
170
170
  return {
171
171
  rid: options.rid,
172
- sessionId: options.sessionId,
172
+ changeId: resolvedChangeId,
173
173
  requestType,
174
174
  complete,
175
175
  rdPhase: { invoked: rdInvoked, state: rdState, gates: rdGates },
@@ -0,0 +1,2 @@
1
+ import type { MigrateOptions, MigrateResult } from './migrate-types.js';
2
+ export declare function migrateWorkspace(options: MigrateOptions): Promise<MigrateResult>;