@yemi33/minions 0.1.2280 → 0.1.2282
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/timeouts-and-liveness.md +6 -0
- package/docs/workspace-manifests.md +2 -4
- package/engine/ado.js +13 -1
- package/engine/cleanup.js +1 -1
- package/engine/dispatch.js +14 -0
- package/engine/github.js +5 -1
- package/engine/lifecycle.js +13 -2
- package/engine/pipeline.js +2 -2
- package/engine/playbook.js +48 -2
- package/engine/shared.js +13 -0
- package/engine.js +12 -2
- package/package.json +1 -1
- package/playbooks/build-fix-complex.md +177 -0
- package/routing.md +2 -0
|
@@ -39,6 +39,12 @@ Orphan declaration requires four checks (all must pass):
|
|
|
39
39
|
3. `isOsPidAliveForDispatch` returns false.
|
|
40
40
|
4. Full-log re-scan still shows no completion.
|
|
41
41
|
|
|
42
|
+
**Minimum-alive guard (M004).** `ENGINE_DEFAULTS.minAliveBeforeOrphanMs` (default 30s, configurable via
|
|
43
|
+
`config.engine.minAliveBeforeOrphanMs`) is checked before the four-check ladder. A dispatch that
|
|
44
|
+
hasn't been alive for that duration is skipped — prevents false-positive orphan declarations for
|
|
45
|
+
short-duration agents (e.g. verify agents that complete within ~20s before process tracking catches up).
|
|
46
|
+
Bypassed when `canReapDeadProcess` is true (confirmed-dead at restart).
|
|
47
|
+
|
|
42
48
|
After engine restart, gated on `engineRestartGraceUntil` (default
|
|
43
49
|
20 min) — see [engine-restart.md](engine-restart.md).
|
|
44
50
|
|
|
@@ -56,7 +56,7 @@ A config that never mentions `workspace_manifest` produces the exact same dispat
|
|
|
56
56
|
| **Dispatch — repo gate** | `engine.js spawnAgent()` (right after project resolution) | Calls `shared.agentCanUseRepo(agent, project)`. Mismatch → `completeDispatch(... FAILURE_CLASS.WORKSPACE_MANIFEST_REPO, agentRetryable: false)` and `cleanupTempAgent` runs. Non-retryable because the structural answer is "widen the manifest or pick a different agent". |
|
|
57
57
|
| **Spawn — tool merge** | `engine.js spawnAgent()` → `_buildAgentSpawnFlags(..., allowedTools)` | `shared.mergeManifestAllowedTools(claudeConfig.allowedTools, manifest.allowed_tools)` produces the intersection of the runtime baseline and the manifest list. Result is passed as `--allowedTools <csv>` to Claude / Copilot / Codex, so the CLI itself enforces the narrowed surface. Empty manifest list (`[]`) = deny-all. Same merge runs on the steering-resume codepath. |
|
|
58
58
|
| **Agent context** | Future work (playbook.js) | Manifest can be surfaced into the agent prompt so the agent sees its declared scope. Today, `shared.resolveAgentManifest(agent)` returns the resolved struct any caller can read. |
|
|
59
|
-
| **URL fetch** | Advisory today | `
|
|
59
|
+
| **URL fetch** | Advisory today | `allowed_external_urls` is validated at config load. No runtime helper function — a future runtime adapter hook can wire URL-fetch intercepts against the manifest. |
|
|
60
60
|
| **Memory scope** | Advisory today | `shared.agentMemoryScope(agent)` is available for consolidation / playbook / inbox callers. Semantics: `private` = agent only sees its own `knowledge/agents/<id>.md`; `shared` = full team knowledge (current default); `read-only-shared` = reads shared memory but should not write inbox/notes. |
|
|
61
61
|
|
|
62
62
|
## Helpers (in `engine/shared.js`)
|
|
@@ -64,15 +64,13 @@ A config that never mentions `workspace_manifest` produces the exact same dispat
|
|
|
64
64
|
```js
|
|
65
65
|
const { MEMORY_SCOPES, WORKSPACE_MANIFEST_DEFAULTS,
|
|
66
66
|
validateWorkspaceManifest, resolveAgentManifest,
|
|
67
|
-
agentCanUseRepo,
|
|
67
|
+
agentCanUseRepo, agentMemoryScope,
|
|
68
68
|
mergeManifestAllowedTools, formatManifestRejection } = require('./engine/shared');
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
- `validateWorkspaceManifest(manifest)` → `{ ok, errors }`. `null`/`undefined` is valid (uses defaults).
|
|
72
72
|
- `resolveAgentManifest(agent, config?)` → fresh copy of `WORKSPACE_MANIFEST_DEFAULTS` overlaid with the agent's `workspace_manifest`. Malformed manifest silently falls back to defaults; surface the error via `validateWorkspaceManifest` at config-load time.
|
|
73
73
|
- `agentCanUseRepo(agent, projectOrString)` → `bool`. Accepts a project object or a string identifier (`github:owner/repo`, `ado:org/proj/repo`, or bare `owner/repo`). Case-insensitive.
|
|
74
|
-
- `agentCanUseTool(agent, toolName)` → `bool`. Case-sensitive.
|
|
75
|
-
- `agentCanFetchUrl(agent, url)` → `bool`. Wildcard `*.example.com` matches subdomains **and** apex.
|
|
76
74
|
- `agentMemoryScope(agent)` → one of `MEMORY_SCOPES`. Unknown values fall back to `'shared'`.
|
|
77
75
|
- `mergeManifestAllowedTools(baselineCsv, manifestArray)` → merged CSV. Intersection semantics: `null` manifest = baseline unchanged; empty array = deny-all; empty baseline + manifest = manifest as ceiling.
|
|
78
76
|
- `formatManifestRejection({ agentId, kind, target, allowed })` → structured human-readable rejection string used by the dispatch repo gate.
|
package/engine/ado.js
CHANGED
|
@@ -942,7 +942,13 @@ async function getAdoToken(opts = {}) {
|
|
|
942
942
|
}
|
|
943
943
|
}
|
|
944
944
|
|
|
945
|
+
// Test seam — overrides adoFetch for unit tests only. Must be null in production.
|
|
946
|
+
let _adoFetchOverride = null;
|
|
947
|
+
function _setAdoFetchForTest(fn) { _adoFetchOverride = fn; }
|
|
948
|
+
function _resetAdoFetchForTest() { _adoFetchOverride = null; }
|
|
949
|
+
|
|
945
950
|
async function adoFetch(url, token, opts = {}) {
|
|
951
|
+
if (_adoFetchOverride) return _adoFetchOverride(url, token, opts);
|
|
946
952
|
const _retryCount = typeof opts === 'number' ? opts : (opts._retryCount || 0); // backward compat
|
|
947
953
|
const method = (typeof opts === 'object' && opts.method) || 'GET';
|
|
948
954
|
const body = (typeof opts === 'object' && opts.body) || undefined;
|
|
@@ -1642,13 +1648,16 @@ async function pollPrStatus(config) {
|
|
|
1642
1648
|
if (prBuilds.length > 0) {
|
|
1643
1649
|
buildStatus = classifyBuildStatus(prBuilds);
|
|
1644
1650
|
if (buildStatus === BUILD_STATUS.FAILING) {
|
|
1645
|
-
|
|
1651
|
+
// M005: count all failed builds for build-fix-complex routing
|
|
1652
|
+
const failedBuilds = prBuilds.filter(b => b.result === 'failed');
|
|
1653
|
+
const failed = failedBuilds[0];
|
|
1646
1654
|
buildFailReason = failed?.definition?.name || 'Build failed';
|
|
1647
1655
|
buildFailureSignature = shared.safeSlugComponent([
|
|
1648
1656
|
failed?.definition?.name,
|
|
1649
1657
|
failed?.result,
|
|
1650
1658
|
failed?.status,
|
|
1651
1659
|
].filter(Boolean).join('\n') || buildFailReason, 80);
|
|
1660
|
+
pr._failedCheckCount = failedBuilds.length;
|
|
1652
1661
|
}
|
|
1653
1662
|
} else if (allBuilds.length > 0) {
|
|
1654
1663
|
// Stale merge-commit classification (Issue #2747). ADO returned
|
|
@@ -1738,6 +1747,7 @@ async function pollPrStatus(config) {
|
|
|
1738
1747
|
if (buildStatus === BUILD_STATUS.PASSING) {
|
|
1739
1748
|
delete pr.buildErrorLog;
|
|
1740
1749
|
delete pr.buildFailureSignature;
|
|
1750
|
+
delete pr._failedCheckCount; // M005: clear on recovery
|
|
1741
1751
|
// Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
|
|
1742
1752
|
if (pr.buildFixAttempts) { delete pr.buildFixAttempts; }
|
|
1743
1753
|
}
|
|
@@ -2945,6 +2955,8 @@ module.exports = {
|
|
|
2945
2955
|
_resetAdoThrottle, // exported for testing
|
|
2946
2956
|
_setAdoThrottleForTest, // exported for testing
|
|
2947
2957
|
_setAdoTokenForTest, // exported for testing
|
|
2958
|
+
_setAdoFetchForTest, // exported for testing
|
|
2959
|
+
_resetAdoFetchForTest, // exported for testing
|
|
2948
2960
|
// Exported for unit tests — engine code MUST go through pollPrStatus / reconcilePrs.
|
|
2949
2961
|
decodeUrlSegment,
|
|
2950
2962
|
stripGitSuffix,
|
package/engine/cleanup.js
CHANGED
|
@@ -1643,7 +1643,7 @@ function runPeriodicWorktreeSweep(config) {
|
|
|
1643
1643
|
// null" claim is true for the dispatch-end GC but NOT for this registry-derived
|
|
1644
1644
|
// periodic sweep.)
|
|
1645
1645
|
const projects = allProjects.filter(p => {
|
|
1646
|
-
try { return !shared.isLiveCheckoutProject(p); } catch { return
|
|
1646
|
+
try { return !shared.isLiveCheckoutProject(p); } catch { return false; } // W-mquoe8fu000w9383: fail-CLOSED — exclude from GC when checkout mode cannot be determined
|
|
1647
1647
|
});
|
|
1648
1648
|
const liveSkipped = allProjects.length - projects.length;
|
|
1649
1649
|
if (projects.length === 0) {
|
package/engine/dispatch.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* engine/dispatch.js — Dispatch queue management: add, complete, mutate, alerts.
|
|
3
3
|
* Extracted from engine.js for modularity. No logic changes.
|
|
4
|
+
*
|
|
5
|
+
* ## Dispatch meta schema — optional fields (M006)
|
|
6
|
+
*
|
|
7
|
+
* Dispatch items carry a free-form `meta` object. The following optional fields
|
|
8
|
+
* are part of the typed handoff envelope contract (added in M006):
|
|
9
|
+
*
|
|
10
|
+
* meta.from_agent {string} — agent id of the completing agent that queued
|
|
11
|
+
* this follow-up (e.g. 'lambert', 'temp-abc123').
|
|
12
|
+
* meta.from_conclusion {string} — 1–3 sentence structured summary of what the
|
|
13
|
+
* completing agent concluded before handing off.
|
|
14
|
+
*
|
|
15
|
+
* These fields are optional and additive. Dispatches without them are unaffected.
|
|
16
|
+
* When present, engine/playbook.js injects a "Handoff Context" section into the
|
|
17
|
+
* rendered prompt so the receiving agent has explicit orientation.
|
|
4
18
|
*/
|
|
5
19
|
|
|
6
20
|
const fs = require('fs');
|
package/engine/github.js
CHANGED
|
@@ -1037,7 +1037,9 @@ async function pollPrStatus(config) {
|
|
|
1037
1037
|
|
|
1038
1038
|
if (hasFailed) {
|
|
1039
1039
|
buildStatus = BUILD_STATUS.FAILING;
|
|
1040
|
-
|
|
1040
|
+
// M005: count all failing runs for build-fix-complex routing
|
|
1041
|
+
const failedRuns = runs.filter(r => r.conclusion === 'failure' || r.conclusion === 'timed_out' || r.conclusion === 'startup_failure');
|
|
1042
|
+
const failed = failedRuns[0];
|
|
1041
1043
|
buildFailReason = failed?.name || 'Check failed';
|
|
1042
1044
|
buildFailureSignature = shared.safeSlugComponent([
|
|
1043
1045
|
failed?.name,
|
|
@@ -1046,6 +1048,7 @@ async function pollPrStatus(config) {
|
|
|
1046
1048
|
failed?.output?.summary,
|
|
1047
1049
|
failed?.output?.text,
|
|
1048
1050
|
].filter(Boolean).join('\n') || buildFailReason, 80);
|
|
1051
|
+
pr._failedCheckCount = failedRuns.length;
|
|
1049
1052
|
} else if (allDone && allPassed) {
|
|
1050
1053
|
buildStatus = BUILD_STATUS.PASSING;
|
|
1051
1054
|
} else if (allDone) {
|
|
@@ -1079,6 +1082,7 @@ async function pollPrStatus(config) {
|
|
|
1079
1082
|
if (buildStatus === BUILD_STATUS.PASSING) {
|
|
1080
1083
|
delete pr.buildErrorLog;
|
|
1081
1084
|
delete pr.buildFailureSignature;
|
|
1085
|
+
delete pr._failedCheckCount; // M005: clear on recovery
|
|
1082
1086
|
// Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
|
|
1083
1087
|
if (pr.buildFixAttempts) { delete pr.buildFixAttempts; }
|
|
1084
1088
|
}
|
package/engine/lifecycle.js
CHANGED
|
@@ -4765,7 +4765,7 @@ function handleHarnessIterationResult(stdout, structuredCompletion, meta, config
|
|
|
4765
4765
|
* - All RMW on dispatch.json goes through addToDispatch (mutateDispatch), per
|
|
4766
4766
|
* the concurrency rules in CLAUDE.md.
|
|
4767
4767
|
*/
|
|
4768
|
-
function dispatchReReviewForFix(fixDispatchItem, meta, config) {
|
|
4768
|
+
function dispatchReReviewForFix(fixDispatchItem, meta, config, opts = {}) {
|
|
4769
4769
|
const addressesReviewWi = meta?.addresses_review_wi || meta?.item?.meta?.addresses_review_wi || null;
|
|
4770
4770
|
if (!addressesReviewWi) return null;
|
|
4771
4771
|
const pr = meta?.pr;
|
|
@@ -4814,6 +4814,15 @@ function dispatchReReviewForFix(fixDispatchItem, meta, config) {
|
|
|
4814
4814
|
const projMeta = project;
|
|
4815
4815
|
const headSha = String(livePr.headSha || livePr._adoSourceCommit || livePr._adoHeadCommit || pr.headSha || '').trim();
|
|
4816
4816
|
const reReviewKey = `rereview-${project?.name || 'default'}-${shared.getPrDisplayId ? shared.getPrDisplayId(livePr) : pr.id}-${headSha ? headSha.slice(0, 8) : 'nohead'}`;
|
|
4817
|
+
// M006 — typed handoff envelope: stamp from_agent / from_conclusion on the
|
|
4818
|
+
// re-review meta so the reviewer agent sees who fixed the PR and what they
|
|
4819
|
+
// concluded. from_agent is the fix agent id; from_conclusion is a 1-sentence
|
|
4820
|
+
// summary derived from the fix resultSummary (capped to avoid bloating the
|
|
4821
|
+
// dispatch queue). Both are optional — the reviewer playbook degrades to
|
|
4822
|
+
// standard orientation when they are absent.
|
|
4823
|
+
const fromAgent = opts.agentId || fixDispatchItem?.agent || '';
|
|
4824
|
+
const rawConclusion = opts.resultSummary || fixDispatchItem?.resultSummary || '';
|
|
4825
|
+
const fromConclusion = rawConclusion ? String(rawConclusion).slice(0, 300) : '';
|
|
4817
4826
|
const rereviewMeta = {
|
|
4818
4827
|
dispatchKey: reReviewKey,
|
|
4819
4828
|
source: 'pr',
|
|
@@ -4822,6 +4831,8 @@ function dispatchReReviewForFix(fixDispatchItem, meta, config) {
|
|
|
4822
4831
|
project: projMeta,
|
|
4823
4832
|
rereview_of: fixDispatchItem?.id || null,
|
|
4824
4833
|
addresses_review_wi: addressesReviewWi,
|
|
4834
|
+
...(fromAgent ? { from_agent: fromAgent } : {}),
|
|
4835
|
+
...(fromConclusion ? { from_conclusion: fromConclusion } : {}),
|
|
4825
4836
|
};
|
|
4826
4837
|
const extraVars = {
|
|
4827
4838
|
pr_id: livePr.id, pr_number: prNumber, pr_title: livePr.title || '', pr_branch: prBranch,
|
|
@@ -5686,7 +5697,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
5686
5697
|
// reviewer. See engine/ado.js:resetReviewerNegativeVote for the verdict-
|
|
5687
5698
|
// flip reset that fires on the resulting APPROVE.
|
|
5688
5699
|
try {
|
|
5689
|
-
dispatchReReviewForFix(dispatchItem, meta, config);
|
|
5700
|
+
dispatchReReviewForFix(dispatchItem, meta, config, { agentId, resultSummary });
|
|
5690
5701
|
} catch (err) {
|
|
5691
5702
|
log('warn', `Re-review auto-dispatch for ${meta?.pr?.id || 'unknown PR'}: ${err.message}`);
|
|
5692
5703
|
}
|
package/engine/pipeline.js
CHANGED
|
@@ -530,7 +530,7 @@ function _findExistingPrdForPlan(planFile, prdDir) {
|
|
|
530
530
|
// safeJsonNoRestore: PRDs are terminal artifacts — never restore archived
|
|
531
531
|
// PRDs from a stale .backup sidecar (W-mouptdh1000h9f39).
|
|
532
532
|
const prd = safeJsonNoRestore(path.join(prdDir, pf));
|
|
533
|
-
if (prd?.source_plan === planFile) return pf;
|
|
533
|
+
if (prd?.source_plan && path.basename(String(prd.source_plan)) === planFile) return pf;
|
|
534
534
|
}
|
|
535
535
|
return null;
|
|
536
536
|
}
|
|
@@ -928,7 +928,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
928
928
|
for (const pf of prdFiles) {
|
|
929
929
|
// safeJsonNoRestore — see _findExistingPrdForPlan above (W-mouptdh1000h9f39).
|
|
930
930
|
const prd = safeJsonNoRestore(path.join(prdDir, pf));
|
|
931
|
-
if (prd?.source_plan === planFile && !(artifacts.prds || []).includes(pf) && !discoveredPrds.includes(pf)) {
|
|
931
|
+
if (prd?.source_plan && path.basename(String(prd.source_plan)) === planFile && !(artifacts.prds || []).includes(pf) && !discoveredPrds.includes(pf)) {
|
|
932
932
|
discoveredPrds.push(pf);
|
|
933
933
|
}
|
|
934
934
|
}
|
package/engine/playbook.js
CHANGED
|
@@ -386,12 +386,21 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
|
386
386
|
// produce matches. Optional for the same reason as the review-only alias.
|
|
387
387
|
'project_skills_block',
|
|
388
388
|
'skip_project_skills',
|
|
389
|
+
// M006 — typed handoff envelope fields. Set on a follow-up dispatch meta
|
|
390
|
+
// when lifecycle.js queues the item after a completing agent so the
|
|
391
|
+
// receiving agent has explicit context about who handed off and why.
|
|
392
|
+
// from_agent: agent id of the completing agent (e.g. 'lambert')
|
|
393
|
+
// from_conclusion: 1–3 sentence structured summary of what that agent concluded
|
|
394
|
+
// Both are optional; playbooks that don't reference them receive empty strings.
|
|
395
|
+
'from_agent',
|
|
396
|
+
'from_conclusion',
|
|
389
397
|
]);
|
|
390
398
|
|
|
391
399
|
const PLAYBOOK_REQUIRED_VARS = {
|
|
392
400
|
'implement': ['item_id', 'item_name', 'branch_name', 'project_path'],
|
|
393
401
|
'implement-shared': ['item_id', 'item_name', 'branch_name', 'worktree_path'],
|
|
394
402
|
'fix': ['pr_id', 'pr_branch'],
|
|
403
|
+
'build-fix-complex': ['pr_id', 'pr_branch'],
|
|
395
404
|
'review': ['pr_id', 'pr_branch'],
|
|
396
405
|
'build-and-test': ['pr_id', 'pr_branch', 'project_path'],
|
|
397
406
|
'explore': ['task_description'],
|
|
@@ -570,6 +579,25 @@ function buildQaValidateContextBlock({ runId, runbook, target, artifactsDir }) {
|
|
|
570
579
|
}
|
|
571
580
|
|
|
572
581
|
|
|
582
|
+
// ─── M006 — Handoff Context Block ────────────────────────────────────────────
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Build a "Handoff Context" appendix section for follow-up dispatches.
|
|
586
|
+
* Called when vars.from_agent or vars.from_conclusion is truthy so the
|
|
587
|
+
* receiving agent knows who handed off and what they concluded.
|
|
588
|
+
*
|
|
589
|
+
* @param {{ fromAgent?: string, fromConclusion?: string }} opts
|
|
590
|
+
* @returns {string} markdown section, or '' when both fields are falsy
|
|
591
|
+
*/
|
|
592
|
+
function buildHandoffContextBlock({ fromAgent, fromConclusion } = {}) {
|
|
593
|
+
if (!fromAgent && !fromConclusion) return '';
|
|
594
|
+
const lines = ['\n\n---\n\n## Handoff Context (from previous agent)\n'];
|
|
595
|
+
if (fromAgent) lines.push(`**Previous agent:** \`${fromAgent}\``);
|
|
596
|
+
if (fromConclusion) lines.push(`\n**Conclusion:** ${fromConclusion}`);
|
|
597
|
+
return lines.join('\n');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
573
601
|
// ─── Playbook Renderer ──────────────────────────────────────────────────────
|
|
574
602
|
|
|
575
603
|
/**
|
|
@@ -823,7 +851,7 @@ function renderPlaybook(type, vars) {
|
|
|
823
851
|
// (of liveValidation.type) runs the full build on the live environment after
|
|
824
852
|
// the PR lands. When autoDispatch is false the agent is responsible for its own
|
|
825
853
|
// inline validation (may fail in isolated worktrees — documented limitation).
|
|
826
|
-
const LIVE_VALIDATION_PLAYBOOKS = new Set(['implement', 'fix', 'docs', 'decompose']);
|
|
854
|
+
const LIVE_VALIDATION_PLAYBOOKS = new Set(['implement', 'fix', 'build-fix-complex', 'docs', 'decompose']);
|
|
827
855
|
if (LIVE_VALIDATION_PLAYBOOKS.has(type)) {
|
|
828
856
|
const lv = matchedProject && matchedProject.liveValidation;
|
|
829
857
|
if (lv && lv.autoDispatch === true) {
|
|
@@ -838,6 +866,18 @@ function renderPlaybook(type, vars) {
|
|
|
838
866
|
}
|
|
839
867
|
}
|
|
840
868
|
|
|
869
|
+
// M006 — typed handoff envelope: inject from_agent/from_conclusion context
|
|
870
|
+
// when the dispatch was queued as a follow-up from a completing agent. Only
|
|
871
|
+
// fires when at least one field is truthy so unrelated dispatches stay clean.
|
|
872
|
+
if (vars.from_agent || vars.from_conclusion) {
|
|
873
|
+
try {
|
|
874
|
+
const block = buildHandoffContextBlock({
|
|
875
|
+
fromAgent: vars.from_agent,
|
|
876
|
+
fromConclusion: vars.from_conclusion,
|
|
877
|
+
});
|
|
878
|
+
if (block) inertAppendices.push(block);
|
|
879
|
+
} catch (e) { log('warn', `handoff-context inject failed: ${e.message}`); }
|
|
880
|
+
}
|
|
841
881
|
// Inject KB guardrail
|
|
842
882
|
content += `\n\n---\n\n## Knowledge Base Rules\n\n`;
|
|
843
883
|
content += `**Never delete, move, or overwrite files in \`knowledge/\`.** The sweep (consolidation engine) is the only process that writes to \`knowledge/\`. If you think a KB file is wrong, note it in your learnings file — do not touch \`knowledge/\` directly.\n`;
|
|
@@ -1266,6 +1306,9 @@ function selectPlaybook(workType, item) {
|
|
|
1266
1306
|
if (workType === WORK_TYPE.FIX && hasPrContext) {
|
|
1267
1307
|
return 'fix';
|
|
1268
1308
|
}
|
|
1309
|
+
if (workType === WORK_TYPE.BUILD_FIX_COMPLEX && hasPrContext) {
|
|
1310
|
+
return 'build-fix-complex';
|
|
1311
|
+
}
|
|
1269
1312
|
const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'docs', 'setup', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
|
|
1270
1313
|
return typeSpecificPlaybooks.includes(workType) ? workType : 'work-item';
|
|
1271
1314
|
}
|
|
@@ -1295,7 +1338,10 @@ function buildPrDispatch(agentId, config, project, pr, type, extraVars, taskLabe
|
|
|
1295
1338
|
...extraVars,
|
|
1296
1339
|
task_id: dispatchId,
|
|
1297
1340
|
};
|
|
1298
|
-
|
|
1341
|
+
// M005: use selectPlaybook for routing rather than a hardcoded ternary so
|
|
1342
|
+
// new work types (e.g. BUILD_FIX_COMPLEX → 'build-fix-complex') are picked
|
|
1343
|
+
// up automatically. TEST keeps its legacy alias ('build-and-test' ≠ 'test').
|
|
1344
|
+
const playbookName = type === WORK_TYPE.TEST ? 'build-and-test' : selectPlaybook(type, extraVars || {});
|
|
1299
1345
|
const prompt = renderPlaybook(playbookName, vars);
|
|
1300
1346
|
if (!prompt) return null;
|
|
1301
1347
|
return {
|
package/engine/shared.js
CHANGED
|
@@ -3795,6 +3795,14 @@ const WORK_TYPE = {
|
|
|
3795
3795
|
// so callers don't need to also set skipPr:true. Still worktree-requiring
|
|
3796
3796
|
// and still project-required (mirrors implement/fix HTTP-validate path).
|
|
3797
3797
|
SETUP: 'setup',
|
|
3798
|
+
// BUILD_FIX_COMPLEX (M005 / SHERLOC): engine-dispatched two-phase build-fix
|
|
3799
|
+
// variant for multi-file or multi-test failures. Phase 1 (Localization)
|
|
3800
|
+
// requires the agent to document fault files, root-cause hypothesis, and
|
|
3801
|
+
// proposed repair before making any edits. Phase 2 (Implementation) executes
|
|
3802
|
+
// the plan. Dispatched automatically by the engine when a PR's build failure
|
|
3803
|
+
// spans more than one file/test. Always operates on a PR branch (PR context
|
|
3804
|
+
// required at dispatch time, same as FIX).
|
|
3805
|
+
BUILD_FIX_COMPLEX: 'build-fix-complex',
|
|
3798
3806
|
};
|
|
3799
3807
|
|
|
3800
3808
|
// Work types whose dispatch path requires a per-project git worktree. The
|
|
@@ -3828,6 +3836,10 @@ const WORKTREE_REQUIRING_TYPES = new Set([
|
|
|
3828
3836
|
// worktree resolver falls back to MINIONS_DIR's parent and collapses to a
|
|
3829
3837
|
// drive root on installs where MINIONS_DIR sits one level below the root.
|
|
3830
3838
|
WORK_TYPE.SETUP,
|
|
3839
|
+
// BUILD_FIX_COMPLEX (M005): runs on the PR's source branch (like FIX) so a
|
|
3840
|
+
// real project worktree is required. Without a project the worktree resolver
|
|
3841
|
+
// falls back to MINIONS_DIR's parent and can collapse to a drive root.
|
|
3842
|
+
WORK_TYPE.BUILD_FIX_COMPLEX,
|
|
3831
3843
|
WORK_TYPE.DOCS,
|
|
3832
3844
|
]);
|
|
3833
3845
|
|
|
@@ -3848,6 +3860,7 @@ const VALID_WORK_TYPES = new Set([
|
|
|
3848
3860
|
WORK_TYPE.TEST,
|
|
3849
3861
|
WORK_TYPE.DOCS,
|
|
3850
3862
|
WORK_TYPE.SETUP,
|
|
3863
|
+
WORK_TYPE.BUILD_FIX_COMPLEX,
|
|
3851
3864
|
]);
|
|
3852
3865
|
|
|
3853
3866
|
// Match a leading "Type: <word>" line in a description (handles both bare
|
package/engine.js
CHANGED
|
@@ -7206,7 +7206,11 @@ async function discoverFromPrs(config, project) {
|
|
|
7206
7206
|
}
|
|
7207
7207
|
} catch (e) { log('warn', `Pre-dispatch build check for ${pr.id}: ${e.message} — skipping dispatch`); continue; }
|
|
7208
7208
|
|
|
7209
|
-
|
|
7209
|
+
// M005 (SHERLOC): route to build-fix-complex when multiple checks failed;
|
|
7210
|
+
// single-check failures stay on the fast 'fix' path.
|
|
7211
|
+
const isComplexBuildFailure = (pr._failedCheckCount != null ? pr._failedCheckCount : 1) > 1;
|
|
7212
|
+
const buildFixWorkType = isComplexBuildFailure ? WORK_TYPE.BUILD_FIX_COMPLEX : WORK_TYPE.FIX;
|
|
7213
|
+
const agentId = resolveAgent(buildFixWorkType, config, { authorAgent: pr.agent });
|
|
7210
7214
|
if (!agentId) continue;
|
|
7211
7215
|
const prBranch = ensurePrBranchForDispatch(project, pr, 'build-fix');
|
|
7212
7216
|
if (!prBranch) continue;
|
|
@@ -7225,7 +7229,7 @@ async function discoverFromPrs(config, project) {
|
|
|
7225
7229
|
pr.url ? `PR URL: ${pr.url}` : '',
|
|
7226
7230
|
].filter(Boolean).join('\n');
|
|
7227
7231
|
|
|
7228
|
-
const item = buildPrDispatch(agentId, config, project, pr,
|
|
7232
|
+
const item = buildPrDispatch(agentId, config, project, pr, buildFixWorkType, {
|
|
7229
7233
|
pr_id: pr.id, pr_branch: prBranch,
|
|
7230
7234
|
review_note: reviewNote,
|
|
7231
7235
|
}, `Fix build failure on ${pr.id}: ${pr.title || ''}`, { dispatchKey: key, cooldownKey: key, automationCauseKey: buildCauseKey, source: 'pr', pr, branch: prBranch, project: projMeta });
|
|
@@ -7614,6 +7618,12 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
|
|
|
7614
7618
|
// Either flag suppresses both blocks on the dispatch.
|
|
7615
7619
|
skip_project_review_skills: !!(item.meta && item.meta.skipProjectReviewSkills),
|
|
7616
7620
|
skip_project_skills: !!(item.meta && (item.meta.skipProjectSkills || item.meta.skipProjectReviewSkills)),
|
|
7621
|
+
// M006 — typed handoff envelope. Set on follow-up dispatch metas by
|
|
7622
|
+
// lifecycle.js when queuing an item after a completing agent. The
|
|
7623
|
+
// renderPlaybook path injects a "Handoff Context" section when truthy
|
|
7624
|
+
// so the receiving agent sees who handed off and what they concluded.
|
|
7625
|
+
from_agent: (item.meta && item.meta.from_agent) ? String(item.meta.from_agent) : '',
|
|
7626
|
+
from_conclusion: (item.meta && item.meta.from_conclusion) ? String(item.meta.from_conclusion) : '',
|
|
7617
7627
|
};
|
|
7618
7628
|
const cpResult = buildWorkItemDispatchVars(item, vars, config, {
|
|
7619
7629
|
worktreePath: vars.worktree_path || root,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2282",
|
|
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,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
requiresProjectContext: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Playbook: Fix Complex Build Failure (SHERLOC two-phase)
|
|
6
|
+
|
|
7
|
+
You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
|
|
8
|
+
TEAM ROOT: {{team_root}}
|
|
9
|
+
|
|
10
|
+
Repository ID comes from `.minions/config.json` under `project.repositoryId`.
|
|
11
|
+
Repo: {{repo_name}} | Org: {{ado_org}} | Project: {{ado_project}}
|
|
12
|
+
|
|
13
|
+
## Your Task
|
|
14
|
+
|
|
15
|
+
Fix the complex build failure on **{{pr_id}}**: {{pr_title}}
|
|
16
|
+
Branch: `{{pr_branch}}`
|
|
17
|
+
|
|
18
|
+
This dispatch was routed to the complex-failure path because the CI failure spans **multiple files, multiple failing tests, or multiple failing checks**. The fast-path build-fix playbook is not used here. You must complete the Localization phase before writing any code.
|
|
19
|
+
|
|
20
|
+
{{checkpoint_context}}
|
|
21
|
+
|
|
22
|
+
## Build Failure Context
|
|
23
|
+
|
|
24
|
+
{{review_note}}
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Localization
|
|
29
|
+
|
|
30
|
+
> **Complete this section before editing any code.** Produce a structured fault hypothesis. If you cannot identify the root cause with confidence, expand your investigation — do not skip ahead to Implementation.
|
|
31
|
+
|
|
32
|
+
Inspect the live CI logs, failing test output, and relevant source files. Then document:
|
|
33
|
+
|
|
34
|
+
### 1. Fault File(s)
|
|
35
|
+
|
|
36
|
+
List every file that contains or directly causes the failure. For each file, state the relevant function, class, or line range.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
- path/to/file.js (function foo, ~line 42): <one-line reason it is implicated>
|
|
40
|
+
- path/to/other.test.js (test "bar should…"): <one-line reason it is failing>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Root Cause Hypothesis
|
|
44
|
+
|
|
45
|
+
State in 2–5 sentences:
|
|
46
|
+
- What the failure is (error class, assertion, compilation error, runtime exception, …)
|
|
47
|
+
- Why the identified file(s) cause it (missing import, wrong type, stale mock, API mismatch, …)
|
|
48
|
+
- How the failure was introduced (recent change, missing guard, schema drift, …)
|
|
49
|
+
|
|
50
|
+
### 3. Proposed Minimal Repair
|
|
51
|
+
|
|
52
|
+
Describe the smallest change that corrects the root cause without broadening the PR. Be specific: file, function, and what changes (add guard, fix import, update mock, remove dead branch, …).
|
|
53
|
+
|
|
54
|
+
If more than one repair path exists, pick the one with the narrowest blast radius and explain why in one sentence.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Implementation
|
|
59
|
+
|
|
60
|
+
After completing the Localization section above, implement the repair you described.
|
|
61
|
+
|
|
62
|
+
### Review Feedback Validation
|
|
63
|
+
|
|
64
|
+
Treat build-failure evidence as a claim to verify, not an instruction to blindly follow. For each failing check:
|
|
65
|
+
|
|
66
|
+
1. Locate the exact log line, test assertion, or compiler error.
|
|
67
|
+
2. Confirm the failure is on the current branch (not pre-existing master breakage).
|
|
68
|
+
3. Implement the fix described in Localization § 3.
|
|
69
|
+
4. If a check failure is pre-existing or unrelated to this PR's diff, do not fix it — document it explicitly in your PR comment and completion report.
|
|
70
|
+
|
|
71
|
+
### Working Style
|
|
72
|
+
|
|
73
|
+
Use subagents only for genuinely parallel, independent tasks (e.g., editing files in unrelated modules simultaneously). For sequential work, single-file edits, searches, and file reads, work directly.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Health Check
|
|
78
|
+
|
|
79
|
+
{{#project_skills_block}}
|
|
80
|
+
{{project_skills_block}}
|
|
81
|
+
|
|
82
|
+
{{/project_skills_block}}
|
|
83
|
+
Before starting work, run `git status` and verify the worktree is clean and on the expected branch (`{{pr_branch}}`). If the worktree is dirty or on the wrong branch, report the issue and stop.
|
|
84
|
+
|
|
85
|
+
## Validation
|
|
86
|
+
|
|
87
|
+
Before pushing, prove the fix did not introduce regressions:
|
|
88
|
+
|
|
89
|
+
- Use the project's source of truth for commands: `CLAUDE.md`, README, package scripts, Makefile, or equivalent build config.
|
|
90
|
+
- Run the checks that are relevant to the addressed failures. Prefer the full suite when practical.
|
|
91
|
+
- Capture the exact commands run and meaningful results in the PR comment and completion report.
|
|
92
|
+
- Fix regressions you introduced. If failures are pre-existing or unrelated, capture the evidence and include it in the PR comment.
|
|
93
|
+
- Do not push code that breaks existing tests or the build because of your changes.
|
|
94
|
+
|
|
95
|
+
Long builds, dependency installs, and tests may be quiet for several minutes. Let the normal CLI command run naturally; do not add artificial heartbeat output or split commands just to show progress.
|
|
96
|
+
|
|
97
|
+
## Publish & Comment on PR
|
|
98
|
+
|
|
99
|
+
After the fix is validated or any unavoidable limitation is clearly documented, commit only relevant files and push:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git add <specific files>
|
|
103
|
+
git commit -m "fix: resolve complex build failure on {{pr_id}}"
|
|
104
|
+
git push
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Do NOT remove the worktree — the engine handles cleanup automatically.
|
|
108
|
+
|
|
109
|
+
{{pr_comment_instructions}}
|
|
110
|
+
- pullRequestId: `{{pr_number}}`
|
|
111
|
+
- content: Include the completed Localization hypothesis (fault files, root cause, repair), what was changed (file:line), and build/test validation results.
|
|
112
|
+
- Sign: `Fixed by [Minions](https://icy-water-0224cc51e.2.azurestaticapps.net/minions) ({{agent_name}} — {{agent_role}} · {{agent_model}})`
|
|
113
|
+
|
|
114
|
+
## PR description audit (mandatory unless `meta.skipDescriptionAudit`)
|
|
115
|
+
|
|
116
|
+
<!-- Inlined from playbooks/_pr-description-audit.md — keep in sync (enforced by test/unit/config-and-playbooks.test.js). -->
|
|
117
|
+
|
|
118
|
+
{{#skip_description_audit}}
|
|
119
|
+
> **Audit suppressed.** This dispatch was invoked with `meta.skipDescriptionAudit: true`.
|
|
120
|
+
> Record `meta.descriptionAudit = { ran: false, result: "skipped:meta-flag" }` in your completion report
|
|
121
|
+
> and skip the audit steps below entirely.
|
|
122
|
+
{{/skip_description_audit}}
|
|
123
|
+
|
|
124
|
+
After you push commits to the PR's source branch and BEFORE you mark the work item done, audit the PR description and refresh it to match the new diff. Skip the audit when you pushed no commits this dispatch or when the dispatch is a no-op completion; record the skip in `meta.descriptionAudit`.
|
|
125
|
+
|
|
126
|
+
**Audit steps**
|
|
127
|
+
|
|
128
|
+
1. Fetch the current PR description.
|
|
129
|
+
- GitHub: `gh pr view <num> --repo <owner>/<repo> --json body --jq .body`
|
|
130
|
+
- Azure DevOps: `az repos pr show --id <num> --query description -o tsv`
|
|
131
|
+
2. Compute the diff for the commits you just pushed: `git log --stat <baseline>..HEAD` where `<baseline>` is `origin/<branch>@{1}` for follow-up pushes (or `origin/main` for the very first push).
|
|
132
|
+
3. For each section of the description, evaluate whether the new diff invalidates anything it claims. Focus on: feature lists / "what changed" bullets, config/option tables, file or path lists, line counts, before/after metrics, ASCII diagrams, and embedded markdown image refs.
|
|
133
|
+
4. **No drift** → record `meta.descriptionAudit = { ran: true, result: "no-changes-needed" }` and stop.
|
|
134
|
+
5. **Drift detected** → build a patched body. **Preserve prose voice.** Edit only the stale parts. Do NOT rewrite. Do NOT add new sections. Do NOT change the PR title. Do NOT toggle draft state. Do NOT post a separate PR comment narrating the description change.
|
|
135
|
+
6. Push the patched description.
|
|
136
|
+
- GitHub: write the new body to a temp file, then `gh pr edit <num> --repo <owner>/<repo> --body-file <file>`.
|
|
137
|
+
- Azure DevOps: `az repos pr update --id <num> --description "$(Get-Content -Raw <file>)"`. If the body exceeds ~4 KB, fall back to `az rest --method patch` against `…/pullRequests/<id>?api-version=7.1` with `{ "description": "<body>" }`.
|
|
138
|
+
7. GET-verify the description post-patch by re-running step 1 and confirming the body matches what you sent.
|
|
139
|
+
|
|
140
|
+
**Out-of-scope guardrails**
|
|
141
|
+
|
|
142
|
+
- Don't add screenshots that weren't already there. Refresh only.
|
|
143
|
+
- Don't rewrite description prose beyond the targeted stale edits.
|
|
144
|
+
- Don't modify the PR title; don't toggle draft state, close/reopen the PR.
|
|
145
|
+
- Don't post a separate PR comment summarizing the description change.
|
|
146
|
+
- Don't fail the work item on a screenshot or attachment failure — degrade gracefully and record the skip.
|
|
147
|
+
- Screenshots NEVER land in the repo. `agents/<id>/screenshots/` only; never `git add` a PNG.
|
|
148
|
+
|
|
149
|
+
**Completion-report shape**
|
|
150
|
+
|
|
151
|
+
When the audit ran (whether it edited the description or no-op'd), include this in the completion JSON (full schema: `docs/completion-reports.md` → "PR description audit"):
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"meta": {
|
|
156
|
+
"descriptionAudit": {
|
|
157
|
+
"ran": true,
|
|
158
|
+
"result": "description-patched",
|
|
159
|
+
"oldScreenshots": [],
|
|
160
|
+
"newScreenshots": [],
|
|
161
|
+
"patchedSections": ["feature-bullets", "config-table"]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Omit `meta.descriptionAudit` entirely when the audit did not run (no commits pushed, no-op completion). When `meta.skipDescriptionAudit` suppressed the audit, set `ran: false` and `result: "skipped:meta-flag"`.
|
|
168
|
+
|
|
169
|
+
## When to Stop
|
|
170
|
+
|
|
171
|
+
Your task is complete when: the Localization hypothesis is documented, the fix is implemented and validated, the branch is pushed, and the PR is commented. Do NOT continue into unrelated improvements.
|
|
172
|
+
|
|
173
|
+
**NEVER run `gh pr merge` or any merge command on this PR.** The engine handles merging after review approval.
|
|
174
|
+
|
|
175
|
+
## Completion
|
|
176
|
+
|
|
177
|
+
After finishing, write the JSON completion report described in the shared rules (schema: `docs/completion-reports.md`). Include the completed Localization section (fault files, root cause, repair) in your `summary` field. A fenced ` ```completion ` block in stdout is accepted only as a compatibility fallback — do not duplicate the schema here.
|
package/routing.md
CHANGED
|
@@ -11,6 +11,7 @@ How the engine decides who handles what. Parsed by engine.js — keep the table
|
|
|
11
11
|
| implement:large | rebecca | dallas |
|
|
12
12
|
| review | ripley | lambert |
|
|
13
13
|
| fix | _author_ | _any_ |
|
|
14
|
+
| build-fix-complex | _author_ | dallas |
|
|
14
15
|
| plan | ripley | rebecca |
|
|
15
16
|
| plan-to-prd | lambert | rebecca |
|
|
16
17
|
| explore | ripley | rebecca |
|
|
@@ -30,6 +31,7 @@ Notes:
|
|
|
30
31
|
- `_author_` means route to the PR author
|
|
31
32
|
- `_any_` means route to any available idle agent (lowest error rate first)
|
|
32
33
|
- `implement:large` is for items with `estimated_complexity: "large"`
|
|
34
|
+
- `build-fix-complex` is for multi-file failures, more than one failing test, or multiple failing checks; simple 1-file-1-test failures use the `fix` work type (fast path)
|
|
33
35
|
- Engine falls back to any idle agent if both preferred and fallback are busy
|
|
34
36
|
- Routing selects an owner; it should not narrow the user's task contract. The assigned agent should behave like the user typed the same task directly into a CLI, with Minions adding only safety, status, and review guardrails.
|
|
35
37
|
- `fix` review-feedback routing sends work to the PR author for context, but the author must still validate review comments as claims before editing and push back with evidence when the comment is invalid, stale, already addressed, out of scope, or harmful.
|