# Hotfix Scratchpad — `install.sh` does not seed `TOOLS.md` - **Issue:** mosaicstack/stack#457 - **Branch:** `fix/tools-md-seeding` - **Type:** Out-of-mission hotfix (not part of Install UX v2 mission) - **Started:** 2026-04-11 - **Ships in:** `@mosaicstack/mosaic` 0.0.30 ## 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`: ```bash 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-160` — `FileConfigAdapter.syncFramework` correctly seeds TOOLS.md, but it does so by iterating _every_ file in `framework/defaults/`: ```ts 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.