baldart 3.8.2 → 3.9.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.9.0] - 2026-05-22
9
+
10
+ `worktree-manager` (`/mw`) gains a configurable merge strategy. The previous flow forced every consumer through `gh pr create` + `gh pr merge`; in repos where `develop` is not protected on GitHub, that roundtrip is unnecessary and was observed to stall (PR opened, never merged). The new `git.merge_strategy` key lets each project pick its lane.
11
+
12
+ ### Added
13
+
14
+ - **`baldart.config.yml` → `git.merge_strategy`** — new top-level config section with one key (default `pr`):
15
+ - `pr` (default, backwards-compatible) — push feature branch, open PR via `gh pr create`, merge via `gh pr merge`. Required if `develop` is protected on origin.
16
+ - `local-push` — after the rebase in `/mw` step 4b, fast-forward push the feature branch directly to `origin/develop` (`git push origin <feat>:develop`). No PR, no `gh` dependency. Fails hard with a clear hint if `develop` is protected on origin.
17
+ - **`framework/.claude/skills/worktree-manager/SKILL.md` step 4c** — fully rewritten into two clearly labelled strategies (A: `pr`, B: `local-push`) plus a shared local-develop-sync block. Both strategies remain strictly worktree-isolated — neither runs `git checkout` / `git switch` / `git branch` on the main repo.
18
+ - **Programmatic API output** — `/mw` now returns `strategy: "pr"|"local-push"`. `prNumber` is `null` in `local-push` mode.
19
+
20
+ ### Changed
21
+
22
+ - **`framework/.claude/skills/new/SKILL.md`** — Phase 6 description and fail-safe rules reference the configured strategy instead of hard-coding `gh pr merge`.
23
+ - **`worktree-manager` Project Context header** — added at the top of the skill, citing `git.merge_strategy` + `paths.backlog_dir` per the always-ask-never-assume contract in `framework/agents/project-context.md`.
24
+ - **`src/commands/configure.js`** — gained autodetection + interactive prompt for `git.merge_strategy`. The probe runs `gh api repos/:o/:r/branches/develop/protection` to recommend `pr` (when develop is protected) or `local-push` (when not). Falls back to `pr` safely when `gh` is missing or the remote is unreachable. The detection reason is surfaced inline so the user knows *why* a strategy is recommended.
25
+ - **`src/commands/update.js`** — schema-drift detection now also covers new top-level sections (currently `git.*`). When new keys are detected, `update` proactively offers to run `configure` instead of just warning. This means an existing consumer running `npx baldart update` will be **asked** about the new `git.merge_strategy` key, not silently defaulted.
26
+
27
+ ### Notes
28
+
29
+ - No migration is required for existing consumers. `git.merge_strategy` defaults to `pr` when the key is absent, preserving the v3.4.0+ behaviour exactly.
30
+ - On `npx baldart update`, existing consumers are **prompted** to run `configure`, where the merge-strategy autodetection probes their repo and recommends `pr` or `local-push` with a one-line reason. The user picks; nothing is auto-applied.
31
+ - `develop` remains the only supported integration branch; configurable `base_branch` is out of scope for this release.
32
+
8
33
  ## [3.8.2] - 2026-05-22
9
34
 
10
35
  `baldart` (smart doctor) no longer double-confirms safe actions. Previously, running `baldart` with a remote-ahead state would ask *"Run: Pull N commit(s)?"* and then the `update` command would ask *"Proceed with update?"* immediately after — two prompts for the same intent.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.8.2
1
+ 3.9.0
@@ -1176,7 +1176,7 @@ After the final review passes AND all cards are committed in the worktree, deleg
1176
1176
  - The worktree path and branch from the tracker
1177
1177
  - `checksAlreadyPassed: true` (final review + QA already validated the build)
1178
1178
  - All card IDs for the commit message
