@yemi33/minions 0.1.1917 → 0.1.1919

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.
@@ -161,6 +161,9 @@ function editWorkItem(id, source) {
161
161
  '<label style="color:var(--text);font-size:var(--text-md)">Acceptance Criteria (one per line)' +
162
162
  '<textarea id="wi-edit-ac" rows="3" style="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;resize:vertical">' + escapeHtml((item.acceptanceCriteria || []).join('\n')) + '</textarea>' +
163
163
  '</label>' +
164
+ '<label style="color:var(--text);font-size:var(--text-md)">Depends On (work-item ids, comma- or newline-separated)' +
165
+ '<textarea id="wi-edit-depends-on" rows="2" placeholder="W-foo, W-bar" style="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;resize:vertical">' + escapeHtml((Array.isArray(item.depends_on) ? item.depends_on : []).join(', ')) + '</textarea>' +
166
+ '</label>' +
164
167
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
165
168
  '<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
166
169
  '<button onclick="submitWorkItemEdit(\'' + escapeHtml(id) + '\',\'' + escapeHtml(source || '') + '\',event)" style="padding:6px 16px;font-size:var(--text-md);background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Save</button>' +
@@ -183,13 +186,15 @@ async function submitWorkItemEdit(id, source, e) {
183
186
  }).filter(function(r) { return r.url; });
184
187
  const acRaw = document.getElementById('wi-edit-ac')?.value || '';
185
188
  const acceptanceCriteria = acRaw.split('\n').filter(function(l) { return l.trim(); });
189
+ const dependsRaw = document.getElementById('wi-edit-depends-on')?.value || '';
190
+ const depends_on = dependsRaw.split(/[\n,]/).map(function(s) { return s.trim(); }).filter(Boolean);
186
191
  if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Title is required'); return; }
187
192
  try { closeModal(); } catch { /* may not be open */ }
188
193
  showToast('cmd-toast', 'Work item updated', true);
189
194
  try {
190
195
  const res = await fetch('/api/work-items/update', {
191
196
  method: 'POST', headers: { 'Content-Type': 'application/json' },
192
- body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent, references, acceptanceCriteria })
197
+ body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent, references, acceptanceCriteria, depends_on })
193
198
  });
194
199
  if (res.ok) { refresh(); } else { const d = await res.json().catch(() => ({})); alert('Update failed: ' + (d.error || 'unknown')); editWorkItem(id, source); }
195
200
  } catch (e) { alert('Update error: ' + e.message); editWorkItem(id, source); }
