@slope-dev/slope 1.62.3 → 1.63.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 (51) hide show
  1. package/dist/cli/commands/map.d.ts.map +1 -1
  2. package/dist/cli/commands/map.js +17 -12
  3. package/dist/cli/commands/map.js.map +1 -1
  4. package/dist/cli/commands/next.js +2 -2
  5. package/dist/cli/commands/next.js.map +1 -1
  6. package/dist/cli/commands/retro.d.ts.map +1 -1
  7. package/dist/cli/commands/retro.js +4 -0
  8. package/dist/cli/commands/retro.js.map +1 -1
  9. package/dist/cli/commands/review-packet.d.ts +18 -0
  10. package/dist/cli/commands/review-packet.d.ts.map +1 -0
  11. package/dist/cli/commands/review-packet.js +159 -0
  12. package/dist/cli/commands/review-packet.js.map +1 -0
  13. package/dist/cli/commands/review-state.d.ts.map +1 -1
  14. package/dist/cli/commands/review-state.js +17 -1
  15. package/dist/cli/commands/review-state.js.map +1 -1
  16. package/dist/cli/commands/roadmap.d.ts.map +1 -1
  17. package/dist/cli/commands/roadmap.js +54 -2
  18. package/dist/cli/commands/roadmap.js.map +1 -1
  19. package/dist/cli/commands/sprint.d.ts.map +1 -1
  20. package/dist/cli/commands/sprint.js +154 -20
  21. package/dist/cli/commands/sprint.js.map +1 -1
  22. package/dist/cli/commands/validate.d.ts.map +1 -1
  23. package/dist/cli/commands/validate.js +25 -2
  24. package/dist/cli/commands/validate.js.map +1 -1
  25. package/dist/cli/guards/sprint-completion.js +1 -1
  26. package/dist/cli/guards/sprint-completion.js.map +1 -1
  27. package/dist/cli/registry.d.ts +1 -1
  28. package/dist/cli/registry.d.ts.map +1 -1
  29. package/dist/cli/registry.js +16 -1
  30. package/dist/cli/registry.js.map +1 -1
  31. package/dist/cli/roadmap-source-store.d.ts +10 -0
  32. package/dist/cli/roadmap-source-store.d.ts.map +1 -1
  33. package/dist/cli/roadmap-source-store.js +48 -0
  34. package/dist/cli/roadmap-source-store.js.map +1 -1
  35. package/dist/cli/sprint-inference.d.ts +3 -0
  36. package/dist/cli/sprint-inference.d.ts.map +1 -1
  37. package/dist/cli/sprint-inference.js +62 -23
  38. package/dist/cli/sprint-inference.js.map +1 -1
  39. package/dist/cli/sprint-state.d.ts +14 -0
  40. package/dist/cli/sprint-state.d.ts.map +1 -1
  41. package/dist/cli/sprint-state.js +30 -0
  42. package/dist/cli/sprint-state.js.map +1 -1
  43. package/dist/core/adapters/codex.d.ts.map +1 -1
  44. package/dist/core/adapters/codex.js +8 -2
  45. package/dist/core/adapters/codex.js.map +1 -1
  46. package/dist/core/index.d.ts +1 -1
  47. package/dist/core/index.d.ts.map +1 -1
  48. package/dist/core/index.js +1 -1
  49. package/dist/core/index.js.map +1 -1
  50. package/package.json +1 -1
  51. package/templates/codex/plugins/slope/.codex-plugin/plugin.json +1 -1
