@wipcomputer/wip-ai-devops-toolbox 1.9.62 → 1.9.64
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 +38 -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/RELEASE-NOTES-v1-9-64.md +23 -0
- package/tools/wip-branch-guard/guard.mjs +1 -0
- package/tools/wip-branch-guard/package.json +4 -1
- package/tools/wip-file-guard/package.json +1 -1
- package/tools/wip-file-guard/test.sh +3 -2
- 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 +19 -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,44 @@
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
## 1.9.64 (2026-03-29)
|
|
36
|
+
|
|
37
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.64
|
|
38
|
+
|
|
39
|
+
Closes #295
|
|
40
|
+
|
|
41
|
+
## Branch guard: allow extension cleanup
|
|
42
|
+
|
|
43
|
+
The branch guard blocked `rm` on deployed extension directories (`~/.openclaw/extensions/` and `~/.ldm/extensions/`) because those paths live inside git repos. But deployed extensions are managed by `ldm install`, not by hand. When a stale `-private` extension needed to be removed (e.g. `wip-xai-grok-private` replaced by the public `wip-xai-grok`), the agent couldn't clean it up without asking the user to run the command manually.
|
|
44
|
+
|
|
45
|
+
Added an allowlist pattern for `rm` targeting `.openclaw/extensions/` and `.ldm/extensions/` paths. Same approach as the existing `.ldm/state/` allowlist. The guard still blocks `rm` on actual repo source files.
|
|
46
|
+
|
|
47
|
+
## 1.9.63 (2026-03-29)
|
|
48
|
+
|
|
49
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.63
|
|
50
|
+
|
|
51
|
+
**Fix all wip-release errors: branch cleanup crashes, shell injection, stale remote refs.**
|
|
52
|
+
|
|
53
|
+
## The story
|
|
54
|
+
|
|
55
|
+
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.
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
## Issues closed
|
|
62
|
+
|
|
63
|
+
- #231 (continued: release pipeline reliability)
|
|
64
|
+
|
|
65
|
+
## How to verify
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
wip-release patch --dry-run
|
|
69
|
+
# Should show no "fatal" or "Not a valid object name" errors
|
|
70
|
+
# Guard tests: cd tools/wip-branch-guard && bash test.sh
|
|
71
|
+
```
|
|
34
72
|
|
|
35
73
|
## 1.9.62 (2026-03-29)
|
|
36
74
|
|
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.64"
|
|
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
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Release Notes: wip-branch-guard v1.9.64
|
|
2
|
+
|
|
3
|
+
**One-line summary of what this release does**
|
|
4
|
+
|
|
5
|
+
Tell the story. What was broken or missing? What did we build? Why does the user care?
|
|
6
|
+
Write at least one real paragraph of prose. Not just bullets. The release notes gate
|
|
7
|
+
will block if there is no narrative. Bullets are fine for details, but the story comes first.
|
|
8
|
+
|
|
9
|
+
## The story
|
|
10
|
+
|
|
11
|
+
(Write a paragraph here. What was the problem? What does this release fix? Why does it matter?
|
|
12
|
+
This is what users read. Make it worth reading.)
|
|
13
|
+
|
|
14
|
+
## Issues closed
|
|
15
|
+
|
|
16
|
+
- #296
|
|
17
|
+
- #295
|
|
18
|
+
|
|
19
|
+
## How to verify
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Commands to test the changes
|
|
23
|
+
```
|
|
@@ -133,6 +133,7 @@ const ALLOWED_BASH_PATTERNS = [
|
|
|
133
133
|
/\bnpm\s+link\b/, // global operation, not repo-local
|
|
134
134
|
/\bldm\s+(install|init|doctor|stack|updates)\b/, // LDM OS commands modify ~/.ldm/, not the repo
|
|
135
135
|
/\brm\s+.*\.ldm\/state\//, // cleaning LDM state files only, not repo files
|
|
136
|
+
/\brm\s+.*\.(openclaw|ldm)\/extensions\//, // cleaning deployed extensions (managed by ldm install, not source code)
|
|
136
137
|
/\bclaude\s+mcp\b/, // MCP registration, not repo files
|
|
137
138
|
/\bmkdir\s+.*\.worktrees\b/, // creating .worktrees/ directory is part of the process
|
|
138
139
|
];
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/wip-branch-guard",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.64",
|
|
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
|
+
"scripts": {
|
|
7
|
+
"test": "bash test.sh"
|
|
8
|
+
},
|
|
6
9
|
"main": "guard.mjs",
|
|
7
10
|
"bin": {
|
|
8
11
|
"wip-branch-guard": "guard.mjs"
|
|
@@ -83,9 +83,10 @@ check "Block Write to TOOLS.md" \
|
|
|
83
83
|
"block"
|
|
84
84
|
|
|
85
85
|
# Large replacement (same line count, different content)
|
|
86
|
-
|
|
86
|
+
# SHARED-CONTEXT.md is shared state (maxReplace=30), so 8 lines is allowed
|
|
87
|
+
check "Allow Edit replacing 8 lines with 8 different lines in SHARED-CONTEXT.md (shared state)" \
|
|
87
88
|
'{"tool_name":"Edit","tool_input":{"file_path":"/foo/SHARED-CONTEXT.md","old_string":"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8","new_string":"new1\nnew2\nnew3\nnew4\nnew5\nnew6\nnew7\nnew8"}}' \
|
|
88
|
-
"
|
|
89
|
+
"allow"
|
|
89
90
|
|
|
90
91
|
check "Allow Edit replacing 3 lines in CLAUDE.md" \
|
|
91
92
|
'{"tool_name":"Edit","tool_input":{"file_path":"/foo/CLAUDE.md","old_string":"a\nb\nc","new_string":"x\ny\nz"}}' \
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { execSync, execFileSync } from 'node:child_process';
|
|
9
9
|
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, renameSync } from 'node:fs';
|
|
10
|
-
import { join, basename } from 'node:path';
|
|
10
|
+
import { join, basename, dirname } from 'node:path';
|
|
11
11
|
|
|
12
12
|
// ── Version ─────────────────────────────────────────────────────────
|
|
13
13
|
|
|
@@ -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
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
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 =
|
|
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
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
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
|
-
|
|
1639
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1666
|
-
|
|
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/universal-installer",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.64",
|
|
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",
|