@yemi33/minions 0.1.2000 → 0.1.2002

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.
@@ -598,7 +598,28 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
598
598
  });
599
599
  log('warn', `Demoted WI ${item.meta.item.id} from DONE → FAILED${classSuffix} — sidecar acceptance gate rejected after completion-report claimed success`);
600
600
  }
601
- } catch (e) { log('warn', `force-demote on ${failureClass}: ${e.message}`); }
601
+ } catch (e) {
602
+ // P-bf06-force-demote-observability — write failures during
603
+ // sidecar-rejection force-demote used to log at warn level only,
604
+ // so they were invisible to dashboards / inbox alerting. Surface
605
+ // them at error level with the full stack AND a synthetic
606
+ // agent-failure inbox alert so the failed write is debuggable.
607
+ // NOTE: do NOT call lifecycle().syncPrdItemStatus from this catch
608
+ // — the success-path PRD sync at line 688 owns that contract and
609
+ // adding it here would double-write PRD status under conflicting
610
+ // assumptions.
611
+ log('error', `force-demote on ${failureClass} failed: ${e.message}\n${e.stack}`, { stack: e.stack, failureClass });
612
+ try {
613
+ // Pass a shimmed dispatch id so writeToInbox's slug-based dedupe
614
+ // (see shared.writeToInbox prefix collision check) doesn't drop
615
+ // this alert against the prior agent-failure note that the
616
+ // ERROR branch already wrote at line 550.
617
+ const demoteItem = { ...item, id: `${item.id}-demote-write` };
618
+ writeFailedAgentReport(demoteItem, `force-demote write failed: ${e.message}`, e.stack || '', 'dispatch-demote-write-failed');
619
+ } catch (reportErr) {
620
+ log('error', `force-demote write failed-agent-report itself failed: ${reportErr.message}`);
621
+ }
622
+ }
602
623
  } else {
603
624
  log('info', `Dispatch error for ${item.meta.item.id} ignored — work item is already completed`);
604
625
  }
package/engine/github.js CHANGED
@@ -869,6 +869,13 @@ async function pollPrStatus(config) {
869
869
  ].filter(Boolean).join('\n') || buildFailReason, 80);
870
870
  } else if (allDone && allPassed) {
871
871
  buildStatus = 'passing';
872
+ } else if (allDone) {
873
+ // Terminal-but-not-passing conclusions (cancelled, action_required,
874
+ // stale, etc.) — map to 'none' rather than 'failing'. These are not
875
+ // actionable build failures, so we deliberately do not block merge or
876
+ // dispatch build-fix agents. Previously fell through to 'running'
877
+ // forever (P-bf02-github-cancelled-stuck).
878
+ buildStatus = 'none';
872
879
  } else {
873
880
  buildStatus = 'running';
874
881
  }