@@ -1,5 +1,5 @@
1
- import { loadSprintState, loadSprintStateResult, createSprintState, initializeSprintState, mutateSprintState, updateGate, updateSprintPhase, updateSprintPhaseForSprint, clearSprintState, isSprintComplete, isReviewGateSatisfied, isRequiredReviewGate, waivedReviewGateNames, pendingGateNames, pendingGates, isSprintPhase, isReviewGateName, validateReviewGateCompletion, SPRINT_PHASES, } from '../sprint-state.js';
2
- import { WorkflowEngine, loadWorkflow, resolveVariables, validateWorkflow, loadConfig, parseRoadmap, castRoadmapStructure, formatSprintLabel, formatSprintNumber, parseSprintNumber } from '../../core/index.js';
1
+ import { loadSprintState, loadSprintStateResult, createSprintState, initializeSprintState, mutateSprintState, updateGate, updateSprintPhase, updateSprintPhaseForSprint, clearSprintState, isSprintComplete, isReviewGateSatisfied, isRequiredReviewGate, waivedReviewGateNames, pendingGateNames, pendingGates, isSprintPhase, isReviewGateName, isReviewGateVerdict, validateReviewGateCompletion, SPRINT_PHASES, } from '../sprint-state.js';
2
+ import { WorkflowEngine, loadWorkflow, resolveVariables, validateWorkflow, loadConfig, parseRoadmap, castRoadmapStructure, formatSprintLabel, formatSprintNumber, formatRoadmapSprintLabel, parseSprintNumber } from '../../core/index.js';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { formatActorName, formatActorSource, formatConflictSummary, resolveActor } from '../actor.js';
5
5
  import { inspectSprintRollover, performSprintRollover, SprintRolloverError, verifySprintRolloverLineage, } from '../sprint-rollover.js';
