@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ai-devops-toolbox",
3
- "version": "1.9.69-alpha.1",
3
+ "version": "1.9.71-alpha.1",
4
4
  "type": "module",
5
5
  "description": "The complete AI DevOps toolkit for AI-assisted development teams.",
6
6
  "license": "MIT",
@@ -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)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-branch-guard",
3
- "version": "1.9.68",
3
+ "version": "1.9.70",
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
  "scripts": {
@@ -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 || existsSync(filePath)) {
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
- // Pattern match but file doesn't exist yet allow creation
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-file-guard",
3
- "version": "1.9.68",
3
+ "version": "1.9.69",
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",
@@ -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 [major, minor, patch] = version.split('.').map(Number);
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. Bump sub-tool versions in toolbox repos (tools/*/)
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
- let subBumped = 0;
1681
- try {
1682
- const entries = readdirSync(toolsDir, { withFileTypes: true });
1683
- for (const entry of entries) {
1684
- if (!entry.isDirectory()) continue;
1685
- const subPkgPath = join(toolsDir, entry.name, 'package.json');
1686
- if (existsSync(subPkgPath)) {
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 subPkg = JSON.parse(readFileSync(subPkgPath, 'utf8'));
1689
- subPkg.version = newVersion;
1690
- writeFileSync(subPkgPath, JSON.stringify(subPkg, null, 2) + '\n');
1691
- subBumped++;
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. Bump sub-tool versions
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
- let subBumped = 0;
2248
- try {
2249
- const entries = readdirSync(toolsDir, { withFileTypes: true });
2250
- for (const entry of entries) {
2251
- if (!entry.isDirectory()) continue;
2252
- const subPkgPath = join(toolsDir, entry.name, 'package.json');
2253
- if (existsSync(subPkgPath)) {
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 subPkg = JSON.parse(readFileSync(subPkgPath, 'utf8'));
2256
- subPkg.version = newVersion;
2257
- writeFileSync(subPkgPath, JSON.stringify(subPkg, null, 2) + '\n');
2258
- subBumped++;
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
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-release",
3
- "version": "1.9.68",
3
+ "version": "1.9.70",
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",