@ikunin/sprintpilot 2.1.4 → 2.2.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/README.md CHANGED
@@ -31,6 +31,17 @@ npx @ikunin/sprintpilot@latest
31
31
  /sprint-autopilot-on
32
32
  ```
33
33
 
34
+ **Start at a specific story or epic** (v2.2.0+):
35
+
36
+ ```
37
+ /sprint-autopilot-on epic 4
38
+ /sprint-autopilot-on stories 3.1, 3.2, 4.5
39
+ /sprint-autopilot-on 4-8-realm-wide-matcher-and-session-lock
40
+ /sprint-autopilot-on voice identity matcher
41
+ ```
42
+
43
+ The skill resolves the natural-language directive against your `sprint-status.yaml` and queues the matching stories. The autopilot runs them in order, then falls back to the normal next-pending-story flow. Ambiguous matches surface a candidate list — never picks arbitrarily.
44
+
34
45
  Non-interactive install:
35
46
 
36
47
  ```bash
@@ -65,6 +65,25 @@ function help() {
65
65
  ' --profile <nano|small|medium|large|legacy>',
66
66
  ' Override resolved profile',
67
67
  ' --help Show this help',
68
+ '',
69
+ 'Story-selection flags (on `start` only):',
70
+ ' --stories <k1,k2,...> Explicit queue of story keys to run, in',
71
+ ' order. Keys must exist in sprint-status.yaml',
72
+ ' and not be already done. Once the queue',
73
+ ' exhausts, the orchestrator falls back to',
74
+ ' its normal next-pending-story flow.',
75
+ ' --epic <id> Queue all non-done stories of the given',
76
+ ' epic (id matches `epic-N` or bare `N`),',
77
+ ' in sprint-status.yaml order. --stories',
78
+ ' takes priority when both are given.',
79
+ ' --force Overwrite an in-flight queue. Without',
80
+ ' this, --stories/--epic refuses to run',
81
+ ' when current_story is set or a queue',
82
+ ' already exists.',
83
+ '',
84
+ 'Natural-language entry: `/sprint-autopilot-on epic 4` /',
85
+ '`/sprint-autopilot-on stories 3.1, 4.5` — the skill resolves the NL',
86
+ 'directive to canonical keys and invokes `autopilot start --stories`.',
68
87
  ].join('\n'),
69
88
  );
70
89
  }
