@wipcomputer/wip-ai-devops-toolbox 1.9.60 → 1.9.62
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 +47 -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/guard.mjs +1 -1
- package/tools/wip-branch-guard/package.json +1 -1
- package/tools/wip-branch-guard/test.sh +117 -0
- 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 +64 -1
- 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,53 @@
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
## 1.9.62 (2026-03-29)
|
|
36
|
+
|
|
37
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.62
|
|
38
|
+
|
|
39
|
+
**Fix wip-release leaving dirty state on main after every release.**
|
|
40
|
+
|
|
41
|
+
## The story
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
## Issues closed
|
|
48
|
+
|
|
49
|
+
- #231 (wip-release rollback version bumps on failure)
|
|
50
|
+
|
|
51
|
+
## How to verify
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
wip-release patch
|
|
55
|
+
git status
|
|
56
|
+
# Should show clean working tree after release
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 1.9.61 (2026-03-29)
|
|
60
|
+
|
|
61
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.61
|
|
62
|
+
|
|
63
|
+
**Add test script for branch guard. Fix node bypass regex.**
|
|
64
|
+
|
|
65
|
+
## The story
|
|
66
|
+
|
|
67
|
+
Every guard bug this session (v1.9.56-59) would have been caught by running a test before merging. This release adds test.sh to the guard that pipes test JSON into guard.mjs and verifies allow/deny results. 30 test cases covering destructive commands, quoted strings (Bug 1/3), compound commands (Bug 2), safe commands, and plan files.
|
|
68
|
+
|
|
69
|
+
Also fixes the node bypass regex: `require('fs').writeFileSync` wasn't caught because the regex looked for `fs.writeFile` literally. Broadened to match `writeFile` after `node -e`.
|
|
70
|
+
|
|
71
|
+
## Issues closed
|
|
72
|
+
|
|
73
|
+
- #232 (guard test coverage)
|
|
74
|
+
|
|
75
|
+
## How to verify
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
cd tools/wip-branch-guard && bash test.sh
|
|
79
|
+
# Should show: 30 passed, 0 failed, 3 skipped
|
|
80
|
+
```
|
|
34
81
|
|
|
35
82
|
## 1.9.60 (2026-03-29)
|
|
36
83
|
|
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.62"
|
|
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
|
@@ -50,7 +50,7 @@ const DESTRUCTIVE_PATTERNS = [
|
|
|
50
50
|
// the attack IS inside quotes (e.g. python -c "open('f').write('x')").
|
|
51
51
|
const DESTRUCTIVE_CODE_PATTERNS = [
|
|
52
52
|
/\bpython3?\s+-c\s+.*\bopen\s*\(/, // python -c "open().write()" bypass (#241)
|
|
53
|
-
/\bnode\s+-e\s+.*\
|
|
53
|
+
/\bnode\s+-e\s+.*\bwriteFile/, // node -e "require('fs').writeFile()" or "fs.writeFile()" bypass
|
|
54
54
|
];
|
|
55
55
|
|
|
56
56
|
// Strip quoted string contents to prevent regex matching inside data.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Guard test runner. Run from the guard directory before merging.
|
|
3
|
+
# Usage: bash test.sh
|
|
4
|
+
#
|
|
5
|
+
# Pipes test JSON into guard.mjs and checks if it allows or blocks.
|
|
6
|
+
# Exit 0 = all tests pass. Exit 1 = at least one failed.
|
|
7
|
+
#
|
|
8
|
+
# Note: compound command tests (Bug 2) and on-main tests need CWD
|
|
9
|
+
# to be in a repo on main. The script auto-detects and skips those
|
|
10
|
+
# tests if running from a branch/worktree.
|
|
11
|
+
|
|
12
|
+
GUARD="$(dirname "$0")/guard.mjs"
|
|
13
|
+
PASS=0
|
|
14
|
+
FAIL=0
|
|
15
|
+
SKIP=0
|
|
16
|
+
|
|
17
|
+
# Check if we're on main (for tests that need main-branch context)
|
|
18
|
+
ON_MAIN=false
|
|
19
|
+
BRANCH=$(git branch --show-current 2>/dev/null)
|
|
20
|
+
if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
|
|
21
|
+
ON_MAIN=true
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Helper: build properly escaped JSON using node (avoids bash quote hell)
|
|
25
|
+
make_json() {
|
|
26
|
+
local tool="$1" key="$2" value="$3"
|
|
27
|
+
node -e "process.stdout.write(JSON.stringify({tool_name:'$tool',tool_input:{$key:process.argv[1]}}))" "$value"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Helper: run a test case
|
|
31
|
+
# Args: description, expected (allow|deny), tool_name, command_or_filepath, [main_only]
|
|
32
|
+
test_case() {
|
|
33
|
+
local desc="$1" expected="$2" tool="$3" input="$4" main_only="${5:-false}"
|
|
34
|
+
|
|
35
|
+
if [[ "$main_only" == "true" && "$ON_MAIN" == "false" ]]; then
|
|
36
|
+
echo " SKIP: $desc (needs main branch)"
|
|
37
|
+
((SKIP++))
|
|
38
|
+
return
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
local json
|
|
42
|
+
if [[ "$tool" == "Bash" ]]; then
|
|
43
|
+
json=$(make_json Bash command "$input")
|
|
44
|
+
elif [[ "$tool" == "Write" || "$tool" == "Edit" ]]; then
|
|
45
|
+
json=$(make_json "$tool" file_path "$input")
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
local output
|
|
49
|
+
output=$(echo "$json" | node "$GUARD" 2>/dev/null)
|
|
50
|
+
|
|
51
|
+
local actual="allow"
|
|
52
|
+
if echo "$output" | grep -q '"deny"' 2>/dev/null; then
|
|
53
|
+
actual="deny"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [[ "$actual" == "$expected" ]]; then
|
|
57
|
+
echo " PASS: $desc"
|
|
58
|
+
((PASS++))
|
|
59
|
+
else
|
|
60
|
+
echo " FAIL: $desc (expected $expected, got $actual)"
|
|
61
|
+
((FAIL++))
|
|
62
|
+
fi
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
echo "=== Branch Guard Tests ==="
|
|
66
|
+
echo ""
|
|
67
|
+
echo "--- Destructive commands (should DENY on any branch) ---"
|
|
68
|
+
test_case "git clean -fd" deny Bash "git clean -fd"
|
|
69
|
+
test_case "git checkout -- file.txt" deny Bash "git checkout -- file.txt"
|
|
70
|
+
test_case "git checkout ." deny Bash "git checkout ."
|
|
71
|
+
test_case "git stash drop" deny Bash "git stash drop"
|
|
72
|
+
test_case "git stash pop" deny Bash "git stash pop"
|
|
73
|
+
test_case "git stash clear" deny Bash "git stash clear"
|
|
74
|
+
test_case "git reset --hard" deny Bash "git reset --hard"
|
|
75
|
+
test_case "git restore file.txt" deny Bash "git restore file.txt"
|
|
76
|
+
test_case "python file write bypass" deny Bash "python3 -c \"open('f','w').write('x')\""
|
|
77
|
+
test_case "node file write bypass" deny Bash "node -e \"require('fs').writeFileSync('f','d')\""
|
|
78
|
+
|
|
79
|
+
echo ""
|
|
80
|
+
echo "--- Quoted strings: should NOT match inside quotes (Bug 1, 3) ---"
|
|
81
|
+
test_case "gh issue with git checkout in body" allow Bash "gh issue create --body 'use git checkout -- to fix'"
|
|
82
|
+
test_case "echo with git commit in quotes" allow Bash "echo 'dont run git commit on main'"
|
|
83
|
+
test_case "gh issue with git reset in body" allow Bash "gh issue create --body 'tried git reset --hard'"
|
|
84
|
+
|
|
85
|
+
echo ""
|
|
86
|
+
echo "--- Compound commands: each segment checked independently (Bug 2) ---"
|
|
87
|
+
echo " (These only run when CWD is on main branch)"
|
|
88
|
+
test_case "rm with echo should still block" deny Bash "rm -f file ; echo done" true
|
|
89
|
+
test_case "safe compound (ls && echo)" allow Bash "ls -la && echo done" true
|
|
90
|
+
test_case "cd then rm should block" deny Bash "cd /tmp && rm -rf somedir" true
|
|
91
|
+
|
|
92
|
+
echo ""
|
|
93
|
+
echo "--- Safe commands (should ALLOW) ---"
|
|
94
|
+
test_case "git status" allow Bash "git status"
|
|
95
|
+
test_case "git log" allow Bash "git log --oneline -5"
|
|
96
|
+
test_case "git diff" allow Bash "git diff"
|
|
97
|
+
test_case "git checkout branch" allow Bash "git checkout feature-branch"
|
|
98
|
+
test_case "git worktree add" allow Bash "git worktree add .worktrees/repo--branch -b feat"
|
|
99
|
+
test_case "git stash list" allow Bash "git stash list"
|
|
100
|
+
test_case "git stash show" allow Bash "git stash show"
|
|
101
|
+
test_case "git restore --staged" allow Bash "git restore --staged file.txt"
|
|
102
|
+
test_case "ls command" allow Bash "ls -la"
|
|
103
|
+
test_case "grep command" allow Bash "grep -r pattern ."
|
|
104
|
+
test_case "gh pr create" allow Bash "gh pr create --title test"
|
|
105
|
+
test_case "gh pr merge" allow Bash "gh pr merge 123 --merge"
|
|
106
|
+
test_case "echo" allow Bash "echo hello"
|
|
107
|
+
test_case "wip-release dry-run" allow Bash "wip-release patch --dry-run"
|
|
108
|
+
test_case "ldm install" allow Bash "ldm install"
|
|
109
|
+
test_case "mkdir .worktrees" allow Bash "mkdir -p .worktrees/repo--branch"
|
|
110
|
+
|
|
111
|
+
echo ""
|
|
112
|
+
echo "--- Plan files (should ALLOW Write/Edit) ---"
|
|
113
|
+
test_case "Edit plan file" allow Edit "/Users/lesa/.claude/plans/my-plan.md"
|
|
114
|
+
|
|
115
|
+
echo ""
|
|
116
|
+
echo "=== Results: $PASS passed, $FAIL failed, $SKIP skipped ==="
|
|
117
|
+
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
|
@@ -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.
|
|
@@ -1235,6 +1260,44 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1235
1260
|
}
|
|
1236
1261
|
}
|
|
1237
1262
|
|
|
1263
|
+
// 0.95. Run test scripts (if any exist)
|
|
1264
|
+
{
|
|
1265
|
+
const toolsDir = join(repoPath, 'tools');
|
|
1266
|
+
const testFiles = [];
|
|
1267
|
+
if (existsSync(toolsDir)) {
|
|
1268
|
+
for (const sub of readdirSync(toolsDir)) {
|
|
1269
|
+
const testPath = join(toolsDir, sub, 'test.sh');
|
|
1270
|
+
if (existsSync(testPath)) testFiles.push({ tool: sub, path: testPath });
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
// Also check repo root test.sh
|
|
1274
|
+
const rootTest = join(repoPath, 'test.sh');
|
|
1275
|
+
if (existsSync(rootTest)) testFiles.push({ tool: '(root)', path: rootTest });
|
|
1276
|
+
|
|
1277
|
+
if (testFiles.length > 0) {
|
|
1278
|
+
let allPassed = true;
|
|
1279
|
+
for (const { tool, path } of testFiles) {
|
|
1280
|
+
try {
|
|
1281
|
+
execFileSync('bash', [path], { cwd: dirname(path), stdio: 'pipe', timeout: 30000 });
|
|
1282
|
+
console.log(` ✓ Tests passed: ${tool}`);
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
allPassed = false;
|
|
1285
|
+
console.log(` ✗ Tests FAILED: ${tool}`);
|
|
1286
|
+
const output = (e.stdout || '').toString().trim();
|
|
1287
|
+
if (output) {
|
|
1288
|
+
for (const line of output.split('\n').slice(-5)) console.log(` ${line}`);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (!allPassed) {
|
|
1293
|
+
console.log('');
|
|
1294
|
+
console.log(' Fix failing tests before releasing.');
|
|
1295
|
+
console.log('');
|
|
1296
|
+
return { currentVersion, newVersion, dryRun: false, failed: true };
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1238
1301
|
if (dryRun) {
|
|
1239
1302
|
// Product docs check (dry-run)
|
|
1240
1303
|
if (!skipProductCheck) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/universal-installer",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.62",
|
|
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",
|