A manifest declares repo path -> target, and the script walks it. Symlinks, not copies, so an installed file edits the repo and one commit reaches every other machine. Idempotent — a correct link is a no-op, safe after every pull. A real file where a link should go is moved to .bak-<timestamp> and reported, because on a new machine that may be the only copy of something written by hand. Conditional targets are what make it portable: officer.dev/CLAUDE.md and the officer-chat-events hook both name ~/officer-dev/platform, so a laptop with no Officer install gets neither instead of getting a document that lies. mask-secret is a command now rather than a path only true on this machine, and user/CLAUDE.md says so. It is a generated wrapper, not a symlink, and that is forced: mask-secret.py resolves its own path to find mask-hooks.d, so a symlink would reach back into the repo and run every machine's hooks. The wrapper sets MASK_HOOKS_DIR to the ones this machine installed and still runs the script from the repo, so no code is copied. Verified against a scratch HOME: re-run is all ok, the hand-written file survives as .bak, and a machine without Officer skips both conditional lines.
79 lines
4.4 KiB
Markdown
79 lines
4.4 KiB
Markdown
# TODO
|
|
|
|
## Bootstrap installer — BUILT 2026-08-20
|
|
|
|
`install.sh` plus `manifest`. Clone the repo, run one command, every file lands where the tooling
|
|
expects it. See README.md.
|
|
|
|
Everything the plan asked for is in: a manifest rather than paths in shell, symlinks rather than
|
|
copies, idempotent, conditional targets, backups instead of silent clobber, `--dry-run`.
|
|
|
|
Verified on this machine and against a scratch `HOME`: a re-run is all `ok`, a real file in the way is
|
|
moved to `.bak-<timestamp>` and survives, and a machine with no `~/officer-dev/platform` skips both
|
|
the workspace file and its hook.
|
|
|
|
**`mask-secret` is a command now**, on `~/.local/bin`, and `user/CLAUDE.md` names it instead of the
|
|
hardcoded path that was only ever true here. It is a generated wrapper, not a symlink, and that is
|
|
forced: `mask-secret.py` finds hooks at `Path(__file__).resolve().parent/mask-hooks.d`, and `.resolve()`
|
|
follows a symlink back into the repo — which carries every machine's hooks, not this one's. The
|
|
wrapper sets `MASK_HOOKS_DIR` and still executes the script from the repo, so no code is copied.
|
|
|
|
### Still open
|
|
|
|
- **Bootstrapping the bootstrap.** The machine needs git, an SSH key for Gitea, and the repo URL
|
|
before any of this runs. A one-line `curl | sh` would close it, and may be deliberately not worth
|
|
having.
|
|
- **`settings.json` and hooks.** Not managed here yet. The repo is named for the docs but already
|
|
carries `tools/`, so the line is already crossed; the question is whether per-machine settings can
|
|
be expressed as one file plus conditions, or whether they are genuinely per-machine.
|
|
- **Which files are universal.** `officer.dev/` is the only machine-specific one so far. The
|
|
`platform-appstore/` set is a candidate and was never folded in.
|
|
- **PATH.** `install.sh` warns when `~/.local/bin` is missing from `PATH` but does not edit a shell
|
|
profile. Editing someone's `.zshrc` from an installer is a bigger intrusion than the warning.
|
|
|
|
### Prior art to discuss: akitaonrails/ai-memory
|
|
|
|
<https://github.com/akitaonrails/ai-memory> — read 2026-08-13. Solves a superset
|
|
of this: cross-vendor long-term memory so you can quit Claude Code mid-task and
|
|
continue in Codex without re-explaining. A Rust binary plus a data dir, served
|
|
over MCP, with `install-mcp` and `install-hooks` subcommands.
|
|
|
|
Worth stealing even if we never run it:
|
|
|
|
- **`_global` scope.** A reserved project that is unioned into every query —
|
|
standing personal rules, tech choices, style. Exactly what `user/CLAUDE.md`
|
|
is. Confirms the split we already made.
|
|
- **Page categories**: `_rules/`, `decisions/`, `procedures/`, `gotchas/`.
|
|
Better than one growing file. Our `user/CLAUDE.md` is six sections and will
|
|
not stay readable at twenty.
|
|
- **Authority-aware retrieval.** Maintained rules outrank episodic session
|
|
evidence, but do not become absolute. A good answer to "the doc says X but
|
|
the code does Y" — which bit us today with the stale `pm2` rule.
|
|
- **`.ai-memory.toml` marker file** in a repo ancestor to pin which
|
|
workspace/project a directory belongs to. Directly relevant to our manifest's
|
|
"only if this path exists" condition — a marker is more explicit than a path
|
|
test.
|
|
- **`bootstrap`** seeds pages from `git log`, README, `docs/`, module headers.
|
|
The thing we would want when adding a new machine or repo.
|
|
|
|
**His position, stated up front: not a fan of MCP, and never was.** So this is a
|
|
source of ideas, not a thing to adopt. Do not open tomorrow by proposing we
|
|
install it. Harvest the taxonomy and the concepts, build our own on plain files.
|
|
|
|
The real decision: it is a **running service** (daemon, SQLite, FTS5,
|
|
embeddings, MCP, hooks). Ours is plain files and symlinks, readable with `cat`,
|
|
and works when nothing is installed. That simplicity is a feature, especially
|
|
given today's "code on disk is not what is running here" problem. Options:
|
|
adopt it, borrow the taxonomy only, or stay flat and revisit when the flat file
|
|
gets unreadable.
|
|
|
|
### Open questions
|
|
|
|
- Which files are genuinely universal vs this-machine-only? Right now
|
|
`officer.dev/CLAUDE.md` is the only machine-specific one, but the
|
|
`platform-appstore/` set is a candidate and was never folded in.
|
|
- Should `settings.json` / hooks be managed here too, or only `.md` files? The
|
|
repo is named for the docs but already carries `tools/`.
|
|
- Bootstrapping the bootstrap: the machine needs git and the repo URL before any
|
|
of this runs. Worth a one-line `curl | sh`, or deliberately not.
|