@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.
- package/dist/cli/actor.d.ts +18 -0
- package/dist/cli/actor.d.ts.map +1 -0
- package/dist/cli/actor.js +133 -0
- package/dist/cli/actor.js.map +1 -0
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/agent.js +8 -6
- package/dist/cli/commands/agent.js.map +1 -1
- package/dist/cli/commands/briefing.d.ts.map +1 -1
- package/dist/cli/commands/briefing.js +12 -1
- package/dist/cli/commands/briefing.js.map +1 -1
- package/dist/cli/commands/claim.d.ts.map +1 -1
- package/dist/cli/commands/claim.js +10 -6
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/commit-ready.d.ts.map +1 -1
- package/dist/cli/commands/commit-ready.js +4 -2
- package/dist/cli/commands/commit-ready.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +6 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/interview.d.ts.map +1 -1
- package/dist/cli/commands/interview.js +5 -0
- package/dist/cli/commands/interview.js.map +1 -1
- package/dist/cli/commands/release.d.ts.map +1 -1
- package/dist/cli/commands/release.js +7 -3
- package/dist/cli/commands/release.js.map +1 -1
- package/dist/cli/commands/retro.d.ts.map +1 -1
- package/dist/cli/commands/retro.js +27 -6
- package/dist/cli/commands/retro.js.map +1 -1
- package/dist/cli/commands/review-run.d.ts +2 -0
- package/dist/cli/commands/review-run.d.ts.map +1 -1
- package/dist/cli/commands/review-run.js +38 -8
- package/dist/cli/commands/review-run.js.map +1 -1
- package/dist/cli/commands/review-state.d.ts +2 -5
- package/dist/cli/commands/review-state.d.ts.map +1 -1
- package/dist/cli/commands/review-state.js +166 -43
- package/dist/cli/commands/review-state.js.map +1 -1
- package/dist/cli/commands/roadmap.d.ts.map +1 -1
- package/dist/cli/commands/roadmap.js +17 -4
- package/dist/cli/commands/roadmap.js.map +1 -1
- package/dist/cli/commands/sprint.d.ts.map +1 -1
- package/dist/cli/commands/sprint.js +255 -28
- package/dist/cli/commands/sprint.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +20 -4
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/ticket.d.ts.map +1 -1
- package/dist/cli/commands/ticket.js +31 -9
- package/dist/cli/commands/ticket.js.map +1 -1
- package/dist/cli/commands/version.d.ts +12 -0
- package/dist/cli/commands/version.d.ts.map +1 -1
- package/dist/cli/commands/version.js +169 -4
- package/dist/cli/commands/version.js.map +1 -1
- package/dist/cli/git-preflight.d.ts +9 -0
- package/dist/cli/git-preflight.d.ts.map +1 -0
- package/dist/cli/git-preflight.js +36 -0
- package/dist/cli/git-preflight.js.map +1 -0
- package/dist/cli/guards/phase-boundary.d.ts.map +1 -1
- package/dist/cli/guards/phase-boundary.js +52 -8
- package/dist/cli/guards/phase-boundary.js.map +1 -1
- package/dist/cli/guards/post-push.d.ts.map +1 -1
- package/dist/cli/guards/post-push.js +7 -1
- package/dist/cli/guards/post-push.js.map +1 -1
- package/dist/cli/guards/pr-review.d.ts +6 -0
- package/dist/cli/guards/pr-review.d.ts.map +1 -1
- package/dist/cli/guards/pr-review.js +22 -11
- package/dist/cli/guards/pr-review.js.map +1 -1
- package/dist/cli/guards/session-briefing.d.ts.map +1 -1
- package/dist/cli/guards/session-briefing.js +20 -2
- package/dist/cli/guards/session-briefing.js.map +1 -1
- package/dist/cli/guards/sprint-completion.js +14 -1
- package/dist/cli/guards/sprint-completion.js.map +1 -1
- package/dist/cli/guards/workflow-step-gate.d.ts.map +1 -1
- package/dist/cli/guards/workflow-step-gate.js +25 -6
- package/dist/cli/guards/workflow-step-gate.js.map +1 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +20 -3
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/reviewer-agents.d.ts +22 -0
- package/dist/cli/reviewer-agents.d.ts.map +1 -0
- package/dist/cli/reviewer-agents.js +141 -0
- package/dist/cli/reviewer-agents.js.map +1 -0
- package/dist/cli/sprint-state.d.ts +28 -2
- package/dist/cli/sprint-state.d.ts.map +1 -1
- package/dist/cli/sprint-state.js +146 -9
- package/dist/cli/sprint-state.js.map +1 -1
- package/dist/cli/workflow-resync.d.ts +2 -0
- package/dist/cli/workflow-resync.d.ts.map +1 -1
- package/dist/cli/workflow-resync.js +25 -4
- package/dist/cli/workflow-resync.js.map +1 -1
- package/dist/core/analyzers/git.d.ts.map +1 -1
- package/dist/core/analyzers/git.js +95 -5
- package/dist/core/analyzers/git.js.map +1 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +1 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/core/findings.d.ts +20 -0
- package/dist/core/findings.d.ts.map +1 -0
- package/dist/core/findings.js +85 -0
- package/dist/core/findings.js.map +1 -0
- package/dist/core/generators/first-sprint.js +1 -1
- package/dist/core/generators/first-sprint.js.map +1 -1
- package/dist/core/generators/roadmap.js +3 -3
- package/dist/core/generators/roadmap.js.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/interview-engine.d.ts.map +1 -1
- package/dist/core/interview-engine.js +5 -4
- package/dist/core/interview-engine.js.map +1 -1
- package/dist/core/interview-metaphor.d.ts +5 -0
- package/dist/core/interview-metaphor.d.ts.map +1 -0
- package/dist/core/interview-metaphor.js +20 -0
- package/dist/core/interview-metaphor.js.map +1 -0
- package/dist/core/interview-steps.d.ts.map +1 -1
- package/dist/core/interview-steps.js +3 -6
- package/dist/core/interview-steps.js.map +1 -1
- package/dist/core/interview.d.ts.map +1 -1
- package/dist/core/interview.js +5 -6
- package/dist/core/interview.js.map +1 -1
- package/dist/core/types.d.ts +11 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/store/index.d.ts +13 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +60 -7
- package/dist/store/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/workflows/sprint-standard.yaml +6 -0
- package/templates/claude-code/commands/post-sprint.md +2 -0
- package/templates/claude-code/commands/start-sprint.md +1 -0
- package/templates/codex/plugins/slope/.codex-plugin/plugin.json +1 -1
- package/templates/codex/plugins/slope/skills/slope-retro/SKILL.md +1 -1
- package/templates/generic/SLOPE-CHECKLIST.md +3 -2
- package/templates/pi/skills/post-sprint.md +2 -0
- 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
|
|
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 ${
|
|
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
|
|
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 ${
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>
|
|
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
|