@ikunin/sprintpilot 1.0.5 → 2.0.5

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 (35) hide show
  1. package/README.md +48 -1
  2. package/_Sprintpilot/Sprintpilot.md +14 -1
  3. package/_Sprintpilot/manifest.yaml +1 -1
  4. package/_Sprintpilot/modules/autopilot/config.yaml +22 -0
  5. package/_Sprintpilot/modules/autopilot/profiles/_base.yaml +45 -0
  6. package/_Sprintpilot/modules/autopilot/profiles/large.yaml +22 -0
  7. package/_Sprintpilot/modules/autopilot/profiles/legacy.yaml +35 -0
  8. package/_Sprintpilot/modules/autopilot/profiles/medium.yaml +5 -0
  9. package/_Sprintpilot/modules/autopilot/profiles/nano.yaml +35 -0
  10. package/_Sprintpilot/modules/autopilot/profiles/small.yaml +5 -0
  11. package/_Sprintpilot/modules/git/config.yaml +8 -0
  12. package/_Sprintpilot/modules/ma/config.yaml +42 -0
  13. package/_Sprintpilot/scripts/agent-adapter.js +247 -0
  14. package/_Sprintpilot/scripts/cached-read.js +238 -0
  15. package/_Sprintpilot/scripts/check-prereqs.js +139 -0
  16. package/_Sprintpilot/scripts/dispatch-layer.js +192 -0
  17. package/_Sprintpilot/scripts/git-portable.js +219 -0
  18. package/_Sprintpilot/scripts/infer-dependencies.js +594 -0
  19. package/_Sprintpilot/scripts/inject-tasks-section.js +279 -0
  20. package/_Sprintpilot/scripts/list-remaining-stories.js +295 -0
  21. package/_Sprintpilot/scripts/log-timing.js +425 -0
  22. package/_Sprintpilot/scripts/mark-done-stories-tasks.js +254 -0
  23. package/_Sprintpilot/scripts/merge-shards.js +339 -0
  24. package/_Sprintpilot/scripts/preflight-merge.js +235 -0
  25. package/_Sprintpilot/scripts/resolve-dag.js +559 -0
  26. package/_Sprintpilot/scripts/resolve-profile.js +355 -0
  27. package/_Sprintpilot/scripts/state-shard.js +602 -0
  28. package/_Sprintpilot/scripts/submodule-lock.js +130 -0
  29. package/_Sprintpilot/scripts/summarize-timings.js +362 -0
  30. package/_Sprintpilot/scripts/sync-status.js +13 -0
  31. package/_Sprintpilot/scripts/with-retry.js +145 -0
  32. package/_Sprintpilot/skills/sprint-autopilot-on/workflow.md +572 -42
  33. package/bin/sprintpilot.js +4 -0
  34. package/lib/commands/install.js +157 -1
  35. package/package.json +1 -1
@@ -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.5",
3
+ "version": "2.0.5",
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": {