@ikunin/sprintpilot 2.2.30 → 2.3.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.
Files changed (34) hide show
  1. package/README.md +232 -413
  2. package/_Sprintpilot/Sprintpilot.md +76 -6
  3. package/_Sprintpilot/bin/autopilot.js +752 -66
  4. package/_Sprintpilot/lib/orchestrator/action-ledger.js +208 -0
  5. package/_Sprintpilot/lib/orchestrator/adapt.js +93 -15
  6. package/_Sprintpilot/lib/orchestrator/profile-rules.js +7 -16
  7. package/_Sprintpilot/lib/orchestrator/sprint-plan.js +488 -0
  8. package/_Sprintpilot/lib/orchestrator/state-store.js +9 -5
  9. package/_Sprintpilot/lib/orchestrator/user-command-applier.js +107 -0
  10. package/_Sprintpilot/lib/orchestrator/user-commands.js +124 -1
  11. package/_Sprintpilot/lib/orchestrator/verify.js +10 -17
  12. package/_Sprintpilot/manifest.yaml +4 -1
  13. package/_Sprintpilot/modules/autopilot/profiles/_base.yaml +18 -4
  14. package/_Sprintpilot/modules/git/config.yaml +15 -9
  15. package/_Sprintpilot/modules/ma/config.yaml +29 -27
  16. package/_Sprintpilot/scripts/dispatch-layer.js +12 -15
  17. package/_Sprintpilot/scripts/infer-dependencies.js +706 -254
  18. package/_Sprintpilot/scripts/log-timing.js +6 -10
  19. package/_Sprintpilot/scripts/merge-shards.js +21 -23
  20. package/_Sprintpilot/scripts/post-green-gates.js +3 -1
  21. package/_Sprintpilot/scripts/resolve-dag.js +452 -280
  22. package/_Sprintpilot/scripts/sprint-plan.js +1068 -0
  23. package/_Sprintpilot/scripts/state-shard.js +13 -5
  24. package/_Sprintpilot/scripts/summarize-timings.js +2 -3
  25. package/_Sprintpilot/skills/sprint-autopilot-on/SKILL.md +30 -2
  26. package/_Sprintpilot/skills/sprint-autopilot-on/workflow.orchestrator.md +36 -10
  27. package/_Sprintpilot/skills/sprintpilot-dependency-graph/SKILL.md +63 -0
  28. package/_Sprintpilot/skills/sprintpilot-dependency-graph/workflow.md +227 -0
  29. package/_Sprintpilot/skills/sprintpilot-plan-sprint/SKILL.md +67 -0
  30. package/_Sprintpilot/skills/sprintpilot-plan-sprint/workflow.md +435 -0
  31. package/_Sprintpilot/skills/sprintpilot-sprint-progress/SKILL.md +53 -0
  32. package/_Sprintpilot/skills/sprintpilot-sprint-progress/workflow.md +169 -0
  33. package/lib/commands/install.js +186 -10
  34. package/package.json +1 -1
