@wipcomputer/wip-ai-devops-toolbox 1.9.71-alpha.7 → 1.9.71-alpha.9

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
@@ -1,5 +1,111 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.71-alpha.9 (2026-04-05)
4
+
5
+ # v1.9.71-alpha.8
6
+
7
+ ## wip-release: automatic PR flow for protected main (Phase 4)
8
+
9
+ When `git push origin main` fails with GitHub's "protected branch" rejection (`GH006: Changes must be made through a pull request`), wip-release now automatically:
10
+
11
+ 1. Creates a release branch `cc-mini/release-v<version>` at the current commit
12
+ 2. Pushes the branch to origin
13
+ 3. Opens a PR via `gh pr create` with title `release: v<version>`
14
+ 4. Merges the PR via `gh pr merge --merge --delete-branch`
15
+ 5. Pushes the tag separately (tags bypass branch protection on most GitHub setups)
16
+ 6. Fast-forwards local main so downstream steps (deploy-public, etc.) have a clean state
17
+
18
+ Previously this was a 4-command manual workflow every release:
19
+
20
+ ```
21
+ git branch cc-mini/release-alpha-N
22
+ git push -u origin cc-mini/release-alpha-N
23
+ gh pr create --base main --head cc-mini/release-alpha-N --title '...'
24
+ gh pr merge <pr> --merge --delete-branch
25
+ git push origin v<version>
26
+ ```
27
+
28
+ Every release. Every time. Eliminated.
29
+
30
+ ## Fallback behavior
31
+
32
+ If any step of the auto-PR flow fails (gh CLI missing, PR create failure, merge failure, tag push failure), wip-release logs a concrete recovery command for the exact failure mode and continues (non-fatal, matches prior push-failed behavior). The user can always complete the remaining steps manually.
33
+
34
+ ## Direct push still works
35
+
36
+ If the repo allows direct push to main (typical for private staging repos), wip-release tries direct push first and only falls back to the PR flow on the specific GH006 / "protected branch" error. No behavioral change for unprotected repos.
37
+
38
+ ## Files changed
39
+
40
+ - `tools/wip-release/core.mjs`: new `pushReleaseWithAutoPr(repoPath, newVersion, level)` and `logPushFailure(result, tag)` helpers. Three push sites in `release()`, `releaseHotfix()`, `releasePrerelease()` migrated to use the helper.
41
+ - `tools/wip-release/package.json`: 1.9.72 -> 1.9.73
42
+ - `CHANGELOG.md`: entry added
43
+
44
+ ## Verified
45
+
46
+ - Module imports cleanly via `node -e "import('./tools/wip-release/core.mjs')"`.
47
+ - Error detection regex handles GH006 variants: `/protected branch|GH006|Changes must be made through a pull request/i`.
48
+ - All three release tracks (stable, prerelease, hotfix) use the same helper.
49
+
50
+ ## Cross-references
51
+
52
+ - `ai/product/bugs/release-pipeline/2026-04-05--cc-mini--release-pipeline-master-plan.md` Phase 4 (Incident 4)
53
+ - `ai/product/bugs/master-plans/bugs-plan-04-05-2026-002.md` Wave 2 phase 7
54
+ - Prior ship: alpha.7 closed Phases 1, 2, 8 of the same plan.
55
+
56
+ ## 1.9.71-alpha.8 (2026-04-05)
57
+
58
+ # v1.9.71-alpha.8
59
+
60
+ ## wip-release: automatic PR flow for protected main (Phase 4)
61
+
62
+ When `git push origin main` fails with GitHub's "protected branch" rejection (`GH006: Changes must be made through a pull request`), wip-release now automatically:
63
+
64
+ 1. Creates a release branch `cc-mini/release-v<version>` at the current commit
65
+ 2. Pushes the branch to origin
66
+ 3. Opens a PR via `gh pr create` with title `release: v<version>`
67
+ 4. Merges the PR via `gh pr merge --merge --delete-branch`
68
+ 5. Pushes the tag separately (tags bypass branch protection on most GitHub setups)
69
+ 6. Fast-forwards local main so downstream steps (deploy-public, etc.) have a clean state
70
+
71
+ Previously this was a 4-command manual workflow every release:
72
+
73
+ ```
74
+ git branch cc-mini/release-alpha-N
75
+ git push -u origin cc-mini/release-alpha-N
76
+ gh pr create --base main --head cc-mini/release-alpha-N --title '...'
77
+ gh pr merge <pr> --merge --delete-branch
78
+ git push origin v<version>
79
+ ```
80
+
81
+ Every release. Every time. Eliminated.
82
+
83
+ ## Fallback behavior
84
+
85
+ If any step of the auto-PR flow fails (gh CLI missing, PR create failure, merge failure, tag push failure), wip-release logs a concrete recovery command for the exact failure mode and continues (non-fatal, matches prior push-failed behavior). The user can always complete the remaining steps manually.
86
+
87
+ ## Direct push still works
88
+
89
+ If the repo allows direct push to main (typical for private staging repos), wip-release tries direct push first and only falls back to the PR flow on the specific GH006 / "protected branch" error. No behavioral change for unprotected repos.
90
+
91
+ ## Files changed
92
+
93
+ - `tools/wip-release/core.mjs`: new `pushReleaseWithAutoPr(repoPath, newVersion, level)` and `logPushFailure(result, tag)` helpers. Three push sites in `release()`, `releaseHotfix()`, `releasePrerelease()` migrated to use the helper.
94
+ - `tools/wip-release/package.json`: 1.9.72 -> 1.9.73
95
+ - `CHANGELOG.md`: entry added
96
+
97
+ ## Verified
98
+
99
+ - Module imports cleanly via `node -e "import('./tools/wip-release/core.mjs')"`.
100
+ - Error detection regex handles GH006 variants: `/protected branch|GH006|Changes must be made through a pull request/i`.
101
+ - All three release tracks (stable, prerelease, hotfix) use the same helper.
102
+
103
+ ## Cross-references
104
+
105
+ - `ai/product/bugs/release-pipeline/2026-04-05--cc-mini--release-pipeline-master-plan.md` Phase 4 (Incident 4)
106
+ - `ai/product/bugs/master-plans/bugs-plan-04-05-2026-002.md` Wave 2 phase 7
107
+ - Prior ship: alpha.7 closed Phases 1, 2, 8 of the same plan.
108
+
3
109
  ## 1.9.71-alpha.7 (2026-04-05)