@@ -3465,6 +3465,9 @@ function handleDecompositionResult(stdout, meta, config, runtimeName) {
3465
3465
  // Mark parent as decomposed
3466
3466
  p.status = WI_STATUS.DECOMPOSED;
3467
3467
  p._decomposed = true;
3468
+ // P-bf04: timestamp used by engine.js areDependenciesMet() to classify
3469
+ // zero-children failures as recent vs stale in logs (both cascade 'failed').
3470
+ p._decomposedAt = ts();
3468
3471
  delete p._decomposing;
3469
3472
  // Sync to PRD so dashboard shows decomposed status
3470
3473
  if (p.sourcePlan) syncPrdItemStatus(p.id, WI_STATUS.DECOMPOSED, p.sourcePlan);
@@ -756,15 +756,23 @@ function executeMergePrsStage(stage, stageState, run, config) {
756
756
  function executeScheduleStage(stage, stageState, config) {
757
757
  // Create/update schedules in config
758
758
  const schedules = stage.schedules || [{ id: stage.id + '-sched', cron: stage.cron, title: stage.title, type: routing.normalizeWorkType(stage.taskType, WORK_TYPE.IMPLEMENT) }];
759
- // Write to config via shared
760
- for (const sched of schedules) {
761
- const existing = (config.schedules || []).find(s => s.id === sched.id);
762
- if (!existing) {
763
- config.schedules = config.schedules || [];
764
- config.schedules.push({ ...sched, enabled: true });
759
+ // P-bf05-config-write-race atomic locked write so concurrent
760
+ // addProject/removeProject/dashboard-settings writers can't clobber the
761
+ // schedules we just appended. The prior `safeWrite(CONFIG_PATH, config)`
762
+ // last-write-wins corrupted config.json on bootstrap (no `.backup`
763
+ // self-heal). Mutate the fresh on-disk snapshot inside the lock so we
764
+ // don't drop concurrent edits.
765
+ mutateJsonFileLocked(CONFIG_PATH, (cfg) => {
766
+ if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) return cfg;
767
+ for (const sched of schedules) {
768
+ const existing = (cfg.schedules || []).find(s => s.id === sched.id);
769
+ if (!existing) {
770
+ cfg.schedules = cfg.schedules || [];
771
+ cfg.schedules.push({ ...sched, enabled: true });
772
+ }
765
773
  }
766
- }
767
- safeWrite(CONFIG_PATH, config);
774
+ return cfg;
775
+ });
768
776
  return { status: PIPELINE_STATUS.COMPLETED, completedAt: ts() };
769
777
  }
770
778
 
@@ -1151,7 +1159,7 @@ module.exports = {
1151
1159
  getPipelineRuns, getActiveRun, startRun, updateRunStage, completeRun,
1152
1160
  discoverPipelineWork,
1153
1161
  evaluateCondition, // exported for testing
1154
- executeTaskStage, executePlanStage, isStageComplete, resolveTemplate, // exported for testing
1162
+ executeTaskStage, executePlanStage, executeScheduleStage, isStageComplete, resolveTemplate, // exported for testing
1155
1163
  _resolvePipelineProjects, // exported for testing
1156
1164
  _findMeetingsInRun, _findExistingPlanForMeeting, _findExistingPrdForPlan, // exported for testing
1157
1165
  };
@@ -467,18 +467,74 @@ function buildQaValidateContextBlock({ runId, runbook, target, artifactsDir }) {
467
467
 
468
468
  // ─── Playbook Renderer ──────────────────────────────────────────────────────
469
469
 
470
+ /**
471
+ * Parse YAML-ish frontmatter at the top of a playbook file.
472
+ * Returns { meta: {key:value}, body: string }. Recognized values:
473
+ * true|yes|1 → boolean true
474
+ * false|no|0 → boolean false
475
+ * everything else → raw string
476
+ */
477
+ function _parsePlaybookFrontmatter(content) {
478
+ const m = String(content || '').match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
479
+ if (!m) return { meta: {}, body: content || '' };
480
+ const meta = {};
481
+ for (const line of m[1].split(/\r?\n/)) {
482
+ const lm = line.match(/^([\w-]+):\s*(.*)$/);
483
+ if (!lm) continue;
484
+ const key = lm[1];
485
+ const raw = lm[2].trim();
486
+ if (/^(true|yes|1)$/i.test(raw)) meta[key] = true;
487
+ else if (/^(false|no|0)$/i.test(raw)) meta[key] = false;
488
+ else meta[key] = raw;
489
+ }
490
+ return { meta, body: m[2].replace(/^\r?\n+/, '') };
491
+ }
492
+
470
493
  function renderPlaybook(type, vars) {
471
494
  const projectName = vars.project_name || null;
472
495
  const pbPath = resolvePlaybookPath(projectName, type);
473
- let content;
474
- try { content = fs.readFileSync(pbPath, 'utf8'); } catch {
496
+ let rawContent;
497
+ try { rawContent = fs.readFileSync(pbPath, 'utf8'); } catch {
475
498
  log('warn', `Playbook not found: ${type}`);
476
499
  return null;
477
500
  }
478
501
 
479
- // Inject shared rules (apply to all playbooks)
502
+ // P-bf07 parse frontmatter and enforce requiresProjectContext before any
503
+ // substitution work. Hard-fail dispatch when the playbook requires a
504
+ // project but none is configured (i.e. fresh install) so the agent prompt
505
+ // can never expand {{project_name}} / {{repo_name}} / {{ado_org}} /
506
+ // {{github_org}} to empty strings or the bogus 'Unknown' defaults.
507
+ const { meta: playbookMeta, body: bodyAfterFm } = _parsePlaybookFrontmatter(rawContent);
508
+ let content = bodyAfterFm;
509
+
510
+ const configEarly = getConfig();
511
+ const projectsEarly = getProjects(configEarly);
512
+ const matchedProject = (vars.repo_id && projectsEarly.find(p => p.repositoryId === vars.repo_id))
513
+ || (vars.repo_name && projectsEarly.find(p => p.repoName === vars.repo_name))
514
+ || projectsEarly[0]
515
+ || null;
516
+ // Callers (PR dispatches, fan-out, unit tests) may supply project context
517
+ // in vars even when on-disk config has zero projects. Treat that as
518
+ // "project context provided" — the only true bug is when buildBaseVars
519
+ // fell back to the placeholder defaults ('Unknown Project' / 'Unknown')
520
+ // because no project object was available.
521
+ const callerSuppliedProjectContext = !!(
522
+ vars.repo_id
523
+ || (vars.repo_name && vars.repo_name !== 'Unknown')
524
+ || (vars.project_name && vars.project_name !== 'Unknown Project')
525
+ );
526
+ if (!matchedProject && !callerSuppliedProjectContext && playbookMeta.requiresProjectContext === true) {
527
+ const err = new Error(`Playbook ${type} requires a project but none is configured. Add a project via \`minions project add\`.`);
528
+ err.code = 'PLAYBOOK_REQUIRES_PROJECT';
529
+ err.playbook = type;
530
+ throw err;
531
+ }
532
+
533
+ // Inject shared rules (apply to all playbooks). Strip any frontmatter so the
534
+ // P-bf07 audit metadata doesn't leak into the rendered prompt.
480
535
  try {
481
- const sharedRules = fs.readFileSync(path.join(PLAYBOOKS_DIR, 'shared-rules.md'), 'utf8');
536
+ const sharedRulesRaw = fs.readFileSync(path.join(PLAYBOOKS_DIR, 'shared-rules.md'), 'utf8');
537
+ const { body: sharedRules } = _parsePlaybookFrontmatter(sharedRulesRaw);
482
538
  if (sharedRules) content += '\n\n' + sharedRules;
483
539
  } catch { /* optional — shared rules file may not exist */ }
484
540
 
@@ -625,12 +681,13 @@ function renderPlaybook(type, vars) {
625
681
  content += `- **SOURCE REFERENCES for every finding** — file paths with line numbers, PR URLs, API endpoints, config keys. Format: \`(source: path/to/file.ts:42)\` or \`(source: PR-12345)\`. Without references, findings cannot be verified.\n\n`;
626
682
 
627
683
  // Inject project-level variables from config
628
- const config = getConfig();
629
- const projects = getProjects(config);
630
- // Find the specific project being dispatched (match by repo_id or repo_name from vars)
631
- const dispatchProject = (vars.repo_id && projects.find(p => p.repositoryId === vars.repo_id))
632
- || (vars.repo_name && projects.find(p => p.repoName === vars.repo_name))
633
- || projects[0] || {};
684
+ const config = configEarly;
685
+ const projects = projectsEarly;
686
+ // Find the specific project being dispatched (match by repo_id or repo_name from vars).
687
+ // P-bf07 when no project resolves we keep the legacy `{}` fallback ONLY for
688
+ // playbooks that opted out (requiresProjectContext: false). Required-context
689
+ // playbooks already short-circuited above with a structured error.
690
+ const dispatchProject = matchedProject || {};
634
691
  const renderProject = {
635
692
  ...dispatchProject,
636
693
  adoOrg: vars.ado_org || vars.adoOrg || dispatchProject.adoOrg,
@@ -997,6 +1054,7 @@ module.exports = {
997
1054
  validatePlaybookVars,
998
1055
  PLAYBOOK_REQUIRED_VARS,
999
1056
  PLAYBOOK_OPTIONAL_VARS,
1057
+ _parsePlaybookFrontmatter,
1000
1058
  buildSystemPrompt,
1001
1059
  buildAgentContext,
1002
1060
  selectPlaybook,
@@ -203,16 +203,13 @@ function removeProject(target, options = {}) {
203
203
  } catch (e) { summary.warnings.push('worktree cleanup: ' + e.message); }
204
204
  }
205
205
 
206
- // 4. Disable schedules whose `project` field targets this project
207
- // specifically. Don't touch schedules with project='any' or unset.
208
- if (Array.isArray(config.schedules)) {
209
- for (const s of config.schedules) {
210
- if (_projectRefMatches(s.project, project, projects) && s.enabled !== false) {
211
- s.enabled = false;
212
- summary.disabledSchedules++;
213
- }
214
- }
215
- }
206
+ // 4. Schedule disables happen atomically with the project unlink in
207
+ // section 7's mutateJsonFileLocked callback so concurrent writers
208
+ // (addProject, dashboard settings update, pipeline schedule stage)
209
+ // don't clobber each other. The previous pattern mutated the unlocked
210
+ // in-memory `config` snapshot here and relied on the section 7 write
211
+ // to persist it — that lost concurrent updates that landed between
212
+ // the initial read and the write.
216
213
 
217
214
  // 5. Archive plans + PRDs targeting this project so they don't keep
218
215
  // showing in the dashboard after removal. Three signals:
@@ -277,10 +274,31 @@ function removeProject(target, options = {}) {
277
274
  }
278
275
  } catch { /* pipelines optional */ }
279
276
 
280
- // 7. Remove from config.json (and persist any schedule disables)
281
- config.projects = (config.projects || []).filter(p => !_sameProjectName(p?.name, project.name));
282
- try { fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); }
283
- catch (e) { return { ...summary, error: 'Failed to write config: ' + e.message }; }
277
+ // 7. Remove from config.json and persist schedule disables atomically
278
+ // under the config.json file lock. P-bf05-config-write-race
279
+ // the prior `fs.writeFileSync` (and section 4's unlocked in-memory
280
+ // mutation) raced with addProject, dashboard settings updates, and
281
+ // pipeline schedule-stage writers, corrupting config.json on fresh
282
+ // bootstrap (which has no `.backup` self-heal). Both mutations now
283
+ // run inside the same mutateJsonFileLocked callback against the
284
+ // fresh on-disk snapshot.
285
+ let writeError = null;
286
+ try {
287
+ shared.mutateJsonFileLocked(configPath, (cfg) => {
288
+ if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) return cfg;
289
+ cfg.projects = (cfg.projects || []).filter(p => !_sameProjectName(p?.name, project.name));
290
+ if (Array.isArray(cfg.schedules)) {
291
+ for (const s of cfg.schedules) {
292
+ if (_projectRefMatches(s.project, project, projects) && s.enabled !== false) {
293
+ s.enabled = false;
294
+ summary.disabledSchedules++;
295
+ }
296
+ }
297
+ }
298
+ return cfg;
299
+ });
300
+ } catch (e) { writeError = e; }
301
+ if (writeError) return { ...summary, error: 'Failed to write config: ' + writeError.message };
284
302
 
285
303
  // 8. Move (or purge) projects/<name>/ — preserves PR/work-item history by
286
304
  // default so a re-add can pick up where it left off.
package/engine/shared.js CHANGED
@@ -1678,6 +1678,13 @@ const ENGINE_DEFAULTS = {
1678
1678
  shutdownTimeout: 300000, // 5min — max wait for active agents during graceful shutdown
1679
1679
  allowTempAgents: false, // opt-in: spawn ephemeral agents when all permanent agents are busy
1680
1680
  autoDecompose: true, // auto-decompose implement:large items into sub-tasks
1681
+ // P-bf04-decompose-zero-children: when a DECOMPOSED parent has zero children
1682
+ // and no decompose dispatch is in flight, areDependenciesMet treats that as
1683
+ // a silent decompose failure and cascades `'failed'` to dependents. This
1684
+ // window splits the failure log into "recent" vs "stale" categories — both
1685
+ // return `'failed'`; the threshold is for monitoring/log classification only,
1686
+ // not behavior. See engine.js areDependenciesMet() DECOMPOSED branch.
1687
+ decomposeZeroChildrenStaleMinutes: 30,
1681
1688
  autoApprovePlans: false, // auto-approve PRDs without waiting for human approval
1682
1689
  autoArchive: false, // opt-in: auto-archive plans after verify completes (false = mark ready, user archives manually)
1683
1690
  autoFixConflicts: true, // auto-dispatch fix agents when a PR has merge conflicts
package/engine.js CHANGED
@@ -3061,7 +3061,43 @@ function areDependenciesMet(item, config) {
3061
3061
  const children = allWorkItems.filter(w => w.parent_id === depId);
3062
3062
  if (children.length > 0 && children.every(c => PRD_MET_STATUSES.has(c.status))) continue; // all children done
3063
3063
  if (children.length > 0) return false; // children still in progress
3064
- continue; // no children found — treat as met (decomposition may be in flight)
3064
+
3065
+ // P-bf04-decompose-zero-children: parent is DECOMPOSED but no children
3066
+ // exist. Previously this silently `continue`d (treat as met), so a crashed
3067
+ // decompose (parser error, empty output, race) cascaded into dependents
3068
+ // running against a parent whose work never happened. New three-branch logic:
3069
+ //
3070
+ // (1) IN-FLIGHT HEDGE — a decompose dispatch for this parent is active
3071
+ // or pending → return false (caller waits, no cascade)
3072
+ // (2) RECENT zero-children — decomposedAt within the last N min
3073
+ // (3) STALE zero-children — decomposedAt older than N min (or unknown)
3074
+ //
3075
+ // (2) and (3) both return 'failed'; the split exists for log/monitor
3076
+ // classification (and test scaffolding), not for caller behavior.
3077
+ // PLAN_TO_PRD dispatches are intentionally NOT consulted here — they
3078
+ // materialize plan items into work items, they do not decompose an
3079
+ // existing DECOMPOSED parent into children.
3080
+ try {
3081
+ const dispatch = queries.getDispatch();
3082
+ const inFlight = [...(dispatch.pending || []), ...(dispatch.active || [])]
3083
+ .some(d => d?.type === WORK_TYPE.DECOMPOSE && d?.meta?.item?.id === depId);
3084
+ if (inFlight || depItem._decomposing === true) {
3085
+ log('warn', `Dep ${depId} (DECOMPOSED, zero children) — decompose dispatch still in flight; treating as unmet`);
3086
+ return false;
3087
+ }
3088
+ } catch (e) { log('warn', `areDependenciesMet bf04 in-flight check failed: ${e.message}`); }
3089
+
3090
+ const staleMin = ENGINE_DEFAULTS.decomposeZeroChildrenStaleMinutes;
3091
+ const parsedDecomposedAt = depItem._decomposedAt ? Date.parse(depItem._decomposedAt) : NaN;
3092
+ const ageMs = Number.isFinite(parsedDecomposedAt) ? (Date.now() - parsedDecomposedAt) : null;
3093
+ const isRecent = ageMs !== null && ageMs < staleMin * 60000;
3094
+ if (isRecent) {
3095
+ log('warn', `Dep ${depId} (DECOMPOSED) produced zero children RECENTLY (${Math.round(ageMs / 60000)}m ago) — cascading dependency_failed to ${item.id}`);
3096
+ } else {
3097
+ const ageLabel = ageMs == null ? 'no _decomposedAt' : `${Math.round(ageMs / 60000)}m ago`;
3098
+ log('warn', `Dep ${depId} (DECOMPOSED) produced zero children STALE (${ageLabel}) — cascading dependency_failed to ${item.id}`);
3099
+ }
3100
+ return 'failed';
3065
3101
  }
3066
3102
  if (!PRD_MET_STATUSES.has(depItem.status)) return false; // Pending, dispatched, or retrying — wait
3067
3103
  }
@@ -4077,7 +4113,7 @@ async function discoverFromPrs(config, project) {
4077
4113
  if (target && target.reviewStatus !== 'approved') target.reviewStatus = liveStatus;
4078
4114
  return data;
4079
4115
  });
4080
- } catch {}
4116
+ } catch (e) { log('warn', `persist live vote-check for ${pr.id}: ${e.message}`); }
4081
4117
  continue;
4082
4118
  }
