feat(sync,init): own only a marked block in AGENTS.md; init emits only the tools a repo uses - #160
Merged
Merged
Conversation
… tools forge init/sync replaced a hand-written AGENTS.md (the original went to AGENTS.md.forge-bak, which no agent reads), and the Stop-hook auto-sync then reverted any human edit to the now fully managed file. Forge now owns only the text between <!-- forge:begin --> and <!-- forge:end -->: - sync appends the block to a hand-written file and afterwards compares and rewrites only the block (agentsMdStatus is the one classifier for sync, auto-sync and doctor; hasManagedBlock replaces isManaged here) - auto-sync refreshes only the block, never adopts a file without one, and no longer runs the full per-tool emit - a legacy fully generated AGENTS.md converts losslessly: the header hash identifies forge's bytes, so text added around them is kept; one edited inside the generated text is converted only by an explicit sync, after a timestamped AGENTS.md.forge-bak-<time> backup - damaged markers are reported and left alone - a generated CLAUDE.md keeps notes added under its header (marker line refreshed in place) - doctor reports "hand-written AGENTS.md (no Forge block)" and its fix appends the block forge init now emits config only for Claude plus the tools the repo already shows (.cursor/, .codex/, .github/copilot-instructions.md, ...) or an explicit --tools <list|all>, and records the set in .forge/forge.config.json so sync, doctor --fix and integrations add emit the same set. A repo with no recorded set still gets every tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UUhB8JaPayd43w37dxiXrW Signed-off-by: Claude <noreply@anthropic.com>
…ile, report a stranded .forge-bak Review follow-ups to the managed AGENTS.md block: - A body line that reads exactly like `<!-- forge:end -->` (a multi-line rule, brain fact or lesson) ended the block early, so every sync and every Stop-hook auto-sync saw drift and left another stale tail behind; the file grew on each run. managedBlock now indents such lines one space (same Markdown rendering, no longer a whole-line marker). - Codex's 32 KiB and Windsurf's ~12k-char checks measured only the canonical body. AGENTS.md is now the person's text plus the block, and those tools cut the end of the file, where the block sits. The rows and a new sync warning measure the file on disk (a 37,992 B file was reported as 6,476 B before). - AGENTS.md.forge-bak (the hand-written file an older forge replaced) was mentioned only when an explicit sync converted a legacy file. The Stop hook usually converts first and its result is discarded, so the rules stayed invisible. strandedAgentsBackup() now drives a warning in both sync and doctor for as long as the backup holds text AGENTS.md lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UUhB8JaPayd43w37dxiXrW Signed-off-by: Claude <noreply@anthropic.com>
…ools` `forge init --tools <list|all>` and the Claude-plus-detected default arrived in 61aa2db under a fix type. This commit completes that feature and carries its feat type, so the auto-release is a minor bump, not a patch. - integrations add recorded ownership (mcp.adopted) for every MCP target, including ones outside the recorded tool set that it never wrote. A same-name entry a person later wrote for such a tool was then overwritten by `forge init --tools ...` and deleted by `forge integrations remove` (breaks ME-08). Adoptions now cover only mcpTargetFilesFor(recorded set). When a tool joins the set later, claimEmittedIntegrations (run by init and `forge tools`) claims the copies sync wrote there by add's absent-or-identical rule, so spec updates and remove still reach forge's own copies and a divergent entry stays the person's. - `forge tools <name>` synced with the recorded set only, so after a default init the chosen primary tool got no config at all. setPrimaryTool now adds the tool to a recorded set in the same config write; the CLI says so. - The integrations dry run lists the files it would write instead of a fixed every-tool list. - Docs: quickstart, integrations and tools docs, the examples README, the landing copy and the onboarding diagrams no longer say init or add write every tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UUhB8JaPayd43w37dxiXrW Signed-off-by: Claude <noreply@anthropic.com>
CodeWithJuber
marked this pull request as ready for review
September 24, 2026 04:19
…ed-block Signed-off-by: Claude <noreply@anthropic.com> # Conflicts: # CHANGELOG.md
…ed-block Signed-off-by: Claude <noreply@anthropic.com> # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
An evaluation of the HostLelo site found a high-severity problem: "forge init/sync replace a hand-written AGENTS.md; Stop-hook autosync silently reverts human edits". What it measured on a scratch copy of HostLelo:
forge init --no-settingsrewrote AGENTS.md (its md5 changed) into the generic# AGENTS.md — engineering rules.AGENTS.md.forge-bak, which no agent reads..aider.conf.yml,.codex/,.continue/,.cursor/,.gemini/,.openclaw/,.roo/,.vscode/,.zed/,.mcp.jsonand.gitattributes.autoSyncIfDriftedcompared the whole file and rewrote any human edit, with no backup.forge doctortold users "stale or hand-edited — run forge sync", which is the destructive path.What this PR changes:
AGENTS.md is a managed block (61aa2db, 2aae444)
<!-- forge:begin -->and<!-- forge:end -->. Sync appends the block to a hand-written file, then compares and rewrites only the block.agentsMdStatus, is shared by sync, auto-sync and doctor.forge syncconverts it, after writing a timestampedAGENTS.md.forge-bak-<time>backup.init emits only the tools a repo uses (61aa2db, 399573f)
--tools <list|all>overrides it..forge/forge.config.json. sync, doctor --fix and integrations use the same set.Fixes from the independent review of 61aa2db (I reproduced each one first):
integrations addrecorded ownership for MCP targets it never wrote. After a default init plusadd context7,mcp.adoptedlisted seven configs that did not exist. A person's own context7 entry in.cursor/mcp.jsonwas then overwritten byforge init --tools claude,cursorand deleted byintegrations remove. Now ownership is recorded only for the targets in the recorded set. When a tool joins the set later,claimEmittedIntegrationstakes ownership of the copies sync wrote there (entries that were absent or identical to Forge's spec). A person's divergent entry stays theirs, and Forge's own copies still get spec updates and are removed byremove.forge tools <name>. It synced with the recorded set only, so after a default initforge tools cursorwrote no Cursor config. The tool is now added to the recorded set in the same config write.<!-- forge:end -->ended the block early. AGENTS.md grew by 25 B on every sync and every auto-sync and never stabilised. Such lines are now indented one space.native (6476/32768 B), and Codex cuts off the end of the file, which is where the block now sits. The checks and a new sync warning now measure the file on disk.AGENTS.md.forge-bak. It was reported only when an explicit sync converted the file. When the Stop hook converted it, which usually happens first, nothing mentioned it again.forge syncandforge doctornow warn for as long as the backup holds text that AGENTS.md does not.fix(sync). I did not split it, because that would rewrite this branch's history. Instead 399573f is typedfeat(init)and this PR title isfeat, so the release is a minor bump whether the PR is squash-merged or merge-committed. I did not add!/BREAKING CHANGE: repos that already ran init keep emitting every tool, and only a newforge initemits fewer files (--tools allrestores the old behaviour). Whether that change to init's default needs a major bump is the maintainer's call.Result on a copy of HostLelo's AGENTS.md/CLAUDE.md:
forge init --no-settingskeeps the original 3579 B byte for byte (checked with cmp) and appends the block after them. It creates only.mcp.json,.forge/and.gitattributes, leaves CLAUDE.md identical, and prints no warnings.Tests added:
test/agents_block.test.js(new, 17 tests): block helpers, preservation by init/sync/auto-sync, legacy conversion and backup, damaged markers, doctor states, marker lines in the body staying stable, whole-file size checks, and warnings about a stranded backup.test/init_tools.test.js(new, 21 tests): tool detection and parsing, init's default and--tools, backward compatibility,recordedTools/mcpTargetFilesFor/planIntegration, per-target MCP ownership across enabling a tool andremove, ownership when a tool joins the set later,forge tools <name>adding the tool, and CLI paths.test/init.test.js,test/regressions.test.jsandtest/sync.test.js, which asserted the old replace-and-back-up and emit-every-tool behaviour.Checks run on HEAD 399573f (Node v22.22.2):
npm test: exit 0. 1478 tests: 1475 pass, 0 fail, 3 skipped (the baseline at 93f37a8 was 1437 passing, 3 skipped).npm run check: exit 0. 14 warnings and 2 infos, the same counts as master.npm run typecheck: exit 0.node src/cli.js docs check: exit 0. Its only advisory, "ARCHITECTURE.md repo-map out of date", is also on master.Known limits:
forge sync --toolsflag. Change the set withforge init --tools.# AGENTS.mdheading.Checklist
npm testpasses (Node 18/20/22) — passes on Node v22.22.2 only; I did not run 18 or 20 locally (CI runs 20/22)npm run checkpasses (Biome lint + format)feat:/fix:/docs:…)CHANGELOG.mdupdated under## [Unreleased]forge substrate,forge impact, router/gate, or MCP substrate tools — n/a: none of these change. The sync/init/doctor/integrations/tools docs are updated.Risk & rollback
forge initemits by default. Mitigations: conversion is lossless, or backed up when the generated text was edited; damaged markers are never written; repos that already ran init keep emitting every tool; the behaviour is covered by the tests above and by the end-to-end runs.git revert 399573f 2aae444 61aa2db). Caveat: the old code treats any AGENTS.md containingforge:sync:as fully generated, and the block's header contains it. After a rollback, the next sync or Stop hook would rewrite the whole file and drop the text around the block, with no backup. Before downgrading, copy AGENTS.md aside or setFORGE_AUTOSYNC=0, and restore the hand-written part afterwards..forge/forge.config.jsontoolsis ignored by the old code, which emits every tool again.Extra checks (tick if applicable)
npm run typecheckpasses--toolsname aborts before any write; a corrupt config is refused, never overwritten; damaged markers are reported, not guessed; new warnings are surfaced by sync, doctor and the CLIFound by an evaluation run against the HostLelo site (CodeWithJuber/my-next-app).
🤖 Generated with Claude Code
https://claude.ai/code/session_01UUhB8JaPayd43w37dxiXrW
Generated by Claude Code