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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **`forge sync` now adopts an existing project `CLAUDE.md` instead of skipping it.** Previously a
repo with its own `CLAUDE.md` was left untouched — which meant Forge's shared rules never
reached Claude Code there. Sync now prepends the one-line `@AGENTS.md` import (idempotent,
every original line preserved) and reports `adopted`. `AGENTS.md` keeps its back-up-then-write
behaviour; your skills and other tool files are untouched.

### Fixed

- **Secret-refusal no longer guts auth-related work.** `SECRET_RE` matched the bare words
Expand Down
22 changes: 18 additions & 4 deletions src/emit/claude.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
// Claude Code reads CLAUDE.md, not AGENTS.md. Emit a thin CLAUDE.md that imports
// the shared source via @AGENTS.md (first line, Windows-safe). Never clobber a
// user's own unmanaged CLAUDE.md.
// the shared source via @AGENTS.md (first line, Windows-safe). If the repo already has its OWN
// CLAUDE.md, ADOPT it rather than skip: prepend the one-line @AGENTS.md import (idempotent) so
// Forge's shared rules actually reach Claude Code, while preserving every line the user wrote.
import { writeFileSync } from "node:fs";
import { join } from "node:path";

const IMPORTS_AGENTS = /^\s*@AGENTS\.md\b/m;

export default {
tool: "Claude Code",
emit(ctx) {
const path = join(ctx.targetRoot, "CLAUDE.md");
const existing = ctx.shared.readIfExists(path);
// A user's own (unmanaged) CLAUDE.md: adopt it by wiring in the shared import, non-destructively.
if (existing !== null && !ctx.shared.isManaged(existing)) {
if (IMPORTS_AGENTS.test(existing)) {
return {
tool: this.tool,
target: "CLAUDE.md",
action: "unchanged",
note: "your CLAUDE.md already imports @AGENTS.md",
};
}
writeFileSync(path, `@AGENTS.md\n\n${existing.replace(/^/, "")}`);
return {
tool: this.tool,
target: "CLAUDE.md",
action: "skipped",
note: "existing unmanaged CLAUDE.md left as-is",
action: "adopted",
note: "prepended @AGENTS.md import to your existing CLAUDE.md (content preserved)",
};
}
const content = [
Expand Down
15 changes: 11 additions & 4 deletions test/sync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,20 @@ test("re-running is idempotent (nothing rewritten)", () => {
assert.equal(written.length, 0, `second sync wrote ${written.map((r) => r.target)}`);
});

test("does not clobber an existing unmanaged CLAUDE.md", () => {
test("adopts an existing unmanaged CLAUDE.md: prepends @AGENTS.md, preserves content, idempotent", () => {
const root = fixture();
writeFileSync(join(root, "CLAUDE.md"), "# my own claude file\n");
writeFileSync(join(root, "CLAUDE.md"), "# my own claude file\nkeep me\n");
const res = sync({ targetRoot: root });
const row = res.report.find((r) => r.target === "CLAUDE.md");
assert.equal(row.action, "skipped");
assert.match(readFileSync(join(root, "CLAUDE.md"), "utf8"), /my own claude file/);
assert.equal(row.action, "adopted", "existing CLAUDE.md is adopted, not skipped or clobbered");
const after = readFileSync(join(root, "CLAUDE.md"), "utf8");
assert.match(after, /^@AGENTS\.md/m, "shared import wired in");
assert.match(after, /my own claude file/, "original content preserved");
assert.match(after, /keep me/);
// Re-running does not re-prepend or rewrite.
const again = sync({ targetRoot: root });
assert.equal(again.report.find((r) => r.target === "CLAUDE.md").action, "unchanged");
assert.equal((after.match(/@AGENTS\.md/g) || []).length, 1, "import appears exactly once");
});

test("warns when a legacy .cursorrules would shadow AGENTS.md", () => {
Expand Down
Loading