4083
4119
  } catch (e) { log('warn', `Pre-dispatch vote check for ${pr.id}: ${e.message} — skipping dispatch`); continue; }
@@ -4237,7 +4273,7 @@ async function discoverFromPrs(config, project) {
4237
4273
  if (target && target.reviewStatus !== 'approved') target.reviewStatus = liveStatus;
4238
4274
  return data;
4239
4275
  });
4240
- } catch {}
4276
+ } catch (e) { log('warn', `persist live re-review vote-check for ${pr.id}: ${e.message}`); }
4241
4277
  continue;
4242
4278
  }
4243
4279
  } catch (e) { log('warn', `Pre-dispatch vote check for ${pr.id}: ${e.message} — skipping dispatch`); continue; }
@@ -4353,7 +4389,7 @@ async function discoverFromPrs(config, project) {
4353
4389
  target._buildStatusStale = true;
4354
4390
  if (live.buildStatusDetail) target._buildStatusDetail = live.buildStatusDetail;
4355
4391
  });
4356
- } catch {}
4392
+ } catch (e) { log('warn', `persist live build-stale-marker for ${pr.id}: ${e.message}`); }
4357
4393
  }
4358
4394
  if (live && live.buildStatus && live.buildStatus !== 'failing') {
4359
4395
  log('info', `Pre-dispatch build check: ${pr.id} build is ${live.buildStatus} (cached was failing) — skipping build-fix`);
@@ -4374,7 +4410,7 @@ async function discoverFromPrs(config, project) {
4374
4410
  }
4375
4411
  }
