@wipcomputer/wip-ai-devops-toolbox 1.9.69-alpha.1 → 1.9.71-alpha.1
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 +11 -0
- package/package.json +1 -1
- package/tools/deploy-public/deploy-public.sh +2 -1
- package/tools/wip-branch-guard/package.json +1 -1
- package/tools/wip-file-guard/guard.mjs +14 -2
- package/tools/wip-file-guard/package.json +1 -1
- package/tools/wip-file-guard/test.sh +22 -0
- package/tools/wip-release/core.mjs +75 -33
- package/tools/wip-release/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.71-alpha.1 (2026-04-01)
|
|
3
4
|
|
|
5
|
+
File guard: allow harness memory writes, guard v1.9.69
|
|
4
6
|
|
|
7
|
+
## 1.9.70 (2026-04-01)
|
|
8
|
+
|
|
9
|
+
### wip-release
|
|
10
|
+
- Fix semver NaN bug: `semver.inc()` returns null on prerelease versions, causing NaN propagation through the entire release pipeline
|
|
11
|
+
- Add sub-tool independent version validation: detects when sub-tool package.json versions drift from root and warns before publish
|
|
12
|
+
|
|
13
|
+
### wip-branch-guard
|
|
14
|
+
- Add cooldown skip: avoids redundant guard checks within rapid tool sequences
|
|
15
|
+
- Add trash pattern exclusion: allows `.Trash` and system cleanup paths through the guard
|
|
5
16
|
|
|
6
17
|
|
|
7
18
|
|
package/package.json
CHANGED
|
@@ -99,6 +99,7 @@ rsync -a \
|
|
|
99
99
|
--exclude='.git/' \
|
|
100
100
|
--exclude='.DS_Store' \
|
|
101
101
|
--exclude='.wrangler/' \
|
|
102
|
+
--exclude='.worktrees/' \
|
|
102
103
|
--exclude='.claude/' \
|
|
103
104
|
--exclude='CLAUDE.md' \
|
|
104
105
|
"$PRIVATE_REPO/" "$TMPDIR/public/"
|
|
@@ -123,7 +124,7 @@ fi
|
|
|
123
124
|
BRANCH="$HARNESS_ID/deploy-$(date +%Y%m%d-%H%M%S)"
|
|
124
125
|
|
|
125
126
|
git add -A
|
|
126
|
-
git commit -m "$COMMIT_MSG (from $COMMIT_HASH)"
|
|
127
|
+
git commit --no-verify -m "$COMMIT_MSG (from $COMMIT_HASH)"
|
|
127
128
|
|
|
128
129
|
if [[ "$EMPTY_REPO" == "true" ]]; then
|
|
129
130
|
# Empty repo: push directly to main (no base branch to PR against)
|
|
@@ -48,6 +48,8 @@ const SHARED_STATE_PATHS = [
|
|
|
48
48
|
/\.ldm\/agents\/.*\/memory\/daily\/.*\.md$/,
|
|
49
49
|
/\.ldm\/memory\/daily\/.*\.md$/,
|
|
50
50
|
/\.ldm\/memory\/shared-log\.jsonl$/,
|
|
51
|
+
/\.claude\/projects\/.*\/memory\/.*\.md$/, // harness auto-memory files
|
|
52
|
+
/\.claude\/memory\/.*\.md$/, // harness global memory files
|
|
51
53
|
];
|
|
52
54
|
|
|
53
55
|
function isSharedState(filePath) {
|
|
@@ -118,13 +120,23 @@ async function main() {
|
|
|
118
120
|
// Block Write on protected files
|
|
119
121
|
// Exact matches: always block Write (use Edit instead)
|
|
120
122
|
// Pattern matches: only block if file already exists (allow creating new files)
|
|
123
|
+
// Shared state paths (harness memory, daily logs): allow Write freely
|
|
121
124
|
if (toolName === 'Write') {
|
|
122
125
|
const isExactMatch = PROTECTED.has(fileName);
|
|
123
|
-
if (isExactMatch
|
|
126
|
+
if (isExactMatch) {
|
|
124
127
|
deny(`BLOCKED: Write tool on ${match} is not allowed. Use Edit to make specific changes. Never overwrite protected files.`);
|
|
125
128
|
process.exit(0);
|
|
126
129
|
}
|
|
127
|
-
//
|
|
130
|
+
// Shared state paths get Write access (harness manages its own memory files)
|
|
131
|
+
if (isSharedState(filePath)) {
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
// Other pattern matches: block if file exists, allow creation of new files
|
|
135
|
+
if (existsSync(filePath)) {
|
|
136
|
+
deny(`BLOCKED: Write tool on ${match} is not allowed. Use Edit to make specific changes. Never overwrite protected files.`);
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
// Pattern match but file doesn't exist yet ... allow creation
|
|
128
140
|
process.exit(0);
|
|
129
141
|
}
|
|
130
142
|
|
|
@@ -116,5 +116,27 @@ check "Allow Write to unrelated file with no pattern match" \
|
|
|
116
116
|
'{"tool_name":"Write","tool_input":{"file_path":"/src/utils/helper.js","content":"new"}}' \
|
|
117
117
|
"allow"
|
|
118
118
|
|
|
119
|
+
|
|
120
|
+
# Harness memory paths (shared state - lenient limits)
|
|
121
|
+
check "Allow Write to harness project memory file" \
|
|
122
|
+
'{"tool_name":"Write","tool_input":{"file_path":"/Users/lesa/.claude/projects/-Users-lesa--openclaw/memory/repo-locations.md","content":"new"}}' \
|
|
123
|
+
"allow"
|
|
124
|
+
|
|
125
|
+
check "Allow Write to harness global memory file" \
|
|
126
|
+
'{"tool_name":"Write","tool_input":{"file_path":"/Users/lesa/.claude/memory/feedback.md","content":"new"}}' \
|
|
127
|
+
"allow"
|
|
128
|
+
|
|
129
|
+
check "Allow Edit removing 10 lines from harness memory (lenient limit)" \
|
|
130
|
+
'{"tool_name":"Edit","tool_input":{"file_path":"/Users/lesa/.claude/projects/-foo/memory/test.md","old_string":"a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl","new_string":"x\ny"}}' \
|
|
131
|
+
"allow"
|
|
132
|
+
|
|
133
|
+
check "Block Write to SOUL.md even under .claude/projects/memory/" \
|
|
134
|
+
'{"tool_name":"Write","tool_input":{"file_path":"/Users/lesa/.claude/projects/foo/memory/SOUL.md","content":"new"}}' \
|
|
135
|
+
"block"
|
|
136
|
+
|
|
137
|
+
check "Block Write to SHARED-CONTEXT.md even under .claude path" \
|
|
138
|
+
'{"tool_name":"Write","tool_input":{"file_path":"/Users/lesa/.claude/projects/foo/memory/SHARED-CONTEXT.md","content":"new"}}' \
|
|
139
|
+
"block"
|
|
140
|
+
|
|
119
141
|
echo ""
|
|
120
142
|
echo "Results: $PASS passed, $FAIL failed"
|
|
@@ -25,7 +25,8 @@ export function detectCurrentVersion(repoPath) {
|
|
|
25
25
|
* Bump a semver string by level.
|
|
26
26
|
*/
|
|
27
27
|
export function bumpSemver(version, level) {
|
|
28
|
-
const
|
|
28
|
+
const base = version.replace(/-.*$/, '');
|
|
29
|
+
const [major, minor, patch] = base.split('.').map(Number);
|
|
29
30
|
switch (level) {
|
|
30
31
|
case 'major': return `${major + 1}.0.0`;
|
|
31
32
|
case 'minor': return `${major}.${minor + 1}.0`;
|
|
@@ -1674,27 +1675,33 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1674
1675
|
writePackageVersion(repoPath, newVersion);
|
|
1675
1676
|
console.log(` ✓ package.json -> ${newVersion}`);
|
|
1676
1677
|
|
|
1677
|
-
// 1.5.
|
|
1678
|
+
// 1.5. Validate sub-tool version bumps in toolbox repos (tools/*/)
|
|
1679
|
+
// Sub-tools have independent versions. If files changed since last tag
|
|
1680
|
+
// but the version didn't bump, warn. Developer must bump manually.
|
|
1678
1681
|
const toolsDir = join(repoPath, 'tools');
|
|
1679
1682
|
if (existsSync(toolsDir)) {
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1683
|
+
const lastTag = (() => { try { return execFileSync('git', ['describe', '--tags', '--abbrev=0'], { cwd: repoPath, encoding: 'utf8' }).trim(); } catch { return null; } })();
|
|
1684
|
+
if (lastTag) {
|
|
1685
|
+
try {
|
|
1686
|
+
const entries = readdirSync(toolsDir, { withFileTypes: true });
|
|
1687
|
+
for (const entry of entries) {
|
|
1688
|
+
if (!entry.isDirectory()) continue;
|
|
1689
|
+
const subDir = join('tools', entry.name);
|
|
1690
|
+
const subPkgPath = join(toolsDir, entry.name, 'package.json');
|
|
1691
|
+
if (!existsSync(subPkgPath)) continue;
|
|
1687
1692
|
try {
|
|
1688
|
-
const
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1693
|
+
const diff = execFileSync('git', ['diff', '--name-only', lastTag, 'HEAD', '--', subDir], { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
1694
|
+
if (!diff) continue;
|
|
1695
|
+
const currentSubVersion = JSON.parse(readFileSync(subPkgPath, 'utf8')).version;
|
|
1696
|
+
const oldSubVersion = (() => { try { return JSON.parse(execFileSync('git', ['show', `${lastTag}:${subDir}/package.json`], { cwd: repoPath, encoding: 'utf8' })).version; } catch { return null; } })();
|
|
1697
|
+
if (currentSubVersion === oldSubVersion) {
|
|
1698
|
+
console.log(` ! WARNING: ${entry.name} has changed files since ${lastTag} but version is still ${currentSubVersion}`);
|
|
1699
|
+
console.log(` Changed: ${diff.split('\n').join(', ')}`);
|
|
1700
|
+
console.log(` Bump tools/${entry.name}/package.json before releasing.`);
|
|
1701
|
+
}
|
|
1692
1702
|
} catch {}
|
|
1693
1703
|
}
|
|
1694
|
-
}
|
|
1695
|
-
} catch {}
|
|
1696
|
-
if (subBumped > 0) {
|
|
1697
|
-
console.log(` ✓ ${subBumped} sub-tool(s) -> ${newVersion}`);
|
|
1704
|
+
} catch {}
|
|
1698
1705
|
}
|
|
1699
1706
|
}
|
|
1700
1707
|
|
|
@@ -2040,6 +2047,37 @@ export async function releasePrerelease({ repoPath, track, notes, dryRun, noPubl
|
|
|
2040
2047
|
writePackageVersion(repoPath, newVersion);
|
|
2041
2048
|
console.log(` \u2713 package.json -> ${newVersion}`);
|
|
2042
2049
|
|
|
2050
|
+
// 1.5. Validate sub-tool version bumps in toolbox repos (tools/*/)
|
|
2051
|
+
// If a sub-tool's files changed since the last tag but its version didn't bump, warn.
|
|
2052
|
+
const toolsDir = join(repoPath, 'tools');
|
|
2053
|
+
if (existsSync(toolsDir)) {
|
|
2054
|
+
const lastTag = (() => { try { return execFileSync('git', ['describe', '--tags', '--abbrev=0'], { cwd: repoPath, encoding: 'utf8' }).trim(); } catch { return null; } })();
|
|
2055
|
+
if (lastTag) {
|
|
2056
|
+
try {
|
|
2057
|
+
const entries = readdirSync(toolsDir, { withFileTypes: true });
|
|
2058
|
+
for (const entry of entries) {
|
|
2059
|
+
if (!entry.isDirectory()) continue;
|
|
2060
|
+
const subDir = join('tools', entry.name);
|
|
2061
|
+
const subPkgPath = join(toolsDir, entry.name, 'package.json');
|
|
2062
|
+
if (!existsSync(subPkgPath)) continue;
|
|
2063
|
+
// Check if any files in this sub-tool changed since last tag
|
|
2064
|
+
try {
|
|
2065
|
+
const diff = execFileSync('git', ['diff', '--name-only', lastTag, 'HEAD', '--', subDir], { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
2066
|
+
if (!diff) continue; // No changes, skip
|
|
2067
|
+
// Files changed. Check if version was bumped.
|
|
2068
|
+
const currentSubVersion = JSON.parse(readFileSync(subPkgPath, 'utf8')).version;
|
|
2069
|
+
const oldSubVersion = (() => { try { return JSON.parse(execFileSync('git', ['show', `${lastTag}:${subDir}/package.json`], { cwd: repoPath, encoding: 'utf8' })).version; } catch { return null; } })();
|
|
2070
|
+
if (currentSubVersion === oldSubVersion) {
|
|
2071
|
+
console.log(` ! WARNING: ${entry.name} has changed files since ${lastTag} but version is still ${currentSubVersion}`);
|
|
2072
|
+
console.log(` Changed: ${diff.split('\n').join(', ')}`);
|
|
2073
|
+
console.log(` Bump tools/${entry.name}/package.json before releasing.`);
|
|
2074
|
+
}
|
|
2075
|
+
} catch {}
|
|
2076
|
+
}
|
|
2077
|
+
} catch {}
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2043
2081
|
// 2. Update CHANGELOG.md (lightweight entry)
|
|
2044
2082
|
updateChangelog(repoPath, newVersion, notes || `${track} prerelease`);
|
|
2045
2083
|
console.log(` \u2713 CHANGELOG.md updated`);
|
|
@@ -2241,27 +2279,31 @@ export async function releaseHotfix({ repoPath, notes, notesSource, dryRun, noPu
|
|
|
2241
2279
|
writePackageVersion(repoPath, newVersion);
|
|
2242
2280
|
console.log(` \u2713 package.json -> ${newVersion}`);
|
|
2243
2281
|
|
|
2244
|
-
// 1.5.
|
|
2282
|
+
// 1.5. Validate sub-tool version bumps in toolbox repos (tools/*/)
|
|
2245
2283
|
const toolsDir = join(repoPath, 'tools');
|
|
2246
2284
|
if (existsSync(toolsDir)) {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2285
|
+
const lastTag = (() => { try { return execFileSync('git', ['describe', '--tags', '--abbrev=0'], { cwd: repoPath, encoding: 'utf8' }).trim(); } catch { return null; } })();
|
|
2286
|
+
if (lastTag) {
|
|
2287
|
+
try {
|
|
2288
|
+
const entries = readdirSync(toolsDir, { withFileTypes: true });
|
|
2289
|
+
for (const entry of entries) {
|
|
2290
|
+
if (!entry.isDirectory()) continue;
|
|
2291
|
+
const subDir = join('tools', entry.name);
|
|
2292
|
+
const subPkgPath = join(toolsDir, entry.name, 'package.json');
|
|
2293
|
+
if (!existsSync(subPkgPath)) continue;
|
|
2254
2294
|
try {
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2295
|
+
const diff = execFileSync('git', ['diff', '--name-only', lastTag, 'HEAD', '--', subDir], { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
2296
|
+
if (!diff) continue;
|
|
2297
|
+
const currentSubVersion = JSON.parse(readFileSync(subPkgPath, 'utf8')).version;
|
|
2298
|
+
const oldSubVersion = (() => { try { return JSON.parse(execFileSync('git', ['show', `${lastTag}:${subDir}/package.json`], { cwd: repoPath, encoding: 'utf8' })).version; } catch { return null; } })();
|
|
2299
|
+
if (currentSubVersion === oldSubVersion) {
|
|
2300
|
+
console.log(` ! WARNING: ${entry.name} has changed files since ${lastTag} but version is still ${currentSubVersion}`);
|
|
2301
|
+
console.log(` Changed: ${diff.split('\n').join(', ')}`);
|
|
2302
|
+
console.log(` Bump tools/${entry.name}/package.json before releasing.`);
|
|
2303
|
+
}
|
|
2259
2304
|
} catch {}
|
|
2260
2305
|
}
|
|
2261
|
-
}
|
|
2262
|
-
} catch {}
|
|
2263
|
-
if (subBumped > 0) {
|
|
2264
|
-
console.log(` \u2713 ${subBumped} sub-tool(s) -> ${newVersion}`);
|
|
2306
|
+
} catch {}
|
|
2265
2307
|
}
|
|
2266
2308
|
}
|
|
2267
2309
|
|