@@ -748,7 +748,15 @@ async function evictV1Installation(projectRoot, { dryRun, migrateV1, yes }) {
748
748
  // Regex-based so we don't add a YAML parser dep for two scalar fields.
749
749
  // Unrecognized / unreadable files fall back to bundled defaults.
750
750
  async function readExistingAutopilotConfig(projectRoot, v1Snapshot) {
751
- const out = { sessionStoryLimit: null, retrospectiveMode: null };
751
+ const out = {
752
+ sessionStoryLimit: null,
753
+ retrospectiveMode: null,
754
+ // v2.3.0 additions. null means "not set in user config" → use the bundled
755
+ // default. autoInferDependencies is read only to surface a deprecation
756
+ // notice on upgrade — we never write it back.
757
+ autoPlanOnStart: null,
758
+ autoInferDependencies: null,
759
+ };
752
760
  let raw = null;
753
761
 
754
762
  // Precedence order:
@@ -809,6 +817,22 @@ async function readExistingAutopilotConfig(projectRoot, v1Snapshot) {
809
817
  if (modeMatch && RETROSPECTIVE_MODES.includes(modeMatch[1])) {
810
818
  out.retrospectiveMode = modeMatch[1];
811
819
  }
820
+ // v2.3.0 — `auto_plan_on_start: true|false`. Bool; bundled default is false.
821
+ const planMatch = raw.match(
822
+ new RegExp(`^[ \\t]*auto_plan_on_start:[ \\t]*(true|false)${commentTail}`, 'm'),
823
+ );
824
+ if (planMatch) {
825
+ out.autoPlanOnStart = planMatch[1] === 'true';
826
+ }
827
+ // Legacy `auto_infer_dependencies: true|false` — read so the installer can
828
+ // surface a deprecation notice when the user is upgrading from v2.2.x with
829
+ // the flag set to true (it's now a no-op). Never written back.
830
+ const inferMatch = raw.match(
831
+ new RegExp(`^[ \\t]*auto_infer_dependencies:[ \\t]*(true|false)${commentTail}`, 'm'),
832
+ );
833
+ if (inferMatch) {
834
+ out.autoInferDependencies = inferMatch[1] === 'true';
835
+ }
812
836
  return out;
813
837
  }
814
838
 
@@ -859,7 +883,10 @@ function applyScalar(source, key, value) {
859
883
  return `${trimmed} ${key}: ${value}\n`;
860
884
  }
861
885
 
862
- async function patchAutopilotConfig(projectRoot, { sessionStoryLimit, retrospectiveMode }) {
886
+ async function patchAutopilotConfig(
887
+ projectRoot,
888
+ { sessionStoryLimit, retrospectiveMode, autoPlanOnStart },
889
+ ) {
863
890
  const file = path.join(
864
891
  projectRoot,
865
892
  PROJECT_ADDON_DIR_NAME,
@@ -871,6 +898,12 @@ async function patchAutopilotConfig(projectRoot, { sessionStoryLimit, retrospect
871
898
  const original = await fs.readFile(file, 'utf8');
872
899
  let updated = applyScalar(original, 'session_story_limit', sessionStoryLimit);
873
900
  updated = applyScalar(updated, 'retrospective_mode', retrospectiveMode);
901
+ // v2.3.0 — auto_plan_on_start is a boolean. applyScalar handles literal
902
+ // values (true/false) the same way as numbers; we just need to pass the
903
+ // unquoted lowercase string for booleans.
904
+ if (autoPlanOnStart !== undefined && autoPlanOnStart !== null) {
905
+ updated = applyScalar(updated, 'auto_plan_on_start', autoPlanOnStart ? 'true' : 'false');
906
+ }
874
907
  if (updated !== original) {
875
908
  await writeAtomic(file, updated);
876
909
  }
@@ -915,6 +948,46 @@ async function readExistingComplexityProfile(projectRoot, v1Snapshot) {
915
948
  return m[1];
916
949
  }
917
950
 
951
+ // v2.3.0 — post-install hygiene check. Cross-reference the project's
952
+ // _Sprintpilot/manifest.yaml `installed_skills` against what actually
953
+ // landed under `_Sprintpilot/skills/<name>/SKILL.md`. Catches the
954
+ // classic "added skill to manifest but forgot to ship the files" bug
955
+ // at install time rather than at first invocation.
956
+ //
957
+ // Returns { missing: string[] } — empty array means everything is
958
+ // wired correctly. The caller chooses how to surface mismatches
959
+ // (warning vs fail). We never fail the install on a mismatch; it's
960
+ // hygiene, not correctness — the skill just won't appear under the
961
+ // host tool's /-command if it's missing on disk.
962
+ async function verifySkillManifest(projectRoot) {
963
+ const manifestPath = path.join(projectRoot, PROJECT_ADDON_DIR_NAME, 'manifest.yaml');
964
+ if (!(await fs.pathExists(manifestPath))) {
965
+ return { missing: [] };
966
+ }
967
+ let raw;
968
+ try {
969
+ raw = await fs.readFile(manifestPath, 'utf8');
970
+ } catch {
971
+ return { missing: [] };
972
+ }
973
+ // Parse the YAML list under `installed_skills:` via regex — bullet
974
+ // lines starting with `-` at consistent indent. Cheap; no YAML dep.
975
+ const skillNames = [];
976
+ const installedMatch = raw.match(/^[ \t]*installed_skills:\s*\n((?:[ \t]+- [^\n]+\n?)+)/m);
977
+ if (installedMatch) {
978
+ for (const line of installedMatch[1].split(/\n/)) {
979
+ const m = line.match(/^[ \t]+-\s+([A-Za-z0-9._-]+)/);
980
+ if (m) skillNames.push(m[1]);
981
+ }
982
+ }
983
+ const missing = [];
984
+ for (const name of skillNames) {
985
+ const skillFile = path.join(projectRoot, PROJECT_ADDON_DIR_NAME, 'skills', name, 'SKILL.md');
986
+ if (!(await fs.pathExists(skillFile))) missing.push(name);
987
+ }
988
+ return { missing };
989
+ }
990
+
918
991
  async function patchComplexityProfile(projectRoot, profile) {
919
992
  const file = path.join(
920
993
  projectRoot,
@@ -1008,25 +1081,39 @@ async function resolveAutopilotSettings({ projectRoot, yes, dryRun, v1Snapshot }
1008
1081
  const existing = await readExistingAutopilotConfig(projectRoot, v1Snapshot);
1009
1082
  const defaultLimit = existing.sessionStoryLimit ?? DEFAULT_SESSION_STORY_LIMIT;
1010
1083
  const defaultMode = existing.retrospectiveMode ?? DEFAULT_RETROSPECTIVE_MODE;
1084
+ // v2.3.0 — opt-in default false; preserve existing user choice on upgrade.
1085
+ const defaultAutoPlan = existing.autoPlanOnStart ?? false;
1011
1086
 
1012
1087
  if (yes) {
1013
- if (existing.sessionStoryLimit != null || existing.retrospectiveMode != null) {
1088
+ if (
1089
+ existing.sessionStoryLimit != null ||
1090
+ existing.retrospectiveMode != null ||
1091
+ existing.autoPlanOnStart != null
1092
+ ) {
1014
1093
  console.log(
1015
1094
  pc.dim(
1016
- `Preserving autopilot config: session_story_limit=${defaultLimit}, retrospective_mode=${defaultMode}`,
1095
+ `Preserving autopilot config: session_story_limit=${defaultLimit}, retrospective_mode=${defaultMode}, auto_plan_on_start=${defaultAutoPlan}`,
1017
1096
  ),
1018
1097
  );
1019
1098
  }
1020
- return { sessionStoryLimit: defaultLimit, retrospectiveMode: defaultMode };
1099
+ return {
1100
+ sessionStoryLimit: defaultLimit,
1101
+ retrospectiveMode: defaultMode,
1102
+ autoPlanOnStart: defaultAutoPlan,
1103
+ };
1021
1104
  }
1022
1105
 
1023
1106
  if (dryRun) {
1024
1107
  console.log(
1025
1108
  pc.dim(
1026
- `[DRY RUN] Would prompt for autopilot config (current: session_story_limit=${defaultLimit}, retrospective_mode=${defaultMode})`,
1109
+ `[DRY RUN] Would prompt for autopilot config (current: session_story_limit=${defaultLimit}, retrospective_mode=${defaultMode}, auto_plan_on_start=${defaultAutoPlan})`,
1027
1110
  ),
1028
1111
  );
1029
- return { sessionStoryLimit: defaultLimit, retrospectiveMode: defaultMode };
1112
+ return {
1113
+ sessionStoryLimit: defaultLimit,
1114
+ retrospectiveMode: defaultMode,
1115
+ autoPlanOnStart: defaultAutoPlan,
1116
+ };
1030
1117
  }
1031
1118
 
1032
1119
  const limitRaw = await prompts.text({
@@ -1065,7 +1152,17 @@ async function resolveAutopilotSettings({ projectRoot, yes, dryRun, v1Snapshot }
1065
1152
  initialValue: defaultMode,
1066
1153
  });
1067
1154
 
1068
- return { sessionStoryLimit, retrospectiveMode };
1155
+ // v2.3.0 single yes/no prompt for the new plan workflow. Default false:
1156
+ // `autopilot start` runs in sprint-status order until the user explicitly
1157
+ // invokes /sprintpilot-plan-sprint, which is always available regardless.
1158
+ // Set this true to auto-trigger the planning skill on greenfield projects.
1159
+ const autoPlanOnStart = await prompts.confirm({
1160
+ message:
1161
+ 'Auto-build a sprint plan on first `autopilot start`? (v2.3.0; runs /sprintpilot-plan-sprint to infer dependencies. You can always invoke the skill manually regardless of this setting.)',
1162
+ initialValue: defaultAutoPlan,
1163
+ });
1164
+
1165
+ return { sessionStoryLimit, retrospectiveMode, autoPlanOnStart };
1069
1166
  }
1070
1167
 
1071
1168
  async function runInteractiveToolPicker(detected) {
@@ -1194,7 +1291,7 @@ async function runInstall(options = {}) {
1194
1291
  // runtime copy — they're NOT threaded through `renderString`, because
1195
1292
  // workflow.md's `{{session_story_limit}}` / `{{retrospective_mode}}`
1196
1293
  // variable references would collide with single-brace token matching.
1197
- const { sessionStoryLimit, retrospectiveMode } = await resolveAutopilotSettings({
1294
+ const { sessionStoryLimit, retrospectiveMode, autoPlanOnStart } = await resolveAutopilotSettings({
1198
1295
  projectRoot,
1199
1296
  yes,
1200
1297
  dryRun,
@@ -1439,7 +1536,11 @@ async function runInstall(options = {}) {
1439
1536
  // wrote the bundled default config) AND after the v1 snapshot
1440
1537
  // reapply (which might have restored an older config.yaml without
1441
1538
  // `retrospective_mode`). The user's prompted values always win.
1442
- await patchAutopilotConfig(projectRoot, { sessionStoryLimit, retrospectiveMode });
1539
+ await patchAutopilotConfig(projectRoot, {
1540
+ sessionStoryLimit,
1541
+ retrospectiveMode,
1542
+ autoPlanOnStart,
1543
+ });
1443
1544
 
1444
1545
  // 6c. Persist the complexity_profile. Separate from patchAutopilotConfig
1445
1546
  // so the existing upgrade test coverage (readExistingAutopilotConfig /
@@ -1501,6 +1602,56 @@ async function runInstall(options = {}) {
1501
1602
  console.log(
1502
1603
  `Total skills installed: ${totalInstalled} (${skillCount} skills x ${selectedTools.length} tools)`,
1503
1604
  );
1605
+
1606
+ // v2.3.0 — post-install hygiene: warn if any skill in manifest.yaml
1607
+ // doesn't have a SKILL.md on disk. Non-blocking; surfaces packaging
1608
+ // bugs without failing the install.
1609
+ try {
1610
+ const verify = await verifySkillManifest(projectRoot);
1611
+ if (verify.missing.length > 0) {
1612
+ console.log('');
1613
+ console.log(
1614
+ pc.yellow(` WARN: manifest references skills missing from disk: ${verify.missing.join(', ')}`),
1615
+ );
1616
+ console.log(pc.yellow(' These won\'t appear under your host tool\'s / menu until the SKILL.md files are present.'));
1617
+ }
1618
+ } catch {
1619
+ // Self-check failure is non-fatal — never block install on hygiene.
1620
+ }
1621
+
1622
+ // v2.3.0 upgrade notes — surfaced only when the relevant signals are
1623
+ // actually present. Greenfield installs see nothing; upgraders from
1624
+ // v2.2.x see migration + deprecation notices.
1625
+ const v23Notes = [];
1626
+ const legacyDepsPath = path.join(projectRoot, '_Sprintpilot', 'sprints', 'dependencies.yaml');
1627
+ if (await fs.pathExists(legacyDepsPath)) {
1628
+ v23Notes.push(
1629
+ 'Legacy file detected: _Sprintpilot/sprints/dependencies.yaml',
1630
+ ' Auto-migrated to sprint-plan.yaml on the first `autopilot start`.',
1631
+ ' Run now: node _Sprintpilot/scripts/infer-dependencies.js migrate',
1632
+ );
1633
+ }
1634
+ // Re-read so we can show the deprecation notice without threading state
1635
+ // through every helper. Cheap (one regex scan); only happens once per install.
1636
+ try {
1637
+ const existingForNotes = await readExistingAutopilotConfig(projectRoot, v1ConfigSnapshot);
1638
+ if (existingForNotes.autoInferDependencies === true) {
1639
+ if (v23Notes.length > 0) v23Notes.push('');
1640
+ v23Notes.push(
1641
+ 'Deprecated: autopilot.auto_infer_dependencies = true in your config.',
1642
+ ' This flag is a no-op in v2.3.0 — superseded by auto_plan_on_start (default false).',
1643
+ ' Safe to remove from config.yaml; the new /sprintpilot-plan-sprint workflow',
1644
+ ' handles inference manually or on opt-in auto-trigger.',
1645
+ );
1646
+ }
1647
+ } catch {
1648
+ // Config re-read failure is non-fatal — skip the deprecation notice.
1649
+ }
1650
+ if (v23Notes.length > 0) {
1651
+ console.log('');
1652
+ console.log(pc.cyan('v2.3.0 upgrade notes:'));
1653
+ for (const line of v23Notes) console.log(' ' + line);
1654
+ }
1504
1655
  console.log('');
1505
1656
  console.log('Skills:');
1506
1657
  for (const skill of allSkills) console.log(` - ${skill}`);
@@ -1513,6 +1664,13 @@ async function runInstall(options = {}) {
1513
1664
  console.log(' /sprint-autopilot-off Disengage and show status');
1514
1665
  console.log(' /bmad-help Orientation and next-step guidance (from BMad Method)');
1515
1666
  console.log('');
1667
+ console.log('First steps for a new sprint:');
1668
+ console.log(' 1. BMad sprint planning: /bmad-sprint-planning');
1669
+ console.log(' 2. (optional) Sprint plan: /sprintpilot-plan-sprint');
1670
+ console.log(' 3. (optional) Inspect DAG: /sprintpilot-dependency-graph mermaid');
1671
+ console.log(' 4. Start autopilot: /sprint-autopilot-on');
1672
+ console.log(' 5. Check live progress: /sprintpilot-sprint-progress');
1673
+ console.log('');
1516
1674
  console.log('Configuration (edit these files to customize behavior):');
1517
1675
  console.log('');
1518
1676
  console.log(' _Sprintpilot/modules/git/config.yaml');
@@ -1542,6 +1700,18 @@ async function runInstall(options = {}) {
1542
1700
  console.log(
1543
1701
  ` ${apKey('autopilot.retrospective_mode')}${apVal(retrospectiveMode)} Epic-end retrospective: auto (inline) | stop (pause) | skip (not recommended)`,
1544
1702
  );
1703
+ console.log(
1704
+ ` ${apKey('autopilot.auto_plan_on_start')}${apVal(String(autoPlanOnStart))} Auto-build sprint plan on first start (v2.3.0; default off)`,
1705
+ );
1706
+ console.log('');
1707
+ console.log('Sprint planning + progress (v2.3.0):');
1708
+ console.log(' /sprintpilot-plan-sprint Build dependency-aware sprint plan');
1709
+ console.log(' /sprintpilot-sprint-progress Concise health-check of autopilot execution');
1710
+ console.log(' /sprintpilot-dependency-graph Render DAG (mermaid / graphviz / text / layers / json)');
1711
+ console.log('');
1712
+ console.log('CLI utilities:');
1713
+ console.log(' autopilot progress Live status (--json / --story <key>)');
1714
+ console.log(' autopilot start --no-auto-plan Skip auto-planning for one session');
1545
1715
  console.log('');
1546
1716
  console.log('Multi-agent skills — run parallel subagents for faster analysis:');
1547
1717
  console.log(' /sprintpilot-code-review Parallel 3-layer adversarial review');
@@ -1551,6 +1721,11 @@ async function runInstall(options = {}) {
1551
1721
  console.log(' /sprintpilot-migrate Legacy migration planning');
1552
1722
  console.log(' /sprintpilot-research Parallel web research');
1553
1723
  console.log(' /sprintpilot-party-mode Multi-persona agent discussions');
1724
+ console.log('');
1725
+ console.log('Documentation:');
1726
+ console.log(' Sprint planning walkthrough: docs/USAGE.md');
1727
+ console.log(' Configuration reference: docs/CONFIGURATION.md');
1728
+ console.log(' Architecture deep-dive: docs/ARCHITECTURE.md');
1554
1729
 
1555
1730
  const latestVersion = await latestVersionPromise;
1556
1731
  if (latestVersion && addonVersion && compareVersions(addonVersion, latestVersion) === 'behind') {
@@ -1586,5 +1761,6 @@ module.exports = {
1586
1761
  KEY_RENAMES,
1587
1762
  snapshotUserOwnedFiles,
1588
1763
  applyUserOwnedFiles,
1764
+ verifySkillManifest,
1589
1765
  },
1590
1766
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "2.2.30",
3
+ "version": "2.3.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": {