@yemi33/minions 0.1.2200 → 0.1.2201
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 +50 -26
- package/engine/cli.js +2 -0
- package/engine/db/index.js +25 -1
- package/engine/lifecycle.js +9 -6
- package/engine/playbook.js +7 -0
- package/engine/projects.js +4 -2
- package/engine/shared.js +25 -0
- package/engine/work-items-store.js +6 -7
- package/engine.js +11 -0
- package/package.json +1 -1
- package/playbooks/docs.md +23 -0
- package/playbooks/shared-rules.md +6 -0
- package/playbooks/test.md +5 -0
package/dashboard.js
CHANGED
|
@@ -1579,10 +1579,11 @@ function _archivePrdPostProcess({ planFile, archivePath, planPath, plansDir, _mu
|
|
|
1579
1579
|
try {
|
|
1580
1580
|
const mdPath = path.join(plansDir, plan.source_plan);
|
|
1581
1581
|
if (fs.existsSync(mdPath)) {
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1582
|
+
// DATA-LOSS GUARD: moveFileNoClobber dedupes + retries so archiving a
|
|
1583
|
+
// source plan can't overwrite a previously-archived plan of the same
|
|
1584
|
+
// basename (and survives transient Windows file locks).
|
|
1585
|
+
const mdDest = shared.moveFileNoClobber(mdPath, path.join(plansDir, 'archive'), plan.source_plan);
|
|
1586
|
+
archivedSource = path.basename(mdDest);
|
|
1586
1587
|
}
|
|
1587
1588
|
} catch (e) {
|
|
1588
1589
|
const warning = `Archive could not move source plan ${plan.source_plan}: ${e.message}`;
|
|
@@ -7033,7 +7034,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
7033
7034
|
const file = body.file || 'notes.md';
|
|
7034
7035
|
// Only allow saving notes.md (prevent arbitrary file writes)
|
|
7035
7036
|
if (file !== 'notes.md') return jsonReply(res, 400, { error: 'only notes.md can be edited' });
|
|
7036
|
-
|
|
7037
|
+
// DATA-LOSS GUARD: write under the same lock the engine consolidation
|
|
7038
|
+
// path holds (withFileLock(notes.md.lock)) so a concurrent consolidation
|
|
7039
|
+
// flush in the engine process can't lose-update this save (and vice-versa).
|
|
7040
|
+
shared.mutateTextFileLocked(path.join(MINIONS_DIR, file), () => body.content);
|
|
7037
7041
|
return jsonReply(res, 200, { ok: true });
|
|
7038
7042
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
7039
7043
|
}
|
|
@@ -7774,9 +7778,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
7774
7778
|
if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan not found' });
|
|
7775
7779
|
|
|
7776
7780
|
const archiveDir = isPrd ? path.join(PRD_DIR, 'archive') : path.join(PLANS_DIR, 'archive');
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7781
|
+
// DATA-LOSS GUARD: moveFileNoClobber dedupes the destination so a same-
|
|
7782
|
+
// basename collision (e.g. re-archiving a project's canonical
|
|
7783
|
+
// <project>-<date> name, or the W-mq8qdai6 live↔archive collision class)
|
|
7784
|
+
// can't silently destroy a previously-archived plan/PRD + its completed
|
|
7785
|
+
// work-item history. Bumps to -2/-3 instead of overwriting.
|
|
7786
|
+
const archivePath = shared.moveFileNoClobber(planPath, archiveDir, body.file);
|
|
7780
7787
|
|
|
7781
7788
|
let archivedSource = null;
|
|
7782
7789
|
let plan = {};
|
|
@@ -7839,9 +7846,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
7839
7846
|
console.warn(warning);
|
|
7840
7847
|
continue;
|
|
7841
7848
|
}
|
|
7842
|
-
|
|
7849
|
+
let prdArchivePath;
|
|
7843
7850
|
try {
|
|
7844
|
-
|
|
7851
|
+
// DATA-LOSS GUARD: dedupe + retry so the cascade can't clobber a
|
|
7852
|
+
// previously-archived PRD of the same basename.
|
|
7853
|
+
prdArchivePath = shared.moveFileNoClobber(prdLivePath, prdArchiveDir, prdFile);
|
|
7845
7854
|
} catch (e) {
|
|
7846
7855
|
const warning = `Archive could not move PRD ${prdFile}: ${e.message}`;
|
|
7847
7856
|
archiveWarnings.push(warning);
|
|
@@ -7906,18 +7915,21 @@ const server = http.createServer(async (req, res) => {
|
|
|
7906
7915
|
const targetDir = isJson ? PRD_DIR : PLANS_DIR;
|
|
7907
7916
|
const archivePath = path.join(targetDir, 'archive', body.file);
|
|
7908
7917
|
if (!fs.existsSync(archivePath)) return jsonReply(res, 404, { error: 'File not found in archive' });
|
|
7909
|
-
|
|
7918
|
+
// DATA-LOSS GUARD: restoring from archive must not clobber a LIVE plan/PRD
|
|
7919
|
+
// of the same basename (renameSync overwrites). moveFileNoClobber bumps
|
|
7920
|
+
// the restored name instead so the existing live file survives.
|
|
7921
|
+
const liveDest = shared.moveFileNoClobber(archivePath, targetDir, body.file);
|
|
7910
7922
|
|
|
7911
7923
|
// Also unarchive linked source plan
|
|
7912
7924
|
let unarchivedSource = null;
|
|
7913
7925
|
if (isJson) {
|
|
7914
7926
|
try {
|
|
7915
|
-
const prd = safeJson(
|
|
7927
|
+
const prd = safeJson(liveDest);
|
|
7916
7928
|
if (prd?.source_plan) {
|
|
7917
7929
|
const mdArchivePath = path.join(PLANS_DIR, 'archive', prd.source_plan);
|
|
7918
7930
|
if (fs.existsSync(mdArchivePath)) {
|
|
7919
|
-
|
|
7920
|
-
unarchivedSource =
|
|
7931
|
+
const liveMdDest = shared.moveFileNoClobber(mdArchivePath, PLANS_DIR, prd.source_plan);
|
|
7932
|
+
unarchivedSource = path.basename(liveMdDest);
|
|
7921
7933
|
}
|
|
7922
7934
|
}
|
|
7923
7935
|
} catch { /* optional */ }
|
|
@@ -7925,7 +7937,13 @@ const server = http.createServer(async (req, res) => {
|
|
|
7925
7937
|
|
|
7926
7938
|
invalidateStatusCache();
|
|
7927
7939
|
invalidatePlansCache();
|
|
7928
|
-
|
|
7940
|
+
// `restoredAs` reflects the actual restored basename: moveFileNoClobber
|
|
7941
|
+
// bumps to <name>-2 when a live file of the same name already exists, so
|
|
7942
|
+
// the client isn't misled into expecting body.file when it differs.
|
|
7943
|
+
const restoredAs = path.basename(liveDest);
|
|
7944
|
+
const payload = { ok: true, unarchivedSource };
|
|
7945
|
+
if (restoredAs !== body.file) payload.restoredAs = restoredAs;
|
|
7946
|
+
return jsonReply(res, 200, payload);
|
|
7929
7947
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
7930
7948
|
}
|
|
7931
7949
|
|
|
@@ -8384,19 +8402,25 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
8384
8402
|
|
|
8385
8403
|
// Append to notes.md as a new team note
|
|
8386
8404
|
const notesPath = path.join(MINIONS_DIR, 'notes.md');
|
|
8387
|
-
let notes = safeRead(notesPath) || '# Minions Notes\n\n## Active Notes\n';
|
|
8388
8405
|
const today = new Date().toISOString().slice(0, 10);
|
|
8389
8406
|
const entry = `\n### ${today}: ${title}\n**By:** Persisted from inbox (${name})\n**What:** ${content.slice(0, 500)}\n\n---\n`;
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8407
|
+
// DATA-LOSS GUARD: read-modify-write notes.md under the same lock the
|
|
8408
|
+
// engine consolidation path holds. The previous unlocked read→splice→write
|
|
8409
|
+
// could lose-update against a concurrent consolidation flush (the engine
|
|
8410
|
+
// and dashboard are separate processes), silently dropping one note.
|
|
8411
|
+
const NOTES_DEFAULT = '# Minions Notes\n\n## Active Notes\n';
|
|
8412
|
+
shared.mutateTextFileLocked(notesPath, (prev) => {
|
|
8413
|
+
let notes = prev || NOTES_DEFAULT;
|
|
8414
|
+
const marker = '## Active Notes';
|
|
8415
|
+
const idx = notes.indexOf(marker);
|
|
8416
|
+
if (idx !== -1) {
|
|
8417
|
+
const insertAt = idx + marker.length;
|
|
8418
|
+
notes = notes.slice(0, insertAt) + '\n' + entry + notes.slice(insertAt);
|
|
8419
|
+
} else {
|
|
8420
|
+
notes += '\n' + entry;
|
|
8421
|
+
}
|
|
8422
|
+
return notes;
|
|
8423
|
+
}, { defaultValue: NOTES_DEFAULT });
|
|
8400
8424
|
|
|
8401
8425
|
// W-mq1j85cj00055a8f — rewrite WI references that pointed at the
|
|
8402
8426
|
// now-archived inbox note to the persisted destination (notes.md).
|
package/engine/cli.js
CHANGED
|
@@ -1211,6 +1211,8 @@ const commands = {
|
|
|
1211
1211
|
|
|
1212
1212
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
1213
1213
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
1214
|
+
// WAL checkpoint-on-exit is registered by engine/db getDb() itself (covers
|
|
1215
|
+
// the engine daemon, dashboard, and CLI uniformly) — no hook needed here.
|
|
1214
1216
|
|
|
1215
1217
|
// Crash handlers — log the error and exit cleanly so the process is detectable as crashed
|
|
1216
1218
|
process.on('unhandledRejection', (reason) => {
|
package/engine/db/index.js
CHANGED
|
@@ -20,6 +20,20 @@ const fs = require('fs');
|
|
|
20
20
|
let _db = null;
|
|
21
21
|
let _dbPath = null;
|
|
22
22
|
let _dbInitError = null;
|
|
23
|
+
let _exitCheckpointRegistered = false;
|
|
24
|
+
|
|
25
|
+
// DATA-LOSS GUARD: register a one-time process-exit hook that checkpoints +
|
|
26
|
+
// closes the DB. Registered by getDb() on first successful open so EVERY DB
|
|
27
|
+
// opener (engine daemon, dashboard, CLI) gets WAL durability on exit — not just
|
|
28
|
+
// the engine CLI path. Idempotent: closeDb()'s `if (_db)` guard makes a later
|
|
29
|
+
// graceful-shutdown close a no-op. Synchronous-only work (node:sqlite is sync),
|
|
30
|
+
// which is all `process.on('exit')` permits.
|
|
31
|
+
function _registerExitCheckpoint() {
|
|
32
|
+
if (_exitCheckpointRegistered) return;
|
|
33
|
+
_exitCheckpointRegistered = true;
|
|
34
|
+
try { process.on('exit', () => { try { closeDb(); } catch { /* best-effort */ } }); }
|
|
35
|
+
catch { /* env without process events */ }
|
|
36
|
+
}
|
|
23
37
|
|
|
24
38
|
function _resolveDbPath() {
|
|
25
39
|
// Lazy-require shared/queries so this module can be safely required
|
|
@@ -129,6 +143,7 @@ function getDb() {
|
|
|
129
143
|
if (lastErr) throw lastErr;
|
|
130
144
|
const { runMigrations } = require('./migrate');
|
|
131
145
|
runMigrations(_db);
|
|
146
|
+
_registerExitCheckpoint();
|
|
132
147
|
return _db;
|
|
133
148
|
} catch (e) {
|
|
134
149
|
const nodeMajor = parseInt(String(process.versions.node).split('.')[0], 10);
|
|
@@ -142,7 +157,16 @@ function getDb() {
|
|
|
142
157
|
|
|
143
158
|
// Force-close — used by tests + graceful shutdown. Safe to call when not open.
|
|
144
159
|
function closeDb() {
|
|
145
|
-
if (_db) {
|
|
160
|
+
if (_db) {
|
|
161
|
+
// DATA-LOSS GUARD: under journal_mode=WAL + synchronous=NORMAL, committed
|
|
162
|
+
// transactions can live only in the -wal file until a checkpoint folds them
|
|
163
|
+
// into the main DB. Checkpoint on graceful close so a host crash / power loss
|
|
164
|
+
// after shutdown can't drop the most recent commits (and so the WAL doesn't
|
|
165
|
+
// grow unbounded). Best-effort — a failed checkpoint must not block close.
|
|
166
|
+
try { _db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch { /* best-effort */ }
|
|
167
|
+
try { _db.close(); } catch { /* already closed */ }
|
|
168
|
+
_db = null;
|
|
169
|
+
}
|
|
146
170
|
_dbPath = null;
|
|
147
171
|
_dbInitError = null;
|
|
148
172
|
}
|
package/engine/lifecycle.js
CHANGED
|
@@ -432,8 +432,11 @@ function archivePlan(planFile, plan, projects, config) {
|
|
|
432
432
|
if (!fs.existsSync(prdArchiveDir)) fs.mkdirSync(prdArchiveDir, { recursive: true });
|
|
433
433
|
try {
|
|
434
434
|
if (fs.existsSync(planPath)) {
|
|
435
|
-
|
|
436
|
-
|
|
435
|
+
// DATA-LOSS GUARD: moveFileNoClobber dedupes + retries so a same-basename
|
|
436
|
+
// collision can't silently overwrite a previously-archived PRD (+ its
|
|
437
|
+
// completed work-item history). renameSync clobbers on Windows.
|
|
438
|
+
const prdDest = shared.moveFileNoClobber(planPath, prdArchiveDir, planFile);
|
|
439
|
+
log('info', `Archived completed PRD: prd/archive/${path.basename(prdDest)}`);
|
|
437
440
|
}
|
|
438
441
|
// Remove .backup sidecar — if left behind, safeJson() would restore the pre-completion
|
|
439
442
|
// snapshot (status: approved, no _completionNotified) on engine restart, re-triggering
|
|
@@ -457,8 +460,8 @@ function archivePlan(planFile, plan, projects, config) {
|
|
|
457
460
|
const sourcePlanName = plan.source_plan || planFile.replace(/\.json$/, '.md');
|
|
458
461
|
if (sourcePlanName && fs.existsSync(path.join(PLANS_DIR, sourcePlanName))) {
|
|
459
462
|
try {
|
|
460
|
-
|
|
461
|
-
log('info', `Archived source plan: plans/archive/${
|
|
463
|
+
const mdDest = shared.moveFileNoClobber(path.join(PLANS_DIR, sourcePlanName), planArchiveDir, sourcePlanName);
|
|
464
|
+
log('info', `Archived source plan: plans/archive/${path.basename(mdDest)}`);
|
|
462
465
|
} catch (err) { log('warn', `Failed to archive plan ${sourcePlanName}: ${err.message}`); }
|
|
463
466
|
} else {
|
|
464
467
|
// Fallback: match by content
|
|
@@ -467,8 +470,8 @@ function archivePlan(planFile, plan, projects, config) {
|
|
|
467
470
|
const mdContent = shared.safeRead(path.join(PLANS_DIR, md)) || '';
|
|
468
471
|
if (mdContent.includes(projectName) || mdContent.includes(plan.plan_summary?.slice(0, 40) || '___nomatch___')) {
|
|
469
472
|
try {
|
|
470
|
-
|
|
471
|
-
log('info', `Archived source plan: plans/archive/${
|
|
473
|
+
const mdDest = shared.moveFileNoClobber(path.join(PLANS_DIR, md), planArchiveDir, md);
|
|
474
|
+
log('info', `Archived source plan: plans/archive/${path.basename(mdDest)}`);
|
|
472
475
|
} catch (err) { log('warn', `Failed to archive plan ${md}: ${err.message}`); }
|
|
473
476
|
break;
|
|
474
477
|
}
|
package/engine/playbook.js
CHANGED
|
@@ -298,6 +298,13 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
|
298
298
|
'existing_prd_json', // only set when re-running plan-to-prd over an existing PRD
|
|
299
299
|
'branch_strategy_hint', // only set for shared-branch plans
|
|
300
300
|
'review_note', // only set on fix/review tasks tied to a comment
|
|
301
|
+
// W-mqh08n0u002ha5c4 — shared-branch carve-out flag. Set to '1' by
|
|
302
|
+
// renderProjectWorkItemPromptForAgent only when the dispatched item is on a
|
|
303
|
+
// shared-branch plan; '' otherwise. Drives the {{#shared_branch}} /
|
|
304
|
+
// {{^shared_branch}} blocks in docs.md / test.md that suppress premature PR
|
|
305
|
+
// creation. Optional because every other playbook ignores it and the
|
|
306
|
+
// default state is the empty string (renders the non-shared branch).
|
|
307
|
+
'shared_branch',
|
|
301
308
|
// P-4d6e2af3 — comma-separated list of target projects for cross-repo
|
|
302
309
|
// plans. Only set in engine.js's WORK_TYPE.PLAN branch when the WI carries
|
|
303
310
|
// item._targetProjects (≥2 entries from dashboard.js#buildPlanWorkItem).
|
package/engine/projects.js
CHANGED
|
@@ -261,8 +261,10 @@ function removeProject(target, options = {}) {
|
|
|
261
261
|
const filenameMatch = f.toLowerCase().includes(lowerName);
|
|
262
262
|
const content = filenameMatch ? '' : fs.readFileSync(fp, 'utf8');
|
|
263
263
|
if (!filenameMatch && !projectLineRe.test(content)) continue;
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
// DATA-LOSS GUARD: dedupe + retry so re-removing a project (same plan
|
|
265
|
+
// basename) can't overwrite a previously-archived plan.
|
|
266
|
+
const planDest = shared.moveFileNoClobber(fp, plansArchive, f);
|
|
267
|
+
summary.archivedPlans.push('plans/' + path.basename(planDest));
|
|
266
268
|
} catch { /* skip unreadable plan */ }
|
|
267
269
|
}
|
|
268
270
|
}
|
package/engine/shared.js
CHANGED
|
@@ -1872,6 +1872,30 @@ function uniquePath(filePath) {
|
|
|
1872
1872
|
return `${base}-${Date.now()}${ext}`;
|
|
1873
1873
|
}
|
|
1874
1874
|
|
|
1875
|
+
/**
|
|
1876
|
+
* Move a file into `destDir` WITHOUT ever overwriting an existing file there —
|
|
1877
|
+
* the canonical archive/restore data-loss guard. `fs.renameSync` clobbers its
|
|
1878
|
+
* destination (silently, on Windows especially), so archiving a plan/PRD whose
|
|
1879
|
+
* basename already exists in the archive dir, or restoring over a live file of
|
|
1880
|
+
* the same name, would destroy the existing file (+ its history). This dedupes
|
|
1881
|
+
* the destination basename via `uniquePath` (-2/-3/...) and wraps the rename in
|
|
1882
|
+
* `_retryFsOp` so a transient Windows EPERM/EBUSY (AV / indexer lock) doesn't
|
|
1883
|
+
* fail the move. Creates `destDir` if missing. Returns the final destination
|
|
1884
|
+
* path actually written (caller derives the basename for logs/records).
|
|
1885
|
+
*
|
|
1886
|
+
* @param {string} srcPath - file to move (caller ensures it exists)
|
|
1887
|
+
* @param {string} destDir - directory to move it into
|
|
1888
|
+
* @param {string} [destName=path.basename(srcPath)] - basename to use at dest
|
|
1889
|
+
* @returns {string} absolute destination path the file now lives at
|
|
1890
|
+
*/
|
|
1891
|
+
function moveFileNoClobber(srcPath, destDir, destName) {
|
|
1892
|
+
const name = destName || path.basename(srcPath);
|
|
1893
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
1894
|
+
const dest = uniquePath(path.join(destDir, name));
|
|
1895
|
+
_retryFsOp(() => fs.renameSync(srcPath, dest), `move ${name} -> archive`);
|
|
1896
|
+
return dest;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1875
1899
|
function truncateTextBytes(text, maxBytes, suffix = '') {
|
|
1876
1900
|
const value = text == null ? '' : String(text);
|
|
1877
1901
|
if (!maxBytes || maxBytes <= 0) return '';
|
|
@@ -8213,6 +8237,7 @@ module.exports = {
|
|
|
8213
8237
|
mutatePullRequests,
|
|
8214
8238
|
uid,
|
|
8215
8239
|
uniquePath,
|
|
8240
|
+
moveFileNoClobber,
|
|
8216
8241
|
isPlainObject,
|
|
8217
8242
|
truncateTextBytes,
|
|
8218
8243
|
tailTextBytes,
|
|
@@ -117,13 +117,12 @@ function _resyncScopeIfJsonDiverged(db, scope) {
|
|
|
117
117
|
const lastHash = _lastMirrorHashByScope.get(scope);
|
|
118
118
|
if (currentHash == null) return;
|
|
119
119
|
if (lastHash != null && currentHash === lastHash) return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
120
|
+
// First touch in this process (lastHash == null): if SQL already holds rows
|
|
121
|
+
// for the scope, SQL is the established source of truth (post-Phase-9.4) —
|
|
122
|
+
// adopt the current JSON hash and DO NOT hydrate. Hydrating here would let a
|
|
123
|
+
// crash-stale / .backup-restored / git-checked-out older JSON mirror DELETE +
|
|
124
|
+
// overwrite newer SQL. Only an empty-in-SQL scope (genuine first-install
|
|
125
|
+
// migration) falls through to hydrate-from-JSON, which is purely additive.
|
|
127
126
|
if (lastHash == null) {
|
|
128
127
|
const sqlHas = db.prepare('SELECT 1 FROM work_items WHERE scope = ? LIMIT 1').get(scope);
|
|
129
128
|
if (sqlHas) {
|
package/engine.js
CHANGED
|
@@ -6773,6 +6773,17 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
|
|
|
6773
6773
|
project_path: root,
|
|
6774
6774
|
worktree_path: worktreePath,
|
|
6775
6775
|
commit_message: item.commitMessage || `feat: ${item.title || item.id}`,
|
|
6776
|
+
// W-mqh08n0u002ha5c4 — shared-branch carve-out flag. On a shared-branch
|
|
6777
|
+
// plan (branch_strategy: shared-branch) NO PR is opened until all plan
|
|
6778
|
+
// items complete — the engine/verify flow opens the single aggregate PR.
|
|
6779
|
+
// implement items already route to implement-shared.md (which omits PR
|
|
6780
|
+
// creation); this flag lets non-implement playbooks (docs.md, test.md)
|
|
6781
|
+
// suppress their own premature PR-creation step the same way via
|
|
6782
|
+
// {{#shared_branch}} / {{^shared_branch}} conditional blocks. Truthy only
|
|
6783
|
+
// when the item is genuinely on a shared branch (mirrors selectPlaybook's
|
|
6784
|
+
// implement→implement-shared gate at engine/playbook.js). Default '' for
|
|
6785
|
+
// parallel/PR-targeted dispatches keeps existing per-type PR behavior.
|
|
6786
|
+
shared_branch: (item.branchStrategy === 'shared-branch' && item.featureBranch) ? '1' : '',
|
|
6776
6787
|
notes_content: '',
|
|
6777
6788
|
pr_id: item.pr_id || item._pr || item.targetPr || item.sourcePr || item.pr || '',
|
|
6778
6789
|
pr_number: item.prNumber || item.pr_number || '',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2201",
|
|
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/docs.md
CHANGED
|
@@ -90,6 +90,20 @@ the engine handles cleanup automatically.
|
|
|
90
90
|
Commit only the doc files (and any helper assets they reference). Do not bundle
|
|
91
91
|
unrelated code changes into a docs PR.
|
|
92
92
|
|
|
93
|
+
{{#shared_branch}}
|
|
94
|
+
```bash
|
|
95
|
+
git add <doc files>
|
|
96
|
+
git commit -m "{{commit_message}}"
|
|
97
|
+
git push origin {{branch_name}}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This is part of a **shared-branch plan** — other plan items share `{{branch_name}}`.
|
|
101
|
+
Push your commit to the shared branch and STOP. Do **NOT** create a PR — the engine
|
|
102
|
+
opens one aggregate PR automatically when all plan items complete. Opening a PR now
|
|
103
|
+
would be premature (other items are still in flight) and the early branch would collect
|
|
104
|
+
merge conflicts from concurrent sibling pushes.
|
|
105
|
+
{{/shared_branch}}
|
|
106
|
+
{{^shared_branch}}
|
|
93
107
|
```bash
|
|
94
108
|
git add <doc files>
|
|
95
109
|
git commit -m "{{commit_message}}"
|
|
@@ -99,6 +113,7 @@ git push -u origin {{branch_name}}
|
|
|
99
113
|
Create the PR for docs tasks — docs go through the same review flow as code.
|
|
100
114
|
Use the appropriate repo-host tooling for PR creation. For Azure DevOps, prefer the
|
|
101
115
|
`az` CLI first and use the ADO MCP only as a fallback.
|
|
116
|
+
{{/shared_branch}}
|
|
102
117
|
|
|
103
118
|
## Rules
|
|
104
119
|
|
|
@@ -109,9 +124,17 @@ Use the appropriate repo-host tooling for PR creation. For Azure DevOps, prefer
|
|
|
109
124
|
|
|
110
125
|
## When to Stop
|
|
111
126
|
|
|
127
|
+
{{#shared_branch}}
|
|
128
|
+
Your task is complete once the doc accurately reflects current code, any doc-validation
|
|
129
|
+
tests pass, and your commit is pushed to the shared branch `{{branch_name}}`. Do NOT
|
|
130
|
+
create a PR — the engine creates one when all plan items are done. Do not continue
|
|
131
|
+
editing adjacent docs that weren't part of the task.
|
|
132
|
+
{{/shared_branch}}
|
|
133
|
+
{{^shared_branch}}
|
|
112
134
|
Your task is complete once the doc accurately reflects current code, the PR is created
|
|
113
135
|
with the changed doc files, and any doc-validation tests pass. Do not continue editing
|
|
114
136
|
adjacent docs that weren't part of the task.
|
|
137
|
+
{{/shared_branch}}
|
|
115
138
|
|
|
116
139
|
## Team Decisions
|
|
117
140
|
{{notes_content}}
|
|
@@ -232,6 +232,12 @@ gh pr review <number> --comment --body-file <verdict.md> --repo OWNER/REPO # f
|
|
|
232
232
|
|
|
233
233
|
The marker is an HTML comment on its own line — GitHub's renderer strips it before display, so reviewers see nothing. The body round-trips verbatim through the API so the engine can match it.
|
|
234
234
|
|
|
235
|
+
**Brand link (MANDATORY).** Whenever the visible body of a PR comment or review names "Minions" — e.g. "Fixed by Minions", "Reviewed by Minions", "Minions rebased this branch" — render that word as a markdown hyperlink to the Minions site:
|
|
236
|
+
```
|
|
237
|
+
[Minions](https://icy-water-0224cc51e.2.azurestaticapps.net/minions)
|
|
238
|
+
```
|
|
239
|
+
So write `Fixed by [Minions](https://icy-water-0224cc51e.2.azurestaticapps.net/minions)`, not a bare `Fixed by Minions`. This applies to GitHub and ADO comments alike. Do **not** link occurrences inside the HTML marker line, code blocks, or file paths — only the human-readable prose mention. Link the first occurrence per comment; subsequent repeats in the same comment may stay plain.
|
|
240
|
+
|
|
235
241
|
## GitHub Tooling and Auth
|
|
236
242
|
|
|
237
243
|
For GitHub repo operations, use GitHub MCP tools or the `gh` CLI. Prefer commands such as `gh pr create`, `gh pr view`, `gh pr comment`, `gh pr review --comment`, `gh issue view`, and `gh run view`. **For comment / review posts, always go through `minions pr comment` or include the marker template above.**
|
package/playbooks/test.md
CHANGED
|
@@ -31,7 +31,12 @@ Builds, tests, dependency installs, and server startups can be silent for severa
|
|
|
31
31
|
|
|
32
32
|
Work from the current project checkout prepared by the engine. Follow the repo's own instructions (`CLAUDE.md`, README, package files, Makefiles, project scripts) and run the smallest sensible set of commands that proves the requested behavior.
|
|
33
33
|
|
|
34
|
+
{{#shared_branch}}
|
|
35
|
+
This task is part of a **shared-branch plan** — other plan items share `{{branch_name}}`. If the task asks you to add or modify files, commit those focused changes and push them to the shared branch (`git push origin {{branch_name}}`), then STOP. Do **NOT** create a PR — the engine opens one aggregate PR automatically when all plan items complete. Creating a PR now would be premature and the early branch would collect merge conflicts from concurrent sibling pushes. For pure build/run/verify tasks, do not push.
|
|
36
|
+
{{/shared_branch}}
|
|
37
|
+
{{^shared_branch}}
|
|
34
38
|
If the task asks you to add or modify files, commit those focused changes, push the branch, and create a PR using the same conventions as implement tasks. For pure build/run/verify tasks, do not push or create a PR.
|
|
39
|
+
{{/shared_branch}}
|
|
35
40
|
|
|
36
41
|
If a build or test fails, report the error clearly instead of fixing it unless the task explicitly asks for a fix.
|
|
37
42
|
|