Skip to content

docs(agent-context): clarify refresh/overwrite behavior in the generated always-on doc#184

Merged
dmealing merged 1 commit into
mainfrom
agent-context-refresh-guidance
Jul 7, 2026
Merged

docs(agent-context): clarify refresh/overwrite behavior in the generated always-on doc#184
dmealing merged 1 commit into
mainfrom
agent-context-refresh-guidance

Conversation

@dmealing

@dmealing dmealing commented Jul 7, 2026

Copy link
Copy Markdown
Member

Intent

Fix the meta init --refresh-docs overwrite confusion adopting apps report. The generated always-on agent-context doc (.metaobjects/CLAUDE.md / AGENTS.md) told adopters the metaobjects-* skills were 'safe to read and edit' while also saying 're-run the agent-context scaffold to refresh them' -- a contradiction: an edit then a refresh parks the fresh copy at .new (or clobbers with --force), and there was no clear home for project-specific notes. DECISION: fix this purely by 'telling' in the generated always-on doc -- NO code-level merge mechanism. A marker-delimited tail-preserve in planScaffold was explicitly considered and RULED OUT by the user; do NOT add it or reference it as a future option. The doc now: (1) marks itself generated; (2) directs project notes to the root CLAUDE.md; (3) explains a refresh never silently destroys edits -- it parks .new, only --force overwrites in place, so never --force a file with local edits; (4) gives the reconcile procedure (diff .new vs , keep hand-edits + take upstream changes, write back, delete .new). Regen the 8 conformance goldens (AGENTS.md + CLAUDE.md across the 4 stacks). Docs-only; no runtime/codegen/CLI-behavior change. The scaffold's manifest-hash protection and .new parking are unchanged.

What Changed

  • Updated the always-on.md.mustache agent-context template to mark itself as generated, direct project-specific notes to the root CLAUDE.md, and explain that a refresh parks the fresh copy at <path>.new (only --force overwrites in place), with a reconcile procedure (diff, keep hand-edits, write back, delete .new).
  • Regenerated the 8 conformance golden docs (AGENTS.md + CLAUDE.md across the csharp, java-react, python, and ts-react-tanstack stacks) from the updated template.
  • Docs-only change; no runtime, codegen, CLI behavior, manifest-hash protection, or .new parking logic altered.

Risk Assessment

✅ Low: Docs-only template prose rewrite plus regenerated conformance goldens; all behavioral claims verified against the unchanged scaffold/init code, and the byte-for-byte conformance test will pass against the matching goldens.

Testing

Exercised the agent-context conformance corpus (4 stacks, byte-identical goldens), the CLI refresh-docs unit tests (confirming .new parking + manifest-hash + --force behavior are unchanged), and a live end-to-end meta init / --refresh-docs / reconcile / --force walkthrough against a real scaffolded project that proves the four behaviors the rewritten doc describes are actually true. All green; the generated doc is now internally consistent and its refresh guidance is accurate. Setup issue (missing node_modules) fixed with a one-time bun install; no code fixes needed.

Evidence: End-to-end meta init --refresh-docs transcript (scaffold → hand-edit → .new parking → reconcile → --force)
=== STEP 1: fresh scaffold, generated CLAUDE.md (head) ===
# Working with MetaObjects in this project

> Stack: typescript server, react, tanstack client.
>
> Generated by MetaObjects (refresh: `meta init --refresh-docs`). Put project notes in your
... [generated-marker present]: true
... [project-notes guidance present]: true
... [refresh .new parking explained]: true
... [reconcile procedure present]: true
... [OLD contradiction 'safe to read and edit' gone]: true

