@yemi33/minions 0.1.1781 → 0.1.1783
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 +10 -0
- package/engine/cleanup.js +11 -10
- package/engine/copilot-models.json +1 -1
- package/engine/shared.js +37 -0
- package/engine.js +2 -14
- package/package.json +1 -1
- package/prompts/cc-system.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1783 (2026-05-07)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- forbid manual git worktree add in CC system prompt
|
|
7
|
+
|
|
8
|
+
## 0.1.1782 (2026-05-07)
|
|
9
|
+
|
|
10
|
+
### Other
|
|
11
|
+
- refactor(worktree): consolidate porcelain parsers behind shared helper
|
|
12
|
+
|
|
3
13
|
## 0.1.1781 (2026-05-07)
|
|
4
14
|
|
|
5
15
|
### Fixes
|
package/engine/cleanup.js
CHANGED
|
@@ -68,11 +68,13 @@ function isProtectedLocalBranch(branch, project = {}) {
|
|
|
68
68
|
|
|
69
69
|
function localBranchWorktreeInUse(root, branch) {
|
|
70
70
|
try {
|
|
71
|
-
const
|
|
71
|
+
const raw = String(shared.execSilent('git worktree list --porcelain', {
|
|
72
72
|
cwd: root, encoding: 'utf8', stdio: 'pipe', timeout: 10000, windowsHide: true,
|
|
73
73
|
}) || '');
|
|
74
|
-
return
|
|
74
|
+
return shared.parseWorktreePorcelain(raw).some(w => w.branch === branch);
|
|
75
75
|
} catch {
|
|
76
|
+
// Defensive: assume "in use" so the caller doesn't proceed to delete a
|
|
77
|
+
// branch when worktree state can't be enumerated.
|
|
76
78
|
return true;
|
|
77
79
|
}
|
|
78
80
|
}
|
|
@@ -386,17 +388,16 @@ async function runCleanup(config, verbose = false) {
|
|
|
386
388
|
log('warn', `Project "${project.name || root}" has localPath "${root}" which does not exist on disk — skipping worktree scan`);
|
|
387
389
|
continue;
|
|
388
390
|
}
|
|
389
|
-
let
|
|
391
|
+
let trees;
|
|
390
392
|
try {
|
|
391
|
-
raw = String(shared.execSilent('git worktree list --porcelain', { cwd: root, timeout: 10000, windowsHide: true }) || '');
|
|
393
|
+
const raw = String(shared.execSilent('git worktree list --porcelain', { cwd: root, timeout: 10000, windowsHide: true }) || '');
|
|
394
|
+
trees = shared.parseWorktreePorcelain(raw);
|
|
392
395
|
} catch (e) { log('warn', `nested-worktree scan for ${project.name || root}: ${e.message}`); continue; }
|
|
393
|
-
for (const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (!wt) continue;
|
|
397
|
-
if (!shared.isPathInside(wt, root)) continue; // strict — main worktree (equal path) is expected, descendants are the leak
|
|
396
|
+
for (const wt of trees) {
|
|
397
|
+
// Strict — main worktree (equal path) is expected, descendants are the leak.
|
|
398
|
+
if (!shared.isPathInside(wt.path, root)) continue;
|
|
398
399
|
cleaned.nestedWorktrees++;
|
|
399
|
-
log('warn', `Nested worktree in project "${project.name || root}": "${wt}" is inside "${root}". This causes glob tools to match both copies and produces mirror writes. Run: git worktree remove "${wt}"`);
|
|
400
|
+
log('warn', `Nested worktree in project "${project.name || root}": "${wt.path}" is inside "${root}". This causes glob tools to match both copies and produces mirror writes. Run: git worktree remove "${wt.path}"`);
|
|
400
401
|
}
|
|
401
402
|
}
|
|
402
403
|
|
package/engine/shared.js
CHANGED
|
@@ -2177,6 +2177,42 @@ function isPathInsideOrEqual(childPath, parentPath) {
|
|
|
2177
2177
|
return isPathInside(childAbs, parentAbs);
|
|
2178
2178
|
}
|
|
2179
2179
|
|
|
2180
|
+
/**
|
|
2181
|
+
* Parse `git worktree list --porcelain` output. Pure — callers run the
|
|
2182
|
+
* subprocess (sync `execSilent` or async `execAsync`) and feed stdout in.
|
|
2183
|
+
*
|
|
2184
|
+
* Returns `[{path, head, branch, bare, detached, locked, prunable}]`.
|
|
2185
|
+
* `branch` strips the `refs/heads/` prefix; flag fields default to false.
|
|
2186
|
+
*
|
|
2187
|
+
* Porcelain format: each worktree is a block of `key value\n` lines
|
|
2188
|
+
* terminated by a blank line. First line of every block is `worktree <path>`
|
|
2189
|
+
* (always present). Optional follow-ups: `HEAD <sha>`, `branch refs/heads/<name>`,
|
|
2190
|
+
* and the flag lines `bare`, `detached`, `locked [<reason>]`, `prunable [<reason>]`.
|
|
2191
|
+
*/
|
|
2192
|
+
function parseWorktreePorcelain(raw) {
|
|
2193
|
+
const trees = [];
|
|
2194
|
+
if (!raw) return trees;
|
|
2195
|
+
let current = null;
|
|
2196
|
+
const flush = () => { if (current) { trees.push(current); current = null; } };
|
|
2197
|
+
for (const line of String(raw).split(/\r?\n/)) {
|
|
2198
|
+
if (line === '') { flush(); continue; }
|
|
2199
|
+
if (line.startsWith('worktree ')) {
|
|
2200
|
+
flush();
|
|
2201
|
+
current = { path: line.slice('worktree '.length).trim(), head: '', branch: '', bare: false, detached: false, locked: false, prunable: false };
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
if (!current) continue;
|
|
2205
|
+
if (line.startsWith('HEAD ')) current.head = line.slice('HEAD '.length).trim();
|
|
2206
|
+
else if (line.startsWith('branch ')) current.branch = line.slice('branch '.length).trim().replace(/^refs\/heads\//, '');
|
|
2207
|
+
else if (line === 'bare') current.bare = true;
|
|
2208
|
+
else if (line === 'detached') current.detached = true;
|
|
2209
|
+
else if (line === 'locked' || line.startsWith('locked ')) current.locked = true;
|
|
2210
|
+
else if (line === 'prunable' || line.startsWith('prunable ')) current.prunable = true;
|
|
2211
|
+
}
|
|
2212
|
+
flush();
|
|
2213
|
+
return trees;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2180
2216
|
/**
|
|
2181
2217
|
* Throws when `worktreePath` would land inside (or equal) `projectRoot`.
|
|
2182
2218
|
* Called by the engine spawn path before `git worktree add`, and by the
|
|
@@ -3269,6 +3305,7 @@ module.exports = {
|
|
|
3269
3305
|
buildWorktreeDirName, // exported for testing
|
|
3270
3306
|
isPathInside,
|
|
3271
3307
|
isPathInsideOrEqual,
|
|
3308
|
+
parseWorktreePorcelain,
|
|
3272
3309
|
assertWorktreeOutsideProject,
|
|
3273
3310
|
isLiveCommandCenterPath,
|
|
3274
3311
|
describeCcProtectedPaths,
|
package/engine.js
CHANGED
|
@@ -474,20 +474,8 @@ async function syncReusedWorktree(rootDir, worktreePath, branchName, gitOpts = {
|
|
|
474
474
|
async function findExistingWorktree(repoDir, branchName) {
|
|
475
475
|
try {
|
|
476
476
|
const out = await execAsync(`git worktree list --porcelain`, { cwd: repoDir, timeout: 10000 });
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
for (let i = 0; i < lines.length; i++) {
|
|
480
|
-
if (lines[i].trim() === branchRef) {
|
|
481
|
-
// Walk back to find the worktree path
|
|
482
|
-
for (let j = i - 1; j >= 0; j--) {
|
|
483
|
-
if (lines[j].startsWith('worktree ')) {
|
|
484
|
-
const wtPath = lines[j].slice('worktree '.length).trim();
|
|
485
|
-
if (fs.existsSync(wtPath)) return wtPath;
|
|
486
|
-
break;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
477
|
+
const found = shared.parseWorktreePorcelain(out).find(w => w.branch === branchName);
|
|
478
|
+
if (found && fs.existsSync(found.path)) return found.path;
|
|
491
479
|
} catch (e) { log('warn', 'git: ' + e.message); }
|
|
492
480
|
return null;
|
|
493
481
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1783",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|
package/prompts/cc-system.md
CHANGED
|
@@ -155,6 +155,7 @@ Terms like schedules, pipelines, agents, inbox, work items, plans, PRD, PRs, dis
|
|
|
155
155
|
2. Be specific — cite IDs, names, filenames, line numbers.
|
|
156
156
|
3. Never modify engine source. Never push to git without user confirmation.
|
|
157
157
|
4. Estimate first, then act (see Role: Orchestrator). For Medium-and-above tasks, your tools are for orientation; the agent does the work. For Small tasks, you may do them yourself.
|
|
158
|
+
5. **Never run `git worktree add` directly.** Worktrees are managed by the engine — they get created automatically when you dispatch an `implement`/`fix`/`test`/`verify`/`review` agent (the engine places them at `engine.worktreeRoot`, default `../worktrees/`, never inside the project tree). A worktree placed inside a project root causes glob/grep tools to match both copies of every file and produces silent mirror-write leaks. If you need work done on a branch, dispatch an agent with the appropriate type — do not manually `git worktree add`, `git worktree remove`, or shell out around the engine's worktree management.
|
|
158
159
|
|
|
159
160
|
## API & CLI Index (auto-injected)
|
|
160
161
|
Your state preamble (delivered alongside this prompt at session start) carries an auto-generated **API Index** rendered from `dashboard.js` `ROUTES` and a **CLI Index** rendered from `engine/cli.js` `CLI_COMMAND_DOCS`. Both are single-source-of-truth — adding a new HTTP endpoint or CLI command auto-surfaces it in your preamble; do not memorize the named action shorthand list above as exhaustive.
|