Files
stack/docs/scratchpads/tools-md-seeding-20260411.md
Jason Woltje c3f810bbd1
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
fix(mosaic): seed TOOLS.md from defaults on install (#458)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-04-12 02:02:21 +00: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.