@yemi33/minions 0.1.2271 → 0.1.2272

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.
@@ -959,9 +959,45 @@ function renderPlaybook(type, vars) {
959
959
  content += inertAppendices.join('');
960
960
  }
961
961
 
962
+ _checkPlaybookSections(content, `playbook "${type}"`);
963
+
962
964
  return content;
963
965
  }
964
966
 
967
+ // ─── Playbook Section Validator ──────────────────────────────────────────────
968
+
969
+ // Required structural section patterns — warn (do not throw) when absent.
970
+ // Pluralisation: /^## Tools?\b/m matches both '## Tool' and '## Tools'.
971
+ const _REQUIRED_PROMPT_SECTIONS = [
972
+ { pattern: /^## Your Task\b/m, label: '## Your Task' },
973
+ { pattern: /^## Tools?\b/m, label: '## Tools' },
974
+ { pattern: /^## Constraints\b/m, label: '## Constraints' },
975
+ ];
976
+
977
+ /**
978
+ * Validate that a rendered prompt string contains the required structural
979
+ * section headers. Logs a 'warn' for each missing section and returns an
980
+ * array of warning strings (empty when all sections are present).
981
+ *
982
+ * @param {string} content - The rendered prompt string to check.
983
+ * @param {string} [context] - Optional label for the log message, e.g.
984
+ * 'playbook "implement"'. Prepended as '<context>: ' when provided.
985
+ * @returns {string[]} warnings - One entry per missing section.
986
+ */
987
+ function _checkPlaybookSections(content, context = '') {
988
+ if (typeof content !== 'string' || !content) return [];
989
+ const prefix = context ? `${context}: ` : '';
990
+ const warnings = [];
991
+ for (const { pattern, label } of _REQUIRED_PROMPT_SECTIONS) {
992
+ if (!pattern.test(content)) {
993
+ const msg = `${prefix}rendered prompt is missing required section "${label}"`;
994
+ warnings.push(msg);
995
+ log('warn', msg);
996
+ }
997
+ }
998
+ return warnings;
999
+ }
1000
+
965
1001
  // ─── System Prompt Builder ──────────────────────────────────────────────────
966
1002
 
967
1003
  // Lean system prompt: agent identity + rules only (~2-4KB, never grows)
@@ -997,7 +1033,11 @@ function buildSystemPrompt(agentId, config, project) {
997
1033
  prompt += `4. Write learnings to the path specified in the task prompt (format: \`notes/inbox/{agent}-{work-item-id}-{date}-{time}.md\`)\n`;
998
1034
  prompt += `5. Agent status is managed by the engine via dispatch.json — agents do not need to track their own status\n\n`;
999
1035
 
1000
- return prompt;
1036
+ // The lean system prompt (identity + rules) does not contain task-dispatch
1037
+ // sections (## Your Task, ## Tools, ## Constraints) by design — those live
1038
+ // in the rendered playbook. Return an empty warnings array so callers can
1039
+ // destructure { prompt, warnings } uniformly.
1040
+ return { prompt, warnings: [] };
1001
1041
  }
1002
1042
 
1003
1043
  // E2.b (W-mp7goxe4000p75f7): build the `## Existing Skills (do not duplicate)`
@@ -1277,6 +1317,7 @@ module.exports = {
1277
1317
  PLAYBOOK_REQUIRED_VARS,
1278
1318
  PLAYBOOK_OPTIONAL_VARS,
1279
1319
  _parsePlaybookFrontmatter,
1320
+ _checkPlaybookSections,
1280
1321
  buildSystemPrompt,
1281
1322
  buildAgentContext,
1282
1323
  selectPlaybook,
package/engine/shared.js CHANGED
@@ -5830,7 +5830,9 @@ function resolveSpawnPaths(project, type, minionsDir, options) {
5830
5830
  // single decision point regardless of task type — read-only tasks in
5831
5831
  // live mode still report liveMode:true so downstream callers don't have
5832
5832
  // to re-resolve the project's checkout mode to know they're running in-place.
5833
- if (isLiveCheckoutProject(project)) {
5833
+ // Pass `type` so hybrid projects (liveValidation) route coding WIs to
5834
+ // worktrees and keep validation WIs in live checkout.
5835
+ if (resolveCheckoutMode(project, type) === CHECKOUT_MODES.LIVE) {
5834
5836
  if (!project.localPath) {
5835
5837
  const err = new Error(
5836
5838
  'live-checkout mode requires project.localPath (checkoutMode === "live" but localPath is missing/falsy).'
package/engine.js CHANGED
@@ -2137,7 +2137,7 @@ async function spawnAgent(dispatchItem, config) {
2137
2137
 
2138
2138
  // Build the initial prompt before worktree setup, then refresh shared-branch
2139
2139
  // work-item prompts after setup because reused worktrees can live at arbitrary paths.
2140
- const systemPrompt = buildSystemPrompt(agentId, config, project);
2140
+ const { prompt: systemPrompt } = buildSystemPrompt(agentId, config, project);
2141
2141
  const agentContext = buildAgentContext(agentId, config, project);
2142
2142
  const pendingSteering = steering.buildPendingSteeringPrompt(agentId, { currentDispatchId: id });
2143
2143
  const completionReportPath = shared.dispatchCompletionReportPath(id);
@@ -9850,7 +9850,7 @@ async function tickInner() {
9850
9850
  const projName = d.project || d.meta?.project?.name || null;
9851
9851
  if (!projName) continue;
9852
9852
  const projCfg = shared.findProjectByName(shared.getProjects(config), projName);
9853
- if (shared.isLiveCheckoutProject(projCfg)) {
9853
+ if (shared.resolveCheckoutMode(projCfg, d.type) === 'live') {
9854
9854
  liveProjectsInUse.add(projName);
9855
9855
  }
9856
9856
  }
@@ -10004,15 +10004,20 @@ async function tickInner() {
10004
10004
  // each other in the operator's localPath, so cap at 1 per project.
10005
10005
  // The branch-mutex check above is the more-specific reason when both
10006
10006
  // apply (same project + same branch), which is why this gate runs
10007
- // AFTER it.
10007
+ // AFTER it. For hybrid projects (liveValidation): only gate when this
10008
+ // item itself resolves to live mode — worktree-mode coding items on
10009
+ // the same project remain uncapped.
10008
10010
  const itemProjName = item.project || item.meta?.project?.name || null;
10009
10011
  if (
10010
10012
  itemProjName
10011
10013
  && !READ_ONLY_ROOT_TASK_TYPES.has(item.type)
10012
10014
  && liveProjectsInUse.has(itemProjName)
10013
10015
  ) {
10014
- item._pendingReason = 'live_checkout_busy';
10015
- continue;
10016
+ const _gateProjCfg = shared.findProjectByName(shared.getProjects(config), itemProjName);
10017
+ if (shared.resolveCheckoutMode(_gateProjCfg, item.type) === 'live') {
10018
+ item._pendingReason = 'live_checkout_busy';
10019
+ continue;
10020
+ }
10016
10021
  }
10017
10022
  if (generalSlots <= 0) continue;
10018
10023
  seenPendingIds.add(item.id);
@@ -10027,7 +10032,7 @@ async function tickInner() {
10027
10032
  && !READ_ONLY_ROOT_TASK_TYPES.has(item.type)
10028
10033
  ) {
10029
10034
  const projCfg = shared.findProjectByName(shared.getProjects(config), itemProjName);
10030
- if (shared.isLiveCheckoutProject(projCfg)) {
10035
+ if (shared.resolveCheckoutMode(projCfg, item.type) === 'live') {
10031
10036
  liveProjectsInUse.add(itemProjName);
10032
10037
  }
10033
10038
  }
@@ -10105,7 +10110,7 @@ async function tickInner() {
10105
10110
  const projName = d.project || d.meta?.project?.name || null;
10106
10111
  if (!projName) continue;
10107
10112
  const projCfg = shared.findProjectByName(shared.getProjects(config), projName);
10108
- if (shared.isLiveCheckoutProject(projCfg)) {
10113
+ if (shared.resolveCheckoutMode(projCfg, d.type) === 'live') {
10109
10114
  postLiveProjectsInUse.add(projName);
10110
10115
  }
10111
10116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2271",
3
+ "version": "0.1.2272",
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"