@yemi33/minions 0.1.2314 → 0.1.2316
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/docs/auto-discovery.md +3 -0
- package/docs/design-state-storage.md +1 -1
- package/docs/pr-review-fix-loop.md +5 -0
- package/docs/team-memory.md +2 -0
- package/docs/watches.md +1 -1
- package/engine/cleanup.js +15 -3
- package/engine/meeting.js +6 -10
- package/engine/pipeline.js +6 -10
- package/package.json +1 -1
package/docs/auto-discovery.md
CHANGED
|
@@ -12,6 +12,7 @@ The engine runs a tick every 10 seconds (configurable via `config.json` → `eng
|
|
|
12
12
|
tick()
|
|
13
13
|
1. checkTimeouts() Enforce runtime limits and stale-orphan cleanup
|
|
14
14
|
1a. checkSteering() Drain steering messages queued by the dashboard
|
|
15
|
+
1a2 checkSpawnPhaseStalls() Watchdog for agents stuck before their first heartbeat
|
|
15
16
|
1b. checkIdleThreshold() Notify on excessive agent idleness
|
|
16
17
|
1c. meetingTimeouts() Advance round-based meetings whose timer fired
|
|
17
18
|
2. consolidateInbox() Merge learnings into notes.md (Haiku-powered)
|
|
@@ -31,6 +32,8 @@ tick()
|
|
|
31
32
|
3a. pruneStalePrDispatches() Clear pending PR dispatches whose underlying PRs no longer warrant action
|
|
32
33
|
3. discoverWork() Scan ALL linked projects for new tasks
|
|
33
34
|
4. updateSnapshot() Write identity/now.md
|
|
35
|
+
4a. memoryBaseline() Sample RSS/heap/GC every memoryBaselineEveryTicks ticks (default 6 ≈ 60s)
|
|
36
|
+
4b. heapSnapshotRequest() Serve a pending guard-token heap-snapshot request, if any
|
|
34
37
|
5. dispatch Spawn agents for pending items (up to maxConcurrent)
|
|
35
38
|
```
|
|
36
39
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Author: Rebecca (Architect) | Date: 2026-04-07 | Status: **Accepted — implementation in progress**
|
|
4
4
|
|
|
5
|
-
> **Implementation status (as of 2026-06):** The `node:sqlite` recommendation in §3 has been adopted ahead of schedule. Phases 0–10 have shipped (events, dispatches, work_items, pull_requests, logs, metrics, watches, schedule_runs + pipeline_runs + managed_processes + worktree_pool, qa_runs + qa_sessions, pr_links, cooldowns + pending_rebases + cc_sessions + doc_sessions, and steering_deliveries (Phases 0–9); plans + prds + prd_items + prd_verify_prs (Phase 10, migration `015-plans-prds.js`) — see `CHANGELOG.md` and `engine/db/migrations/`). The SQLite schema lives under `engine/db/migrations/` and the singleton opens `engine/state.db` in WAL mode. Phase 8 added the first opt-out toggle for the JSON sidecars (`engine.qaDualWriteJson`, default true). Phase 9.4 went further and deleted the silent SQL-unavailable JSON fallbacks in the engine — SQL is now the only reader/writer for everything migrated; the JSON mirror layer is dual-written as a passive mirror and slated for deletion in Phase 9.5. Phase 10 dual-writes PRD state to SQL (`engine/prd-store.js`)
|
|
5
|
+
> **Implementation status (as of 2026-06):** The `node:sqlite` recommendation in §3 has been adopted ahead of schedule. Phases 0–10 have shipped (events, dispatches, work_items, pull_requests, logs, metrics, watches, schedule_runs + pipeline_runs + managed_processes + worktree_pool, qa_runs + qa_sessions, pr_links, cooldowns + pending_rebases + cc_sessions + doc_sessions, and steering_deliveries (Phases 0–9); plans + prds + prd_items + prd_verify_prs (Phase 10, migration `015-plans-prds.js`) — see `CHANGELOG.md` and `engine/db/migrations/`). The SQLite schema lives under `engine/db/migrations/` and the singleton opens `engine/state.db` in WAL mode. Phase 8 added the first opt-out toggle for the JSON sidecars (`engine.qaDualWriteJson`, default true). Phase 9.4 went further and deleted the silent SQL-unavailable JSON fallbacks in the engine — SQL is now the only reader/writer for everything migrated; the JSON mirror layer is dual-written as a passive mirror and slated for deletion in Phase 9.5. Phase 10 dual-writes PRD state to SQL (`engine/prd-store.js`); the `prdReadsFromSql` read-flip flag was removed after soaking (default had been ON since 0.1.2090) — SQL is now the unconditional PRD read path in `engine/queries.js`, with SQL errors propagating instead of silently falling back to a disk scan. Phase 10 step 4.3 added a second, still-flagged toggle, `prdJoinFromFk`: it resolves the dashboard WI↔PRD-item join via the SQL FK (`work_items.prd_item_id` → `prd_items.id`) instead of the legacy feature-id string match, with a fallback to the string match when the flag is OFF or the FK is unstamped/NULL (default ON, reversible, expires 2026-12-01). The "Phase 2: estimated Node 26 LTS" timeline in §3 is now historical context; treat sections 1–3 as design rationale rather than a forward plan.
|
|
6
6
|
|
|
7
7
|
## Executive Summary
|
|
8
8
|
|
|
@@ -81,6 +81,10 @@ A paused cause **suppresses further auto-dispatch for that cause** until cleared
|
|
|
81
81
|
|
|
82
82
|
The exported `recordPrNoOpFixAttempt` / `clearPrNoOpFixAttempt` / `sweepStalePrNoOpFixes` helpers in `engine/lifecycle.js` are the only sanctioned entry points. The dispatch evaluator gates re-dispatch on `shared.isPrNoOpFixCausePaused(pr, cause)` (which re-validates the fingerprint per call), the dashboard chip surface uses `shared.getPrPausedCauses(pr)` (same gate), and the `pollPrStatus` callback in `engine/ado.js` + `engine/github.js` calls `sweepStalePrNoOpFixes(pr)` each tick to GC records whose fingerprint no longer matches the live PR.
|
|
83
83
|
|
|
84
|
+
#### E2. Build-fix-ineffective tracker (`pr._buildFixIneffective`, issue #639)
|
|
85
|
+
|
|
86
|
+
A "branch unchanged" no-op report for a `BUILD_FAILURE`-cause fix used to be conflated with the generic `_noOpFixes[BUILD_FAILURE]` counter even when the build was still genuinely red on the unchanged head — silently masking a stuck failure as "handled". `checkBuildStillFailingLive()` re-polls host-specific live CI (`checkLiveBuildAndConflict` in `engine/github.js` / `engine/ado.js`) right after `detectPrFixBranchChange()` reports no branch change for a build-failure fix. If the build is still failing, `recordBuildFixIneffective()` tracks a **separate**, `headSha`-keyed counter (`pr._buildFixIneffective`) instead of bumping `_noOpFixes[BUILD_FAILURE]`, pausing after `prNoOpFixPauseAttempts` on the same head and writing a distinct `build-fix-ineffective-<pr>` inbox alert; `shared.isBuildFixIneffectivePaused()` re-validates the stored `headSha` against the live PR before honoring the pause (same fingerprint-revalidation pattern as `isPrNoOpFixCausePaused`), and `engine.js` wires `isBuildFixIneffectiveSuppressed()` into the `BUILD_FAILURE` dispatch gate alongside the existing no-op check. A real branch-changing fix clears the stale record. The dashboard PR list (`dashboard/js/render-prs.js`) renders a separate, non-interactive "infra issue suspected" chip for a paused `_buildFixIneffective` record (tooltip shows `reason`/`count`/`headSha`/`liveBuildStatus`) alongside the existing `_pausedCauses` chips.
|
|
87
|
+
|
|
84
88
|
## 5. Fix completes
|
|
85
89
|
|
|
86
90
|
- `updatePrAfterFix()` (lifecycle.js) sets `reviewStatus = 'waiting'` + `fixedAt = ts()`
|
|
@@ -163,3 +167,4 @@ The exported `recordPrNoOpFixAttempt` / `clearPrNoOpFixAttempt` / `sweepStalePrN
|
|
|
163
167
|
- Builds use the merge commit hash as `sourceVersion`, not the source branch commit — compare against `lastMergeCommit.commitId`
|
|
164
168
|
- `partiallySucceeded` counts as passing (warnings, not failures)
|
|
165
169
|
- A stale but passing build is still valid — don't re-trigger builds that already passed
|
|
170
|
+
- ADO never auto-clears a reviewer's numeric vote when new commits land, so a hard-reject `-10` vote stays live forever unless the reviewer explicitly re-votes. Both `pollPrStatus()` and the pre-dispatch `checkLiveReviewStatus()` treat a `-10` as **stale/superseded** (resolving to `waiting` instead of `changes-requested`) via `isStaleAdoRejectVote()` once a fix has completed after the vote's last review (`fixedAt > lastReviewedAt`) with no push since (`lastPushedAt <= fixedAt`) — otherwise the auto re-review-after-fix flow can never fire for that PR (issue #633).
|
package/docs/team-memory.md
CHANGED
|
@@ -48,6 +48,8 @@ Filenames follow the `YYYY-MM-DD-<agent>-<slug>.md` convention so chronological
|
|
|
48
48
|
|
|
49
49
|
Agents read `knowledge/` directly with grep/glob/view. They **do not** write to it. See [Sweep-write-only constraint](#sweep-write-only-constraint) below.
|
|
50
50
|
|
|
51
|
+
**Full-body copies vs. condensed stubs.** When `engine/consolidation.js#classifyToKnowledgeBase` promotes an inbox note into `knowledge/`, it only copies the full body when the note is routed to `conventions/` or its content carries a reusable-knowledge section header (`## Patterns`, `## Conventions`, `## Gotchas`, `## Dependencies`, `## Best Practices`, etc.). Narrowly-scoped, one-off notes (a single PR review, a task status update) instead get a deterministic, LLM-free condensed stub — title + first-paragraph abstract + a link back to the archived inbox note — to avoid duplicating disk usage and prompt-token cost on every future dispatch. Every promoted entry's frontmatter gains a `reusable: true|false` field so this can be audited (issue #604).
|
|
52
|
+
|
|
51
53
|
### 3. `notes.md` — consolidated team notes
|
|
52
54
|
|
|
53
55
|
`notes.md` is the rolling team notebook. The consolidation sweep periodically merges high-signal findings from the inbox into dated `### YYYY-MM-DD` sections at the top of `notes.md`, with links back to the underlying KB entries.
|
package/docs/watches.md
CHANGED
|
@@ -169,7 +169,7 @@ I/O happens **outside the lock**: notifications via `writeToInbox`, follow-up ac
|
|
|
169
169
|
| `dispatch-work-item` | Append a new WI to the project (or central) `work-items.json` with `createdBy: "watch:<id>"` |
|
|
170
170
|
| `run-skill` | Wrapper around `dispatch-work-item` that asks the agent to run a specific `.claude` skill |
|
|
171
171
|
| `webhook` | `http`/`https` request to an arbitrary URL (10s safety timeout, JSON or string body) |
|
|
172
|
-
| `minions-api` | Loopback call to the in-process dashboard at `http://127.0.0.1:${
|
|
172
|
+
| `minions-api` | Loopback call to the in-process dashboard at `http://127.0.0.1:${shared.readDashboardPortFile(MINIONS_DIR)?.port \|\| 7331}` (reads the bound-port beacon file, not the `MINIONS_PORT` env, so it still resolves after an `EADDRINUSE` port scan) — `endpoint` must start with `/api/`; sets `X-Minions-Internal: 1`; returns `{ok, status, summary, response}` for chain-step templating |
|
|
173
173
|
| `cancel-work-item` | Flip a WI to `WI_STATUS.CANCELLED` across all known work-items files |
|
|
174
174
|
| `trigger-pipeline` | Start a new pipeline run (skipped if the pipeline already has an active run) |
|
|
175
175
|
| `archive-plan` | Set PRD `status="archived"` + `archivedAt` |
|
package/engine/cleanup.js
CHANGED
|
@@ -740,10 +740,22 @@ async function runCleanup(config, verbose = false) {
|
|
|
740
740
|
shared._writeWorktreeSkipLiveInboxNote(full, 'cleanup.orphanWorktreeDirSweep', blockingInfo);
|
|
741
741
|
continue;
|
|
742
742
|
}
|
|
743
|
+
// W-mr2zu0hb000f20c7 — route through shared.removeWorktree instead of a
|
|
744
|
+
// raw fs.rmSync so this path gets the same retry-with-backoff (transient
|
|
745
|
+
// Windows EBUSY/locked-file handling) and real-repo-refusal safety net
|
|
746
|
+
// that every other worktree removal in this file uses. This dir already
|
|
747
|
+
// isn't registered in `git worktree list`, so the primary
|
|
748
|
+
// `git worktree remove --force` inside removeWorktree is expected to
|
|
749
|
+
// fail and fall through to its fs.rmSync + retry logic — that's fine,
|
|
750
|
+
// the safety checks (ownership marker already verified above; real-repo
|
|
751
|
+
// and live-dispatch refusals re-checked inside removeWorktree) still apply.
|
|
743
752
|
try {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
753
|
+
if (shared.removeWorktree(full, root, wtRoot)) {
|
|
754
|
+
cleaned.orphanWorktreeDirs++;
|
|
755
|
+
log('info', `Cleanup: removed orphan worktree dir ${full} (not registered in git)`);
|
|
756
|
+
} else {
|
|
757
|
+
log('warn', `orphan worktree dir ${full}: removeWorktree declined (see prior log line for reason)`);
|
|
758
|
+
}
|
|
747
759
|
} catch (err) {
|
|
748
760
|
log('warn', `orphan worktree dir ${full}: ${err.message}`);
|
|
749
761
|
}
|
package/engine/meeting.js
CHANGED
|
@@ -231,18 +231,14 @@ function resolveMeetingContributionContent(output, structuredCompletion) {
|
|
|
231
231
|
return resolveStructuredMeetingContent(structuredCompletion);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
function truncateMeetingContext(text, maxBytes, label) {
|
|
235
|
-
return shared.truncateTextBytes(text, maxBytes, `\n\n_...${label} truncated — review the meeting transcript if needed._`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
234
|
function formatMeetingContributions(entries, agents, emptyText, label, maxBytes) {
|
|
239
235
|
const pairs = Object.entries(typeof entries === 'object' && entries ? entries : {});
|
|
240
236
|
if (pairs.length === 0) return emptyText;
|
|
241
237
|
const perEntryBytes = Math.max(1024, Math.floor(maxBytes / Math.max(1, pairs.length)));
|
|
242
238
|
const combined = pairs.map(([agent, value]) =>
|
|
243
|
-
`### ${agents[agent]?.name || agent}\n\n${
|
|
239
|
+
`### ${agents[agent]?.name || agent}\n\n${shared.truncateTextBytes(value?.content || emptyText, perEntryBytes, `\n\n_...${label} entry truncated — review the meeting transcript if needed._`)}`
|
|
244
240
|
).join('\n\n---\n\n');
|
|
245
|
-
return
|
|
241
|
+
return shared.truncateTextBytes(combined, maxBytes, `\n\n_...${label} truncated — review the meeting transcript if needed._`);
|
|
246
242
|
}
|
|
247
243
|
|
|
248
244
|
function stripMeetingSummaryMarkdown(text) {
|
|
@@ -542,10 +538,10 @@ function discoverMeetingWork(config) {
|
|
|
542
538
|
const key = `${concludePrefix}${concluder}`;
|
|
543
539
|
if (activeKeys.has(key)) continue;
|
|
544
540
|
|
|
545
|
-
const humanNotes =
|
|
541
|
+
const humanNotes = shared.truncateTextBytes(
|
|
546
542
|
(Array.isArray(meeting.humanNotes) ? meeting.humanNotes : []).map(n => '- ' + n).join('\n'),
|
|
547
543
|
ENGINE_DEFAULTS.maxMeetingHumanNotesBytes,
|
|
548
|
-
'human meeting notes'
|
|
544
|
+
'\n\n_...human meeting notes truncated — review the meeting transcript if needed._'
|
|
549
545
|
);
|
|
550
546
|
const allFindings = formatMeetingContributions(
|
|
551
547
|
meeting.findings,
|
|
@@ -605,10 +601,10 @@ function discoverMeetingWork(config) {
|
|
|
605
601
|
const key = `meeting-${meeting.id}-r${round}-${agentId}`;
|
|
606
602
|
if (activeKeys.has(key)) continue;
|
|
607
603
|
|
|
608
|
-
const humanNotes =
|
|
604
|
+
const humanNotes = shared.truncateTextBytes(
|
|
609
605
|
(meeting.humanNotes || []).map(n => '- ' + n).join('\n'),
|
|
610
606
|
ENGINE_DEFAULTS.maxMeetingHumanNotesBytes,
|
|
611
|
-
'human meeting notes'
|
|
607
|
+
'\n\n_...human meeting notes truncated — review the meeting transcript if needed._'
|
|
612
608
|
);
|
|
613
609
|
const vars = {
|
|
614
610
|
agent_name: agents[agentId]?.name || agentId,
|
package/engine/pipeline.js
CHANGED
|
@@ -26,10 +26,6 @@ const NOTES_INBOX_DIR = path.join(MINIONS_DIR, 'notes', 'inbox');
|
|
|
26
26
|
const NOTES_ARCHIVE_DIR = path.join(MINIONS_DIR, 'notes', 'archive');
|
|
27
27
|
const CONFIG_PATH = path.join(MINIONS_DIR, 'config.json');
|
|
28
28
|
|
|
29
|
-
function truncatePipelineContext(text, maxBytes, label) {
|
|
30
|
-
return shared.truncateTextBytes(text, maxBytes, `\n\n_...${label} truncated — inspect the upstream artifacts if needed._`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
// ── Pipeline CRUD ────────────────────────────────────────────────────────────
|
|
34
30
|
|
|
35
31
|
function getPipelines() {
|
|
@@ -619,10 +615,10 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
619
615
|
try {
|
|
620
616
|
const mtg = safeJson(path.join(MEETINGS_DIR, mid + '.json'));
|
|
621
617
|
if (mtg) {
|
|
622
|
-
const transcript =
|
|
618
|
+
const transcript = shared.truncateTextBytes(
|
|
623
619
|
(mtg.transcript || []).map(formatTranscriptEntry).join('\n\n---\n\n'),
|
|
624
620
|
ENGINE_DEFAULTS.maxPipelineMeetingContextBytes,
|
|
625
|
-
|
|
621
|
+
`\n\n_...meeting transcript for ${mtg.title || mid} truncated — inspect the upstream artifacts if needed._`
|
|
626
622
|
);
|
|
627
623
|
meetingContext += '# Meeting: ' + (mtg.title || mid) + '\n\n**Agenda:** ' + (mtg.agenda || '') + '\n\n' + transcript + '\n\n';
|
|
628
624
|
}
|
|
@@ -633,15 +629,15 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
633
629
|
for (const depId of stage.dependsOn) {
|
|
634
630
|
const depStage = run.stages[depId];
|
|
635
631
|
if (depStage?.output && !depStage.artifacts?.meetings?.length) {
|
|
636
|
-
meetingContext += '## From: ' + depId + '\n\n' +
|
|
632
|
+
meetingContext += '## From: ' + depId + '\n\n' + shared.truncateTextBytes(
|
|
637
633
|
depStage.output,
|
|
638
634
|
ENGINE_DEFAULTS.maxPipelineMeetingContextBytes,
|
|
639
|
-
|
|
635
|
+
`\n\n_...pipeline stage output from ${depId} truncated — inspect the upstream artifacts if needed._`
|
|
640
636
|
) + '\n\n';
|
|
641
637
|
}
|
|
642
638
|
}
|
|
643
639
|
}
|
|
644
|
-
meetingContext =
|
|
640
|
+
meetingContext = shared.truncateTextBytes(meetingContext, ENGINE_DEFAULTS.maxPipelineMeetingContextBytes, '\n\n_...pipeline meeting context truncated — inspect the upstream artifacts if needed._');
|
|
645
641
|
|
|
646
642
|
// Use LLM to generate a structured plan (same approach as dashboard "Create Plan from Meeting" button)
|
|
647
643
|
let content = '';
|
|
@@ -1259,7 +1255,7 @@ module.exports = {
|
|
|
1259
1255
|
getPipelineRuns, getActiveRun, startRun, updateRunStage, upsertRunStage, completeRun,
|
|
1260
1256
|
discoverPipelineWork,
|
|
1261
1257
|
evaluateCondition, // exported for testing
|
|
1262
|
-
|
|
1258
|
+
executeTaskStage, executePlanStage, executeScheduleStage, executeApiStage, executeMeetingStage, executeMergePrsStage, isStageComplete, resolveTemplate, // exported for testing
|
|
1263
1259
|
_resolvePipelineProjects, // exported for testing
|
|
1264
1260
|
_findMeetingsInRun, _findExistingPlanForMeeting, _findExistingPrdForPlan, _canonicalPlanName, // exported for testing
|
|
1265
1261
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2316",
|
|
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"
|