@yemi33/minions 0.1.2300 → 0.1.2302

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.
@@ -134,7 +134,7 @@ const RENDER_VERSIONS = {
134
134
  prdProgress: 2,
135
135
  prdPrs: 1,
136
136
  inbox: 2,
137
- projects: 3,
137
+ projects: 4,
138
138
  notes: 1,
139
139
  prd: 3,
140
140
  prs: 3,
@@ -91,9 +91,18 @@ function _renderProjectBranch(p) {
91
91
  // unset → 'worktree'). Live is the riskier/special mode (capped to one mutating
92
92
  // dispatch per project, refuses on a dirty tree) so it gets a more prominent
93
93
  // color than the muted worktree pill.
94
+ //
95
+ // Hybrid live-validation: when checkoutMode is 'live' AND p.liveValidationType
96
+ // is set, coding WIs author in isolated worktrees and only the named validation
97
+ // type runs in-place on the live checkout. This is a materially different
98
+ // dispatch profile from full-live, so it gets its own pill.
94
99
  function _renderWorktreeModePill(p) {
95
100
  if (!p) return '';
96
101
  if (p.checkoutMode === 'live') {
102
+ if (p.liveValidationType) {
103
+ const vt = escapeHtml(p.liveValidationType);
104
+ return ' <span class="project-mode-pill project-mode-hybrid" title="Hybrid live-validation — coding work items author in isolated worktrees; only the &quot;' + vt + '&quot; validation type runs in-place on the live checkout (capped to one mutating dispatch, refused on a dirty tree)">⚡ Hybrid · ' + vt + '</span>';
105
+ }
97
106
  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>';
98
107
  }
99
108
  return ' <span class="project-mode-pill project-mode-isolated" title="Worktree dispatch mode (default) — each agent runs in its own git worktree">Worktrees</span>';
@@ -1286,6 +1286,7 @@
1286
1286
  }
1287
1287
  .project-mode-isolated { background: var(--surface); color: var(--muted); border: 1px solid var(--border); }
1288
1288
  .project-mode-live { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid var(--yellow); }
1289
+ .project-mode-hybrid { background: rgba(88,166,255,0.15); color: var(--blue); border: 1px solid var(--blue); }
1289
1290
 
1290
1291
  /* QA tab (W-mpeiwz6k0005bf34-d) — targets / runbooks / runs sections.
1291
1292
  * Reuses surface/border/text tokens defined in :root so the QA page
package/dashboard.js CHANGED
@@ -2719,6 +2719,14 @@ function _buildStatusSlowState() {
2719
2719
  // pill. resolveCheckoutMode honors the legacy worktreeMode field and
2720
2720
  // defaults to 'worktree' when unset (matches engine spawn behavior).
2721
2721
  checkoutMode: shared.resolveCheckoutMode(p),
2722
+ // Surface the hybrid live-validation type (when configured) so the
2723
+ // Projects view can render a distinct "Hybrid" pill: coding WIs author
2724
+ // in isolated worktrees while only the named validation type runs
2725
+ // in-place on the live checkout. Only meaningful when checkoutMode is
2726
+ // 'live'; null otherwise (full-live or worktree projects).
2727
+ liveValidationType: (shared.resolveCheckoutMode(p) === 'live' && p.liveValidation && p.liveValidation.type)
2728
+ ? p.liveValidation.type
2729
+ : null,
2722
2730
  };
2723
2731
  }),
2724
2732
  autoMode: {
@@ -4255,12 +4263,25 @@ function _resetPreambleCache() {
4255
4263
  _preambleCacheTs = 0;
4256
4264
  }
4257
4265
 
4266
+ // Compact one-word-ish label for a project's effective dispatch checkout mode,
4267
+ // for CC state surfaces (preamble + refresh). 'worktree' | 'live' |
4268
+ // 'hybrid (live-validation: <type>)' so CC can see at a glance whether a project
4269
+ // authors in isolated worktrees, runs in-place on the operator tree, or splits
4270
+ // the two (coding → worktree, validation type → live). See resolveCheckoutMode.
4271
+ function _projectCheckoutModeLabel(p) {
4272
+ const mode = shared.resolveCheckoutMode(p);
4273
+ if (mode === 'live' && p && p.liveValidation && p.liveValidation.type) {
4274
+ return `hybrid (live-validation: ${p.liveValidation.type})`;
4275
+ }
4276
+ return mode;
4277
+ }
4278
+
4258
4279
  function buildCCStatePreamble() {
4259
4280
  const now = Date.now();
4260
4281
  if (_preambleCache && now - _preambleCacheTs < PREAMBLE_TTL) return _preambleCache;
4261
4282
  // Lightweight snapshot — just enough to orient. Use tools for details.
4262
4283
  const agents = getAgents().map(a => `- ${a.name} (${a.id}): ${a.status}${a.currentTask ? ' — ' + a.currentTask.slice(0, 60) : ''}`).join('\n');
4263
- const projects = PROJECTS.map(p => `- ${p.name}: ${p.localPath}`).join('\n');
4284
+ const projects = PROJECTS.map(p => `- ${p.name}: ${p.localPath} [${_projectCheckoutModeLabel(p)}]`).join('\n');
4264
4285
 
4265
4286
  const dq = getDispatchQueue();
4266
4287
  const active = (dq.active || []).map(d => `- ${d.agentName || d.agent}: ${(d.task || '').slice(0, 50)}`).join('\n') || '(none)';
@@ -4332,7 +4353,7 @@ function buildCCStateRefresh() {
4332
4353
  // Agent roster carries the current task too — without it a resumed session
4333
4354
  // sees only that an agent is "busy", never *what* it is busy with.
4334
4355
  const agents = getAgents().map(a => `- ${a.name}: ${a.status}${a.currentTask ? ' — ' + a.currentTask.slice(0, 50) : ''}`).join('\n');
4335
- const projects = PROJECTS.map(p => `- ${p.name} (${p.repo || p.localPath})`).join('\n');
4356
+ const projects = PROJECTS.map(p => `- ${p.name} (${p.repo || p.localPath}) [${_projectCheckoutModeLabel(p)}]`).join('\n');
4336
4357
 
4337
4358
  // MCP servers — just names + source for orientation
4338
4359
  let mcpLine = '(none discovered)';
@@ -11329,6 +11350,31 @@ What would you like to discuss or change? When you're happy, say "approve" and I
11329
11350
  }
11330
11351
  // Drop the legacy field so a migrated project never carries both.
11331
11352
  delete proj.worktreeMode;
11353
+ // Defensive cleanup: liveValidation is only honored under
11354
+ // checkoutMode:'live'. If this update moves the project OFF live,
11355
+ // drop any stale hybrid block so resolveCheckoutMode doesn't have to
11356
+ // warn-and-ignore an orphaned liveValidation on every resolve.
11357
+ if (shared.resolveCheckoutMode(proj) !== 'live') {
11358
+ delete proj.liveValidation;
11359
+ }
11360
+ }
11361
+ // Hybrid live-validation block (per-project). Only meaningful when the
11362
+ // resolved checkout mode is 'live' — runs AFTER the checkoutMode block
11363
+ // above so resolveCheckoutMode(proj) reflects the intended final mode.
11364
+ // Empty string / null clears the override; any other value flows
11365
+ // through shared.validateLiveValidation which throws HTTP 400 on
11366
+ // malformed input or when checkoutMode !== 'live'. Absent key leaves
11367
+ // any existing block untouched (so a Settings save that omits the
11368
+ // field never silently drops a configured hybrid project).
11369
+ if (Object.prototype.hasOwnProperty.call(update, 'liveValidation')) {
11370
+ const rawLv = update.liveValidation;
11371
+ if (rawLv === '' || rawLv === null || rawLv === undefined) {
11372
+ delete proj.liveValidation;
11373
+ } else {
11374
+ const validatedLv = shared.validateLiveValidation(rawLv, { checkoutMode: shared.resolveCheckoutMode(proj) });
11375
+ if (validatedLv === undefined) delete proj.liveValidation;
11376
+ else proj.liveValidation = validatedLv;
11377
+ }
11332
11378
  }
11333
11379
  }
11334
11380
  }
@@ -14293,7 +14339,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
14293
14339
 
14294
14340
  // Settings
14295
14341
  { method: 'GET', path: '/api/settings', desc: 'Return current engine + claude + routing config', handler: handleSettingsRead },
14296
- { method: 'POST', path: '/api/settings', desc: 'Update engine + claude + agent + projects config', params: 'engine?, claude?, agents?, projects?', handler: handleSettingsUpdate },
14342
+ { method: 'POST', path: '/api/settings', desc: 'Update engine + claude + agent + projects config', params: 'engine?, claude?, agents?, projects? (per-project: checkoutMode, liveValidation, mainBranch, workSources)', handler: handleSettingsUpdate },
14297
14343
  { method: 'POST', path: '/api/settings/routing', desc: 'Update routing.md', params: 'content', handler: handleSettingsRouting },
14298
14344
  { method: 'POST', path: '/api/settings/reset', desc: 'Reset engine + claude + agent settings to defaults', handler: handleSettingsReset },
14299
14345
 
@@ -14504,6 +14550,7 @@ module.exports = {
14504
14550
  _resolveScheduleProjectValue: resolveScheduleProjectValue,
14505
14551
  _collectArchivedWorkItems: collectArchivedWorkItems,
14506
14552
  buildCCStatePreamble,
14553
+ _projectCheckoutModeLabel,
14507
14554
  buildCCStateRefresh,
14508
14555
  _resetRefreshCache,
14509
14556
  _routesAsMeta,
package/engine/shared.js CHANGED
@@ -2757,6 +2757,47 @@ function validateCheckoutMode(value) {
2757
2757
  return value;
2758
2758
  }
2759
2759
 
2760
+ // Validate + normalize a per-project `liveValidation` block (hybrid mode).
2761
+ // Hybrid = checkoutMode:'live' + liveValidation:{ type, autoDispatch }: coding
2762
+ // work items author in isolated worktrees (escaping the live cap) while only
2763
+ // work items whose type === liveValidation.type run in-place on the live
2764
+ // checkout. See resolveCheckoutMode (which routes per work-item type) and
2765
+ // docs/live-checkout-mode.md.
2766
+ //
2767
+ // Returns:
2768
+ // - undefined → caller should clear the field (empty / null / '' input)
2769
+ // - { type, autoDispatch? } normalized object on success
2770
+ // Throws HTTP 400 (via _httpError) on malformed input, or when the effective
2771
+ // checkout mode is not 'live' (liveValidation is meaningless without it — it is
2772
+ // silently ignored at resolve time, so we refuse to persist a misconfiguration).
2773
+ //
2774
+ // `opts.checkoutMode` is the project's effective (resolved) checkout mode; pass
2775
+ // shared.resolveCheckoutMode(project) AFTER any checkoutMode update has been
2776
+ // applied so the gate sees the intended final mode.
2777
+ function validateLiveValidation(value, opts) {
2778
+ if (value === undefined || value === null || value === '') return undefined;
2779
+ if (typeof value !== 'object' || Array.isArray(value)) {
2780
+ throw _httpError(400, 'Invalid liveValidation: must be an object { type, autoDispatch }.');
2781
+ }
2782
+ const checkoutMode = opts && opts.checkoutMode;
2783
+ if (checkoutMode !== CHECKOUT_MODES.LIVE) {
2784
+ throw _httpError(400, 'liveValidation requires checkoutMode "live" (hybrid mode). Set checkoutMode to "live" first, or clear liveValidation.');
2785
+ }
2786
+ const type = typeof value.type === 'string' ? value.type.trim() : '';
2787
+ if (!type) {
2788
+ throw _httpError(400, 'Invalid liveValidation: "type" is required and must be a non-empty string — the work-item type that runs on the live checkout (e.g. "build-and-test").');
2789
+ }
2790
+ const out = { type };
2791
+ // autoDispatch (optional): when true, lifecycle auto-creates a validation WI
2792
+ // of `type` after each coding WI completes with a PR (engine/lifecycle.js
2793
+ // autoDispatchLiveValidationWi). Coerce to a strict boolean; absent leaves it
2794
+ // off so the operator opts in explicitly.
2795
+ if (Object.prototype.hasOwnProperty.call(value, 'autoDispatch')) {
2796
+ out.autoDispatch = !!value.autoDispatch;
2797
+ }
2798
+ return out;
2799
+ }
2800
+
2760
2801
  // ── Engine Defaults ─────────────────────────────────────────────────────────
2761
2802
  // Single source of truth for engine configuration defaults.
2762
2803
  // Used by: engine.js, minions.js (init). config.template.json only has the project schema.
@@ -9192,6 +9233,7 @@ module.exports = {
9192
9233
  validateProjectPath,
9193
9234
  CHECKOUT_MODES,
9194
9235
  validateCheckoutMode,
9236
+ validateLiveValidation,
9195
9237
  resolveCheckoutMode,
9196
9238
  isLiveCheckoutProject,
9197
9239
  resolveLiveCheckoutAutoReset,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2300",
3
+ "version": "0.1.2302",
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"
@@ -319,6 +319,27 @@ The `X-CC-Turn-Id` header is the audit trail — the handler stamps it onto the
319
319
 
320
320
  **Errors:** if a `curl` returns 4xx/5xx, surface the error text in your reply so the user sees what went wrong. Don't retry blindly — usually the body explains the missing field.
321
321
 
322
+ ## Project checkout modes (worktree / live / hybrid)
323
+
324
+ Every configured project has an effective **checkout mode** — surfaced in your state snapshot next to the project as `[worktree]`, `[live]`, or `[hybrid (live-validation: <type>)]`. It controls where dispatched agents run:
325
+
326
+ - **`worktree`** (default) — each dispatch gets its own isolated `git worktree`. Agents run fully in parallel; nothing touches the operator's working tree. Use for normal repos.
327
+ - **`live`** — agents run **in-place** inside the project's `localPath` (no worktree). The engine caps this to **one mutating dispatch at a time** per project and **refuses on a dirty tree**. Use only when worktrees are unworkable (e.g. Android `repo`, submodules, deep Windows paths, emulators that bind the real checkout).
328
+ - **`hybrid`** — `live` **plus** a `liveValidation: { type, autoDispatch }` block. Coding work items (`implement`/`fix`/`docs`/`decompose`) author in **isolated worktrees** (full parallelism), while **only** work items whose type matches `liveValidation.type` (e.g. `build-and-test`) run **in-place on the live checkout**. This is the best of both: parallel code authoring + a real on-disk build/validation that can't run in a worktree.
329
+
330
+ **When to recommend hybrid:** the project's build/test/validation genuinely cannot run in an isolated worktree (it needs the real checkout — submodules, a `repo`-managed tree, an emulator/dev-server bound to `localPath`, deep-path tooling), **but** you still want coding agents to work in parallel rather than serialize through the single live checkout. If the *whole* workflow must run on the real tree, use plain `live`. If nothing needs the real tree, stay on `worktree`.
331
+
332
+ **Configuring it** (via the projects array on `POST /api/settings` — never hand-edit `config.json`):
333
+ ```bash
334
+ # Switch a project to hybrid mode (live checkout + deferred build-and-test validation)
335
+ curl -s -X POST http://localhost:{{dashboard_port}}/api/settings \
336
+ -H 'Content-Type: application/json' -H 'X-CC-Turn-Id: {{cc_turn_id}}' \
337
+ -d '{"projects":[{"name":"<project>","checkoutMode":"live","liveValidation":{"type":"build-and-test","autoDispatch":true}}]}'
338
+ # Clear hybrid (back to plain live): pass "liveValidation": null
339
+ # Clear live entirely (back to worktree): pass "checkoutMode": "worktree" (also clear liveValidation)
340
+ ```
341
+ The server validates: `liveValidation` requires `checkoutMode:"live"` (400 otherwise) and a non-empty `type`. With `autoDispatch:true`, the engine auto-creates a `<type>` validation WI on the live checkout after each coding WI completes with a PR. **Always confirm the mode switch with the user before applying it** — it changes how every future dispatch on that project runs.
342
+
322
343
  ## GitHub auth
323
344
 
324
345
  We have multiple authed `gh` accounts (`yemi33`, `yemishin_microsoft`) covering three repo scopes (`yemi33/minions`, `yemishin_microsoft/minions`, `opg-microsoft/minions`).