@wipcomputer/wip-ai-devops-toolbox 1.9.62 → 1.9.63

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
@@ -31,6 +31,32 @@
31
31
 
32
32
 
33
33
 
34
+
35
+ ## 1.9.63 (2026-03-29)
36
+
37
+ # Release Notes: wip-ai-devops-toolbox v1.9.63
38
+
39
+ **Fix all wip-release errors: branch cleanup crashes, shell injection, stale remote refs.**
40
+
41
+ ## The story
42
+
43
+ Every wip-release run produced errors: "fatal: Not a valid object name +", "remote ref does not exist", and shell injection risks from branch names passed through execSync template strings. These were dismissed as "non-blocking" but they cluttered every release output and masked real problems.
44
+
45
+ Root cause: branch cleanup code (sections 10 and 11) used `execSync` with template strings, which breaks on branch names with special characters and allows shell injection. Also tried to delete remote branches that GitHub already deleted during PR merge.
46
+
47
+ Fix: replaced all `execSync` template strings with `execFileSync` array args (safe from injection). Added character validation to skip branches with special chars. Wrapped remote delete in try/catch since GitHub PR merge already handles deletion.
48
+
49
+ ## Issues closed
50
+
51
+ - #231 (continued: release pipeline reliability)
52
+
53
+ ## How to verify
54
+
55
+ ```bash
56
+ wip-release patch --dry-run
57
+ # Should show no "fatal" or "Not a valid object name" errors
58
+ # Guard tests: cd tools/wip-branch-guard && bash test.sh
59
+ ```
34
60
 
35
61
  ## 1.9.62 (2026-03-29)
36
62
 
package/SKILL.md CHANGED
@@ -5,7 +5,7 @@ license: MIT
5
5
  interface: [cli, module, mcp, skill, hook, plugin]
6
6
  metadata:
7
7
  display-name: "WIP AI DevOps Toolbox"
8
- version: "1.9.62"
8
+ version: "1.9.63"
9
9
  homepage: "https://github.com/wipcomputer/wip-ai-devops-toolbox"
10
10
  author: "Parker Todd Brooks"
11
11
  category: dev-tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ai-devops-toolbox",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "type": "module",
5
5
  "description": "The complete AI DevOps toolkit for AI-assisted development teams.",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/deploy-public",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "description": "Private-to-public repo sync. Excludes ai/ folder, creates PR, merges, cleans up branches.",
