@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.
- package/README.md +48 -1
- package/_Sprintpilot/Sprintpilot.md +14 -1
- package/_Sprintpilot/manifest.yaml +1 -1
- package/_Sprintpilot/modules/autopilot/config.yaml +22 -0
- package/_Sprintpilot/modules/autopilot/profiles/_base.yaml +45 -0
- package/_Sprintpilot/modules/autopilot/profiles/large.yaml +22 -0
- package/_Sprintpilot/modules/autopilot/profiles/legacy.yaml +35 -0
- package/_Sprintpilot/modules/autopilot/profiles/medium.yaml +5 -0
- package/_Sprintpilot/modules/autopilot/profiles/nano.yaml +35 -0
- package/_Sprintpilot/modules/autopilot/profiles/small.yaml +5 -0
- package/_Sprintpilot/modules/git/config.yaml +8 -0
- package/_Sprintpilot/modules/ma/config.yaml +42 -0
- package/_Sprintpilot/scripts/agent-adapter.js +247 -0
- package/_Sprintpilot/scripts/cached-read.js +238 -0
- package/_Sprintpilot/scripts/check-prereqs.js +139 -0
- package/_Sprintpilot/scripts/dispatch-layer.js +192 -0
- package/_Sprintpilot/scripts/git-portable.js +219 -0
- package/_Sprintpilot/scripts/infer-dependencies.js +594 -0
- package/_Sprintpilot/scripts/inject-tasks-section.js +279 -0
- package/_Sprintpilot/scripts/list-remaining-stories.js +295 -0
- package/_Sprintpilot/scripts/log-timing.js +425 -0
- package/_Sprintpilot/scripts/mark-done-stories-tasks.js +254 -0
- package/_Sprintpilot/scripts/merge-shards.js +339 -0
- package/_Sprintpilot/scripts/preflight-merge.js +235 -0
- package/_Sprintpilot/scripts/resolve-dag.js +559 -0
- package/_Sprintpilot/scripts/resolve-profile.js +355 -0
- package/_Sprintpilot/scripts/state-shard.js +602 -0
- package/_Sprintpilot/scripts/submodule-lock.js +130 -0
- package/_Sprintpilot/scripts/summarize-timings.js +362 -0
- package/_Sprintpilot/scripts/sync-status.js +13 -0
- package/_Sprintpilot/scripts/with-retry.js +145 -0
- package/_Sprintpilot/skills/sprint-autopilot-on/workflow.md +572 -42
- package/bin/sprintpilot.js +4 -0
- package/lib/commands/install.js +157 -1
- package/package.json +1 -1
package/bin/sprintpilot.js
CHANGED
|
@@ -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(
|
package/lib/commands/install.js
CHANGED
|
@@ -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.
|
|
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