@slope-dev/slope 1.59.2 → 1.60.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 (135) hide show
  1. package/dist/cli/actor.d.ts +18 -0
  2. package/dist/cli/actor.d.ts.map +1 -0
  3. package/dist/cli/actor.js +133 -0
  4. package/dist/cli/actor.js.map +1 -0
  5. package/dist/cli/commands/agent.d.ts.map +1 -1
  6. package/dist/cli/commands/agent.js +8 -6
  7. package/dist/cli/commands/agent.js.map +1 -1
  8. package/dist/cli/commands/briefing.d.ts.map +1 -1
  9. package/dist/cli/commands/briefing.js +12 -1
  10. package/dist/cli/commands/briefing.js.map +1 -1
  11. package/dist/cli/commands/claim.d.ts.map +1 -1
  12. package/dist/cli/commands/claim.js +10 -6
  13. package/dist/cli/commands/claim.js.map +1 -1
  14. package/dist/cli/commands/commit-ready.d.ts.map +1 -1
  15. package/dist/cli/commands/commit-ready.js +4 -2
  16. package/dist/cli/commands/commit-ready.js.map +1 -1
  17. package/dist/cli/commands/init.d.ts.map +1 -1
  18. package/dist/cli/commands/init.js +6 -0
  19. package/dist/cli/commands/init.js.map +1 -1
  20. package/dist/cli/commands/interview.d.ts.map +1 -1
  21. package/dist/cli/commands/interview.js +5 -0
  22. package/dist/cli/commands/interview.js.map +1 -1
  23. package/dist/cli/commands/release.d.ts.map +1 -1
  24. package/dist/cli/commands/release.js +7 -3
  25. package/dist/cli/commands/release.js.map +1 -1
  26. package/dist/cli/commands/retro.d.ts.map +1 -1
  27. package/dist/cli/commands/retro.js +27 -6
  28. package/dist/cli/commands/retro.js.map +1 -1
  29. package/dist/cli/commands/review-run.d.ts +2 -0
  30. package/dist/cli/commands/review-run.d.ts.map +1 -1
  31. package/dist/cli/commands/review-run.js +38 -8
  32. package/dist/cli/commands/review-run.js.map +1 -1
  33. package/dist/cli/commands/review-state.d.ts +2 -5
  34. package/dist/cli/commands/review-state.d.ts.map +1 -1
  35. package/dist/cli/commands/review-state.js +166 -43
  36. package/dist/cli/commands/review-state.js.map +1 -1
  37. package/dist/cli/commands/roadmap.d.ts.map +1 -1
  38. package/dist/cli/commands/roadmap.js +17 -4
  39. package/dist/cli/commands/roadmap.js.map +1 -1
  40. package/dist/cli/commands/sprint.d.ts.map +1 -1
  41. package/dist/cli/commands/sprint.js +255 -28
  42. package/dist/cli/commands/sprint.js.map +1 -1
  43. package/dist/cli/commands/start.d.ts.map +1 -1
  44. package/dist/cli/commands/start.js +20 -4
  45. package/dist/cli/commands/start.js.map +1 -1
  46. package/dist/cli/commands/ticket.d.ts.map +1 -1
  47. package/dist/cli/commands/ticket.js +31 -9
  48. package/dist/cli/commands/ticket.js.map +1 -1
  49. package/dist/cli/commands/version.d.ts +12 -0
  50. package/dist/cli/commands/version.d.ts.map +1 -1
  51. package/dist/cli/commands/version.js +169 -4
  52. package/dist/cli/commands/version.js.map +1 -1
  53. package/dist/cli/git-preflight.d.ts +9 -0
  54. package/dist/cli/git-preflight.d.ts.map +1 -0
  55. package/dist/cli/git-preflight.js +36 -0
  56. package/dist/cli/git-preflight.js.map +1 -0
  57. package/dist/cli/guards/phase-boundary.d.ts.map +1 -1
  58. package/dist/cli/guards/phase-boundary.js +52 -8
  59. package/dist/cli/guards/phase-boundary.js.map +1 -1
  60. package/dist/cli/guards/post-push.d.ts.map +1 -1
  61. package/dist/cli/guards/post-push.js +7 -1
  62. package/dist/cli/guards/post-push.js.map +1 -1
  63. package/dist/cli/guards/pr-review.d.ts +6 -0
  64. package/dist/cli/guards/pr-review.d.ts.map +1 -1
  65. package/dist/cli/guards/pr-review.js +22 -11
  66. package/dist/cli/guards/pr-review.js.map +1 -1
  67. package/dist/cli/guards/session-briefing.d.ts.map +1 -1
  68. package/dist/cli/guards/session-briefing.js +20 -2
  69. package/dist/cli/guards/session-briefing.js.map +1 -1
  70. package/dist/cli/guards/sprint-completion.js +14 -1
  71. package/dist/cli/guards/sprint-completion.js.map +1 -1
  72. package/dist/cli/guards/workflow-step-gate.d.ts.map +1 -1
  73. package/dist/cli/guards/workflow-step-gate.js +25 -6
  74. package/dist/cli/guards/workflow-step-gate.js.map +1 -1
  75. package/dist/cli/registry.d.ts.map +1 -1
  76. package/dist/cli/registry.js +20 -3
  77. package/dist/cli/registry.js.map +1 -1
  78. package/dist/cli/reviewer-agents.d.ts +22 -0
  79. package/dist/cli/reviewer-agents.d.ts.map +1 -0
  80. package/dist/cli/reviewer-agents.js +141 -0
  81. package/dist/cli/reviewer-agents.js.map +1 -0
  82. package/dist/cli/sprint-state.d.ts +28 -2
  83. package/dist/cli/sprint-state.d.ts.map +1 -1
  84. package/dist/cli/sprint-state.js +146 -9
  85. package/dist/cli/sprint-state.js.map +1 -1
  86. package/dist/cli/workflow-resync.d.ts +2 -0
  87. package/dist/cli/workflow-resync.d.ts.map +1 -1
  88. package/dist/cli/workflow-resync.js +25 -4
  89. package/dist/cli/workflow-resync.js.map +1 -1
  90. package/dist/core/analyzers/git.d.ts.map +1 -1
  91. package/dist/core/analyzers/git.js +95 -5
  92. package/dist/core/analyzers/git.js.map +1 -1
  93. package/dist/core/constants.d.ts.map +1 -1
  94. package/dist/core/constants.js +1 -0
  95. package/dist/core/constants.js.map +1 -1
  96. package/dist/core/findings.d.ts +20 -0
  97. package/dist/core/findings.d.ts.map +1 -0
  98. package/dist/core/findings.js +85 -0
  99. package/dist/core/findings.js.map +1 -0
  100. package/dist/core/generators/first-sprint.js +1 -1
  101. package/dist/core/generators/first-sprint.js.map +1 -1
  102. package/dist/core/generators/roadmap.js +3 -3
  103. package/dist/core/generators/roadmap.js.map +1 -1
  104. package/dist/core/index.d.ts +3 -1
  105. package/dist/core/index.d.ts.map +1 -1
  106. package/dist/core/index.js +2 -0
  107. package/dist/core/index.js.map +1 -1
  108. package/dist/core/interview-engine.d.ts.map +1 -1
  109. package/dist/core/interview-engine.js +5 -4
  110. package/dist/core/interview-engine.js.map +1 -1
  111. package/dist/core/interview-metaphor.d.ts +5 -0
  112. package/dist/core/interview-metaphor.d.ts.map +1 -0
  113. package/dist/core/interview-metaphor.js +20 -0
  114. package/dist/core/interview-metaphor.js.map +1 -0
  115. package/dist/core/interview-steps.d.ts.map +1 -1
  116. package/dist/core/interview-steps.js +3 -6
  117. package/dist/core/interview-steps.js.map +1 -1
  118. package/dist/core/interview.d.ts.map +1 -1
  119. package/dist/core/interview.js +5 -6
  120. package/dist/core/interview.js.map +1 -1
  121. package/dist/core/types.d.ts +11 -1
  122. package/dist/core/types.d.ts.map +1 -1
  123. package/dist/store/index.d.ts +13 -0
  124. package/dist/store/index.d.ts.map +1 -1
  125. package/dist/store/index.js +60 -7
  126. package/dist/store/index.js.map +1 -1
  127. package/package.json +1 -1
  128. package/src/core/workflows/sprint-standard.yaml +6 -0
  129. package/templates/claude-code/commands/post-sprint.md +2 -0
  130. package/templates/claude-code/commands/start-sprint.md +1 -0
  131. package/templates/codex/plugins/slope/.codex-plugin/plugin.json +1 -1
  132. package/templates/codex/plugins/slope/skills/slope-retro/SKILL.md +1 -1
  133. package/templates/generic/SLOPE-CHECKLIST.md +3 -2
  134. package/templates/pi/skills/post-sprint.md +2 -0
  135. package/templates/pi/skills/start-sprint.md +1 -0
