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>
5.9 KiB
Hotfix Scratchpad — install.sh does not seed TOOLS.md
- Issue: install.sh does not seed TOOLS.md — breaks AGENTS.md mandatory load order (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/mosaic0.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:
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/:
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
packages/mosaic/framework/install.sh— extend the explicit seed list to includeTOOLS.md.packages/mosaic/src/config/file-adapter.ts— restrictsyncFrameworkdefaults-seeding to an explicit whitelist (AGENTS.md,STANDARDS.md,TOOLS.md) so the TS wizard never accidentally seedsSOUL.md/USER.md/README.md/AUDIT-*.mdinto the mosaic home.packages/mosaic/framework/templates/TOOLS.md.template— replacerails/withtools/in the wrapper-path examples (minimal surgical fix; full template modernization is out of scope for a 0.0.30 hotfix).- Regression test — unit test around
FileConfigAdapter.syncFrameworkthat runs against a tmpdir fixture asserting:TOOLS.mdis seeded when absentAGENTS.md/STANDARDS.mdare still seeded when absentSOUL.md/USER.mdare not seeded fromdefaults/(the wizard stages own those)- Existing root files are not clobbered.
Out of scope (tracked separately / future work):
- Regenerating
defaults/SOUL.mdanddefaults/USER.mdso they no longer contain Jarvis-specific content. - Fully modernizing
TOOLS.md.templateto match the rich canonicaldefaults/TOOLS.mdreference. issue-create.sh/pr-create.shevalbugs (already captured to OpenBrain from the prior hotfix).
Plan / checklist
- Branch
fix/tools-md-seedingfrommain(atb2cbf89) - File Gitea issue (direct API; wrappers broken for bodies with backticks)
- Scratchpad created (this file)
install.shseed loop extended toAGENTS.md STANDARDS.md TOOLS.mdfile-adapter.tsseeding restricted to explicit whitelistTOOLS.md.templaterails/→tools/- Regression test added (
file-adapter.test.ts) — failing first, then green pnpm --filter @mosaicstack/mosaic run typecheckgreenpnpm --filter @mosaicstack/mosaic run lintgreenpnpm --filter @mosaicstack/mosaic exec vitest run— new test green, no new failures beyond the known pre-existinguninstall.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.shif 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.30branch bumpspackages/mosaic/package.json0.0.29 → 0.0.30- Release PR opened + merged
.woodpecker/publish.ymlauto-publishes to Gitea npm registry- Publish verified (
npm view @mosaicstack/mosaic versionor registry check)
Risks / blockers
ci-queue-wait.shwrapper may still crash on staleoriginURL (captured in OpenBrain from prior hotfix). Workaround: query Gitea API directly for running/queued pipelines.issue-create.sh/pr-create.shevalbugs. Workaround: Gitea API direct call.uninstall.spec.ts:138is a pre-existing failure on main; not this change's problem.- Publish flow is fire-and-forget on main push — if
publish.ymlfails, rollback means republishing a follow-up patch, not reverting the version bump.