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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ the compact layout everywhere.
| --- | --- |
| `AGENTS.md` **with** markers | Refreshes **only** the managed block. |
| `AGENTS.md` **without** markers | Appends the managed block to the end. |
| No `AGENTS.md` | Creates it with the managed block. |
| No `AGENTS.md` | Creates it with the managed block, then links `CLAUDE.md` to it. |
| No root `CLAUDE.md` | Creates a `CLAUDE.md` symlink to `AGENTS.md`. |
| A root `CLAUDE.md` | Leaves it alone. |

---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-12
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Why

- Claude Code reads `CLAUDE.md`, while Guardex keeps the canonical repo guidance in `AGENTS.md`.
- Fresh `gx setup` repos currently get `AGENTS.md` only, so Claude sessions can miss the same safety contract.

## What Changes

- Make `gx setup` / `gx doctor` create a root `CLAUDE.md` symlink to `AGENTS.md` when `CLAUDE.md` is absent.
- Preserve existing root `CLAUDE.md` files unchanged.
- Update setup regression coverage and README guidance.

## Impact

- Affected surface is setup/doctor scaffold output only.
- Existing user-authored Claude guidance remains untouched.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## ADDED Requirements

### Requirement: Setup links Claude guidance to AGENTS when absent
`gx setup` and `gx doctor` SHALL create a root `CLAUDE.md` symlink to `AGENTS.md` when the target repository has no root `CLAUDE.md`.

#### Scenario: Fresh setup creates both guidance entrypoints
- **WHEN** `gx setup --target <repo>` runs in a repo with no root `AGENTS.md` or `CLAUDE.md`
- **THEN** `AGENTS.md` contains the Guardex managed guidance block
- **AND** `CLAUDE.md` is a symlink whose target is `AGENTS.md`.

#### Scenario: Existing Claude guidance is preserved
- **GIVEN** the target repo already has a root `CLAUDE.md`
- **WHEN** `gx setup --target <repo>` runs
- **THEN** Guardex SHALL leave the existing `CLAUDE.md` content and file type unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Definition of Done

This change is complete only when **all** of the following are true:

- Every checkbox below is checked.
- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff.
- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline.

## Handoff

- Handoff: change=`agent-codex-codex-task-2026-05-13-00-50`; branch=`agent/codex/codex-task-2026-05-13-00-50`; scope=`gx setup creates CLAUDE.md link to AGENTS.md`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
- Copy prompt: Continue `agent-codex-codex-task-2026-05-13-00-50` on branch `agent/codex/codex-task-2026-05-13-00-50`. Work inside the existing sandbox, review `openspec/changes/agent-codex-codex-task-2026-05-13-00-50/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/codex/codex-task-2026-05-13-00-50 --base dev --via-pr --wait-for-merge --cleanup`.

## 1. Specification

- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-codex-task-2026-05-13-00-50`.
- [x] 1.2 Define normative requirements in `specs/codex-task/spec.md`.

## 2. Implementation

- [x] 2.1 Implement scoped behavior changes.
- [x] 2.2 Add/update focused regression coverage.

## 3. Verification

- [x] 3.1 Run targeted project verification commands.
- `node --test --test-name-pattern "CLAUDE.md|setup provisions workflow files" test/setup.test.js` passed 2/2.
- Full `node --test test/setup.test.js` ran the new cases successfully, but the suite still has unrelated baseline failures in hook redirect, older git `worktree --orphan`, masterplan path, disabled OpenSpec slug, and Colony package rename expectations.
- [x] 3.2 Run `openspec validate agent-codex-codex-task-2026-05-13-00-50 --type change --strict`.
- [x] 3.3 Run `openspec validate --specs`.

## 4. Cleanup (mandatory; run before claiming completion)

- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff.
- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch).
3 changes: 3 additions & 0 deletions src/cli/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ const {
installUserLevelAsset,
removeLegacyManagedRepoFile,
ensureAgentsSnippet,
ensureClaudeAgentsLink,
ensureManagedGitignore,
buildRepoVscodeSettings,
ensureRepoVscodeSettings,
Expand Down Expand Up @@ -1581,6 +1582,7 @@ function runInstallInternal(options) {

if (!options.skipAgents) {
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
operations.push(ensureClaudeAgentsLink(repoRoot, Boolean(options.dryRun)));
}

const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
Expand Down Expand Up @@ -1664,6 +1666,7 @@ function runFixInternal(options) {

if (!options.skipAgents) {
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
operations.push(ensureClaudeAgentsLink(repoRoot, Boolean(options.dryRun)));
}

const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
Expand Down
19 changes: 19 additions & 0 deletions src/scaffold/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,24 @@ function ensureAgentsSnippet(repoRoot, dryRun) {
return { status: 'updated', file: 'AGENTS.md' };
}

function ensureClaudeAgentsLink(repoRoot, dryRun) {
const claudePath = path.join(repoRoot, 'CLAUDE.md');
try {
fs.lstatSync(claudePath);
return { status: 'unchanged', file: 'CLAUDE.md', note: 'existing path preserved' };
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}

if (!dryRun) {
fs.symlinkSync('AGENTS.md', claudePath);
}

return { status: dryRun ? 'would-create' : 'created', file: 'CLAUDE.md', note: 'symlink to AGENTS.md' };
}

function ensureManagedGitignore(repoRoot, dryRun) {
const gitignorePath = path.join(repoRoot, '.gitignore');
const managedBlock = [
Expand Down Expand Up @@ -763,6 +781,7 @@ module.exports = {
installUserLevelAsset,
removeLegacyManagedRepoFile,
ensureAgentsSnippet,
ensureClaudeAgentsLink,
ensureManagedGitignore,
parseJsonObjectLikeFile,
buildRepoVscodeSettings,
Expand Down
17 changes: 17 additions & 0 deletions test/setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ test('setup provisions workflow files and repo config', () => {
'.gitignore',
'.vscode/settings.json',
'AGENTS.md',
'CLAUDE.md',
];

for (const relativePath of requiredFiles) {
Expand Down Expand Up @@ -149,6 +150,10 @@ test('setup provisions workflow files and repo config', () => {
assert.match(agentsContent, /### Caveman style/);
assert.match(agentsContent, /Answer order stays fixed: answer first, cause next, fix or next step last\./);

const claudeStats = fs.lstatSync(path.join(repoDir, 'CLAUDE.md'));
assert.equal(claudeStats.isSymbolicLink(), true, 'CLAUDE.md should link to AGENTS.md');
assert.equal(fs.readlinkSync(path.join(repoDir, 'CLAUDE.md')), 'AGENTS.md');

const gitignoreContent = fs.readFileSync(path.join(repoDir, '.gitignore'), 'utf8');
assert.match(gitignoreContent, /# multiagent-safety:START/);
assert.match(gitignoreContent, /^scripts\/agent-session-state\.js$/m);
Expand Down Expand Up @@ -203,6 +208,18 @@ test('setup provisions workflow files and repo config', () => {
}
});

test('setup preserves an existing root CLAUDE.md instead of replacing it', () => {
const repoDir = initRepo();
fs.writeFileSync(path.join(repoDir, 'CLAUDE.md'), '# Existing Claude guidance\n', 'utf8');

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

assert.equal(fs.readFileSync(path.join(repoDir, 'CLAUDE.md'), 'utf8'), '# Existing Claude guidance\n');
assert.equal(fs.lstatSync(path.join(repoDir, 'CLAUDE.md')).isSymbolicLink(), false);
assert.match(result.stdout, /existing path preserved/);
});


test('setup on a fresh compose repo prints onboarding hints and installs a working docker loader', () => {
const repoDir = initRepoOnBranch('main');
Expand Down
Loading