Files
stack/docs/scratchpads/tools-md-seeding-20260411.md
Jason Woltje 03a53c543a
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
fix(mosaic): seed TOOLS.md from defaults on install
Closes #457.

The bash framework installer only seeded AGENTS.md and STANDARDS.md from
defaults/, even though TOOLS.md is listed in PRESERVE_PATHS and AGENTS.md
declares it as mandatory reading at position 5 of the load order. A fresh
bootstrap install therefore left ~/.config/mosaic/TOOLS.md missing and the
agent contract pointing at a non-existent file.

Fixes:

- packages/mosaic/framework/install.sh — extend the explicit defaults-seed
  loop from "AGENTS.md STANDARDS.md" to "AGENTS.md STANDARDS.md TOOLS.md".
- packages/mosaic/src/config/file-adapter.ts — replace the greedy
  readdirSync loop in syncFramework with an exported DEFAULT_SEED_FILES
  whitelist, so the TS wizard no longer silently seeds the Jarvis-flavored
  defaults/SOUL.md, placeholder defaults/USER.md, or internal
  README.md/AUDIT-*.md into the mosaic home. Also align preservePaths with
  the bash PRESERVE_PATHS list (AGENTS.md, STANDARDS.md, sources, and
  credentials were previously missing) so both install paths have the
  same upgrade-preservation semantics.
- packages/mosaic/framework/templates/TOOLS.md.template — replace stale
  ~/.config/mosaic/rails/ references with ~/.config/mosaic/tools/. The
  rails/ tree was renamed to tools/ in the v1→v2 framework migration.

Tests:

- packages/mosaic/src/config/file-adapter.test.ts (new, 5 tests): pins
  the whitelist, asserts SOUL.md/USER.md/README.md/AUDIT-*.md are not
  seeded, verifies existing user contract files (including AGENTS.md)
  survive a keep-mode sync, and asserts a no-op when defaults/ is absent.

Baselines: mosaic typecheck / lint green. Full mosaic vitest 275/276 —
the one failure (src/commands/uninstall.spec.ts:138) is a pre-existing
EACCES issue on main and is unrelated to this change. Repo-wide
typecheck / lint / format:check green. Live smoke of
`bash framework/install.sh` against a tmp MOSAIC_HOME confirms the
installer now prints "Seeded TOOLS.md from defaults" and the file lands.

Ships in @mosaicstack/mosaic 0.0.30.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 20:56:55 -05:00

5.9 KiB

Hotfix Scratchpad — install.sh does not seed TOOLS.md

Objective

Ensure ~/.config/mosaic/TOOLS.md is created on every supported install path so the mandatory AGENTS.md load order actually resolves. The load order lists TOOLS.md at position 5 but the bash installer never seeds it.

Root cause

packages/mosaic/framework/install.sh:228-236 — the post-sync "Seed defaults" loop explicitly lists AGENTS.md STANDARDS.md:

DEFAULTS_DIR="$TARGET_DIR/defaults"
if [[ -d "$DEFAULTS_DIR" ]]; then
  for default_file in AGENTS.md STANDARDS.md; do    # ← missing TOOLS.md
    if [[ -f "$DEFAULTS_DIR/$default_file" ]] && [[ ! -f "$TARGET_DIR/$default_file" ]]; then
      cp "$DEFAULTS_DIR/$default_file" "$TARGET_DIR/$default_file"
      ok "Seeded $default_file from defaults"
    fi
  done
fi

TOOLS.md is listed in PRESERVE_PATHS (line 24) but never created in the first place. A fresh bootstrap install via tools/install.sh → framework/install.sh leaves ~/.config/mosaic/TOOLS.md absent, and the agent load order then points at a missing file.

Secondary: TypeScript syncFramework is too greedy

packages/mosaic/src/config/file-adapter.ts:133-160FileConfigAdapter.syncFramework correctly seeds TOOLS.md, but it does so by iterating every file in framework/defaults/:

for (const entry of readdirSync(defaultsDir)) {
  const dest = join(this.mosaicHome, entry);
  if (!existsSync(dest)) {
    copyFileSync(join(defaultsDir, entry), dest);
  }
}

framework/defaults/ contains:

