@wipcomputer/wip-ai-devops-toolbox 1.9.45 → 1.9.47
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 +86 -0
- package/DEV-GUIDE-GENERAL-PUBLIC.md +29 -3
- package/SKILL.md +1 -1
- package/_trash/RELEASE-NOTES-v1-9-41.md +28 -0
- package/_trash/RELEASE-NOTES-v1-9-46.md +38 -0
- package/_trash/RELEASE-NOTES-v1-9-47.md +42 -0
- package/package.json +1 -1
- package/templates/global-claude-md.md +73 -0
- package/templates/repo-claude-md.template +24 -0
- package/tools/deploy-public/package.json +1 -1
- package/tools/post-merge-rename/package.json +1 -1
- package/tools/wip-branch-guard/guard.mjs +15 -0
- package/tools/wip-branch-guard/package.json +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/core.mjs +33 -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/claude.mjs +248 -0
- package/tools/wip-repos/cli.mjs +6 -0
- package/tools/wip-repos/package.json +1 -1
- package/tools/wip-universal-installer/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,92 @@
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
## 1.9.47 (2026-03-20)
|
|
36
|
+
|
|
37
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.47
|
|
38
|
+
|
|
39
|
+
**New: `wip-repos claude` command + CLAUDE.md templates.**
|
|
40
|
+
|
|
41
|
+
## What changed
|
|
42
|
+
|
|
43
|
+
### `wip-repos claude` (Phases 1-3 of the CLAUDE.md plan)
|
|
44
|
+
|
|
45
|
+
New subcommand that generates cross-repo ecosystem sections in CLAUDE.md files. When an agent opens repo-A, it can't read repo-B. This command pre-generates the context.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
wip-repos claude # regenerate all repos
|
|
49
|
+
wip-repos claude my-repo # regenerate one repo
|
|
50
|
+
wip-repos claude --init # create CLAUDE.md for repos missing one
|
|
51
|
+
wip-repos claude --dry-run # preview changes
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Features:
|
|
55
|
+
- Reads all repos from manifest, extracts metadata (package.json, SKILL.md, directory structure)
|
|
56
|
+
- Generates `## Ecosystem` sections with delimiter comments (`<!-- wip-repos:start/end -->`)
|
|
57
|
+
- Hand-written sections are never overwritten
|
|
58
|
+
- Relevance filtering: only related repos shown (same category + core repos)
|
|
59
|
+
- `--init` creates starter CLAUDE.md from template for repos missing one
|
|
60
|
+
|
|
61
|
+
### Templates
|
|
62
|
+
|
|
63
|
+
- `templates/global-claude-md.md` ... universal CLAUDE.md for ~/.claude/CLAUDE.md
|
|
64
|
+
- `templates/repo-claude-md.template` ... per-repo starter with ecosystem placeholder
|
|
65
|
+
|
|
66
|
+
## Why
|
|
67
|
+
|
|
68
|
+
Agents lose context across repos. They can't read sibling repos at runtime. Pre-generating cross-repo maps into CLAUDE.md solves this without requiring runtime access.
|
|
69
|
+
|
|
70
|
+
## Issues closed
|
|
71
|
+
|
|
72
|
+
- #212 (partial: Phases 1-3 of 6)
|
|
73
|
+
|
|
74
|
+
## How to verify
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
wip-repos claude --dry-run
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 1.9.46 (2026-03-18)
|
|
81
|
+
|
|
82
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.46
|
|
83
|
+
|
|
84
|
+
**Centralized worktree management: guard rule, wip-release prune, Dev Guide convention.**
|
|
85
|
+
|
|
86
|
+
## What changed
|
|
87
|
+
|
|
88
|
+
### Guard: worktree path warning (#212)
|
|
89
|
+
Branch guard now warns when `git worktree add` creates a worktree outside `_worktrees/`. Shows the convention and suggests `ldm worktree add`. Warning only, not a hard block.
|
|
90
|
+
|
|
91
|
+
### wip-release: worktree prune (#212)
|
|
92
|
+
New step 12 in the release pipeline. After branch cleanup, prunes stale worktrees from `_worktrees/` whose branches are merged into main. Automatic cleanup after every release.
|
|
93
|
+
|
|
94
|
+
### Dev Guide: _worktrees/ convention (#212)
|
|
95
|
+
Documents the centralized worktree convention:
|
|
96
|
+
- All worktrees go in `_worktrees/<repo-name>--<branch-suffix>/`
|
|
97
|
+
- Use `ldm worktree add` (auto-detects repo, creates in the right place)
|
|
98
|
+
- Guard warns about worktrees outside the convention
|
|
99
|
+
- `wip-release` auto-prunes merged worktrees
|
|
100
|
+
|
|
101
|
+
## Why
|
|
102
|
+
|
|
103
|
+
Worktrees created as repo siblings confused iCloud sync, looked like real repos in directory listings, and were never cleaned up. This session alone created 10+ stale worktrees. The convention keeps them organized and the release pipeline cleans them automatically.
|
|
104
|
+
|
|
105
|
+
## Issues closed
|
|
106
|
+
|
|
107
|
+
- #212
|
|
108
|
+
- #213
|
|
109
|
+
|
|
110
|
+
## How to verify
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Guard warning:
|
|
114
|
+
cd /path/to/repo
|
|
115
|
+
git worktree add ../my-worktree -b test # should warn about _worktrees/
|
|
116
|
+
|
|
117
|
+
# Correct path:
|
|
118
|
+
ldm worktree add cc-mini/test # creates _worktrees/<repo>--cc-mini--test/
|
|
119
|
+
```
|
|
34
120
|
|
|
35
121
|
## 1.9.45 (2026-03-18)
|
|
36
122
|
|
|
@@ -524,16 +524,42 @@ gh api "repos/<org>/<repo>/branches/main/protection" -X PUT \
|
|
|
524
524
|
|
|
525
525
|
The main working tree stays on `main` and is read-only in practice. All development happens in git worktrees. This keeps the primary clone clean, prevents accidental commits to main, and enables parallel work within a single agent.
|
|
526
526
|
|
|
527
|
+
### Worktree Location Convention
|
|
528
|
+
|
|
529
|
+
**All worktrees go in `_worktrees/` as a sibling to the repos directory.** Never create worktrees as siblings to repos directly. They look like real repos, confuse iCloud sync, and are hard to find/clean.
|
|
530
|
+
|
|
531
|
+
**Convention:** `_worktrees/<repo-name>--<branch-suffix>/`
|
|
532
|
+
|
|
533
|
+
```
|
|
534
|
+
repos/
|
|
535
|
+
ldm-os/
|
|
536
|
+
components/
|
|
537
|
+
memory-crystal-private/ <- real repo
|
|
538
|
+
devops/
|
|
539
|
+
wip-ai-devops-toolbox-private/ <- real repo
|
|
540
|
+
_worktrees/
|
|
541
|
+
memory-crystal-private--cc-mini--fix-search/ <- worktree
|
|
542
|
+
wip-ai-devops-toolbox-private--cc-mini--guard/ <- worktree
|
|
543
|
+
```
|
|
544
|
+
|
|
527
545
|
### Starting a Worktree
|
|
528
546
|
|
|
529
|
-
|
|
547
|
+
**Preferred:** Use the `ldm worktree` command:
|
|
548
|
+
```bash
|
|
549
|
+
ldm worktree add cc-mini/fix-bug # auto-detects repo, creates in _worktrees/
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**From Claude Code:**
|
|
530
553
|
```bash
|
|
531
554
|
claude --worktree <name>
|
|
532
555
|
```
|
|
533
556
|
|
|
534
|
-
|
|
557
|
+
**Manual:**
|
|
558
|
+
```bash
|
|
559
|
+
git worktree add ../_worktrees/<repo>--<branch> -b <branch>
|
|
560
|
+
```
|
|
535
561
|
|
|
536
|
-
|
|
562
|
+
The branch guard warns if you create worktrees outside `_worktrees/` or `.claude/worktrees/`.
|
|
537
563
|
|
|
538
564
|
### Branch Naming
|
|
539
565
|
|
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.47"
|
|
9
9
|
homepage: "https://github.com/wipcomputer/wip-ai-devops-toolbox"
|
|
10
10
|
author: "Parker Todd Brooks"
|
|
11
11
|
category: dev-tools
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.41
|
|
2
|
+
|
|
3
|
+
**One-line summary of what this release does**
|
|
4
|
+
|
|
5
|
+
## What changed
|
|
6
|
+
|
|
7
|
+
Describe the changes. Not a commit list. Explain:
|
|
8
|
+
- What was built or fixed
|
|
9
|
+
- Why it matters
|
|
10
|
+
- What the user should know
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
What problem does this solve? What was broken or missing?
|
|
15
|
+
|
|
16
|
+
## Issues closed
|
|
17
|
+
|
|
18
|
+
- #251
|
|
19
|
+
- #117
|
|
20
|
+
- #128
|
|
21
|
+
- #250
|
|
22
|
+
- #73
|
|
23
|
+
|
|
24
|
+
## How to verify
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Commands to test the changes
|
|
28
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.46
|
|
2
|
+
|
|
3
|
+
**Centralized worktree management: guard rule, wip-release prune, Dev Guide convention.**
|
|
4
|
+
|
|
5
|
+
## What changed
|
|
6
|
+
|
|
7
|
+
### Guard: worktree path warning (#212)
|
|
8
|
+
Branch guard now warns when `git worktree add` creates a worktree outside `_worktrees/`. Shows the convention and suggests `ldm worktree add`. Warning only, not a hard block.
|
|
9
|
+
|
|
10
|
+
### wip-release: worktree prune (#212)
|
|
11
|
+
New step 12 in the release pipeline. After branch cleanup, prunes stale worktrees from `_worktrees/` whose branches are merged into main. Automatic cleanup after every release.
|
|
12
|
+
|
|
13
|
+
### Dev Guide: _worktrees/ convention (#212)
|
|
14
|
+
Documents the centralized worktree convention:
|
|
15
|
+
- All worktrees go in `_worktrees/<repo-name>--<branch-suffix>/`
|
|
16
|
+
- Use `ldm worktree add` (auto-detects repo, creates in the right place)
|
|
17
|
+
- Guard warns about worktrees outside the convention
|
|
18
|
+
- `wip-release` auto-prunes merged worktrees
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
Worktrees created as repo siblings confused iCloud sync, looked like real repos in directory listings, and were never cleaned up. This session alone created 10+ stale worktrees. The convention keeps them organized and the release pipeline cleans them automatically.
|
|
23
|
+
|
|
24
|
+
## Issues closed
|
|
25
|
+
|
|
26
|
+
- #212
|
|
27
|
+
- #213
|
|
28
|
+
|
|
29
|
+
## How to verify
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Guard warning:
|
|
33
|
+
cd /path/to/repo
|
|
34
|
+
git worktree add ../my-worktree -b test # should warn about _worktrees/
|
|
35
|
+
|
|
36
|
+
# Correct path:
|
|
37
|
+
ldm worktree add cc-mini/test # creates _worktrees/<repo>--cc-mini--test/
|
|
38
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.47
|
|
2
|
+
|
|
3
|
+
**New: `wip-repos claude` command + CLAUDE.md templates.**
|
|
4
|
+
|
|
5
|
+
## What changed
|
|
6
|
+
|
|
7
|
+
### `wip-repos claude` (Phases 1-3 of the CLAUDE.md plan)
|
|
8
|
+
|
|
9
|
+
New subcommand that generates cross-repo ecosystem sections in CLAUDE.md files. When an agent opens repo-A, it can't read repo-B. This command pre-generates the context.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
wip-repos claude # regenerate all repos
|
|
13
|
+
wip-repos claude my-repo # regenerate one repo
|
|
14
|
+
wip-repos claude --init # create CLAUDE.md for repos missing one
|
|
15
|
+
wip-repos claude --dry-run # preview changes
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Features:
|
|
19
|
+
- Reads all repos from manifest, extracts metadata (package.json, SKILL.md, directory structure)
|
|
20
|
+
- Generates `## Ecosystem` sections with delimiter comments (`<!-- wip-repos:start/end -->`)
|
|
21
|
+
- Hand-written sections are never overwritten
|
|
22
|
+
- Relevance filtering: only related repos shown (same category + core repos)
|
|
23
|
+
- `--init` creates starter CLAUDE.md from template for repos missing one
|
|
24
|
+
|
|
25
|
+
### Templates
|
|
26
|
+
|
|
27
|
+
- `templates/global-claude-md.md` ... universal CLAUDE.md for ~/.claude/CLAUDE.md
|
|
28
|
+
- `templates/repo-claude-md.template` ... per-repo starter with ecosystem placeholder
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
Agents lose context across repos. They can't read sibling repos at runtime. Pre-generating cross-repo maps into CLAUDE.md solves this without requiring runtime access.
|
|
33
|
+
|
|
34
|
+
## Issues closed
|
|
35
|
+
|
|
36
|
+
- #212 (partial: Phases 1-3 of 6)
|
|
37
|
+
|
|
38
|
+
## How to verify
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
wip-repos claude --dry-run
|
|
42
|
+
```
|
package/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# CLAUDE.md — Global (WIP Computer)
|
|
2
|
+
|
|
3
|
+
## Team
|
|
4
|
+
|
|
5
|
+
WIP Computer. Three contributors on every commit:
|
|
6
|
+
- Parker Todd Brooks (human, parkertoddbrooks)
|
|
7
|
+
- Lesa (OpenClaw agent, lesaai)
|
|
8
|
+
- Claude Code (Claude Opus 4.6)
|
|
9
|
+
|
|
10
|
+
## Git Rules
|
|
11
|
+
|
|
12
|
+
**Never push directly to main.** Branch, PR, merge. Every time.
|
|
13
|
+
**Never squash merge.** Always `--merge`. Squashing destroys co-author attribution.
|
|
14
|
+
**Always include `--delete-branch`** on `gh pr merge`.
|
|
15
|
+
**Co-authors on every commit:**
|
|
16
|
+
```
|
|
17
|
+
Co-Authored-By: Parker Todd Brooks <parkertoddbrooks@users.noreply.github.com>
|
|
18
|
+
Co-Authored-By: Lesa <lesaai@icloud.com>
|
|
19
|
+
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Branch Prefixes
|
|
23
|
+
|
|
24
|
+
`cc-mini/` for Claude Code on Mac mini. `cc-air/` for MacBook Air. `lesa-mini/` for Lesa.
|
|
25
|
+
|
|
26
|
+
## Worktree Workflow
|
|
27
|
+
|
|
28
|
+
All edits happen in worktrees. Never edit directly on main.
|
|
29
|
+
```bash
|
|
30
|
+
ldm worktree add cc-mini/feature-name # preferred
|
|
31
|
+
# or: git worktree add ../_worktrees/<repo>--<branch> -b <branch>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Writing Style
|
|
35
|
+
|
|
36
|
+
**Never use em dashes.** Use periods, colons, semicolons, or "..." instead.
|
|
37
|
+
|
|
38
|
+
## Release Process
|
|
39
|
+
|
|
40
|
+
1. Branch + commit + push
|
|
41
|
+
2. Release notes file on the branch: `RELEASE-NOTES-v{version}.md` (dashes, not dots)
|
|
42
|
+
3. `gh pr create` then `gh pr merge --merge --delete-branch`
|
|
43
|
+
4. `git checkout main && git pull`
|
|
44
|
+
5. `wip-release patch` (auto-detects release notes file)
|
|
45
|
+
6. `deploy-public.sh` to sync public repo
|
|
46
|
+
|
|
47
|
+
**Release notes go on the feature branch, committed with the code.** Not as a separate PR.
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
- `wip-release` for releases (version bump, changelog, npm, GitHub release)
|
|
52
|
+
- `wip-file-guard` protects CLAUDE.md, SOUL.md, MEMORY.md, SHARED-CONTEXT.md
|
|
53
|
+
- `wip-branch-guard` blocks writes on main, teaches the workflow
|
|
54
|
+
- `ldm install` for installing/updating extensions
|
|
55
|
+
- `ldm doctor` for health checks
|
|
56
|
+
|
|
57
|
+
## Exclude from npm
|
|
58
|
+
|
|
59
|
+
Always add to `.npmignore`:
|
|
60
|
+
```
|
|
61
|
+
CLAUDE.md
|
|
62
|
+
ai/
|
|
63
|
+
.claude/
|
|
64
|
+
_worktrees/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Boot Sequence
|
|
68
|
+
|
|
69
|
+
Read the repo's own CLAUDE.md first. Then check `ai/read-me-first.md` if it exists.
|
|
70
|
+
|
|
71
|
+
## Dev Guide
|
|
72
|
+
|
|
73
|
+
Full conventions: `DEV-GUIDE-GENERAL-PUBLIC.md` in wip-ai-devops-toolbox.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# CLAUDE.md — {{repo-name}}
|
|
2
|
+
|
|
3
|
+
## Boot Sequence
|
|
4
|
+
1. Read this file completely before acting
|
|
5
|
+
2. Read `ai/read-me-first.md` if it exists
|
|
6
|
+
3. Never commit directly to main. Use worktrees.
|
|
7
|
+
|
|
8
|
+
## Project Overview
|
|
9
|
+
- **Description:** {{description}}
|
|
10
|
+
- **Language:** {{language}}
|
|
11
|
+
- **Test:** `{{scripts.test}}`
|
|
12
|
+
- **Build:** `{{scripts.build}}`
|
|
13
|
+
- **Lint:** `{{scripts.lint}}`
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
{{detected-dirs}}
|
|
17
|
+
|
|
18
|
+
## Repo-Specific Guardrails
|
|
19
|
+
{{guardrails}}
|
|
20
|
+
|
|
21
|
+
## Ecosystem
|
|
22
|
+
<!-- wip-repos:start -->
|
|
23
|
+
_Run `wip-repos claude` to populate this section with cross-repo context._
|
|
24
|
+
<!-- wip-repos:end -->
|
|
@@ -225,6 +225,21 @@ async function main() {
|
|
|
225
225
|
}
|
|
226
226
|
} catch {}
|
|
227
227
|
}
|
|
228
|
+
|
|
229
|
+
// Warn when creating worktrees outside _worktrees/ (#212)
|
|
230
|
+
const wtMatch = cmd.match(/\bgit\s+worktree\s+add\s+["']?([^\s"']+)/);
|
|
231
|
+
if (wtMatch) {
|
|
232
|
+
const wtPath = wtMatch[1];
|
|
233
|
+
if (!wtPath.includes('_worktrees') && !wtPath.includes('.claude/worktrees')) {
|
|
234
|
+
deny(`WARNING: Creating worktree outside _worktrees/. Use: ldm worktree add <branch>
|
|
235
|
+
|
|
236
|
+
The convention is _worktrees/<repo>--<branch>/ so worktrees don't mix with real repos.
|
|
237
|
+
Manual equivalent: git worktree add ../_worktrees/<repo>--<branch> -b <branch>
|
|
238
|
+
|
|
239
|
+
This is a warning, not a block. If you need to create it here, retry.`);
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
// Determine which repo to check.
|
|
@@ -1579,6 +1579,39 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1579
1579
|
console.log(` ! Branch prune skipped: ${e.message}`);
|
|
1580
1580
|
}
|
|
1581
1581
|
|
|
1582
|
+
// 12. Prune stale worktrees (#212)
|
|
1583
|
+
try {
|
|
1584
|
+
execSync('git worktree prune', { cwd: repoPath, stdio: 'pipe' });
|
|
1585
|
+
// Also check _worktrees/ for dirs whose branches are now merged
|
|
1586
|
+
const worktreesDir = join(dirname(repoPath), '_worktrees');
|
|
1587
|
+
if (existsSync(worktreesDir)) {
|
|
1588
|
+
const repoBase = basename(repoPath);
|
|
1589
|
+
const wtDirs = readdirSync(worktreesDir, { withFileTypes: true })
|
|
1590
|
+
.filter(d => d.isDirectory() && d.name.startsWith(repoBase + '--'));
|
|
1591
|
+
let wtPruned = 0;
|
|
1592
|
+
for (const d of wtDirs) {
|
|
1593
|
+
const wtPath = join(worktreesDir, d.name);
|
|
1594
|
+
try {
|
|
1595
|
+
// Check if branch is merged into main
|
|
1596
|
+
const branch = execSync('git branch --show-current', {
|
|
1597
|
+
cwd: wtPath, encoding: 'utf8', timeout: 3000
|
|
1598
|
+
}).trim();
|
|
1599
|
+
if (branch) {
|
|
1600
|
+
execSync(`git merge-base --is-ancestor "${branch}" main`, {
|
|
1601
|
+
cwd: repoPath, stdio: 'pipe', timeout: 5000
|
|
1602
|
+
});
|
|
1603
|
+
// Branch is merged. Remove worktree.
|
|
1604
|
+
execSync(`git worktree remove "${wtPath}"`, { cwd: repoPath, stdio: 'pipe' });
|
|
1605
|
+
wtPruned++;
|
|
1606
|
+
}
|
|
1607
|
+
} catch {} // Branch not merged or other issue, leave it
|
|
1608
|
+
}
|
|
1609
|
+
if (wtPruned > 0) {
|
|
1610
|
+
console.log(` ✓ Pruned ${wtPruned} merged worktree(s) from _worktrees/`);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
} catch {}
|
|
1614
|
+
|
|
1582
1615
|
// Write release marker so branch guard blocks immediate install (#73)
|
|
1583
1616
|
try {
|
|
1584
1617
|
const markerDir = join(process.env.HOME || '', '.ldm', 'state');
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wip-repos claude ... generate cross-repo CLAUDE.md ecosystem sections
|
|
3
|
+
*
|
|
4
|
+
* Reads all repos on disk via the manifest, extracts metadata from each,
|
|
5
|
+
* and writes an ## Ecosystem section into each repo's CLAUDE.md.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* wip-repos claude # regenerate all repos
|
|
9
|
+
* wip-repos claude <repo> # regenerate one repo
|
|
10
|
+
* wip-repos claude --init # create CLAUDE.md for repos missing one
|
|
11
|
+
* wip-repos claude --dry-run # preview changes
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'node:fs';
|
|
15
|
+
import { join, basename, resolve, dirname } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const TEMPLATE_PATH = join(__dirname, '..', '..', 'templates', 'repo-claude-md.template');
|
|
20
|
+
const START_MARKER = '<!-- wip-repos:start -->';
|
|
21
|
+
const END_MARKER = '<!-- wip-repos:end -->';
|
|
22
|
+
|
|
23
|
+
function readJSON(path) {
|
|
24
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract metadata from a single repo on disk.
|
|
29
|
+
*/
|
|
30
|
+
function extractRepoMeta(repoPath) {
|
|
31
|
+
const pkg = readJSON(join(repoPath, 'package.json'));
|
|
32
|
+
const name = pkg?.name || basename(repoPath);
|
|
33
|
+
const description = pkg?.description || '';
|
|
34
|
+
const version = pkg?.version || '';
|
|
35
|
+
const exports = pkg?.exports ? Object.keys(pkg.exports) : [];
|
|
36
|
+
const binCommands = pkg?.bin ? Object.keys(pkg.bin) : [];
|
|
37
|
+
const scripts = pkg?.scripts || {};
|
|
38
|
+
|
|
39
|
+
// Detect interfaces
|
|
40
|
+
const interfaces = [];
|
|
41
|
+
if (binCommands.length > 0) interfaces.push('CLI');
|
|
42
|
+
if (pkg?.main || pkg?.exports) interfaces.push('Module');
|
|
43
|
+
if (existsSync(join(repoPath, 'mcp-server.mjs')) || existsSync(join(repoPath, 'dist', 'mcp-server.js'))) interfaces.push('MCP');
|
|
44
|
+
if (existsSync(join(repoPath, 'openclaw.plugin.json'))) interfaces.push('OpenClaw Plugin');
|
|
45
|
+
if (existsSync(join(repoPath, 'SKILL.md'))) interfaces.push('Skill');
|
|
46
|
+
|
|
47
|
+
// Detect key dirs
|
|
48
|
+
const dirs = [];
|
|
49
|
+
for (const d of ['src', 'lib', 'tools', 'bin', 'dist', 'test', 'scripts', 'ai']) {
|
|
50
|
+
if (existsSync(join(repoPath, d))) dirs.push(d + '/');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
name,
|
|
55
|
+
description,
|
|
56
|
+
version,
|
|
57
|
+
interfaces,
|
|
58
|
+
binCommands,
|
|
59
|
+
dirs,
|
|
60
|
+
scripts,
|
|
61
|
+
path: repoPath,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Determine which repos are relevant to a given repo.
|
|
67
|
+
* Returns only repos in the same category or that share dependencies.
|
|
68
|
+
*/
|
|
69
|
+
function filterRelevant(targetMeta, allMetas, manifest) {
|
|
70
|
+
// Find target's category from manifest
|
|
71
|
+
const targetBase = basename(targetMeta.path);
|
|
72
|
+
let targetCategory = null;
|
|
73
|
+
|
|
74
|
+
for (const [category, repos] of Object.entries(manifest.repos || {})) {
|
|
75
|
+
if (repos.some(r => r.local?.includes(targetBase) || r.name === targetBase)) {
|
|
76
|
+
targetCategory = category;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Core repos every repo should know about
|
|
82
|
+
const coreNames = ['wip-ldm-os', 'wip-ai-devops-toolbox', 'memory-crystal'];
|
|
83
|
+
|
|
84
|
+
return allMetas.filter(m => {
|
|
85
|
+
if (m.path === targetMeta.path) return false; // skip self
|
|
86
|
+
const mBase = basename(m.path);
|
|
87
|
+
// Same category
|
|
88
|
+
if (targetCategory) {
|
|
89
|
+
for (const repos of Object.values(manifest.repos || {})) {
|
|
90
|
+
if (repos.some(r => r.local?.includes(mBase))) return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Core repo
|
|
94
|
+
if (coreNames.some(c => mBase.includes(c))) return true;
|
|
95
|
+
return false;
|
|
96
|
+
}).slice(0, 15); // cap at 15 to keep CLAUDE.md focused
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate the ecosystem section for a repo.
|
|
101
|
+
*/
|
|
102
|
+
function generateEcosystem(targetMeta, relevantMetas) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
for (const m of relevantMetas) {
|
|
105
|
+
const relPath = basename(m.path);
|
|
106
|
+
lines.push(`### ${m.name}`);
|
|
107
|
+
lines.push(`**Path:** \`${relPath}\``);
|
|
108
|
+
if (m.description) lines.push(m.description);
|
|
109
|
+
if (m.interfaces.length > 0) lines.push(`**Interfaces:** ${m.interfaces.join(', ')}`);
|
|
110
|
+
if (m.binCommands.length > 0) lines.push(`**CLI:** ${m.binCommands.join(', ')}`);
|
|
111
|
+
if (m.version) lines.push(`**Version:** ${m.version}`);
|
|
112
|
+
lines.push('');
|
|
113
|
+
}
|
|
114
|
+
return lines.join('\n').trim();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Update the ecosystem section in a CLAUDE.md file.
|
|
119
|
+
* Only replaces content between the delimiter comments.
|
|
120
|
+
*/
|
|
121
|
+
function updateEcosystemSection(claudeMdPath, ecosystemContent) {
|
|
122
|
+
const content = readFileSync(claudeMdPath, 'utf8');
|
|
123
|
+
const startIdx = content.indexOf(START_MARKER);
|
|
124
|
+
const endIdx = content.indexOf(END_MARKER);
|
|
125
|
+
|
|
126
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
127
|
+
// No delimiters found. Append section.
|
|
128
|
+
const section = `\n## Ecosystem (auto-generated by wip-repos claude)\n${START_MARKER}\n\n${ecosystemContent}\n\n${END_MARKER}\n`;
|
|
129
|
+
return content + section;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Replace between delimiters
|
|
133
|
+
const before = content.substring(0, startIdx + START_MARKER.length);
|
|
134
|
+
const after = content.substring(endIdx);
|
|
135
|
+
return `${before}\n\n${ecosystemContent}\n\n${after}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create a starter CLAUDE.md from the template.
|
|
140
|
+
*/
|
|
141
|
+
function createFromTemplate(repoPath, meta) {
|
|
142
|
+
const template = existsSync(TEMPLATE_PATH)
|
|
143
|
+
? readFileSync(TEMPLATE_PATH, 'utf8')
|
|
144
|
+
: '# CLAUDE.md\n\n## Ecosystem\n<!-- wip-repos:start -->\n<!-- wip-repos:end -->\n';
|
|
145
|
+
|
|
146
|
+
let content = template
|
|
147
|
+
.replace(/\{\{repo-name\}\}/g, meta.name)
|
|
148
|
+
.replace(/\{\{description\}\}/g, meta.description || 'No description')
|
|
149
|
+
.replace(/\{\{language\}\}/g, meta.scripts?.build?.includes('tsc') ? 'TypeScript' : 'JavaScript')
|
|
150
|
+
.replace(/\{\{scripts\.test\}\}/g, meta.scripts?.test || 'npm test')
|
|
151
|
+
.replace(/\{\{scripts\.build\}\}/g, meta.scripts?.build || 'npm run build')
|
|
152
|
+
.replace(/\{\{scripts\.lint\}\}/g, meta.scripts?.lint || 'npm run lint')
|
|
153
|
+
.replace(/\{\{detected-dirs\}\}/g, meta.dirs.length > 0 ? meta.dirs.map(d => `- \`${d}\``).join('\n') : '(no standard dirs detected)')
|
|
154
|
+
.replace(/\{\{guardrails\}\}/g, existsSync(join(repoPath, '.license-guard.json')) ? '- Dual-license (MIT + AGPL). See LICENSE.' : '');
|
|
155
|
+
|
|
156
|
+
return content;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Main entry point.
|
|
161
|
+
*/
|
|
162
|
+
export function runClaude(manifestPath, args = []) {
|
|
163
|
+
const dryRun = args.includes('--dry-run');
|
|
164
|
+
const init = args.includes('--init');
|
|
165
|
+
const targetRepo = args.find(a => !a.startsWith('--'));
|
|
166
|
+
|
|
167
|
+
const manifest = readJSON(manifestPath);
|
|
168
|
+
if (!manifest) {
|
|
169
|
+
console.error(' Could not read manifest at ' + manifestPath);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Find all repo paths from manifest
|
|
174
|
+
const manifestDir = dirname(manifestPath);
|
|
175
|
+
const repoPaths = [];
|
|
176
|
+
for (const [category, repos] of Object.entries(manifest.repos || {})) {
|
|
177
|
+
for (const repo of repos) {
|
|
178
|
+
const localPath = repo.local ? resolve(manifestDir, repo.local) : null;
|
|
179
|
+
if (localPath && existsSync(localPath)) {
|
|
180
|
+
repoPaths.push(localPath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (repoPaths.length === 0) {
|
|
186
|
+
console.log(' No repos found in manifest.');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Extract metadata from all repos
|
|
191
|
+
const allMetas = repoPaths.map(p => extractRepoMeta(p));
|
|
192
|
+
|
|
193
|
+
// Filter to target if specified
|
|
194
|
+
const targets = targetRepo
|
|
195
|
+
? allMetas.filter(m => basename(m.path).includes(targetRepo))
|
|
196
|
+
: allMetas;
|
|
197
|
+
|
|
198
|
+
if (targets.length === 0) {
|
|
199
|
+
console.error(` No repo matching "${targetRepo}" found.`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(` Processing ${targets.length} repo(s)...`);
|
|
204
|
+
let created = 0, updated = 0, skipped = 0;
|
|
205
|
+
|
|
206
|
+
for (const meta of targets) {
|
|
207
|
+
const claudeMdPath = join(meta.path, 'CLAUDE.md');
|
|
208
|
+
const repoName = basename(meta.path);
|
|
209
|
+
|
|
210
|
+
if (!existsSync(claudeMdPath)) {
|
|
211
|
+
if (init) {
|
|
212
|
+
const content = createFromTemplate(meta.path, meta);
|
|
213
|
+
if (dryRun) {
|
|
214
|
+
console.log(` [create] ${repoName}/CLAUDE.md`);
|
|
215
|
+
} else {
|
|
216
|
+
writeFileSync(claudeMdPath, content);
|
|
217
|
+
console.log(` + Created ${repoName}/CLAUDE.md`);
|
|
218
|
+
}
|
|
219
|
+
created++;
|
|
220
|
+
} else {
|
|
221
|
+
skipped++;
|
|
222
|
+
}
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Generate ecosystem section
|
|
227
|
+
const relevant = filterRelevant(meta, allMetas, manifest);
|
|
228
|
+
const ecosystem = generateEcosystem(meta, relevant);
|
|
229
|
+
const newContent = updateEcosystemSection(claudeMdPath, ecosystem);
|
|
230
|
+
const oldContent = readFileSync(claudeMdPath, 'utf8');
|
|
231
|
+
|
|
232
|
+
if (newContent === oldContent) {
|
|
233
|
+
skipped++;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (dryRun) {
|
|
238
|
+
console.log(` [update] ${repoName}/CLAUDE.md (${relevant.length} related repos)`);
|
|
239
|
+
} else {
|
|
240
|
+
writeFileSync(claudeMdPath, newContent);
|
|
241
|
+
console.log(` + Updated ${repoName}/CLAUDE.md (${relevant.length} related repos)`);
|
|
242
|
+
}
|
|
243
|
+
updated++;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.log('');
|
|
247
|
+
console.log(` ${dryRun ? 'Dry run' : 'Done'}. ${created} created, ${updated} updated, ${skipped} skipped.`);
|
|
248
|
+
}
|
package/tools/wip-repos/cli.mjs
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { check, planSync, executeSync, addRepo, moveRepo, generateReadmeTree, loadManifest } from './core.mjs';
|
|
15
|
+
import { runClaude } from './claude.mjs';
|
|
15
16
|
import { resolve, dirname, join } from 'node:path';
|
|
16
17
|
import { readFileSync } from 'node:fs';
|
|
17
18
|
import { fileURLToPath } from 'node:url';
|
|
@@ -173,6 +174,11 @@ try {
|
|
|
173
174
|
break;
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
case 'claude': {
|
|
178
|
+
runClaude(manifestPath, args.slice(1));
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
176
182
|
default:
|
|
177
183
|
usage();
|
|
178
184
|
if (command && command !== '--help' && command !== '-h') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/universal-installer",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.47",
|
|
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",
|