# 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.