@@ -103,6 +103,12 @@ function parseGateOptions(args) {
103
103
  let notes;
104
104
  let reason;
105
105
  let overrideReason;
106
+ let verdict;
107
+ let packet;
108
+ let reviewedCommit;
109
+ let tokenBudget;
110
+ let tokensUsed;
111
+ let overBudgetReason;
106
112
  let help = false;
107
113
  for (let i = 0; i < args.length; i++) {
108
114
  const arg = args[i];
@@ -137,6 +143,69 @@ function parseGateOptions(args) {
137
143
  i = prReviewValue.next;
138
144
  continue;
139
145
  }
146
+ const verdictValue = optionValue(args, i, '--verdict');
147
+ if (verdictValue) {
148
+ const normalized = verdictValue.value?.trim().toLowerCase().replace(/[\s-]+/g, '_');
149
+ if (!normalized || normalized.startsWith('--'))
150
+ errors.push('--verdict requires pass, changes_requested, or blocked');
151
+ else if (!isReviewGateVerdict(normalized))
152
+ errors.push('--verdict must be pass, changes_requested, or blocked');
153
+ else
154
+ verdict = normalized;
155
+ i = verdictValue.next;
156
+ continue;
157
+ }
158
+ const packetValue = optionValue(args, i, '--packet');
159
+ if (packetValue) {
160
+ if (!packetValue.value || packetValue.value.startsWith('--'))
161
+ errors.push('--packet requires a value');
162
+ else
163
+ packet = packetValue.value;
164
+ i = packetValue.next;
165
+ continue;
166
+ }
167
+ const reviewedCommitValue = optionValue(args, i, '--reviewed-commit') ?? optionValue(args, i, '--head');
168
+ if (reviewedCommitValue) {
169
+ if (!reviewedCommitValue.value || reviewedCommitValue.value.startsWith('--'))
170
+ errors.push('--reviewed-commit requires a value');
171
+ else
172
+ reviewedCommit = reviewedCommitValue.value;
173
+ i = reviewedCommitValue.next;
174
+ continue;
175
+ }
176
+ const tokenBudgetValue = optionValue(args, i, '--token-budget');
177
+ if (tokenBudgetValue) {
178
+ if (!tokenBudgetValue.value || tokenBudgetValue.value.startsWith('--'))
179
+ errors.push('--token-budget requires a value');
180
+ else
181
+ tokenBudget = tokenBudgetValue.value;
182
+ i = tokenBudgetValue.next;
183
+ continue;
184
+ }
185
+ const tokensUsedValue = optionValue(args, i, '--tokens-used');
186
+ if (tokensUsedValue) {
187
+ if (!tokensUsedValue.value || tokensUsedValue.value.startsWith('--')) {
188
+ errors.push('--tokens-used requires a numeric value');
189
+ }
190
+ else {
191
+ const parsed = Number(tokensUsedValue.value);
192
+ if (!Number.isFinite(parsed) || parsed < 0)
193
+ errors.push('--tokens-used must be a non-negative number');
194
+ else
195
+ tokensUsed = parsed;
196
+ }
197
+ i = tokensUsedValue.next;
198
+ continue;
199
+ }
200
+ const overBudgetValue = optionValue(args, i, '--over-budget-reason');
201
+ if (overBudgetValue) {
202
+ if (!overBudgetValue.value || overBudgetValue.value.startsWith('--'))
203
+ errors.push('--over-budget-reason requires a value');
204
+ else
205
+ overBudgetReason = overBudgetValue.value;
206
+ i = overBudgetValue.next;
207
+ continue;
208
+ }
140
209
  if (arg === '--self-review') {
141
210
  selfReview = true;
142
211
  continue;
@@ -183,6 +252,7 @@ function parseGateOptions(args) {
183
252
  if (modeCount > 1) {
184
253
  errors.push('Use only one review provenance mode: --reviewer/--evidence, --pr-review, --self-review, --waive-independent-review, or --override.');
185
254
  }
255
+ const inferredVerdict = verdict ?? inferVerdictFromEvidence(evidence);
186
256
  let review;
187
257
  if (modeCount === 1) {
188
258
  if (hasPrReview) {
@@ -200,9 +270,45 @@ function parseGateOptions(args) {
200
270
  else {
201
271
  review = { provenance: 'independent_review', evidence, reviewer, notes: notes ?? reason };
202
272
  }
273
+ review = {
274
+ ...review,
275
+ ...(inferredVerdict ? { verdict: inferredVerdict } : {}),
276
+ ...(packet ? { packet } : {}),
277
+ ...(reviewedCommit ? { reviewed_commit: reviewedCommit } : {}),
278
+ ...(tokenBudget ? { token_budget: tokenBudget } : {}),
279
+ ...(tokensUsed !== undefined ? { tokens_used: tokensUsed } : {}),
280
+ ...(overBudgetReason ? { over_budget_reason: overBudgetReason } : {}),
281
+ };
203
282
  }
204
283
  return { review, errors, help };
205
284
  }
285
+ function inferVerdictFromEvidence(evidence) {
286
+ const verdicts = [];
287
+ for (const item of evidence) {
288
+ if (/^https?:\/\//i.test(item))
289
+ continue;
290
+ const path = isAbsolute(item) ? item : join(process.cwd(), item);
291
+ if (!existsSync(path))
292
+ continue;
293
+ try {
294
+ const content = readFileSync(path, 'utf8');
295
+ const match = content.match(/^\s*verdict\s*:\s*([A-Za-z _-]+)/im);
296
+ const value = match?.[1]?.trim().toLowerCase().replace(/[\s-]+/g, '_');
297
+ if (isReviewGateVerdict(value))
298
+ verdicts.push(value);
299
+ }
300
+ catch {
301
+ continue;
302
+ }
303
+ }
304
+ if (verdicts.includes('blocked'))
305
+ return 'blocked';
306
+ if (verdicts.includes('changes_requested'))
307
+ return 'changes_requested';
308
+ if (verdicts.includes('pass'))
309
+ return 'pass';
310
+ return undefined;
311
+ }
206
312
  function printGateUsage(gateName) {
207
313
  const gate = gateName && isReviewGateName(gateName) ? gateName : 'code_review';
208
314
  console.error('');
@@ -210,6 +316,7 @@ function printGateUsage(gateName) {
210
316
  console.error(' slope sprint gate <name>');
211
317
  console.error(` slope sprint gate ${gate} --reviewer=<agent-or-person> --evidence=<transcript-or-output>`);
212
318
  console.error(` slope sprint gate ${gate} --pr-review=<url-or-id>`);
319
+ console.error(` slope sprint gate ${gate} --reviewer=<id> --evidence=<file> --verdict=pass --packet=<review-packet.json>`);
213
320
  console.error(` slope sprint gate ${gate} --self-review --reason="why self-review is acceptable"`);
214
321
  console.error(` slope sprint gate ${gate} --waive-independent-review --reason="why the required independent review is being waived"`);
215
322
  console.error(` slope sprint gate ${gate} --override="manual override reason"`);
@@ -220,6 +327,7 @@ function printGateUsage(gateName) {
220
327
  console.error(' self_review (weaker): requires --self-review and --reason');
221
328
  console.error(' independent_review_waived: explicit waiver for a required review; requires --waive-independent-review and --reason');
222
329
  console.error(' manual_override (weaker): requires --override');
330
+ console.error(' verdict metadata: --verdict=pass|changes_requested|blocked; local evidence files with "Verdict:" are parsed automatically');
223
331
  console.error('');
224
332
  }
225
333
  function commandValue(value) {
@@ -229,6 +337,28 @@ function commandValue(value) {
229
337
  ? `'${value.replaceAll("'", "''")}'`
230
338
  : `'${value.replaceAll("'", `'"'"'`)}`;
231
339
  }
340
+ function loadRoadmapForSprintLabels(cwd) {
341
+ const config = loadConfig(cwd);
342
+ const roadmapPath = join(cwd, config.roadmapPath);
343
+ if (!existsSync(roadmapPath))
344
+ return null;
345
+ try {
346
+ const raw = JSON.parse(readFileSync(roadmapPath, 'utf8'));
347
+ const parsed = parseRoadmap(raw);
348
+ return parsed.roadmap ?? castRoadmapStructure(raw);
349
+ }
350
+ catch {
351
+ return null;
352
+ }
353
+ }
354
+ function sprintLabelForCwd(cwd, sprint) {
355
+ const roadmap = loadRoadmapForSprintLabels(cwd);
356
+ return roadmap ? formatRoadmapSprintLabel(roadmap, sprint) : formatSprintLabel(sprint);
357
+ }
358
+ function sprintNumberForCwd(cwd, sprint) {
359
+ const label = sprintLabelForCwd(cwd, sprint);
360
+ return label.startsWith('S') ? label.slice(1) : label;
361
+ }
232
362
  function actorRetryOption(args) {
233
363
  for (const flag of ['--actor', '--player']) {
234
364
  const value = findOptionValue(args, flag);
@@ -450,6 +580,8 @@ async function beginCommand(args, cwd) {
450
580
  process.exit(1);
451
581
  }
452
582
  // Step 1: initialize without overwriting existing or corrupt local state.
583
+ const sprintLabel = sprintLabelForCwd(cwd, sprint);
584
+ const sprintNumber = sprintNumberForCwd(cwd, sprint);
453
585
  const initialized = initializeSprintState(cwd, createSprintState(sprint, 'planning'));
454
586
  if (initialized.status === 'corrupt') {
455
587
  console.error(`Refusing to begin: corrupt sprint evidence was preserved at ${relative(cwd, initialized.path)}.`);
@@ -457,11 +589,11 @@ async function beginCommand(args, cwd) {
457
589
  }
458
590
  let state = initialized.state;
459
591
  const retryActor = actorRetryOption(args);
460
- if (initialized.status === 'existing' && requireMatchingSprintOrRollover(cwd, state, sprint, `begin ${formatSprintLabel(sprint)}`, `slope sprint begin --sprint=${formatSprintNumber(sprint)} --ticket=${commandValue(ticket)}${retryActor ? ` ${retryActor}` : ''}`)) {
461
- console.log(`Sprint ${formatSprintNumber(sprint)}: already started (phase: ${state.phase}).`);
592
+ if (initialized.status === 'existing' && requireMatchingSprintOrRollover(cwd, state, sprint, `begin ${sprintLabel}`, `slope sprint begin --sprint=${sprintNumber} --ticket=${commandValue(ticket)}${retryActor ? ` ${retryActor}` : ''}`)) {
593
+ console.log(`Sprint ${sprintNumber}: already started (phase: ${state.phase}).`);
462
594
  }
463
595
  else {
464
- console.log(`Sprint ${formatSprintNumber(sprint)}: started (phase: planning).`);
596
+ console.log(`Sprint ${sprintNumber}: started (phase: planning).`);
465
597
  }
466
598
  // Step 2: claim
467
599
  const { resolveStore } = await import('../store.js');
@@ -491,7 +623,7 @@ async function beginCommand(args, cwd) {
491
623
  console.error(`\nClaim blocked — overlap conflict(s) detected:`);
492
624
  for (const c of overlaps)
493
625
  console.error(` [!!] ${formatConflictSummary(c)}`);
494
- console.error(`\nResolve conflicts or run \`slope claim --target=${ticket} --sprint=${formatSprintNumber(sprint)} --force\` to override.`);
626
+ console.error(`\nResolve conflicts or run \`slope claim --target=${ticket} --sprint=${sprintNumber} --force\` to override.`);
495
627
  process.exit(1);
496
628
  }
497
629
  const claim = await store.claim({ sprint_number: sprint, player, target: ticket, scope: 'ticket' });
@@ -508,11 +640,11 @@ async function beginCommand(args, cwd) {
508
640
  console.log('\n' + '─'.repeat(50));
509
641
  try {
510
642
  const { briefingCommand } = await import('./briefing.js');
511
- await briefingCommand([`--sprint=${formatSprintNumber(sprint)}`]);
643
+ await briefingCommand([`--sprint=${sprintNumber}`]);
512
644
  }
513
645
  catch (err) {
514
646
  console.error(` Could not run briefing: ${err.message}`);
515
- console.error(` Sprint state was already created; retry with: slope briefing --sprint=${formatSprintNumber(sprint)}`);
647
+ console.error(` Sprint state was already created; retry with: slope briefing --sprint=${sprintNumber}`);
516
648
  }
517
649
  // Step 4: prep --lite
518
650
  console.log('\n' + '─'.repeat(50));
@@ -582,28 +714,30 @@ async function startCommand(args, cwd) {
582
714
  ...(retryActor ? [retryActor] : []),
583
715
  ...(force ? ['--force'] : []),
584
716
  ];
585
- const retryStart = `slope sprint start --number=${formatSprintNumber(sprint)} --phase=${phase}${retryExtras.length > 0 ? ` ${retryExtras.join(' ')}` : ''}`;
717
+ const sprintLabel = sprintLabelForCwd(cwd, sprint);
718
+ const sprintNumber = sprintNumberForCwd(cwd, sprint);
719
+ const retryStart = `slope sprint start --number=${sprintNumber} --phase=${phase}${retryExtras.length > 0 ? ` ${retryExtras.join(' ')}` : ''}`;
586
720
  failOnCorruptSprintState(cwd);
587
721
  const existingResult = loadSprintStateResult(cwd);
588
722
  const existing = existingResult.status === 'valid' ? existingResult.state : null;
589
723
  if (existing) {
590
- requireMatchingSprintOrRollover(cwd, existing, sprint, `start ${formatSprintLabel(sprint)}`, retryStart);
724
+ requireMatchingSprintOrRollover(cwd, existing, sprint, `start ${sprintLabel}`, retryStart);
591
725
  if (phaseValue && existing.phase !== phase) {
592
726
  const updated = updateSprintPhaseForSprint(cwd, existing.sprint, phase);
593
727
  if (!updated.matched) {
594
728
  console.error('Refusing to update phase because sprint state changed concurrently; retry the start command.');
595
729
  process.exit(1);
596
730
  }
597
- console.log(`Sprint ${formatSprintNumber(sprint)} phase updated: ${phase}.`);
731
+ console.log(`Sprint ${sprintNumber} phase updated: ${phase}.`);
598
732
  return;
599
733
  }
600
- console.log(`Sprint ${formatSprintNumber(sprint)} state already exists (phase: ${existing.phase}).`);
734
+ console.log(`Sprint ${sprintNumber} state already exists (phase: ${existing.phase}).`);
601
735
  return;
602
736
  }
603
737
  const roadmapReality = loadRoadmapReality(cwd);
604
738
  const blockingRoadmapIssues = blockingRoadmapIssuesForSprint(roadmapReality, sprint);
605
739
  if (blockingRoadmapIssues.length > 0 && !force) {
606
- console.error(`\nPre-sprint reality check failed for ${formatSprintLabel(sprint)}:`);
740
+ console.error(`\nPre-sprint reality check failed for ${sprintLabel}:`);
607
741
  for (const issue of blockingRoadmapIssues) {
608
742
  console.error(` [${issue.type}] ${issue.message}`);
609
743
  }
@@ -629,12 +763,12 @@ async function startCommand(args, cwd) {
629
763
  process.exit(1);
630
764
  }
631
765
  if (initialized.status === 'existing') {
632
- requireMatchingSprintOrRollover(cwd, initialized.state, sprint, `start ${formatSprintLabel(sprint)}`, retryStart);
633
- console.log(`Sprint ${formatSprintNumber(sprint)} state already exists (phase: ${initialized.state.phase}).`);
766
+ requireMatchingSprintOrRollover(cwd, initialized.state, sprint, `start ${sprintLabel}`, retryStart);
767
+ console.log(`Sprint ${sprintNumber} state already exists (phase: ${initialized.state.phase}).`);
634
768
  return;
635
769
  }
636
770
  const autoClaim = await autoClaimSprint(cwd, sprint, actorOverride(args));
637
- console.log(`Sprint ${formatSprintNumber(sprint)} started (phase: ${phase}). Use 'slope sprint gate <name>' to mark gates; review gates require evidence options.`);
771
+ console.log(`Sprint ${sprintNumber} started (phase: ${phase}). Use 'slope sprint gate <name>' to mark gates; review gates require evidence options.`);
638
772
  if (autoClaim)
639
773
  console.log(autoClaim);
640
774
  }
@@ -643,7 +777,7 @@ async function autoClaimSprint(cwd, sprint, explicitActor) {
643
777
  const actor = resolveActor(cwd, { explicitActor });
644
778
  const player = actor.name;
645
779
  const playerDisplay = formatActorName(actor);
646
- const target = `sprint:${formatSprintLabel(sprint)}`;
780
+ const target = `sprint:${sprintLabelForCwd(cwd, sprint)}`;
647
781
  try {
648
782
  const store = await resolveStore(cwd);
649
783
  try {
@@ -681,10 +815,10 @@ function phaseCommand(args, cwd) {
681
815
  }
682
816
  updateSprintPhase(cwd, phaseInput);
683
817
  if (before.phase === phaseInput) {
684
- console.log(`Sprint ${formatSprintNumber(before.sprint)} already in ${phaseInput} phase.`);
818
+ console.log(`Sprint ${sprintNumberForCwd(cwd, before.sprint)} already in ${phaseInput} phase.`);
685
819
  }
686
820
  else {
687
- console.log(`Sprint ${formatSprintNumber(before.sprint)} phase updated: ${before.phase} -> ${phaseInput}.`);
821
+ console.log(`Sprint ${sprintNumberForCwd(cwd, before.sprint)} phase updated: ${before.phase} -> ${phaseInput}.`);
688
822
  }
689
823
  }
690
824
  function gateCommand(args, cwd) {
@@ -800,7 +934,7 @@ function statusCommand(cwd) {
800
934
  ? waivedReviews.length > 0 ? 'ready_for_pr_with_review_waiver' : 'ready_for_pr'
801
935
  : state.phase;
802
936
  const phaseContext = complete ? ' (all gates complete)' : ` (phase: ${state.phase})`;
803
- console.log(`Sprint ${formatSprintNumber(state.sprint)} - status: ${derivedStatus}${phaseContext}`);
937
+ console.log(`Sprint ${sprintNumberForCwd(cwd, state.sprint)} - status: ${derivedStatus}${phaseContext}`);
804
938
  console.log(`Started: ${state.started_at}`);
805
939
  console.log(`Updated: ${state.updated_at}`);
806
940
  console.log('');