@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/README.md
CHANGED
|
@@ -197,6 +197,32 @@ Output files:
|
|
|
197
197
|
|
|
198
198
|
---
|
|
199
199
|
|
|
200
|
+
## Adaptive Process Scaling (v2)
|
|
201
|
+
|
|
202
|
+
Sprintpilot v2 introduced **complexity profiles** as a first-class config dimension. The right amount of process for a 2-story bug-fix sprint is different from a 30-story green-field rebuild — and the cost of running the heavy flow on a small change is real (more LLM turns, more context rot, more time). One knob picks the right balance:
|
|
203
|
+
|
|
204
|
+
| Profile | Per-story flow | Branching | Worktrees | Parallel stories | Use it for |
|
|
205
|
+
|---------|---------------|-----------|-----------|------------------|-----------|
|
|
206
|
+
| `nano` | `bmad-quick-dev` (one-shot) | `epic` (one PR per epic) | off | n/a | Tiny patch sprints, hot-fix runs |
|
|
207
|
+
| `small` | Full 7-step BMad cycle | `story` (one PR per story) | on | off | Single-developer projects, ≤10 stories |
|
|
208
|
+
| `medium` *(default)* | Full 7-step BMad cycle | `story` | on | off | Default — balanced for most sprints |
|
|
209
|
+
| `large` | Full 7-step BMad cycle | `story` | on | **on** (Claude Code) | Multi-epic sprints, 20+ stories |
|
|
210
|
+
| `legacy` | Pinned to v1.0.5 behavior byte-for-byte | `story` | on | off | Existing installs that want zero behavior change |
|
|
211
|
+
|
|
212
|
+
Pick the profile at install time — interactive installer asks, non-interactive flag is `--profile <nano|small|medium|large|legacy>`. Missing profile defaults to `medium` with no behavior change vs. v1.0.5.
|
|
213
|
+
|
|
214
|
+
**One knob per feature** — every v2 optimization layer can be disabled in isolation without uninstalling. See [Configuration Reference](docs/CONFIGURATION.md#autopilot-configuration-modulesautopilotconfigyaml).
|
|
215
|
+
|
|
216
|
+
### What v2 ships on top of the core flow
|
|
217
|
+
|
|
218
|
+
- **Phase timing instrumentation** — `mark` action emits `duration` records per skill phase; auto-emitted on critical paths (no LLM bracket calls to skip). `summarize-timings.js` reports hotspots > 5% of total time.
|
|
219
|
+
- **State sharding** — non-critical writes accumulate in `.pending/` shards, flushed atomically at story boundaries / session checkpoints / sprint complete. Crash-recovery keys still write straight through.
|
|
220
|
+
- **Conditional boot work** — clean-repo sessions skip the slow health-check / branch-reconciliation block (saves 8–30s per session).
|
|
221
|
+
- **Cached reads** — TTL + source-mtime aware file cache; any writer's mtime advance forces a miss without explicit invalidate.
|
|
222
|
+
- **Auto-inferred story DAG** — autopilot infers inter-story dependencies once after `bmad-sprint-planning` and writes `_Sprintpilot/sprints/dependencies.yaml` with an `# AUTO-INFERRED` marker. Hand-authored files are detected and respected silently.
|
|
223
|
+
- **Parallel story dispatch** — when `parallel_stories: true` and the host supports it, layer-aware dispatch runs N stories concurrently in their own worktrees, then merges their state shards. Claude Code today; Gemini CLI experimentally.
|
|
224
|
+
- **Cross-platform** — every workflow.md call site runs under bash, zsh, Git Bash, PowerShell, and cmd. Portable Node.js helpers replace POSIX-shell idioms.
|
|
225
|
+
|
|
200
226
|
## Quick Start
|
|
201
227
|
|
|
202
228
|
```bash
|
|
@@ -215,9 +241,12 @@ npx bmad-method install
|
|
|
215
241
|
```
|
|
216
242
|
|
|
217
243
|
```bash
|
|
218
|
-
# 2. Install Sprintpilot (interactive — select your tool when prompted)
|
|
244
|
+
# 2. Install Sprintpilot (interactive — select your tool and complexity profile when prompted)
|
|
219
245
|
npx @ikunin/sprintpilot@latest
|
|
220
246
|
|
|
247
|
+
# 2b. Or pick the profile non-interactively
|
|
248
|
+
npx @ikunin/sprintpilot@latest install --tools claude-code --profile medium --yes
|
|
249
|
+
|
|
221
250
|
# 3. Start the autopilot in your IDE
|
|
222
251
|
/sprint-autopilot-on
|
|
223
252
|
```
|
|
@@ -298,6 +327,19 @@ All settings live in two YAML files — edit after install to customize behavior
|
|
|
298
327
|
| `git.lock.stale_timeout_minutes` | `30` | Auto-remove orphaned lock files |
|
|
299
328
|
| `git.worktree.cleanup_on_merge` | `true` | Delete worktrees after merge |
|
|
300
329
|
|
|
330
|
+
### Autopilot (`_Sprintpilot/modules/autopilot/config.yaml`)
|
|
331
|
+
|
|
332
|
+
| Setting | Default (medium) | Description |
|
|
333
|
+
|---------|------------------|-------------|
|
|
334
|
+
| `complexity_profile` | `medium` | One of `nano`, `small`, `medium`, `large`, `legacy`. Selects the per-story flow + which v2 layers are enabled. |
|
|
335
|
+
| `autopilot.session_story_limit` | `3` (nano: `5`) | Stories per session before checkpoint. `0` = unlimited. |
|
|
336
|
+
| `autopilot.retrospective_mode` | `auto` | `auto` (deterministic artifact) / `stop` (pause for `/bmad-retrospective`) / `skip`. |
|
|
337
|
+
| `autopilot.auto_infer_dependencies` | `true` (nano + legacy: `false`) | Infer story DAG once after `bmad-sprint-planning`. Hand-authored sidecars (no `# AUTO-INFERRED` marker) are respected silently. |
|
|
338
|
+
| `autopilot.phase_timings` | `true` (legacy: `false`) | Emit phase duration records via `log-timing.js mark`. |
|
|
339
|
+
| `autopilot.coalesce_state_writes` | `true` (legacy: `false`) | Buffer non-critical state in `.pending/` shards. |
|
|
340
|
+
| `autopilot.conditional_boot_work` | `true` (large + legacy: `false`) | Skip health-check / branch-reconciliation on clean repos. |
|
|
341
|
+
| `autopilot.cache_shared_reads` | `true` (legacy: `false`) | TTL + mtime-aware file cache for hot reads. |
|
|
342
|
+
|
|
301
343
|
### Multi-Agent (`_Sprintpilot/modules/ma/config.yaml`)
|
|
302
344
|
|
|
303
345
|
| Setting | Default | Description |
|
|
@@ -305,6 +347,11 @@ All settings live in two YAML files — edit after install to customize behavior
|
|
|
305
347
|
| `multi_agent.enabled` | `true` | Enable parallel agent skills |
|
|
306
348
|
| `multi_agent.max_parallel_research` | `3` | Concurrent research agents per batch |
|
|
307
349
|
| `multi_agent.max_parallel_analysis` | `5` | Concurrent codebase analysis agents |
|
|
350
|
+
| `ma.state_sharding` | `auto` (large: `always`) | `auto`, `always`, `never` — shards per-story state instead of contending on root YAMLs. |
|
|
351
|
+
| `ma.parallel_stories` | `false` (large: `true`) | Dispatch independent stories from a DAG layer concurrently. Requires Claude Code (or Gemini CLI w/ experimental flag). |
|
|
352
|
+
| `ma.max_parallel_stories` | `2` (large: `3`) | Cap on concurrent stories per layer. |
|
|
353
|
+
| `ma.experimental_parallel_on_gemini` | `false` | Opt-in parallel dispatch under Gemini CLI (worktree-scoped subagents are still upstream). |
|
|
354
|
+
| `ma.parallel_epics` | `false` | EXPERIMENTAL — cross-epic parallelism with merge-conflict preflight. Off on every profile by default. |
|
|
308
355
|
|
|
309
356
|
See the [Configuration Reference](docs/CONFIGURATION.md) for the full list.
|
|
310
357
|
|
|
@@ -31,8 +31,9 @@ Edit `_Sprintpilot/modules/autopilot/config.yaml`:
|
|
|
31
31
|
|
|
32
32
|
| Setting | Default | Values | Purpose |
|
|
33
33
|
|---------|---------|--------|---------|
|
|
34
|
-
| `autopilot.session_story_limit` | `3` | integer ≥ 0 | Stories fully implemented per autopilot run before checkpoint. `0` = unlimited. |
|
|
34
|
+
| `autopilot.session_story_limit` | `3` (nano: `5`) | integer ≥ 0 | Stories fully implemented per autopilot run before checkpoint. `0` = unlimited. Retuned in 2.0.1 after context-rot exposure on longer sessions; nano is cheaper per story so fits a higher cap. |
|
|
35
35
|
| `autopilot.retrospective_mode` | `auto` | `auto` / `stop` / `skip` | How epic-end retrospectives are handled (see below). |
|
|
36
|
+
| `autopilot.auto_infer_dependencies` | `true` (nano + legacy: `false`) | bool | 2.0.2 — autopilot session infers inter-story DAG once after `bmad-sprint-planning` and writes `_Sprintpilot/sprints/dependencies.yaml`. Hand-authored sidecars (no `# AUTO-INFERRED` marker) are detected and respected. See "Dependency Inference" below. |
|
|
36
37
|
|
|
37
38
|
`retrospective_mode` options:
|
|
38
39
|
- **`auto`** *(default)* — autopilot writes a deterministic retrospective artifact from `sprint-status.yaml` + `decision-log.yaml`, then continues. Single pass, no external skill call, safe under every CLI.
|
|
@@ -41,6 +42,18 @@ Edit `_Sprintpilot/modules/autopilot/config.yaml`:
|
|
|
41
42
|
|
|
42
43
|
Both settings are prompted during `sprintpilot install` (interactive mode) with existing values as defaults, so reinstalls preserve your choices.
|
|
43
44
|
|
|
45
|
+
### Dependency Inference
|
|
46
|
+
|
|
47
|
+
After `bmad-sprint-planning` completes, the autopilot session reads `epics.md`, `architecture.md`, and `sprint-status.yaml` and emits a JSON dependency envelope. `_Sprintpilot/scripts/infer-dependencies.js` validates it (schema, unknown keys, self-deps, cross-epic edges, missing rationales, cycles) and writes `_Sprintpilot/sprints/dependencies.yaml` with an `# AUTO-INFERRED` marker header. The script never calls an LLM — the autopilot session is the inference caller.
|
|
48
|
+
|
|
49
|
+
This unblocks parallel story dispatch (`parallel_stories: true` + `dispatch-layer.js`) without requiring users to discover and hand-author the sidecar. Hand-authored files (no marker) are respected silently. Failure modes (invalid JSON, validation errors) log and continue — `resolve-dag.js` falls back to its safe linear `ordering` strategy on dispatch.
|
|
50
|
+
|
|
51
|
+
### Mandatory fresh-context finalize
|
|
52
|
+
|
|
53
|
+
Independent of `session_story_limit`, the autopilot forces an extra session at end-of-sprint. When step 2 detects all stories are done, it writes `current_bmad_step = sprint-finalize-pending` to the state file and halts — it does **not** run step 10 (cleanup) in that session. The next `/sprint-autopilot-on` invocation reads the marker in step 1 and jumps directly to step 10 with a clean context window, where seven CRITICAL deterministic script calls run the cleanup (checkbox marking, worktree removal, lock release, artifact commit, sprint-complete state, verification, state-file delete).
|
|
54
|
+
|
|
55
|
+
This behavior is not configurable: it's a mitigation for late-session instruction decay that was reliably dropping cleanup actions in long single-session runs. The extra session is short (typically ~60-100 turns, under $2). See `_Sprintpilot/skills/sprint-autopilot-on/workflow.md` step 2 and step 10 for the exact protocol.
|
|
56
|
+
|
|
44
57
|
---
|
|
45
58
|
|
|
46
59
|
## Full skill reference by lifecycle phase
|
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
# Controls /sprint-autopilot-on session behavior.
|
|
3
3
|
|
|
4
4
|
autopilot:
|
|
5
|
+
# Adaptive Process Scaling profile.
|
|
6
|
+
#
|
|
7
|
+
# nano — toy / tutorial / learning project, solo, small codebase.
|
|
8
|
+
# Routes through bmad-quick-dev one-shot (PR 4+). Fastest.
|
|
9
|
+
# small — MVP / internal tool / prototype, solo or 1–2 devs.
|
|
10
|
+
# medium — team product with real users. Default, recommended.
|
|
11
|
+
# large — production system, compliance / uptime stakes. Full
|
|
12
|
+
# reconciliation, interactive retrospectives, tighter
|
|
13
|
+
# checkpoints, parallelism on by default.
|
|
14
|
+
# legacy — rollback to v1.0.5 behavior. No v2 optimizations active.
|
|
15
|
+
# Pinned by version_pinned in profiles/legacy.yaml so future
|
|
16
|
+
# changes cannot silently affect legacy users.
|
|
17
|
+
#
|
|
18
|
+
# Profile defaults live in _Sprintpilot/modules/autopilot/profiles/*.yaml.
|
|
19
|
+
# Override specific knobs here or in git/ma config.yaml — user overrides
|
|
20
|
+
# always win over profile defaults. See docs/adaptive-process-scaling.md.
|
|
21
|
+
#
|
|
22
|
+
# Missing key defaults to 'medium' (matches v1.0.5 behavior byte-for-byte).
|
|
23
|
+
# Upgrading v1 installs don't need to set this; new installs get it from
|
|
24
|
+
# the installer prompt or `sprintpilot install --profile <name>`.
|
|
25
|
+
complexity_profile: medium
|
|
26
|
+
|
|
5
27
|
# Maximum stories to FULLY IMPLEMENT in a single autopilot session.
|
|
6
28
|
#
|
|
7
29
|
# "Fully implement" means the complete per-story cycle:
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Adaptive Process Scaling — base defaults for all non-legacy profiles.
|
|
2
|
+
# Per-profile YAML files override only the keys they want to change.
|
|
3
|
+
# See docs/adaptive-process-scaling.md and docs/implementation-plan.md.
|
|
4
|
+
|
|
5
|
+
name: _base
|
|
6
|
+
version_pinned: null
|
|
7
|
+
|
|
8
|
+
autopilot:
|
|
9
|
+
implementation_flow: full # full | quick
|
|
10
|
+
# Proactive handoff threshold. Lowered from 5 → 3 because context rot
|
|
11
|
+
# (late-session instruction decay) caused the LLM to skip late-step
|
|
12
|
+
# actions in longer single-session runs. Keeping sessions short enough
|
|
13
|
+
# that every step 10 CRITICAL action fits comfortably inside the prompt
|
|
14
|
+
# budget is cheaper than paying for skipped cleanup actions.
|
|
15
|
+
session_story_limit: 3
|
|
16
|
+
retrospective_mode: auto
|
|
17
|
+
phase_timings: true # M0, PR 2 — enabled by default (legacy overrides to false)
|
|
18
|
+
coalesce_state_writes: true # M3, PR 6 — batch non-critical writes, flush at story boundary
|
|
19
|
+
conditional_boot_work: true # M4, PR 7 — skip health-check + branch reconciliation on a clean repo
|
|
20
|
+
cache_shared_reads: true # M5, PR 8 — memoize sprint-status / git-status / decision-log reads per loop iteration
|
|
21
|
+
# 2.0.2 — autopilot session infers inter-story DAG once after bmad-sprint-planning
|
|
22
|
+
# and writes _Sprintpilot/sprints/dependencies.yaml. Hand-authored sidecars
|
|
23
|
+
# (no AUTO-INFERRED marker) are detected and respected.
|
|
24
|
+
auto_infer_dependencies: true
|
|
25
|
+
|
|
26
|
+
git:
|
|
27
|
+
granularity: story # story | epic
|
|
28
|
+
worktree:
|
|
29
|
+
enabled: true
|
|
30
|
+
squash_on_merge: false
|
|
31
|
+
|
|
32
|
+
ma:
|
|
33
|
+
parallel_stories: false
|
|
34
|
+
max_parallel_stories: 2
|
|
35
|
+
# PR 11 intra-epic parallelism safety rails. Honored by dispatch-layer.js
|
|
36
|
+
# when a host declares parallel support (see agent-adapter.js).
|
|
37
|
+
min_epic_duration_for_parallel_sec: 300
|
|
38
|
+
baseline_story_duration_sec: 180
|
|
39
|
+
max_consecutive_conflicts: 2
|
|
40
|
+
effective_parallel_floor: 1
|
|
41
|
+
parallel_epics: false
|
|
42
|
+
# PR 3: state_sharding semantics — auto = shard when parallelism is active,
|
|
43
|
+
# always = shard unconditionally (for forcing the code path in tests),
|
|
44
|
+
# never = write authoritative files directly (pre-PR behavior, rollback).
|
|
45
|
+
state_sharding: auto
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Large profile — production system, compliance / uptime stakes.
|
|
2
|
+
# Parallelism on by default; full reconciliation every boot; tighter
|
|
3
|
+
# session checkpointing; interactive retrospectives.
|
|
4
|
+
|
|
5
|
+
name: large
|
|
6
|
+
|
|
7
|
+
autopilot:
|
|
8
|
+
session_story_limit: 3
|
|
9
|
+
retrospective_mode: stop
|
|
10
|
+
conditional_boot_work: false # always run full reconciliation
|
|
11
|
+
|
|
12
|
+
ma:
|
|
13
|
+
parallel_stories: true
|
|
14
|
+
max_parallel_stories: 3
|
|
15
|
+
# PR 11 safety rails (inherited defaults via _base, overridden here).
|
|
16
|
+
max_consecutive_conflicts: 2
|
|
17
|
+
effective_parallel_floor: 1
|
|
18
|
+
# PR 12 cross-epic parallelism stays OFF even on large — experimental.
|
|
19
|
+
# Explicitly pinned (not inherited) so future _base changes can't silently
|
|
20
|
+
# flip it on for compliance-conscious users.
|
|
21
|
+
parallel_epics: false
|
|
22
|
+
state_sharding: always
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Legacy profile — rollback to pre-v2 (v1.0.5) behavior.
|
|
2
|
+
#
|
|
3
|
+
# Explicitly duplicates every setting that was default in v1.0.5 so future
|
|
4
|
+
# changes to _base.yaml cannot silently affect legacy behavior. This is
|
|
5
|
+
# the forward-compatibility guarantee: legacy produces a file-and-schema
|
|
6
|
+
# superset match against the v1.0.5 snapshot.
|
|
7
|
+
#
|
|
8
|
+
# Unlike the other profile YAMLs, legacy does NOT extend _base. The
|
|
9
|
+
# resolver detects version_pinned and skips the base overlay.
|
|
10
|
+
|
|
11
|
+
name: legacy
|
|
12
|
+
version_pinned: "v1.0.5"
|
|
13
|
+
|
|
14
|
+
autopilot:
|
|
15
|
+
implementation_flow: full
|
|
16
|
+
session_story_limit: 3
|
|
17
|
+
retrospective_mode: auto
|
|
18
|
+
phase_timings: false
|
|
19
|
+
coalesce_state_writes: false
|
|
20
|
+
conditional_boot_work: false
|
|
21
|
+
cache_shared_reads: false
|
|
22
|
+
# Legacy is byte-for-byte v1.0.5; no v2 LLM calls.
|
|
23
|
+
auto_infer_dependencies: false
|
|
24
|
+
|
|
25
|
+
git:
|
|
26
|
+
granularity: story
|
|
27
|
+
worktree:
|
|
28
|
+
enabled: true
|
|
29
|
+
squash_on_merge: false
|
|
30
|
+
|
|
31
|
+
ma:
|
|
32
|
+
parallel_stories: false
|
|
33
|
+
max_parallel_stories: 2
|
|
34
|
+
parallel_epics: false
|
|
35
|
+
state_sharding: never
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Nano profile — toy / tutorial / learning projects, solo.
|
|
2
|
+
# Routes each story through bmad-quick-dev (one-shot Implement → Review
|
|
3
|
+
# → Classify → Commit). Drops worktrees, per-story PRs, retrospectives,
|
|
4
|
+
# and session checkpoints. Quality gates preserved via quick-dev's
|
|
5
|
+
# internal review step.
|
|
6
|
+
#
|
|
7
|
+
# Behavior is gated on PR 4 (nano routing) landing. Until then this
|
|
8
|
+
# profile is configurable but has no runtime effect.
|
|
9
|
+
|
|
10
|
+
name: nano
|
|
11
|
+
|
|
12
|
+
autopilot:
|
|
13
|
+
implementation_flow: quick
|
|
14
|
+
# Nano's quick-dev one-shot is cheaper per story, so nano can run longer
|
|
15
|
+
# sessions — but unlimited (0) runs into the same context-rot failure
|
|
16
|
+
# mode as the full flow. 5 stories per session keeps step 10's CRITICAL
|
|
17
|
+
# actions reliably in-context without paying for excessive session boots.
|
|
18
|
+
session_story_limit: 5
|
|
19
|
+
# Nano runs at epic granularity with parallelism off — auto-inferring a
|
|
20
|
+
# story DAG would just burn one LLM call per sprint and never get used.
|
|
21
|
+
auto_infer_dependencies: false
|
|
22
|
+
retrospective_mode: skip
|
|
23
|
+
# Nano safety net — if a story fails under quick-dev, escalate to the
|
|
24
|
+
# small profile for the remainder of the session (scope: session only,
|
|
25
|
+
# never written back to config.yaml).
|
|
26
|
+
nano:
|
|
27
|
+
fallback_on_tests_fail: true
|
|
28
|
+
fallback_on_quick_dev_high_severity: true
|
|
29
|
+
fallback_target: small
|
|
30
|
+
|
|
31
|
+
git:
|
|
32
|
+
granularity: epic
|
|
33
|
+
worktree:
|
|
34
|
+
enabled: false
|
|
35
|
+
squash_on_merge: true
|
|
@@ -6,6 +6,14 @@ git:
|
|
|
6
6
|
base_branch: main
|
|
7
7
|
# Git status is tracked in git-status.yaml (Sprintpilot-owned), NOT sprint-status.yaml (BMad Method-owned)
|
|
8
8
|
|
|
9
|
+
# Branch + PR granularity.
|
|
10
|
+
# story — one branch + one PR per story (default).
|
|
11
|
+
# epic — one branch + one PR per epic; stories committed on it.
|
|
12
|
+
# Epic branch name: {branch_prefix}epic-{epic_id}.
|
|
13
|
+
# Epic PR merges with --squash; the PR body lists each
|
|
14
|
+
# story commit. Set on nano profile by default.
|
|
15
|
+
granularity: story
|
|
16
|
+
|
|
9
17
|
# Branch naming
|
|
10
18
|
branch_prefix: "story/" # prefix for story branches (e.g., story/1-2-user-auth)
|
|
11
19
|
max_branch_length: 60 # truncate + 6-char hash if longer
|
|
@@ -7,3 +7,45 @@ multi_agent:
|
|
|
7
7
|
max_parallel_research: 3 # Max concurrent research agents per batch
|
|
8
8
|
max_parallel_analysis: 5 # Max concurrent codebase analysis agents
|
|
9
9
|
# session_story_limit NOT duplicated here — uses autopilot's single authoritative limit
|
|
10
|
+
|
|
11
|
+
# PR 11: intra-epic parallel story execution.
|
|
12
|
+
# parallel_stories: true enables dispatch-layer.js on Claude Code;
|
|
13
|
+
# other hosts silently fall back to sequential (per agent-adapter.js
|
|
14
|
+
# detection — confidence high + supports_parallel true).
|
|
15
|
+
# max_parallel_stories: cap on concurrent sub-agents per layer.
|
|
16
|
+
# min_epic_duration_for_parallel_sec: skip parallelism for epics
|
|
17
|
+
# whose estimated wall-clock is below this threshold (saves
|
|
18
|
+
# dispatch overhead).
|
|
19
|
+
# max_consecutive_conflicts: disable parallelism for the rest of the
|
|
20
|
+
# session once N consecutive merge conflicts occur.
|
|
21
|
+
# effective_parallel_floor: never drop below this mid-session even
|
|
22
|
+
# after failure-driven concurrency reduction.
|
|
23
|
+
parallel_stories: false
|
|
24
|
+
max_parallel_stories: 2
|
|
25
|
+
min_epic_duration_for_parallel_sec: 300
|
|
26
|
+
baseline_story_duration_sec: 180
|
|
27
|
+
max_consecutive_conflicts: 2
|
|
28
|
+
effective_parallel_floor: 1
|
|
29
|
+
|
|
30
|
+
# EXPERIMENTAL: enable parallel_stories dispatch on Gemini CLI.
|
|
31
|
+
#
|
|
32
|
+
# Gemini CLI has a subagent primitive (invoke_subagent) but its
|
|
33
|
+
# worktree-scoped variant is not yet shipped upstream (tracker:
|
|
34
|
+
# github.com/google-gemini/gemini-cli#22967) and real-world parallelism
|
|
35
|
+
# reports serialization + quota throttling. Sprintpilot detects Gemini
|
|
36
|
+
# CLI via GEMINI_CLI=1 (env, HIGH confidence) or parent process `gemini`
|
|
37
|
+
# (MEDIUM), but supports_parallel stays false by default.
|
|
38
|
+
#
|
|
39
|
+
# Flip to true PER PROJECT once upstream worktree support lands (or to
|
|
40
|
+
# experiment at your own risk). Workflow logs an "experimental parallel"
|
|
41
|
+
# warning once per session when this is true AND host=gemini-cli.
|
|
42
|
+
experimental_parallel_on_gemini: false
|
|
43
|
+
|
|
44
|
+
# PR 12 — cross-epic parallelism (EXPERIMENTAL).
|
|
45
|
+
# Off by default on ALL profiles including large. Enabling requires:
|
|
46
|
+
# 1. Both epics carry `independent: true` in dependencies.yaml.
|
|
47
|
+
# 2. preflight-merge.js reports no conflicts between them.
|
|
48
|
+
# 3. max_parallel_epics is hardcoded at 2 — no tuning knob.
|
|
49
|
+
# A single cross-epic merge conflict in a session disables parallel_epics
|
|
50
|
+
# for the rest of the session.
|
|
51
|
+
parallel_epics: false
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// agent-adapter.js — detect the host coding agent currently running the
|
|
4
|
+
// Sprintpilot workflow. Output informs whether parallel sub-agent
|
|
5
|
+
// dispatch (PR 11) is safe.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// agent-adapter.js detect [--project-root <path>]
|
|
9
|
+
//
|
|
10
|
+
// Output (stdout, JSON):
|
|
11
|
+
// {
|
|
12
|
+
// "host": "claude-code" | "cursor" | "windsurf" | "aider" | "cline"
|
|
13
|
+
// | "roo" | "trae" | "kiro" | "copilot" | "unknown",
|
|
14
|
+
// "supports_parallel": boolean,
|
|
15
|
+
// "detection_reason": string,
|
|
16
|
+
// "confidence": "high" | "medium" | "low"
|
|
17
|
+
// }
|
|
18
|
+
//
|
|
19
|
+
// Detection priority (first match wins):
|
|
20
|
+
// 1. Env vars set by the running host → HIGH confidence
|
|
21
|
+
// 2. Parent process name → MEDIUM confidence
|
|
22
|
+
// 3. Filesystem install markers → LOW confidence
|
|
23
|
+
//
|
|
24
|
+
// Tautology guard (concept §M13): filesystem markers prove the INSTALL
|
|
25
|
+
// target, not the CURRENT host. confidence=low forces supports_parallel
|
|
26
|
+
// = false regardless of which host the markers suggest.
|
|
27
|
+
|
|
28
|
+
const fs = require('node:fs');
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const { spawnSync } = require('node:child_process');
|
|
31
|
+
|
|
32
|
+
const { parseArgs } = require('../lib/runtime/args');
|
|
33
|
+
const log = require('../lib/runtime/log');
|
|
34
|
+
|
|
35
|
+
// Host capability table. supports_parallel is true only for hosts with
|
|
36
|
+
// a first-class multi-agent API that Sprintpilot's dispatch-layer.js
|
|
37
|
+
// can reliably drive — which today means worktree-scoped sub-agents
|
|
38
|
+
// with parallel fan-out. Claude Code is the only host that meets both
|
|
39
|
+
// bars at the time of writing. Gemini CLI has a subagent primitive
|
|
40
|
+
// (invoke_subagent) but its worktree-scoped variant is still open
|
|
41
|
+
// upstream (github.com/google-gemini/gemini-cli#22967) and real-world
|
|
42
|
+
// parallelism reports serialization + quota throttling (#25534); hence
|
|
43
|
+
// supports_parallel=false by default, with an experimental opt-in via
|
|
44
|
+
// `ma.experimental_parallel_on_gemini: true` handled at workflow level.
|
|
45
|
+
const HOSTS = {
|
|
46
|
+
'claude-code': { supports_parallel: true },
|
|
47
|
+
'gemini-cli': { supports_parallel: false, subagents: 'experimental' },
|
|
48
|
+
cursor: { supports_parallel: false },
|
|
49
|
+
windsurf: { supports_parallel: false },
|
|
50
|
+
aider: { supports_parallel: false },
|
|
51
|
+
cline: { supports_parallel: false },
|
|
52
|
+
roo: { supports_parallel: false },
|
|
53
|
+
trae: { supports_parallel: false },
|
|
54
|
+
kiro: { supports_parallel: false },
|
|
55
|
+
copilot: { supports_parallel: false },
|
|
56
|
+
unknown: { supports_parallel: false },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const ENV_DETECTORS = [
|
|
60
|
+
{ host: 'claude-code', match: (env) => env.CLAUDECODE === '1' || !!env.CLAUDE_CODE_SESSION_ID },
|
|
61
|
+
// Gemini CLI sets GEMINI_CLI=1 for every subprocess it spawns
|
|
62
|
+
// (docs/tools/shell.md + docs/reference/commands.md as of v0.33.x).
|
|
63
|
+
{ host: 'gemini-cli', match: (env) => env.GEMINI_CLI === '1' || !!env.GEMINI_CLI_SURFACE },
|
|
64
|
+
{ host: 'cursor', match: (env) => !!env.CURSOR_SESSION_ID || !!env.CURSOR_TRACE_ID },
|
|
65
|
+
{ host: 'windsurf', match: (env) => !!env.WINDSURF_SESSION },
|
|
66
|
+
{ host: 'aider', match: (env) => !!env.AIDER_SESSION || !!env.AIDER_HISTORY_FILE },
|
|
67
|
+
{ host: 'cline', match: (env) => !!env.CLINE_SESSION || !!env.CLINE_CONFIG },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const PARENT_DETECTORS = [
|
|
71
|
+
{ host: 'claude-code', parent: 'claude' },
|
|
72
|
+
{ host: 'gemini-cli', parent: 'gemini' },
|
|
73
|
+
{ host: 'cursor', parent: 'cursor-agent' },
|
|
74
|
+
{ host: 'aider', parent: 'aider' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
function help() {
|
|
78
|
+
log.out('Usage: agent-adapter.js detect [--project-root <path>]');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function detectFromEnv(env) {
|
|
82
|
+
for (const d of ENV_DETECTORS) {
|
|
83
|
+
if (d.match(env)) {
|
|
84
|
+
return { host: d.host, confidence: 'high', detection_reason: `env var set (${d.host})` };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Parsers extracted as pure functions so the platform branches stay
|
|
91
|
+
// unit-testable on any OS. Each takes the raw stdout from the platform
|
|
92
|
+
// command and returns the basename or null. Negative test cases
|
|
93
|
+
// (empty input, "INFO: No tasks…", malformed lines) covered by tests.
|
|
94
|
+
|
|
95
|
+
/** Parse `ps -p <pid> -o comm=` output (POSIX). */
|
|
96
|
+
function parsePsOutput(raw) {
|
|
97
|
+
if (!raw) return null;
|
|
98
|
+
const trimmed = raw.trim();
|
|
99
|
+
if (!trimmed) return null;
|
|
100
|
+
return path.basename(trimmed.split(/\s+/)[0]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse `tasklist /FO CSV /NH` output (Windows). Strips `.exe` so the
|
|
105
|
+
* basename matches the POSIX path (so PARENT_DETECTORS only needs the
|
|
106
|
+
* non-extension name once).
|
|
107
|
+
* Sample row: "claude.exe","12345","Console","1","123,456 K"
|
|
108
|
+
*/
|
|
109
|
+
function parseTasklistOutput(raw) {
|
|
110
|
+
if (!raw) return null;
|
|
111
|
+
const trimmed = raw.trim();
|
|
112
|
+
if (!trimmed || /^INFO:/i.test(trimmed)) return null; // "INFO: No tasks…"
|
|
113
|
+
const m = trimmed.match(/^"([^"]+)"/);
|
|
114
|
+
if (!m) return null;
|
|
115
|
+
return path.basename(m[1]).replace(/\.exe$/i, '');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parentProcessName() {
|
|
119
|
+
try {
|
|
120
|
+
const pid = process.ppid;
|
|
121
|
+
if (process.platform === 'win32') {
|
|
122
|
+
const res = spawnSync(
|
|
123
|
+
'tasklist',
|
|
124
|
+
['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'],
|
|
125
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
126
|
+
);
|
|
127
|
+
if (res.status !== 0) return null;
|
|
128
|
+
return parseTasklistOutput(res.stdout || '');
|
|
129
|
+
}
|
|
130
|
+
// POSIX: macOS and Linux both support `ps -p <pid> -o comm=`.
|
|
131
|
+
const res = spawnSync('ps', ['-p', String(pid), '-o', 'comm='], {
|
|
132
|
+
encoding: 'utf8',
|
|
133
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
134
|
+
});
|
|
135
|
+
if (res.status !== 0) return null;
|
|
136
|
+
return parsePsOutput(res.stdout || '');
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function detectFromParent() {
|
|
143
|
+
const parent = parentProcessName();
|
|
144
|
+
if (!parent) return null;
|
|
145
|
+
for (const d of PARENT_DETECTORS) {
|
|
146
|
+
if (parent === d.parent) {
|
|
147
|
+
return {
|
|
148
|
+
host: d.host,
|
|
149
|
+
confidence: 'medium',
|
|
150
|
+
detection_reason: `parent process '${parent}'`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function detectFromFilesystem(projectRoot) {
|
|
158
|
+
const markers = [
|
|
159
|
+
{ path: path.join(projectRoot, '.claude', 'skills'), host: 'claude-code' },
|
|
160
|
+
{ path: path.join(projectRoot, '.claude-code'), host: 'claude-code' },
|
|
161
|
+
{ path: path.join(projectRoot, '.cursor'), host: 'cursor' },
|
|
162
|
+
{ path: path.join(projectRoot, '.windsurf'), host: 'windsurf' },
|
|
163
|
+
{ path: path.join(projectRoot, '.aider.conf.yml'), host: 'aider' },
|
|
164
|
+
{ path: path.join(projectRoot, '.cline'), host: 'cline' },
|
|
165
|
+
];
|
|
166
|
+
for (const m of markers) {
|
|
167
|
+
if (fs.existsSync(m.path)) {
|
|
168
|
+
return {
|
|
169
|
+
host: m.host,
|
|
170
|
+
confidence: 'low',
|
|
171
|
+
detection_reason: `filesystem marker '${path.relative(projectRoot, m.path)}' — NOT proof of current host`,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function detect({ env = process.env, projectRoot = process.cwd() } = {}) {
|
|
179
|
+
const fromEnv = detectFromEnv(env);
|
|
180
|
+
if (fromEnv) {
|
|
181
|
+
const caps = HOSTS[fromEnv.host] || HOSTS.unknown;
|
|
182
|
+
return {
|
|
183
|
+
host: fromEnv.host,
|
|
184
|
+
supports_parallel: caps.supports_parallel,
|
|
185
|
+
detection_reason: fromEnv.detection_reason,
|
|
186
|
+
confidence: 'high',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const fromParent = detectFromParent();
|
|
190
|
+
if (fromParent) {
|
|
191
|
+
const caps = HOSTS[fromParent.host] || HOSTS.unknown;
|
|
192
|
+
return {
|
|
193
|
+
host: fromParent.host,
|
|
194
|
+
supports_parallel: caps.supports_parallel,
|
|
195
|
+
detection_reason: fromParent.detection_reason,
|
|
196
|
+
confidence: 'medium',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const fromFs = detectFromFilesystem(projectRoot);
|
|
200
|
+
if (fromFs) {
|
|
201
|
+
// Tautology guard: filesystem markers never enable parallel.
|
|
202
|
+
return {
|
|
203
|
+
host: fromFs.host,
|
|
204
|
+
supports_parallel: false,
|
|
205
|
+
detection_reason: fromFs.detection_reason,
|
|
206
|
+
confidence: 'low',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
host: 'unknown',
|
|
211
|
+
supports_parallel: false,
|
|
212
|
+
detection_reason: 'no env/parent/filesystem signal',
|
|
213
|
+
confidence: 'low',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function main() {
|
|
218
|
+
const { opts, positional } = parseArgs(process.argv.slice(2));
|
|
219
|
+
if (opts.help || positional.length === 0) {
|
|
220
|
+
help();
|
|
221
|
+
process.exit(opts.help ? 0 : 1);
|
|
222
|
+
}
|
|
223
|
+
const action = positional[0];
|
|
224
|
+
if (action !== 'detect') {
|
|
225
|
+
log.error(`unknown action '${action}'. Valid: detect`);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
const projectRoot = opts['project-root'] || process.cwd();
|
|
229
|
+
const result = detect({ env: process.env, projectRoot });
|
|
230
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
HOSTS,
|
|
235
|
+
ENV_DETECTORS,
|
|
236
|
+
PARENT_DETECTORS,
|
|
237
|
+
detectFromEnv,
|
|
238
|
+
detectFromParent,
|
|
239
|
+
detectFromFilesystem,
|
|
240
|
+
detect,
|
|
241
|
+
parsePsOutput,
|
|
242
|
+
parseTasklistOutput,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (require.main === module) {
|
|
246
|
+
main();
|
|
247
|
+
}
|