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
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ settings.
| Source | Destination | How to apply |
|---|---|---|
| [config.toml](codex/config.toml) | `~/.codex/config.toml` | Merge the listed keys into the existing file and `[agents]` table |
| [global-instructions.md](codex/global-instructions.md) | `~/.codex/AGENTS.md` | Add this section to the existing instructions |
| [global-instructions.md](codex/global-instructions.md) | `~/.codex/AGENTS.md` | Merge these shared instructions into the existing file |
| [optional-instructions.md](codex/optional-instructions.md) | `~/.codex/AGENTS.md` | Merge only the sections you want to enable |
| [optional.rules](codex/optional.rules) | `~/.codex/rules/default.rules` | Replace its path placeholder and merge only the rules you want to enable |
| [junior.toml](codex/agents/junior.toml) | `~/.codex/agents/junior.toml` | Copy this rank file |
| [engineer.toml](codex/agents/engineer.toml) | `~/.codex/agents/engineer.toml` | Copy this rank file |
| [senior.toml](codex/agents/senior.toml) | `~/.codex/agents/senior.toml` | Copy this rank file |
Expand All @@ -46,29 +48,28 @@ settings.
To apply these defaults, back up the local files, merge or copy only these parts, and
check that the TOML parses. Keep unrelated values and existing instructions.
If `~/.codex/AGENTS.override.md` exists, Codex reads it instead of `AGENTS.md`;
merge the section into that active file, or deliberately retire the override
before using `AGENTS.md`.
merge these shared instructions into that active file, or deliberately retire
the override before using `AGENTS.md`.
If a destination role already exists, review its differences before replacing
it.
No install script or whole-config symlink is needed for this first version.

The global instructions expect [orchestration](skills/orchestration/SKILL.md),
[goal-swarm](skills/goal-swarm/SKILL.md), and
[review-fix-loop](skills/review-fix-loop/SKILL.md), plus their referenced skills,
to be installed from this repo. The
[routing reference](skills/orchestration/references/agent-routing.md) explains role
Orchestration and review use the
[routing reference](skills/orchestration/references/agent-routing.md) for role
selection and the host's spawn rules. Model and effort values live in config
and agent files; the skills own delegation, review coverage, and rounds.

These are defaults for local tasks using the same Codex home. Project config,
explicit model choices, and custom roles can override them. Defining roles makes
them available; the global and skill instructions request their use. Start a
them available; the skills request their use. Start a
new task after applying the files and check the effective settings. Existing
tasks may retain their selections. Keep any installed links on a stable clone,
not a temporary worktree.

Sample checks after installation:

- Every nonempty review scope also receives a separate Gemini review. Native
reviewer counts below do not include Gemini.
- A small behavior change uses one reviewer; substantial changes, multiple
behaviors, shared contracts, risky logic, or work from several agents use two.
Each reviewer reads the whole diff in every round.
Expand Down
32 changes: 17 additions & 15 deletions codex/global-instructions.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
## Delegation and review
## Doing the work

Use `/orchestration` when deciding whether to delegate or coordinating workers.
For independent review, `/review-fix-loop` reads orchestration’s routing reference
directly and owns reviewer dispatch.
When the user explicitly requests goal-backed parallel work, use `/goal-swarm`.
Create goals only when the user explicitly requests them.
- I value clean, maintainable code and modern coding practices. Consult official documentation when needed.
- Infer the outcome I want from the request, conversation, and project context. Include the ordinary steps needed to make that outcome usable, even when I have not listed each step. Keep this within the requested scope.
- Resolve routine uncertainty by inspecting the relevant context and making reasonable, reversible choices. Ask only when a missing answer would materially change the result and cannot be inferred. Continue independent work while waiting.
- Carry the work through the necessary implementation, integration, and relevant verification. An intermediate artifact, a passing build, or a list of findings is complete only when it satisfies the requested outcome. Keep explanations concise without shortening the work.
- In performance work, measure the actual bottleneck before changing it. Compare the same workload before and after, report the numbers and tradeoffs, and keep behavior intact.