=== STEP 2: adopter hand-edited CLAUDE.md, then ran `meta init --refresh-docs` ===
result.created: [".claude/skills/metaobjects-audit/SKILL.md",".claude/skills/metaobjects-audit/references/capability-checklist.md",".claude/skills/metaobjects-audit/references/csharp.md",".claude/skills/metaobjects-audit/references/java.md",".claude/skills/metaobjects-audit/references/kotlin.md",".claude/skills/metaobjects-audit/references/python.md",".claude/skills/metaobjects-audit/references/typescript.md",".claude/skills/metaobjects-authoring/SKILL.md",".claude/skills/metaobjects-codegen/SKILL.md",".claude/skills/metaobjects-codegen/references/csharp.md",".claude/skills/metaobjects-codegen/references/java.md",".claude/skills/metaobjects-codegen/references/kotlin.md",".claude/skills/metaobjects-codegen/references/python.md",".claude/skills/metaobjects-codegen/references/typescript.md",".claude/skills/metaobjects-prompts/SKILL.md",".claude/skills/metaobjects-prompts/references/csharp.md",".claude/skills/metaobjects-prompts/references/java.md",".claude/skills/metaobjects-prompts/references/kotlin.md",".claude/skills/metaobjects-prompts/references/python.md",".claude/skills/metaobjects-prompts/references/typescript.md",".claude/skills/metaobjects-runtime-ui/SKILL.md",".claude/skills/metaobjects-runtime-ui/references/java.md",".claude/skills/metaobjects-runtime-ui/references/kotlin.md",".claude/skills/metaobjects-runtime-ui/references/react.md",".claude/skills/metaobjects-runtime-ui/references/tanstack.md",".claude/skills/metaobjects-runtime-ui/references/typescript.md",".claude/skills/metaobjects-verify/SKILL.md",".claude/skills/metaobjects-verify/references/migration.md",".metaobjects/AGENTS.md",".metaobjects/CLAUDE.md.new"]
edited CLAUDE.md preserved (still has 'My project note'): true
fresh copy parked at CLAUDE.md.new: true
the .new copy carries the generated-marker: true

=== STEP 3: reconcile per the doc — keep hand-edit, take upstream, delete .new ===
after reconcile — hand-edit kept: true
after reconcile — upstream generated-marker kept: true
.new deleted: true

=== STEP 4: `meta init --refresh-docs --force` overwrites the edited file in place ===
true
no .new parked: true
Evidence: e2e.ts — the script driving the real init/refresh/force walkthrough

Drives init({servers:['typescript'],clients:['react','tanstack']}) → reads generated CLAUDE.md, asserts generated-marker + project-notes guidance + .new-parking explanation + reconcile procedure present and old 'safe to read and edit' contradiction gone → hand-edits the doc → init({refreshDocs:true}) asserts edited file preserved + fresh copy parked at CLAUDE.md.new → reconciles (keep hand-edit + take upstream, delete .new) → init({refreshDocs:true,force:true}) asserts in-place overwrite, no .new. All assertions true.

import { init } from "/home/doug/.no-mistakes/worktrees/4a36a911fd68/01KWYBDG7C0GNHSE04E9S1PJXJ/server/typescript/packages/cli/src/commands/init.js";
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const cwd = mkdtempSync(join(tmpdir(), "mo-e2e-"));
const log: string[] = [];
const line = (s: string) => { log.push(s); console.log(s); };

