@yemi33/minions 0.1.201 → 0.1.203

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/minions.js +47 -27
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.203 (2026-04-02)
4
+
5
+ ### Other
6
+ - minions.js
7
+
8
+ ## 0.1.202 (2026-04-02)
9
+
10
+ ### Other
11
+ - minions.js
12
+
3
13
  ## 0.1.201 (2026-04-02)
4
14
 
5
15
  ### Engine
package/minions.js CHANGED
@@ -54,7 +54,12 @@ function autoDiscover(targetDir) {
54
54
 
55
55
  // 1. Detect main branch from git
56
56
  try {
57
- const head = execSync('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || git symbolic-ref HEAD', { cwd: targetDir, encoding: 'utf8', timeout: 5000 }).trim();
57
+ let head = '';
58
+ try {
59
+ head = execSync('git symbolic-ref refs/remotes/origin/HEAD', { cwd: targetDir, encoding: 'utf8', timeout: 5000 }).trim();
60
+ } catch {
61
+ head = execSync('git symbolic-ref HEAD', { cwd: targetDir, encoding: 'utf8', timeout: 5000 }).trim();
62
+ }
58
63
  const branch = head.replace('refs/remotes/origin/', '').replace('refs/heads/', '');
59
64
  if (branch) { result.mainBranch = branch; result._found.push('main branch'); }
60
65
  } catch {}
@@ -124,38 +129,29 @@ function buildPrUrlBase({ repoHost, org, project, repoName }) {
124
129
  if (repoHost === 'github') {
125
130
  return org && repoName ? `https://github.com/${org}/${repoName}/pull/` : '';
126
131
  }
127
- return org && project && repoName
128
- ? `https://${org}.visualstudio.com/DefaultCollection/${project}/_git/${repoName}/pullrequest/`
129
- : '';
132
+ if (repoHost === 'ado' && org && project && repoName) {
133
+ return `https://dev.azure.com/${org}/${project}/_git/${repoName}/pullrequest/`;
134
+ }
135
+ return '';
130
136
  }
131
137
 
132
138
  function buildProjectEntry({ name, description, localPath, repoHost, repositoryId, org, project, repoName, mainBranch }) {
139
+ // Sanitize name for use as directory name in projects/<name>/
140
+ const safeName = (name || 'project').replace(/[^a-zA-Z0-9._-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 60) || 'project';
133
141
  return {
134
- name,
142
+ name: safeName,
135
143
  description: description || '',
136
144
  localPath: (localPath || '').replace(/\\/g, '/'),
137
- repoHost: repoHost || 'ado',
145
+ repoHost: repoHost || 'github',
138
146
  repositoryId: repositoryId || '',
139
147
  adoOrg: org || '',
140
148
  adoProject: project || '',
141
149
  repoName: repoName || name,
142
150
  mainBranch: mainBranch || 'main',
143
151
  prUrlBase: buildPrUrlBase({ repoHost, org, project, repoName }),
144
- workSources: {
145
- pullRequests: { enabled: true, path: '.minions/pull-requests.json', cooldownMinutes: 30 },
146
- workItems: { enabled: true, path: '.minions/work-items.json', cooldownMinutes: 0 },
147
- }
148
152
  };
149
153
  }
150
154
 
151
- function ensureProjectStateFiles(projectPath) {
152
- const minionsDir = path.join(projectPath, '.minions');
153
- if (!fs.existsSync(minionsDir)) fs.mkdirSync(minionsDir, { recursive: true });
154
- for (const f of ['pull-requests.json', 'work-items.json']) {
155
- const fp = path.join(minionsDir, f);
156
- if (!fs.existsSync(fp)) fs.writeFileSync(fp, '[]');
157
- }
158
- }
159
155
 
160
156
  // ─── Commands ────────────────────────────────────────────────────────────────
161
157
 
@@ -190,7 +186,7 @@ async function addProject(targetDir) {
190
186
 
191
187
  const name = await ask('Project name', detected.name || path.basename(target));
192
188
  const description = await ask('Description (what this repo contains/does)', detected.description || '');
193
- const repoHost = await ask('Repo host (ado/github)', detected.repoHost || 'ado');
189
+ const repoHost = await ask('Repo host (github/ado)', detected.repoHost || 'github');
194
190
  const org = await ask('Organization', detected.org || '');
195
191
  const project = await ask('Project', detected.project || '');
196
192
  const repoName = await ask('Repo name', detected.repoName || name);
@@ -201,7 +197,6 @@ async function addProject(targetDir) {
201
197
 
202
198
  config.projects.push(buildProjectEntry({ name, description, localPath: target, repoHost, repositoryId, org, project, repoName, mainBranch }));
203
199
  saveConfig(config);
204
- ensureProjectStateFiles(target);
205
200
 
206
201
  console.log(`\n Linked "${name}" (${target})`);
207
202
  console.log(` Total projects: ${config.projects.length}`);
@@ -262,7 +257,9 @@ function findGitRepos(rootDir, maxDepth = 3) {
262
257
  // Skip common non-project dirs
263
258
  const base = path.basename(dir);
264
259
  if (['node_modules', '.git', '.hg', 'AppData', '$Recycle.Bin', 'Windows', 'Program Files',
265
- 'Program Files (x86)', '.cache', '.npm', '.yarn', '.nuget', 'worktrees'].includes(base)) return;
260
+ 'Program Files (x86)', '.cache', '.npm', '.yarn', '.nuget', 'worktrees', '.minions'].includes(base)) return;
261
+ // Skip minions home directory itself
262
+ if (path.resolve(dir) === path.resolve(MINIONS_HOME)) return;
266
263
 
267
264
  const gitDir = path.join(dir, '.git');
268
265
  if (fs.existsSync(gitDir)) {
@@ -380,14 +377,22 @@ async function scanAndAdd({ root, depth } = {}) {
380
377
 
381
378
  console.log(`\n Adding ${toAdd.length} project(s)...\n`);
382
379
 
380
+ const existingNames = new Set((config.projects || []).map(p => p.name));
383
381
  for (const repo of toAdd) {
382
+ // Deduplicate names — append -2, -3 etc. if name already taken
383
+ let name = repo.name;
384
+ if (existingNames.has(name)) {
385
+ let i = 2;
386
+ while (existingNames.has(name + '-' + i)) i++;
387
+ name = name + '-' + i;
388
+ }
389
+ existingNames.add(name);
384
390
  config.projects.push(buildProjectEntry({
385
- name: repo.name, description: repo.description, localPath: repo.path,
391
+ name, description: repo.description, localPath: repo.path,
386
392
  repoHost: repo.host, org: repo.org, project: repo.project,
387
393
  repoName: repo.repoName, mainBranch: repo.mainBranch,
388
394
  }));
389
- ensureProjectStateFiles(repo.path);
390
- console.log(` + ${repo.name} (${repo.path})`);
395
+ console.log(` + ${name} (${repo.path})`);
391
396
  }
392
397
 
393
398
  saveConfig(config);
@@ -419,19 +424,34 @@ async function initMinions({ skipScan = false, scanRoot, scanDepth } = {}) {
419
424
  saveConfig(config);
420
425
  console.log(`\n Minions initialized at ${MINIONS_HOME}`);
421
426
  console.log(` Config, agents, and engine defaults created.\n`);
427
+
428
+ // Preflight checks
429
+ let preflightOk = true;
430
+ try { execSync('git --version', { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }); console.log(' ✓ Git found'); }
431
+ catch { console.log(' ✗ Git not found — agents need git for worktrees and PRs'); preflightOk = false; }
432
+ try {
433
+ const isWin = process.platform === 'win32';
434
+ const cmd = isWin ? 'where claude 2>NUL' : 'which claude 2>/dev/null';
435
+ execSync(cmd, { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
436
+ console.log(' ✓ Claude Code CLI found');
437
+ } catch { console.log(' ✗ Claude Code CLI not found — install: npm i -g @anthropic-ai/claude-code (or native installer)'); preflightOk = false; }
438
+ const nodeVer = parseInt(process.versions.node);
439
+ if (nodeVer >= 18) { console.log(' ✓ Node.js ' + process.versions.node); }
440
+ else { console.log(' ✗ Node.js ' + process.versions.node + ' — version 18+ required'); preflightOk = false; }
441
+ if (!preflightOk) console.log('\n Some prerequisites missing — agents may fail until resolved.\n');
442
+ else console.log('');
422
443
  if (removedPlaceholders > 0) {
423
444
  console.log(` Removed ${removedPlaceholders} placeholder project entr${removedPlaceholders === 1 ? 'y' : 'ies'} from config.\n`);
424
445
  }
425
446
 
426
447
  if (skipScan) {
427
448
  console.log(' Skipping repo scan (--skip-scan). Run "node minions.js scan" later to link projects.\n');
449
+ rl.close();
428
450
  } else {
429
- // Auto-chain into scan
451
+ // Auto-chain into scan (scanAndAdd closes rl)
430
452
  console.log(' Now let\'s find your repos...\n');
431
453
  await scanAndAdd({ root: scanRoot, depth: scanDepth });
432
454
  }
433
-
434
- rl.close();
435
455
  }
436
456
 
437
457
  function nukeMinions() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.201",
3
+ "version": "0.1.203",
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"