@@ -1,6 +1,7 @@
1
- import { loadSprintState, saveSprintState, createSprintState, mutateSprintState, updateGate, updateSprintPhase, clearSprintState, isSprintComplete, pendingGates, isSprintPhase, SPRINT_PHASES, } from '../sprint-state.js';
1
+ import { loadSprintState, saveSprintState, createSprintState, mutateSprintState, updateGate, updateSprintPhase, clearSprintState, isSprintComplete, isReviewGateSatisfied, pendingGateNames, pendingGates, isSprintPhase, isReviewGateName, validateReviewGateCompletion, SPRINT_PHASES, } from '../sprint-state.js';
2
2
  import { WorkflowEngine, loadWorkflow, resolveVariables, validateWorkflow, loadConfig, parseRoadmap, castRoadmapStructure, formatSprintLabel, formatSprintNumber, parseSprintNumber } from '../../core/index.js';
3
3
  import { createHash } from 'node:crypto';
4
+ import { formatActorName, formatActorSource, formatConflictSummary, resolveActor } from '../actor.js';
4
5
  /** Get workflow definition from execution snapshot (preferred) or disk (fallback for old executions) */
5
6
  function getDefinition(exec, cwd) {
6
7
  // Prefer snapshot from execution
@@ -25,7 +26,7 @@ import { createStore } from '../../store/index.js';
25
26
  import { formatCliError } from '../error-reporter.js';
26
27
  import { buildSprintResumePointer, planPortableSprintResume, writeSprintResumePointer, } from '../sprint-resume.js';
27
28
  import { blockingRoadmapIssuesForSprint, collectSiblingWorktreeReality, findWorktreeOverlaps, formatWorktreeRealitySection, loadRoadmapReality, parseTouchedPaths, } from '../pre-sprint-reality.js';
28
- import { findStaleWorkflowExecutions, reconcileWorkflowExecutions } from '../workflow-resync.js';
29
+ import { findStaleWorkflowExecutions, reconcileWorkflowExecutions, sprintLabelForExecution } from '../workflow-resync.js';
29
30
  /**
30
31
  * Check completion_conditions for a step before allowing completion/skip.
31
32
  * Returns null if all conditions met, or an error message string.
@@ -67,6 +68,148 @@ function checkCompletionConditions(stepId, def, currentPhase, cwd) {
67
68
  return null;
68
69
  }
69
70
  const VALID_GATES = ['tests', 'code_review', 'architect_review', 'scorecard', 'review_md'];
71
+ function optionValue(args, index, flag) {
72
+ const arg = args[index];
73
+ if (arg.startsWith(`${flag}=`))
74
+ return { value: arg.slice(flag.length + 1), next: index };
75
+ if (arg === flag)
76
+ return { value: args[index + 1] ?? null, next: index + 1 };
77
+ return null;
78
+ }
79
+ function findOptionValue(args, flag) {
80
+ for (let i = 0; i < args.length; i++) {
81
+ const value = optionValue(args, i, flag);
82
+ if (value)
83
+ return value.value ?? undefined;
84
+ }
85
+ return undefined;
86
+ }
87
+ function actorOverride(args) {
88
+ for (const flag of ['--actor', '--player']) {
89
+ const value = findOptionValue(args, flag);
90
+ if (value && !value.startsWith('--'))
91
+ return value;
92
+ }
93
+ return undefined;
94
+ }
95
+ function parseGateOptions(args) {
96
+ const errors = [];
97
+ const evidence = [];
98
+ let reviewer;
99
+ let prReview;
100
+ let selfReview = false;
101
+ let notes;
102
+ let reason;
103
+ let overrideReason;
104
+ let help = false;
105
+ for (let i = 0; i < args.length; i++) {
106
+ const arg = args[i];
107
+ if (arg === '--help' || arg === '-h') {
108
+ help = true;
109
+ continue;
110
+ }
111
+ const evidenceValue = optionValue(args, i, '--evidence');
112
+ if (evidenceValue) {
113
+ if (!evidenceValue.value || evidenceValue.value.startsWith('--'))
114
+ errors.push('--evidence requires a value');
115
+ else
116
+ evidence.push(evidenceValue.value);
117
+ i = evidenceValue.next;
118
+ continue;
119
+ }
120
+ const reviewerValue = optionValue(args, i, '--reviewer');
121
+ if (reviewerValue) {
122
+ if (!reviewerValue.value || reviewerValue.value.startsWith('--'))
123
+ errors.push('--reviewer requires a value');
124
+ else
125
+ reviewer = reviewerValue.value;
126
+ i = reviewerValue.next;
127
+ continue;
128
+ }
129
+ const prReviewValue = optionValue(args, i, '--pr-review');
130
+ if (prReviewValue) {
131
+ if (!prReviewValue.value || prReviewValue.value.startsWith('--'))
132
+ errors.push('--pr-review requires a value');
133
+ else
134
+ prReview = prReviewValue.value;
135
+ i = prReviewValue.next;
136
+ continue;
137
+ }
138
+ if (arg === '--self-review') {
139
+ selfReview = true;
140
+ continue;
141
+ }
142
+ const reasonValue = optionValue(args, i, '--reason');
143
+ if (reasonValue) {
144
+ if (!reasonValue.value || reasonValue.value.startsWith('--'))
145
+ errors.push('--reason requires a value');
146
+ else
147
+ reason = reasonValue.value;
148
+ i = reasonValue.next;
149
+ continue;
150
+ }
151
+ const notesValue = optionValue(args, i, '--notes');
152
+ if (notesValue) {
153
+ if (!notesValue.value || notesValue.value.startsWith('--'))
154
+ errors.push('--notes requires a value');
155
+ else
156
+ notes = notesValue.value;
157
+ i = notesValue.next;
158
+ continue;
159
+ }
160
+ const overrideValue = optionValue(args, i, '--override') ?? optionValue(args, i, '--manual-override');
161
+ if (overrideValue) {
162
+ if (!overrideValue.value || overrideValue.value.startsWith('--'))
163
+ errors.push('--override requires a value');
164
+ else
165
+ overrideReason = overrideValue.value;
166
+ i = overrideValue.next;
167
+ continue;
168
+ }
169
+ errors.push(`Unknown gate option: ${arg}`);
170
+ }
171
+ const hasPrReview = Boolean(prReview);
172
+ const hasSelfReview = selfReview;
173
+ const hasOverride = Boolean(overrideReason);
174
+ const hasIndependent = !hasPrReview && !hasSelfReview && !hasOverride && (Boolean(reviewer) || evidence.length > 0);
175
+ const modeCount = [hasPrReview, hasSelfReview, hasOverride, hasIndependent].filter(Boolean).length;
176
+ if (modeCount > 1) {
177
+ errors.push('Use only one review provenance mode: --reviewer/--evidence, --pr-review, --self-review, or --override.');
178
+ }
179
+ let review;
180
+ if (modeCount === 1) {
181
+ if (hasPrReview) {
182
+ review = { provenance: 'pr_review', evidence: [prReview, ...evidence], reviewer, notes: notes ?? reason };
183
+ }
184
+ else if (hasSelfReview) {
185
+ review = { provenance: 'self_review', evidence, reviewer, notes: reason ?? notes };
186
+ }
187
+ else if (hasOverride) {
188
+ review = { provenance: 'manual_override', evidence, reviewer, notes: overrideReason };
189
+ }
190
+ else {
191
+ review = { provenance: 'independent_review', evidence, reviewer, notes: notes ?? reason };
192
+ }
193
+ }
194
+ return { review, errors, help };
195
+ }
196
+ function printGateUsage(gateName) {
197
+ const gate = gateName && isReviewGateName(gateName) ? gateName : 'code_review';
198
+ console.error('');
199
+ console.error('Usage:');
200
+ console.error(' slope sprint gate <name>');
201
+ console.error(` slope sprint gate ${gate} --reviewer=<agent-or-person> --evidence=<transcript-or-output>`);
202
+ console.error(` slope sprint gate ${gate} --pr-review=<url-or-id>`);
203
+ console.error(` slope sprint gate ${gate} --self-review --reason="why self-review is acceptable"`);
204
+ console.error(` slope sprint gate ${gate} --override="manual override reason"`);
205
+ console.error('');
206
+ console.error('Review provenance modes:');
207
+ console.error(' independent_review: requires --reviewer and --evidence');
208
+ console.error(' pr_review: records external PR review evidence from --pr-review');
209
+ console.error(' self_review (weaker): requires --self-review and --reason');
210
+ console.error(' manual_override (weaker): requires --override');
211
+ console.error('');
212
+ }
70
213
  /**
71
214
  * `slope sprint begin --sprint=N --ticket=T` — bundled start-of-work flow.
72
215
  *
@@ -82,7 +225,7 @@ async function beginCommand(args, cwd) {
82
225
  const sprintArg = args.find(a => a.startsWith('--sprint='));
83
226
  const ticketArg = args.find(a => a.startsWith('--ticket='));
84
227
  if (!sprintArg || !ticketArg) {
85
- console.error('\nUsage: slope sprint begin --sprint=N --ticket=KEY');
228
+ console.error('\nUsage: slope sprint begin --sprint=N --ticket=KEY [--actor=<name>]');
86
229
  console.error('Bundles: sprint start + claim + briefing + prep --lite\n');
87
230
  process.exit(1);
88
231
  }
@@ -110,13 +253,15 @@ async function beginCommand(args, cwd) {
110
253
  // Step 2: claim
111
254
  const { resolveStore } = await import('../store.js');
112
255
  const { checkConflicts } = await import('../../core/index.js');
113
- const player = process.env.USER || 'unknown';
256
+ const actor = resolveActor(cwd, { explicitActor: actorOverride(args) });
257
+ const player = actor.name;
258
+ const playerDisplay = formatActorName(actor);
114
259
  const store = await resolveStore(cwd);
115
260
  try {
116
261
  const existing = await store.list(sprint);
117
262
  const ownClaim = existing.find(c => c.target === ticket && c.player === player);
118
263
  if (ownClaim) {
119
- console.log(`Ticket ${ticket}: already claimed by ${player}.`);
264
+ console.log(`Ticket ${ticket}: already claimed by ${playerDisplay} (actor source: ${formatActorSource(actor)}).`);
120
265
  }
121
266
  else {
122
267
  // Detect overlap conflicts via core check
@@ -132,12 +277,12 @@ async function beginCommand(args, cwd) {
132
277
  if (overlaps.length > 0) {
133
278
  console.error(`\nClaim blocked — overlap conflict(s) detected:`);
134
279
  for (const c of overlaps)
135
- console.error(` [!!] ${c.reason}`);
280
+ console.error(` [!!] ${formatConflictSummary(c)}`);
136
281
  console.error(`\nResolve conflicts or run \`slope claim --target=${ticket} --sprint=${formatSprintNumber(sprint)} --force\` to override.`);
137
282
  process.exit(1);
138
283
  }
139
284
  const claim = await store.claim({ sprint_number: sprint, player, target: ticket, scope: 'ticket' });
140
- console.log(`Ticket ${ticket}: claimed (id ${claim.id.slice(0, 8)}, player ${player}).`);
285
+ console.log(`Ticket ${ticket}: claimed (id ${claim.id.slice(0, 8)}, player ${playerDisplay}, actor source: ${formatActorSource(actor)}).`);
141
286
  }
142
287
  }
143
288
  finally {
@@ -250,21 +395,23 @@ async function startCommand(args, cwd) {
250
395
  }
251
396
  const state = createSprintState(sprint, phase);
252
397
  saveSprintState(cwd, state);
253
- const autoClaim = await autoClaimSprint(cwd, sprint);
254
- console.log(`Sprint ${formatSprintNumber(sprint)} started (phase: ${phase}). Use 'slope sprint gate <name>' to mark gates.`);
398
+ const autoClaim = await autoClaimSprint(cwd, sprint, actorOverride(args));
399
+ console.log(`Sprint ${formatSprintNumber(sprint)} started (phase: ${phase}). Use 'slope sprint gate <name>' to mark gates; review gates require evidence options.`);
255
400
  if (autoClaim)
256
401
  console.log(autoClaim);
257
402
  }
258
- async function autoClaimSprint(cwd, sprint) {
403
+ async function autoClaimSprint(cwd, sprint, explicitActor) {
259
404
  const { resolveStore } = await import('../store.js');
260
- const player = process.env.USER || 'unknown';
405
+ const actor = resolveActor(cwd, { explicitActor });
406
+ const player = actor.name;
407
+ const playerDisplay = formatActorName(actor);
261
408
  const target = `sprint:${formatSprintLabel(sprint)}`;
262
409
  try {
263
410
  const store = await resolveStore(cwd);
264
411
  try {
265
412
  const existing = await store.list(sprint);
266
413
  if (existing.some(c => c.player === player && c.target === target)) {
267
- return `Claim: ${target} already held by ${player}.`;
414
+ return `Claim: ${target} already held by ${playerDisplay} (actor source: ${formatActorSource(actor)}).`;
268
415
  }
269
416
  const claim = await store.claim({
270
417
  sprint_number: sprint,
@@ -273,7 +420,7 @@ async function autoClaimSprint(cwd, sprint) {
273
420
  scope: 'area',
274
421
  notes: 'auto-claimed by slope sprint start',
275
422
  });
276
- return `Claim: ${claim.target} (${claim.scope}) auto-claimed for ${player}.`;
423
+ return `Claim: ${claim.target} (${claim.scope}) auto-claimed for ${playerDisplay} (actor source: ${formatActorSource(actor)}).`;
277
424
  }
278
425
  finally {
279
426
  store.close();
@@ -306,25 +453,82 @@ function gateCommand(args, cwd) {
306
453
  const gateName = args[0];
307
454
  if (!gateName || !VALID_GATES.includes(gateName)) {
308
455
  console.error(`Error: gate name required. Valid gates: ${VALID_GATES.join(', ')}`);
456
+ printGateUsage();
457
+ process.exit(1);
458
+ }
459
+ const options = parseGateOptions(args.slice(1));
460
+ if (options.help) {
461
+ printGateUsage(gateName);
462
+ return;
463
+ }
464
+ if (options.errors.length > 0) {
465
+ for (const error of options.errors)
466
+ console.error(`Error: ${error}`);
467
+ printGateUsage(gateName);
468
+ process.exit(1);
469
+ }
470
+ if (!isReviewGateName(gateName) && options.review) {
471
+ console.error('Error: review evidence options only apply to code_review and architect_review gates.');
309
472
  process.exit(1);
310
473
  }
474
+ if (isReviewGateName(gateName)) {
475
+ const validation = validateReviewGateCompletion(options.review);
476
+ if (validation) {
477
+ console.error(`Error: ${validation}.`);
478
+ printGateUsage(gateName);
479
+ process.exit(1);
480
+ }
481
+ }
311
482
  const state = loadSprintState(cwd);
312
483
  if (!state) {
313
484
  console.error("No active sprint. Run 'slope sprint start --number=N' first.");
314
485
  process.exit(1);
315
486
  }
316
- if (state.gates[gateName]) {
487
+ if (state.gates[gateName] && (!isReviewGateName(gateName) || !options.review)) {
317
488
  console.log(`Gate '${gateName}' is already complete.`);
318
489
  return;
319
490
  }
320
- updateGate(cwd, gateName, true);
491
+ const changed = updateGate(cwd, gateName, true, { review: options.review });
492
+ if (!changed) {
493
+ console.error(`Error: could not update gate '${gateName}'.`);
494
+ process.exit(1);
495
+ }
321
496
  const updated = loadSprintState(cwd);
322
497
  const remaining = pendingGates(updated);
498
+ const provenance = isReviewGateName(gateName) ? ` (${updated.review_gates[gateName].provenance})` : '';
323
499
  if (remaining.length === 0) {
324
- console.log(`Gate '${gateName}' marked complete. All gates done ready for PR!`);
500
+ console.log(`Gate '${gateName}' marked complete${provenance}. All gates done - ready for PR!`);
325
501
  }
326
502
  else {
327
- console.log(`Gate '${gateName}' marked complete. Remaining: ${remaining.join(', ')}`);
503
+ console.log(`Gate '${gateName}' marked complete${provenance}. Remaining: ${remaining.join(', ')}`);
504
+ }
505
+ }
506
+ function formatReviewEvidence(review) {
507
+ return review.evidence.length > 0 ? review.evidence.join(', ') : '(missing)';
508
+ }
509
+ function formatReviewGateStatus(state, gate) {
510
+ const review = state.review_gates[gate];
511
+ if (!state.gates[gate] || review.provenance === 'pending') {
512
+ return 'pending review evidence';
513
+ }
514
+ const validation = validateReviewGateCompletion({
515
+ provenance: review.provenance,
516
+ evidence: review.evidence,
517
+ reviewer: review.reviewer,
518
+ notes: review.notes,
519
+ });
520
+ if (validation) {
521
+ return `invalid ${review.provenance}: ${validation}`;
522
+ }
523
+ switch (review.provenance) {
524
+ case 'independent_review':
525
+ return `independent_review; reviewer=${review.reviewer}; evidence=${formatReviewEvidence(review)}`;
526
+ case 'pr_review':
527
+ return `pr_review; evidence=${formatReviewEvidence(review)}`;
528
+ case 'self_review':
529
+ return `self_review (weaker); reason=${review.notes}`;
530
+ case 'manual_override':
531
+ return `manual_override (weaker); reason=${review.notes}`;
328
532
  }
329
533
  }
330
534
  function statusCommand(cwd) {
@@ -334,19 +538,38 @@ function statusCommand(cwd) {
334
538
  return;
335
539
  }
336
540
  const complete = isSprintComplete(state);
337
- console.log(`Sprint ${formatSprintNumber(state.sprint)} phase: ${state.phase}${complete ? ' (all gates complete)' : ''}`);
541
+ const derivedStatus = complete ? 'ready_for_pr' : state.phase;
542
+ const phaseContext = complete ? ` (phase: ${state.phase}, all gates complete)` : ` (phase: ${state.phase})`;
543
+ console.log(`Sprint ${formatSprintNumber(state.sprint)} - status: ${derivedStatus}${phaseContext}`);
338
544
  console.log(`Started: ${state.started_at}`);
339
545
  console.log(`Updated: ${state.updated_at}`);
340
546
  console.log('');
341
547
  console.log('Gates:');
342
548
  for (const [gate, done] of Object.entries(state.gates)) {
343
- const marker = done ? '[x]' : '[ ]';
344
- console.log(` ${marker} ${gate}`);
549
+ if (isReviewGateName(gate)) {
550
+ const satisfied = isReviewGateSatisfied(state, gate);
551
+ const marker = satisfied ? '[x]' : done ? '[!]' : '[ ]';
552
+ console.log(` ${marker} ${gate} (${formatReviewGateStatus(state, gate)})`);
553
+ }
554
+ else {
555
+ const marker = done ? '[x]' : '[ ]';
556
+ console.log(` ${marker} ${gate}`);
557
+ }
345
558
  }
346
559
  if (!complete) {
347
- const pending = pendingGates(state);
560
+ const pendingNames = pendingGateNames(state);
561
+ const pendingLabels = pendingGates(state);
562
+ const pending = pendingNames.map((gate, index) => {
563
+ if (isReviewGateName(gate) && state.gates[gate]) {
564
+ return `${gate} (review evidence incomplete)`;
565
+ }
566
+ return pendingLabels[index] ?? gate;
567
+ });
348
568
  console.log(`\nRemaining: ${pending.join(', ')}`);
349
569
  }
570
+ else {
571
+ console.log('\nNext: create PR for this branch; after merge, run post-merge retro.');
572
+ }
350
573
  }
351
574
  function resetCommand(cwd) {
352
575
  clearSprintState(cwd);
@@ -614,10 +837,10 @@ async function workflowCleanupCommand(args, cwd) {
614
837
  try {
615
838
  const result = await reconcileWorkflowExecutions(cwd, store);
616
839
  for (const { exec, reason } of result.paused) {
617
- console.log(`Paused ${exec.sprint_id ?? exec.id} (${exec.workflow_name}) at ${exec.current_phase}/${exec.current_step} — ${reason}`);
840
+ console.log(`Paused ${sprintLabelForExecution(exec)} (${exec.workflow_name}) at ${exec.current_phase}/${exec.current_step} — ${reason}`);
618
841
  }
619
842
  for (const item of result.fastForwarded) {
620
- console.log(`Fast-forwarded ${item.exec.sprint_id ?? item.exec.id} (${item.exec.workflow_name}) to ${item.phase}/${item.step} — ${item.reason}`);
843
+ console.log(`Fast-forwarded ${sprintLabelForExecution(item.exec)} (${item.exec.workflow_name}) to ${item.phase}/${item.step} — ${item.reason}`);
621
844
  }
622
845
  if (result.paused.length === 0 && result.fastForwarded.length === 0) {
623
846
  console.log('Workflow state already matches git/roadmap reality.');
@@ -644,7 +867,7 @@ async function workflowCleanupCommand(args, cwd) {
644
867
  }
645
868
  const engine = new WorkflowEngine();
646
869
  for (const { exec, reason } of stale) {
647
- const label = exec.sprint_id ?? exec.id;
870
+ const label = sprintLabelForExecution(exec);
648
871
  if (dryRun) {
649
872
  console.log(`[dry-run] Would pause ${label} (${exec.workflow_name}) at ${exec.current_phase}/${exec.current_step} — ${reason}`);
650
873
  }
@@ -823,7 +1046,8 @@ async function restoreResumeClaims(cwd, plan) {
823
1046
  return 0;
824
1047
  const { resolveStore } = await import('../store.js');
825
1048
  const store = await resolveStore(cwd);
826
- const player = process.env.USER || 'unknown';
1049
+ const actor = resolveActor(cwd);
1050
+ const player = actor.name;
827
1051
  let restored = 0;
828
1052
  try {
829
1053
  const existing = await store.list(plan.sprint);
@@ -1165,12 +1389,15 @@ function printSprintUsage() {
1165
1389
  slope sprint — Sprint lifecycle management
1166
1390
 
1167
1391
  Legacy commands:
1168
- slope sprint start --number=N [--phase=<phase>] [--touches=<paths>] [--force]
1392
+ slope sprint start --number=N [--phase=<phase>] [--touches=<paths>] [--actor=<name>] [--force]
1169
1393
  Start sprint state tracking with pre-sprint reality checks
1170
- slope sprint begin --sprint=N --ticket=T Bundled start + claim + briefing + prep (#311)
1394
+ slope sprint begin --sprint=N --ticket=T [--actor=<name>] Bundled start + claim + briefing + prep (#311)
1171
1395
  slope sprint plan --sprint=N [--output=path] Generate markdown sprint plan (#312)
1172
1396
  slope sprint phase <phase> Update current sprint phase
1173
- slope sprint gate <name> Mark a gate as complete
1397
+ slope sprint gate <name> [review evidence options]
1398
+ Mark a gate as complete
1399
+ Review gates require independent evidence, PR review evidence,
1400
+ or explicit weaker-mode self_review/manual_override provenance
1174
1401
  slope sprint status Show sprint state and gates
1175
1402
  slope sprint resume --portable [--from=path] [--force] [--dry-run]
1176
1403
  Reconstruct local sprint state from tracked artifacts