@@ -109,10 +128,11 @@ function safeExistsSync(p) {
109
128
  // back to "story/unknown" because state.story_key is null on a fresh
110
129
  // sprint (CREATE_STORY hasn't run yet; for nano there's no CREATE_STORY
111
130
  // at all and quick-dev reads sprint-status itself). Returns the first
112
- // story whose status is NOT "done" (case-insensitive), or null when:
131
+ // non-done STORY key (filtering out epic rollup headers), or null when:
113
132
  // - the status file doesn't exist (pre-planning)
114
133
  // - all stories are done (sprint complete)
115
134
  // - the file can't be parsed
135
+ // - the only non-done entries are epic rollups (no real stories yet)
116
136
  function resolveNextStoryKey(projectRoot) {
117
137
  if (!projectRoot) return null;
118
138
  const sprintStatusPath = path.join(
@@ -126,12 +146,167 @@ function resolveNextStoryKey(projectRoot) {
126
146
  const raw = fs.readFileSync(sprintStatusPath, 'utf8');
127
147
  const stories = parseSprintStatuses(raw);
128
148
  const remaining = remainingStoriesFrom(stories);
129
- return remaining.length > 0 ? remaining[0] : null;
149
+ // parseStatuses returns every key under `development_status:`
150
+ // including BMad's epic rollup headers (`epic-4: in-progress`).
151
+ // Filter them out so we don't ask the orchestrator to branch on an
152
+ // epic identifier (the v2.1.4 hotfix shipped without this filter and
153
+ // a user reported branch: story/epic-4 instead of story/4-8-...).
154
+ const realStories = remaining.filter(looksLikeStoryKey);
155
+ return realStories.length > 0 ? realStories[0] : null;
130
156
  } catch (_e) {
131
157
  return null;
132
158
  }
133
159
  }
134
160
 
161
+ // Tell story keys apart from non-story bookkeeping entries in
162
+ // sprint-status.yaml. BMad development_status: holds three kinds of
163
+ // entries that parseStatuses returns side-by-side:
164
+ //
165
+ // 1. Real stories — `4-8-realm-wide-matcher` / `epic-1-game-engine`.
166
+ // Always have at least one hyphen AFTER the epic identifier.
167
+ // 2. Epic rollup headers — `epic-4` / bare `4`. The status reflects
168
+ // child-story rollup, not a unit of work for the autopilot.
169
+ // 3. Retrospective entries — `4-retrospective` / `epic-4-retrospective`.
170
+ // Status tracks whether the per-epic retro ritual has run; not a
171
+ // story to dev.
172
+ //
173
+ // Reject (2) and (3). The v2.1.4 hotfix shipped without this filter and
174
+ // the user reported `branch: story/epic-4` instead of the real next
175
+ // pending story. The v2.1.5 hotfix extends the filter to retrospectives
176
+ // after a follow-up report.
177
+ function looksLikeStoryKey(key) {
178
+ if (typeof key !== 'string' || !key) return false;
179
+ // Retrospective entries (`-retrospective` suffix, with or without epic
180
+ // prefix). Match anywhere the suffix appears so `epic-4-retrospective`
181
+ // and `4-retrospective` are both rejected.
182
+ if (/-retrospective$/i.test(key)) return false;
183
+ // Strip any leading `epic-` prefix and require a remaining hyphen.
184
+ // `epic-4` → `4` → no hyphen → epic header (reject).
185
+ // `epic-1-game-engine` → `1-game-engine` → has hyphen → story (accept).
186
+ // `4-8-realm-wide-matcher` → unchanged → has hyphen → story (accept).
187
+ const withoutEpicPrefix = key.replace(/^epic-/i, '');
188
+ return withoutEpicPrefix.includes('-');
189
+ }
190
+
191
+ // Build an explicit story queue from CLI opts (--stories / --epic).
192
+ // Returns { queue: [], error?: string }. Either or both flags can be
193
+ // provided; --stories is the canonical list and --epic expands to all
194
+ // non-done stories under that epic. When both are given, --stories
195
+ // takes priority. When neither is given, returns an empty queue
196
+ // (orchestrator falls back to resolveNextStoryKey).
197
+ //
198
+ // Validation:
199
+ // - Every key listed in --stories must exist in sprint-status.yaml.
200
+ // - Every key must NOT have status 'done'.
201
+ // - For --epic, the epic must have at least one non-done story.
202
+ function buildExplicitQueueFromOpts(opts, projectRoot) {
203
+ const rawStories = typeof opts.stories === 'string' ? opts.stories : null;
204
+ const rawEpic = opts.epic !== undefined && opts.epic !== null ? String(opts.epic) : null;
205
+ if (!rawStories && !rawEpic) return { queue: [] };
206
+
207
+ const sprintStories = readSprintStatuses(projectRoot);
208
+ if (!sprintStories || Object.keys(sprintStories).length === 0) {
209
+ return {
210
+ queue: [],
211
+ error:
212
+ '--stories / --epic given but sprint-status.yaml is missing or empty. ' +
213
+ 'Run BMad sprint-planning to populate it before queuing stories.',
214
+ };
215
+ }
216
+
217
+ if (rawStories) {
218
+ const requested = rawStories
219
+ .split(',')
220
+ .map((s) => s.trim())
221
+ .filter(Boolean);
222
+ if (requested.length === 0) {
223
+ return { queue: [], error: '--stories was empty after parsing the comma-separated list.' };
224
+ }
225
+ const missing = [];
226
+ const alreadyDone = [];
227
+ const queue = [];
228
+ for (const key of requested) {
229
+ if (!Object.prototype.hasOwnProperty.call(sprintStories, key)) {
230
+ missing.push(key);
231
+ continue;
232
+ }
233
+ const status = String(sprintStories[key].status || '').trim().toLowerCase();
234
+ if (status === 'done') {
235
+ alreadyDone.push(key);
236
+ continue;
237
+ }
238
+ queue.push(key);
239
+ }
240
+ if (missing.length > 0 || alreadyDone.length > 0) {
241
+ const parts = [];
242
+ if (missing.length > 0) {
243
+ parts.push(
244
+ `not in sprint-status.yaml: ${missing.join(', ')}. ` +
245
+ 'Use canonical keys (e.g. 4-8-realm-wide-matcher), not story numbers (e.g. 4.8).',
246
+ );
247
+ }
248
+ if (alreadyDone.length > 0) {
249
+ parts.push(`already done: ${alreadyDone.join(', ')}`);
250
+ }
251
+ return { queue: [], error: `--stories rejected: ${parts.join(' | ')}` };
252
+ }
253
+ return { queue };
254
+ }
255
+
256
+ // --epic only
257
+ const expanded = resolveStoriesForEpic(projectRoot, rawEpic);
258
+ if (expanded.length === 0) {
259
+ return {
260
+ queue: [],
261
+ error: `--epic ${rawEpic}: no non-done stories found in sprint-status.yaml`,
262
+ };
263
+ }
264
+ return { queue: expanded };
265
+ }
266
+
267
+ // Read and parse sprint-status.yaml. Returns { stories } where stories
268
+ // is a map of {key: {status: string|null}}. Returns null on any failure
269
+ // (missing file, parse error). Callers handle null gracefully.
270
+ function readSprintStatuses(projectRoot) {
271
+ if (!projectRoot) return null;
272
+ const p = path.join(
273
+ projectRoot,
274
+ '_bmad-output',
275
+ 'implementation-artifacts',
276
+ 'sprint-status.yaml',
277
+ );
278
+ if (!safeExistsSync(p)) return null;
279
+ try {
280
+ const raw = fs.readFileSync(p, 'utf8');
281
+ return parseSprintStatuses(raw);
282
+ } catch (_e) {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ // Resolve all non-done story keys for the given epic id, in
288
+ // sprint-status.yaml insertion order. Used by `autopilot start
289
+ // --epic <id>` to expand into an explicit queue. Returns [] when:
290
+ // - sprint-status doesn't exist or fails to parse
291
+ // - no stories match the epic
292
+ // - all matching stories are already done
293
+ function resolveStoriesForEpic(projectRoot, epicId) {
294
+ if (!epicId) return [];
295
+ const stories = readSprintStatuses(projectRoot);
296
+ if (!stories) return [];
297
+ const keys = Object.keys(stories);
298
+ const out = [];
299
+ for (const key of keys) {
300
+ if (!looksLikeStoryKey(key)) continue;
301
+ const derivedEpic = deriveEpicFromStoryKey(key);
302
+ if (derivedEpic !== epicId && derivedEpic !== `epic-${epicId}`) continue;
303
+ const status = String(stories[key].status || '').trim().toLowerCase();
304
+ if (status === 'done') continue;
305
+ out.push(key);
306
+ }
307
+ return out;
308
+ }
309
+
135
310
  // Derive the epic identifier from a BMad story key. Convention:
136
311
  // `epic-N-slug` → `epic-N`; `<epic>-<story>-<slug>` → `<epic>`.
137
312
  // Returns null when the key doesn't parse cleanly. Kept in sync with
@@ -247,6 +422,20 @@ function composeRuntimeState(persisted, profile, projectRoot) {
247
422
  let resolvedStoryKey = persisted.current_story || null;
248
423
  let resolvedEpic = persisted.current_epic || null;
249
424
  let resolvedStoryFilePath = persisted.story_file_path || null;
425
+ // Explicit queue (populated by `autopilot start --stories` / `--epic`)
426
+ // takes priority over the linear resolveNextStoryKey scan: when a
427
+ // user specifies "start with stories 4-1, 4-2, 4-5" we honor that
428
+ // order regardless of what comes first in sprint-status.yaml.
429
+ // Forward-compat for ma.parallel_stories: the queue is the source
430
+ // multiple workers will pull from when the parallel-batch path is
431
+ // wired into the state machine.
432
+ const persistedQueue = Array.isArray(persisted.story_queue)
433
+ ? persisted.story_queue.filter((k) => typeof k === 'string' && k.length > 0)
434
+ : [];
435
+ if (!resolvedStoryKey && persistedQueue.length > 0) {
436
+ resolvedStoryKey = persistedQueue[0];
437
+ if (!resolvedEpic) resolvedEpic = deriveEpicFromStoryKey(resolvedStoryKey);
438
+ }
250
439
  if (phase === STATES.PREPARE_STORY_BRANCH && !resolvedStoryKey) {
251
440
  const next = resolveNextStoryKey(projectRoot);
252
441
  if (next) {
@@ -280,6 +469,10 @@ function composeRuntimeState(persisted, profile, projectRoot) {
280
469
  escalation_note: persisted.escalation_note || null,
281
470
  // Branch reuse: persisted across resumes once detected on first boot.
282
471
  user_branch: persisted.user_branch || null,
472
+ // Explicit story queue from `autopilot start --stories` / `--epic`.
473
+ // Head is the current pick; adapt.advanceState pops on story
474
+ // completion. Empty array means "no override; use resolveNextStoryKey."
475
+ story_queue: persistedQueue,
283
476
  // Land-as-you-go: pending land state survives rebase-conflict halts.
284
477
  land_pending: persisted.land_pending || null,
285
478
  // Pending alternative (propose_alternative → user_prompt) survives
@@ -311,6 +504,7 @@ function persistRuntimeState(runtime, profile, projectRoot) {
311
504
  verify_reject_count: runtime.verify_reject_count,
312
505
  consecutive_test_failures: runtime.consecutive_test_failures,
313
506
  user_branch: runtime.user_branch,
507
+ story_queue: Array.isArray(runtime.story_queue) ? runtime.story_queue : [],
314
508
  land_pending: runtime.land_pending,
315
509
  pending_alternative: runtime.pending_alternative || null,
316
510
  };
@@ -617,6 +811,37 @@ function cmdStart(opts) {
617
811
  const { typed: profile } = resolveProfile(projectRoot, opts.profile);
618
812
  const persisted = loadState(projectRoot);
619
813
 
814
+ // Build an explicit story queue from --stories / --epic flags. The
815
+ // user (or the LLM via /sprint-autopilot-on natural-language args)
816
+ // tells the orchestrator EXACTLY which stories to run, and in what
817
+ // order. Queue head is consumed first; resolveNextStoryKey takes
818
+ // over once the queue exhausts.
819
+ const queueBuildResult = buildExplicitQueueFromOpts(opts, projectRoot);
820
+ if (queueBuildResult.error) {
821
+ log.error(queueBuildResult.error);
822
+ process.stdout.write(
823
+ `${JSON.stringify({ error: queueBuildResult.error, kind: 'queue_validation_error' }, null, 2)}\n`,
824
+ );
825
+ return 2;
826
+ }
827
+ const explicitQueue = queueBuildResult.queue; // may be []
828
+
829
+ // Mid-sprint guard: refuse to overwrite an in-flight queue without
830
+ // --force. The user almost certainly wants to finish what's running
831
+ // before pivoting; a silent overwrite would lose state.
832
+ if (
833
+ explicitQueue.length > 0 &&
834
+ (persisted.current_story || (persisted.story_queue || []).length > 0) &&
835
+ !opts.force
836
+ ) {
837
+ const err =
838
+ `Sprint already in progress (current_story=${persisted.current_story || '<queue head>'}). ` +
839
+ `Pass --force to overwrite the queue, or finish the current story first.`;
840
+ log.error(err);
841
+ process.stdout.write(`${JSON.stringify({ error: err, kind: 'mid_sprint_queue_overwrite' }, null, 2)}\n`);
842
+ return 2;
843
+ }
844
+
620
845
  // Resume detection: if a prior session left a fingerprint, diff.
621
846
  const lastHalt = ledger.last({ projectRoot }, 'halt');
622
847
  if (lastHalt && lastHalt.fingerprint) {
@@ -633,6 +858,25 @@ function cmdStart(opts) {
633
858
  }
634
859
  }
635
860
 
861
+ // Persist the new queue BEFORE composing runtime state so the queue
862
+ // head is visible to composeRuntimeState's resolver.
863
+ if (explicitQueue.length > 0) {
864
+ persisted.story_queue = explicitQueue;
865
+ // --force overwrite also clears the prior story identity so the
866
+ // queue head is selected cleanly. Without this, persisted.current_
867
+ // story would short-circuit the queue read.
868
+ if (opts.force) {
869
+ persisted.current_story = null;
870
+ persisted.story_file_path = null;
871
+ persisted.current_epic = null;
872
+ persisted.current_bmad_step = null;
873
+ }
874
+ ledger.append(
875
+ { kind: 'story_queue_set', queue: explicitQueue, force: !!opts.force },
876
+ { projectRoot },
877
+ );
878
+ }
879
+
636
880
  // Fresh start or clean resume. `composeRuntimeState` applies the
637
881
  // profile-aware initial phase when persisted state is empty.
638
882
  const runtime = composeRuntimeState(persisted, profile, projectRoot);
@@ -849,7 +1093,7 @@ function cmdStatus(opts) {
849
1093
  // ------------------------------------------------------------ main
850
1094
 
851
1095
  function main(argv) {
852
- const { opts, positional } = parseArgs(argv, { booleanFlags: ['help'] });
1096
+ const { opts, positional } = parseArgs(argv, { booleanFlags: ['help', 'force'] });
853
1097
  if (opts.help) {
854
1098
  help();
855
1099
  return 0;
@@ -35,6 +35,10 @@ const VALID_KINDS = [
35
35
  'lock_acquired',
36
36
  'lock_released',
37
37
  'flush',
38
+ // Explicit story queue installed via `autopilot start --stories` /
39
+ // `--epic`. Logged once per start invocation so resume/audit can see
40
+ // why a queue head differs from sprint-status's natural order.
41
+ 'story_queue_set',
38
42
  ];
39
43
 
40
44
  function isPlainObject(v) {
@@ -548,6 +548,23 @@ function advanceState(state, profile, newPhase, signal) {
548
548
  }
549
549
  }
550
550
 
551
+ // Story-completion boundary: STORY_DONE → EPIC_BOUNDARY_CHECK means
552
+ // the current story is committed and pushed. Pop the explicit story
553
+ // queue (if any) — its head was THIS story — and clear story_key so
554
+ // composeRuntimeState picks queue[1] (now queue[0]) on the next
555
+ // emission. Without this pop, composeRuntimeState would re-pick the
556
+ // just-completed story (via the signal-output propagation above) and
557
+ // loop. This block runs AFTER propagation so the clearing wins.
558
+ if (state.phase === STATES.STORY_DONE && newPhase === STATES.EPIC_BOUNDARY_CHECK) {
559
+ if (Array.isArray(state.story_queue) && state.story_queue.length > 0) {
560
+ next.story_queue = state.story_queue.slice(1);
561
+ }
562
+ next.story_key = null;
563
+ next.story_file_path = null;
564
+ next.current_epic = null;
565
+ next.ac_summary = null;
566
+ }
567
+
551
568
  return next;
552
569
  }
553
570
 
@@ -27,6 +27,11 @@ const CRITICAL_KEYS = new Set([
27
27
  'current_bmad_step',
28
28
  'in_worktree',
29
29
  'patch_commits',
30
+ // Explicit story queue populated by `autopilot start --stories <csv>`
31
+ // / `--epic <id>`. composeRuntimeState reads queue[0] as the next
32
+ // story_key; adapt.advanceState pops the head when a story completes.
33
+ // When empty, the orchestrator falls back to resolveNextStoryKey.
34
+ 'story_queue',
30
35
  ]);
31
36
 
32
37
  // In-memory pending buffer. Process-scoped — flushed at story boundary or
@@ -1,6 +1,6 @@
1
1
  addon:
2
2
  name: sprintpilot
3
- version: 2.1.4
3
+ version: 2.2.0
4
4
  description: Sprintpilot — autopilot and multi-agent addon for BMad Method (git workflow, parallel agents, autonomous story execution)
5
5
  bmad_compatibility: ">=6.2.0"
6
6
  modules:
@@ -26,3 +26,87 @@ Follow **`./workflow.orchestrator.md`** verbatim. Flow control lives in
26
26
 
27
27
  `workflow.orchestrator.md` is the **sole authority** for the rest of the
28
28
  session.
29
+
30
+ ---
31
+
32
+ ## Natural-language entry: starting at a specific story or epic
33
+
34
+ The user may invoke this skill with extra arguments specifying which
35
+ story / epic to run, e.g.:
36
+
37
+ - `/sprint-autopilot-on epic 4`
38
+ - `/sprint-autopilot-on stories 3.1, 3.2, 4.5`
39
+ - `/sprint-autopilot-on 4-8-realm-wide-matcher`
40
+ - `/sprint-autopilot-on voice identity matcher`
41
+ - `/sprint-autopilot-on starting from 4.5`
42
+
43
+ Translate the natural-language directive into an explicit queue of
44
+ canonical story keys, then call `autopilot start --stories <csv>` (or
45
+ `--epic <id>`). The orchestrator validates the keys before running.
46
+
47
+ **Resolution procedure** — do this BEFORE the first `autopilot next`:
48
+
49
+ 1. **Read sprint-status.yaml** at `_bmad-output/implementation-artifacts/sprint-status.yaml`.
50
+ If it's missing, tell the user to run BMad sprint-planning first and
51
+ stop — don't invoke `autopilot start`.
52
+
53
+ 2. **Parse the user's directive.** Match against these forms:
54
+
55
+ | User says | Resolve to |
56
+ |---|---|
57
+ | "epic 4", "epic-4", "all of epic 4" | `autopilot start --epic 4` |
58
+ | "stories 3.1, 3.2, 4.5", "3.1 3.2 4.5" | Match each `<epic>.<story>` to canonical keys (`3-1-*`, `3-2-*`, `4-5-*`) in sprint-status order; `autopilot start --stories <csv>` |
59
+ | "4-8-realm-wide-matcher" (already canonical) | `autopilot start --stories 4-8-realm-wide-matcher` |
60
+ | "voice identity", "matcher" (name fragment) | Search story slugs in sprint-status for fuzzy matches |
61
+ | "starting from 4.5" (everything from here) | Resolve `4.5` to canonical key, then queue it + every subsequent non-done story in sprint-status order: `autopilot start --stories <key1>,<key2>,...` |
62
+ | (no extra args) | Plain `autopilot start` — orchestrator picks the next pending story |
63
+
64
+ 3. **Ambiguity handling.** If a name fragment or number matches more
65
+ than one story, **do not pick arbitrarily**. List the candidates and
66
+ ask the user to disambiguate. Example:
67
+
68
+ > "voice identity" matches 3 stories:
69
+ > - 3-2-speaker-enrollment
70
+ > - 4-2b-voice-identity-matcher
71
+ > - 4-8-realm-wide-matcher-and-session-lock
72
+ > Which one(s) do you mean?
73
+
74
+ Do not invoke `autopilot start` with a guess.
75
+
76
+ 4. **Validation.** Every resolved key must exist in sprint-status.yaml
77
+ and not be `done`. The orchestrator double-checks this and errors
78
+ out otherwise — but verifying ahead of time gives the user clearer
79
+ feedback. If a key resolves to a `done` entry, mention that and ask
80
+ whether they meant something else.
81
+
82
+ 5. **Mid-sprint overwrite.** If `autopilot state` shows a sprint already
83
+ in progress (`current_story` is set or `story_queue` is non-empty)
84
+ AND the user is asking to start something different, the orchestrator
85
+ will refuse without `--force`. Confirm with the user before adding
86
+ `--force` — it discards the current story identity.
87
+
88
+ 6. **Continuation behavior.** Once the explicit queue exhausts, the
89
+ orchestrator falls back to its normal next-pending-story resolver
90
+ (so a user who says "epic 4" gets epic 4 done, then continues with
91
+ whatever comes next in sprint-status — including epic 5+). Tell the
92
+ user this if they ask.
93
+
94
+ 7. **Then proceed normally.** After `autopilot start ...` returns
95
+ successfully, follow `workflow.orchestrator.md` from `autopilot next`
96
+ onward.
97
+
98
+ ### Examples
99
+
100
+ | Input | Resolved invocation |
101
+ |---|---|
102
+ | `/sprint-autopilot-on` | `autopilot start --project-root <root>` |
103
+ | `/sprint-autopilot-on epic 4` | `autopilot start --epic 4 --project-root <root>` |
104
+ | `/sprint-autopilot-on stories 3.1, 3.2` (after matching `3-1-game-engine`, `3-2-input-parser`) | `autopilot start --stories 3-1-game-engine,3-2-input-parser --project-root <root>` |
105
+ | `/sprint-autopilot-on 4-8-realm-wide-matcher-and-session-lock` | `autopilot start --stories 4-8-realm-wide-matcher-and-session-lock --project-root <root>` |
106
+ | `/sprint-autopilot-on starting from 4.5` (resolved + all-subsequent) | `autopilot start --stories 4-5-realm-config,4-8-realm-wide-matcher --project-root <root>` |
107
+
108
+ Failure cases that should stop you (do NOT invoke autopilot):
109
+
110
+ - `sprint-status.yaml` missing → ask the user to run sprint-planning.
111
+ - Ambiguous match → list candidates, ask which.
112
+ - Every resolved key is `done` → tell the user there's nothing to run.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "description": "Sprintpilot — autopilot and multi-agent addon for BMad Method v6: git workflow, parallel agents, autonomous story execution",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {