Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion bin/multiagent-safety.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const CRITICAL_GUARDRAIL_PATHS = new Set([

const LOCK_FILE_RELATIVE = '.omx/state/agent-file-locks.json';
const AGENTS_MARKER_START = '<!-- multiagent-safety:START -->';
const AGENTS_MARKER_END = '<!-- multiagent-safety:END -->';
const GITIGNORE_MARKER_START = '# multiagent-safety:START';
const GITIGNORE_MARKER_END = '# multiagent-safety:END';
const MANAGED_GITIGNORE_PATHS = [
Expand Down Expand Up @@ -608,6 +609,10 @@ function ensurePackageScripts(repoRoot, dryRun) {
function ensureAgentsSnippet(repoRoot, dryRun) {
const agentsPath = path.join(repoRoot, 'AGENTS.md');
const snippet = fs.readFileSync(path.join(TEMPLATE_ROOT, 'AGENTS.multiagent-safety.md'), 'utf8').trimEnd();
const managedRegex = new RegExp(
`${AGENTS_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${AGENTS_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
'm',
);

if (!fs.existsSync(agentsPath)) {
if (!dryRun) {
Expand All @@ -617,8 +622,19 @@ function ensureAgentsSnippet(repoRoot, dryRun) {
}

const existing = fs.readFileSync(agentsPath, 'utf8');
if (managedRegex.test(existing)) {
const next = existing.replace(managedRegex, snippet);
if (next === existing) {
return { status: 'unchanged', file: 'AGENTS.md' };
}
if (!dryRun) {
fs.writeFileSync(agentsPath, next, 'utf8');
}
return { status: 'updated', file: 'AGENTS.md', note: 'refreshed guardex-managed block' };
}

if (existing.includes(AGENTS_MARKER_START)) {
return { status: 'unchanged', file: 'AGENTS.md' };
return { status: 'unchanged', file: 'AGENTS.md', note: 'existing marker found without managed end marker' };
}

const separator = existing.endsWith('\n') ? '\n' : '\n\n';
Expand Down
3 changes: 2 additions & 1 deletion templates/AGENTS.multiagent-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
- If codex-agent auto-finish cannot complete, immediately run `scripts/agent-branch-finish.sh --branch "<agent-branch>" --via-pr --wait-for-merge` and keep the branch open until checks/review pass.
- If merge/rebase conflicts block auto-finish, run a conflict-resolution review pass in that sandbox branch, then rerun `agent-branch-finish.sh --via-pr` until merged.
- Completion is not valid until these are true: commit exists on the agent branch, branch is pushed to `origin`, and PR/merge status is produced by `agent-branch-finish.sh` or `codex-agent`.
- Per-message loop is mandatory: for every new user message/task, start a fresh agent branch/worktree, claim ownership locks, implement and verify, finish via PR/merge cleanup, then repeat for the next message/task.
- For every new task, if an assigned agent sub-branch/worktree is already open, continue in that sub-branch; otherwise create a fresh one from the current local base snapshot with `scripts/agent-branch-start.sh`.
- Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
- If the change publishes or bumps a version, the same change must also update release notes/changelog entries.

1. Explicit ownership before edits
Expand Down
34 changes: 33 additions & 1 deletion test/install.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ test('setup provisions workflow files and repo config', () => {

const agentsContent = fs.readFileSync(path.join(repoDir, 'AGENTS.md'), 'utf8');
assert.equal(agentsContent.includes('<!-- multiagent-safety:START -->'), true);
assert.match(agentsContent, /Per-message loop is mandatory/);
assert.match(agentsContent, /For every new task, if an assigned agent sub-branch\/worktree is already open, continue in that sub-branch/);

const gitignoreContent = fs.readFileSync(path.join(repoDir, '.gitignore'), 'utf8');
assert.match(gitignoreContent, /# multiagent-safety:START/);
Expand All @@ -271,6 +271,38 @@ test('setup provisions workflow files and repo config', () => {
assert.equal(secondRun.status, 0, secondRun.stderr || secondRun.stdout);
});

test('setup refreshes existing managed AGENTS block to latest template policy', () => {
const repoDir = initRepo();
const legacyAgents = `# AGENTS

Project-specific guidance before managed block.

<!-- multiagent-safety:START -->
## Multi-Agent Execution Contract (multiagent-safety)
- legacy managed clause
<!-- multiagent-safety:END -->

Trailing project notes after managed block.
`;
fs.writeFileSync(path.join(repoDir, 'AGENTS.md'), legacyAgents, 'utf8');

const result = runNode(['setup', '--target', repoDir, '--no-global-install'], repoDir);
assert.equal(result.status, 0, result.stderr || result.stdout);

const nextAgents = fs.readFileSync(path.join(repoDir, 'AGENTS.md'), 'utf8');
assert.match(nextAgents, /Project-specific guidance before managed block\./);
assert.match(nextAgents, /Trailing project notes after managed block\./);
assert.match(
nextAgents,
/For every new task, if an assigned agent sub-branch\/worktree is already open, continue in that sub-branch/,
);
assert.match(
nextAgents,
/Never implement directly on the local\/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch\/worktree\./,
);
assert.doesNotMatch(nextAgents, /legacy managed clause/);
});

test('setup auto-adds existing local user branches to protected branches', () => {
const repoDir = initRepo();

Expand Down
Loading