try {
  // 1. Fresh scaffold — generates the always-on doc with the new generated-marker + refresh note.
  await init({ cwd, servers: ["typescript"], clients: ["react", "tanstack"] });
  const claude = readFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), "utf8");
  line("=== STEP 1: fresh scaffold, generated CLAUDE.md (head) ===");
  line(claude.split("\n").slice(0, 5).join("\n"));
  line("... [generated-marker present]: " + claude.includes("Generated by MetaObjects (refresh: `meta init --refresh-docs`)"));
  line("... [project-notes guidance present]: " + claude.includes("root `CLAUDE.md`"));
  line("... [refresh .new parking explained]: " + claude.includes("writes the\nnew copy to `<path>.new`"));
  line("... [reconcile procedure present]: " + claude.includes("keep the hand-edits and take\nthe upstream changes"));
  line("... [OLD contradiction 'safe to read and edit' gone]: " + !claude.includes("safe to read and edit"));
  line("");

  // 2. Adopter hand-edits the generated doc (the wrong thing, per the new guidance) then refreshes.
  writeFileSync(join(cwd, ".metaobjects", "CLAUDE.md"),
    readFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), "utf8") + "\n\n## My project note\n", "utf8");
  line("=== STEP 2: adopter hand-edited CLAUDE.md, then ran `meta init --refresh-docs` ===");
  const r2 = await init({ cwd, refreshDocs: true });
  line("result.created: " + JSON.stringify(r2.created));
  line("edited CLAUDE.md preserved (still has 'My project note'): "
    + readFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), "utf8").includes("My project note"));
  line("fresh copy parked at CLAUDE.md.new: " + existsSync(join(cwd, ".metaobjects", "CLAUDE.md.new")));
  line("the .new copy carries the generated-marker: "
    + readFileSync(join(cwd, ".metaobjects", "CLAUDE.md.new"), "utf8").includes("Generated by MetaObjects"));
  line("");

  // 3. Reconcile per the doc: diff .new vs current, keep hand-edit + take upstream, write back, delete .new.
  line("=== STEP 3: reconcile per the doc — keep hand-edit, take upstream, delete .new ===");
  const merged = readFileSync(join(cwd, ".metaobjects", "CLAUDE.md.new"), "utf8") + "\n\n## My project note\n";
  writeFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), merged, "utf8");
  rmSync(join(cwd, ".metaobjects", "CLAUDE.md.new"));
  line("after reconcile — hand-edit kept: " + readFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), "utf8").includes("My project note"));
  line("after reconcile — upstream generated-marker kept: " + readFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), "utf8").includes("Generated by MetaObjects"));
  line(".new deleted: " + !existsSync(join(cwd, ".metaobjects", "CLAUDE.md.new")));
  line("");

  // 4. --force overwrites in place (the doc warns: never --force a file with local edits).
  writeFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), "stale content", "utf8");
  line("=== STEP 4: `meta init --refresh-docs --force` overwrites the edited file in place ===");
  await init({ cwd, refreshDocs: true, force: true });
  line("file overwritten in place (no longer 'stale content'): "
    + readFileSync(join(cwd, ".metaobjects", "CLAUDE.md"), "utf8") !== "stale content");
  line("no .new parked: " + !existsSync(join(cwd, ".metaobjects", "CLAUDE.md.new")));
} finally {
  rmSync(cwd, { recursive: true, force: true });
  writeFileSync("/tmp/no-mistakes-evidence/01KWYBDG7C0GNHSE04E9S1PJXJ/e2e-transcript.txt", log.join("\n"), "utf8");
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • git diff 99dfde2..93f81573 --stat (confirmed docs-only: 1 template + 8 goldens)
  • cd server/typescript && bun test packages/sdk/test/agent-context-conformance.test.ts (4 stacks byte-identical to goldens)
  • cd server/typescript && bun test packages/cli/test/unit/init-agent-context.test.ts packages/cli/test/unit/init-refresh-docs.test.ts (refresh/.new/manifest-hash behavior unchanged)
  • bun /tmp/no-mistakes-evidence/.../e2e.ts — real meta init → hand-edit → --refresh-docs (.new parking) → reconcile → --force overwrite walkthrough
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

… the always-on doc

The generated always-on doc (`.metaobjects/CLAUDE.md`/`AGENTS.md`) said the skills
were "safe to read and edit" while also telling adopters to "re-run the agent-context
scaffold to refresh them" -- a contradiction: an edit followed by a refresh parks the
fresh copy at `<path>.new` (or clobbers it with `--force`), which adopting apps trip over
with no clear home for project-specific notes.

Fix by telling, in the doc itself (no code merge): mark the file as generated, direct
project notes to the root `CLAUDE.md`, explain that a refresh never silently destroys
edits (it parks `<path>.new`; only `--force` overwrites in place), and give the
reconcile procedure for `<path>.new`. Regen the 8 conformance goldens (AGENTS.md +
CLAUDE.md across the 4 stacks). Docs-only; no runtime/codegen change.

Co-Authored-By: Claude <noreply@anthropic.com>
@dmealing
dmealing merged commit 1efc4a1 into main Jul 7, 2026
1 check passed
@dmealing
dmealing deleted the agent-context-refresh-guidance branch July 7, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant