@yemi33/minions 0.1.38 → 0.1.40

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.40 (2026-03-29)
4
+
5
+ ### Dashboard
6
+ - dashboard.js
7
+ - dashboard/js/render-plans.js
8
+ - dashboard/pages/plans.html
9
+
10
+ ## 0.1.39 (2026-03-29)
11
+
12
+ ### Engine
13
+ - engine.js
14
+
15
+ ### Dashboard
16
+ - dashboard.js
17
+ - dashboard/js/render-prs.js
18
+ - dashboard/pages/prs.html
19
+
3
20
  ## 0.1.38 (2026-03-29)
4
21
 
5
22
  ### Dashboard
@@ -1,5 +1,51 @@
1
1
  // render-plans.js — Plan rendering functions extracted from dashboard.html
2
2
 
3
+ function openCreatePlanModal() {
4
+ const projOpts = (typeof cmdProjects !== 'undefined' ? cmdProjects : []).map(p =>
5
+ '<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>'
6
+ ).join('');
7
+ const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
8
+
9
+ document.getElementById('modal-title').textContent = 'Create Plan';
10
+ document.getElementById('modal-body').innerHTML =
11
+ '<div style="display:flex;flex-direction:column;gap:10px">' +
12
+ '<label style="color:var(--text);font-size:var(--text-md)">Title <input id="plan-new-title" style="' + inputStyle + '" placeholder="e.g. Add user authentication with JWT"></label>' +
13
+ '<label style="color:var(--text);font-size:var(--text-md)">Project <select id="plan-new-project" style="' + inputStyle + '"><option value="">Auto</option>' + projOpts + '</select></label>' +
14
+ '<label style="color:var(--text);font-size:var(--text-md)">Plan Content <textarea id="plan-new-content" rows="12" style="' + inputStyle + ';resize:vertical;font-family:monospace;font-size:12px" placeholder="Write your plan in markdown...\n\nDescribe what needs to be built, the approach, requirements, and any constraints.\n\nThe squad will convert this into a PRD with structured work items."></textarea></label>' +
15
+ '<div style="font-size:11px;color:var(--muted)">After creating, click Execute on the plan card to have an agent convert it into a PRD with work items.</div>' +
16
+ '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
17
+ '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
18
+ '<button onclick="_submitCreatePlan()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Create Plan</button>' +
19
+ '</div>' +
20
+ '</div>';
21
+ document.getElementById('modal').classList.add('open');
22
+ setTimeout(() => document.getElementById('plan-new-title')?.focus(), 100);
23
+ }
24
+
25
+ async function _submitCreatePlan() {
26
+ const title = document.getElementById('plan-new-title')?.value?.trim();
27
+ const content = document.getElementById('plan-new-content')?.value?.trim();
28
+ if (!title) { alert('Title is required'); return; }
29
+ if (!content) { alert('Plan content is required'); return; }
30
+ const project = document.getElementById('plan-new-project')?.value || '';
31
+
32
+ try {
33
+ const res = await fetch('/api/plans/create', {
34
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ title, content, project })
36
+ });
37
+ const data = await res.json();
38
+ if (res.ok) {
39
+ try { closeModal(); } catch {}
40
+ refreshPlans();
41
+ refresh();
42
+ try { showToast('cmd-toast', 'Plan "' + data.file + '" created — click Execute to convert to PRD', true); } catch {}
43
+ } else {
44
+ alert('Failed: ' + (data.error || 'unknown'));
45
+ }
46
+ } catch (e) { alert('Error: ' + e.message); }
47
+ }
48
+
3
49
  async function refreshPlans() {
4
50
  try {
5
51
  const plans = await fetch('/api/plans').then(r => r.json());
@@ -92,3 +92,54 @@ function openModal(i) {
92
92
  if (card) clearNotifBadge(card);
93
93
  document.getElementById('modal').classList.add('open');
94
94
  }
95
+
96
+ function openAddPrModal() {
97
+ const projOpts = (typeof cmdProjects !== 'undefined' ? cmdProjects : []).map(p =>
98
+ '<option value="' + escHtml(p) + '">' + escHtml(p) + '</option>'
99
+ ).join('');
100
+ const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md);font-family:inherit';
101
+
102
+ document.getElementById('modal-title').textContent = 'Link Pull Request';
103
+ document.getElementById('modal-body').innerHTML =
104
+ '<div style="display:flex;flex-direction:column;gap:10px">' +
105
+ '<label style="color:var(--text);font-size:var(--text-md)">PR URL <input id="pr-link-url" style="' + inputStyle + '" placeholder="https://github.com/org/repo/pull/123"></label>' +
106
+ '<label style="color:var(--text);font-size:var(--text-md)">Title <input id="pr-link-title" style="' + inputStyle + '" placeholder="Short description (optional — auto-detected from URL)"></label>' +
107
+ '<label style="color:var(--text);font-size:var(--text-md)">Project <select id="pr-link-project" style="' + inputStyle + '"><option value="">Auto / Central</option>' + projOpts + '</select></label>' +
108
+ '<label style="color:var(--text);font-size:var(--text-md)">Context <textarea id="pr-link-context" rows="3" style="' + inputStyle + ';resize:vertical" placeholder="Why are you linking this? What should agents know about it?"></textarea></label>' +
109
+ '<label style="display:flex;align-items:center;gap:8px;color:var(--text);font-size:var(--text-md);margin-top:4px;cursor:pointer">' +
110
+ '<input type="checkbox" id="pr-link-observe" style="width:16px;height:16px;accent-color:var(--blue)">' +
111
+ '<span>Auto-observe <span style="color:var(--muted);font-weight:400">(monitor builds, resolve comments, fix failures)</span></span>' +
112
+ '</label>' +
113
+ '<div style="font-size:11px;color:var(--muted);margin-top:-4px;padding-left:24px">Off = context only (e.g. teammate\'s PR). On = agents actively monitor and fix issues.</div>' +
114
+ '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
115
+ '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
116
+ '<button onclick="_submitLinkPr()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Link PR</button>' +
117
+ '</div>' +
118
+ '</div>';
119
+ document.getElementById('modal').classList.add('open');
120
+ setTimeout(() => document.getElementById('pr-link-url')?.focus(), 100);
121
+ }
122
+
123
+ async function _submitLinkPr() {
124
+ const url = document.getElementById('pr-link-url')?.value?.trim();
125
+ if (!url) { alert('PR URL is required'); return; }
126
+ const title = document.getElementById('pr-link-title')?.value?.trim() || '';
127
+ const project = document.getElementById('pr-link-project')?.value || '';
128
+ const context = document.getElementById('pr-link-context')?.value || '';
129
+ const autoObserve = document.getElementById('pr-link-observe')?.checked || false;
130
+
131
+ try {
132
+ const res = await fetch('/api/pull-requests/link', {
133
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
134
+ body: JSON.stringify({ url, title, project, context, autoObserve })
135
+ });
136
+ const data = await res.json();
137
+ if (res.ok) {
138
+ try { closeModal(); } catch {}
139
+ refresh();
140
+ try { showToast('cmd-toast', 'PR ' + (data.id || '') + ' linked' + (autoObserve ? ' (auto-observe on)' : ''), true); } catch {}
141
+ } else {
142
+ alert('Failed: ' + (data.error || 'unknown'));
143
+ }
144
+ } catch (e) { alert('Error: ' + e.message); }
145
+ }
@@ -1,4 +1,6 @@
1
1
  <section>
2
- <h2>Plans <span class="count" id="plans-count">0</span></h2>
2
+ <h2>Plans <span class="count" id="plans-count">0</span>
3
+ <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openCreatePlanModal()">+ New Plan</button>
4
+ </h2>
3
5
  <div id="plans-list"><p class="empty">No plans yet. Use /plan in the command center to create one.</p></div>
4
6
  </section>
@@ -1,4 +1,6 @@
1
1
  <section class="pr-panel" id="pr-section">
2
- <h2>Pull Requests <span class="count" id="pr-count">0</span></h2>
2
+ <h2>Pull Requests <span class="count" id="pr-count">0</span>
3
+ <button class="pr-pager-btn" style="font-size:9px;padding:1px 6px;color:var(--green);border-color:var(--green);margin-left:8px" onclick="openAddPrModal()">+ Link PR</button>
4
+ </h2>
3
5
  <div id="pr-content"><p class="pr-empty">No pull requests yet.</p></div>
4
6
  </section>
package/dashboard.js CHANGED
@@ -2916,6 +2916,65 @@ What would you like to discuss or change? When you're happy, say "approve" and I
2916
2916
  { method: 'POST', path: '/api/prd/regenerate', desc: 'Regenerate PRD from revised source plan', params: 'file', handler: handlePrdRegenerate },
2917
2917
 
2918
2918
  // Agents
2919
+ { method: 'POST', path: '/api/pull-requests/link', desc: 'Manually link an external PR for tracking', params: 'url, title?, project?, autoObserve?, context?', handler: async (req, res) => {
2920
+ const body = await readBody(req);
2921
+ const { url, title, project: projectName, autoObserve, context } = body;
2922
+ if (!url) return jsonReply(res, 400, { error: 'url required' });
2923
+
2924
+ // Determine project
2925
+ reloadConfig();
2926
+ const projects = shared.getProjects(CONFIG);
2927
+ const targetProject = projectName ? projects.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) : projects[0];
2928
+ const prPath = targetProject ? shared.projectPrPath(targetProject) : path.join(MINIONS_DIR, 'pull-requests.json');
2929
+ const prs = JSON.parse(safeRead(prPath) || '[]');
2930
+
2931
+ // Extract PR number from URL
2932
+ const prNumMatch = url.match(/\/pull\/(\d+)|pullrequest\/(\d+)/);
2933
+ const prNum = prNumMatch ? (prNumMatch[1] || prNumMatch[2]) : Date.now().toString().slice(-6);
2934
+ const prId = 'PR-' + prNum;
2935
+
2936
+ if (prs.some(p => p.id === prId || p.url === url)) return jsonReply(res, 400, { error: 'PR already tracked' });
2937
+
2938
+ prs.push({
2939
+ id: prId,
2940
+ title: (title || 'Linked PR #' + prNum).slice(0, 120),
2941
+ agent: 'human',
2942
+ branch: '',
2943
+ reviewStatus: autoObserve ? 'pending' : 'none',
2944
+ status: autoObserve ? 'active' : 'linked',
2945
+ created: new Date().toISOString().slice(0, 10),
2946
+ url,
2947
+ prdItems: [],
2948
+ _manual: true,
2949
+ _autoObserve: !!autoObserve,
2950
+ _context: context || '',
2951
+ });
2952
+ safeWrite(prPath, prs);
2953
+ invalidateStatusCache();
2954
+ return jsonReply(res, 200, { ok: true, id: prId });
2955
+ }},
2956
+
2957
+ { method: 'POST', path: '/api/plans/create', desc: 'Create a plan from user-provided content', params: 'title, content, project?', handler: async (req, res) => {
2958
+ const body = await readBody(req);
2959
+ const { title, content, project: projectName } = body;
2960
+ if (!title || !content) return jsonReply(res, 400, { error: 'title and content required' });
2961
+
2962
+ const plansDir = path.join(MINIONS_DIR, 'plans');
2963
+ if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
2964
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 50);
2965
+ const date = new Date().toISOString().slice(0, 10);
2966
+ const filename = `${slug}-${date}.md`;
2967
+ const filePath = shared.uniquePath(path.join(plansDir, filename));
2968
+
2969
+ const header = `# ${title}\n\n` +
2970
+ (projectName ? `**Project:** ${projectName}\n` : '') +
2971
+ `**Created:** ${date}\n**By:** human teammate\n\n---\n\n`;
2972
+ safeWrite(filePath, header + content);
2973
+
2974
+ invalidateStatusCache();
2975
+ return jsonReply(res, 200, { ok: true, file: path.basename(filePath) });
2976
+ }},
2977
+
2919
2978
  { method: 'POST', path: '/api/agents/steer', desc: 'Inject steering message into a running agent', params: 'agent, message', handler: async (req, res) => {
2920
2979
  const body = await readBody(req);
2921
2980
  const { agent: agentId, message } = body;
package/engine.js CHANGED
@@ -592,16 +592,23 @@ function buildAgentContext(agentId, config, project) {
592
592
  context += `## Recently Completed\n\n${recentCompleted.join('\n')}\n\n`;
593
593
  }
594
594
 
595
- // Active PRs across projects — coordination awareness
595
+ // Active + linked PRs across projects — coordination awareness
596
596
  const projects = getProjects(config);
597
597
  const allPrs = [];
598
598
  for (const p of projects) {
599
- const prs = getPrs(p).filter(pr => pr.status === 'active');
599
+ const prs = getPrs(p).filter(pr => pr.status === 'active' || pr.status === 'linked');
600
600
  for (const pr of prs) allPrs.push({ ...pr, _project: p.name });
601
601
  }
602
+ // Also check central pull-requests.json
603
+ try {
604
+ const centralPrs = safeJson(path.join(MINIONS_DIR, 'pull-requests.json')) || [];
605
+ for (const pr of centralPrs.filter(pr => pr.status === 'active' || pr.status === 'linked')) {
606
+ if (!allPrs.some(p => p.id === pr.id)) allPrs.push({ ...pr, _project: 'central' });
607
+ }
608
+ } catch {}
602
609
  if (allPrs.length > 0) {
603
610
  const prLines = allPrs.map(pr =>
604
- `- **${pr.id}** (${pr._project}): ${(pr.title || '').slice(0, 80)} [${pr.reviewStatus || 'pending'}${pr.buildStatus === 'failing' ? ', BUILD FAILING' : ''}]${pr.branch ? ' branch: `' + pr.branch + '`' : ''}`
611
+ `- **${pr.id}** (${pr._project}): ${(pr.title || '').slice(0, 80)} [${pr.status === 'linked' ? 'context-only' : (pr.reviewStatus || 'pending')}${pr.buildStatus === 'failing' ? ', BUILD FAILING' : ''}]${pr.branch ? ' branch: `' + pr.branch + '`' : ''}${pr._context ? ' — ' + pr._context.slice(0, 100) : ''}`
605
612
  );
606
613
  context += `## Active Pull Requests\n\n${prLines.join('\n')}\n\n`;
607
614
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
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"