@ikunin/sprintpilot 1.0.4 → 2.0.4

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 (37) hide show
  1. package/_Sprintpilot/Sprintpilot.md +14 -1
  2. package/_Sprintpilot/manifest.yaml +1 -1
  3. package/_Sprintpilot/modules/autopilot/config.yaml +22 -0
  4. package/_Sprintpilot/modules/autopilot/profiles/_base.yaml +45 -0
  5. package/_Sprintpilot/modules/autopilot/profiles/large.yaml +22 -0
  6. package/_Sprintpilot/modules/autopilot/profiles/legacy.yaml +35 -0
  7. package/_Sprintpilot/modules/autopilot/profiles/medium.yaml +5 -0
  8. package/_Sprintpilot/modules/autopilot/profiles/nano.yaml +35 -0
  9. package/_Sprintpilot/modules/autopilot/profiles/small.yaml +5 -0
  10. package/_Sprintpilot/modules/git/config.yaml +8 -0
  11. package/_Sprintpilot/modules/ma/config.yaml +42 -0
  12. package/_Sprintpilot/scripts/agent-adapter.js +247 -0
  13. package/_Sprintpilot/scripts/cached-read.js +238 -0
  14. package/_Sprintpilot/scripts/check-prereqs.js +139 -0
  15. package/_Sprintpilot/scripts/dispatch-layer.js +192 -0
  16. package/_Sprintpilot/scripts/git-portable.js +219 -0
  17. package/_Sprintpilot/scripts/infer-dependencies.js +594 -0
  18. package/_Sprintpilot/scripts/inject-tasks-section.js +279 -0
  19. package/_Sprintpilot/scripts/list-remaining-stories.js +295 -0
  20. package/_Sprintpilot/scripts/log-timing.js +360 -0
  21. package/_Sprintpilot/scripts/mark-done-stories-tasks.js +254 -0
  22. package/_Sprintpilot/scripts/merge-shards.js +339 -0
  23. package/_Sprintpilot/scripts/preflight-merge.js +235 -0
  24. package/_Sprintpilot/scripts/resolve-dag.js +559 -0
  25. package/_Sprintpilot/scripts/resolve-profile.js +355 -0
  26. package/_Sprintpilot/scripts/state-shard.js +602 -0
  27. package/_Sprintpilot/scripts/submodule-lock.js +130 -0
  28. package/_Sprintpilot/scripts/summarize-timings.js +362 -0
  29. package/_Sprintpilot/scripts/sync-status.js +13 -0
  30. package/_Sprintpilot/scripts/with-retry.js +145 -0
  31. package/_Sprintpilot/skills/sprint-autopilot-on/workflow.md +673 -540
  32. package/_Sprintpilot/skills/sprintpilot-update/workflow.md +2 -1
  33. package/_Sprintpilot/templates/epic-retrospective.md +24 -0
  34. package/_Sprintpilot/templates/sprint-report.txt +60 -0
  35. package/bin/sprintpilot.js +4 -0
  36. package/lib/commands/install.js +157 -1
  37. package/package.json +1 -1
@@ -34,8 +34,9 @@
34
34
 
35
35
  6. **Install the update.** On confirmation, run:
36
36
  ```bash
37
- npx @ikunin/sprintpilot@latest@{latest} install --yes
37
+ npx @ikunin/sprintpilot@{latest} install --yes
38
38
  ```
39
+ (`{latest}` is the version resolved in step 2 — e.g., `npx @ikunin/sprintpilot@1.0.4 install --yes`.)
39
40
  Stream the output so the user can see progress.
40
41
 
41
42
  7. **Verify.** Read `_Sprintpilot/manifest.yaml` again and confirm the version updated:
