@wipcomputer/wip-ai-devops-toolbox 1.9.27 → 1.9.28

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
@@ -31,6 +31,35 @@
31
31
 
32
32
 
33
33
 
34
+
35
+ ## 1.9.28 (2026-03-15)
36
+
37
+ # Release Notes Quality Gate
38
+
39
+ **Date:** 2026-03-15
40
+
41
+ ## What changed
42
+
43
+ wip-release now blocks ALL releases (patch, minor, major) if the release notes are bad. Previously, patch releases only warned. Now they block.
44
+
45
+ The gate checks:
46
+ - Notes must be at least 50 characters
47
+ - Notes can't look like a changelog entry ("fix: ...", "add: ...", "update: ...")
48
+ - Minor/major still require a file (not --notes flag)
49
+
50
+ If the gate blocks, it tells you exactly how to fix it: write a RELEASE-NOTES file, write a dev update, or use --notes with at least 50 chars of real description.
51
+
52
+ ## Why
53
+
54
+ Release notes were consistently garbage. One-liner --notes flags like "Fix bug" or "Update docs" sailed through on patch releases. The warnings were ignored by both humans and agents. Every release page on GitHub had thin, useless notes that didn't explain what changed or why.
55
+
56
+ ## Also in this release
57
+
58
+ - wip-repo-init templates renamed from ai/ to templates/ so they ship with npm install (deploy-public.sh was stripping them)
59
+ - SKILL.md restart notice after install (hooks need session restart)
60
+ - SPEC.md and TECHNICAL.md updated with all 17 tools and LDM OS links
61
+ - Branch guard matcher fix (catches Bash + NotebookEdit)
62
+ - Forced Git Worktrees and Branch Guard sections added to SKILL.md
34
63
 
35
64
  ## 1.9.27 (2026-03-15)
36
65
 
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.27"
8
+ version: "1.9.28"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ai-devops-toolbox",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "type": "module",
5
5
  "description": "The complete AI DevOps toolkit for AI-assisted development teams.",
6
6
  "license": "MIT",
@@ -18,12 +18,29 @@
18
18
 
19
19
  set -euo pipefail
20
20
 
21
- PRIVATE_REPO="$1"
22
- PUBLIC_REPO="$2"
21
+ PRIVATE_REPO="${1:-}"
22
+ PUBLIC_REPO="${2:-}"
23
+ DRY_RUN=false
24
+
25
+ # Parse flags
26
+ for arg in "$@"; do
27
+ case "$arg" in
28
+ --dry-run) DRY_RUN=true ;;
29
+ esac
30
+ done
31
+
32
+ # Strip flags from positional args
33
+ ARGS=()
34
+ for arg in "$@"; do
35
+ [[ "$arg" == --* ]] || ARGS+=("$arg")
36
+ done
37
+ PRIVATE_REPO="${ARGS[0]:-}"
38
+ PUBLIC_REPO="${ARGS[1]:-}"
23
39
 
24
40
  if [[ -z "$PRIVATE_REPO" || -z "$PUBLIC_REPO" ]]; then
25
- echo "Usage: bash deploy-public.sh <private-repo-path> <public-github-repo>"
41
+ echo "Usage: bash deploy-public.sh <private-repo-path> <public-github-repo> [--dry-run]"
26
42
  echo "Example: bash deploy-public.sh /path/to/memory-crystal wipcomputer/memory-crystal"
43
+ echo " bash deploy-public.sh /path/to/memory-crystal wipcomputer/memory-crystal --dry-run"
27
44
  exit 1
28
45
  fi
29
46
 
@@ -123,6 +140,24 @@ fi
123
140
  BRANCH="$HARNESS_ID/deploy-$(date +%Y%m%d-%H%M%S)"
124
141
 
125
142
  git add -A
143
+
144
+ # Dry-run: show what would be deployed, then stop
145
+ if $DRY_RUN; then
146
+ echo ""
147
+ echo " Dry run: deploy-public.sh"
148
+ echo " ────────────────────────────────────"
149
+ echo " Source: $PRIVATE_REPO"
150
+ echo " Target: $PUBLIC_REPO"
151
+ echo " Commit: $COMMIT_MSG ($COMMIT_HASH)"
152
+ echo ""
153
+ echo " Files that would change:"
154
+ git diff --cached --stat 2>/dev/null || git diff --stat HEAD 2>/dev/null || echo " (new files)"
155
+ git ls-files --others --exclude-standard | head -20 | while read f; do echo " + $f"; done
156
+ echo ""
157
+ echo " Dry run complete. No changes pushed."
158
+ exit 0
159
+ fi
160
+
126
161
  git commit -m "$COMMIT_MSG (from $COMMIT_HASH)"
127
162
 
128
163
  if [[ "$EMPTY_REPO" == "true" ]]; then
