Skip to content

setup: re-running replaces an authored scaffold file that still holds one placeholder, and reports it as success #164

Description

@alvistar

setup: re-running replaces an authored scaffold file that still holds one placeholder, and reports it as success

Environment

mex-agent 0.7.3, node v26.0.0, macOS 26.6.2 arm64.

What happens

In runSetup, an existing scaffold file is preserved only when it passes both tests:

if (existsSync(dest)) {
  const existingContent = readFileSync(dest, "utf-8");
  const templateContent = readFileSync(src, "utf-8");
  if (!isTemplateContent(existingContent) && existingContent !== templateContent) {
    info(`Skipped .mex/${file} (already populated)`);
    continue;
  }
}
// otherwise falls through to:
copyFileSync(src, dest);

and

function isTemplateContent(content) {
  return content.includes("[Project Name]") || content.includes("[YYYY-MM-DD]");
}

So a single leftover placeholder anywhere in the file marks the whole file as
template, and the copy replaces it. last_updated: [YYYY-MM-DD] in frontmatter is enough,
and that is the placeholder an author is most likely to leave for last.

Repro

Two files, identical in structure. The only difference is that one still has the date
placeholder.

mkdir repro-setup && cd repro-setup && git init -q
mkdir -p .mex/context .mex/patterns
echo 'export const x = 1;' > app.ts
# start from the shipped templates, as a real setup would leave them
# (from a checkout, TPL=<repo>/templates)
TPL=$(find ~/.npm/_npx -maxdepth 6 -type d -path '*mex-agent/templates' | head -1)
for f in ROUTER.md AGENTS.md SETUP.md SYNC.md context/architecture.md context/stack.md \
         context/conventions.md context/decisions.md context/setup.md \
         patterns/README.md patterns/INDEX.md; do cp "$TPL/$f" ".mex/$f"; done

# VICTIM: authored, but the frontmatter date was never filled in
cat > .mex/context/architecture.md <<'MD'
---
name: architecture
description: How a request becomes a response in this service.
last_updated: [YYYY-MM-DD]
grounds_to: []
edges:
  - target: context/stack.md
    condition: when the runtime primitives matter
---

# Architecture

The ingress terminates TLS and forwards to the router, which resolves a handler from the
route table and calls it inside a request-scoped transaction. Failures roll back and
surface as problem+json.
MD

# CONTROL: authored, no placeholder
cat > .mex/context/stack.md <<'MD'
---
name: stack
description: Runtime primitives.
last_updated: 2026-09-02
grounds_to: []
edges:
  - target: context/architecture.md
    condition: when the request flow matters
---

# Stack

Postgres 17, Node 22, no ORM.
MD

mex setup

Output:

✓ Copied .mex/context/architecture.md
→ Skipped .mex/context/stack.md (already populated)

architecture.md is replaced by the 68-line template; every hand-written line is gone.
stack.md survives untouched. Exit code 0, and the file that
was destroyed is reported with a green tick reading Copied.

mex setup --dry-run shows the same decision without making it, which is a good way to
confirm the repro before running it for real.

Why this matters

Populating a scaffold is hours of work, and mex setup is the command a user re-runs when
something looks wrong — after adding a second AI tool, after an upgrade, or when following
the "Scaffold directory exists but looks incomplete" advice. The file most at risk is the
one someone was in the middle of writing, and the destruction is silent: no diff, no
prompt, no backup, and a success line.

What would help

In rough order of preference:

  1. Do not treat a file as a template because it contains a placeholder. Compare against
    the template content only — existingContent !== templateContent already distinguishes
    an untouched template from an edited one.
  2. If the placeholder test is kept, require the file to be mostly template — e.g. it
    still matches the template except for placeholders — rather than "contains one".
  3. At minimum, never overwrite without saying so: prompt, or write file.md.bak, or list
    the files that will be replaced and ask for confirmation.
  4. Report the destructive case distinctly. Copied reads like a creation; something like
    Replaced .mex/context/architecture.md (was: 24 lines) would not.

Happy to test a patch.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions