@yemi33/minions 0.1.1558 → 0.1.1560

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1560 (2026-04-27)
4
+
5
+ ### Features
6
+ - removeProject also archives plans + PRDs targeting the project
7
+
8
+ ### Fixes
9
+ - handle project browse cancel
10
+
3
11
  ## 0.1.1558 (2026-04-27)
4
12
 
5
13
  ### Fixes
@@ -44,6 +44,7 @@ async function projectChipRemove(name) {
44
44
  var parts = ['Removed "' + name + '"'];
45
45
  if (d.cancelledItems) parts.push(d.cancelledItems + ' WI cancelled');
46
46
  if (d.drainedDispatches) parts.push(d.drainedDispatches + ' dispatch drained');
47
+ if (d.archivedPlans?.length) parts.push(d.archivedPlans.length + ' plan/PRD archived');
47
48
  if (d.archivedTo) parts.push('archived');
48
49
  if (d.pipelineRefs?.length) parts.push('! pipelines still reference: ' + d.pipelineRefs.join(', '));
49
50
  showToast('cmd-toast', parts.join(' — '), true);
@@ -424,6 +424,7 @@ async function removeProject(name) {
424
424
  if (d.cancelledItems) parts.push(d.cancelledItems + ' WI cancelled');
425
425
  if (d.drainedDispatches) parts.push(d.drainedDispatches + ' dispatch drained');
426
426
  if (d.cleanedWorktrees) parts.push(d.cleanedWorktrees + ' worktree(s) cleaned');
427
+ if (d.archivedPlans?.length) parts.push(d.archivedPlans.length + ' plan/PRD archived');
427
428
  if (d.archivedTo) parts.push('archived to ' + d.archivedTo);
428
429
  if (d.pipelineRefs?.length) parts.push('! pipelines still reference: ' + d.pipelineRefs.join(', '));
429
430
  showToast('cmd-toast', parts.join(' — '), true);
package/dashboard.js CHANGED
@@ -3707,11 +3707,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3707
3707
 
3708
3708
  async function handleProjectsBrowse(req, res) {
3709
3709
  try {
3710
- const { execSync } = require('child_process');
3710
+ const { execSync, execFileSync } = require('child_process');
3711
3711
  let selectedPath = '';
3712
3712
  if (process.platform === 'win32') {
3713
- // PowerShell STA with topmost window as owner forces folder dialog to foreground
3714
- // Write PS script to temp file to avoid shell quoting issues
3713
+ // Launch PowerShell directly (not through cmd.exe) and hide its console so
3714
+ // only the folder picker is visible. Closing the picker should cancel cleanly
3715
+ // instead of surfacing a raw shell "Command failed" error.
3715
3716
  const psScript = [
3716
3717
  'Add-Type -AssemblyName System.Windows.Forms',
3717
3718
  '$f = New-Object System.Windows.Forms.FolderBrowserDialog',
@@ -3730,10 +3731,43 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3730
3731
  fs.mkdirSync(path.dirname(psPath), { recursive: true });
3731
3732
  fs.writeFileSync(psPath, psScript);
3732
3733
  try {
3733
- selectedPath = execSync(`powershell -STA -NoProfile -ExecutionPolicy Bypass -File "${psPath}"`, { encoding: 'utf8', timeout: 120000 }).trim();
3734
+ selectedPath = execFileSync('powershell.exe', [
3735
+ '-STA',
3736
+ '-NoProfile',
3737
+ '-ExecutionPolicy', 'Bypass',
3738
+ '-File', psPath,
3739
+ ], {
3740
+ encoding: 'utf8',
3741
+ timeout: 120000,
3742
+ windowsHide: true,
3743
+ stdio: ['ignore', 'pipe', 'pipe'],
3744
+ }).trim();
3745
+ } catch (e) {
3746
+ const stdout = String(e.stdout || '').trim();
3747
+ const stderr = String(e.stderr || '').trim();
3748
+ const signal = String(e.signal || '').toUpperCase();
3749
+ const status = Number.isInteger(e.status) ? e.status : null;
3750
+ const interrupted = signal === 'SIGINT' || signal === 'SIGBREAK' || status === 0xC000013A;
3751
+ if (interrupted && !stdout && !stderr) return jsonReply(res, 200, { cancelled: true });
3752
+ throw e;
3734
3753
  } finally { try { fs.unlinkSync(psPath); } catch { /* cleanup */ } }
3735
3754
  } else if (process.platform === 'darwin') {
3736
- selectedPath = execSync(`osascript -e 'POSIX path of (choose folder with prompt "Select project folder")'`, { encoding: 'utf8', timeout: 120000 }).trim();
3755
+ try {
3756
+ selectedPath = execFileSync('osascript', [
3757
+ '-e',
3758
+ 'POSIX path of (choose folder with prompt "Select project folder")',
3759
+ ], {
3760
+ encoding: 'utf8',
3761
+ timeout: 120000,
3762
+ stdio: ['ignore', 'pipe', 'pipe'],
3763
+ }).trim();
3764
+ } catch (e) {
3765
+ const stderr = String(e.stderr || '').trim();
3766
+ const message = String(e.message || '').trim();
3767
+ const cancelled = stderr.includes('User canceled') || message.includes('User canceled') || message.includes('(-128)') || stderr.includes('(-128)');
3768
+ if (cancelled) return jsonReply(res, 200, { cancelled: true });
3769
+ throw e;
3770
+ }
3737
3771
  } else {
3738
3772
  selectedPath = execSync(`zenity --file-selection --directory --title="Select project folder" 2>/dev/null`, { encoding: 'utf8', timeout: 120000 }).trim();
3739
3773
  }
@@ -44,6 +44,7 @@ function removeProject(target, options = {}) {
44
44
  drainedDispatches: 0, // includes active dispatches whose agent processes were killed
45
45
  cleanedWorktrees: 0,
46
46
  disabledSchedules: 0,
47
+ archivedPlans: [],
47
48
  pipelineRefs: [],
48
49
  archivedTo: null,
49
50
  purgedDataDir: false,
@@ -110,7 +111,52 @@ function removeProject(target, options = {}) {
110
111
  }
111
112
  }
112
113
 
113
- // 5. Surface pipelines that reference this project so the user can review
114
+ // 5. Archive plans + PRDs targeting this project so they don't keep
115
+ // showing in the dashboard after removal. Three signals:
116
+ // a) PRD JSON `project` field matches → archive PRD + linked source plan
117
+ // b) plan .md content has `Project: <name>` (any markdown emphasis)
118
+ // c) plan .md filename contains the project name (case-insensitive)
119
+ // The .backup sidecar removal in archivePlan prevents engine restart
120
+ // from re-triggering plan completion (mirror of f28162b0 fix).
121
+ const plansDir = path.join(MINIONS_DIR, 'plans');
122
+ const plansArchive = path.join(plansDir, 'archive');
123
+ const prdDir = path.join(MINIONS_DIR, 'prd');
124
+ const escName = project.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
125
+ const projectLineRe = new RegExp('^[*_>\\s]{0,4}\\*{0,2}_{0,2}Project:?\\*{0,2}_{0,2}\\s*' + escName + '\\b', 'im');
126
+ const archivedSourcePlans = new Set();
127
+ try {
128
+ if (fs.existsSync(prdDir)) {
129
+ const { archivePlan } = require('./lifecycle');
130
+ for (const f of fs.readdirSync(prdDir).filter(f => f.endsWith('.json'))) {
131
+ try {
132
+ const prd = JSON.parse(fs.readFileSync(path.join(prdDir, f), 'utf8'));
133
+ if (prd?.project !== project.name) continue;
134
+ archivePlan(f, prd, [project], config);
135
+ summary.archivedPlans.push('prd/' + f);
136
+ if (prd.source_plan) archivedSourcePlans.add(prd.source_plan);
137
+ } catch { /* skip unreadable PRD */ }
138
+ }
139
+ }
140
+ } catch (e) { summary.warnings.push('archive PRDs: ' + e.message); }
141
+ try {
142
+ if (fs.existsSync(plansDir)) {
143
+ fs.mkdirSync(plansArchive, { recursive: true });
144
+ const lowerName = project.name.toLowerCase();
145
+ for (const f of fs.readdirSync(plansDir).filter(f => f.endsWith('.md'))) {
146
+ if (archivedSourcePlans.has(f)) continue; // already moved by archivePlan above
147
+ const fp = path.join(plansDir, f);
148
+ try {
149
+ const filenameMatch = f.toLowerCase().includes(lowerName);
150
+ const content = filenameMatch ? '' : fs.readFileSync(fp, 'utf8');
151
+ if (!filenameMatch && !projectLineRe.test(content)) continue;
152
+ fs.renameSync(fp, path.join(plansArchive, f));
153
+ summary.archivedPlans.push('plans/' + f);
154
+ } catch { /* skip unreadable plan */ }
155
+ }
156
+ }
157
+ } catch (e) { summary.warnings.push('archive plans: ' + e.message); }
158
+
159
+ // 6. Surface pipelines that reference this project so the user can review
114
160
  // them. Don't auto-modify — user intent there is unclear.
115
161
  try {
116
162
  const { getPipelines } = require('./pipeline');
@@ -125,12 +171,12 @@ function removeProject(target, options = {}) {
125
171
  }
126
172
  } catch { /* pipelines optional */ }
127
173
 
128
- // 6. Remove from config.json (and persist any schedule disables)
174
+ // 7. Remove from config.json (and persist any schedule disables)
129
175
  config.projects = (config.projects || []).filter(p => p.name !== project.name);
130
176
  try { fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); }
131
177
  catch (e) { return { ...summary, error: 'Failed to write config: ' + e.message }; }
132
178
 
133
- // 7. Move (or purge) projects/<name>/ — preserves PR/work-item history by
179
+ // 8. Move (or purge) projects/<name>/ — preserves PR/work-item history by
134
180
  // default so a re-add can pick up where it left off.
135
181
  const dataDir = path.join(MINIONS_DIR, 'projects', project.name);
136
182
  if (fs.existsSync(dataDir)) {
package/minions.js CHANGED
@@ -221,6 +221,7 @@ function removeProject(target, options = {}) {
221
221
  if (result.cancelledItems) lines.push(` cancelled ${result.cancelledItems} pending work item(s)`);
222
222
  if (result.drainedDispatches) lines.push(` drained ${result.drainedDispatches} dispatch entr${result.drainedDispatches === 1 ? 'y' : 'ies'} (active agents killed)`);
223
223
  if (result.cleanedWorktrees) lines.push(` cleaned ${result.cleanedWorktrees} worktree(s)`);
224
+ if (result.archivedPlans?.length) lines.push(` archived ${result.archivedPlans.length} plan/PRD file(s)`);
224
225
  if (result.disabledSchedules) lines.push(` disabled ${result.disabledSchedules} schedule(s)`);
225
226
  if (result.archivedTo) lines.push(` archived data → ${result.archivedTo}`);
226
227
  if (result.purgedDataDir) lines.push(` purged data directory`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1558",
3
+ "version": "0.1.1560",
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"