1179
- 3. The skill handles: safety commit of any remaining uncommitted files (step 3), rebasing onto latest develop (step 4b — auto-resolves doc conflicts, stops on code conflicts), remote merge to develop via `gh pr merge` (step 4c — NEVER `git checkout develop` on the main repo; this respects the absolute terminal-isolation rule that many consumer projects declare in CLAUDE.md), post-merge verification, worktree removal, registry cleanup, and remote branch deletion.
1179
+ 3. The skill handles: safety commit of any remaining uncommitted files (step 3), rebasing onto latest develop (step 4b — auto-resolves doc conflicts, stops on code conflicts), merging into develop via the configured `git.merge_strategy` (step 4c — `pr` uses `gh pr merge`, `local-push` does a direct FF push to `origin/develop`; NEITHER runs `git checkout develop` on the main repo, respecting the absolute terminal-isolation rule), post-merge verification, worktree removal, registry cleanup, and remote branch deletion.
1180
1180
  4. **If code merge conflicts** → the skill STOPs and reports. Doc-only conflicts (ssot-registry.md, project-status.md, etc.) are auto-resolved by keeping both sides.
1181
1181
  5. **If post-merge build fails** → the skill STOPs and keeps the worktree intact for investigation.
1182
1182
  6. Record the merge commit hash and result in the tracker.
@@ -1245,8 +1245,8 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1245
1245
  **Why this exists**: Agents frequently skip the DONE marking in Phase 4 (step 27) due to context compaction, commit failures that interrupt the flow, or team mode where Step D.6 gets lost. This gate is the safety net that catches ALL of these cases.
1246
1246
 
1247
1247
  ### Fail-safe rules (enforced by worktree-manager skill)
1248
- - **Never `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo** from inside this orchestrator. The main repo is shared across parallel terminals. Use `gh pr merge` for develop merges and `git -C <main> pull --ff-only` (only when `HEAD = develop` already) for sync. See `/mw` step 4c.
1249
- - Never merge to `main` — only to `develop`, and only via `gh pr merge` (NOT local checkout).
1248
+ - **Never `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo** from inside this orchestrator. The main repo is shared across parallel terminals. Use the configured `git.merge_strategy` for develop merges and `git -C <main> pull --ff-only` (only when `HEAD = develop` already) for sync. See `/mw` step 4c.
1249
+ - Never merge to `main` — only to `develop`, via the configured `git.merge_strategy` (NOT local checkout).
1250
1250
  - Never force push to `main` or `develop`. `--force-with-lease` on feature branches after rebase is allowed.
1251
1251
  - Never delete a branch before successful merge verification.
1252
1252
  - Never remove a worktree before confirming develop is stable post-merge.
@@ -14,6 +14,11 @@ description: >
14
14
 
15
15
  # Worktree Manager
16
16
 
17
+ **Project Context (resolved from `baldart.config.yml`)**:
18
+ - `git.merge_strategy` — `pr` (default, GitHub PR via `gh`) or `local-push` (direct fast-forward to `origin/develop`). Drives `/mw` step 4c.
19
+ - `paths.backlog_dir` — used for syncing untracked backlog cards (`/nw` step 3b).
20
+ - Protocol reference: `framework/agents/project-context.md`. Skills must ASK when a needed key is missing — never assume.
21
+
17
22
  **IMMEDIATE EXECUTION**: When invoked via `/nw`, `/mw`, `/lw`, or `/cw`, do NOT explain the process. Start executing the matching command flow immediately:
18
23
 
19
24
  - `/nw` → Ask for **slug** (required) and optionally a **card ID**, then execute all steps (pre-flight → create → install → build → report). If the user provided args, skip questions and use them directly. Supports three forms:
@@ -38,9 +43,13 @@ Output: `{ path: string, branch: string, port: number }`
38
43
  ### /mw programmatic
39
44
 
40
45
  Input: `{ worktreePath: string, checksAlreadyPassed?: boolean }`
41
- Output: `{ merged: boolean, developVerified: boolean, prNumber: number, mergeCommit: string }`
46
+ Output: `{ merged: boolean, developVerified: boolean, prNumber: number|null, mergeCommit: string, strategy: "pr"|"local-push" }`
47
+
48
+ The merge path depends on `git.merge_strategy` in `baldart.config.yml`:
49
+ - `pr` (default, since v3.4.0): merge via `gh pr create` + `gh pr merge`. `prNumber` is the PR number, `mergeCommit` is the SHA returned by the GitHub merge API.
50
+ - `local-push` (since v3.9.0): direct fast-forward push of the rebased feature branch to `origin/develop` — no PR, no `gh`. `prNumber` is `null`, `mergeCommit` is the local SHA of the feature branch tip.
42
51
 