4
110
 
5
111
  # v1.9.71-alpha.7
@@ -0,0 +1,50 @@
1
+ # v1.9.71-alpha.8
2
+
3
+ ## wip-release: automatic PR flow for protected main (Phase 4)
4
+
5
+ When `git push origin main` fails with GitHub's "protected branch" rejection (`GH006: Changes must be made through a pull request`), wip-release now automatically:
6
+
7
+ 1. Creates a release branch `cc-mini/release-v<version>` at the current commit
8
+ 2. Pushes the branch to origin
9
+ 3. Opens a PR via `gh pr create` with title `release: v<version>`
10
+ 4. Merges the PR via `gh pr merge --merge --delete-branch`
11
+ 5. Pushes the tag separately (tags bypass branch protection on most GitHub setups)
12
+ 6. Fast-forwards local main so downstream steps (deploy-public, etc.) have a clean state
13
+
14
+ Previously this was a 4-command manual workflow every release:
15
+
16
+ ```
17
+ git branch cc-mini/release-alpha-N
18
+ git push -u origin cc-mini/release-alpha-N
19
+ gh pr create --base main --head cc-mini/release-alpha-N --title '...'
20
+ gh pr merge <pr> --merge --delete-branch
21
+ git push origin v<version>
22
+ ```
23
+
24
+ Every release. Every time. Eliminated.
25
+
26
+ ## Fallback behavior
27
+
28
+ If any step of the auto-PR flow fails (gh CLI missing, PR create failure, merge failure, tag push failure), wip-release logs a concrete recovery command for the exact failure mode and continues (non-fatal, matches prior push-failed behavior). The user can always complete the remaining steps manually.
29
+
30
+ ## Direct push still works
31
+
32
+ If the repo allows direct push to main (typical for private staging repos), wip-release tries direct push first and only falls back to the PR flow on the specific GH006 / "protected branch" error. No behavioral change for unprotected repos.
33
+
34
+ ## Files changed
35
+
36
+ - `tools/wip-release/core.mjs`: new `pushReleaseWithAutoPr(repoPath, newVersion, level)` and `logPushFailure(result, tag)` helpers. Three push sites in `release()`, `releaseHotfix()`, `releasePrerelease()` migrated to use the helper.
37
+ - `tools/wip-release/package.json`: 1.9.72 -> 1.9.73
38
+ - `CHANGELOG.md`: entry added
39
+
40
+ ## Verified
41
+
42
+ - Module imports cleanly via `node -e "import('./tools/wip-release/core.mjs')"`.
43
+ - Error detection regex handles GH006 variants: `/protected branch|GH006|Changes must be made through a pull request/i`.
44
+ - All three release tracks (stable, prerelease, hotfix) use the same helper.
45
+
46
+ ## Cross-references
47
+
48
+ - `ai/product/bugs/release-pipeline/2026-04-05--cc-mini--release-pipeline-master-plan.md` Phase 4 (Incident 4)
49
+ - `ai/product/bugs/master-plans/bugs-plan-04-05-2026-002.md` Wave 2 phase 7
50
+ - Prior ship: alpha.7 closed Phases 1, 2, 8 of the same plan.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ai-devops-toolbox",
3
- "version": "1.9.71-alpha.7",
3
+ "version": "1.9.71-alpha.9",
4
4
  "type": "module",
