@yemi33/minions 0.1.2288 → 0.1.2290
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/render-plans.js +7 -2
- package/dashboard/js/settings.js +3 -0
- package/dashboard/slim/body.html +4 -11
- package/dashboard/slim/js/modals-tiles.js +66 -1
- package/dashboard/slim/js/status.js +82 -98
- package/dashboard/slim/styles.css +13 -87
- package/dashboard.js +81 -50
- package/docs/deprecated.json +175 -37
- package/docs/live-checkout-mode.md +26 -4
- package/engine/ado.js +11 -2
- package/engine/github.js +7 -0
- package/engine/lifecycle.js +9 -3
- package/engine/live-checkout.js +171 -18
- package/engine/playbook.js +5 -0
- package/engine/queries.js +10 -3
- package/engine/shared.js +104 -5
- package/engine.js +145 -9
- package/package.json +1 -1
- package/playbooks/plan-to-prd.md +2 -1
- package/playbooks/shared-rules.md +1 -0
package/engine.js
CHANGED
|
@@ -2380,6 +2380,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
2380
2380
|
gitOpts: _gitOpts,
|
|
2381
2381
|
dispatchId: id,
|
|
2382
2382
|
wiId: _wiIdForAlert,
|
|
2383
|
+
skipDirtyCheck: !!project.skipLiveCheckoutDirtyCheck,
|
|
2383
2384
|
log: (msg, lvl) => log(lvl || 'info', msg),
|
|
2384
2385
|
});
|
|
2385
2386
|
} catch (liveErr) {
|
|
@@ -5283,6 +5284,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
5283
5284
|
isTerminalFailure: effectiveResult !== DISPATCH_RESULT.SUCCESS,
|
|
5284
5285
|
resultLabel: errorReason || effectiveResult,
|
|
5285
5286
|
gitOpts: _gitOpts,
|
|
5287
|
+
skipDirtyCheck: !!project?.skipLiveCheckoutDirtyCheck,
|
|
5286
5288
|
log,
|
|
5287
5289
|
writeInboxAlert,
|
|
5288
5290
|
});
|
|
@@ -5792,6 +5794,63 @@ function reservePrdFilename(prdDir, baseFileName, content = '') {
|
|
|
5792
5794
|
throw new Error('Could not reserve unique PRD filename after 100 attempts');
|
|
5793
5795
|
}
|
|
5794
5796
|
|
|
5797
|
+
// Fleet branches the engine/CC create. A worktree-mode operator checkout should
|
|
5798
|
+
// NEVER sit on one of these (or any branch); when it does, a flow ran raw
|
|
5799
|
+
// `git checkout`/`merge` in the live checkout instead of a worktree.
|
|
5800
|
+
const _FLEET_BRANCH_RE = /^(cc-pr|backport|e2e|verify|sched-|work)\//;
|
|
5801
|
+
|
|
5802
|
+
// Self-heal: a worktree-mode project's operator checkout (project.localPath) must
|
|
5803
|
+
// always sit on its mainBranch — agents use isolated worktrees, CC Create-PR uses
|
|
5804
|
+
// prepare-create-pr-worktree. But fleet flows (CC raw git, backport, sync)
|
|
5805
|
+
// sometimes run `git checkout <branch>` + `git merge` THERE despite the rules,
|
|
5806
|
+
// leaving it stuck on a feature branch (often mid-conflict) — which restores
|
|
5807
|
+
// git-tracked dead PRDs and pollutes the operator's main, then the materializer
|
|
5808
|
+
// re-runs finished work. Detect + restore. Conservative: only acts on a stuck
|
|
5809
|
+
// merge/rebase OR a recognizably fleet-created branch, so a human's deliberate
|
|
5810
|
+
// `git checkout my-feature` in their checkout is left alone. Branch commits are
|
|
5811
|
+
// preserved on their ref (reset --hard only drops the uncommitted hijack state).
|
|
5812
|
+
function restoreHijackedOperatorCheckouts(config) {
|
|
5813
|
+
if (config?.engine?.autoRestoreOperatorCheckout === false) return;
|
|
5814
|
+
let projects;
|
|
5815
|
+
try { projects = getProjects(config); } catch { return; }
|
|
5816
|
+
for (const project of projects) {
|
|
5817
|
+
try {
|
|
5818
|
+
const localPath = project?.localPath;
|
|
5819
|
+
if (!localPath || !fs.existsSync(localPath)) continue;
|
|
5820
|
+
if (shared.resolveCheckoutMode(project) === shared.CHECKOUT_MODES.LIVE) continue; // live-mode: feature branch is intentional
|
|
5821
|
+
const main = project.mainBranch || 'main';
|
|
5822
|
+
if (String(execSilent('git rev-parse --is-inside-work-tree', { cwd: localPath }) || '').trim() !== 'true') continue;
|
|
5823
|
+
const cur = String(execSilent('git rev-parse --abbrev-ref HEAD', { cwd: localPath }) || '').trim();
|
|
5824
|
+
if (!cur || cur === 'HEAD' || cur === main) continue; // on main or detached — fine
|
|
5825
|
+
const gitDir = String(execSilent('git rev-parse --git-dir', { cwd: localPath }) || '').trim();
|
|
5826
|
+
const absGitDir = gitDir ? (path.isAbsolute(gitDir) ? gitDir : path.join(localPath, gitDir)) : path.join(localPath, '.git');
|
|
5827
|
+
const mergeStuck = fs.existsSync(path.join(absGitDir, 'MERGE_HEAD'));
|
|
5828
|
+
const rebaseStuck = fs.existsSync(path.join(absGitDir, 'rebase-merge')) || fs.existsSync(path.join(absGitDir, 'rebase-apply'));
|
|
5829
|
+
if (!mergeStuck && !rebaseStuck && !_FLEET_BRANCH_RE.test(cur)) continue; // likely a human's deliberate checkout — leave it
|
|
5830
|
+
const why = mergeStuck ? ' (stuck merge)' : rebaseStuck ? ' (stuck rebase)' : ' (fleet branch in live checkout)';
|
|
5831
|
+
log('warn', `Operator checkout for ${project.name} is on '${cur}'${why}; worktree-mode requires ${main} — restoring`);
|
|
5832
|
+
const opts = { cwd: localPath };
|
|
5833
|
+
if (mergeStuck) execSilent('git merge --abort', opts);
|
|
5834
|
+
if (rebaseStuck) execSilent('git rebase --abort', opts);
|
|
5835
|
+
execSilent('git reset --hard HEAD', opts); // drop uncommitted hijack state; branch commits stay on the ref
|
|
5836
|
+
try { shared.validateGitRef(main); } catch (ve) { throw new Error(`restoreHijackedOperatorCheckouts: invalid mainBranch ${JSON.stringify(main)}: ${ve.message}`); }
|
|
5837
|
+
shared.shellSafeGitSync(['checkout', main], opts);
|
|
5838
|
+
const now = String(execSilent('git rev-parse --abbrev-ref HEAD', { cwd: localPath }) || '').trim();
|
|
5839
|
+
if (now === main) {
|
|
5840
|
+
try {
|
|
5841
|
+
writeInboxAlert(`operator-checkout-restored-${project.name}`,
|
|
5842
|
+
`Operator checkout ${localPath} was hijacked onto '${cur}'${why} and restored to ${main}. ` +
|
|
5843
|
+
'A fleet flow ran git checkout/merge in the LIVE operator checkout instead of an isolated worktree ' +
|
|
5844
|
+
'(playbooks/shared-rules.md: "Do NOT checkout branches in the main working tree — use worktrees"). ' +
|
|
5845
|
+
`The '${cur}' branch commits are preserved on its ref.`);
|
|
5846
|
+
} catch { /* alert is best-effort */ }
|
|
5847
|
+
} else {
|
|
5848
|
+
log('warn', `Operator checkout restore for ${project.name} did not reach ${main} (now '${now}') — manual intervention needed`);
|
|
5849
|
+
}
|
|
5850
|
+
} catch (e) { log('warn', `restoreHijackedOperatorCheckouts ${project?.name || '?'}: ${e?.message || e}`); }
|
|
5851
|
+
}
|
|
5852
|
+
}
|
|
5853
|
+
|
|
5795
5854
|
function materializePlansAsWorkItems(config) {
|
|
5796
5855
|
if (!fs.existsSync(PRD_DIR)) { try { fs.mkdirSync(PRD_DIR, { recursive: true }); } catch (e) { log('warn', 'create PRD directory: ' + e.message); } }
|
|
5797
5856
|
const writePrdLocked = (fileName, data) => {
|
|
@@ -6027,6 +6086,19 @@ function materializePlansAsWorkItems(config) {
|
|
|
6027
6086
|
// (W-mouptdh1000h9f39).
|
|
6028
6087
|
let plan = safeJsonNoRestore(path.join(PRD_DIR, file));
|
|
6029
6088
|
if (!plan?.missing_features) continue;
|
|
6089
|
+
// Resurrection-re-execution guard ("old PRDs came back"): never (re)materialize
|
|
6090
|
+
// a defunct PRD — archived, or completed with its source plan gone from plans/.
|
|
6091
|
+
// Such a PRD can linger in prd/ with stale `missing`-status items; without
|
|
6092
|
+
// this, the materializer re-creates and re-runs already-finished work (it only
|
|
6093
|
+
// filters per-item by PRD_MATERIALIZABLE, which a stale 'missing' item passes).
|
|
6094
|
+
if (shared.isDefunctPrd(plan, PLANS_DIR)) {
|
|
6095
|
+
if (!_defunctPrdSkipLogged.has(file)) {
|
|
6096
|
+
log('info', `Skipping defunct PRD ${file} (archived or completed with archived/removed source plan) — not re-materializing`);
|
|
6097
|
+
_defunctPrdSkipLogged.add(file);
|
|
6098
|
+
}
|
|
6099
|
+
continue;
|
|
6100
|
+
}
|
|
6101
|
+
_defunctPrdSkipLogged.delete(file);
|
|
6030
6102
|
({ fileName: file, plan } = enforceDeclaredPlanProject(file, plan));
|
|
6031
6103
|
|
|
6032
6104
|
// ID collision prevention: remap sequential IDs (P-001, P-002) to globally unique P-<uid> IDs.
|
|
@@ -8833,7 +8905,7 @@ function discoverCentralWorkItems(config) {
|
|
|
8833
8905
|
const prdFiles = safeReadDir(PRD_DIR).filter(f => f.endsWith('.json'));
|
|
8834
8906
|
for (const pf of prdFiles) {
|
|
8835
8907
|
const prd = safeJson(path.join(PRD_DIR, pf));
|
|
8836
|
-
if (planKey && prd &&
|
|
8908
|
+
if (planKey && prd && !shared.isPrdArchived(prd) && shared.prdMatchesSourcePlan(prd.source_plan, planKey)) {
|
|
8837
8909
|
prdFilename = pf;
|
|
8838
8910
|
try { vars.existing_prd_json = fs.readFileSync(path.join(PRD_DIR, pf), 'utf8'); } catch (_) { /* ignore */ }
|
|
8839
8911
|
log('info', `plan-to-prd: reusing existing PRD "${pf}" for plan "${item.planFile}" (#884)`);
|
|
@@ -9129,21 +9201,25 @@ async function discoverWork(config) {
|
|
|
9129
9201
|
// (W-mouptdh1000h9f39). Idempotent and fast — readdir + existsSync.
|
|
9130
9202
|
sweepStaleArchivedPrdBackups(prdDir, prdArchiveDir);
|
|
9131
9203
|
|
|
9132
|
-
// Pass 2 —
|
|
9204
|
+
// Pass 2 — completion sweep. (The ghost-PRD purge that used to live here
|
|
9205
|
+
// was a REDUNDANT + UNSAFE duplicate of the plan-completion scan's
|
|
9206
|
+
// version: it unlinked prd/<f> whenever prd/archive/<f> existed, with NO
|
|
9207
|
+
// `_isGhostPrdRestore` identity check — the exact footgun #7 (#496)
|
|
9208
|
+
// hazard of deleting a legitimately re-opened PRD that shares an archived
|
|
9209
|
+
// basename. The plan-completion scan does the same purge safely (identity-
|
|
9210
|
+
// checked + neutralize); here the isDefunctPrd guard below already skips
|
|
9211
|
+
// archived/defunct PRDs, so this loop never re-runs completion on a ghost.)
|
|
9133
9212
|
for (const f of fs.readdirSync(prdDir).filter(f => f.endsWith('.json'))) {
|
|
9134
9213
|
if (completedPlanCache.has(f)) continue;
|
|
9135
|
-
if (fs.existsSync(path.join(prdArchiveDir, f))) {
|
|
9136
|
-
// Orphaned backup restore — plan is already archived. Purge the ghost copy.
|
|
9137
|
-
try { fs.unlinkSync(path.join(prdDir, f)); } catch { }
|
|
9138
|
-
try { fs.unlinkSync(path.join(prdDir, f + '.backup')); } catch { }
|
|
9139
|
-
completedPlanCache.add(f);
|
|
9140
|
-
continue;
|
|
9141
|
-
}
|
|
9142
9214
|
// safeJsonNoRestore — defense in depth: if the file vanished between
|
|
9143
9215
|
// readdir and read (e.g. concurrent archive), do not resurrect it
|
|
9144
9216
|
// from a stale .backup sidecar (W-mouptdh1000h9f39).
|
|
9145
9217
|
const plan = safeJsonNoRestore(path.join(prdDir, f));
|
|
9146
9218
|
if (!plan?.missing_features) continue;
|
|
9219
|
+
// Resurrection guard (mirror of completion-scan-B / #523): never re-run
|
|
9220
|
+
// completion (which re-creates the verify gate) for a defunct PRD —
|
|
9221
|
+
// archived, or completed with its plan gone from plans/.
|
|
9222
|
+
if (shared.isDefunctPrd(plan, PLANS_DIR)) { completedPlanCache.add(f); continue; }
|
|
9147
9223
|
// A completed PRD may still be missing its aggregate verify WI — e.g. it
|
|
9148
9224
|
// landed on disk pre-completed via the plan-to-prd / pipeline path, so no
|
|
9149
9225
|
// agent-completion event ever drove checkPlanCompletion (W-mqk5ld3p). The
|
|
@@ -9335,6 +9411,9 @@ let tickCount = 0;
|
|
|
9335
9411
|
// In-memory cache of plan filenames confirmed completed — avoids redundant
|
|
9336
9412
|
// checkPlanCompletion calls. Cleared automatically on engine restart.
|
|
9337
9413
|
const completedPlanCache = new Set();
|
|
9414
|
+
// Dedupe the "Skipping defunct PRD" materializer log so a lingering defunct PRD
|
|
9415
|
+
// doesn't spam one line per discovery tick; cleared when the file stops being defunct.
|
|
9416
|
+
const _defunctPrdSkipLogged = new Set();
|
|
9338
9417
|
// Filenames where a live PRD shares a basename with an archived PRD but diverges
|
|
9339
9418
|
// (different source_plan / introduces new work). We refuse to auto-purge those
|
|
9340
9419
|
// and warn once instead of every tick. (RC4 — footgun #7 collision protection.)
|
|
@@ -9795,6 +9874,12 @@ async function tickInner() {
|
|
|
9795
9874
|
}
|
|
9796
9875
|
}
|
|
9797
9876
|
const plan = safeJson(path.join(PRD_DIR, file));
|
|
9877
|
+
// Resurrection-re-execution guard: a defunct PRD (archived, or completed
|
|
9878
|
+
// with its source plan gone from plans/) must NOT have its verify gate
|
|
9879
|
+
// re-created. This scan deliberately re-runs completion for completed
|
|
9880
|
+
// PRDs (to build the aggregate verify task), which is exactly how a
|
|
9881
|
+
// resurrected/lingering completed PRD spawned spurious verify WIs.
|
|
9882
|
+
if (shared.isDefunctPrd(plan, PLANS_DIR)) { completedPlanCache.add(file); continue; }
|
|
9798
9883
|
if (plan && plan.missing_features) {
|
|
9799
9884
|
// Run for completed PRDs too — a pre-completed plan (flag already set,
|
|
9800
9885
|
// items pre-marked done) still needs its aggregate verify gate created.
|
|
@@ -10413,6 +10498,9 @@ async function tickInner() {
|
|
|
10413
10498
|
const maxC = resolveMaxConcurrent(config);
|
|
10414
10499
|
setTempBudget(Math.max(0, maxC - activeCountPre));
|
|
10415
10500
|
}
|
|
10501
|
+
// Self-heal a hijacked operator checkout BEFORE discovery/materialization, so a
|
|
10502
|
+
// dead PRD restored by a stray `git checkout` can't be re-materialized this tick.
|
|
10503
|
+
try { restoreHijackedOperatorCheckouts(config); } catch (e) { log('warn', 'restoreHijackedOperatorCheckouts: ' + e.message); }
|
|
10416
10504
|
let discoveryOk = true;
|
|
10417
10505
|
try { await withTickTimeout(() => discoverWork(config), 'discoverWork', tickOpTimeoutMs); } catch (e) { log('warn', 'discoverWork: ' + e.message); discoveryOk = false; }
|
|
10418
10506
|
if (_isTickStale(myGeneration)) return;
|
|
@@ -10514,6 +10602,7 @@ module.exports = {
|
|
|
10514
10602
|
materializePlansAsWorkItems,
|
|
10515
10603
|
materializeSpecsAsWorkItems, // exported for testing (P-f7-git-log)
|
|
10516
10604
|
reservePrdFilename, // exported for testing (P-9b7e5d3c)
|
|
10605
|
+
restoreHijackedOperatorCheckouts, // exported for testing — operator-checkout self-heal
|
|
10517
10606
|
sweepStaleArchivedPrdBackups, // exported for testing
|
|
10518
10607
|
_isGhostPrdRestore, // exported for testing (RC4 — ghost-purge identity check)
|
|
10519
10608
|
|
|
@@ -10577,7 +10666,54 @@ module.exports = {
|
|
|
10577
10666
|
|
|
10578
10667
|
// ─── Entrypoint ─────────────────────────────────────────────────────────────
|
|
10579
10668
|
|
|
10669
|
+
// Source-level defense against agents committing prd/*.json + plans/*.md
|
|
10670
|
+
// (gitignored engine work-plan state) as if they were deliverables — a tracked
|
|
10671
|
+
// PRD resurrects + re-executes on the next checkout ("old PRDs came back"). The
|
|
10672
|
+
// hook is GENERATED at runtime into the repo's git hooks dir (NOT committed —
|
|
10673
|
+
// opg push-protection blocks committed hooks, and committing one would itself be
|
|
10674
|
+
// the kind of supply-chain footgun we're guarding against). The git common hooks
|
|
10675
|
+
// dir is shared across all worktrees, so the operator checkout AND every agent
|
|
10676
|
+
// worktree are covered. Idempotent; never clobbers a foreign pre-commit hook.
|
|
10677
|
+
const PRD_PRECOMMIT_HOOK = [
|
|
10678
|
+
'#!/bin/sh',
|
|
10679
|
+
'# Minions guard (engine-generated; not committed). prd/*.json + plans/*.md are',
|
|
10680
|
+
'# gitignored runtime state and must NEVER be committed — a tracked PRD/plan is',
|
|
10681
|
+
'# resurrected by git checkout/reset/merge and re-runs finished work. See',
|
|
10682
|
+
'# engine.js ensureGitHooksInstalled. Bypass (rarely): git commit --no-verify.',
|
|
10683
|
+
"blocked=$(git diff --cached --name-only --diff-filter=AM | grep -E '^prd/.*\\.json$|^plans/.*\\.md$' || true)",
|
|
10684
|
+
'if [ -n "$blocked" ]; then',
|
|
10685
|
+
' echo "" 1>&2',
|
|
10686
|
+
' echo " Refusing to commit gitignored Minions runtime state (prd/ + plans/ guard):" 1>&2',
|
|
10687
|
+
' echo "$blocked" | sed \'s/^/ /\' 1>&2',
|
|
10688
|
+
' echo " Internal work-plan artifacts, not deliverables. Unstage: git reset HEAD -- prd/ plans/" 1>&2',
|
|
10689
|
+
' echo " (genuinely intentional? bypass with: git commit --no-verify)" 1>&2',
|
|
10690
|
+
' exit 1',
|
|
10691
|
+
'fi',
|
|
10692
|
+
'exit 0',
|
|
10693
|
+
'',
|
|
10694
|
+
].join('\n');
|
|
10695
|
+
|
|
10696
|
+
function ensureGitHooksInstalled() {
|
|
10697
|
+
try {
|
|
10698
|
+
const inWorkTree = String(shared.execSilent('git rev-parse --is-inside-work-tree', { cwd: MINIONS_DIR }) || '').trim();
|
|
10699
|
+
if (inWorkTree !== 'true') return;
|
|
10700
|
+
const hooksRel = String(shared.execSilent('git rev-parse --git-path hooks', { cwd: MINIONS_DIR }) || '').trim();
|
|
10701
|
+
if (!hooksRel) return;
|
|
10702
|
+
const hooksDir = path.isAbsolute(hooksRel) ? hooksRel : path.join(MINIONS_DIR, hooksRel);
|
|
10703
|
+
const hookFile = path.join(hooksDir, 'pre-commit');
|
|
10704
|
+
let existing = null;
|
|
10705
|
+
try { existing = fs.readFileSync(hookFile, 'utf8'); } catch { /* absent */ }
|
|
10706
|
+
if (existing === PRD_PRECOMMIT_HOOK) return; // already current
|
|
10707
|
+
if (existing && !existing.includes('Minions guard')) return; // respect a foreign hook
|
|
10708
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
10709
|
+
fs.writeFileSync(hookFile, PRD_PRECOMMIT_HOOK);
|
|
10710
|
+
try { fs.chmodSync(hookFile, 0o755); } catch { /* non-posix */ }
|
|
10711
|
+
log('info', `Installed git pre-commit guard (${hookFile}) — blocks committing prd/*.json + plans/*.md`);
|
|
10712
|
+
} catch { /* best-effort */ }
|
|
10713
|
+
}
|
|
10714
|
+
|
|
10580
10715
|
if (require.main === module) {
|
|
10716
|
+
try { ensureGitHooksInstalled(); } catch { /* best-effort */ }
|
|
10581
10717
|
// Every copilot this process spawns — agent dispatches AND internal
|
|
10582
10718
|
// `llm.callLLM` calls (consolidation, classification, meetings, …) — inherits
|
|
10583
10719
|
// this process's COPILOT_HOME, so point it at the seeded, MCP-filtered home.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2290",
|
|
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
|
@@ -191,10 +191,11 @@ If the task description contains `mode: diff-aware-update`, you are updating an
|
|
|
191
191
|
- Write ONLY the single `.json` PRD file to `{{team_root}}/prd/` — do NOT write any `.md` files there
|
|
192
192
|
- Do NOT create a git branch, worktree, or PR — this playbook writes minions-internal state only
|
|
193
193
|
- Do NOT modify any files in the project repo
|
|
194
|
+
- **NEVER run `git add prd/` or `git add -f prd/` — PRD files are gitignored runtime artifacts (`prd/*.json` is in `.gitignore`). Force-adding them exposes internal work plans in the public repo diff. A CI guard (`test/unit/prd-not-git-tracked.test.js`) will fail the PR if you do this, and you will be required to run `git rm --cached prd/<file>.json` to undo it.**
|
|
194
195
|
- The engine will dispatch implementation agents automatically once the JSON file exists
|
|
195
196
|
- For `shared-branch`: agents commit to a single branch — one PR is created automatically when all items are done
|
|
196
197
|
- For `parallel`: each agent creates its own branch and PR
|
|
197
198
|
|
|
198
199
|
## When to Stop
|
|
199
200
|
|
|
200
|
-
Your task is complete once you have written the PRD JSON file to `{{team_root}}/prd/{{prd_filename}}`. The engine will detect the file and begin dispatching work. Do NOT create branches, PRs, or modify project code. Stop after writing the JSON file.
|
|
201
|
+
Your task is complete once you have written the PRD JSON file to `{{team_root}}/prd/{{prd_filename}}`. The engine will detect the file and begin dispatching work. Do NOT run any git commands. Do NOT create branches, PRs, or modify project code. Stop after writing the JSON file.
|
|
@@ -86,6 +86,7 @@ This task is part of a **shared-branch plan** — every plan item shares the bra
|
|
|
86
86
|
- Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
|
|
87
87
|
- Do NOT remove worktrees — the engine handles cleanup automatically.
|
|
88
88
|
- Do NOT checkout branches in the main working tree — use worktrees or `git diff`/`git show`.
|
|
89
|
+
- **NEVER run `git checkout <branch>` / `git switch` / `git merge` / `git rebase` / `git cherry-pick` in a project's operator checkout (`project.localPath`, e.g. the configured repo root).** That tree must stay on its main branch. For ANY branch operation — backport, sync, cross-branch merge, "create a PR off branch X" — create an isolated worktree first: `git -C <localPath> worktree add <scratch-path> <base>`, do all checkout/merge/commit/push **inside that worktree**, then `git -C <localPath> worktree remove <scratch-path>`. Running these in the live checkout pollutes the operator's main, leaves stuck-merge conflicts, and resurrects gitignored runtime state (the "old PRDs came back" incident). The engine auto-restores a hijacked operator checkout and files an inbox alert, but do not rely on that — use a worktree.
|
|
89
90
|
- Treat `notes/inbox/` writes as success artifacts only. If the task fails, is blocked, is cancelled, or ends partial, do **not** create an inbox note; report the failure in your final response, completion block, PR/work-item comment, or other task-specific failure channel instead.
|
|
90
91
|
- Read `notes.md` for team rules and decisions before starting.
|
|
91
92
|
- **Check team memory first, then look outside.** Before researching from scratch, check what the team already knows — in this order:
|