43
- Note: since v3.4.0 the merge happens via `gh pr merge` against GitHub (NOT a local `git checkout develop` + `git merge`). `mergeCommit` is the SHA returned by the GitHub merge API. The orchestrator can use `prNumber` for follow-up (linking, deploy webhooks, etc.).
52
+ Neither path runs `git checkout` / `git switch` / `git branch` on the main repo. The orchestrator can branch on `strategy` if it needs PR linking (only meaningful in `pr` mode).
44
53
 
45
54
  ### /cw programmatic
46
55
 
@@ -435,16 +444,32 @@ npm run build
435
444
 
436
445
  If build fails after rebase → STOP and report. The rebase introduced incompatibilities.
437
446
 
438
- ### 4c. Remote merge via GitHub PR (NO main-repo checkout)
447
+ ### 4c. Land the feature branch onto develop (strategy-branched)
439
448
 
440
- **IMPORTANT — Remote merge only, NO local checkout/switch/branch on the main repo.** Consumer projects often share the main repo as a parallel resource across multiple terminals; running `git checkout develop` on it preempts whatever the other terminals are doing and breaks worktree-driven workflows. We therefore do the merge via the GitHub API (`gh pr create` + `gh pr merge`) — the main repo's `HEAD` is never touched.
449
+ **IMPORTANT — NO local checkout/switch/branch on the main repo, regardless of strategy.** Consumer projects share the main repo as a parallel resource across multiple terminals; running `git checkout develop` on it preempts whatever the other terminals are doing and breaks worktree-driven workflows. Both strategies below operate from the worktree and the main repo's `HEAD` is never touched.
441
450
 
442
- The previous local-merge flow caused: (a) classifier-blocked `git checkout develop` in projects with terminal-isolation rules in CLAUDE.md, (b) silent failure of the orchestrator's merge phase.
451
+ Read `git.merge_strategy` from `baldart.config.yml` (default: `pr`):
443
452
 
