@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 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 out = String(shared.execSilent('git worktree list --porcelain', {
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 out.split(/\r?\n/).some(line => line.trim() === `branch refs/heads/${branch}`);
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 raw;
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 line of raw.split('\n')) {
394
- if (!line.startsWith('worktree ')) continue;
395
- const wt = line.slice('worktree '.length).trim();
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
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-07T23:33:01.432Z"
4
+ "cachedAt": "2026-05-07T23:48:12.752Z"
5
5
  }
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 branchRef = `branch refs/heads/${branchName}`;
478
- const lines = out.split('\n');
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.1781",
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"
@@ -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.