Use `/review-fix-loop` before handing off code changes that affect behavior,
including work by multiple agents, and docs that change agent behavior, such
as skill procedures or global instructions. It owns reviewer count, full-diff
coverage, and fresh rounds. For ordinary docs, comments, or formatting alone,
the parent reviews the change and runs relevant checks; an explicit review
request still invokes the skill.
## Verification

Follow orchestration’s routing reference for worker and reviewer selection, including when
the host requires explicit model settings. Keep model and effort values in
config and agent files.
- Verify the result I’ll actually use, and be clear about anything you haven’t tested.
- Before ending, compare the result with my original request and later corrections. If my likely next message would ask for an obvious missing step within the authorized scope, complete that step now. If something remains blocked, state exactly what is unfinished and what prevents completion.

## Writing rules

- Write docs, PR text, and messages in short, direct, active sentences. Use everyday words where they stay exact. Cut filler and stock phrases. Keep code and technical terms exact.

## GitHub CLI

Run `gh` outside the sandbox on the first try. Its token lives in macOS Keychain, so a sandboxed check can falsely report an invalid token. Use the saved command rules for approval, and keep the token out of config files and environment variables.
11 changes: 11 additions & 0 deletions codex/optional-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Optional Codex instructions

These sections are inactive. Merge a section into the active global instruction
file only when you want its behavior.

Replace `/absolute/path/to/SreeStack` in this file and `optional.rules` with the
path to the clone you keep outside temporary worktrees.

## New Codex worktree preflight

Before reading or editing project files in a new Codex-managed worktree based on local `main`, run the optional preflight command `node /absolute/path/to/SreeStack/codex/scripts/worktree-start.mjs` outside the sandbox. It fetches `origin` and moves the detached worktree to `origin/main` only when safe. Treat a result with `"continue": false` as a hard stop: leave the worktree unchanged and tell the user why. Skip this preflight when the user chose another base or resumed an existing worktree.
12 changes: 12 additions & 0 deletions codex/optional.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Replace /absolute/path/to/SreeStack before merging this block into an active rules file.
prefix_rule(
pattern=["node", "/absolute/path/to/SreeStack/codex/scripts/worktree-start.mjs"],
decision="allow",
justification="Sync a new Codex worktree to origin/main before work starts.",
match=[
["node", "/absolute/path/to/SreeStack/codex/scripts/worktree-start.mjs"],
],
not_match=[
["node", "/absolute/path/to/SreeStack/codex/scripts/other.mjs"],
],
)
231 changes: 231 additions & 0 deletions codex/scripts/worktree-start.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#!/usr/bin/env node

import path from 'node:path';
import { spawnSync } from 'node:child_process';

const BASE_BRANCH = 'main';
const REMOTE = 'origin';
const HOOK_EVENT_NAME = 'SessionStart';

function readStdin() {
return new Promise((resolve) => {
if (process.stdin.isTTY) {
resolve('');
return;
}

let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
input += chunk;
});
process.stdin.on('end', () => resolve(input));
process.stdin.on('error', () => resolve(''));
});
}

function parseInput(text) {
if (!text.trim()) {
return {};
}

try {
return JSON.parse(text);
} catch {
return {};
}
}

function git(cwd, args) {
const result = spawnSync('git', args, {
cwd,
encoding: 'utf8',
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 30_000,
});

return {
ok: !result.error && result.status === 0,
status: result.status ?? 1,
stdout: (result.stdout ?? '').trim(),
stderr: (result.stderr ?? result.error?.message ?? '').trim(),
};
}

function commit(cwd, ref) {
const result = git(cwd, ['rev-parse', '--verify', `${ref}^{commit}`]);
return result.ok ? result.stdout : null;
}

