@yemi33/minions 0.1.2215 → 0.1.2217
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/bin/minions.js +22 -0
- package/dashboard/js/render-other.js +6 -6
- package/dashboard/js/settings.js +25 -22
- package/dashboard.js +37 -26
- package/docs/README.md +2 -1
- package/docs/deprecated.json +12 -0
- package/docs/harness-propagation.md +3 -3
- package/docs/live-checkout-mode.md +14 -12
- package/docs/specs/agent-configurability.md +271 -0
- package/engine/cleanup.js +1 -1
- package/engine/cli.js +8 -2
- package/engine/consolidation.js +1 -1
- package/engine/lifecycle.js +1 -1
- package/engine/pipeline.js +48 -5
- package/engine/pr-clone-keep.js +1 -1
- package/engine/preflight.js +38 -13
- package/engine/project-discovery.js +8 -7
- package/engine/projects.js +1 -1
- package/engine/shared.js +111 -19
- package/engine/timeout.js +1 -1
- package/engine/worktree-pool.js +1 -1
- package/engine.js +7 -7
- package/package.json +1 -1
package/bin/minions.js
CHANGED
|
@@ -649,6 +649,28 @@ const { cmd, rest, devMode, devPort } = (() => {
|
|
|
649
649
|
const [firstCmd, ...restArgs] = out;
|
|
650
650
|
return { cmd: firstCmd, rest: restArgs, devMode: dev, devPort: port || DEFAULT_DEV_DASH_PORT };
|
|
651
651
|
})();
|
|
652
|
+
|
|
653
|
+
// ── Node / node:sqlite version gate (issue #244) ────────────────────────────
|
|
654
|
+
// node:sqlite is the only state backend post Phase 9.4 and exists only on
|
|
655
|
+
// Node >= 22.5; the --experimental-sqlite self-reexec at the top of this file
|
|
656
|
+
// is a no-op below 22.5. Without this gate, any command that touches getDb()
|
|
657
|
+
// (`minions status`, `queue`, engine delegations, …) dumps a raw node:sqlite
|
|
658
|
+
// stack trace. Fail closed with the canonical one-line remediation. Numeric
|
|
659
|
+
// version compare via shared helper — never string compares, never throws.
|
|
660
|
+
//
|
|
661
|
+
// Exempt the commands a stranded user still needs: `doctor` re-emits the same
|
|
662
|
+
// remediation as a structured FAIL (and suppresses the downstream fallout
|
|
663
|
+
// warnings); help/version are pure; uninstall/nuke must stay reachable to
|
|
664
|
+
// clean up. Everything else short-circuits before we spawn a child that would
|
|
665
|
+
// only crash with the same trace.
|
|
666
|
+
const NODE_GATE_EXEMPT = new Set([
|
|
667
|
+
'doctor', 'version', '--version', '-v', 'help', '--help', '-h', 'uninstall', 'nuke',
|
|
668
|
+
]);
|
|
669
|
+
if (!shared.nodeSupportsBuiltinSqlite() && cmd && !NODE_GATE_EXEMPT.has(cmd)) {
|
|
670
|
+
console.error(`\n ${shared.nodeSqliteRemediationLine()}\n`);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
|
|
652
674
|
let force = rest.includes('--force');
|
|
653
675
|
const skipScan = rest.includes('--skip-scan');
|
|
654
676
|
const skipStart = rest.includes('--skip-start') || rest.includes('--no-start');
|
|
@@ -73,18 +73,18 @@ function _renderProjectBranch(p) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// Renders a compact pill indicating the project's dispatch mode (W-mqgzcrln002613b3):
|
|
76
|
-
// "Worktrees" (
|
|
76
|
+
// "Worktrees" (worktree — default, agents run in their own git worktree) vs
|
|
77
77
|
// "Live checkout" (live — agents run in-place inside the operator's working
|
|
78
|
-
// tree). Driven by p.
|
|
79
|
-
// unset → '
|
|
78
|
+
// tree). Driven by p.checkoutMode (shared.CHECKOUT_MODES; engine defaults
|
|
79
|
+
// unset → 'worktree'). Live is the riskier/special mode (capped to one mutating
|
|
80
80
|
// dispatch per project, refuses on a dirty tree) so it gets a more prominent
|
|
81
|
-
// color than the muted
|
|
81
|
+
// color than the muted worktree pill.
|
|
82
82
|
function _renderWorktreeModePill(p) {
|
|
83
83
|
if (!p) return '';
|
|
84
|
-
if (p.
|
|
84
|
+
if (p.checkoutMode === 'live') {
|
|
85
85
|
return ' <span class="project-mode-pill project-mode-live" title="Live-checkout dispatch mode — agents run in-place inside the project working tree (no isolated worktree); capped to one mutating dispatch and refused on a dirty tree">⚡ Live checkout</span>';
|
|
86
86
|
}
|
|
87
|
-
return ' <span class="project-mode-pill project-mode-isolated" title="
|
|
87
|
+
return ' <span class="project-mode-pill project-mode-isolated" title="Worktree dispatch mode (default) — each agent runs in its own git worktree">Worktrees</span>';
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function _projectCachePath(project) {
|
package/dashboard/js/settings.js
CHANGED
|
@@ -259,20 +259,22 @@ async function openSettings() {
|
|
|
259
259
|
'<div style="font-size:var(--text-xs);color:var(--muted);margin-top:1px">Parsed from <code>git symbolic-ref refs/remotes/origin/HEAD</code>' + (localBranch ? '. Local HEAD: <code>' + escHtml(localBranch) + '</code>' : '') + '.</div>' +
|
|
260
260
|
'</div>' +
|
|
261
261
|
'</div>';
|
|
262
|
-
// P-a3f9b207 — per-project
|
|
263
|
-
// '
|
|
264
|
-
// runs the agent directly in p.localPath for repos
|
|
265
|
-
// unworkable.
|
|
266
|
-
|
|
267
|
-
|
|
262
|
+
// P-a3f9b207 (consolidated W-mqiaw974) — per-project checkoutMode dropdown
|
|
263
|
+
// + warning chip. Default 'worktree' (engine creates a dedicated worktree
|
|
264
|
+
// per dispatch); 'live' runs the agent directly in p.localPath for repos
|
|
265
|
+
// where worktrees are unworkable. p.checkoutMode is resolved server-side
|
|
266
|
+
// (honors the legacy worktreeMode field). Chip is hidden by default and
|
|
267
|
+
// toggled reactively below.
|
|
268
|
+
var currentWtMode = (p.checkoutMode === 'live') ? 'live' : 'worktree';
|
|
269
|
+
var wtModeSearch = 'checkout mode worktree isolated live dispatch';
|
|
268
270
|
var worktreeModeBlock =
|
|
269
271
|
'<div data-search="' + escHtml(wtModeSearch) + '" style="margin-bottom:6px">' +
|
|
270
|
-
'<label style="font-size:var(--text-sm);color:var(--muted);display:block;margin-bottom:2px">
|
|
271
|
-
'<select id="set-
|
|
272
|
-
'<option value="
|
|
272
|
+
'<label style="font-size:var(--text-sm);color:var(--muted);display:block;margin-bottom:2px">Checkout mode</label>' +
|
|
273
|
+
'<select id="set-checkoutMode-' + escHtml(p.name) + '" data-checkout-mode-select="' + escHtml(p.name) + '" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--text-md)">' +
|
|
274
|
+
'<option value="worktree"' + (currentWtMode === 'worktree' ? ' selected' : '') + '>Worktree (default)</option>' +
|
|
273
275
|
'<option value="live"' + (currentWtMode === 'live' ? ' selected' : '') + '>Live checkout</option>' +
|
|
274
276
|
'</select>' +
|
|
275
|
-
'<div data-
|
|
277
|
+
'<div data-checkout-mode-chip="' + escHtml(p.name) + '" style="' + (currentWtMode === 'live' ? '' : 'display:none;') + 'margin-top:6px;padding:6px 8px;background:rgba(234,179,8,0.12);border:1px solid var(--yellow);border-radius:4px;color:var(--yellow);font-size:var(--text-xs);line-height:1.4">' +
|
|
276
278
|
'⚠ Live mode: dispatches run directly in this repo\'s checkout. Only one mutating dispatch runs at a time. Dirty working trees block dispatch — commit or stash before running.' +
|
|
277
279
|
'</div>' +
|
|
278
280
|
'</div>';
|
|
@@ -666,14 +668,14 @@ async function openSettings() {
|
|
|
666
668
|
});
|
|
667
669
|
|
|
668
670
|
// P-a3f9b207 — toggle the live-mode warning chip reactively when the
|
|
669
|
-
// operator flips the per-project
|
|
670
|
-
// chip is rendered once with display:none for
|
|
671
|
+
// operator flips the per-project checkoutMode dropdown (before save). The
|
|
672
|
+
// chip is rendered once with display:none for worktree-mode projects and
|
|
671
673
|
// visible for already-live projects; this handler only flips the
|
|
672
674
|
// display style — no markup is regenerated.
|
|
673
|
-
document.querySelectorAll('[data-
|
|
675
|
+
document.querySelectorAll('[data-checkout-mode-select]').forEach(function(sel) {
|
|
674
676
|
sel.addEventListener('change', function() {
|
|
675
|
-
const projName = sel.getAttribute('data-
|
|
676
|
-
const chip = document.querySelector('[data-
|
|
677
|
+
const projName = sel.getAttribute('data-checkout-mode-select');
|
|
678
|
+
const chip = document.querySelector('[data-checkout-mode-chip="' + (window.CSS && CSS.escape ? CSS.escape(projName) : projName) + '"]');
|
|
677
679
|
if (!chip) return;
|
|
678
680
|
chip.style.display = (sel.value === 'live') ? '' : 'none';
|
|
679
681
|
});
|
|
@@ -1083,16 +1085,17 @@ async function saveSettings() {
|
|
|
1083
1085
|
// Projects. Empty string = clear the override; the field stays optional.
|
|
1084
1086
|
const mainBranchInput = document.getElementById('set-mainBranch-' + p.name);
|
|
1085
1087
|
const mainBranchValue = mainBranchInput ? mainBranchInput.value.trim() : (p.mainBranch || '');
|
|
1086
|
-
// P-a3f9b207 — per-project
|
|
1087
|
-
// any value other than 'live' so a stale DOM
|
|
1088
|
-
// server-side validator
|
|
1089
|
-
// authoritative gate for unknown
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1088
|
+
// P-a3f9b207 (consolidated W-mqiaw974) — per-project checkoutMode.
|
|
1089
|
+
// Normalize to 'worktree' for any value other than 'live' so a stale DOM
|
|
1090
|
+
// never POSTs garbage; the server-side validator
|
|
1091
|
+
// (shared.validateCheckoutMode) is the authoritative gate for unknown
|
|
1092
|
+
// values.
|
|
1093
|
+
const wtModeInput = document.getElementById('set-checkoutMode-' + p.name);
|
|
1094
|
+
const wtModeValue = (wtModeInput && wtModeInput.value === 'live') ? 'live' : 'worktree';
|
|
1092
1095
|
return {
|
|
1093
1096
|
name: p.name,
|
|
1094
1097
|
mainBranch: mainBranchValue || null,
|
|
1095
|
-
|
|
1098
|
+
checkoutMode: wtModeValue,
|
|
1096
1099
|
workSources: {
|
|
1097
1100
|
pullRequests: { enabled: document.getElementById('set-ws-prs-' + p.name)?.checked ?? true },
|
|
1098
1101
|
workItems: { enabled: document.getElementById('set-ws-wi-' + p.name)?.checked ?? true }
|
package/dashboard.js
CHANGED
|
@@ -358,17 +358,21 @@ function mergeSettingsConfigUpdate(current, candidate, body, patch = {}) {
|
|
|
358
358
|
} else {
|
|
359
359
|
delete currentProject.mainBranch;
|
|
360
360
|
}
|
|
361
|
-
// P-a3f9b208 — mirror
|
|
362
|
-
// candidate clears the field so the engine falls back to "
|
|
361
|
+
// P-a3f9b208 — mirror checkoutMode the same way: empty / unset on
|
|
362
|
+
// candidate clears the field so the engine falls back to "worktree".
|
|
363
363
|
// Without this branch the validated POST-body update (mutated on
|
|
364
364
|
// `candidate` in handleSettingsUpdate) is silently dropped on the way
|
|
365
365
|
// through mergeSettingsConfigUpdate → mutateDashboardConfig and never
|
|
366
366
|
// reaches disk — the endpoint would return 200 but persist nothing.
|
|
367
|
-
if (Object.prototype.hasOwnProperty.call(candidateProject, '
|
|
368
|
-
currentProject.
|
|
367
|
+
if (Object.prototype.hasOwnProperty.call(candidateProject, 'checkoutMode')) {
|
|
368
|
+
currentProject.checkoutMode = candidateProject.checkoutMode;
|
|
369
369
|
} else {
|
|
370
|
-
delete currentProject.
|
|
370
|
+
delete currentProject.checkoutMode;
|
|
371
371
|
}
|
|
372
|
+
// W-mqiaw974 (issue #241): the field was renamed from the legacy
|
|
373
|
+
// `worktreeMode`. Drop any stale legacy key on every settings save so a
|
|
374
|
+
// migrated project never carries both fields.
|
|
375
|
+
delete currentProject.worktreeMode;
|
|
372
376
|
}
|
|
373
377
|
}
|
|
374
378
|
shared.pruneDefaultClaudeConfig(current);
|
|
@@ -1206,7 +1210,7 @@ const _PROJECT_LOCAL_FOOTGUN_WARNING =
|
|
|
1206
1210
|
'git. A fresh `git worktree add` for a mutating dispatch will NOT see them, so ' +
|
|
1207
1211
|
'the dispatched agent silently underperforms. Fix: commit them, move them to ' +
|
|
1208
1212
|
'user scope (~/.claude/skills/..., ~/.copilot/skills/...), or flip the project ' +
|
|
1209
|
-
'to live-checkout mode (
|
|
1213
|
+
'to live-checkout mode (checkoutMode: live).';
|
|
1210
1214
|
|
|
1211
1215
|
function _walkUncommittedHarnessAssets(absStart, rootDir, projectName) {
|
|
1212
1216
|
const results = [];
|
|
@@ -2351,8 +2355,9 @@ function _buildStatusSlowState() {
|
|
|
2351
2355
|
branchMismatch,
|
|
2352
2356
|
// P-a3f9b209 / W-mqgzcrln002613b3 — surface the per-project dispatch
|
|
2353
2357
|
// mode so the Projects view can render a "Worktrees" vs "Live checkout"
|
|
2354
|
-
// pill.
|
|
2355
|
-
|
|
2358
|
+
// pill. resolveCheckoutMode honors the legacy worktreeMode field and
|
|
2359
|
+
// defaults to 'worktree' when unset (matches engine spawn behavior).
|
|
2360
|
+
checkoutMode: shared.resolveCheckoutMode(p),
|
|
2356
2361
|
};
|
|
2357
2362
|
}),
|
|
2358
2363
|
autoMode: {
|
|
@@ -8817,7 +8822,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
8817
8822
|
confirmToken: body.confirmToken,
|
|
8818
8823
|
isValidToken: _consumeProjectConfirmToken,
|
|
8819
8824
|
name: body.name,
|
|
8820
|
-
|
|
8825
|
+
checkoutMode: body.checkoutMode ?? body.worktreeMode,
|
|
8821
8826
|
observeAuthors: Array.isArray(body.observeAuthors) ? body.observeAuthors : undefined,
|
|
8822
8827
|
});
|
|
8823
8828
|
} catch (e) {
|
|
@@ -10369,10 +10374,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10369
10374
|
name: p.name,
|
|
10370
10375
|
localPath: p.localPath || null,
|
|
10371
10376
|
mainBranch: p.mainBranch || null,
|
|
10372
|
-
// P-a3f9b207 — surface
|
|
10373
|
-
// the per-project dropdown.
|
|
10374
|
-
//
|
|
10375
|
-
|
|
10377
|
+
// P-a3f9b207 — surface checkoutMode so the Settings UI can pre-fill
|
|
10378
|
+
// the per-project dropdown. resolveCheckoutMode honors the legacy
|
|
10379
|
+
// worktreeMode field; 'worktree' (default) or 'live'.
|
|
10380
|
+
checkoutMode: shared.resolveCheckoutMode(p),
|
|
10376
10381
|
workSources: {
|
|
10377
10382
|
pullRequests: { enabled: p.workSources?.pullRequests?.enabled !== false, cooldownMinutes: p.workSources?.pullRequests?.cooldownMinutes ?? 30 },
|
|
10378
10383
|
workItems: { enabled: p.workSources?.workItems?.enabled !== false, cooldownMinutes: p.workSources?.workItems?.cooldownMinutes ?? 0 }
|
|
@@ -10755,21 +10760,27 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10755
10760
|
if (raw) proj.mainBranch = raw;
|
|
10756
10761
|
else delete proj.mainBranch;
|
|
10757
10762
|
}
|
|
10758
|
-
// P-a3f9b201 — per-project
|
|
10759
|
-
// 'live' for repos where worktrees are
|
|
10760
|
-
// null clears the override (engine falls
|
|
10761
|
-
// other value flows through
|
|
10762
|
-
// HTTP 400 on unknown values
|
|
10763
|
-
// and
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10763
|
+
// P-a3f9b201 (consolidated W-mqiaw974) — per-project checkoutMode enum
|
|
10764
|
+
// ('worktree' default, 'live' for repos where worktrees are
|
|
10765
|
+
// unworkable). Empty string / null clears the override (engine falls
|
|
10766
|
+
// back to 'worktree'); any other value flows through
|
|
10767
|
+
// shared.validateCheckoutMode which throws HTTP 400 on unknown values
|
|
10768
|
+
// (and silently coerces the legacy 'isolated' → 'worktree'). Accepts
|
|
10769
|
+
// the legacy `worktreeMode` key for back-compat. Errors propagate to
|
|
10770
|
+
// the outer catch and are returned via e.statusCode/e.message.
|
|
10771
|
+
const hasCheckoutMode = Object.prototype.hasOwnProperty.call(update, 'checkoutMode');
|
|
10772
|
+
const hasLegacyMode = Object.prototype.hasOwnProperty.call(update, 'worktreeMode');
|
|
10773
|
+
if (hasCheckoutMode || hasLegacyMode) {
|
|
10774
|
+
const raw = hasCheckoutMode ? update.checkoutMode : update.worktreeMode;
|
|
10775
|
+
if (raw === '' || raw === null || raw === undefined) {
|
|
10776
|
+
delete proj.checkoutMode;
|
|
10768
10777
|
} else {
|
|
10769
|
-
const validated = shared.
|
|
10770
|
-
if (validated === undefined) delete proj.
|
|
10771
|
-
else proj.
|
|
10778
|
+
const validated = shared.validateCheckoutMode(raw);
|
|
10779
|
+
if (validated === undefined) delete proj.checkoutMode;
|
|
10780
|
+
else proj.checkoutMode = validated;
|
|
10772
10781
|
}
|
|
10782
|
+
// Drop the legacy field so a migrated project never carries both.
|
|
10783
|
+
delete proj.worktreeMode;
|
|
10773
10784
|
}
|
|
10774
10785
|
}
|
|
10775
10786
|
}
|
package/docs/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Architecture, design proposals, and lifecycle references for people working on t
|
|
|
15
15
|
|
|
16
16
|
- [branch-derivation.md](branch-derivation.md) — Engine-side branch fallback (`work/<wi-id>`) vs. agent-authored long form, the structured-vs-loose PR-pointer extractors, and the canonical PR-fix duplication incident.
|
|
17
17
|
- [command-center.md](command-center.md) — Command Center (CC) chat panel: persistent Sonnet sessions, `--resume` semantics, system-prompt invalidation, and per-tab session storage.
|
|
18
|
+
- [specs/agent-configurability.md](specs/agent-configurability.md) — Design spec for the user-configurable Role / Agent Library: Phase 1 exposes & edits each role's definition + skills; Phase 2 adds role/agent CRUD. Covers the additive `config.roles` model, resolver tiering, migration from per-agent `charter.md`, and API/UI surface.
|
|
18
19
|
- [completion-reports.md](completion-reports.md) — Canonical schema for the per-spawn completion JSON: trust nonce, `failure_class` enum, `noop` semantics, `retryable` / `needs_rerun` shape, and the artifacts array.
|
|
19
20
|
- [constants.md](constants.md) — Cross-cutting status / type / condition constants (`WI_STATUS`, `WORK_TYPE`, `PR_STATUS`, `WATCH_CONDITION`, …) and the no-magic-strings invariant.
|
|
20
21
|
- [constellation-bridge.md](constellation-bridge.md) — Read-only cross-repo bridge: `engine.constellationBridge.enabled` flag, marker-file contract, and the `minions bridge` subcommand for local debugging.
|
|
@@ -30,7 +31,7 @@ Architecture, design proposals, and lifecycle references for people working on t
|
|
|
30
31
|
- [harness-transparency.md](harness-transparency.md) — The `harnessUsed` self-report contract: capture (agent reports the skills / MCPs / commands / docs it used) → ground (engine cross-checks against `_harnessPropagated` and annotates `grounded:true\|false`, never dropping) → surface (PR comment, notes/inbox digest, work-item modal).
|
|
31
32
|
- [kb-sweep.md](kb-sweep.md) — Knowledge-base consolidation sweep (hash dedup → LLM batch dedup/reclassify → per-entry compress) and the detached runner that keeps it alive across `minions restart`.
|
|
32
33
|
- [keep-processes.md](keep-processes.md) — `meta.keep_processes` sidecar contract: when to use it vs managed-spawn, sidecar schema, caps, and the [`engine/keep-process-sweep.js`](../engine/keep-process-sweep.js) lifecycle.
|
|
33
|
-
- [live-checkout-mode.md](live-checkout-mode.md) — Per-project opt-in `
|
|
34
|
+
- [live-checkout-mode.md](live-checkout-mode.md) — Per-project opt-in `checkoutMode: 'live'`: skips `git worktree add` and dispatches in-place inside `project.localPath` for `repo`-managed trees, submodule-heavy repos, deep Windows paths, and native build state. Includes the refuse-on-dirty contract and the per-project mutating-concurrency cap of 1.
|
|
34
35
|
- [managed-spawn.md](managed-spawn.md) — Engine-owned long-running services (managed-spawn primitive): sidecar schema, healthcheck examples, lifecycle, dashboard API, and the WI 1 (build) → WI 2 (test) chained-validation pattern.
|
|
35
36
|
- [plan-lifecycle.md](plan-lifecycle.md) — Full plan pipeline from `/plan` through PRD materialization, dispatch with dependency gating, verify task, and human archive.
|
|
36
37
|
- [pr-auto-fix-dispatch.md](pr-auto-fix-dispatch.md) — Short reference table mapping each PR auto-fix / review dispatch site in `engine.js#discoverFromPrs` to its gate flag, plus the `pollingPaused` / `autoFixPaused` master kill-switches and the per-provider polling gates.
|
package/docs/deprecated.json
CHANGED
|
@@ -142,6 +142,18 @@
|
|
|
142
142
|
"targetRemovalDate": "2026-06-16",
|
|
143
143
|
"notes": "Unlike the three record-field aliases, nothing is written here — this is purely an input read-fallback. Removal scope (DEFERRED — gated on confirming no client still POSTs `autoObserve`, not on the expired calendar date): drop the `!body.autoObserve` fallback in the link handler in dashboard.js, drop `autoObserve?` from the route registry `params` string, and update any client (dashboard JS, ops scripts) that still POSTs `autoObserve`. After removal, callers that still send `autoObserve` will see their value silently ignored."
|
|
144
144
|
},
|
|
145
|
+
{
|
|
146
|
+
"id": "worktreemode-field-rename",
|
|
147
|
+
"description": "Legacy `project.worktreeMode` config field (enum 'isolated'|'live'). Consolidated by W-mqiaw974 (issue #241) into a single `project.checkoutMode` field (enum 'worktree'|'live'): the old 'isolated' value became the implicit default 'worktree', and the overlapping/never-shipped 'shared' value was removed. The write side is migrated — buildProjectEntry, dashboard settings POST, and projects.addProject all write `checkoutMode`, and the dashboard settings/merge paths delete any stale `worktreeMode` key on save. What survives is a READ-BRIDGE ONLY: `shared.resolveCheckoutMode(project)` (and the `isLiveCheckoutProject` predicate built on it) reads canonical `checkoutMode` first, then falls back to the legacy `worktreeMode` field ('isolated'→'worktree', 'live'→'live') so an un-migrated config.json keeps dispatching live-checkout projects correctly. `validateCheckoutMode` also silently coerces a submitted legacy 'isolated' value to 'worktree'.",
|
|
148
|
+
"code": [
|
|
149
|
+
{ "file": "engine/shared.js", "note": "resolveCheckoutMode reads project.worktreeMode as the fallback when checkoutMode is absent; validateCheckoutMode coerces 'isolated'→'worktree'. The only surviving reads of the legacy field are these two back-compat bridges." },
|
|
150
|
+
{ "file": "engine/projects.js", "note": "addProject threads options.checkoutMode ?? options.worktreeMode into buildProjectEntry (accepts the legacy options key)." },
|
|
151
|
+
{ "file": "dashboard.js", "note": "handleProjectsAdd reads body.checkoutMode ?? body.worktreeMode; handleSettingsUpdate + mergeSettingsConfigUpdate accept the legacy update.worktreeMode key and delete proj.worktreeMode on every save (active migration)." }
|
|
152
|
+
],
|
|
153
|
+
"deprecated": "2026-06-17",
|
|
154
|
+
"targetRemovalDate": "2026-09-17",
|
|
155
|
+
"notes": "Safe to remove on or after 2026-09-17 (90 days, ~3 release windows) once a sweep of every persisted config.json confirms no `worktreeMode` key remains (the dashboard settings save actively migrates each project the next time it is touched, so the residue shrinks over time) AND no external tooling reads `project.worktreeMode`. Removal scope: drop the legacy-field fallback in shared.resolveCheckoutMode, the 'isolated' coercion in validateCheckoutMode, the `?? options.worktreeMode` / `?? body.worktreeMode` input fallbacks in projects.addProject + dashboard handleProjectsAdd, the `update.worktreeMode` acceptance + `delete proj.worktreeMode` migration in dashboard handleSettingsUpdate/mergeSettingsConfigUpdate, and the legacy back-compat tests in test/unit/{worktree-mode-schema,settings-worktree-mode,live-checkout-mode}.test.js."
|
|
156
|
+
},
|
|
145
157
|
{
|
|
146
158
|
"id": "pr-observe-observe-body-param",
|
|
147
159
|
"description": "Legacy `observe` body parameter on `POST /api/pull-requests/observe`. The W-mq5s5ttx000j7ab8 endpoint sub-WI introduces canonical `contextOnly` as the inverse (`observe: false` ⇔ `contextOnly: true`) and keeps `observe` accepted for backward compat. Registering the deprecation here so the alias has a documented removal path; the WI explicitly notes this entry is the implementer's call (it is kept for backward compat and may live longer than the underscore-prefixed record fields).",
|
|
@@ -61,7 +61,7 @@ cwd**. Project-scope skills like `<repo>/.claude/skills/foo/SKILL.md` are
|
|
|
61
61
|
loaded by the CLI only if `<cwd>/.claude/skills/foo/SKILL.md` exists at
|
|
62
62
|
spawn time. See *The worktree-uncommitted footgun* below.
|
|
63
63
|
|
|
64
|
-
Live-checkout mode (`project.
|
|
64
|
+
Live-checkout mode (`project.checkoutMode: 'live'`) collapses both branches
|
|
65
65
|
to `cwd = project.localPath` for every dispatch type, so the agent sees
|
|
66
66
|
the operator's working tree as-is (including uncommitted assets). The
|
|
67
67
|
tradeoff is single-mutating-dispatch concurrency per project — see
|
|
@@ -149,7 +149,7 @@ so the only workarounds today are:
|
|
|
149
149
|
- **Move it user-scope.** Drop it under `~/.claude/skills/bar/` instead —
|
|
150
150
|
the CLI's user-skill discovery still works inside a worktree because
|
|
151
151
|
`--add-dir` attaches `~/.claude`.
|
|
152
|
-
- **Flip to live-checkout mode.** Set `project.
|
|
152
|
+
- **Flip to live-checkout mode.** Set `project.checkoutMode = 'live'` on
|
|
153
153
|
the project so every dispatch runs in `project.localPath`. Caveats in
|
|
154
154
|
`docs/live-checkout-mode.md`.
|
|
155
155
|
|
|
@@ -263,7 +263,7 @@ hand; `collectSkillFiles` / `collectCommandFiles` already do.
|
|
|
263
263
|
## Related docs
|
|
264
264
|
|
|
265
265
|
- `docs/runtime-adapters.md` — full adapter interface table.
|
|
266
|
-
- `docs/live-checkout-mode.md` — `project.
|
|
266
|
+
- `docs/live-checkout-mode.md` — `project.checkoutMode: 'live'`
|
|
267
267
|
contract (alternative to the worktree-add-dir story for repos where
|
|
268
268
|
worktrees are unworkable).
|
|
269
269
|
- `docs/skills.md` — skill block format and auto-extraction targets.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Live-checkout dispatch mode
|
|
2
2
|
|
|
3
|
-
> Per-project opt-in (`project.
|
|
3
|
+
> Per-project opt-in (`project.checkoutMode: 'live'`) that runs Minions agents directly inside the operator's project checkout instead of an engine-managed git worktree.
|
|
4
4
|
>
|
|
5
|
-
> Plan: `plan-w-mq5rmtt9000a42f9-2026-06-08`. PRD: `prd/minions-opg-2026-06-10.json` (items `P-a3f9b201` … `P-a3f9b209`). Default for every project remains `'
|
|
5
|
+
> Plan: `plan-w-mq5rmtt9000a42f9-2026-06-08`. PRD: `prd/minions-opg-2026-06-10.json` (items `P-a3f9b201` … `P-a3f9b209`). Default for every project remains `'worktree'`; the rest of this doc only applies when a project flips itself to `'live'`.
|
|
6
|
+
>
|
|
7
|
+
> **Field consolidation (W-mqiaw974 / issue #241).** The dispatch-mode field was renamed from the legacy `worktreeMode` (`'isolated'`|`'live'`) to a single `checkoutMode` (`'worktree'`|`'live'`). `'isolated'` became the implicit `'worktree'` (default). `shared.resolveCheckoutMode(project)` reads the canonical `checkoutMode` first and falls back to the legacy `worktreeMode` (`'isolated'`→`'worktree'`, `'live'`→`'live'`), so existing live-checkout configs keep working with no rewrite. See `docs/deprecated.json` (id `worktreemode-field-rename`).
|
|
6
8
|
|
|
7
9
|
## Motivation
|
|
8
10
|
|
|
9
|
-
The default `
|
|
11
|
+
The default `worktree` mode spawns each dispatch inside its own git worktree under `../worktrees/`. That works for almost every repo, but it falls over when:
|
|
10
12
|
|
|
11
13
|
- **`repo`-managed multi-project trees** (Android AOSP / Office mobile / Chromium) where the manifest pins dozens of nested repos and `git worktree add` either errors out or strands sub-projects.
|
|
12
14
|
- **Submodule-heavy repos** where worktrees skip `.gitmodules` configuration or leave submodules pointing at the wrong SHA.
|
|
@@ -60,9 +62,9 @@ Pool short-circuits live in `engine.js:1368` and `engine/cleanup.js`; both gate
|
|
|
60
62
|
### Enabling live mode (dashboard)
|
|
61
63
|
|
|
62
64
|
1. Open the Minions dashboard → **Settings** → **Projects** → expand the target project.
|
|
63
|
-
2. Set **
|
|
65
|
+
2. Set **Checkout mode** → **Live checkout**. A yellow warning chip appears immediately:
|
|
64
66
|
> ⚠ Live mode: dispatches run directly in this repo's checkout. Only one mutating dispatch runs at a time. Dirty working trees block dispatch — commit or stash before running.
|
|
65
|
-
3. Click **Save**. The dashboard POSTs the change through `mergeSettingsConfigUpdate`; `shared.
|
|
67
|
+
3. Click **Save**. The dashboard POSTs the change through `mergeSettingsConfigUpdate`; `shared.validateCheckoutMode` rejects anything other than `'worktree'` or `'live'` with HTTP 400 (the legacy `'isolated'` value is silently coerced to `'worktree'`).
|
|
66
68
|
|
|
67
69
|
### Enabling live mode (config.json)
|
|
68
70
|
|
|
@@ -71,13 +73,13 @@ Pool short-circuits live in `engine.js:1368` and `engine/cleanup.js`; both gate
|
|
|
71
73
|
"projects": [{
|
|
72
74
|
"name": "android-aosp",
|
|
73
75
|
"localPath": "/home/yemi/aosp",
|
|
74
|
-
"
|
|
76
|
+
"checkoutMode": "live",
|
|
75
77
|
// …
|
|
76
78
|
}]
|
|
77
79
|
}
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
Absent / `null` / `''` reads as `'
|
|
82
|
+
Absent / `null` / `''` reads as `'worktree'` (the default) — explicit is preferred. A legacy `"worktreeMode": "live"` is still honored (and `"worktreeMode": "isolated"` reads as `'worktree'`), but new configs should use `checkoutMode`.
|
|
81
83
|
|
|
82
84
|
### Recovering from `live_checkout_dirty` refusal
|
|
83
85
|
|
|
@@ -119,11 +121,11 @@ The engine has no opinion about local branches; this hygiene is the operator's r
|
|
|
119
121
|
|
|
120
122
|
Live-checkout mode is deliberately small. These are NOT supported and will not be added:
|
|
121
123
|
|
|
122
|
-
- **No `auto` mode.** The choice between `
|
|
124
|
+
- **No `auto` mode.** The choice between `worktree` and `live` is per-project and operator-set. The engine will not auto-detect submodules / `repo` workspaces and silently switch modes.
|
|
123
125
|
- **No auto-stash on dirty refusal.** The engine refuses and exits; it never `git stash`es to "make room" for a dispatch. Stashes silently mutate the operator's tree and conflate engine state with operator state.
|
|
124
126
|
- **No concurrent dispatches per project.** The cap is 1; raising it would require per-WI subdirectories, which live mode explicitly does not provide.
|
|
125
|
-
- **No per-WI subdirectory isolation.** Live mode is one-checkout-per-project by design. If you need isolation, use `
|
|
126
|
-
- **No per-WI override.** `
|
|
127
|
+
- **No per-WI subdirectory isolation.** Live mode is one-checkout-per-project by design. If you need isolation, use `checkoutMode: 'worktree'` (the default).
|
|
128
|
+
- **No per-WI override.** `checkoutMode` is per-project only. There is no `meta.checkoutMode` on a work item that overrides the project setting.
|
|
127
129
|
- **No auto-pull / no fast-forward on existing branches.** See Guarantee 3. If a PR branch is checked out locally at a different SHA than `origin/<branch>`, the operator resolves it manually.
|
|
128
130
|
- **No special timeout / kill handling.** Live-mode dispatches are killed by PID exactly like isolated-mode dispatches (`engine/timeout.js` header comment). The engine sends SIGTERM/SIGKILL to the tracked process and never touches the working tree on kill.
|
|
129
131
|
|
|
@@ -131,13 +133,13 @@ Live-checkout mode is deliberately small. These are NOT supported and will not b
|
|
|
131
133
|
|
|
132
134
|
| File | Purpose |
|
|
133
135
|
|---|---|
|
|
134
|
-
| `engine/shared.js` — `
|
|
136
|
+
| `engine/shared.js` — `CHECKOUT_MODES`, `validateCheckoutMode`, `resolveCheckoutMode`, `isLiveCheckoutProject` | Enum + validator + back-compat resolver (P-a3f9b201; consolidated W-mqiaw974). |
|
|
135
137
|
| `engine/shared.js` — `resolveSpawnPaths` | Returns `{ cwd: localPath, worktreeRootDir: null, liveMode: true }` for live projects (P-a3f9b202). |
|
|
136
138
|
| `engine/live-checkout.js` — `prepareLiveCheckout` | Pure helper: dirty check, branch resolution from HEAD (no fetch, no `origin/<mainRef>` — issue #226) (P-a3f9b203). |
|
|
137
139
|
| `engine.js` — `spawnAgent` live-mode block | Calls `prepareLiveCheckout`, handles dirty / throw branches, gates `git worktree add` on `!liveMode` (P-a3f9b204). |
|
|
138
140
|
| `engine.js` — dispatcher `liveProjectsInUse` set | Per-project mutating-concurrency cap (P-a3f9b205). |
|
|
139
141
|
| `engine.js` — worktree-pool / orphan-GC short-circuits | `worktreePath===null` no-ops in live mode (P-a3f9b206). |
|
|
140
|
-
| `dashboard/js/settings.js` —
|
|
142
|
+
| `dashboard/js/settings.js` — checkoutMode dropdown + chip | Operator-facing UI (P-a3f9b207). |
|
|
141
143
|
| `test/unit/{resolve-spawn-paths-live-mode,prepare-live-checkout,spawn-agent-live-mode-wiring}.test.js` | Wiring and contract tests (P-a3f9b208). |
|
|
142
144
|
| `engine/shared.js` — `FAILURE_CLASS.LIVE_CHECKOUT_DIRTY` | Non-retryable refusal class. |
|
|
143
145
|
| `engine/dispatch.js` — `isRetryableFailureReason` neverRetry | Excludes `LIVE_CHECKOUT_DIRTY` from mechanical retry. |
|