@@ -0,0 +1,24 @@
1
+ # Epic {{epic_id}} — {{epic_title}} — Retrospective
2
+
3
+ **Completed:** {current_date}
4
+ **Stories done:** {{n_done}}/{{n_total}}
5
+
6
+ ## Stories
7
+ {{#each stories}}
8
+ - **{{story-key}}** — {{title}}
9
+ - Tests: {{test_pass_count}}
10
+ - Patches applied: {{patch_count}}
11
+ {{/each}}
12
+
13
+ ## Key decisions
14
+ {{#each decisions}}
15
+ - [{{impact}}] {{category}}: {{decision}} — {{rationale}}
16
+ {{/each}}
17
+
18
+ ## Risks carried forward
19
+ {{#each open_risks}}
20
+ - {{risk}}
21
+ {{/each}}
22
+
23
+ ## Notes
24
+ Generated inline by Sprintpilot autopilot per `autopilot.retrospective_mode: auto`.
@@ -0,0 +1,60 @@
1
+ ╔═══════════════════════════════════════════════════════════════╗
2
+ ║ BMAD AUTOPILOT — REPORT ║
3
+ ╚═══════════════════════════════════════════════════════════════╝
4
+
5
+ SUMMARY
6
+ Stories completed : {{done_count}}/{{total_stories}}
7
+ Epics completed : {{done_epics}}/{{total_epics}}
8
+ Total tests : {{N}}/{{N}} passed
9
+ {{#if git_enabled}}
10
+ Platform : {{platform}}
11
+ {{/if}}
12
+
13
+ STORIES
14
+ {{#each epic}}
15
+ Epic {{epic_number}}: {{epic_title}}
16
+ {{#each stories}}
17
+ ✓ {{story-key}} — {{test_count}} tests{{#if pr_url}} PR: {{pr_url}}{{/if}}
18
+ {{/each}}
19
+ {{/each}}
20
+ {{#if remaining_stories}}
21
+ Not started:
22
+ {{#each remaining_stories}}
23
+ · {{story-key}}
24
+ {{/each}}
25
+ {{/if}}
26
+
27
+ DECISIONS REQUIRING REVIEW (high/medium impact)
28
+ {{#each medium_high_decisions}}
29
+ #{{id}} [{{impact}}] {{story}} / {{phase}}
30
+ {{decision}}
31
+ → {{rationale}}
32
+ {{/each}}
33
+ {{#if no_medium_high_decisions}}
34
+ None — all decisions were low-impact.
35
+ {{/if}}
36
+
37
+ Full log: {decision_log_file}
38
+
39
+ REVIEW FINDINGS APPLIED
40
+ Patches applied : {{total_patches}}
41
+ Findings dismissed : {{total_dismissed}}
42
+ Review rounds : {{total_review_rounds}}
43
+
44
+ CODE REVIEW SUMMARY (per story)
45
+ {{#each completed_stories}}
46
+ {{story-key}} : {{patches_applied}} patches applied, {{findings_dismissed}} dismissed
47
+ {{/each}}
48
+
49
+ WHAT TO DO NEXT
50
+ 1. Review decisions marked medium/high above
51
+ {{#if has_pr_urls}}
52
+ 2. Merge open PRs: {{pr_urls_list}}
53
+ {{/if}}
54
+ {{#if launch_cmd}}
55
+ {{next_number}}. Run the app: {{launch_cmd}}
56
+ {{/if}}
57
+ {{next_number}}. Manual smoke test checklist:
58
+ {{#each completed_stories}}
59
+ · [{{story-key}}] {{smoke_test_suggestion}}
60
+ {{/each}}
@@ -53,6 +53,10 @@ async function main() {
53
53
  '--tools <list>',
54
54
  'Comma-separated tools (claude-code,cursor,windsurf,cline,roo,trae,kiro,gemini-cli,github-copilot,all)',
55
55
  )
56
+ .option(
57
+ '--profile <name>',
58
+ 'Complexity profile: nano | small | medium | large | legacy (default: medium)',
59
+ )
56
60
  .option('--dry-run', 'Preview without making changes')
57
61
  .option('--force', 'Skip backup of existing skills')
58
62
  .option(
@@ -67,6 +67,8 @@ const PROJECT_ADDON_DIR_NAME = '_Sprintpilot';
67
67
  const DEFAULT_SESSION_STORY_LIMIT = 3;
68
68
  const DEFAULT_RETROSPECTIVE_MODE = 'auto';
69
69
  const RETROSPECTIVE_MODES = ['auto', 'stop', 'skip'];
70
+ const COMPLEXITY_PROFILES = ['nano', 'small', 'medium', 'large', 'legacy'];
71
+ const DEFAULT_COMPLEXITY_PROFILE = 'medium';
70
72
  const RUNTIME_RESOURCES = [
71
73
  'Sprintpilot.md',
72
74
  'manifest.yaml',
@@ -747,6 +749,134 @@ async function patchAutopilotConfig(projectRoot, { sessionStoryLimit, retrospect
747
749
  }
748
750
  }
749
751
 
752
+ // Read the existing complexity_profile from autopilot/config.yaml so upgrades
753
+ // preserve the user's v2 profile choice. Uses regex for the same reason
754
+ // readExistingAutopilotConfig does — workflow.md's `{{variable}}` syntax
755
+ // means a full YAML parse would mis-interpret the raw file.
756
+ async function readExistingComplexityProfile(projectRoot, v1Snapshot) {
757
+ let raw = null;
758
+ const candidates = [
759
+ path.join(projectRoot, PROJECT_ADDON_DIR_NAME, 'modules', 'autopilot', 'config.yaml'),
760
+ path.join(projectRoot, V1_ADDON_DIR_NAME, 'modules', 'autopilot', 'config.yaml'),
761
+ ];
762
+ for (const file of candidates) {
763
+ if (!(await fs.pathExists(file))) continue;
764
+ try {
765
+ raw = await fs.readFile(file, 'utf8');
766
+ break;
767
+ } catch {
768
+ /* try next */
769
+ }
770
+ }
771
+ if (raw == null && v1Snapshot && Array.isArray(v1Snapshot.autopilot)) {
772
+ const entry = v1Snapshot.autopilot.find((f) => f.relPath === 'config.yaml');
773
+ if (entry && Buffer.isBuffer(entry.buffer)) {
774
+ try {
775
+ raw = entry.buffer.toString('utf8');
776
+ } catch {
777
+ /* unreadable */
778
+ }
779
+ }
780
+ }
781
+ if (raw == null) return null;
782
+ const commentTail = /[ \t]*(?:#.*)?$/.source;
783
+ const m = raw.match(
784
+ new RegExp(`^[ \\t]*complexity_profile:[ \\t]*["']?([a-zA-Z_-]+)["']?${commentTail}`, 'm'),
785
+ );
786
+ if (!m) return null;
787
+ if (!COMPLEXITY_PROFILES.includes(m[1])) return null;
788
+ return m[1];
789
+ }
790
+
791
+ async function patchComplexityProfile(projectRoot, profile) {
792
+ const file = path.join(
793
+ projectRoot,
794
+ PROJECT_ADDON_DIR_NAME,
795
+ 'modules',
796
+ 'autopilot',
797
+ 'config.yaml',
798
+ );
799
+ if (!(await fs.pathExists(file))) return;
800
+ const original = await fs.readFile(file, 'utf8');
801
+ const updated = applyScalar(original, 'complexity_profile', profile);
802
+ if (updated !== original) {
803
+ await writeAtomic(file, updated);
804
+ }
805
+ }
806
+
807
+ // Validates --profile flag (if provided) and prompts interactively when
808
+ // neither flag nor --yes is set. Returns the resolved profile string.
809
+ // Backwards-compatible: if the user is upgrading and already has a profile
810
+ // key, default to that; if absent, default to 'medium' (matches v1.0.5).
811
+ async function resolveComplexityProfile({ projectRoot, yes, dryRun, options, v1Snapshot }) {
812
+ const existing = await readExistingComplexityProfile(projectRoot, v1Snapshot);
813
+
814
+ // Flag takes priority even in interactive mode so scripted runs are
815
+ // deterministic. Invalid flag = hard fail, not a silent default.
816
+ if (options && options.profile) {
817
+ if (!COMPLEXITY_PROFILES.includes(options.profile)) {
818
+ console.error(
819
+ pc.red(
820
+ `ERROR: unknown --profile '${options.profile}'. Valid: ${COMPLEXITY_PROFILES.join(', ')}`,
821
+ ),
822
+ );
823
+ process.exit(1);
824
+ }
825
+ return options.profile;
826
+ }
827
+
828
+ const fallback = existing || DEFAULT_COMPLEXITY_PROFILE;
829
+
830
+ if (yes) {
831
+ if (existing != null) {
832
+ console.log(pc.dim(`Preserving complexity_profile: ${existing}`));
833
+ } else {
834
+ console.log(
835
+ pc.dim(
836
+ `complexity_profile not set; defaulting to '${DEFAULT_COMPLEXITY_PROFILE}' (matches v1.0.5 behavior).`,
837
+ ),
838
+ );
839
+ }
840
+ return fallback;
841
+ }
842
+
843
+ if (dryRun) {
844
+ console.log(
845
+ pc.dim(`[DRY RUN] Would prompt for complexity_profile (current default: ${fallback})`),
846
+ );
847
+ return fallback;
848
+ }
849
+
850
+ const profile = await prompts.select({
851
+ message: 'Which complexity profile fits your project?',
852
+ options: [
853
+ {
854
+ value: 'nano',
855
+ label: 'nano — toy / tutorial / learning, solo, small codebase',
856
+ },
857
+ {
858
+ value: 'small',
859
+ label: 'small — MVP / internal tool / prototype, solo or 1–2 devs',
860
+ },
861
+ {
862
+ value: 'medium',
863
+ label: 'medium — team product with real users (recommended)',
864
+ },
865
+ {
866
+ value: 'large',
867
+ label: 'large — production, compliance / uptime stakes',
868
+ },
869
+ {
870
+ value: 'legacy',
871
+ label: 'legacy — pre-v2 behavior (rollback escape hatch)',
872
+ },
873
+ ],
874
+ initialValue: fallback,
875
+ });
876
+
877
+ return profile;
878
+ }
879
+
750
880
  async function resolveAutopilotSettings({ projectRoot, yes, dryRun, v1Snapshot }) {
751
881
  const existing = await readExistingAutopilotConfig(projectRoot, v1Snapshot);
752
882
  const defaultLimit = existing.sessionStoryLimit ?? DEFAULT_SESSION_STORY_LIMIT;
@@ -878,7 +1008,20 @@ async function runInstall(options = {}) {
878
1008
  console.log('');
879
1009
  }
880
1010
 
881
- // 2a. Autopilot configuration (prompt or preserve existing values).
1011
+ // 2a. Complexity profile (Adaptive Process Scaling v2.0.0).
1012
+ // Selected before the autopilot scalar prompts because a non-default
1013
+ // profile may influence those defaults in a future PR. For PR 1 the
1014
+ // profile is written to config.yaml alongside the existing keys; it
1015
+ // doesn't yet drive behavior — see docs/implementation-plan.md PR 4+.
1016
+ const complexityProfile = await resolveComplexityProfile({
1017
+ projectRoot,
1018
+ yes,
1019
+ dryRun,
1020
+ options,
1021
+ v1Snapshot: v1ConfigSnapshot,
1022
+ });
1023
+
1024
+ // 2b. Autopilot configuration (prompt or preserve existing values).
882
1025
  // These values are patched into modules/autopilot/config.yaml AFTER the
883
1026
  // runtime copy — they're NOT threaded through `renderString`, because
884
1027
  // workflow.md's `{{session_story_limit}}` / `{{retrospective_mode}}`
@@ -1114,6 +1257,11 @@ async function runInstall(options = {}) {
1114
1257
  // reapply (which might have restored an older config.yaml without
1115
1258
  // `retrospective_mode`). The user's prompted values always win.
1116
1259
  await patchAutopilotConfig(projectRoot, { sessionStoryLimit, retrospectiveMode });
1260
+
1261
+ // 6c. Persist the complexity_profile. Separate from patchAutopilotConfig
1262
+ // so the existing upgrade test coverage (readExistingAutopilotConfig /
1263
+ // patchAutopilotConfig) is unaffected by the new key.
1264
+ await patchComplexityProfile(projectRoot, complexityProfile);
1117
1265
  }
1118
1266
 
1119
1267
  // 7. Verify git check-ignore
@@ -1166,6 +1314,9 @@ async function runInstall(options = {}) {
1166
1314
  // independent of the chosen values' widths (e.g. 2-digit limit, 4-char mode).
1167
1315
  const apKey = (k) => k.padEnd(31, ' ');
1168
1316
  const apVal = (v) => String(v).padEnd(6, ' ');
1317
+ console.log(
1318
+ ` ${apKey('autopilot.complexity_profile')}${apVal(complexityProfile)} Profile: nano | small | medium | large | legacy (rollback)`,
1319
+ );
1169
1320
  console.log(
1170
1321
  ` ${apKey('autopilot.session_story_limit')}${apVal(sessionStoryLimit)} Stories to fully implement per run (0 = unlimited)`,
1171
1322
  );
@@ -1203,8 +1354,13 @@ module.exports = {
1203
1354
  readExistingAutopilotConfig,
1204
1355
  patchAutopilotConfig,
1205
1356
  applyScalar,
1357
+ readExistingComplexityProfile,
1358
+ patchComplexityProfile,
1359
+ resolveComplexityProfile,
1206
1360
  RETROSPECTIVE_MODES,
1207
1361
  DEFAULT_SESSION_STORY_LIMIT,
1208
1362
  DEFAULT_RETROSPECTIVE_MODE,
1363
+ COMPLEXITY_PROFILES,
1364
+ DEFAULT_COMPLEXITY_PROFILE,
1209
1365
  },
1210
1366
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "1.0.4",
3
+ "version": "2.0.4",
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": {