@slope-dev/slope 1.57.3 → 1.58.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/atomic-write.d.ts.map +1 -1
- package/dist/cli/atomic-write.js +12 -3
- package/dist/cli/atomic-write.js.map +1 -1
- package/dist/cli/commands/auto-card.d.ts.map +1 -1
- package/dist/cli/commands/auto-card.js +27 -7
- package/dist/cli/commands/auto-card.js.map +1 -1
- package/dist/cli/commands/commit-ready.d.ts.map +1 -1
- package/dist/cli/commands/commit-ready.js +19 -33
- package/dist/cli/commands/commit-ready.js.map +1 -1
- package/dist/cli/commands/enrich.js +4 -4
- package/dist/cli/commands/enrich.js.map +1 -1
- package/dist/cli/commands/guard.d.ts.map +1 -1
- package/dist/cli/commands/guard.js +2 -1
- package/dist/cli/commands/guard.js.map +1 -1
- package/dist/cli/commands/issue.d.ts +2 -0
- package/dist/cli/commands/issue.d.ts.map +1 -0
- package/dist/cli/commands/issue.js +548 -0
- package/dist/cli/commands/issue.js.map +1 -0
- package/dist/cli/commands/map.d.ts.map +1 -1
- package/dist/cli/commands/map.js +127 -19
- package/dist/cli/commands/map.js.map +1 -1
- package/dist/cli/commands/pr.d.ts +1 -1
- package/dist/cli/commands/pr.d.ts.map +1 -1
- package/dist/cli/commands/pr.js +19 -7
- package/dist/cli/commands/pr.js.map +1 -1
- package/dist/cli/commands/resume.d.ts +2 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +5 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/retro.d.ts +2 -1
- package/dist/cli/commands/retro.d.ts.map +1 -1
- package/dist/cli/commands/retro.js +243 -4
- package/dist/cli/commands/retro.js.map +1 -1
- package/dist/cli/commands/review.d.ts +1 -1
- package/dist/cli/commands/review.d.ts.map +1 -1
- package/dist/cli/commands/review.js +13 -5
- package/dist/cli/commands/review.js.map +1 -1
- package/dist/cli/commands/session.d.ts.map +1 -1
- package/dist/cli/commands/session.js +17 -0
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/sprint.d.ts.map +1 -1
- package/dist/cli/commands/sprint.js +302 -59
- package/dist/cli/commands/sprint.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +18 -9
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/store.d.ts.map +1 -1
- package/dist/cli/commands/store.js +10 -0
- package/dist/cli/commands/store.js.map +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +32 -3
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/commands/version.d.ts.map +1 -1
- package/dist/cli/commands/version.js +15 -8
- package/dist/cli/commands/version.js.map +1 -1
- package/dist/cli/commands/worktree.d.ts.map +1 -1
- package/dist/cli/commands/worktree.js +8 -6
- package/dist/cli/commands/worktree.js.map +1 -1
- package/dist/cli/guards/branch-before-commit.d.ts.map +1 -1
- package/dist/cli/guards/branch-before-commit.js +2 -1
- package/dist/cli/guards/branch-before-commit.js.map +1 -1
- package/dist/cli/guards/claim-required.d.ts.map +1 -1
- package/dist/cli/guards/claim-required.js +26 -15
- package/dist/cli/guards/claim-required.js.map +1 -1
- package/dist/cli/guards/commit-nudge.d.ts.map +1 -1
- package/dist/cli/guards/commit-nudge.js +6 -5
- package/dist/cli/guards/commit-nudge.js.map +1 -1
- package/dist/cli/guards/compaction.d.ts.map +1 -1
- package/dist/cli/guards/compaction.js +6 -5
- package/dist/cli/guards/compaction.js.map +1 -1
- package/dist/cli/guards/docs.js +2 -2
- package/dist/cli/guards/docs.js.map +1 -1
- package/dist/cli/guards/explore.d.ts.map +1 -1
- package/dist/cli/guards/explore.js +6 -2
- package/dist/cli/guards/explore.js.map +1 -1
- package/dist/cli/guards/git-utils.js +3 -3
- package/dist/cli/guards/git-utils.js.map +1 -1
- package/dist/cli/guards/post-hole-enforcement.d.ts.map +1 -1
- package/dist/cli/guards/post-hole-enforcement.js +22 -2
- package/dist/cli/guards/post-hole-enforcement.js.map +1 -1
- package/dist/cli/guards/post-push.d.ts.map +1 -1
- package/dist/cli/guards/post-push.js +15 -10
- package/dist/cli/guards/post-push.js.map +1 -1
- package/dist/cli/guards/pr-review.d.ts.map +1 -1
- package/dist/cli/guards/pr-review.js +1 -0
- package/dist/cli/guards/pr-review.js.map +1 -1
- package/dist/cli/guards/push-nudge.d.ts.map +1 -1
- package/dist/cli/guards/push-nudge.js +4 -3
- package/dist/cli/guards/push-nudge.js.map +1 -1
- package/dist/cli/guards/scope-drift.js +2 -0
- package/dist/cli/guards/scope-drift.js.map +1 -1
- package/dist/cli/guards/sprint-completion.d.ts.map +1 -1
- package/dist/cli/guards/sprint-completion.js +222 -21
- package/dist/cli/guards/sprint-completion.js.map +1 -1
- package/dist/cli/guards/stop-check.d.ts.map +1 -1
- package/dist/cli/guards/stop-check.js +14 -12
- package/dist/cli/guards/stop-check.js.map +1 -1
- package/dist/cli/guards/version-check.d.ts.map +1 -1
- package/dist/cli/guards/version-check.js +3 -1
- package/dist/cli/guards/version-check.js.map +1 -1
- package/dist/cli/guards/workflow-step-gate.d.ts.map +1 -1
- package/dist/cli/guards/workflow-step-gate.js +14 -3
- package/dist/cli/guards/workflow-step-gate.js.map +1 -1
- package/dist/cli/guards/worktree-check.d.ts.map +1 -1
- package/dist/cli/guards/worktree-check.js +138 -8
- package/dist/cli/guards/worktree-check.js.map +1 -1
- package/dist/cli/guards/worktree-merge.d.ts.map +1 -1
- package/dist/cli/guards/worktree-merge.js +3 -2
- package/dist/cli/guards/worktree-merge.js.map +1 -1
- package/dist/cli/guards/worktree-reuse.d.ts.map +1 -1
- package/dist/cli/guards/worktree-reuse.js +2 -1
- package/dist/cli/guards/worktree-reuse.js.map +1 -1
- package/dist/cli/guards/worktree-self-remove.d.ts.map +1 -1
- package/dist/cli/guards/worktree-self-remove.js +3 -2
- package/dist/cli/guards/worktree-self-remove.js.map +1 -1
- package/dist/cli/index.js +24 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/loop/slope-executor.d.ts.map +1 -1
- package/dist/cli/loop/slope-executor.js +4 -2
- package/dist/cli/loop/slope-executor.js.map +1 -1
- package/dist/cli/pr-closeout.d.ts +28 -0
- package/dist/cli/pr-closeout.d.ts.map +1 -1
- package/dist/cli/pr-closeout.js +297 -8
- package/dist/cli/pr-closeout.js.map +1 -1
- package/dist/cli/pr-review-state.d.ts +15 -0
- package/dist/cli/pr-review-state.d.ts.map +1 -1
- package/dist/cli/pr-review-state.js +68 -0
- package/dist/cli/pr-review-state.js.map +1 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +52 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/source-runtime.d.ts +9 -0
- package/dist/cli/source-runtime.d.ts.map +1 -0
- package/dist/cli/source-runtime.js +50 -0
- package/dist/cli/source-runtime.js.map +1 -0
- package/dist/cli/sprint-resume.d.ts +55 -0
- package/dist/cli/sprint-resume.d.ts.map +1 -0
- package/dist/cli/sprint-resume.js +213 -0
- package/dist/cli/sprint-resume.js.map +1 -0
- package/dist/cli/template-generator.d.ts.map +1 -1
- package/dist/cli/template-generator.js +10 -2
- package/dist/cli/template-generator.js.map +1 -1
- package/dist/cli/workflow-resync.d.ts +37 -0
- package/dist/cli/workflow-resync.d.ts.map +1 -0
- package/dist/cli/workflow-resync.js +273 -0
- package/dist/cli/workflow-resync.js.map +1 -0
- package/dist/core/analyzers/walk.d.ts.map +1 -1
- package/dist/core/analyzers/walk.js +4 -1
- package/dist/core/analyzers/walk.js.map +1 -1
- package/dist/core/briefing.d.ts.map +1 -1
- package/dist/core/briefing.js +17 -5
- package/dist/core/briefing.js.map +1 -1
- package/dist/core/flows.d.ts.map +1 -1
- package/dist/core/flows.js +2 -2
- package/dist/core/flows.js.map +1 -1
- package/dist/core/formatter.d.ts.map +1 -1
- package/dist/core/formatter.js +3 -48
- package/dist/core/formatter.js.map +1 -1
- package/dist/core/harness.d.ts +1 -1
- package/dist/core/harness.d.ts.map +1 -1
- package/dist/core/harness.js +7 -5
- package/dist/core/harness.js.map +1 -1
- package/dist/core/imports.d.ts.map +1 -1
- package/dist/core/imports.js +7 -4
- package/dist/core/imports.js.map +1 -1
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/interview-engine.d.ts.map +1 -1
- package/dist/core/interview-engine.js +3 -1
- package/dist/core/interview-engine.js.map +1 -1
- package/dist/core/issue-scout.d.ts +70 -0
- package/dist/core/issue-scout.d.ts.map +1 -0
- package/dist/core/issue-scout.js +481 -0
- package/dist/core/issue-scout.js.map +1 -0
- package/dist/core/loader.d.ts +5 -1
- package/dist/core/loader.d.ts.map +1 -1
- package/dist/core/loader.js +102 -4
- package/dist/core/loader.js.map +1 -1
- package/dist/core/memory-types.d.ts +1 -1
- package/dist/core/memory-types.d.ts.map +1 -1
- package/dist/core/memory-validation.js +1 -1
- package/dist/core/memory-validation.js.map +1 -1
- package/dist/core/memory.d.ts.map +1 -1
- package/dist/core/memory.js +5 -0
- package/dist/core/memory.js.map +1 -1
- package/dist/core/prep.d.ts.map +1 -1
- package/dist/core/prep.js +7 -4
- package/dist/core/prep.js.map +1 -1
- package/dist/core/process.d.ts +3 -0
- package/dist/core/process.d.ts.map +1 -0
- package/dist/core/process.js +3 -0
- package/dist/core/process.js.map +1 -0
- package/dist/core/retro.d.ts +49 -0
- package/dist/core/retro.d.ts.map +1 -0
- package/dist/core/retro.js +112 -0
- package/dist/core/retro.js.map +1 -0
- package/dist/core/vision.js +5 -5
- package/dist/core/vision.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +74 -33
- package/dist/mcp/index.js.map +1 -1
- package/package.json +3 -3
- package/templates/codex/plugins/slope/.codex-plugin/plugin.json +4 -3
- package/templates/codex/plugins/slope/skills/slope-retro/SKILL.md +33 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadSprintState, saveSprintState, createSprintState, mutateSprintState, updateGate, updateSprintPhase, clearSprintState, isSprintComplete, pendingGates, isSprintPhase, SPRINT_PHASES, } from '../sprint-state.js';
|
|
2
|
-
import { WorkflowEngine, loadWorkflow, resolveVariables, validateWorkflow, loadConfig,
|
|
2
|
+
import { WorkflowEngine, loadWorkflow, resolveVariables, validateWorkflow, loadConfig, parseRoadmap, castRoadmapStructure, formatSprintLabel, formatSprintNumber, parseSprintNumber } from '../../core/index.js';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
/** Get workflow definition from execution snapshot (preferred) or disk (fallback for old executions) */
|
|
5
5
|
function getDefinition(exec, cwd) {
|
|
@@ -20,9 +20,12 @@ function getDefinition(exec, cwd) {
|
|
|
20
20
|
return { def: loadWorkflow(exec.workflow_name, cwd), drifted: false };
|
|
21
21
|
}
|
|
22
22
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
23
|
-
import { join, dirname, basename } from 'node:path';
|
|
23
|
+
import { join, dirname, basename, isAbsolute } from 'node:path';
|
|
24
24
|
import { createStore } from '../../store/index.js';
|
|
25
|
+
import { formatCliError } from '../error-reporter.js';
|
|
26
|
+
import { buildSprintResumePointer, planPortableSprintResume, writeSprintResumePointer, } from '../sprint-resume.js';
|
|
25
27
|
import { blockingRoadmapIssuesForSprint, collectSiblingWorktreeReality, findWorktreeOverlaps, formatWorktreeRealitySection, loadRoadmapReality, parseTouchedPaths, } from '../pre-sprint-reality.js';
|
|
28
|
+
import { findStaleWorkflowExecutions, reconcileWorkflowExecutions } from '../workflow-resync.js';
|
|
26
29
|
/**
|
|
27
30
|
* Check completion_conditions for a step before allowing completion/skip.
|
|
28
31
|
* Returns null if all conditions met, or an error message string.
|
|
@@ -385,6 +388,59 @@ function getStore(cwd) {
|
|
|
385
388
|
const config = loadConfig(cwd);
|
|
386
389
|
return createStore({ storePath: config.store_path ?? '.slope/slope.db', cwd });
|
|
387
390
|
}
|
|
391
|
+
function roadmapTicketKeysForSprint(cwd, sprintId) {
|
|
392
|
+
if (!sprintId)
|
|
393
|
+
return [];
|
|
394
|
+
const sprintNumber = parseSprintNumber(sprintId);
|
|
395
|
+
if (sprintNumber === null)
|
|
396
|
+
return [];
|
|
397
|
+
const config = loadConfig(cwd);
|
|
398
|
+
if (!config.roadmapPath)
|
|
399
|
+
return [];
|
|
400
|
+
const roadmapPath = join(cwd, config.roadmapPath);
|
|
401
|
+
if (!existsSync(roadmapPath))
|
|
402
|
+
return [];
|
|
403
|
+
try {
|
|
404
|
+
const raw = JSON.parse(readFileSync(roadmapPath, 'utf8'));
|
|
405
|
+
const parsed = parseRoadmap(raw);
|
|
406
|
+
const roadmap = parsed.roadmap ?? castRoadmapStructure(raw);
|
|
407
|
+
const sprint = roadmap?.sprints.find(s => s.id === sprintNumber);
|
|
408
|
+
return sprint?.tickets.map(t => t.key).filter(Boolean) ?? [];
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function applyWorkflowVariableDefaults(def, vars, cwd, sprintId) {
|
|
415
|
+
if (sprintId && !('sprint_id' in vars)) {
|
|
416
|
+
vars.sprint_id = sprintId;
|
|
417
|
+
}
|
|
418
|
+
const ticketsSpec = def.variables?.tickets;
|
|
419
|
+
if (ticketsSpec?.required && !('tickets' in vars)) {
|
|
420
|
+
const tickets = roadmapTicketKeysForSprint(cwd, sprintId);
|
|
421
|
+
if (tickets.length > 0) {
|
|
422
|
+
vars.tickets = tickets.join(',');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function positionalSprintArg(args) {
|
|
427
|
+
for (let i = 0; i < args.length; i++) {
|
|
428
|
+
const arg = args[i];
|
|
429
|
+
if (arg === '--var') {
|
|
430
|
+
i++;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (!arg.startsWith('--'))
|
|
434
|
+
return arg;
|
|
435
|
+
}
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
function sprintIdFromRunArgs(args) {
|
|
439
|
+
const sprintFlag = args.find(a => a.startsWith('--sprint='));
|
|
440
|
+
if (sprintFlag)
|
|
441
|
+
return sprintFlag.slice('--sprint='.length);
|
|
442
|
+
return positionalSprintArg(args);
|
|
443
|
+
}
|
|
388
444
|
const SPRINT_PHASE_ORDER = {
|
|
389
445
|
planning: 0,
|
|
390
446
|
reviewing: 1,
|
|
@@ -437,7 +493,7 @@ function syncSprintStateWithWorkflow(cwd, sprintId, workflowPhase) {
|
|
|
437
493
|
});
|
|
438
494
|
}
|
|
439
495
|
async function runWorkflowCommand(args, cwd) {
|
|
440
|
-
const
|
|
496
|
+
const explicitSprintId = sprintIdFromRunArgs(args);
|
|
441
497
|
const workflowArg = args.find(a => a.startsWith('--workflow='));
|
|
442
498
|
const varArgs = [];
|
|
443
499
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -453,12 +509,11 @@ async function runWorkflowCommand(args, cwd) {
|
|
|
453
509
|
console.error('Usage: slope sprint run <sprint_id> --workflow=<name> [--var key=value ...]');
|
|
454
510
|
process.exit(1);
|
|
455
511
|
}
|
|
456
|
-
const sprintId = sprintArg?.startsWith('--') ? undefined : sprintArg;
|
|
457
512
|
const workflowName = workflowArg.slice('--workflow='.length);
|
|
458
513
|
// Parse variables
|
|
459
514
|
const vars = {};
|
|
460
|
-
if (
|
|
461
|
-
vars.sprint_id =
|
|
515
|
+
if (explicitSprintId)
|
|
516
|
+
vars.sprint_id = explicitSprintId;
|
|
462
517
|
for (const v of varArgs) {
|
|
463
518
|
const kv = v.slice('--var='.length);
|
|
464
519
|
const eq = kv.indexOf('=');
|
|
@@ -466,6 +521,7 @@ async function runWorkflowCommand(args, cwd) {
|
|
|
466
521
|
vars[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
467
522
|
}
|
|
468
523
|
}
|
|
524
|
+
const sprintId = explicitSprintId ?? vars.sprint_id;
|
|
469
525
|
// Load and validate workflow
|
|
470
526
|
const def = loadWorkflow(workflowName, cwd);
|
|
471
527
|
const validation = validateWorkflow(def);
|
|
@@ -476,6 +532,7 @@ async function runWorkflowCommand(args, cwd) {
|
|
|
476
532
|
}
|
|
477
533
|
process.exit(1);
|
|
478
534
|
}
|
|
535
|
+
applyWorkflowVariableDefaults(def, vars, cwd, sprintId);
|
|
479
536
|
// Resolve variables
|
|
480
537
|
const resolved = resolveVariables(def, vars);
|
|
481
538
|
// Start execution
|
|
@@ -513,8 +570,9 @@ async function runWorkflowCommand(args, cwd) {
|
|
|
513
570
|
}
|
|
514
571
|
async function workflowStatusCommand(args, cwd) {
|
|
515
572
|
const sprintArg = args.find(a => !a.startsWith('--'));
|
|
516
|
-
|
|
573
|
+
let store = null;
|
|
517
574
|
try {
|
|
575
|
+
store = getStore(cwd);
|
|
518
576
|
if (sprintArg) {
|
|
519
577
|
const exec = await store.getExecutionBySprint(sprintArg);
|
|
520
578
|
if (!exec) {
|
|
@@ -536,60 +594,45 @@ async function workflowStatusCommand(args, cwd) {
|
|
|
536
594
|
}
|
|
537
595
|
}
|
|
538
596
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
function completedRoadmapSprintIds(cwd, config) {
|
|
544
|
-
if (!config.roadmapPath)
|
|
545
|
-
return new Set();
|
|
546
|
-
const roadmapPath = join(cwd, config.roadmapPath);
|
|
547
|
-
if (!existsSync(roadmapPath))
|
|
548
|
-
return new Set();
|
|
549
|
-
try {
|
|
550
|
-
const raw = JSON.parse(readFileSync(roadmapPath, 'utf8'));
|
|
551
|
-
const parsed = parseRoadmap(raw);
|
|
552
|
-
const roadmap = parsed.roadmap ?? castRoadmapStructure(raw);
|
|
553
|
-
if (!roadmap)
|
|
554
|
-
return new Set();
|
|
555
|
-
return new Set(roadmap.sprints
|
|
556
|
-
.filter(sprint => {
|
|
557
|
-
const status = sprint.status;
|
|
558
|
-
return status === 'complete' || status === 'superseded';
|
|
559
|
-
})
|
|
560
|
-
.map(sprint => sprint.id));
|
|
561
|
-
}
|
|
562
|
-
catch {
|
|
563
|
-
return new Set();
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
async function findStaleWorkflowExecutions(cwd, store) {
|
|
567
|
-
const config = loadConfig(cwd);
|
|
568
|
-
const scorecardSprintIds = new Set(loadScorecards(config, cwd).map(card => card.sprint_number));
|
|
569
|
-
const roadmapDoneIds = completedRoadmapSprintIds(cwd, config);
|
|
570
|
-
const running = await store.listExecutions({ status: 'running' });
|
|
571
|
-
const stale = [];
|
|
572
|
-
for (const exec of running) {
|
|
573
|
-
const sprint = sprintNumberFromId(exec.sprint_id);
|
|
574
|
-
if (sprint === null)
|
|
575
|
-
continue;
|
|
576
|
-
const reasons = [];
|
|
577
|
-
if (scorecardSprintIds.has(sprint))
|
|
578
|
-
reasons.push('scorecard exists');
|
|
579
|
-
if (roadmapDoneIds.has(sprint))
|
|
580
|
-
reasons.push('roadmap complete/superseded');
|
|
581
|
-
if (reasons.length > 0) {
|
|
582
|
-
stale.push({ exec, reason: reasons.join(', ') });
|
|
597
|
+
catch (err) {
|
|
598
|
+
for (const line of formatCliError(err, cwd)) {
|
|
599
|
+
console.error(line);
|
|
583
600
|
}
|
|
601
|
+
process.exitCode = 1;
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
store?.close();
|
|
584
605
|
}
|
|
585
|
-
return stale;
|
|
586
606
|
}
|
|
587
607
|
async function workflowCleanupCommand(args, cwd) {
|
|
588
608
|
const action = args[0];
|
|
589
609
|
const dryRun = args.includes('--dry-run');
|
|
590
610
|
const staleOnly = args.includes('--stale');
|
|
611
|
+
const resync = action === 'resync';
|
|
612
|
+
if (resync) {
|
|
613
|
+
const store = getStore(cwd);
|
|
614
|
+
try {
|
|
615
|
+
const result = await reconcileWorkflowExecutions(cwd, store);
|
|
616
|
+
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}`);
|
|
618
|
+
}
|
|
619
|
+
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}`);
|
|
621
|
+
}
|
|
622
|
+
if (result.paused.length === 0 && result.fastForwarded.length === 0) {
|
|
623
|
+
console.log('Workflow state already matches git/roadmap reality.');
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
console.log(`\nResynced workflow state: ${result.paused.length} paused, ${result.fastForwarded.length} fast-forwarded.`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
finally {
|
|
630
|
+
store.close();
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
591
634
|
if (action !== 'cleanup' || !staleOnly) {
|
|
592
|
-
console.error('Usage: slope sprint workflow cleanup --stale [--dry-run]');
|
|
635
|
+
console.error('Usage: slope sprint workflow cleanup --stale [--dry-run]\n slope sprint workflow resync');
|
|
593
636
|
process.exit(1);
|
|
594
637
|
}
|
|
595
638
|
const store = getStore(cwd);
|
|
@@ -629,7 +672,184 @@ function printExecution(exec) {
|
|
|
629
672
|
console.log(` Started: ${exec.started_at}`);
|
|
630
673
|
console.log('');
|
|
631
674
|
}
|
|
675
|
+
function shouldUsePortableResume(args) {
|
|
676
|
+
return args.includes('--portable')
|
|
677
|
+
|| args.includes('--write-pointer')
|
|
678
|
+
|| args.includes('--dry-run')
|
|
679
|
+
|| args.some(a => a.startsWith('--from=') || a.startsWith('--sprint=') || a.startsWith('--phase=') || a.startsWith('--output='));
|
|
680
|
+
}
|
|
681
|
+
function parsePortableResumeFlags(args) {
|
|
682
|
+
const flags = {
|
|
683
|
+
portable: args.includes('--portable'),
|
|
684
|
+
writePointer: args.includes('--write-pointer'),
|
|
685
|
+
force: args.includes('--force'),
|
|
686
|
+
dryRun: args.includes('--dry-run'),
|
|
687
|
+
};
|
|
688
|
+
for (const arg of args) {
|
|
689
|
+
if (arg.startsWith('--sprint=')) {
|
|
690
|
+
const raw = arg.slice('--sprint='.length);
|
|
691
|
+
const sprint = parseSprintNumber(raw);
|
|
692
|
+
if (sprint)
|
|
693
|
+
flags.sprint = sprint;
|
|
694
|
+
else
|
|
695
|
+
flags.invalidSprint = raw;
|
|
696
|
+
}
|
|
697
|
+
else if (arg.startsWith('--phase=')) {
|
|
698
|
+
const phase = arg.slice('--phase='.length);
|
|
699
|
+
if (isSprintPhase(phase))
|
|
700
|
+
flags.phase = phase;
|
|
701
|
+
else
|
|
702
|
+
flags.invalidPhase = phase;
|
|
703
|
+
}
|
|
704
|
+
else if (arg.startsWith('--from=')) {
|
|
705
|
+
flags.from = arg.slice('--from='.length);
|
|
706
|
+
}
|
|
707
|
+
else if (arg.startsWith('--output=')) {
|
|
708
|
+
flags.output = arg.slice('--output='.length);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return flags;
|
|
712
|
+
}
|
|
713
|
+
async function portableResumeCommand(args, cwd) {
|
|
714
|
+
const flags = parsePortableResumeFlags(args);
|
|
715
|
+
const config = loadConfig(cwd);
|
|
716
|
+
if (flags.invalidSprint) {
|
|
717
|
+
console.error(`Error: invalid sprint "${flags.invalidSprint}". Use --sprint=N, e.g. --sprint=177.`);
|
|
718
|
+
process.exit(1);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (flags.invalidPhase) {
|
|
722
|
+
console.error(`Error: invalid phase "${flags.invalidPhase}". Valid phases: ${SPRINT_PHASES.join(', ')}`);
|
|
723
|
+
process.exit(1);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (flags.writePointer) {
|
|
727
|
+
const current = loadSprintState(cwd);
|
|
728
|
+
const sprint = flags.sprint ?? current?.sprint;
|
|
729
|
+
if (!sprint) {
|
|
730
|
+
console.error('Usage: slope sprint resume --write-pointer --sprint=N [--phase=<phase>] [--output=path]');
|
|
731
|
+
process.exit(1);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const phase = flags.phase ?? current?.phase ?? 'implementing';
|
|
735
|
+
const resumeClaims = await collectResumeClaimPointers(cwd, sprint);
|
|
736
|
+
const pointer = buildSprintResumePointer(cwd, config, { sprint, phase, resumeClaims });
|
|
737
|
+
const outputPath = flags.output ? (isAbsolute(flags.output) ? flags.output : join(cwd, flags.output)) : undefined;
|
|
738
|
+
const written = writeSprintResumePointer(cwd, pointer, outputPath);
|
|
739
|
+
console.log(`Sprint resume pointer written: ${written}`);
|
|
740
|
+
console.log(` Sprint: ${formatSprintLabel(sprint)} (${phase})`);
|
|
741
|
+
console.log(` Resume claims: ${resumeClaims.length}`);
|
|
742
|
+
console.log(' Local runtime state excluded: slope.db, session locks, guard metrics, baselines');
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const plan = planPortableSprintResume(cwd, config, {
|
|
746
|
+
sprint: flags.sprint,
|
|
747
|
+
phase: flags.phase,
|
|
748
|
+
from: flags.from,
|
|
749
|
+
force: flags.force,
|
|
750
|
+
});
|
|
751
|
+
printPortableResumePlan(plan);
|
|
752
|
+
if (plan.unsafe.length > 0 && !flags.force) {
|
|
753
|
+
console.error('\nPortable resume refused. Rerun with --force after reviewing the unsafe condition(s).');
|
|
754
|
+
process.exit(1);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const existing = loadSprintState(cwd);
|
|
758
|
+
if (existing && existing.sprint !== plan.sprint && !flags.force) {
|
|
759
|
+
console.error(`\nPortable resume refused. Local sprint-state is ${formatSprintLabel(existing.sprint)}, but resume target is ${formatSprintLabel(plan.sprint)}.`);
|
|
760
|
+
console.error('Run `slope sprint reset` or rerun with --force if replacing local state is intentional.');
|
|
761
|
+
process.exit(1);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (flags.dryRun) {
|
|
765
|
+
console.log('\nDry run: local sprint-state and claims were not changed.');
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
saveSprintState(cwd, createSprintState(plan.sprint, plan.phase));
|
|
769
|
+
const restoredClaims = await restoreResumeClaims(cwd, plan);
|
|
770
|
+
console.log(`\nPortable sprint resume complete: ${formatSprintLabel(plan.sprint)} (${plan.phase}).`);
|
|
771
|
+
console.log(` Fresh local sprint-state written to .slope/sprint-state.json`);
|
|
772
|
+
console.log(` Resume claims restored: ${restoredClaims}`);
|
|
773
|
+
console.log(' Local DB/locks/metrics were not imported from another machine.');
|
|
774
|
+
}
|
|
775
|
+
function printPortableResumePlan(plan) {
|
|
776
|
+
console.log('\nPortable sprint resume plan');
|
|
777
|
+
console.log('='.repeat(32));
|
|
778
|
+
console.log(` Sprint: ${formatSprintLabel(plan.sprint)}`);
|
|
779
|
+
console.log(` Phase: ${plan.phase}`);
|
|
780
|
+
console.log(` Source: ${plan.source}${plan.pointerPath ? ` (${plan.pointerPath})` : ''}`);
|
|
781
|
+
if (plan.currentBranch)
|
|
782
|
+
console.log(` Branch: ${plan.currentBranch}`);
|
|
783
|
+
if (plan.headCommit)
|
|
784
|
+
console.log(` HEAD: ${plan.headCommit.slice(0, 12)}`);
|
|
785
|
+
const evidence = Object.entries(plan.evidence);
|
|
786
|
+
if (evidence.length > 0) {
|
|
787
|
+
console.log(' Evidence:');
|
|
788
|
+
for (const [label, value] of evidence) {
|
|
789
|
+
console.log(` - ${label}: ${value}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (plan.resumeClaims.length > 0) {
|
|
793
|
+
console.log(' Resume claims:');
|
|
794
|
+
for (const claim of plan.resumeClaims) {
|
|
795
|
+
console.log(` - ${claim.id} (${claim.scope ?? 'ticket'}, ${claim.state})${claim.last_evidence ? ` via ${claim.last_evidence}` : ''}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (plan.unsafe.length > 0) {
|
|
799
|
+
console.log(' Unsafe conditions:');
|
|
800
|
+
for (const item of plan.unsafe)
|
|
801
|
+
console.log(` - ${item}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
async function collectResumeClaimPointers(cwd, sprint) {
|
|
805
|
+
const { resolveStore } = await import('../store.js');
|
|
806
|
+
const store = await resolveStore(cwd);
|
|
807
|
+
try {
|
|
808
|
+
const claims = await store.list(sprint);
|
|
809
|
+
return claims.map(claim => ({
|
|
810
|
+
id: claim.target,
|
|
811
|
+
state: 'in_progress',
|
|
812
|
+
scope: claim.scope,
|
|
813
|
+
last_evidence: claim.notes,
|
|
814
|
+
}));
|
|
815
|
+
}
|
|
816
|
+
finally {
|
|
817
|
+
store.close();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async function restoreResumeClaims(cwd, plan) {
|
|
821
|
+
const claims = plan.resumeClaims.filter(claim => claim.state !== 'done');
|
|
822
|
+
if (claims.length === 0)
|
|
823
|
+
return 0;
|
|
824
|
+
const { resolveStore } = await import('../store.js');
|
|
825
|
+
const store = await resolveStore(cwd);
|
|
826
|
+
const player = process.env.USER || 'unknown';
|
|
827
|
+
let restored = 0;
|
|
828
|
+
try {
|
|
829
|
+
const existing = await store.list(plan.sprint);
|
|
830
|
+
for (const claim of claims) {
|
|
831
|
+
if (existing.some(c => c.target === claim.id && c.player === player))
|
|
832
|
+
continue;
|
|
833
|
+
await store.claim({
|
|
834
|
+
sprint_number: plan.sprint,
|
|
835
|
+
player,
|
|
836
|
+
target: claim.id,
|
|
837
|
+
scope: claim.scope ?? 'ticket',
|
|
838
|
+
notes: claim.last_evidence ? `portable resume: ${claim.last_evidence}` : 'portable resume',
|
|
839
|
+
});
|
|
840
|
+
restored++;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
finally {
|
|
844
|
+
store.close();
|
|
845
|
+
}
|
|
846
|
+
return restored;
|
|
847
|
+
}
|
|
632
848
|
async function resumeCommand(args, cwd) {
|
|
849
|
+
if (shouldUsePortableResume(args)) {
|
|
850
|
+
await portableResumeCommand(args, cwd);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
633
853
|
const sprintArg = args.find(a => !a.startsWith('--'));
|
|
634
854
|
if (!sprintArg) {
|
|
635
855
|
console.error('Usage: slope sprint resume <sprint_id>');
|
|
@@ -875,6 +1095,10 @@ async function validateSprintCommand(args, cwd) {
|
|
|
875
1095
|
export async function sprintCommand(args) {
|
|
876
1096
|
const cwd = process.cwd();
|
|
877
1097
|
const sub = args[0];
|
|
1098
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
1099
|
+
printSprintUsage();
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
878
1102
|
switch (sub) {
|
|
879
1103
|
case 'start':
|
|
880
1104
|
await startCommand(args.slice(1), cwd);
|
|
@@ -896,9 +1120,19 @@ export async function sprintCommand(args) {
|
|
|
896
1120
|
case 'status':
|
|
897
1121
|
await workflowStatusCommand(args.slice(1), cwd);
|
|
898
1122
|
break;
|
|
899
|
-
case 'reset':
|
|
1123
|
+
case 'reset': {
|
|
1124
|
+
const resetArgs = args.slice(1);
|
|
1125
|
+
if (resetArgs.includes('--help') || resetArgs.includes('-h')) {
|
|
1126
|
+
printSprintUsage();
|
|
1127
|
+
break;
|
|
1128
|
+
}
|
|
1129
|
+
if (resetArgs.length > 0) {
|
|
1130
|
+
printSprintUsage();
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
900
1133
|
resetCommand(cwd);
|
|
901
1134
|
break;
|
|
1135
|
+
}
|
|
902
1136
|
case 'run':
|
|
903
1137
|
await runWorkflowCommand(args.slice(1), cwd);
|
|
904
1138
|
break;
|
|
@@ -921,7 +1155,13 @@ export async function sprintCommand(args) {
|
|
|
921
1155
|
await validateSprintCommand(args.slice(1), cwd);
|
|
922
1156
|
break;
|
|
923
1157
|
default:
|
|
924
|
-
|
|
1158
|
+
printSprintUsage();
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function printSprintUsage() {
|
|
1164
|
+
console.log(`
|
|
925
1165
|
slope sprint — Sprint lifecycle management
|
|
926
1166
|
|
|
927
1167
|
Legacy commands:
|
|
@@ -932,6 +1172,10 @@ Legacy commands:
|
|
|
932
1172
|
slope sprint phase <phase> Update current sprint phase
|
|
933
1173
|
slope sprint gate <name> Mark a gate as complete
|
|
934
1174
|
slope sprint status Show sprint state and gates
|
|
1175
|
+
slope sprint resume --portable [--from=path] [--force] [--dry-run]
|
|
1176
|
+
Reconstruct local sprint state from tracked artifacts
|
|
1177
|
+
slope sprint resume --write-pointer [--output=path]
|
|
1178
|
+
Write tracked resume pointer without syncing DB/locks
|
|
935
1179
|
slope sprint reset Clear sprint state
|
|
936
1180
|
|
|
937
1181
|
Workflow commands:
|
|
@@ -939,12 +1183,11 @@ Workflow commands:
|
|
|
939
1183
|
slope sprint status [sprint_id] Show workflow execution progress
|
|
940
1184
|
slope sprint resume <sprint_id> Resume a paused workflow execution
|
|
941
1185
|
slope sprint pause <sprint_id> Pause a running workflow execution
|
|
1186
|
+
slope sprint context <sprint_id> Show current workflow step and remaining work
|
|
1187
|
+
slope sprint validate <sprint_id> Validate workflow, plan, scorecard, and tests
|
|
942
1188
|
slope sprint workflow cleanup --stale [--dry-run] Pause stale completed/superseded executions
|
|
1189
|
+
slope sprint workflow resync Reconcile workflow executions with git/roadmap reality
|
|
943
1190
|
slope sprint skip <id> --step=<s> --reason="..." Skip a blocking step
|
|
944
1191
|
`);
|
|
945
|
-
if (sub)
|
|
946
|
-
process.exit(1);
|
|
947
|
-
break;
|
|
948
|
-
}
|
|
949
1192
|
}
|
|
950
1193
|
//# sourceMappingURL=sprint.js.map
|