@yemi33/minions 0.1.225 → 0.1.226

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,6 +1,11 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.225 (2026-04-03)
3
+ ## 0.1.226 (2026-04-03)
4
+
5
+ ### Features
6
+ - add evaluate type to routing and playbooks (#70)
7
+ - fix cooldown context accumulation bloat (#71)
8
+ - wire dead test and extract progress bar helper (#64)
4
9
 
5
10
  ### Fixes
6
11
  - silence git stderr noise during repo scan
@@ -64,6 +64,54 @@ function _collectRunArtifacts(run) {
64
64
  return { merged: merged, total: total };
65
65
  }
66
66
 
67
+ /**
68
+ * Build a segmented progress bar from pipeline stages and a run.
69
+ * @param {Array} stages - pipeline stage definitions
70
+ * @param {Object} run - the run object containing stages status map
71
+ * @param {Object} [options] - optional config: { height: '8px', detailLabel: true }
72
+ * @returns {string} HTML string for the progress bar
73
+ */
74
+ function _buildProgressBar(stages, run, options) {
75
+ var totalStages = stages.length;
76
+ var completedCount = 0;
77
+ var runningCount = 0;
78
+ var failedCount = 0;
79
+ stages.forEach(function(s) {
80
+ var st = run.stages?.[s.id]?.status;
81
+ if (st === 'completed') completedCount++;
82
+ else if (st === 'running') runningCount++;
83
+ else if (st === 'failed') failedCount++;
84
+ });
85
+ var pct = Math.round((completedCount / totalStages) * 100);
86
+
87
+ var segments = stages.map(function(s) {
88
+ var st = run.stages?.[s.id]?.status || 'pending';
89
+ var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
90
+ return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / totalStages) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
91
+ }).join('');
92
+
93
+ var barStyle = options && options.height ? ' style="height:' + options.height + '"' : '';
94
+
95
+ var label;
96
+ if (options && options.detailLabel) {
97
+ label = '<span style="font-weight:600;color:' + (pct === 100 ? 'var(--green)' : failedCount ? 'var(--red)' : 'var(--blue)') + '">' + pct + '% complete</span> <span style="color:var(--muted)">(' + completedCount + '/' + totalStages + ' stages)</span>';
98
+ } else {
99
+ var statusParts = [];
100
+ if (completedCount) statusParts.push(completedCount + ' done');
101
+ if (runningCount) statusParts.push(runningCount + ' running');
102
+ if (failedCount) statusParts.push(failedCount + ' failed');
103
+ var remaining = totalStages - completedCount - runningCount - failedCount;
104
+ if (remaining > 0) statusParts.push(remaining + ' pending');
105
+ label = '<span style="font-weight:600;color:' + (pct === 100 ? 'var(--green)' : failedCount ? 'var(--red)' : 'var(--blue)') + '">' + pct + '%</span>' +
106
+ '<span style="color:var(--muted)">' + statusParts.join(' \u00b7 ') + '</span>';
107
+ }
108
+
109
+ return '<div class="pl-progress-wrap">' +
110
+ '<div class="pl-progress-bar"' + barStyle + '>' + segments + '</div>' +
111
+ '<div class="pl-progress-label">' + label + '</div>' +
112
+ '</div>';
113
+ }
114
+
67
115
  function renderPipelines(pipelines) {
68
116
  _pipelinesData = pipelines || [];
69
117
  const el = document.getElementById('pipelines-content');
@@ -95,39 +143,7 @@ function renderPipelines(pipelines) {
95
143
  var progressHtml = '';
96
144
  var displayRun = activeRun || lastRun;
97
145
  if (displayRun && (p.stages || []).length > 0) {
98
- var totalStages = (p.stages || []).length;
99
- var completedCount = 0;
100
- var runningCount = 0;
101
- var failedCount = 0;
102
- (p.stages || []).forEach(function(s) {
103
- var st = displayRun.stages?.[s.id]?.status;
104
- if (st === 'completed') completedCount++;
105
- else if (st === 'running') runningCount++;
106
- else if (st === 'failed') failedCount++;
107
- });
108
- var pct = Math.round((completedCount / totalStages) * 100);
109
-
110
- // Segmented progress bar — one segment per stage
111
- var segments = (p.stages || []).map(function(s) {
112
- var st = displayRun.stages?.[s.id]?.status || 'pending';
113
- var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
114
- return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / totalStages) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
115
- }).join('');
116
-
117
- var statusParts = [];
118
- if (completedCount) statusParts.push(completedCount + ' done');
119
- if (runningCount) statusParts.push(runningCount + ' running');
120
- if (failedCount) statusParts.push(failedCount + ' failed');
121
- var remaining = totalStages - completedCount - runningCount - failedCount;
122
- if (remaining > 0) statusParts.push(remaining + ' pending');
123
-
124
- progressHtml = '<div class="pl-progress-wrap">' +
125
- '<div class="pl-progress-bar">' + segments + '</div>' +
126
- '<div class="pl-progress-label">' +
127
- '<span style="font-weight:600;color:' + (pct === 100 ? 'var(--green)' : failedCount ? 'var(--red)' : 'var(--blue)') + '">' + pct + '%</span>' +
128
- '<span style="color:var(--muted)">' + statusParts.join(' \u00b7 ') + '</span>' +
129
- '</div>' +
130
- '</div>';
146
+ progressHtml = _buildProgressBar(p.stages || [], displayRun);
131
147
  }
132
148
 
133
149
  return '<div style="background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:8px;cursor:pointer" onclick="openPipelineDetail(\'' + escHtml(p.id) + '\')">' +
@@ -166,24 +182,7 @@ function openPipelineDetail(id) {
166
182
  // Stage detail with progress bar
167
183
  var detailRun = activeRun || (p.runs || []).slice(-1)[0];
168
184
  if (detailRun && (p.stages || []).length > 0) {
169
- var dtotal = (p.stages || []).length;
170
- var ddone = 0, drun = 0, dfail = 0;
171
- (p.stages || []).forEach(function(s) {
172
- var st = detailRun.stages?.[s.id]?.status;
173
- if (st === 'completed') ddone++;
174
- else if (st === 'running') drun++;
175
- else if (st === 'failed') dfail++;
176
- });
177
- var dpct = Math.round((ddone / dtotal) * 100);
178
- var dsegs = (p.stages || []).map(function(s) {
179
- var st = detailRun.stages?.[s.id]?.status || 'pending';
180
- var cls = st === 'completed' ? 'complete' : st === 'running' ? 'running' : st === 'failed' ? 'failed' : st === 'waiting-human' ? 'waiting' : 'pending';
181
- return '<div class="pl-prog-seg ' + cls + '" style="width:' + (100 / dtotal) + '%" title="' + escHtml(s.id) + ': ' + st + '"></div>';
182
- }).join('');
183
- html += '<div class="pl-progress-wrap">' +
184
- '<div class="pl-progress-bar" style="height:8px">' + dsegs + '</div>' +
185
- '<div class="pl-progress-label"><span style="font-weight:600;color:' + (dpct === 100 ? 'var(--green)' : dfail ? 'var(--red)' : 'var(--blue)') + '">' + dpct + '% complete</span> <span style="color:var(--muted)">(' + ddone + '/' + dtotal + ' stages)</span></div>' +
186
- '</div>';
185
+ html += _buildProgressBar(p.stages || [], detailRun, { height: '8px', detailLabel: true });
187
186
  }
188
187
  html += '<h4 style="font-size:12px;color:var(--blue);margin:0">Stages</h4>';
189
188
  (p.stages || []).forEach(function(s, i) {
@@ -7,10 +7,12 @@ const path = require('path');
7
7
  const shared = require('./shared');
8
8
  const queries = require('./queries');
9
9
 
10
+ const { createHash } = require('crypto');
10
11
  const { safeJson, safeWrite, log } = shared;
11
12
  const { ENGINE_DIR } = queries;
12
13
 
13
14
  const COOLDOWN_PATH = path.join(ENGINE_DIR, 'cooldowns.json');
15
+ const PENDING_CONTEXTS_CAP = 10;
14
16
  const dispatchCooldowns = new Map(); // key → { timestamp, failures }
15
17
 
16
18
  function loadCooldowns() {
@@ -24,6 +26,35 @@ function loadCooldowns() {
24
26
  }
25
27
  }
26
28
  log('info', `Loaded ${dispatchCooldowns.size} cooldowns from disk`);
29
+ // One-time purge of bloated pendingContexts on startup
30
+ purgeBloatedCooldowns();
31
+ }
32
+
33
+ /** Deduplicate and cap pendingContexts in all loaded cooldown entries. */
34
+ function purgeBloatedCooldowns() {
35
+ let totalRemoved = 0;
36
+ for (const [k, v] of dispatchCooldowns) {
37
+ if (!Array.isArray(v.pendingContexts) || v.pendingContexts.length <= 1) continue;
38
+ const seen = new Set();
39
+ const deduped = [];
40
+ for (const ctx of v.pendingContexts) {
41
+ const hash = _contentHash(ctx);
42
+ if (!seen.has(hash)) {
43
+ seen.add(hash);
44
+ deduped.push(ctx);
45
+ }
46
+ }
47
+ const before = v.pendingContexts.length;
48
+ // Apply FIFO cap after dedup — keep the most recent entries
49
+ v.pendingContexts = deduped.length > PENDING_CONTEXTS_CAP
50
+ ? deduped.slice(deduped.length - PENDING_CONTEXTS_CAP)
51
+ : deduped;
52
+ totalRemoved += before - v.pendingContexts.length;
53
+ }
54
+ if (totalRemoved > 0) {
55
+ log('info', `Purged ${totalRemoved} duplicate/excess pendingContexts entries from cooldowns`);
56
+ saveCooldowns();
57
+ }
27
58
  }
28
59
 
29
60
  let _cooldownWriteTimer = null;
@@ -55,10 +86,26 @@ function setCooldown(key) {
55
86
  saveCooldowns();
56
87
  }
57
88
 
89
+ function _contentHash(content) {
90
+ const str = typeof content === 'string' ? content : JSON.stringify(content);
91
+ return createHash('sha256').update(str).digest('hex');
92
+ }
93
+
58
94
  function setCooldownWithContext(key, context) {
59
95
  const existing = dispatchCooldowns.get(key);
60
96
  const pendingContexts = existing?.pendingContexts || [];
61
- if (context) pendingContexts.push(context);
97
+ if (context) {
98
+ // Dedup: only append if content differs from all existing entries
99
+ const newHash = _contentHash(context);
100
+ const isDuplicate = pendingContexts.some(c => _contentHash(c) === newHash);
101
+ if (!isDuplicate) {
102
+ pendingContexts.push(context);
103
+ // FIFO cap: drop oldest entries when exceeding cap
104
+ while (pendingContexts.length > PENDING_CONTEXTS_CAP) {
105
+ pendingContexts.shift();
106
+ }
107
+ }
108
+ }
62
109
  dispatchCooldowns.set(key, {
63
110
  timestamp: Date.now(),
64
111
  failures: existing?.failures || 0,
@@ -101,13 +148,16 @@ function isAlreadyDispatched(key) {
101
148
 
102
149
  module.exports = {
103
150
  COOLDOWN_PATH,
151
+ PENDING_CONTEXTS_CAP,
104
152
  dispatchCooldowns,
105
153
  loadCooldowns,
106
154
  saveCooldowns,
155
+ purgeBloatedCooldowns,
107
156
  isOnCooldown,
108
157
  setCooldown,
109
158
  setCooldownWithContext,
110
159
  getCoalescedContexts,
111
160
  setCooldownFailure,
112
161
  isAlreadyDispatched,
162
+ _contentHash,
113
163
  };
@@ -481,7 +481,7 @@ function selectPlaybook(workType, item) {
481
481
  if (workType === 'review' && !item?._pr && !item?.pr_id) {
482
482
  return 'work-item';
483
483
  }
484
- const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
484
+ const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'evaluate', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
485
485
  return typeSpecificPlaybooks.includes(workType) ? workType : 'work-item';
486
486
  }
487
487
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.225",
3
+ "version": "0.1.226",
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"
@@ -0,0 +1,149 @@
1
+ # Playbook: Evaluate
2
+
3
+ You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
4
+ TEAM ROOT: {{team_root}}
5
+
6
+ ## Your Task
7
+
8
+ Evaluate the implementation quality of a completed work item against its acceptance criteria and code quality standards.
9
+
10
+ ## Work Item Under Evaluation
11
+
12
+ - **ID:** {{item_id}}
13
+ - **Title:** {{item_title}}
14
+ - **Description:** {{item_description}}
15
+ - **Branch:** `{{branch_name}}`
16
+ - **Project:** {{project_name}} (`{{project_path}}`)
17
+
18
+ {{#acceptance_criteria}}
19
+ ## Acceptance Criteria
20
+
21
+ {{acceptance_criteria}}
22
+ {{/acceptance_criteria}}
23
+
24
+ {{#references}}
25
+ ## References
26
+
27
+ {{references}}
28
+ {{/references}}
29
+
30
+ ## Evaluation Rubric
31
+
32
+ Score each category on a 1-5 scale. A category **passes** at 3 or above.
33
+
34
+ ### 1. Correctness (weight: 30%)
35
+
36
+ Does the implementation do what the task description and acceptance criteria require?
37
+
38
+ - **5 — Excellent:** All acceptance criteria met, edge cases handled, no functional gaps
39
+ - **4 — Good:** All core criteria met, minor edge cases not handled
40
+ - **3 — Adequate:** Most criteria met, one minor gap that doesn't block usage
41
+ - **2 — Deficient:** One or more acceptance criteria not met
42
+ - **1 — Failing:** Core functionality missing or broken
43
+
44
+ **Pass threshold:** 3
45
+
46
+ ### 2. Completeness (weight: 25%)
47
+
48
+ Is the implementation finished end-to-end? No TODO stubs, no half-wired features, no missing integration points.
49
+
50
+ - **5 — Excellent:** Fully integrated, no loose ends, documentation updated if applicable
51
+ - **4 — Good:** Feature complete, minor polish items remain (comments, naming)
52
+ - **3 — Adequate:** Core feature works, one non-critical integration point incomplete
53
+ - **2 — Deficient:** Significant pieces missing or stubbed out
54
+ - **1 — Failing:** Skeleton or partial implementation only
55
+
56
+ **Pass threshold:** 3
57
+
58
+ ### 3. Code Quality (weight: 25%)
59
+
60
+ Does the code follow existing project patterns, naming conventions, and architectural decisions?
61
+
62
+ - **5 — Excellent:** Clean, idiomatic, follows all project conventions, well-structured
63
+ - **4 — Good:** Follows conventions, minor style inconsistencies
64
+ - **3 — Adequate:** Generally follows patterns, one area deviates without justification
65
+ - **2 — Deficient:** Multiple convention violations, poor structure
66
+ - **1 — Failing:** Ignores project patterns, introduces anti-patterns
67
+
68
+ **Pass threshold:** 3
69
+
70
+ ### 4. Test Coverage (weight: 20%)
71
+
72
+ Are there tests for the new functionality? Do existing tests still pass?
73
+
74
+ - **5 — Excellent:** Comprehensive tests for happy path and edge cases, all passing
75
+ - **4 — Good:** Tests cover core functionality, existing tests pass
76
+ - **3 — Adequate:** At least one test for the main feature, no regressions
77
+ - **2 — Deficient:** No new tests, but existing tests pass
78
+ - **1 — Failing:** No tests, or existing tests broken
79
+
80
+ **Pass threshold:** 3
81
+
82
+ ## Evaluation Steps
83
+
84
+ 1. **Fetch and review the diff:**
85
+ ```bash
86
+ git fetch origin
87
+ git diff {{main_branch}}...origin/{{branch_name}}
88
+ ```
89
+
90
+ 2. **Check acceptance criteria** one by one — mark each as MET or NOT MET with evidence
91
+
92
+ 3. **Review code quality** — check for pattern adherence, naming, structure
93
+
94
+ 4. **Verify tests:**
95
+ ```bash
96
+ cd {{project_path}}
97
+ npm test
98
+ ```
99
+
100
+ 5. **Calculate scores** using the rubric above
101
+
102
+ 6. **Determine verdict:**
103
+ - **PASS** — all four categories score 3 or above
104
+ - **FAIL** — any category scores below 3
105
+
106
+ ## Output Format
107
+
108
+ Structure your evaluation result as follows:
109
+
110
+ ```
111
+ ## Evaluation Result
112
+
113
+ **Item:** {{item_id}} — {{item_title}}
114
+ **Verdict:** PASS | FAIL
115
+ **Weighted Score:** X.X / 5.0
116
+
117
+ ### Scores
118
+
119
+ | Category | Score | Pass | Notes |
120
+ |----------|-------|------|-------|
121
+ | Correctness | X/5 | YES/NO | ... |
122
+ | Completeness | X/5 | YES/NO | ... |
123
+ | Code Quality | X/5 | YES/NO | ... |
124
+ | Test Coverage | X/5 | YES/NO | ... |
125
+
126
+ ### Acceptance Criteria Checklist
127
+
128
+ - [x] Criterion 1 — evidence
129
+ - [ ] Criterion 2 — what's missing
130
+
131
+ ### Issues Found
132
+
133
+ 1. **[severity]** Description (file:line)
134
+
135
+ ### Recommendations
136
+
137
+ - What to fix before merging (if FAIL)
138
+ - Suggestions for improvement (if PASS)
139
+ ```
140
+
141
+ ## Rules
142
+
143
+ - Base your evaluation on **evidence from the diff and test output** — not assumptions
144
+ - If acceptance criteria are missing, evaluate against the task description
145
+ - A FAIL verdict should include actionable feedback — what specifically needs to change
146
+ - Do NOT modify any code — this is a read-only evaluation
147
+ - NEVER checkout branches in the main working tree — use `git diff` and `git show` only
148
+
149
+ **Note:** Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
package/routing.md CHANGED
@@ -17,6 +17,7 @@ How the engine decides who handles what. Parsed by engine.js — keep the table
17
17
  | test | dallas | ralph |
18
18
  | ask | ripley | rebecca |
19
19
  | verify | dallas | ralph |
20
+ | evaluate | ripley | rebecca |
20
21
  | decompose | ripley | rebecca |
21
22
  | meeting | ripley | rebecca |
22
23