AGENTS.md
AUDIT-2026-02-17-framework-consistency.md
README.md
SOUL.md        ← hardcoded "Jarvis"
STANDARDS.md
TOOLS.md
USER.md

So on a fresh install the TS wizard would silently copy the Jarvis-flavored SOUL.md + placeholder USER.md + internal AUDIT-*.md and README.md into the user's mosaic home before mosaic init ever prompts them. That's a latent identity bug as well as a root-clutter bug — the wizard's own stages are responsible for generating SOUL.md/USER.md via templates.

Tertiary: stale TOOLS.md.template

packages/mosaic/framework/templates/TOOLS.md.template still references ~/.config/mosaic/rails/git/… and ~/.config/mosaic/rails/codex/…. The rails/ tree was renamed to tools/ in the v1→v2 migration (see run_migrations in install.sh, which removes the old rails/ symlink). Any user who does run mosaic init ends up with a TOOLS.md that points to paths that no longer exist.

Scope of this fix

  1. packages/mosaic/framework/install.sh — extend the explicit seed list to include TOOLS.md.
  2. packages/mosaic/src/config/file-adapter.ts — restrict syncFramework defaults-seeding to an explicit whitelist (AGENTS.md, STANDARDS.md, TOOLS.md) so the TS wizard never accidentally seeds SOUL.md/USER.md/README.md/AUDIT-*.md into the mosaic home.
  3. packages/mosaic/framework/templates/TOOLS.md.template — replace rails/ with tools/ in the wrapper-path examples (minimal surgical fix; full template modernization is out of scope for a 0.0.30 hotfix).
  4. Regression test — unit test around FileConfigAdapter.syncFramework that runs against a tmpdir fixture asserting:
    • TOOLS.md is seeded when absent
    • AGENTS.md / STANDARDS.md are still seeded when absent
    • SOUL.md / USER.md are not seeded from defaults/ (the wizard stages own those)
    • Existing root files are not clobbered.

Out of scope (tracked separately / future work):

  • Regenerating defaults/SOUL.md and defaults/USER.md so they no longer contain Jarvis-specific content.
  • Fully modernizing TOOLS.md.template to match the rich canonical defaults/TOOLS.md reference.
  • issue-create.sh / pr-create.sh eval bugs (already captured to OpenBrain from the prior hotfix).

Plan / checklist

  • Branch fix/tools-md-seeding from main (at b2cbf89)
  • File Gitea issue (direct API; wrappers broken for bodies with backticks)
  • Scratchpad created (this file)
  • install.sh seed loop extended to AGENTS.md STANDARDS.md TOOLS.md
  • file-adapter.ts seeding restricted to explicit whitelist
  • TOOLS.md.template rails/tools/
  • Regression test added (file-adapter.test.ts) — failing first, then green
  • pnpm --filter @mosaicstack/mosaic run typecheck green
  • pnpm --filter @mosaicstack/mosaic run lint green
  • pnpm --filter @mosaicstack/mosaic exec vitest run — new test green, no new failures beyond the known pre-existing uninstall.spec.ts:138
  • Repo baselines: pnpm typecheck / pnpm lint / pnpm format:check
  • Independent code review (feature-dev:code-reviewer, sonnet tier)
  • Commit + push
  • PR opened via Gitea API
  • CI queue guard cleared (bypass local ci-queue-wait.sh if stale origin URL breaks it; query Gitea API directly)
  • CI green on PR
  • PR merged (squash)
  • CI green on main
  • Issue closed with link to merge commit
  • chore/release-mosaic-0.0.30 branch bumps packages/mosaic/package.json 0.0.29 → 0.0.30
  • Release PR opened + merged
  • .woodpecker/publish.yml auto-publishes to Gitea npm registry
  • Publish verified (npm view @mosaicstack/mosaic version or registry check)

Risks / blockers

  • ci-queue-wait.sh wrapper may still crash on stale origin URL (captured in OpenBrain from prior hotfix). Workaround: query Gitea API directly for running/queued pipelines.
  • issue-create.sh / pr-create.sh eval bugs. Workaround: Gitea API direct call.
  • uninstall.spec.ts:138 is a pre-existing failure on main; not this change's problem.
  • Publish flow is fire-and-forget on main push — if publish.yml fails, rollback means republishing a follow-up patch, not reverting the version bump.