4376
4412
  });
4377
- } catch {}
4413
+ } catch (e) { log('warn', `persist live build-status for ${pr.id}: ${e.message}`); }
4378
4414
  continue;
4379
4415
  }
4380
4416
  } catch (e) { log('warn', `Pre-dispatch build check for ${pr.id}: ${e.message} — skipping dispatch`); continue; }
@@ -4452,7 +4488,7 @@ async function discoverFromPrs(config, project) {
4452
4488
  delete target._mergeConflict;
4453
4489
  delete target._conflictFixedAt;
4454
4490
  });
4455
- } catch {}
4491
+ } catch (e) { log('warn', `persist live conflict-clear for ${pr.id}: ${e.message}`); }
4456
4492
  liveSkip = true;
4457
4493
  }
4458
4494
  } catch (e) { log('warn', `Pre-dispatch conflict check for ${pr.id}: ${e.message} — skipping dispatch`); liveSkip = true; }
@@ -6626,6 +6662,7 @@ module.exports = {
6626
6662
 
6627
6663
  // Shared helpers (used by lifecycle.js and tests)
6628
6664
  reconcileItemsWithPrs, detectDependencyCycles,
6665
+ areDependenciesMet, // exported for testing (P-bf04-decompose-zero-children)
6629
6666
  parseConflictFiles, pruneAncestorDeps, preflightMergeSimulation, // exported for testing
6630
6667
  buildDepConflictFixItem, // exported for testing (W-mpcwojgr000a0244)
6631
6668
  isWorktreeRetryableError, removeStaleIndexLock, syncReusedWorktree, assertCleanSharedWorktree, // exported for testing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2000",
3
+ "version": "0.1.2002",
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"
package/playbooks/ask.md CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Ask Playbook
2
6
 
3
7
  > Agent: {{agent_name}} | Question from user | ID: {{task_id}}
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Build & Test: PR {{pr_id}}
2
6
 
3
7
  > Agent: {{agent_name}} ({{agent_role}}) | Project: {{project_name}}
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Task Decomposition
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
package/playbooks/docs.md CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Docs Playbook
2
6
 
3
7
  > Agent: {{agent_name}} ({{agent_role}}) | Task: {{item_name}} | ID: {{item_id}}
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Explore Playbook
2
6
 
3
7
  > Agent: {{agent_name}} | Task: {{task_description}} | ID: {{task_id}}
package/playbooks/fix.md CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Fix Review Issues
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Implement (Shared Branch)
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Implement
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: false
3
+ ---
4
+
1
5
  # Meeting: Conclusion
2
6
 
3
7
  You are {{agent_name}} ({{agent_role}}), synthesizing the team meeting results.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: false
3
+ ---
4
+
1
5
  # Meeting: Debate Round
2
6
 
3
7
  You are {{agent_name}} ({{agent_role}}) in Round 2 of a team meeting.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: false
3
+ ---
4
+
1
5
  # Meeting: Investigation Round
2
6
 
3
7
  You are {{agent_name}} ({{agent_role}}) participating in a team meeting.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Plan → PRD
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
package/playbooks/plan.md CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Feature Plan
2
6
 
3
7
  > Agent: {{agent_name}} | Task: {{task_description}} | ID: {{task_id}}
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: QA Validate
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Review
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Playbook: Setup
2
6
 
3
7
  You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: false
3
+ ---
4
+
1
5
  ## Operating Principle
2
6
 
3
7
  Treat a Minions assignment like the user typed the same task directly into a capable CLI agent. Optimize for the requested outcome and use the repo's own tools, conventions, and acceptance criteria.
package/playbooks/test.md CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Test / Build / Run: {{item_name}}
2
6
 
3
7
  > Agent: {{agent_name}} ({{agent_role}}) | ID: {{item_id}} | Priority: {{item_priority}}
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Plan Verification: {{item_name}}
2
6
 
3
7
  > Agent: {{agent_name}} ({{agent_role}}) | Team root: {{team_root}}
@@ -1,3 +1,7 @@
1
+ ---
2
+ requiresProjectContext: true
3
+ ---
4
+
1
5
  # Work Item: {{item_name}}
2
6
 
3
7
  > Agent: {{agent_name}} ({{agent_role}}) | ID: {{item_id}} | Priority: {{item_priority}} | Type: {{work_type}}