@yemi33/minions 0.1.1874 → 0.1.1875
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/CHANGELOG.md +5 -0
- package/dashboard/js/render-plans.js +1 -1
- package/dashboard.js +34 -3
- package/engine/lifecycle.js +167 -121
- package/package.json +1 -1
- package/playbooks/plan-to-prd.md +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -16,7 +16,7 @@ function openCreatePlanModal() {
|
|
|
16
16
|
document.getElementById('modal-body').innerHTML =
|
|
17
17
|
'<div style="display:flex;flex-direction:column;gap:10px">' +
|
|
18
18
|
'<label style="color:var(--text);font-size:var(--text-md)">Title <input id="plan-new-title" style="' + inputStyle + '" placeholder="e.g. Add user authentication with JWT"></label>' +
|
|
19
|
-
'<label style="color:var(--text);font-size:var(--text-md)">Project <select id="plan-new-project" style="' + inputStyle + '"><option value="">
|
|
19
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Project <select id="plan-new-project" style="' + inputStyle + '"><option value="">Multiple / cross-repo (agent routes per item)</option>' + projOpts + '</select><span style="display:block;font-size:10px;color:var(--muted);margin-top:2px">Pick a project to scope every item to one repo. Leave on cross-repo for plans that span multiple projects.</span></label>' +
|
|
20
20
|
'<label style="color:var(--text);font-size:var(--text-md)">Plan Content <textarea id="plan-new-content" rows="12" style="' + inputStyle + ';resize:vertical;font-family:monospace;font-size:12px" placeholder="Write your plan in markdown...\n\nDescribe what needs to be built, the approach, requirements, and any constraints.\n\nThe squad will convert this into a PRD with structured work items."></textarea></label>' +
|
|
21
21
|
'<div style="font-size:11px;color:var(--muted)">After creating, click Execute on the plan card to have an agent convert it into a PRD with work items.</div>' +
|
|
22
22
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
package/dashboard.js
CHANGED
|
@@ -516,22 +516,53 @@ function findWorkItemsTargetById(id, source, projects = PROJECTS) {
|
|
|
516
516
|
|
|
517
517
|
function buildPlanWorkItem(body, projects = PROJECTS, options = {}) {
|
|
518
518
|
if (!body?.title || !String(body.title).trim()) return { error: 'title is required' };
|
|
519
|
-
|
|
519
|
+
|
|
520
|
+
// Multi-project: body.project may be an array. Each entry must resolve to a
|
|
521
|
+
// known project; ≥2 valid entries trigger cross-repo mode (WI lands in central,
|
|
522
|
+
// _targetProjects records the list, description gains an explicit directive so
|
|
523
|
+
// the plan agent writes a header-less plan and plan-to-prd routes per item).
|
|
524
|
+
const rawProject = body.project;
|
|
525
|
+
let targetProjects = null;
|
|
526
|
+
let singleProjectInput = rawProject;
|
|
527
|
+
if (Array.isArray(rawProject)) {
|
|
528
|
+
const names = [];
|
|
529
|
+
for (const entry of rawProject) {
|
|
530
|
+
const t = resolveWorkItemsCreateTarget(entry, projects);
|
|
531
|
+
if (t.error) return { error: t.error };
|
|
532
|
+
if (t.project?.name && !names.includes(t.project.name)) names.push(t.project.name);
|
|
533
|
+
}
|
|
534
|
+
if (names.length > 1) {
|
|
535
|
+
targetProjects = names;
|
|
536
|
+
singleProjectInput = '';
|
|
537
|
+
} else {
|
|
538
|
+
singleProjectInput = names[0] || '';
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const target = resolveWorkItemsCreateTarget(singleProjectInput, projects);
|
|
520
543
|
if (target.error) return { error: target.error };
|
|
521
544
|
let now = options.now ? new Date(options.now) : new Date();
|
|
522
545
|
if (!Number.isFinite(now.getTime())) now = new Date();
|
|
546
|
+
|
|
547
|
+
let description = body.description || '';
|
|
548
|
+
if (targetProjects) {
|
|
549
|
+
const prefix = `**Target Projects:** ${targetProjects.join(', ')}\n\nThis is a cross-repo plan. In the plan markdown, omit the \`**Project:**\` header (or leave it blank) so the plan-to-prd step can route each PRD item to the correct project via per-item \`project\` fields.\n\n---\n\n`;
|
|
550
|
+
description = prefix + description;
|
|
551
|
+
}
|
|
552
|
+
|
|
523
553
|
const item = {
|
|
524
554
|
id: options.id || ('W-' + shared.uid()),
|
|
525
555
|
title: body.title,
|
|
526
556
|
type: 'plan',
|
|
527
557
|
priority: body.priority || 'high',
|
|
528
|
-
description
|
|
558
|
+
description,
|
|
529
559
|
status: WI_STATUS.PENDING,
|
|
530
560
|
created: now.toISOString(),
|
|
531
561
|
createdBy: 'dashboard',
|
|
532
562
|
branchStrategy: body.branch_strategy || body.branchStrategy || 'parallel',
|
|
533
563
|
};
|
|
534
564
|
if (target.project) item.project = target.project.name;
|
|
565
|
+
if (targetProjects) item._targetProjects = targetProjects;
|
|
535
566
|
if (body.agent) item.agent = body.agent;
|
|
536
567
|
return { item, id: item.id };
|
|
537
568
|
}
|
|
@@ -7082,7 +7113,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7082
7113
|
{ method: 'POST', path: '/api/notes-save', desc: 'Save edited notes.md content', params: 'content, file?', handler: handleNotesSave },
|
|
7083
7114
|
|
|
7084
7115
|
// Plans
|
|
7085
|
-
{ method: 'POST', path: '/api/plan', desc: 'Create a plan work item that chains to PRD on completion', params: 'title, description?, priority?, project
|
|
7116
|
+
{ method: 'POST', path: '/api/plan', desc: 'Create a plan work item that chains to PRD on completion', params: 'title, description?, priority?, project? (string OR array for cross-repo plans), agent?, branch_strategy? or branchStrategy?', handler: handlePlanCreate },
|
|
7086
7117
|
{ method: 'GET', path: '/api/plans', desc: 'List plan files (.md drafts + .json PRDs)', handler: handlePlansList },
|
|
7087
7118
|
{ method: 'POST', path: '/api/plans/trigger-verify', desc: 'Manually trigger verification for a completed plan', params: 'file', handler: handlePlansTriggerVerify },
|
|
7088
7119
|
{ method: 'POST', path: '/api/plans/approve', desc: 'Approve a plan for execution', params: 'file, approvedBy?', handler: handlePlansApprove },
|
package/engine/lifecycle.js
CHANGED
|
@@ -157,11 +157,28 @@ function checkPlanCompletion(meta, config) {
|
|
|
157
157
|
return data;
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
// Resolve the primary project for writing new work items (PR, verify)
|
|
160
|
+
// Resolve the primary project for writing new work items (PR, verify).
|
|
161
|
+
// Multi-project plans (no plan.project) derive primary from the done items —
|
|
162
|
+
// the project with the most completed items hosts the verify WI; its description
|
|
163
|
+
// already enumerates worktrees for every project so the agent verifies cross-repo.
|
|
161
164
|
const projectName = plan.project;
|
|
162
|
-
|
|
165
|
+
let primaryProject = projectName
|
|
163
166
|
? shared.resolveProjectSource(projectName, projects, { allowCentral: false }).project
|
|
164
167
|
: (projects.length === 1 ? projects[0] : null);
|
|
168
|
+
if (!primaryProject && doneItems.length > 0) {
|
|
169
|
+
const counts = new Map();
|
|
170
|
+
for (const wi of doneItems) {
|
|
171
|
+
if (!wi.project) continue;
|
|
172
|
+
counts.set(wi.project, (counts.get(wi.project) || 0) + 1);
|
|
173
|
+
}
|
|
174
|
+
if (counts.size > 0) {
|
|
175
|
+
const [topName] = [...counts.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
176
|
+
primaryProject = shared.resolveProjectSource(topName, projects, { allowCentral: false }).project;
|
|
177
|
+
if (primaryProject) {
|
|
178
|
+
log('info', `Plan ${planFile}: multi-project — verify WI routed to "${primaryProject.name}" (${counts.get(topName)}/${doneItems.length} done items)`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
165
182
|
if (!primaryProject) {
|
|
166
183
|
log('warn', `Plan ${planFile}: no primary project found — skipping PR/verify creation`);
|
|
167
184
|
return;
|
|
@@ -190,32 +207,19 @@ function checkPlanCompletion(meta, config) {
|
|
|
190
207
|
}
|
|
191
208
|
}
|
|
192
209
|
|
|
193
|
-
// 4. Create verification work
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const verifyProject = existingVerify.project || projectName;
|
|
200
|
-
const vProject = shared.resolveProjectSource(verifyProject, projects, { allowCentral: false }).project || primaryProject;
|
|
201
|
-
const vWiPath = shared.projectWorkItemsPath(vProject);
|
|
202
|
-
let reopenedVerify = false;
|
|
203
|
-
mutateWorkItems(vWiPath, items => {
|
|
204
|
-
const v = items.find(w => w.id === existingVerify.id);
|
|
205
|
-
if (isReopenableVerify(v)) {
|
|
206
|
-
shared.reopenWorkItem(v);
|
|
207
|
-
reopenedVerify = true;
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
if (reopenedVerify) log('info', `Re-opened verification work item ${existingVerify.id} for modified plan ${planFile}`);
|
|
211
|
-
} else if (!existingVerify && doneItems.length > 0) {
|
|
212
|
-
const verifyId = 'PL-' + shared.uid();
|
|
210
|
+
// 4. Create verification work items — one per touched project (project with
|
|
211
|
+
// active PRs linked to this plan's done items). Each project's verify agent
|
|
212
|
+
// works in its own worktree on its own build/test cycle, in parallel.
|
|
213
|
+
// Single-project plans behave as before (one verify WI). Cross-repo plans
|
|
214
|
+
// fan out so each repo gets its own dedicated verification run.
|
|
215
|
+
if (doneItems.length > 0) {
|
|
213
216
|
const planSlug = planFile.replace('.json', '');
|
|
217
|
+
const isSharedBranch = plan.branch_strategy === 'shared-branch' && plan.feature_branch;
|
|
214
218
|
|
|
215
|
-
// Group PRs by project —
|
|
216
|
-
const projectPrs = {};
|
|
219
|
+
// Group active PRs by project — only projects with PRs from this plan get a verify WI
|
|
220
|
+
const projectPrs = {};
|
|
221
|
+
const prLinks = getPrLinks();
|
|
217
222
|
for (const p of projects) {
|
|
218
|
-
const prLinks = getPrLinks();
|
|
219
223
|
const prs = (safeJson(shared.projectPrPath(p)) || [])
|
|
220
224
|
.filter(pr => {
|
|
221
225
|
const linkedIds = prLinks[pr.id] || [];
|
|
@@ -226,118 +230,160 @@ function checkPlanCompletion(meta, config) {
|
|
|
226
230
|
}
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
//
|
|
230
|
-
|
|
233
|
+
// Fallback: no PRs surfaced (e.g., items completed without PR records, or
|
|
234
|
+
// legacy state). Verify the primary project so the plan still gets verified.
|
|
235
|
+
const touchedProjects = Object.keys(projectPrs).length > 0
|
|
236
|
+
? Object.entries(projectPrs)
|
|
237
|
+
: [[primaryProject.name, { project: primaryProject, prs: [], mainBranch: shared.resolveMainBranch(primaryProject.localPath, primaryProject.mainBranch) }]];
|
|
238
|
+
|
|
239
|
+
const otherProjectNames = (current) => touchedProjects.map(([n]) => n).filter(n => n !== current);
|
|
240
|
+
let createdAny = false;
|
|
241
|
+
let reopenedAny = false;
|
|
242
|
+
|
|
243
|
+
for (const [projName, { project: p, prs, mainBranch }] of touchedProjects) {
|
|
244
|
+
// Per-project existing verify lookup. Legacy single-WI plans may have
|
|
245
|
+
// existingVerify.project unset; match those against the primary project name.
|
|
246
|
+
const existingVerify = allWorkItems.find(w =>
|
|
247
|
+
w.sourcePlan === planFile && w.itemType === 'verify' &&
|
|
248
|
+
(w.project === projName || (!w.project && projName === primaryProject.name)));
|
|
249
|
+
|
|
250
|
+
if (isActiveVerify(existingVerify)) {
|
|
251
|
+
log('info', `Plan ${planFile}: verify WI ${existingVerify.id} for ${projName} already ${existingVerify.status} — skipping`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (isReopenableVerify(existingVerify)) {
|
|
256
|
+
const verifyProject = existingVerify.project || projName;
|
|
257
|
+
const vProject = shared.resolveProjectSource(verifyProject, projects, { allowCentral: false }).project || p;
|
|
258
|
+
const vWiPath = shared.projectWorkItemsPath(vProject);
|
|
259
|
+
let reopened = false;
|
|
260
|
+
mutateWorkItems(vWiPath, items => {
|
|
261
|
+
const v = items.find(w => w.id === existingVerify.id);
|
|
262
|
+
if (isReopenableVerify(v)) {
|
|
263
|
+
shared.reopenWorkItem(v);
|
|
264
|
+
reopened = true;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
if (reopened) {
|
|
268
|
+
reopenedAny = true;
|
|
269
|
+
log('info', `Re-opened verification work item ${existingVerify.id} for ${projName} (modified plan ${planFile})`);
|
|
270
|
+
}
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
231
273
|
|
|
232
|
-
|
|
233
|
-
|
|
274
|
+
// Build per-project setup block
|
|
275
|
+
let checkoutBlock;
|
|
276
|
+
let wtPath;
|
|
234
277
|
if (isSharedBranch) {
|
|
278
|
+
wtPath = `${p.localPath}/../worktrees/verify-${projName}-${planSlug}`;
|
|
235
279
|
const featureBranch = plan.feature_branch;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
`# ${name} — shared-branch: use existing feature branch directly`,
|
|
280
|
+
checkoutBlock = [
|
|
281
|
+
`# ${projName} — shared-branch: use existing feature branch directly`,
|
|
239
282
|
`cd "${p.localPath.replace(/\\/g, '/')}"`,
|
|
240
283
|
`git fetch origin "${featureBranch}"`,
|
|
241
284
|
`git worktree add "${wtPath}" "origin/${featureBranch}" 2>/dev/null || (cd "${wtPath}" && git checkout "${featureBranch}" && git pull origin "${featureBranch}")`,
|
|
242
|
-
];
|
|
243
|
-
|
|
285
|
+
].join('\n');
|
|
286
|
+
} else {
|
|
287
|
+
wtPath = `${p.localPath}/../worktrees/verify-${projName}-${planSlug}-${shared.uid()}`;
|
|
288
|
+
const branches = prs.map(pr => pr.branch).filter(Boolean);
|
|
289
|
+
checkoutBlock = [
|
|
290
|
+
`# ${projName} — merge ${branches.length} PR branch(es) into one worktree`,
|
|
291
|
+
`cd "${p.localPath.replace(/\\/g, '/')}"`,
|
|
292
|
+
branches.length > 0
|
|
293
|
+
? `git fetch origin ${branches.map(b => `"${b}"`).join(' ')} "${mainBranch}"`
|
|
294
|
+
: `git fetch origin "${mainBranch}"`,
|
|
295
|
+
`git worktree add "${wtPath}" "origin/${mainBranch}" 2>/dev/null || (cd "${wtPath}" && git checkout "${mainBranch}" && git pull origin "${mainBranch}")`,
|
|
296
|
+
`cd "${wtPath}"`,
|
|
297
|
+
...branches.map(b => `git merge "origin/${b}" --no-edit # ${prs.find(pr => pr.branch === b)?.id || b}`),
|
|
298
|
+
].join('\n');
|
|
244
299
|
}
|
|
245
|
-
const wtPath = `${p.localPath}/../worktrees/verify-${name}-${planSlug}-${shared.uid()}`;
|
|
246
|
-
const branches = prs.map(pr => pr.branch).filter(Boolean);
|
|
247
|
-
const lines = [
|
|
248
|
-
`# ${name} — merge ${branches.length} PR branch(es) into one worktree`,
|
|
249
|
-
`cd "${p.localPath.replace(/\\/g, '/')}"`,
|
|
250
|
-
`git fetch origin ${branches.map(b => `"${b}"`).join(' ')} "${mainBranch}"`,
|
|
251
|
-
`git worktree add "${wtPath}" "origin/${mainBranch}" 2>/dev/null || (cd "${wtPath}" && git checkout "${mainBranch}" && git pull origin "${mainBranch}")`,
|
|
252
|
-
`cd "${wtPath}"`,
|
|
253
|
-
...branches.map(b => `git merge "origin/${b}" --no-edit # ${prs.find(pr => pr.branch === b)?.id || b}`),
|
|
254
|
-
];
|
|
255
|
-
return lines.join('\n');
|
|
256
|
-
}).join('\n\n');
|
|
257
|
-
|
|
258
|
-
// Build completed items summary with acceptance criteria
|
|
259
|
-
const itemsWithCriteria = doneItems.map(w => {
|
|
260
|
-
const planItem = plan.missing_features?.find(f => f.id === w.id);
|
|
261
|
-
const criteria = (planItem?.acceptance_criteria || []).map(c => ` - ${c}`).join('\n');
|
|
262
|
-
return `### ${w.id}: ${(w.title || 'Untitled').replace('Implement: ', '')}\n${criteria ? '**Acceptance Criteria:**\n' + criteria : ''}`;
|
|
263
|
-
}).join('\n\n');
|
|
264
|
-
|
|
265
|
-
const prSummary = uniquePrs.map(pr =>
|
|
266
|
-
`- ${pr.id}: ${pr.title || ''} (branch: \`${pr.branch || '?'}\`) ${pr.url || ''}`
|
|
267
|
-
).join('\n');
|
|
268
|
-
|
|
269
|
-
// List projects and their worktree paths for the agent
|
|
270
|
-
const projectWorktrees = Object.entries(projectPrs).map(([name, { project: p }]) =>
|
|
271
|
-
`- **${name}**: \`${p.localPath}/../worktrees/verify-${planSlug}\``
|
|
272
|
-
).join('\n');
|
|
273
300
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
301
|
+
// Items scoped to this project (fall back to all done items if items lack project field — single-project plans)
|
|
302
|
+
const projItems = doneItems.filter(w => w.project === projName);
|
|
303
|
+
const itemsForProject = projItems.length > 0 ? projItems : doneItems;
|
|
304
|
+
const itemsWithCriteria = itemsForProject.map(w => {
|
|
305
|
+
const planItem = plan.missing_features?.find(f => f.id === w.id);
|
|
306
|
+
const criteria = (planItem?.acceptance_criteria || []).map(c => ` - ${c}`).join('\n');
|
|
307
|
+
return `### ${w.id}: ${(w.title || 'Untitled').replace('Implement: ', '')}\n${criteria ? '**Acceptance Criteria:**\n' + criteria : ''}`;
|
|
308
|
+
}).join('\n\n');
|
|
309
|
+
|
|
310
|
+
const prSummary = prs.map(pr =>
|
|
311
|
+
`- ${pr.id}: ${pr.title || ''} (branch: \`${pr.branch || '?'}\`) ${pr.url || ''}`
|
|
312
|
+
).join('\n') || '_No PRs surfaced for this project — verify the worktree directly._';
|
|
313
|
+
|
|
314
|
+
const sharedBranchNote = isSharedBranch
|
|
315
|
+
? `\n**Shared-branch plan** — all changes are already on branch \`${plan.feature_branch}\`. Use this branch directly for the E2E PR instead of creating a new \`e2e/\` branch. Check if a PR already exists for this branch before creating one.\n`
|
|
316
|
+
: '';
|
|
317
|
+
|
|
318
|
+
const siblings = otherProjectNames(projName);
|
|
319
|
+
const crossRepoNote = touchedProjects.length > 1
|
|
320
|
+
? `\n**Cross-repo plan** — this verify task covers **${projName}** only. Sibling verify tasks run in parallel for: ${siblings.join(', ')}. Build/test this project's worktree independently.\n`
|
|
321
|
+
: '';
|
|
322
|
+
|
|
323
|
+
const description = [
|
|
324
|
+
`Verification task for completed plan \`${planFile}\` — project: **${projName}**.`,
|
|
325
|
+
crossRepoNote,
|
|
326
|
+
sharedBranchNote,
|
|
327
|
+
`## Worktree`,
|
|
328
|
+
``,
|
|
329
|
+
`- **${projName}**: \`${wtPath}\``,
|
|
330
|
+
``,
|
|
331
|
+
`## Setup Commands`,
|
|
332
|
+
``,
|
|
333
|
+
`\`\`\`bash`,
|
|
334
|
+
checkoutBlock,
|
|
335
|
+
`\`\`\``,
|
|
336
|
+
``,
|
|
337
|
+
`If any merge conflicts occur, resolve them (prefer the PR branch changes).`,
|
|
338
|
+
`After setup, build and test from the worktree path above.`,
|
|
339
|
+
``,
|
|
340
|
+
`## Completed Items`,
|
|
341
|
+
``,
|
|
342
|
+
itemsWithCriteria,
|
|
343
|
+
``,
|
|
344
|
+
`## Pull Requests`,
|
|
345
|
+
``,
|
|
346
|
+
prSummary,
|
|
347
|
+
].join('\n');
|
|
348
|
+
|
|
349
|
+
const verifyId = 'PL-' + shared.uid();
|
|
350
|
+
const vWiPath = shared.projectWorkItemsPath(p);
|
|
351
|
+
let created = false;
|
|
352
|
+
mutateWorkItems(vWiPath, workItems => {
|
|
353
|
+
// Re-check under lock to prevent races
|
|
354
|
+
if (workItems.some(w => w.sourcePlan === planFile && w.itemType === 'verify' && (w.project === projName || (!w.project && projName === primaryProject.name)))) {
|
|
355
|
+
return workItems;
|
|
312
356
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
357
|
+
workItems.push({
|
|
358
|
+
id: verifyId,
|
|
359
|
+
title: touchedProjects.length > 1
|
|
360
|
+
? `Verify plan (${projName}): ${(plan.plan_summary || planFile).slice(0, 70)}`
|
|
361
|
+
: `Verify plan: ${(plan.plan_summary || planFile).slice(0, 80)}`,
|
|
362
|
+
type: 'verify',
|
|
363
|
+
priority: 'high',
|
|
364
|
+
description,
|
|
365
|
+
status: WI_STATUS.PENDING,
|
|
366
|
+
created: ts(),
|
|
367
|
+
createdBy: 'engine:plan-verification',
|
|
368
|
+
sourcePlan: planFile,
|
|
369
|
+
itemType: 'verify',
|
|
370
|
+
project: projName,
|
|
371
|
+
});
|
|
372
|
+
created = true;
|
|
327
373
|
});
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
374
|
+
if (created) {
|
|
375
|
+
createdAny = true;
|
|
376
|
+
log('info', `Created verification work item ${verifyId} for plan ${planFile} → ${projName}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
332
379
|
|
|
333
|
-
|
|
380
|
+
if (createdAny) {
|
|
334
381
|
try {
|
|
335
382
|
const teams = require('./teams');
|
|
336
383
|
teams.teamsNotifyPlanEvent({ name: plan.plan_summary || planFile, file: planFile }, 'verify-created').catch(() => {});
|
|
337
384
|
} catch {}
|
|
338
|
-
} else if (reopenedVerifyId) {
|
|
339
|
-
log('info', `Re-opened verification work item ${reopenedVerifyId} for modified plan ${planFile}`);
|
|
340
385
|
}
|
|
386
|
+
if (reopenedAny) log('info', `Plan ${planFile}: re-opened verify WI(s) for modified plan`);
|
|
341
387
|
}
|
|
342
388
|
|
|
343
389
|
// Archive deferred until verify completes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1875",
|
|
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/plan-to-prd.md
CHANGED
|
@@ -88,6 +88,14 @@ Rules for items:
|
|
|
88
88
|
- **`status` is `"missing"` for new items** — do not set `done`, `complete`, `implemented`, or any other value based on codebase observations. The only exception is when reusing an existing PRD (see below) — items already `"done"` in the existing PRD carry forward as `"done"`. Pre-setting any other status on new items causes them to be silently skipped by the engine.
|
|
89
89
|
- **Do NOT include a "verify" or "test" or "integration test" item** — the engine automatically creates a verify task when all PRD items are done. Adding one manually creates a duplicate that blocks plan completion.
|
|
90
90
|
- **`project` field is REQUIRED** — set it to the project name where the code changes go (e.g., `"OfficeAgent"`, `"office-bohemia"`). If the plan declares a single project, every item must use `{{project_name}}`; contextual mentions of another repo/product must not override it. Cross-repo plans must route each item to the correct project. The engine materializes items into that project's work queue.
|
|
91
|
+
- **Cross-repo plans:** when the plan body does NOT declare `**Project:**` (or declares it empty), `{{project_name}}` is unset. In that case, set each item's `project` to the actual repo where its code changes belong, and omit the top-level `"project"` field (or set it to `""`). Example:
|
|
92
|
+
```json
|
|
93
|
+
"missing_features": [
|
|
94
|
+
{ "id": "P-aaa1", "name": "API endpoint", "project": "backend-service", ... },
|
|
95
|
+
{ "id": "P-bbb2", "name": "UI client", "project": "web-dashboard", ... }
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
The engine routes each item to that project's `work-items.json`. The verify task at plan completion picks the project with the most completed items and runs cross-repo verification from there.
|
|
91
99
|
- `depends_on` lists IDs of items that must be done first
|
|
92
100
|
- Keep descriptions actionable — name the files, functions, patterns, or integration points the implementing agent should touch whenever the plan makes them clear
|
|
93
101
|
- Include `acceptance_criteria` so reviewers know when it's done
|