diff --git a/.gitignore b/.gitignore index 2ec724c..97c5171 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ dist/ .automaker/features/ .automaker/checkpoints/ .automaker/trajectory/ + +# auto-mode runtime lock — never commit (tracking it conflicts every concurrent PR) +.automaker-lock diff --git a/lib/workspace-config.mjs b/lib/workspace-config.mjs index e1aaca6..84c513a 100644 --- a/lib/workspace-config.mjs +++ b/lib/workspace-config.mjs @@ -166,6 +166,27 @@ export const WORKSPACE_STANDARD = /** @type {Rule[]} */ ([ ok: (m) => !m.hasFile('.beads/beads.db'), fix: 'git rm --cached .beads/beads.db and add it to .gitignore', }, + { + id: 'automaker-lock-gitignored', + description: '.automaker-lock is gitignored (auto-mode runtime lock — must never be committed)', + severity: 'error', + ok: (m) => resolveIgnored(m, '.automaker-lock', ['.automaker-lock']), + fix: 'Add `.automaker-lock` to .gitignore', + }, + { + // The headline rule: a *tracked* .automaker-lock is what bites. auto-mode + // rewrites this lock on every run, so once it's committed every concurrent + // auto-mode PR mutates it and they all collide on it — the whole branch's + // PRs go CONFLICTING/DIRTY the moment one merges (observed fleet-wide: + // protoContent, protoMaker #4109/#4120). Gitignoring alone does NOT fix an + // already-tracked file — it must be untracked. + id: 'automaker-lock-not-committed', + description: + '.automaker-lock is NOT committed (a tracked runtime lock makes every concurrent auto-mode PR conflict on it)', + severity: 'error', + ok: (m) => !m.hasFile('.automaker-lock'), + fix: 'git rm --cached .automaker-lock and add it to .gitignore', + }, { id: 'automaker-settings-committed', description: '.automaker/settings.json is committed (versioned per-repo agent baseline)', @@ -322,6 +343,7 @@ export const REQUIRED_GITIGNORE = [ '.automaker/features/', '.automaker/checkpoints/', '.automaker/trajectory/', + '.automaker-lock', ]; /** diff --git a/test/workspace-config.test.mjs b/test/workspace-config.test.mjs index 94f1936..97d297c 100644 --- a/test/workspace-config.test.mjs +++ b/test/workspace-config.test.mjs @@ -33,6 +33,7 @@ function goodManifest() { '.automaker/features/', '.automaker/checkpoints/', '.automaker/trajectory/', + '.automaker-lock', ].join('\n') ); } @@ -134,7 +135,7 @@ test('missing automaker transient ignores is a WARN, not an error', () => { // Everything required present + ignored, but no transient-dir ignores. const m = manifest( ['.beads/issues.jsonl', '.automaker/settings.json'], - '.beads/beads.db\n.worktrees/' + '.beads/beads.db\n.worktrees/\n.automaker-lock' ); const r = evaluateStandard(m); assert.equal(r.ok, true, 'warn-only violation should not fail the standard'); @@ -151,13 +152,15 @@ test('missing automaker transient ignores is a WARN, not an error', () => { test('a bare repo (nothing) reports all errors + the warn', () => { const r = evaluateStandard(manifest([], '')); - // 4 errors: issues.jsonl, beads-db-gitignored, automaker-settings, worktrees - // (beads-db-not-committed passes — it's absent — so it is NOT a violation) - assert.equal(r.errorCount, 4); + // 5 errors: issues.jsonl, beads-db-gitignored, automaker-settings, worktrees, + // automaker-lock-gitignored. (beads-db-not-committed AND automaker-lock-not-committed + // pass — both absent — so they are NOT violations.) + assert.equal(r.errorCount, 5); // 2 warns: automaker-transient-gitignored + workflow-security-lint assert.equal(r.warnCount, 2); assert.equal(r.ok, false); assert.ok(r.passed.includes('beads-db-not-committed')); + assert.ok(r.passed.includes('automaker-lock-not-committed')); }); test('each rule carries a fix hint', () => {