5
5
  "description": "The complete AI DevOps toolkit for AI-assisted development teams.",
6
6
  "license": "MIT",
@@ -1507,6 +1507,125 @@ function checkTagCollision(repoPath, newVersion) {
1507
1507
  return { ok: true };
1508
1508
  }
1509
1509
 
1510
+ /**
1511
+ * Push the release commit + tag, handling protected-main rejection via
1512
+ * automatic PR flow.
1513
+ *
1514
+ * If `git push origin main` succeeds directly, we also push tags and return.
1515
+ * If it fails with a "protected branch" error (GH006), create a temporary
1516
+ * release branch from the current HEAD, push the branch, open a PR targeting
1517
+ * main, auto-merge it via `gh pr merge --merge --delete-branch`, then push
1518
+ * the tag separately (tags bypass branch protection).
1519
+ *
1520
+ * Phase 4 of the release-pipeline master plan. Earlier today this entire
1521
+ * flow was done manually every time, adding 2-3 minutes per release.
1522
+ *
1523
+ * Returns `{ ok: true, via }` on success where `via` is `"direct"` or `"pr"`.
1524
+ * Returns `{ ok: false, reason, detail }` on failure; caller logs and
1525
+ * continues (matches prior non-fatal push behavior).
1526
+ */
1527
+ function pushReleaseWithAutoPr(repoPath, newVersion, level) {
1528
+ const tag = `v${newVersion}`;
1529
+ // 1. Try direct push first. Most repos allow it.
1530
+ try {
1531
+ execFileSync('git', ['push'], { cwd: repoPath, stdio: 'pipe' });
1532
+ execFileSync('git', ['push', 'origin', tag], { cwd: repoPath, stdio: 'pipe' });
1533
+ return { ok: true, via: 'direct' };
1534
+ } catch (err) {
1535
+ const msg = String(err?.stderr ?? err?.message ?? err);
1536
+ const isProtected = /protected branch|GH006|Changes must be made through a pull request/i.test(msg);
1537
+ if (!isProtected) {
1538
+ return { ok: false, reason: 'push-failed', detail: msg };
1539
+ }
1540
+ }
1541
+
1542
+ // 2. Protected main. Open auto-PR flow.
1543
+ const releaseBranch = `cc-mini/release-${tag}`;
1544
+ console.log(` - Direct push to main refused (protected). Opening release PR...`);
1545
+ try {
1546
+ // Create branch at current HEAD
1547
+ execFileSync('git', ['branch', releaseBranch], { cwd: repoPath, stdio: 'pipe' });
1548
+ execFileSync('git', ['push', '-u', 'origin', releaseBranch], {
1549
+ cwd: repoPath, stdio: 'pipe'
1550
+ });
1551
+ } catch (err) {
1552
+ return {
1553
+ ok: false,
1554
+ reason: 'branch-push-failed',
1555
+ detail: String(err?.stderr ?? err?.message ?? err),
1556
+ };
1557
+ }
1558
+
1559
+ // 3. Create and merge PR via gh CLI
1560
+ try {
1561
+ const prTitle = `release: ${tag}`;
1562
+ const prBody = `Release commit for ${tag} (${level}). Auto-generated by wip-release.`;
1563
+ execFileSync('gh', [
1564
+ 'pr', 'create',
1565
+ '--base', 'main',
1566
+ '--head', releaseBranch,
1567
+ '--title', prTitle,
1568
+ '--body', prBody,
1569
+ ], { cwd: repoPath, stdio: 'pipe' });
1570
+ execFileSync('gh', [
1571
+ 'pr', 'merge', releaseBranch,
1572
+ '--merge', '--delete-branch',
1573
+ ], { cwd: repoPath, stdio: 'pipe' });
1574
+ console.log(` ✓ Release PR merged`);
1575
+ } catch (err) {
1576
+ return {
1577
+ ok: false,
1578
+ reason: 'gh-pr-failed',
1579
+ detail: String(err?.stderr ?? err?.message ?? err),
1580
+ };
1581
+ }
1582
+
1583
+ // 4. Push the tag separately. Tags bypass branch protection on most
1584
+ // GitHub setups, but if this fails the user can push the tag manually.
1585
+ try {
1586
+ execFileSync('git', ['push', 'origin', tag], { cwd: repoPath, stdio: 'pipe' });
1587
+ console.log(` ✓ Pushed tag ${tag}`);
1588
+ } catch (err) {
1589
+ return {
1590
+ ok: false,
1591
+ reason: 'tag-push-failed',
1592
+ detail: String(err?.stderr ?? err?.message ?? err),
1593
+ partialSuccess: 'pr-merged',
1594
+ };
1595
+ }
1596
+
1597
+ // 5. Pull latest main so local HEAD reflects the merge commit. This
1598
+ // keeps subsequent git operations (like deploy-public) happy.
1599
+ try {
1600
+ execFileSync('git', ['fetch', 'origin', 'main'], { cwd: repoPath, stdio: 'pipe' });
1601
+ execFileSync('git', ['merge', '--ff-only', 'origin/main'], {
1602
+ cwd: repoPath, stdio: 'pipe'
1603
+ });
1604
+ } catch {
1605
+ // Non-fatal: main may have diverged (unlikely here). Deploy-public
1606
+ // will handle its own state.
1607
+ }
1608
+
1609
+ return { ok: true, via: 'pr' };
1610
+ }
1611
+
1612
+ function logPushFailure(result, tag) {
1613
+ console.log(` ! Push failed: ${result.reason}`);
1614
+ if (result.detail) {
1615
+ console.log(` ${result.detail.split('\n')[0]}`);
1616
+ }
1617
+ console.log(` Manual recovery:`);
1618
+ if (result.reason === 'push-failed' || result.reason === 'branch-push-failed') {
1619
+ console.log(` git push && git push origin ${tag}`);
1620
+ } else if (result.reason === 'gh-pr-failed') {
1621
+ console.log(` Open a PR for cc-mini/release-${tag} targeting main, merge, then:`);
1622
+ console.log(` git push origin ${tag}`);
1623
+ } else if (result.reason === 'tag-push-failed') {
1624
+ console.log(` PR already merged. Just push the tag:`);
1625
+ console.log(` git push origin ${tag}`);
1626
+ }
1627
+ }
1628
+
1510
1629
  function logMainBranchGuardFailure(result) {
1511
1630
  if (result.reason === 'linked-worktree') {
1512
1631
  console.log(` \u2717 wip-release must run from the main working tree, not a worktree.`);
@@ -1898,12 +2017,14 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
1898
2017
  gitCommitAndTag(repoPath, newVersion, notes);
1899
2018
  console.log(` ✓ Committed and tagged v${newVersion}`);
1900
2019
 
1901
- // 5. Push commit + tag
1902
- try {
1903
- execSync('git push && git push --tags', { cwd: repoPath, stdio: 'pipe' });
1904
- console.log(` ✓ Pushed to remote`);
1905
- } catch {
1906
- console.log(` ! Push failed (maybe branch protection). Push manually.`);
2020
+ // 5. Push commit + tag (with auto-PR fallback on protected main, Phase 4)
2021
+ {
2022
+ const pushResult = pushReleaseWithAutoPr(repoPath, newVersion, level);
2023
+ if (pushResult.ok) {
2024
+ console.log(` Pushed to remote (${pushResult.via})`);
2025
+ } else {
2026
+ logPushFailure(pushResult, `v${newVersion}`);
2027
+ }
1907
2028
  }
1908
2029
 
1909
2030
  // Distribution results collector (#104)
@@ -2260,12 +2381,14 @@ export async function releasePrerelease({ repoPath, track, notes, dryRun, noPubl
2260
2381
  execFileSync('git', ['tag', `v${newVersion}`], { cwd: repoPath, stdio: 'pipe' });
2261
2382
  console.log(` \u2713 Committed and tagged v${newVersion}`);
2262
2383
 
2263
- // 4. Push commit + tag
2264
- try {
2265
- execSync('git push && git push --tags', { cwd: repoPath, stdio: 'pipe' });
2266
- console.log(` \u2713 Pushed to remote`);
2267
- } catch {
2268
- console.log(` ! Push failed. Push manually.`);
2384
+ // 4. Push commit + tag (with auto-PR fallback on protected main, Phase 4)
2385
+ {
2386
+ const pushResult = pushReleaseWithAutoPr(repoPath, newVersion, track);
2387
+ if (pushResult.ok) {
2388
+ console.log(` \u2713 Pushed to remote (${pushResult.via})`);
2389
+ } else {
2390
+ logPushFailure(pushResult, `v${newVersion}`);
2391
+ }
2269
2392
  }
2270
2393
 
2271
2394
  const distResults = [];
@@ -2470,12 +2593,14 @@ export async function releaseHotfix({ repoPath, notes, notesSource, dryRun, noPu
2470
2593
  gitCommitAndTag(repoPath, newVersion, notes);
2471
2594
  console.log(` \u2713 Committed and tagged v${newVersion}`);
2472
2595
 
2473
- // 5. Push commit + tag
2474
- try {
2475
- execSync('git push && git push --tags', { cwd: repoPath, stdio: 'pipe' });
2476
- console.log(` \u2713 Pushed to remote`);
2477
- } catch {
2478
- console.log(` ! Push failed. Push manually.`);
2596
+ // 5. Push commit + tag (with auto-PR fallback on protected main, Phase 4)
2597
+ {
2598
+ const pushResult = pushReleaseWithAutoPr(repoPath, newVersion, 'hotfix');
2599
+ if (pushResult.ok) {
2600
+ console.log(` \u2713 Pushed to remote (${pushResult.via})`);
2601
+ } else {
2602
+ logPushFailure(pushResult, `v${newVersion}`);
2603
+ }
2479
2604
  }
2480
2605
 
2481
2606
  const distResults = [];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-release",
3
- "version": "1.9.72",
3
+ "version": "1.9.73",
4
4
  "type": "module",
5
5
  "description": "One-command release pipeline. Bumps version, updates changelog + SKILL.md, publishes to npm + GitHub.",
6
6
  "main": "core.mjs",