@@ -375,6 +380,7 @@ function openCreateWorkItemModal() {
375
380
  '<label style="flex:1;color:var(--text);font-size:var(--text-md)">Project <select id="wi-new-project" style="' + inputStyle + '"><option value="">Central</option>' + projOpts + '</select></label>' +
376
381
  '</div>' +
377
382
  '<label style="color:var(--text);font-size:var(--text-md)">Acceptance Criteria <textarea id="wi-new-ac" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="One criterion per line (optional)"></textarea></label>' +
383
+ '<label style="color:var(--text);font-size:var(--text-md)">Depends On <textarea id="wi-new-depends-on" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="W-foo, W-bar — comma- or newline-separated work-item ids (optional)"></textarea></label>' +
378
384
  '<label style="color:var(--text);font-size:var(--text-md)">References <textarea id="wi-new-refs" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="url | title | type — one per line (optional)"></textarea></label>' +
379
385
  '<label id="wi-new-skippr-row" style="color:var(--text);font-size:var(--text-md);display:flex;gap:8px;align-items:center;cursor:pointer"><input type="checkbox" id="wi-new-skippr"> Skip PR creation (push branch only)</label>' +
380
386
  '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
@@ -406,6 +412,8 @@ async function _submitCreateWorkItem(e) {
406
412
  const project = document.getElementById('wi-new-project')?.value || '';
407
413
  const acRaw = document.getElementById('wi-new-ac')?.value || '';
408
414
  const acceptanceCriteria = acRaw.split('\n').map(l => l.trim()).filter(Boolean);
415
+ const dependsRaw = document.getElementById('wi-new-depends-on')?.value || '';
416
+ const depends_on = dependsRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean);
409
417
  const refsRaw = document.getElementById('wi-new-refs')?.value || '';
410
418
  const references = refsRaw.split('\n').filter(l => l.trim()).map(l => {
411
419
  const parts = l.split('|').map(s => s.trim());
@@ -418,6 +426,7 @@ async function _submitCreateWorkItem(e) {
418
426
  if (project) body.project = project;
419
427
  if (acceptanceCriteria.length) body.acceptanceCriteria = acceptanceCriteria;
420
428
  if (references.length && references[0].url) body.references = references;
429
+ if (depends_on.length) body.depends_on = depends_on;
421
430
  const skipPr = document.getElementById('wi-new-skippr')?.checked || false;
422
431
  if (skipPr) body.skipPr = true;
423
432
 
package/dashboard.js CHANGED
@@ -3779,6 +3779,10 @@ const server = http.createServer(async (req, res) => {
3779
3779
  try {
3780
3780
  const body = await readBody(req);
3781
3781
  if (!body.title || !body.title.trim()) return jsonReply(res, 400, { error: 'title is required' });
3782
+ if (body.depends_on !== undefined) {
3783
+ if (!Array.isArray(body.depends_on)) return jsonReply(res, 400, { error: 'depends_on must be an array of strings' });
3784
+ if (!body.depends_on.every(s => typeof s === 'string')) return jsonReply(res, 400, { error: 'depends_on entries must be strings' });
3785
+ }
3782
3786
  const target = resolveWorkItemsCreateTarget(body.project);
3783
3787
  if (target.error) return jsonReply(res, 400, { error: target.error });
3784
3788
  const wiPath = target.wiPath;
@@ -3789,6 +3793,9 @@ const server = http.createServer(async (req, res) => {
3789
3793
  priority: body.priority || 'medium', description: body.description || '',
3790
3794
  status: WI_STATUS.PENDING, created: new Date().toISOString(), createdBy: 'dashboard',
3791
3795
  };
3796
+ if (Array.isArray(body.depends_on)) {
3797
+ item.depends_on = body.depends_on.map(s => s.trim()).filter(Boolean);
3798
+ }
3792
3799
  if (targetProject) item.project = targetProject.name;
3793
3800
  if (body.scope) item.scope = body.scope;
3794
3801
  // Agent assignment normalization: `agent` and `agents` are routing hints.
@@ -3832,6 +3839,10 @@ const server = http.createServer(async (req, res) => {
3832
3839
  const body = await readBody(req);
3833
3840
  const { id, source, title, description, type, priority, agent } = body;
3834
3841
  if (!id) return jsonReply(res, 400, { error: 'id required' });
3842
+ if (body.depends_on !== undefined) {
3843
+ if (!Array.isArray(body.depends_on)) return jsonReply(res, 400, { error: 'depends_on must be an array of strings' });
3844
+ if (!body.depends_on.every(s => typeof s === 'string')) return jsonReply(res, 400, { error: 'depends_on entries must be strings' });
3845
+ }
3835
3846
 
3836
3847
  const target = resolveProjectSourceTarget(source, PROJECTS);
3837
3848
  if (target.error) return jsonReply(res, 404, { error: target.error });
@@ -3855,6 +3866,9 @@ const server = http.createServer(async (req, res) => {
3855
3866
  }
3856
3867
  if (body.references !== undefined) item.references = body.references;
3857
3868
  if (body.acceptanceCriteria !== undefined) item.acceptanceCriteria = body.acceptanceCriteria;
3869
+ if (Array.isArray(body.depends_on)) {
3870
+ item.depends_on = body.depends_on.map(s => s.trim()).filter(Boolean);
3871
+ }
3858
3872
  item.updatedAt = new Date().toISOString();
3859
3873
  result = { code: 200, body: { ok: true, item } };
3860
3874
  return items;
@@ -3,6 +3,23 @@
3
3
  */
4
4
 
5
5
  // Entry shape: id → { description, default: bool, addedIn?: version, expires?: ISO-date }
6
+ //
7
+ // INTENTIONAL EMPTY REGISTRY — DO NOT DELETE.
8
+ //
9
+ // The `FEATURES = {}` literal below is intentional scaffolding, not dead code.
10
+ // The surrounding framework (isFeatureOn, listFeatures, hasFeature, env-var
11
+ // override resolver, expiration timestamps) is load-bearing: dashboard.js
12
+ // boot wires it into /api/features (list), /api/features/toggle, and the
13
+ // `window.MINIONS_FEATURES` client bootstrap. Deleting the registry — even
14
+ // while empty — would break dashboard startup and the experimental-flags UI.
15
+ //
16
+ // New flags belong here. Register them in this object instead of removing
17
+ // the empty literal. The first real entry will replace the example below;
18
+ // until then the empty object is the correct, expected shape.
19
+ //
20
+ // Decision logged in the 2026-05-13 daily architecture & bug review meeting
21
+ // (knowledge/architecture/2026-05-13-ripley-meeting-conclusion-daily-architecture-bug-review-2.md,
22
+ // PR-C, Option B — keep framework, document the empty registry).
6
23
  const FEATURES = {
7
24
  // Example:
8
25
  // 'ux-sidebar-v2': { description: '…', default: false, addedIn: '0.1.1738', expires: '2026-06-01' },
@@ -2597,6 +2597,12 @@ function parseAgentOutput(stdout, runtimeName) {
2597
2597
  * Agents produce a ```completion fenced block with key: value pairs.
2598
2598
  * Returns parsed object or null if not found / malformed.
2599
2599
  * If multiple blocks exist, the last one wins (agent may retry).
2600
+ *
2601
+ * DEPRECATED — slated for removal once telemetry shows zero hits over a 14-day
2602
+ * window. Telemetry: see _engine.completionFallbacks in metrics.json (the JSON
2603
+ * sidecar at MINIONS_COMPLETION_REPORT is the documented contract; this fenced
2604
+ * fallback exists only to support runtimes/agents that have not yet adopted it).
2605
+ * Removal is tracked by the P-c8f5e1b3 follow-up plan.
2600
2606
  */
2601
2607
  function parseStructuredCompletion(stdout, runtimeName) {
2602
2608
  if (!stdout || typeof stdout !== 'string') return null;
@@ -2692,6 +2698,13 @@ function parseCompletionKeyValues(text) {
2692
2698
  return result;
2693
2699
  }
2694
2700
 
2701
+ /**
2702
+ * DEPRECATED — slated for removal once telemetry shows zero hits over a 14-day
2703
+ * window. Telemetry: see _engine.completionFallbacks in metrics.json (the JSON
2704
+ * sidecar at MINIONS_COMPLETION_REPORT is the documented contract; this prose
2705
+ * fallback only fires when both the sidecar AND the fenced ```completion block
2706
+ * are missing). Removal is tracked by the P-c8f5e1b3 follow-up plan.
2707
+ */
2695
2708
  function parseCompletionFieldSummary(text) {
2696
2709
  if (!text || typeof text !== 'string') return null;
2697
2710
 
@@ -3203,6 +3216,28 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
3203
3216
  const summaryCompletion = reportCompletion || fencedCompletion ? null : parseCompletionFieldSummary(resultSummary);
3204
3217
  const fallbackCompletion = fencedCompletion || summaryCompletion;
3205
3218
  const fallbackSource = fencedCompletion && hasCompletionFence(stdout, runtimeName) ? 'fenced-completion' : 'summary-completion';
3219
+ // P-c8f5e1b3 — telemetry: completion-fallback. Emit a grep-able log + bump a
3220
+ // counter in metrics.json whenever the fenced or summary fallback fires (i.e.
3221
+ // the documented sidecar contract was missing). Used to confirm zero adoption
3222
+ // gaps before removing parseStructuredCompletion / parseCompletionFieldSummary
3223
+ // in a follow-up. Sidecar (happy) path is silent — no log, no increment.
3224
+ if (!reportCompletion && fallbackCompletion) {
3225
+ const counterKey = fallbackSource === 'fenced-completion' ? 'fenced' : 'summary';
3226
+ const wiId = dispatchItem.meta?.item?.id || null;
3227
+ log('info', `telemetry: completion-fallback agent=${agentId || 'unknown'} wi=${wiId || 'N/A'} runtime=${runtimeName} source=${fallbackSource}`);
3228
+ try {
3229
+ const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
3230
+ mutateJsonFileLocked(metricsPath, (metrics) => {
3231
+ metrics = metrics || {};
3232
+ if (!metrics._engine) metrics._engine = {};
3233
+ if (!metrics._engine.completionFallbacks) metrics._engine.completionFallbacks = { fenced: 0, summary: 0 };
3234
+ metrics._engine.completionFallbacks[counterKey] = (metrics._engine.completionFallbacks[counterKey] || 0) + 1;
3235
+ return metrics;
3236
+ });
3237
+ } catch (err) {
3238
+ log('warn', `telemetry: completion-fallback metrics write failed: ${err.message}`);
3239
+ }
3240
+ }
3206
3241
  const structuredCompletion = reportCompletion || persistCompletionReport(dispatchItem, fallbackCompletion, fallbackSource);
3207
3242
  if (structuredCompletion) {
3208
3243
  if (structuredCompletion.summary) resultSummary = String(structuredCompletion.summary);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1917",
3
+ "version": "0.1.1919",
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"
@@ -1,5 +0,0 @@
1
- {
2
- "runtime": "copilot",
3
- "models": null,
4
- "cachedAt": "2026-05-13T22:15:26.685Z"
5
- }