444
453
  ```bash
445
454
  MAIN=$(git -C "$WORKTREE_PATH" rev-parse --git-common-dir)/..
446
455
  MAIN=$(cd "$MAIN" && pwd)
447
456
 
457
+ # Resolve strategy from baldart.config.yml (default: pr)
458
+ STRATEGY=$(grep -A1 '^git:' "$MAIN/baldart.config.yml" 2>/dev/null \
459
+ | grep 'merge_strategy:' | head -1 \
460
+ | sed -E 's/.*merge_strategy:[[:space:]]*([a-z-]+).*/\1/' || echo "pr")
461
+ STRATEGY=${STRATEGY:-pr}
462
+ ```
463
+
464
+ If neither `pr` nor `local-push` resolves, ASK the user once which to use and suggest persisting the choice (per the always-ask, never-assume contract in `framework/agents/project-context.md`).
465
+
466
+ ---
467
+
468
+ #### Strategy A — `pr` (default, GitHub PR via `gh`)
469
+
470
+ Use this when `develop` is protected on origin (required status checks, reviews, merge queue) — a direct push would be rejected.
471
+
472
+ ```bash
448
473
  # 1. Push the feature branch to origin (force-with-lease in case of rebase in 4b).
449
474
  git -C "$WORKTREE_PATH" push --force-with-lease origin "$BRANCH"
450
475
 
@@ -464,10 +489,6 @@ fi
464
489
  echo "PR #$PR_NUMBER ready to merge."
465
490
 
466
491
  # 3. Merge via gh CLI. --delete-branch removes the remote branch on success.
467
- # The CLI may print a non-fatal warning if the LOCAL branch can't be deleted
468
- # (e.g. "fatal: 'develop' is already used by worktree at ..." when develop is
469
- # checked out in another worktree). The REMOTE merge happens regardless — we
470
- # swallow the local-cleanup warning.
471
492
  MERGE_OUTPUT=$(gh pr merge "$PR_NUMBER" --merge --delete-branch 2>&1) || MERGE_RC=$?
472
493
  echo "$MERGE_OUTPUT"
473
494
 
@@ -488,11 +509,63 @@ if [ "$PR_STATE" != "MERGED" ]; then
488
509
  exit 1
489
510
  fi
490
511
  echo "✅ PR #$PR_NUMBER merged to develop."
512
+ ```
513
+
514
+ If `gh` is not installed in the project, STOP and ask the user to install GitHub CLI (`brew install gh` or equivalent) and run `gh auth login`. Do NOT fall back to a local checkout — it violates terminal isolation.
515
+
516
+ If both gh CLI and REST API merge fail → STOP and report. Suggest switching to `merge_strategy: local-push` only if `develop` is NOT protected on origin.
517
+
518
+ ---
519
+
520
+ #### Strategy B — `local-push` (direct FF push, no PR)
521
+
522
+ Use this when `develop` is NOT protected on origin and the user wants to skip the GitHub PR roundtrip. After step 4b the feature branch is rebased onto `origin/develop`, so it can fast-forward `develop` on origin in a single push.
523
+
524
+ ```bash
525
+ # 1. Sanity: confirm the feature branch is strictly ahead of origin/develop
526
+ # (i.e. the rebase in step 4b succeeded and we can FF develop on origin).
527
+ git -C "$WORKTREE_PATH" fetch origin develop --quiet
528
+ AHEAD=$(git -C "$WORKTREE_PATH" rev-list --count "origin/develop..$BRANCH")
529
+ BEHIND=$(git -C "$WORKTREE_PATH" rev-list --count "$BRANCH..origin/develop")
530
+ if [ "$BEHIND" -ne 0 ]; then
531
+ echo "❌ local-push aborted: $BRANCH is $BEHIND commit(s) behind origin/develop." >&2
532
+ echo " Re-run step 4b (rebase onto origin/develop), then retry." >&2
533
+ exit 1
534
+ fi
535
+ if [ "$AHEAD" -eq 0 ]; then
536
+ echo "ℹ️ Nothing to push — $BRANCH is identical to origin/develop. Skipping."
537
+ else
538
+ echo "Fast-forwarding origin/develop by $AHEAD commit(s) via local-push…"
539
+ fi
540
+
541
+ # 2. Direct fast-forward push to develop on origin. No PR.
542
+ # If develop is protected on origin, this fails — surface the hint and stop.
543
+ if ! PUSH_OUTPUT=$(git -C "$WORKTREE_PATH" push origin "$BRANCH:develop" 2>&1); then
544
+ echo "$PUSH_OUTPUT"
545
+ if echo "$PUSH_OUTPUT" | grep -qiE "protected branch|required status|review required"; then
546
+ echo "❌ local-push rejected: 'develop' is protected on origin." >&2
547
+ echo " Switch baldart.config.yml → git.merge_strategy: pr, or remove the protection." >&2
548
+ fi
549
+ exit 1
550
+ fi
551
+ echo "✅ $BRANCH merged into develop on origin (fast-forward, no PR)."
491
552
 
492
- # 4. Sync local develop ONLY if the main repo's HEAD is already develop.
493
- # NEVER `git checkout develop` here that's the regression we're fixing.
494
- # If the user (or another terminal) had develop checked out, ff-only updates
495
- # it cleanly. Otherwise we leave their HEAD alone and they'll pull next time.
553
+ # 3. Delete the remote feature branch (best-effort never fatal).
554
+ git -C "$WORKTREE_PATH" push origin --delete "$BRANCH" 2>/dev/null || true
555
+
556
+ MERGE_SHA=$(git -C "$WORKTREE_PATH" rev-parse HEAD)
557
+ PR_NUMBER=""
558
+ ```
559
+
560
+ ---
561
+
562
+ #### Common — sync local develop ref (both strategies)
563
+
564
+ ```bash
565
+ # Sync local develop ONLY if the main repo's HEAD is already develop.
566
+ # NEVER `git checkout develop` here. If the user (or another terminal) had
567
+ # develop checked out, ff-only updates it cleanly. Otherwise we leave their
568
+ # HEAD alone and they'll pull next time.
496
569
  CURRENT_BRANCH=$(git -C "$MAIN" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
497
570
  if [ "$CURRENT_BRANCH" = "develop" ]; then
498
571
  git -C "$MAIN" pull --ff-only origin develop || \
@@ -503,10 +576,6 @@ else
503
576
  fi
504
577
  ```
