@wipcomputer/wip-ai-devops-toolbox 1.9.65 → 1.9.67
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 +70 -0
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/tools/deploy-public/package.json +1 -1
- package/tools/ldm-jobs/backup.sh +1 -1
- package/tools/post-merge-rename/package.json +1 -1
- package/tools/wip-branch-guard/package.json +1 -1
- package/tools/wip-branch-guard/test.sh +1 -1
- 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/cli.js +30 -2
- package/tools/wip-release/core.mjs +155 -0
- 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,76 @@
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
## 1.9.67 (2026-03-31)
|
|
36
|
+
|
|
37
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.67
|
|
38
|
+
|
|
39
|
+
**Date:** 2026-03-30
|
|
40
|
+
|
|
41
|
+
## What changed
|
|
42
|
+
|
|
43
|
+
### Hardcoded path removal
|
|
44
|
+
|
|
45
|
+
Two files in the devops toolbox had paths that assumed a specific username or iCloud layout.
|
|
46
|
+
|
|
47
|
+
**ldm-jobs/backup.sh** referenced `/Users/lesa/Library/Mobile Documents/.../ldm/bin/` to find the `ldm` binary for scheduled backup jobs. This iCloud path was fragile (iCloud sync delays, different usernames). The script now uses `$HOME/.ldm/bin/` which is the standard LDM install location and works on any machine (#301).
|
|
48
|
+
|
|
49
|
+
**test.sh** (the branch guard test harness) had `/Users/lesa` hardcoded for creating temp directories. It now uses `$HOME` so tests run correctly under any user account (#301).
|
|
50
|
+
|
|
51
|
+
### Earlier changes included in this release
|
|
52
|
+
|
|
53
|
+
**v1.9.66** added auto-combine for release notes from batched PRs (#237). When multiple PRs are merged between releases, their individual RELEASE-NOTES files are automatically combined into a single changelog entry.
|
|
54
|
+
|
|
55
|
+
**v1.9.65** fixed the scaffold-on-main issue (#223) where scaffolding left untracked files that blocked `git pull` on the main working tree.
|
|
56
|
+
|
|
57
|
+
## Why
|
|
58
|
+
|
|
59
|
+
The backup job is scheduled via LaunchAgent and runs unattended. If the path to `ldm` is wrong, backups silently fail. Moving to `$HOME/.ldm/bin/` aligns with the standard LDM install path and eliminates the iCloud dependency. The test fix ensures CI and local test runs work for all contributors.
|
|
60
|
+
|
|
61
|
+
## Issues closed
|
|
62
|
+
|
|
63
|
+
- #301
|
|
64
|
+
|
|
65
|
+
## How to verify
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
grep -r "/Users/lesa" ldm-jobs/ tools/wip-branch-guard/test.sh
|
|
69
|
+
# Should return zero results
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 1.9.66 (2026-03-30)
|
|
73
|
+
|
|
74
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.66
|
|
75
|
+
|
|
76
|
+
Auto-combine release notes when batching multiple PRs into a single release.
|
|
77
|
+
|
|
78
|
+
## The story
|
|
79
|
+
|
|
80
|
+
When multiple PRs merge to main before wip-release runs, the release had no good way to gather all their stories. Each PR might have its own RELEASE-NOTES file committed on the branch, but once the branch merges and the file gets trashed, the next release only sees an empty repo root. The agent had to write a new RELEASE-NOTES file from scratch, losing the narrative that was already reviewed in each PR.
|
|
81
|
+
|
|
82
|
+
Now wip-release looks back through git history. It finds every merge commit since the last tag, checks each one for RELEASE-NOTES files via `git diff-tree` and `git show`, and combines them into a single document. If only one PR had notes, it uses them as-is (fully backwards compatible). If multiple PRs had notes, it wraps them with per-PR section headers, strips duplicate top-level headings, and collects all issue references into a combined list at the end.
|
|
83
|
+
|
|
84
|
+
The detection sits at priority 2.5 in the release notes cascade: after the single-file check (RELEASE-NOTES-v{ver}.md on disk) but before the dev-update fallback. A file on disk always wins. The merged-PR scan only kicks in when nothing is found on disk.
|
|
85
|
+
|
|
86
|
+
## What changed
|
|
87
|
+
|
|
88
|
+
- New exported function `collectMergedPRNotes()` in `core.mjs` that scans git merge history for RELEASE-NOTES files
|
|
89
|
+
- Updated `cli.js` to call it at priority 2.5 in the notes detection cascade
|
|
90
|
+
- Updated help text to document the new detection path
|
|
91
|
+
- Zero breaking changes. Single-file detection still works exactly as before.
|
|
92
|
+
|
|
93
|
+
## Issues closed
|
|
94
|
+
|
|
95
|
+
- Closes #237
|
|
96
|
+
|
|
97
|
+
## How to verify
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# In any repo with multiple merged PRs since last tag, each having RELEASE-NOTES files:
|
|
101
|
+
wip-release patch --dry-run
|
|
102
|
+
# Should show: "Combined release notes from N merged PRs"
|
|
103
|
+
```
|
|
34
104
|
|
|
35
105
|
## 1.9.65 (2026-03-29)
|
|
36
106
|
|
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.67"
|
|
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
package/tools/ldm-jobs/backup.sh
CHANGED
|
@@ -110,7 +110,7 @@ test_case "mkdir .worktrees" allow Bash "mkdir -p .worktrees/repo--branch"
|
|
|
110
110
|
|
|
111
111
|
echo ""
|
|
112
112
|
echo "--- Plan files (should ALLOW Write/Edit) ---"
|
|
113
|
-
test_case "Edit plan file" allow Edit "
|
|
113
|
+
test_case "Edit plan file" allow Edit "$HOME/.claude/plans/my-plan.md"
|
|
114
114
|
|
|
115
115
|
echo ""
|
|
116
116
|
echo "=== Results: $PASS passed, $FAIL failed, $SKIP skipped ==="
|
package/tools/wip-release/cli.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Release tool CLI. Bumps version, updates docs, publishes.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { release, detectCurrentVersion } from './core.mjs';
|
|
8
|
+
import { release, detectCurrentVersion, collectMergedPRNotes } from './core.mjs';
|
|
9
9
|
|
|
10
10
|
const args = process.argv.slice(2);
|
|
11
11
|
const level = args.find(a => ['patch', 'minor', 'major'].includes(a));
|
|
@@ -31,6 +31,7 @@ let notesSource = (notes !== null && notes !== undefined && notes !== '') ? 'fla
|
|
|
31
31
|
// Release notes priority (highest wins):
|
|
32
32
|
// 1. --notes-file=path Explicit file path (always wins)
|
|
33
33
|
// 2. RELEASE-NOTES-v{ver}.md In repo root (always wins over --notes flag)
|
|
34
|
+
// 2.5. Merged PR notes Auto-combined from git history (#237)
|
|
34
35
|
// 3. ai/dev-updates/YYYY-MM-DD* Today's dev update (wins over --notes flag if longer)
|
|
35
36
|
// 4. --notes="text" Flag fallback (only if nothing better exists)
|
|
36
37
|
//
|
|
@@ -71,6 +72,31 @@ let notesSource = (notes !== null && notes !== undefined && notes !== '') ? 'fla
|
|
|
71
72
|
} catch {}
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// 2.5. Auto-combine release notes from merged PRs since last tag (#237)
|
|
76
|
+
// Only runs when no single RELEASE-NOTES file was found on disk.
|
|
77
|
+
// Scans git merge history for RELEASE-NOTES files committed on PR branches.
|
|
78
|
+
if (level && notesSource !== 'file') {
|
|
79
|
+
try {
|
|
80
|
+
const { collectMergedPRNotes, detectCurrentVersion: dcv, bumpSemver: bs } = await import('./core.mjs');
|
|
81
|
+
const cwd = process.cwd();
|
|
82
|
+
const cv = dcv(cwd);
|
|
83
|
+
const nv = bs(cv, level);
|
|
84
|
+
const combined = collectMergedPRNotes(cwd, cv, nv);
|
|
85
|
+
if (combined) {
|
|
86
|
+
if (flagNotes && flagNotes !== combined.notes) {
|
|
87
|
+
console.log(` ! --notes flag ignored: merged PR notes take priority`);
|
|
88
|
+
}
|
|
89
|
+
notes = combined.notes;
|
|
90
|
+
notesSource = combined.notesSource;
|
|
91
|
+
if (combined.prCount > 1) {
|
|
92
|
+
console.log(` \u2713 Combined release notes from ${combined.prCount} merged PRs`);
|
|
93
|
+
} else {
|
|
94
|
+
console.log(` \u2713 Found release notes from merged PR`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
|
|
74
100
|
// 3. Auto-detect dev update from ai/dev-updates/ (wins over --notes flag if longer)
|
|
75
101
|
if (level && (!notes || (notesSource === 'flag' && notes.length < 200))) {
|
|
76
102
|
try {
|
|
@@ -134,9 +160,11 @@ Flags:
|
|
|
134
160
|
Release notes (REQUIRED, must be a file on disk):
|
|
135
161
|
1. --notes-file=path Explicit file path
|
|
136
162
|
2. RELEASE-NOTES-v{ver}.md In repo root (auto-detected)
|
|
137
|
-
3.
|
|
163
|
+
3. Merged PR notes Auto-combined from git history (#237)
|
|
164
|
+
4. ai/dev-updates/YYYY-MM-DD* Today's dev update (auto-detected)
|
|
138
165
|
The --notes flag is NOT accepted. Write a file. Commit it on your branch.
|
|
139
166
|
The file shows up in the PR diff so it can be reviewed before merge.
|
|
167
|
+
When batching multiple PRs, each PR's RELEASE-NOTES are auto-combined.
|
|
140
168
|
|
|
141
169
|
Skill publish to website:
|
|
142
170
|
Add .publish-skill.json to repo root: { "name": "my-tool" }
|
|
@@ -368,6 +368,161 @@ ${issueRefs || '- #XX (replace with actual issue numbers)'}
|
|
|
368
368
|
return notesPath;
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Collect release notes from merged PRs since the last tag.
|
|
373
|
+
*
|
|
374
|
+
* When multiple PRs are batched into a single release, each PR may have
|
|
375
|
+
* committed its own RELEASE-NOTES-v*.md file. This function finds those
|
|
376
|
+
* notes in git history and combines them into one document.
|
|
377
|
+
*
|
|
378
|
+
* Steps:
|
|
379
|
+
* 1. git log v{prev}..HEAD --merges --oneline to find merge commits
|
|
380
|
+
* 2. Extract PR number from "Merge pull request #XX from ..."
|
|
381
|
+
* 3. Check each merge commit's diff for RELEASE-NOTES*.md files
|
|
382
|
+
* 4. Read content via git show {sha}:{path}
|
|
383
|
+
* 5. Combine into a single document (newest first)
|
|
384
|
+
*
|
|
385
|
+
* Returns { notes, notesSource, prCount } or null if nothing found.
|
|
386
|
+
*/
|
|
387
|
+
export function collectMergedPRNotes(repoPath, currentVersion, newVersion) {
|
|
388
|
+
let lastTag;
|
|
389
|
+
try {
|
|
390
|
+
lastTag = execFileSync('git', ['describe', '--tags', '--abbrev=0'],
|
|
391
|
+
{ cwd: repoPath, encoding: 'utf8' }).trim();
|
|
392
|
+
} catch {
|
|
393
|
+
return null; // No tags yet
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Find merge commits since last tag
|
|
397
|
+
let mergeLog;
|
|
398
|
+
try {
|
|
399
|
+
mergeLog = execFileSync('git', [
|
|
400
|
+
'log', `${lastTag}..HEAD`, '--merges', '--oneline'
|
|
401
|
+
], { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
402
|
+
} catch {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!mergeLog) return null;
|
|
407
|
+
|
|
408
|
+
const mergeLines = mergeLog.split('\n').filter(Boolean);
|
|
409
|
+
const prNotes = [];
|
|
410
|
+
|
|
411
|
+
for (const line of mergeLines) {
|
|
412
|
+
// Format: "abc1234 Merge pull request #XX from org/branch"
|
|
413
|
+
const prMatch = line.match(/^([a-f0-9]+)\s+Merge pull request #(\d+)\s+from\s+(.+)$/);
|
|
414
|
+
if (!prMatch) continue;
|
|
415
|
+
|
|
416
|
+
const [, shortHash, prNum, branchRef] = prMatch;
|
|
417
|
+
|
|
418
|
+
// Get the full hash for this merge commit
|
|
419
|
+
let fullHash;
|
|
420
|
+
try {
|
|
421
|
+
fullHash = execFileSync('git', ['rev-parse', shortHash],
|
|
422
|
+
{ cwd: repoPath, encoding: 'utf8' }).trim();
|
|
423
|
+
} catch {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// List files changed in this merge commit.
|
|
428
|
+
// For merge commits, diff against first parent to see what the PR brought in.
|
|
429
|
+
// Plain diff-tree on a merge commit shows nothing without -m or -c.
|
|
430
|
+
let changedFiles;
|
|
431
|
+
try {
|
|
432
|
+
changedFiles = execFileSync('git', [
|
|
433
|
+
'diff', '--name-only', `${fullHash}^1`, fullHash
|
|
434
|
+
], { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
435
|
+
} catch {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Look for RELEASE-NOTES*.md files
|
|
440
|
+
const noteFiles = changedFiles.split('\n')
|
|
441
|
+
.filter(f => /^RELEASE-NOTES.*\.md$/i.test(f.trim()));
|
|
442
|
+
|
|
443
|
+
if (noteFiles.length === 0) continue;
|
|
444
|
+
|
|
445
|
+
// Read the content of each release notes file from that commit
|
|
446
|
+
for (const noteFile of noteFiles) {
|
|
447
|
+
try {
|
|
448
|
+
const content = execFileSync('git', [
|
|
449
|
+
'show', `${fullHash}:${noteFile.trim()}`
|
|
450
|
+
], { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
451
|
+
|
|
452
|
+
if (content) {
|
|
453
|
+
prNotes.push({
|
|
454
|
+
prNum,
|
|
455
|
+
branch: branchRef,
|
|
456
|
+
content,
|
|
457
|
+
hash: shortHash,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
// File might have been deleted in the merge. Skip.
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (prNotes.length === 0) return null;
|
|
467
|
+
|
|
468
|
+
// If only one PR had notes, return it directly (no wrapping)
|
|
469
|
+
if (prNotes.length === 1) {
|
|
470
|
+
return {
|
|
471
|
+
notes: prNotes[0].content,
|
|
472
|
+
notesSource: 'file',
|
|
473
|
+
prCount: 1,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Multiple PRs: combine into one document (newest first, already in log order)
|
|
478
|
+
const pkg = JSON.parse(readFileSync(join(repoPath, 'package.json'), 'utf8'));
|
|
479
|
+
const name = pkg.name?.replace(/^@[^/]+\//, '') || basename(repoPath);
|
|
480
|
+
|
|
481
|
+
const sections = [];
|
|
482
|
+
sections.push(`# Release Notes: ${name} v${newVersion}`);
|
|
483
|
+
sections.push('');
|
|
484
|
+
sections.push(`This release combines ${prNotes.length} merged pull requests.`);
|
|
485
|
+
sections.push('');
|
|
486
|
+
|
|
487
|
+
// Collect all issue refs for a combined summary
|
|
488
|
+
const allIssueRefs = new Set();
|
|
489
|
+
|
|
490
|
+
for (const pr of prNotes) {
|
|
491
|
+
sections.push(`---`);
|
|
492
|
+
sections.push('');
|
|
493
|
+
sections.push(`### PR #${pr.prNum}`);
|
|
494
|
+
sections.push('');
|
|
495
|
+
|
|
496
|
+
// Strip the top-level heading from individual notes to avoid duplicate titles
|
|
497
|
+
let body = pr.content;
|
|
498
|
+
body = body.replace(/^#\s+.*\n+/, '');
|
|
499
|
+
sections.push(body);
|
|
500
|
+
sections.push('');
|
|
501
|
+
|
|
502
|
+
// Collect issue references
|
|
503
|
+
const refs = body.match(/#\d+/g) || [];
|
|
504
|
+
for (const ref of refs) allIssueRefs.add(ref);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Add combined issue references at the end
|
|
508
|
+
if (allIssueRefs.size > 0) {
|
|
509
|
+
sections.push('---');
|
|
510
|
+
sections.push('');
|
|
511
|
+
sections.push('## All issues referenced');
|
|
512
|
+
sections.push('');
|
|
513
|
+
for (const ref of allIssueRefs) {
|
|
514
|
+
sections.push(`- ${ref}`);
|
|
515
|
+
}
|
|
516
|
+
sections.push('');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
notes: sections.join('\n'),
|
|
521
|
+
notesSource: 'file',
|
|
522
|
+
prCount: prNotes.length,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
371
526
|
/**
|
|
372
527
|
* Check if a file was modified in commits since the last git tag.
|
|
373
528
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/universal-installer",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.67",
|
|
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",
|