@yemi33/minions 0.1.2301 → 0.1.2303

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/dashboard.js CHANGED
@@ -4263,12 +4263,25 @@ function _resetPreambleCache() {
4263
4263
  _preambleCacheTs = 0;
4264
4264
  }
4265
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
+
4266
4279
  function buildCCStatePreamble() {
4267
4280
  const now = Date.now();
4268
4281
  if (_preambleCache && now - _preambleCacheTs < PREAMBLE_TTL) return _preambleCache;
4269
4282
  // Lightweight snapshot — just enough to orient. Use tools for details.
4270
4283
  const agents = getAgents().map(a => `- ${a.name} (${a.id}): ${a.status}${a.currentTask ? ' — ' + a.currentTask.slice(0, 60) : ''}`).join('\n');
4271
- 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');
4272
4285
 
4273
4286
  const dq = getDispatchQueue();
4274
4287
  const active = (dq.active || []).map(d => `- ${d.agentName || d.agent}: ${(d.task || '').slice(0, 50)}`).join('\n') || '(none)';
@@ -4340,7 +4353,7 @@ function buildCCStateRefresh() {
4340
4353
  // Agent roster carries the current task too — without it a resumed session
4341
4354
  // sees only that an agent is "busy", never *what* it is busy with.
4342
4355
  const agents = getAgents().map(a => `- ${a.name}: ${a.status}${a.currentTask ? ' — ' + a.currentTask.slice(0, 50) : ''}`).join('\n');
4343
- 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');
4344
4357
 
4345
4358
  // MCP servers — just names + source for orientation
4346
4359
  let mcpLine = '(none discovered)';
@@ -11337,6 +11350,31 @@ What would you like to discuss or change? When you're happy, say "approve" and I
11337
11350
  }
11338
11351
  // Drop the legacy field so a migrated project never carries both.
11339
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
+ }
11340
11378
  }
11341
11379
  }
11342
11380
  }
@@ -14301,7 +14339,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
14301
14339
 
14302
14340
  // Settings
14303
14341
  { method: 'GET', path: '/api/settings', desc: 'Return current engine + claude + routing config', handler: handleSettingsRead },
14304
- { 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 },
14305
14343
  { method: 'POST', path: '/api/settings/routing', desc: 'Update routing.md', params: 'content', handler: handleSettingsRouting },
14306
14344
  { method: 'POST', path: '/api/settings/reset', desc: 'Reset engine + claude + agent settings to defaults', handler: handleSettingsReset },
14307
14345
 
@@ -14512,6 +14550,7 @@ module.exports = {
14512
14550
  _resolveScheduleProjectValue: resolveScheduleProjectValue,
14513
14551
  _collectArchivedWorkItems: collectArchivedWorkItems,
14514
14552
  buildCCStatePreamble,
14553
+ _projectCheckoutModeLabel,
14515
14554
  buildCCStateRefresh,
14516
14555
  _resetRefreshCache,
14517
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/engine.js CHANGED
@@ -4168,6 +4168,12 @@ async function spawnAgent(dispatchItem, config) {
4168
4168
  // re-attached on next start via PID file + live-output.log. We do NOT call
4169
4169
  // proc.unref(): the engine still tracks exit while it's alive; detached
4170
4170
  // only kicks in when the engine itself goes away.
4171
+ // Expose the engine-resolved working directory so playbooks (setup, qa-*,
4172
+ // shared-rules) can read $MINIONS_AGENT_CWD as the authoritative worktree /
4173
+ // live-checkout root instead of guessing from pwd. Mirrors the spawn `cwd`
4174
+ // exactly — the agent's process already starts here, but cd-ing around or
4175
+ // sub-shells can drift, so the env var is the stable anchor.
4176
+ childEnv.MINIONS_AGENT_CWD = cwd;
4171
4177
  proc = runFile(process.execPath, spawnArgs, {
4172
4178
  cwd,
4173
4179
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -4499,6 +4505,9 @@ async function spawnAgent(dispatchItem, config) {
4499
4505
  || dispatchItem.meta?.keep_processes_skip_workdir_check) {
4500
4506
  childEnv.MINIONS_KEEP_PROCESSES_SKIP_WORKDIR_CHECK = '1';
4501
4507
  }
4508
+ // Re-expose the engine-resolved cwd on steering resume (matches the
4509
+ // initial spawn path) so $MINIONS_AGENT_CWD stays valid across resumes.
4510
+ childEnv.MINIONS_AGENT_CWD = cwd;
4502
4511
  let resumeProc;
4503
4512
  try {
4504
4513
  // detached so the resumed steering session also survives engine death (matches initial spawn)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2301",
3
+ "version": "0.1.2303",
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"
@@ -83,6 +83,8 @@ This task is part of a **shared-branch plan** — every plan item shares the bra
83
83
 
84
84
  **Context compaction:** Your context window may be compacted mid-task by Claude's infrastructure. If you notice your earlier conversation history appears truncated or summarized, this is normal and expected. Do not interpret compaction as a signal to stop early or wrap up. Continue working toward your task objective — all relevant instructions and state remain available.
85
85
 
86
+ **Working directory (`$MINIONS_AGENT_CWD`).** The engine spawns you with your process working directory already set to the correct location for this task — the pre-created worktree for code-mutating work, or the project's live checkout for in-place/validation work. That same path is also exported as the `MINIONS_AGENT_CWD` environment variable, which is the **authoritative** anchor: prefer it over `pwd`/`Get-Location`, because sub-shells, `cd`, or tool calls can drift your shell elsewhere. If you ever need to be sure you're in the right tree (before `git add`/`commit`/`push`, builds, or path-sensitive commands), run `cd "$MINIONS_AGENT_CWD"` (PowerShell: `Set-Location $env:MINIONS_AGENT_CWD`) first. Do **not** `cd` into a sibling worktree, the operator's main checkout, or `MINIONS_DIR` to do task work.
87
+
86
88
  - Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
87
89
  - Do NOT remove worktrees — the engine handles cleanup automatically.
88
90
  - Do NOT checkout branches in the main working tree — use worktrees or `git diff`/`git show`.
@@ -185,7 +185,7 @@ Link an external PR for tracking:
185
185
  curl -s -X POST http://localhost:{{dashboard_port}}/api/pull-requests/link \
186
186
  -H 'Content-Type: application/json' \
187
187
  -H 'X-CC-Turn-Id: {{cc_turn_id}}' \
188
- -d '{"url":"https://...","title":"...","autoObserve":true}'
188
+ -d '{"url":"https://...","title":"...","contextOnly":true}'
189
189
  ```
190
190
 
191
191
  Read-only inline answers (no header needed):
@@ -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`).