505
578
 
506
- If `gh` is not installed in the project, STOP and ask the user to install GitHub CLI (`brew install gh` or equivalent) and run `gh auth login`. Do NOT fall back to the legacy local-checkout flow — it violates terminal isolation in worktree-driven projects.
507
-
508
- If both gh CLI and REST API merge fail → STOP and report. Do NOT attempt a local checkout as fallback.
509
-
510
579
  ### 5. Post-merge verification
511
580
 
512
581
  ```bash
@@ -554,7 +623,9 @@ Remove the entry from `.worktrees/registry.json`.
554
623
  Merge complete:
555
624
  Card: <CARD-ID>
556
625
  Branch: <BRANCH> (deleted on remote; local-cleanup may have skipped if checked out elsewhere)
557
- PR: #<PR_NUMBER> merged via `gh pr merge`
626
+ Strategy: <pr | local-push>
627
+ PR: #<PR_NUMBER> merged via `gh pr merge` ← only in `pr` strategy
628
+ Push: fast-forward to origin/develop ← only in `local-push` strategy
558
629
  Worktree: <WORKTREE_PATH> (removed)
559
630
  develop: remote up to date; local main-repo synced only if HEAD was already develop
560
631
  ```
@@ -665,8 +736,8 @@ Cleanup complete:
665
736
 
666
737
  ## Safety Rules
667
738
 
668
- - **NEVER `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo from inside `/mw` / `/nw` / `/cw`.** The main repo is a shared resource across parallel terminals; touching its `HEAD` preempts other agents and breaks worktree-driven workflows. Many consumer projects (e.g. mayo) declare this as an absolute rule in CLAUDE.md and Claude Code's classifier will pattern-match and block these commands preemptively. Use `gh pr create` + `gh pr merge` for develop merges and `git -C <main> pull --ff-only` (only when `HEAD = develop` already) for sync. See `/mw` step 4c.
669
- - NEVER merge to `main` — only to `develop`, and only via `gh pr merge` (NOT local checkout).
739
+ - **NEVER `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo from inside `/mw` / `/nw` / `/cw`.** The main repo is a shared resource across parallel terminals; touching its `HEAD` preempts other agents and breaks worktree-driven workflows. Many consumer projects declare this as an absolute rule in CLAUDE.md and Claude Code's classifier will pattern-match and block these commands preemptively. Use the configured `git.merge_strategy` for develop merges and `git -C <main> pull --ff-only` (only when `HEAD = develop` already) for sync. See `/mw` step 4c.
740
+ - NEVER merge to `main` — only to `develop`, via the configured `git.merge_strategy` (`pr` → `gh pr merge`; `local-push` `git push origin <feat>:develop`). NEVER via local checkout.
670
741
  - NEVER force push from a worktree (`--force-with-lease` on the feature branch after rebase is the only allowed force variant).
671
742
  - NEVER `git add .` or `git add -A` — always explicit file names.
672
743
  - NEVER remove a worktree with uncommitted changes without explicit user confirmation.
@@ -112,6 +112,26 @@ features:
112
112
  # LLM-wiki overlay (paths.wiki_dir + capture/wiki-curator loop).
113
113
  has_wiki_overlay: false
114
114
 
115
+ # ─── GIT ─────────────────────────────────────────────────────────────────
116
+ # Controls how worktree-manager (`/mw`) integrates a worktree's feature
117
+ # branch back into `develop`.
118
+ git:
119
+ # How `/mw` lands the feature branch onto develop:
120
+ #
121
+ # pr (default) — push feature branch, open a PR via `gh pr create`,
122
+ # merge via `gh pr merge`. Required if `develop` is protected
123
+ # on origin (status checks, required reviews, merge queue).
124
+ #
125
+ # local-push — after rebasing the feature branch onto `origin/develop`,
126
+ # fast-forward push the feature branch directly to
127
+ # `origin/develop` (`git push origin <feat>:develop`).
128
+ # No PR, no `gh` dependency. FAILS hard if `develop` is
129
+ # protected on origin — switch back to `pr` in that case.
130
+ #
131
+ # Neither strategy ever touches the main repo's HEAD — both flows are
132
+ # worktree-isolated and respect the terminal-isolation rule.
133
+ merge_strategy: pr
134
+
115
135
  # ─── TOOLS ───────────────────────────────────────────────────────────────
