@wipcomputer/wip-ai-devops-toolbox 1.9.61 → 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 +50 -0
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/tools/deploy-public/package.json +1 -1
- package/tools/post-merge-rename/package.json +1 -1
- package/tools/wip-branch-guard/package.json +1 -1
- package/tools/wip-file-guard/package.json +1 -1
- package/tools/wip-license-guard/package.json +1 -1
- package/tools/wip-license-hook/package.json +1 -1
- package/tools/wip-readme-format/package.json +1 -1
- package/tools/wip-release/core.mjs +44 -16
- package/tools/wip-release/package.json +1 -1
- package/tools/wip-repo-init/package.json +1 -1
- package/tools/wip-repo-permissions-hook/package.json +1 -1
- package/tools/wip-repos/package.json +1 -1
- package/tools/wip-universal-installer/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,56 @@
|
|
|
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
|
+
```
|
|
60
|
+
|
|
61
|
+
## 1.9.62 (2026-03-29)
|
|
62
|
+
|
|
63
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.62
|
|
64
|
+
|
|
65
|
+
**Fix wip-release leaving dirty state on main after every release.**
|
|
66
|
+
|
|
67
|
+
## The story
|
|
68
|
+
|
|
69
|
+
wip-release writes to 15+ files during a release (root package.json, 12 sub-tool package.json files, SKILL.md, CHANGELOG.md, product docs, trashed release notes). But gitCommitAndTag() only staged 3 files (package.json, CHANGELOG.md, SKILL.md). The other 12+ files were left modified on disk, uncommitted. This blocked git pull on the next operation and required manual `git checkout -- .` every time.
|
|
70
|
+
|
|
71
|
+
Fix: stage all files that wip-release modifies. Sub-tool package.json files, product docs (ai/product/), and trashed release notes (_trash/) are now included in the release commit.
|
|
72
|
+
|
|
73
|
+
## Issues closed
|
|
74
|
+
|
|
75
|
+
- #231 (wip-release rollback version bumps on failure)
|
|
76
|
+
|
|
77
|
+
## How to verify
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
wip-release patch
|
|
81
|
+
git status
|
|
82
|
+
# Should show clean working tree after release
|
|
83
|
+
```
|
|
34
84
|
|
|
35
85
|
## 1.9.61 (2026-03-29)
|
|
36
86
|
|
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.
|
|
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
|
@@ -149,12 +149,37 @@ function trashReleaseNotes(repoPath) {
|
|
|
149
149
|
|
|
150
150
|
function gitCommitAndTag(repoPath, newVersion, notes) {
|
|
151
151
|
const msg = `v${newVersion}: ${notes || 'Release'}`;
|
|
152
|
-
// Stage
|
|
152
|
+
// Stage ALL files that wip-release modifies:
|
|
153
|
+
// - Root: package.json, CHANGELOG.md, SKILL.md
|
|
154
|
+
// - Sub-tools: tools/*/package.json
|
|
155
|
+
// - Product docs: ai/product/plans-prds/roadmap.md, ai/product/readme-first-product.md
|
|
156
|
+
// - Trashed release notes: _trash/RELEASE-NOTES-*.md
|
|
157
|
+
// Using git add -A on specific paths instead of listing each file (#231)
|
|
153
158
|
for (const f of ['package.json', 'CHANGELOG.md', 'SKILL.md']) {
|
|
154
159
|
if (existsSync(join(repoPath, f))) {
|
|
155
160
|
execFileSync('git', ['add', f], { cwd: repoPath, stdio: 'pipe' });
|
|
156
161
|
}
|
|
157
162
|
}
|
|
163
|
+
// Stage sub-tool package.json files
|
|
164
|
+
const toolsDir = join(repoPath, 'tools');
|
|
165
|
+
if (existsSync(toolsDir)) {
|
|
166
|
+
for (const sub of readdirSync(toolsDir, { withFileTypes: true })) {
|
|
167
|
+
if (!sub.isDirectory()) continue;
|
|
168
|
+
const subPkg = join('tools', sub.name, 'package.json');
|
|
169
|
+
if (existsSync(join(repoPath, subPkg))) {
|
|
170
|
+
execFileSync('git', ['add', subPkg], { cwd: repoPath, stdio: 'pipe' });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Stage product docs and trashed release notes
|
|
175
|
+
const aiProduct = join(repoPath, 'ai', 'product');
|
|
176
|
+
if (existsSync(aiProduct)) {
|
|
177
|
+
execFileSync('git', ['add', 'ai/product/'], { cwd: repoPath, stdio: 'pipe' });
|
|
178
|
+
}
|
|
179
|
+
const trash = join(repoPath, '_trash');
|
|
180
|
+
if (existsSync(trash)) {
|
|
181
|
+
execFileSync('git', ['add', '_trash/'], { cwd: repoPath, stdio: 'pipe' });
|
|
182
|
+
}
|
|
158
183
|
// Use execFileSync to avoid shell injection via notes.
|
|
159
184
|
// --no-verify: wip-release legitimately commits on main (version bump + changelog).
|
|
160
185
|
// The pre-commit hook blocks all commits on main, but wip-release is the one exception.
|
|
@@ -1544,31 +1569,33 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1544
1569
|
.filter(b => b && b !== 'main' && b !== 'master' && !b.startsWith('*') && !b.includes('--merged-'));
|
|
1545
1570
|
|
|
1546
1571
|
if (merged.length > 0) {
|
|
1572
|
+
const current = execSync('git branch --show-current', { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
1547
1573
|
console.log(` Scanning ${merged.length} merged branch(es) for rename...`);
|
|
1548
1574
|
for (const branch of merged) {
|
|
1549
|
-
const current = execSync('git branch --show-current', { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
1550
1575
|
if (branch === current) continue;
|
|
1576
|
+
// Skip branches with characters that break git commands
|
|
1577
|
+
if (/[+\s~^:?*\[\]]/.test(branch)) continue;
|
|
1551
1578
|
|
|
1552
1579
|
let mergeDate;
|
|
1553
1580
|
try {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
).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];
|
|
1559
1585
|
} catch {}
|
|
1560
1586
|
if (!mergeDate) {
|
|
1561
1587
|
try {
|
|
1562
|
-
mergeDate =
|
|
1588
|
+
mergeDate = execFileSync('git', ['log', branch, '-1', '--format=%ai'], { cwd: repoPath, encoding: 'utf8' }).trim().split(' ')[0];
|
|
1563
1589
|
} catch {}
|
|
1564
1590
|
}
|
|
1565
1591
|
if (!mergeDate) continue;
|
|
1566
1592
|
|
|
1567
1593
|
const newName = `${branch}--merged-${mergeDate}`;
|
|
1568
1594
|
try {
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
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 {}
|
|
1572
1599
|
console.log(` ✓ Renamed: ${branch} -> ${newName}`);
|
|
1573
1600
|
} catch (e) {
|
|
1574
1601
|
console.log(` ! Could not rename ${branch}: ${e.message}`);
|
|
@@ -1610,8 +1637,8 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1610
1637
|
|
|
1611
1638
|
for (let i = KEEP_COUNT; i < branches.length; i++) {
|
|
1612
1639
|
try {
|
|
1613
|
-
|
|
1614
|
-
|
|
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 {}
|
|
1615
1642
|
pruned++;
|
|
1616
1643
|
} catch {}
|
|
1617
1644
|
}
|
|
@@ -1634,11 +1661,12 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1634
1661
|
let staleCleaned = 0;
|
|
1635
1662
|
for (const branch of allRemote) {
|
|
1636
1663
|
if (branch === current) continue;
|
|
1664
|
+
if (/[+\s~^:?*\[\]]/.test(branch)) continue;
|
|
1637
1665
|
try {
|
|
1638
|
-
|
|
1666
|
+
execFileSync('git', ['merge-base', '--is-ancestor', `origin/${branch}`, 'origin/main'], { cwd: repoPath, stdio: 'pipe' });
|
|
1639
1667
|
// If we get here, branch is fully merged
|
|
1640
|
-
|
|
1641
|
-
|
|
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 {}
|
|
1642
1670
|
staleCleaned++;
|
|
1643
1671
|
} catch {}
|
|
1644
1672
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/universal-installer",
|
|
3
|
-
"version": "1.9.
|
|
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",
|