@ikunin/sprintpilot 2.2.4 → 2.2.6
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 +1 -1
- package/_Sprintpilot/bin/autopilot.js +24 -1
- package/_Sprintpilot/lib/orchestrator/adapt.js +21 -0
- package/_Sprintpilot/manifest.yaml +1 -1
- package/_Sprintpilot/skills/sprintpilot-update/workflow.md +1 -1
- package/lib/commands/install.js +11 -2
- package/lib/commands/uninstall.js +8 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -192,7 +192,7 @@ Before every commit the orchestrator runs deterministic checks against the stage
|
|
|
192
192
|
| **Secrets scan** | Greps staged content for `API_KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `aws_access`, `private_key`. WARN severity by default — surfaced in the log but does not block the commit. Allowlist patterns live in `.secrets-allowlist`. |
|
|
193
193
|
| **File size** | Rejects files larger than `staging.max_file_size_mb` (default `1`). |
|
|
194
194
|
| **Binary detection** | Warns on binary files detected via `file --mime-encoding`. |
|
|
195
|
-
| **Gitignore check** | Verifies `.gitignore` covers `.autopilot.lock` and `.claude/.
|
|
195
|
+
| **Gitignore check** | Verifies `.gitignore` covers `.autopilot.lock` and `.claude/.sprintpilot-backups/`. |
|
|
196
196
|
|
|
197
197
|
For each story, every commit (the main story commit + each code-review patch commit) runs the full check chain.
|
|
198
198
|
|
|
@@ -526,9 +526,32 @@ function composeRuntimeState(persisted, profile, projectRoot) {
|
|
|
526
526
|
// Forward-compat for ma.parallel_stories: the queue is the source
|
|
527
527
|
// multiple workers will pull from when the parallel-batch path is
|
|
528
528
|
// wired into the state machine.
|
|
529
|
-
|
|
529
|
+
// Validate persisted.story_queue entries against sprint-status. Same
|
|
530
|
+
// rejection rules as current_story (epic-rollup shape / retrospective
|
|
531
|
+
// / missing from sprint-status / marked done) — applies to every queue
|
|
532
|
+
// member. Without this, a legacy queue persisted by an older
|
|
533
|
+
// orchestrator (or after a sprint-status edit that removed entries)
|
|
534
|
+
// would feed garbage keys to subsequent emissions.
|
|
535
|
+
//
|
|
536
|
+
// Defensive: if sprint-status can't be read, only the shape-based
|
|
537
|
+
// rejections (epic-N, retrospective) apply; presence/status checks
|
|
538
|
+
// are skipped. Same don't-punish-missing-artifact policy as
|
|
539
|
+
// current_story validation.
|
|
540
|
+
const rawPersistedQueue = Array.isArray(persisted.story_queue)
|
|
530
541
|
? persisted.story_queue.filter((k) => typeof k === 'string' && k.length > 0)
|
|
531
542
|
: [];
|
|
543
|
+
const persistedQueue = [];
|
|
544
|
+
for (const k of rawPersistedQueue) {
|
|
545
|
+
const reason = persistedStoryRejectionReason(k, projectRoot);
|
|
546
|
+
if (reason) {
|
|
547
|
+
process.stderr.write(
|
|
548
|
+
`[autopilot] WARN story_queue entry "${k}" rejected: ${reason}. ` +
|
|
549
|
+
'Dropping from queue.\n',
|
|
550
|
+
);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
persistedQueue.push(k);
|
|
554
|
+
}
|
|
532
555
|
const isNewStoryStartPhase =
|
|
533
556
|
phase === STATES.CREATE_STORY ||
|
|
534
557
|
phase === STATES.NANO_QUICK_DEV ||
|
|
@@ -405,18 +405,39 @@ function handleUserInput(state, signal, profile, sideEffects) {
|
|
|
405
405
|
// a specific story but autopilot-state.yaml still shows
|
|
406
406
|
// `current_story: null` — subsequent emissions / persists / verify
|
|
407
407
|
// checks all reference the wrong story.
|
|
408
|
+
//
|
|
409
|
+
// Phase advance: when the alternative carries `phase` and it's a
|
|
410
|
+
// valid STATES value, also advance state.phase. Pre-v2.2.6 the
|
|
411
|
+
// dispatch was one-shot — the alternative ran for ONE emission then
|
|
412
|
+
// state.phase reverted, defeating use cases like "skip dev_red /
|
|
413
|
+
// dev_green / code_review because the work is already done on the
|
|
414
|
+
// branch from a prior session." The user explicitly proposes the
|
|
415
|
+
// alternative including a target phase; they accept the consequences
|
|
416
|
+
// (e.g. verify may reject the new phase if its preconditions aren't
|
|
417
|
+
// met). Without this, accept_alternative is useless for cycle skips.
|
|
408
418
|
const dispatch = applied.sideEffects.find((e) => e && e.kind === 'dispatch_action');
|
|
409
419
|
if (dispatch && dispatch.action) {
|
|
410
420
|
const a = dispatch.action;
|
|
411
421
|
const slots = a.template_slots || {};
|
|
422
|
+
const KNOWN_PHASES = new Set(Object.values(STATES));
|
|
423
|
+
const phaseAdvance =
|
|
424
|
+
typeof a.phase === 'string' && KNOWN_PHASES.has(a.phase) && a.phase !== newState.phase
|
|
425
|
+
? a.phase
|
|
426
|
+
: null;
|
|
412
427
|
const enrichedState = {
|
|
413
428
|
...newState,
|
|
429
|
+
phase: phaseAdvance || newState.phase,
|
|
414
430
|
story_key: newState.story_key || slots.story_key || a.story_key || null,
|
|
415
431
|
current_epic:
|
|
416
432
|
newState.current_epic || slots.current_epic || a.epic_key || null,
|
|
417
433
|
story_file_path:
|
|
418
434
|
newState.story_file_path || slots.story_file_path || null,
|
|
419
435
|
ac_summary: newState.ac_summary || slots.ac_summary || null,
|
|
436
|
+
// Reset retry counters on phase advance so the new phase isn't
|
|
437
|
+
// immediately throttled by a stale retry budget from the phase
|
|
438
|
+
// we just skipped.
|
|
439
|
+
retry_count_this_phase: phaseAdvance ? 0 : newState.retry_count_this_phase,
|
|
440
|
+
verify_reject_count: phaseAdvance ? 0 : newState.verify_reject_count,
|
|
420
441
|
};
|
|
421
442
|
return {
|
|
422
443
|
newState: enrichedState,
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
7. **Verify.** Read `_Sprintpilot/manifest.yaml` again and confirm the version updated:
|
|
43
43
|
```
|
|
44
44
|
Updated to v{latest}
|
|
45
|
-
Previous version backed up to .claude/.
|
|
45
|
+
Previous version backed up to .claude/.sprintpilot-backups/
|
|
46
46
|
```
|
|
47
47
|
If the version did not change, warn the user that the update may have failed.
|
package/lib/commands/install.js
CHANGED
|
@@ -1262,9 +1262,18 @@ async function runInstall(options = {}) {
|
|
|
1262
1262
|
for (const tool of selectedTools) {
|
|
1263
1263
|
const toolDir = getToolDir(tool);
|
|
1264
1264
|
const skillsDir = path.join(projectRoot, toolDir, 'skills');
|
|
1265
|
-
|
|
1265
|
+
// Backup dir was renamed in v2.2.6: `.addon-backups/` → `.sprintpilot-backups/`.
|
|
1266
|
+
// Migrate the legacy name on upgrade so users don't end up with both
|
|
1267
|
+
// an old (stale) and new (active) backup dir side-by-side. Migration
|
|
1268
|
+
// is a single fs.rename — preserves all existing backups.
|
|
1269
|
+
const legacyBackupDir = path.join(projectRoot, toolDir, '.addon-backups');
|
|
1270
|
+
const backupDir = path.join(projectRoot, toolDir, '.sprintpilot-backups');
|
|
1271
|
+
if (!dryRun && (await fs.pathExists(legacyBackupDir)) && !(await fs.pathExists(backupDir))) {
|
|
1272
|
+
await fs.rename(legacyBackupDir, backupDir);
|
|
1273
|
+
console.log(`Migrated ${toolDir}/.addon-backups/ → ${toolDir}/.sprintpilot-backups/`);
|
|
1274
|
+
}
|
|
1266
1275
|
|
|
1267
|
-
const backupIgnoreEntry = `${toolDir}/.
|
|
1276
|
+
const backupIgnoreEntry = `${toolDir}/.sprintpilot-backups/`;
|
|
1268
1277
|
const backupIgnoreResult = await addIgnoreEntry(ignore.path, backupIgnoreEntry, { dryRun });
|
|
1269
1278
|
if (backupIgnoreResult.added) {
|
|
1270
1279
|
const name = path.basename(ignore.path);
|
|
@@ -170,10 +170,14 @@ async function runUninstall(options = {}) {
|
|
|
170
170
|
totalRemoved += removed;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
// Remove both the current backup dir AND the legacy name in case the
|
|
174
|
+
// user never ran install after v2.2.6 to migrate it.
|
|
175
|
+
for (const name of ['.sprintpilot-backups', '.addon-backups']) {
|
|
176
|
+
const backupDir = path.join(projectRoot, toolDir, name);
|
|
177
|
+
if (await fs.pathExists(backupDir)) {
|
|
178
|
+
await fs.remove(backupDir);
|
|
179
|
+
console.log(`${tool}: removed ${name}/`);
|
|
180
|
+
}
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
await removeSystemPrompt(tool, projectRoot);
|
package/package.json
CHANGED