5
5
  "bin": {
6
6
  "deploy-public": "./deploy-public.sh"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/post-merge-rename",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "description": "Post-merge branch renaming. Appends --merged-YYYY-MM-DD to preserve history.",
5
5
  "bin": {
6
6
  "post-merge-rename": "./post-merge-rename.sh"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-branch-guard",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "description": "PreToolUse hook that blocks all writes on main branch. Forces agents to work on branches or worktrees.",
5
5
  "type": "module",
6
6
  "main": "guard.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-file-guard",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "type": "module",
5
5
  "description": "Hook that blocks destructive edits to protected identity files. For Claude Code CLI and OpenClaw.",
6
6
  "main": "guard.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-license-guard",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "description": "License compliance for your own repos. Ensures correct copyright, dual-license blocks, and LICENSE files.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-license-hook",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "description": "License rug-pull detection and dependency license compliance for open source projects",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-readme-format",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "description": "Reformat any repo's README to follow the WIP Computer standard. Agent-first, human-readable.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1569,31 +1569,33 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
1569
1569
  .filter(b => b && b !== 'main' && b !== 'master' && !b.startsWith('*') && !b.includes('--merged-'));
1570
1570
 
1571
1571
  if (merged.length > 0) {
1572
+ const current = execSync('git branch --show-current', { cwd: repoPath, encoding: 'utf8' }).trim();
1572
1573
  console.log(` Scanning ${merged.length} merged branch(es) for rename...`);
1573
1574
  for (const branch of merged) {
1574
- const current = execSync('git branch --show-current', { cwd: repoPath, encoding: 'utf8' }).trim();
1575
1575
  if (branch === current) continue;
1576
+ // Skip branches with characters that break git commands
1577
+ if (/[+\s~^:?*\[\]]/.test(branch)) continue;
1576
1578
 
1577
1579
  let mergeDate;
1578
1580
  try {
1579
- const mergeBase = execSync(`git merge-base main ${branch}`, { cwd: repoPath, encoding: 'utf8' }).trim();
1580
- mergeDate = execSync(
1581
- `git log main --format="%ai" --ancestry-path ${mergeBase}..main`,
1582
- { cwd: repoPath, encoding: 'utf8' }
1583
- ).trim().split('\n').pop().split(' ')[0];
1581
+ // Use execFileSync (array args) instead of execSync (shell string) to avoid injection
1582
+ const mergeBase = execFileSync('git', ['merge-base', 'main', branch], { cwd: repoPath, encoding: 'utf8' }).trim();
1583
+ const logOutput = execFileSync('git', ['log', 'main', '--format=%ai', '--ancestry-path', `${mergeBase}..main`], { cwd: repoPath, encoding: 'utf8' }).trim();
1584
+ if (logOutput) mergeDate = logOutput.split('\n').pop().split(' ')[0];
1584
1585
  } catch {}
1585
1586
  if (!mergeDate) {
1586
1587
  try {
1587
- mergeDate = execSync(`git log ${branch} -1 --format="%ai"`, { cwd: repoPath, encoding: 'utf8' }).trim().split(' ')[0];
1588
+ mergeDate = execFileSync('git', ['log', branch, '-1', '--format=%ai'], { cwd: repoPath, encoding: 'utf8' }).trim().split(' ')[0];
1588
1589
  } catch {}
1589
1590
  }
1590
1591
  if (!mergeDate) continue;
1591
1592
 
1592
1593
  const newName = `${branch}--merged-${mergeDate}`;
1593
1594
  try {
1594
- execSync(`git branch -m "${branch}" "${newName}"`, { cwd: repoPath, stdio: 'pipe' });
1595
- execSync(`git push origin "${newName}"`, { cwd: repoPath, stdio: 'pipe' });
1596
- execSync(`git push origin --delete "${branch}"`, { cwd: repoPath, stdio: 'pipe' });
1595
+ execFileSync('git', ['branch', '-m', branch, newName], { cwd: repoPath, stdio: 'pipe' });
1596
+ execFileSync('git', ['push', 'origin', newName], { cwd: repoPath, stdio: 'pipe' });
1597
+ // Remote branch may already be deleted by GitHub PR merge. That's fine.
1598
+ try { execFileSync('git', ['push', 'origin', '--delete', branch], { cwd: repoPath, stdio: 'pipe' }); } catch {}
1597
1599
  console.log(` ✓ Renamed: ${branch} -> ${newName}`);
1598
1600
  } catch (e) {
1599
1601
  console.log(` ! Could not rename ${branch}: ${e.message}`);
@@ -1635,8 +1637,8 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
1635
1637
 
1636
1638
  for (let i = KEEP_COUNT; i < branches.length; i++) {
1637
1639
  try {
1638
- execSync(`git push origin --delete "${branches[i]}"`, { cwd: repoPath, stdio: 'pipe' });
1639
- execSync(`git branch -d "${branches[i]}" 2>/dev/null || true`, { cwd: repoPath, stdio: 'pipe', shell: true });
1640
+ execFileSync('git', ['push', 'origin', '--delete', branches[i]], { cwd: repoPath, stdio: 'pipe' });
1641
+ try { execFileSync('git', ['branch', '-d', branches[i]], { cwd: repoPath, stdio: 'pipe' }); } catch {}
1640
1642
  pruned++;
1641
1643
  } catch {}
1642
1644
  }
@@ -1659,11 +1661,12 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
1659
1661
  let staleCleaned = 0;
1660
1662
  for (const branch of allRemote) {
1661
1663
  if (branch === current) continue;
1664
+ if (/[+\s~^:?*\[\]]/.test(branch)) continue;
1662
1665
  try {
1663
- execSync(`git merge-base --is-ancestor origin/${branch} origin/main`, { cwd: repoPath, stdio: 'pipe' });
1666
+ execFileSync('git', ['merge-base', '--is-ancestor', `origin/${branch}`, 'origin/main'], { cwd: repoPath, stdio: 'pipe' });
1664
1667
  // If we get here, branch is fully merged
1665
- execSync(`git push origin --delete "${branch}"`, { cwd: repoPath, stdio: 'pipe' });
1666
- execSync(`git branch -d "${branch}" 2>/dev/null || true`, { cwd: repoPath, stdio: 'pipe', shell: true });
1668
+ try { execFileSync('git', ['push', 'origin', '--delete', branch], { cwd: repoPath, stdio: 'pipe' }); } catch {}
1669
+ try { execFileSync('git', ['branch', '-d', branch], { cwd: repoPath, stdio: 'pipe' }); } catch {}
1667
1670
  staleCleaned++;
1668
1671
  } catch {}
1669
1672
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-release",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-repo-init",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "description": "Scaffold the standard ai/ directory structure in any repo",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-repo-permissions-hook",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "type": "module",
5
5
  "description": "Repo visibility guard. Blocks repos from going public without a -private counterpart.",
6
6
  "main": "core.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-repos",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "type": "module",
5
5
  "description": "Repo manifest reconciler. Single source of truth for repo organization. Like prettier for folder structure.",
6
6
  "main": "core.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/universal-installer",
3
- "version": "1.9.62",
3
+ "version": "1.9.63",
4
4
  "type": "module",
5
5
  "description": "The Universal Interface specification for agent-native software. Teaches your AI how to build repos with every interface: CLI, Module, MCP Server, OpenClaw Plugin, Skill, Claude Code Hook.",
6
6
  "main": "detect.mjs",