@@ -159,6 +159,11 @@ prune_branches() {
159
159
  if [[ $count -le $KEEP_COUNT ]]; then
160
160
  echo " ✓ KEEP $branch"
161
161
  else
162
+ # Safety: verify branch is actually merged into main before deleting
163
+ if ! git merge-base --is-ancestor "origin/$branch" origin/main 2>/dev/null; then
164
+ echo " ! SKIP $branch (NOT merged into main despite --merged suffix)"
165
+ continue
166
+ fi
162
167
  if $DRY_RUN; then
163
168
  echo " [dry-run] DELETE $branch"
164
169
  else
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/deploy-public",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "description": "Private-to-public repo sync. Excludes ai/ folder, creates PR, merges, cleans up branches.",
5
5
  "bin": {
6
6
  "deploy-public": "./deploy-public.sh"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/post-merge-rename",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "description": "Post-merge branch renaming. Appends --merged-YYYY-MM-DD to preserve history.",
5
5
  "bin": {
6
6
  "post-merge-rename": "./post-merge-rename.sh"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-branch-guard",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
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
  "main": "guard.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-file-guard",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-license-guard",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "description": "License compliance for your own repos. Ensures correct copyright, dual-license blocks, and LICENSE files.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-license-hook",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "description": "License rug-pull detection and dependency license compliance for open source projects",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -337,6 +337,26 @@ if (DEPLOY) {
337
337
  process.exit(1);
338
338
  }
339
339
 
340
+ // Safety: init files must be reviewed (committed or modified) before deploy.
341
+ // If all init files are untracked (just generated, never reviewed), block.
342
+ try {
343
+ const { execSync } = await import('node:child_process');
344
+ const initFiles = readdirSync(repoPath).filter(f => f.startsWith('README-init-'));
345
+ const allUntracked = initFiles.every(f => {
346
+ try {
347
+ const status = execSync(`git status --porcelain "${f}"`, { cwd: repoPath, encoding: 'utf8' }).trim();
348
+ return status.startsWith('??');
349
+ } catch { return false; }
350
+ });
351
+ if (allUntracked && initFiles.length > 0) {
352
+ fail('Init files have not been reviewed. They are all untracked (just generated).');
353
+ console.log(' Review the README-init-*.md files, edit as needed, then git add them before deploying.');
354
+ console.log(' Or commit them first so there is a review trail.');
355
+ process.exit(1);
356
+ }
357
+ } catch {}
358
+
359
+
340
360
  const date = new Date().toISOString().slice(0, 10);
341
361
  const aiTrash = join(repoPath, 'ai', '_trash');
342
362
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-readme-format",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "description": "Reformat any repo's README to follow the WIP Computer standard. Agent-first, human-readable.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -252,6 +252,44 @@ function checkReleaseNotes(notes, notesSource, level) {
252
252
  return { ok: issues.length === 0, issues, block: issues.length > 0 };
253
253
  }
254
254
 
255
+ /**
256
+ * Scaffold a RELEASE-NOTES-v{version}.md template if one doesn't exist.
257
+ * Called when the release notes gate blocks. Gives the agent a file to fill in.
258
+ */
259
+ export function scaffoldReleaseNotes(repoPath, version) {
260
+ const dashed = version.replace(/\./g, '-');
261
+ const notesPath = join(repoPath, `RELEASE-NOTES-v${dashed}.md`);
262
+ if (existsSync(notesPath)) return notesPath;
263
+
264
+ const pkg = JSON.parse(readFileSync(join(repoPath, 'package.json'), 'utf8'));
265
+ const name = pkg.name?.replace(/^@[^/]+\//, '') || basename(repoPath);
266
+
267
+ const template = `# Release Notes: ${name} v${version}
268
+
269
+ **One-line summary of what this release does**
270
+
271
+ ## What changed
272
+
273
+ Describe the changes. Not a commit list. Explain:
274
+ - What was built or fixed
275
+ - Why it matters
276
+ - What the user should know
277
+
278
+ ## Why
279
+
280
+ What problem does this solve? What was broken or missing?
281
+
282
+ ## How to verify
283
+
284
+ \`\`\`bash
285
+ # Commands to test the changes
286
+ \`\`\`
287
+ `;
288
+
289
+ writeFileSync(notesPath, template);
290
+ return notesPath;
291
+ }
292
+
255
293
  /**
256
294
  * Check if a file was modified in commits since the last git tag.
257
295
  */
@@ -783,11 +821,10 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
783
821
  console.log(` ✗ Release notes blocked:`);
784
822
  for (const issue of notesCheck.issues) console.log(` - ${issue}`);
785
823
  console.log('');
786
- console.log(' Release notes must explain what changed and why.');
787
- console.log(' Options:');
788
- console.log(' 1. Write RELEASE-NOTES-v{version}.md (dashes not dots) and commit it');
789
- console.log(' 2. Write ai/dev-updates/YYYY-MM-DD--description.md and commit it');
790
- console.log(' 3. Use --notes="at least 50 chars explaining the change and its impact"');
824
+ // Scaffold a template so the agent has something to fill in
825
+ const templatePath = scaffoldReleaseNotes(repoPath, newVersion);
826
+ console.log(` Scaffolded template: ${basename(templatePath)}`);
827
+ console.log(' Fill it in, commit, then run wip-release again.');
791
828
  console.log('');
792
829
  return { currentVersion, newVersion, dryRun: false, failed: true };
793
830
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-release",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-repo-init",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "description": "Scaffold the standard ai/ directory structure in any repo",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-repo-permissions-hook",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "type": "module",
5
5
  "description": "Repo visibility guard. Blocks repos from going public without a -private counterpart.",
6
6
  "main": "core.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-repos",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
4
4
  "type": "module",
5
5
  "description": "Repo manifest reconciler. Single source of truth for repo organization. Like prettier for folder structure.",
6
6
  "main": "core.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/universal-installer",
3
- "version": "1.9.27",
3
+ "version": "1.9.28",
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",