@yemi33/minions 0.1.1961 → 0.1.1962

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.
@@ -8,7 +8,7 @@ const fs = require('fs');
8
8
  const path = require('path');
9
9
  const shared = require('./shared');
10
10
  const queries = require('./queries');
11
- const { safeJson, safeJsonNoRestore, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, mutateWorkItems, slugify, formatTranscriptEntry, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, ENGINE_DEFAULTS, MINIONS_DIR } = shared;
11
+ const { safeJson, safeJsonNoRestore, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, mutateWorkItems, slugify, formatTranscriptEntry, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, READ_ONLY_ROOT_TASK_TYPES, ENGINE_DEFAULTS, MINIONS_DIR } = shared;
12
12
  const routing = require('./routing');
13
13
  const http = require('http');
14
14
  const { parseCronExpr, shouldRunNow } = require('./scheduler');
@@ -388,13 +388,20 @@ function executeTaskStage(stage, stageState, run, config, pipeline = {}) {
388
388
  const projectSlug = _pipelineProjectSlug(project);
389
389
  const id = `PL-${run.runId.slice(4, 12)}-${stage.id}-${i}${projectResolution.projects.length > 1 || project ? '-' + projectSlug : ''}`;
390
390
  const wiPath = _pipelineWorkItemsPath(project);
391
+ const wiType = routing.normalizeWorkType(item.type || stage.taskType, WORK_TYPE.EXPLORE);
392
+ // W-mp8ho6w500034a58: read-only stages don't commit, so a pipeline
393
+ // branch label is meaningless to them — omit it entirely so the
394
+ // dispatcher's read-only fast-path runs without ceremony.
395
+ const wiBranch = READ_ONLY_ROOT_TASK_TYPES.has(wiType)
396
+ ? null
397
+ : `pipeline/${run.pipelineId}/${stage.id}`;
391
398
  mutateWorkItems(wiPath, workItems => {
392
399
  if (workItems.some(w => w.id === id)) { createdIds.push(id); return workItems; }
393
400
  workItems.push({
394
401
  id,
395
402
  title: item.title || stage.title,
396
403
  description: item.description || stage.description || '',
397
- type: routing.normalizeWorkType(item.type || stage.taskType, WORK_TYPE.EXPLORE),
404
+ type: wiType,
398
405
  priority: item.priority || stage.priority || 'medium',
399
406
  // Agent is a soft routing hint unless agentLock/hardAgent is set.
400
407
  ...(item.agent || stage.agent ? { agent: item.agent || stage.agent } : {}),
@@ -403,7 +410,7 @@ function executeTaskStage(stage, stageState, run, config, pipeline = {}) {
403
410
  status: WI_STATUS.PENDING,
404
411
  created: ts(),
405
412
  createdBy: 'pipeline:' + run.pipelineId,
406
- branch: `pipeline/${run.pipelineId}/${stage.id}`,
413
+ ...(wiBranch ? { branch: wiBranch } : {}),
407
414
  _pipelineRun: run.runId,
408
415
  _pipelineStage: stage.id,
409
416
  });
@@ -430,11 +437,18 @@ function executeTaskStageLegacy(stage, stageState, run, config) {
430
437
  const item = items[i % items.length];
431
438
  const id = `PL-${run.runId.slice(4, 12)}-${stage.id}-${i}`;
432
439
  if (workItems.some(w => w.id === id)) { createdIds.push(id); continue; }
440
+ const wiType = routing.normalizeWorkType(item.type || stage.taskType, WORK_TYPE.EXPLORE);
441
+ // W-mp8ho6w500034a58: read-only stages don't commit, so the branch
442
+ // label is meaningless — omit it so dispatch takes the read-only path
443
+ // without recomputing a worktree placement that will never be used.
444
+ const wiBranch = READ_ONLY_ROOT_TASK_TYPES.has(wiType)
445
+ ? null
446
+ : `pipeline/${run.pipelineId}/${stage.id}`;
433
447
  workItems.push({
434
448
  id,
435
449
  title: item.title || stage.title,
436
450
  description: item.description || stage.description || '',
437
- type: routing.normalizeWorkType(item.type || stage.taskType, WORK_TYPE.EXPLORE),
451
+ type: wiType,
438
452
  priority: item.priority || stage.priority || 'medium',
439
453
  // Agent is a soft routing hint unless agentLock/hardAgent is set.
440
454
  ...(item.agent || stage.agent ? { agent: item.agent || stage.agent } : {}),
@@ -442,7 +456,7 @@ function executeTaskStageLegacy(stage, stageState, run, config) {
442
456
  status: WI_STATUS.PENDING,
443
457
  created: ts(),
444
458
  createdBy: 'pipeline:' + run.pipelineId,
445
- branch: `pipeline/${run.pipelineId}/${stage.id}`,
459
+ ...(wiBranch ? { branch: wiBranch } : {}),
446
460
  _pipelineRun: run.runId,
447
461
  _pipelineStage: stage.id,
448
462
  });
@@ -571,7 +585,8 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
571
585
  id: wiId, title: `Convert plan to PRD: ${existingPlanFile}`,
572
586
  type: WORK_TYPE.PLAN_TO_PRD, priority: 'high', status: WI_STATUS.PENDING,
573
587
  planFile: existingPlanFile, created: ts(), createdBy: 'pipeline:' + run.pipelineId,
574
- branch: `pipeline/${run.pipelineId}/${stage.id}`, _pipelineRun: run.runId, _pipelineStage: stage.id,
588
+ // W-mp8ho6w500034a58: PLAN_TO_PRD is read-only no branch needed.
589
+ _pipelineRun: run.runId, _pipelineStage: stage.id,
575
590
  ...(project ? { project: project.name } : {}),
576
591
  });
577
592
  }
@@ -665,7 +680,7 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
665
680
  planFile: path.basename(filePath),
666
681
  created: ts(),
667
682
  createdBy: 'pipeline:' + run.pipelineId,
668
- branch: `pipeline/${run.pipelineId}/${stage.id}`,
683
+ // W-mp8ho6w500034a58: PLAN_TO_PRD is read-only — no branch needed.
669
684
  _pipelineRun: run.runId,
670
685
  _pipelineStage: stage.id,
671
686
  ...(project ? { project: project.name } : {}),
package/engine/shared.js CHANGED
@@ -2883,10 +2883,12 @@ const READ_ONLY_ROOT_TASK_TYPES = new Set(['meeting', 'ask', 'explore', 'plan-to
2883
2883
  * the drive-root preflight that fires when MINIONS_DIR sits one level
2884
2884
  * below a filesystem root (resolveProjectRootDir's collapse case).
2885
2885
  *
2886
- * NOTE: Pipeline branches (engine.js `isPipelineBranchName`) override this
2887
- * they always need a worktree even for read-only types because the worktree
2888
- * IS the pipeline's isolated workspace. The caller must detect the pipeline
2889
- * branch case and recompute worktreeRootDir via `resolveProjectRootDir`.
2886
+ * NOTE (W-mp8ho6w500034a58): Pipeline branches no longer override this.
2887
+ * Read-only pipeline stages don't commit, so a `pipeline/...` branch is a
2888
+ * meaningless label for them the dispatcher short-circuits read-only WIs
2889
+ * regardless of branch name, and `engine/pipeline.js` now omits the branch
2890
+ * field for read-only stages. Only code-mutating pipeline stages need a
2891
+ * worktree, and they take the normal code-mutating path below.
2890
2892
  *
2891
2893
  * @param {{ localPath?: string|null }|null|undefined} project
2892
2894
  * @param {string} type — work type (e.g. 'fix', 'explore', 'meeting')
package/engine.js CHANGED
@@ -108,10 +108,6 @@ const CHECKPOINT_CAP_FAIL_REASON = 'Exceeded 3 checkpoint-resumes; manual interv
108
108
  // re-aliased here for the existing call sites in this file.
109
109
  const READ_ONLY_ROOT_TASK_TYPES = shared.READ_ONLY_ROOT_TASK_TYPES;
110
110
 
111
- function isPipelineBranchName(branchName) {
112
- return typeof branchName === 'string' && branchName.startsWith('pipeline/');
113
- }
114
-
115
111
  // ─── Dispatch Management (extracted to engine/dispatch.js) ───────────────────
116
112
 
117
113
  const { mutateDispatch, addToDispatch, addToDispatchWithValidation, isRetryableFailureReason, completeDispatch,
@@ -777,8 +773,13 @@ async function spawnAgent(dispatchItem, config) {
777
773
  // (caller defaults cwd to worktreeRootDir; drive-root collapse throws
778
774
  // WORKTREE_ROOTDIR_COLLAPSED_TO_DRIVE_ROOT — same fail-fast behavior as
779
775
  // the legacy resolveProjectRootDir call this replaced).
780
- // Pipeline branches force a worktree even for read-only types — handled
781
- // immediately after the resolver call below.
776
+ // W-mp8ho6w500034a58: read-only task types (meeting/ask/explore/plan/plan-to-prd)
777
+ // never need a worktree even when carrying a pipeline branch. Pipeline branches
778
+ // on read-only stages are a no-op label; the stage doesn't commit anything, so
779
+ // the worktree had no functional purpose and was only there to absorb a drive-
780
+ // root preflight that fired against MINIONS_DIR's parent. Read-only pipeline
781
+ // stages now short-circuit alongside any other read-only WI (see the gate at
782
+ // `if (branchName && READ_ONLY_ROOT_TASK_TYPES.has(type))` below).
782
783
  const _preBranchName = meta?.branch ? sanitizeBranch(meta.branch) : null;
783
784
  let cwd, worktreeRootDir;
784
785
  try {
@@ -799,29 +800,6 @@ async function spawnAgent(dispatchItem, config) {
799
800
  }
800
801
  throw rootErr;
801
802
  }
802
- // Pipeline branches need a worktree even for read-only types (the worktree
803
- // IS the pipeline's isolated workspace). When we detect a pipeline branch
804
- // on a read-only type, recompute worktreeRootDir so the worktree creation
805
- // block has a placement parent — and so the drive-root preflight still fires.
806
- if (worktreeRootDir === null && isPipelineBranchName(_preBranchName)) {
807
- try {
808
- worktreeRootDir = shared.resolveProjectRootDir(project.localPath, MINIONS_DIR);
809
- } catch (rootErr) {
810
- if (rootErr?.code === 'WORKTREE_ROOTDIR_COLLAPSED_TO_DRIVE_ROOT' || rootErr?.code === 'WORKTREE_ROOTDIR_MISSING_BASE') {
811
- log('error', `spawnAgent: pipeline-branch rootDir resolution failed for ${id}: ${rootErr.message}`);
812
- completeDispatch(
813
- id,
814
- DISPATCH_RESULT.ERROR,
815
- rootErr.message.slice(0, 800),
816
- 'Pre-spawn worktree preflight rejected — see failure_class for the specific cause.',
817
- { failureClass: FAILURE_CLASS.WORKTREE_PREFLIGHT, agentRetryable: false },
818
- );
819
- cleanupTempAgent(agentId);
820
- return null;
821
- }
822
- throw rootErr;
823
- }
824
- }
825
803
  // Legacy local alias: downstream git ops (worktree add, prune, fetch) and
826
804
  // the `cwd === rootDir` safety warn at line ~1387 reference `rootDir`. For
827
805
  // read-only rootless tasks (no worktree, no branch) this is null — the
@@ -910,14 +888,15 @@ async function spawnAgent(dispatchItem, config) {
910
888
  };
911
889
  _phaseT.afterPrompt = Date.now();
912
890
 
913
- if (branchName && READ_ONLY_ROOT_TASK_TYPES.has(type) && !isPipelineBranchName(branchName)) {
891
+ if (branchName && READ_ONLY_ROOT_TASK_TYPES.has(type)) {
914
892
  // W-mp7havqf0007ce6b: read-only types (meeting/ask/explore/plan/plan-to-prd)
915
893
  // short-circuit BEFORE the worktree-creation block. resolveSpawnPaths returns
916
894
  // worktreeRootDir=null for read-only types, and path.resolve(null, ...) throws
917
- // ("paths[0] must be of type string. Received null"). Pipeline branches are
918
- // exempt — they always need a worktree (the worktree IS the pipeline workspace),
919
- // and the recompute at lines ~806-824 ensures worktreeRootDir is non-null
920
- // before the worktree-creation block runs for them.
895
+ // ("paths[0] must be of type string. Received null"). Pipeline branches used
896
+ // to be exempt — they don't need to be (W-mp8ho6w500034a58): read-only stages
897
+ // never commit, so a pipeline-branch label is meaningless for them and the
898
+ // forced worktree only existed to feed the drive-root preflight that this
899
+ // short-circuit now correctly avoids.
921
900
  log('info', `${type}: read-only task with branch ${branchName} — skipping worktree, running in cwd ${cwd}`);
922
901
  branchName = null;
923
902
  worktreePath = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1961",
3
+ "version": "0.1.1962",
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"