116
136
  # Which AI CLI tools should the framework target on this machine?
117
137
  # Each enabled tool gets its own per-item skill symlinks pointing at the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "3.8.2",
3
+ "version": "3.9.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"
@@ -26,6 +26,48 @@ const IGNORED_DIRS = new Set([
26
26
  '.expo', '.parcel-cache', 'tmp', '.idea', '.vscode', '.DS_Store'
27
27
  ]);
28
28
 
29
+ /**
30
+ * Probe origin/develop branch protection to recommend a merge strategy.
31
+ * Returns: { value, protected, reason }
32
+ * - 'pr' when develop is protected (PR is the only viable path)
33
+ * - 'local-push' when develop is NOT protected (FF push is faster, no gh needed)
34
+ * - 'pr' as a safe default if we can't tell (gh missing, network down, no remote)
35
+ *
36
+ * Non-fatal: any failure returns `{ value: 'pr', protected: null }` so configure
37
+ * keeps moving. The user can override in the interactive prompt.
38
+ */
39
+ function detectMergeStrategy(cwd = process.cwd()) {
40
+ try {
41
+ const { execSync } = require('child_process');
42
+ const opts = { cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000, encoding: 'utf8' };
43
+ // Need both git + gh. If either is absent or unauthenticated, default 'pr'.
44
+ try { execSync('gh --version', opts); } catch (_) {
45
+ return { value: 'pr', protected: null, reason: 'gh CLI not installed — defaulting to pr.' };
46
+ }
47
+ let nameWithOwner = '';
48
+ try {
49
+ nameWithOwner = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', opts).trim();
50
+ } catch (_) {
51
+ return { value: 'pr', protected: null, reason: 'no GitHub remote detected — defaulting to pr.' };
52
+ }
53
+ // Probe branch protection. 404 means "not protected" → local-push viable.
54
+ try {
55
+ execSync(`gh api repos/${nameWithOwner}/branches/develop/protection`, opts);
56
+ return { value: 'pr', protected: true, reason: 'develop is protected on origin — PR strategy required.' };
57
+ } catch (_) {
58
+ // Could be 404 (not protected) OR branch doesn't exist. Check existence:
59
+ try {
60
+ execSync(`gh api repos/${nameWithOwner}/branches/develop -q .name`, opts);
61
+ return { value: 'local-push', protected: false, reason: 'develop exists on origin and is not protected — local-push viable.' };
62
+ } catch (_) {
63
+ return { value: 'pr', protected: null, reason: 'develop branch not found on origin — defaulting to pr.' };
64
+ }
65
+ }
66
+ } catch (_) {
67
+ return { value: 'pr', protected: null, reason: 'probe failed — defaulting to pr.' };
68
+ }
69
+ }
70
+
29
71
  function detect(cwd = process.cwd()) {
30
72
  const exists = (p) => fs.existsSync(path.join(cwd, p));
31
73
  const findFirst = (...candidates) => candidates.find(exists) || '';
@@ -238,6 +280,8 @@ function detect(cwd = process.cwd()) {
238
280
  : (exists('cypress.config.ts') || exists('cypress.config.js') || hasDep('cypress')) ? 'cypress'
239
281
  : '';
240
282
 
283
+ const mergeStrategyProbe = detectMergeStrategy(cwd);
284
+
241
285
  const detected = {
242
286
  paths: {
243
287
  design_system: designSystemPath,
@@ -302,6 +346,16 @@ function detect(cwd = process.cwd()) {
302
346
  tools: {
303
347
  enabled: toolAdapters.defaultEnabled(cwd)
304
348
  },
349
+ git: {
350
+ // Set to a plain string — the structured probe details (`protected`,
351
+ // `reason`) are kept on a sibling key so mergePreserving stays type-safe.
352
+ merge_strategy: mergeStrategyProbe.value,
353
+ },
354
+ _probes: {
355
+ // Internal scratchpad — never serialized to YAML. Used by
356
+ // interactivePrompts to surface the autodetection reasoning.
357
+ merge_strategy: mergeStrategyProbe,
358
+ },
305
359
  };
306
360
 
307
361
  // Reference walkFirst once so the dead-code linter is happy and the helper
@@ -424,6 +478,25 @@ async function interactivePrompts(merged, detected) {
424
478
  }
425
479
  merged.tools.enabled = currentEnabled;
426
480
 
481
+ // ---- Git merge strategy (since v3.9.0) --------------------------------
482
+ UI.section('Git merge strategy (how `/mw` lands feature branches onto develop)');
483
+ merged.git = merged.git || {};
484
+ const probe = (detected._probes && detected._probes.merge_strategy)
485
+ || { value: 'pr', protected: null };
486
+ if (probe.reason) UI.info(probe.reason);
487
+ const currentMerge = merged.git.merge_strategy || probe.value || 'pr';
488
+ const chosenMerge = await UI.select(
489
+ `Choose merge strategy (current: ${currentMerge})`,
490
+ [
491
+ { name: 'pr — open a GitHub PR, merge via `gh pr merge` (required if develop is protected)', value: 'pr' },
492
+ { name: 'local-push — direct FF push to origin/develop (no PR, no `gh` dependency)', value: 'local-push' },
493
+ ]
494
+ );
495
+ merged.git.merge_strategy = chosenMerge;
496
+ if (chosenMerge === 'local-push' && probe.protected === true) {
497
+ UI.warning('You picked `local-push` but develop appears protected on origin — pushes will be rejected. Switch back to `pr` if it fails.');
498
+ }
499
+
427
500
  UI.section('Features (explicit yes/no — option A: always ask)');
428
501
  for (const flag of [
429
502
  ['has_design_system', 'Project has a documented design system?'],
@@ -535,7 +608,9 @@ function serialize(config) {
535
608
  # NOTE: features.* flags MUST be explicit (true | false). Any flag absent
536
609
  # from this file will prompt the user on first skill invocation.
537
610
  `;
538
- return header + yaml.dump(stripNullFeatures(config), { lineWidth: 100, noRefs: true });
611
+ const clean = stripNullFeatures(config);
612
+ if (clean && clean._probes) delete clean._probes;
613
+ return header + yaml.dump(clean, { lineWidth: 100, noRefs: true });
539
614
  }
540
615
 
541
616
  async function configure(opts = {}) {
@@ -413,12 +413,21 @@ async function update(options = {}) {
413
413
  .filter((k) => !(k in (cur2.features || {})));
414
414
  const missingPaths = Object.keys(tpl.paths || {})
415
415
  .filter((k) => !(k in (cur2.paths || {})));
416
- if (missingFeatures.length || missingPaths.length) {
416
+ const missingGit = Object.keys(tpl.git || {})
417
+ .filter((k) => !(k in (cur2.git || {})));
418
+ const allMissing = [...missingPaths, ...missingFeatures, ...missingGit.map((k) => `git.${k}`)];
419
+ if (allMissing.length) {
417
420
  UI.newline();
418
- UI.warning(
419
- `New config keys in this version: ${[...missingPaths, ...missingFeatures].join(', ')}. ` +
420
- 'Re-run `npx baldart configure` to populate them (existing values are preserved).'
421
- );
421
+ UI.warning(`New config keys in this version: ${allMissing.join(', ')}.`);
422
+ // Auto-offer configure so the user actually answers, instead of
423
+ // silently falling back to template defaults on first use.
424
+ const runConfigure = await UI.confirm('Run `baldart configure` now to populate them? (existing values are preserved)', true);
425
+ if (runConfigure) {
426
+ const configureCmd = require('./configure');
427
+ await configureCmd();
428
+ } else {
429
+ UI.info('Skipped. Skills will prompt for the missing keys on first use.');
430
+ }
422
431
  }
423
432
  } catch (_) {
424
433
  // Non-fatal — schema drift detection is best-effort.