function branch(cwd) {
const result = git(cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
return result.ok ? result.stdout : null;
}

function isAncestor(cwd, ancestor, descendant) {
return git(cwd, ['merge-base', '--is-ancestor', ancestor, descendant]).status === 0;
}

function short(value) {
return value?.slice(0, 12) ?? 'unknown';
}

function oneLine(value) {
return value.replace(/\s+/g, ' ').trim().slice(0, 500);
}

function output({ continueSession, message, context }) {
const result = {
continue: continueSession,
hookSpecificOutput: {
hookEventName: HOOK_EVENT_NAME,
},
};

if (!continueSession) {
result.stopReason = message;
}
if (message) {
result.systemMessage = message;
}
if (context) {
result.hookSpecificOutput.additionalContext = context;
}

console.log(JSON.stringify(result));
}

function allow(context) {
output({ continueSession: true, context });
}

function block(message) {
output({ continueSession: false, message, context: message });
}

function isPrimaryCheckout(root) {
const gitDir = git(root, ['rev-parse', '--absolute-git-dir']);
const commonDir = git(root, ['rev-parse', '--git-common-dir']);
if (!gitDir.ok || !commonDir.ok) {
throw new Error('Could not find the Git worktree paths.');
}

return path.resolve(gitDir.stdout) === path.resolve(root, commonDir.stdout);
}

async function main() {
const input = parseInput(await readStdin());
if (input.source && input.source !== 'startup') {
return;
}

const cwd = input.cwd || process.cwd();
const rootResult = git(cwd, ['rev-parse', '--show-toplevel']);
if (!rootResult.ok) {
return;
}

const root = rootResult.stdout;
const currentBranch = branch(root);
const primary = isPrimaryCheckout(root);

if (primary) {
if (currentBranch === BASE_BRANCH) {
block(
'Codex opened the primary checkout on main. Start this task in Worktree mode so the primary checkout stays clean.',
);
}
return;
}

if (currentBranch) {
return;
}

const initialHead = commit(root, 'HEAD');
const localMain = commit(root, `refs/heads/${BASE_BRANCH}`);
if (!initialHead || !localMain || initialHead !== localMain) {
return;
}

const trackedState = git(root, ['status', '--porcelain', '--untracked-files=no']);
if (!trackedState.ok) {
block('Could not check the new worktree for tracked changes. The worktree was left unchanged.');
return;
}
if (trackedState.stdout) {
block('The new main-based worktree has tracked or staged changes. The worktree was left unchanged.');
return;
}

const origin = git(root, ['remote', 'get-url', REMOTE]);
if (!origin.ok) {
allow('This repository has no origin remote. The new worktree uses local main.');
return;
}

const fetch = git(root, [
'fetch',
'--quiet',
'--no-prune',
'--no-tags',
'--no-recurse-submodules',
REMOTE,
`+refs/heads/${BASE_BRANCH}:refs/remotes/${REMOTE}/${BASE_BRANCH}`,
]);
if (!fetch.ok) {
const detail = oneLine(fetch.stderr || fetch.stdout || 'unknown Git error');
block(`Could not fetch origin/main: ${detail}. The worktree was left unchanged.`);
return;
}

const remoteMain = commit(root, `refs/remotes/${REMOTE}/${BASE_BRANCH}`);
if (!remoteMain) {
block('The origin remote has no origin/main ref. The worktree was left unchanged.');
return;
}

if (initialHead === remoteMain) {
allow(`The new worktree already matches origin/main at ${short(remoteMain)}.`);
return;
}

if (isAncestor(root, initialHead, remoteMain)) {
const move = git(root, [
'-c',
'core.hooksPath=/dev/null',
'switch',
'--detach',
'--quiet',
'--no-overwrite-ignore',
'--no-recurse-submodules',
remoteMain,
]);
if (!move.ok) {
const detail = oneLine(move.stderr || move.stdout || 'unknown Git error');
block(`Could not move the new worktree to origin/main: ${detail}.`);
return;
}

allow(
`Moved the new worktree from local main ${short(initialHead)} to origin/main ${short(remoteMain)}.`,
);
return;
}

if (isAncestor(root, remoteMain, initialHead)) {
block(
`Local main ${short(initialHead)} is ahead of origin/main ${short(remoteMain)}. The worktree was left unchanged. Push or reconcile main, then start a new worktree.`,
);
return;
}

block(
`Local main ${short(initialHead)} and origin/main ${short(remoteMain)} have split. The worktree was left unchanged. Reconcile main, then start a new worktree.`,
);
}

main().catch((error) => {
block(`The worktree preflight failed: ${oneLine(error.message)}.`);
});
Loading
Loading