onboard-project / references/what-gets-written.md
What gets written, and how to take it back
A reference shipped with the skill · 1,144 words
What gets written, and how to take it back
Everything this skill can write, where it lands, and the undo. Nothing here happens without one explicit yes, and every line of it is meant to be removable by somebody who has forgotten this skill exists.
| What | Where | Written by | Undo |
|---|---|---|---|
| The project's skill list | skills-profile.json at the repository root (committed placement) |
apply --yes |
Delete it, or git revert |
| The same list, kept out of the repository | <agents dir>/project-profiles/<encoded repo path>-<hash>.json (local placement) |
apply --yes |
Delete that file |
| The routing every session loads | .claude/rules/skill-routing.md |
apply --yes |
Delete it, or git revert |
| A git exclude line for that file | .git/info/exclude, or the shared one in a worktree (local placement only) — a row of its own in the change list |
apply --yes |
Remove the line |
| The user's placement defaults | <agents dir>/onboard-project.json |
You, by hand | Delete or edit it |
| The session-start check | A SessionStart hook in the user's settings.json |
install-check-hook.mjs, run by the user |
install-check-hook.mjs --remove |
| A one-time "onboarding exists" marker | <agents dir>/project-profiles/<encoded repo path>-<hash>.json |
check, once per repository |
Delete that file |
<agents dir> is the Skills CLI's own directory under the user's home, where its global lock file
already lives.
The local file name is Claude Code's own project-directory encoding followed by a short hash of the
full path. The encoding alone collides — work/foo-bar and work/foo/bar are one name — and in
1.0.0 it was the whole name, so two repositories could share one profile. A local profile also
records the repository it belongs to, and one naming a different repository is never read.
A 1.0.0 local profile at the old name is not read: it carries no repository, and its name is the collision itself, so nothing can say which repository wrote it. A repository onboarded locally with 1.0.0 is onboarded again, and the old file can be deleted.
The marker and a profile can live at the same name, and are told apart by content: a profile has a skill list, and the marker never does. Only the check reads the marker.
The profile
{
"version": 1,
"generatedBy": "onboard-project 1.0.0",
"scannedAt": "2026-09-14T23:50:00Z",
"placement": "committed",
"fingerprint": "sha256 over the repository signals that evaluated true",
"evidence": {
"release-notes": {
"signals": ["exists:.changeset/", "exists:CHANGELOG.md", "json:package.json#version"],
"true": ["json:package.json#version"]
}
},
"skills": {
"release-notes": {
"match": "strong",
"evidence": ["package.json has a version"],
"useWhen": "about to cut a release, bump a version, or write a CHANGELOG entry",
"required": true,
"scope": "project"
}
},
"declined": {
"github-webhooks": { "at": "2026-09-14", "fingerprint": "sha256 over that skill's own true signals" }
}
}
matchisstrong(a declared fit matched),weak(a reader suggested it from its description and said so) orgeneral(fits nearly any repository).requiredis true for strong matches only. The check warns about a required skill that is not installed, and says nothing about the others.scoperecords where the skill is installed, or where the plan would install it:projectfor a committed placement,globalfor a local one.evidenceis what the session-start check compares: for each skill that was in the catalogue at scan time, the repository signals it was evaluated on and the ones that were true. The check compares only signals that both the profile and the installed fit define, and that it could read this time — so a skill added to the catalogue, or a fit.json that edits a signal, never reads as this repository changing. A profile from 1.0.0 has noevidenceand gets no evidence check until a refresh writes one.fingerprintis the same information as one hash, kept for profiles written by 1.0.0.declinedremembers a "don't ask again" (apply --decline <names>) against the fingerprint of that skill's own true signals, so it is offered again if and only if its evidence changes.- A skill the profile lists is never removed by a scan. One whose evidence is gone stays, shown as
a
-row, untilapply --drop <names>; a weak one stays whether or not--weakis passed again. - Committed evidence carries counts and repository-relative paths only. Never an absolute path, never a file's contents, never anything read out of session history beyond a number.
The lock file beside it — skills-lock.json — belongs to the Skills CLI and says what is
installed. That is why the profile is a separate file: the CLI rewrites its lock, and anything
added to it can be dropped without warning.
The routing file
<!-- Generated by onboard-project from skills-profile.json. Do not edit; change the profile and run refresh. -->
# Skill routing for this project
These skills were chosen from evidence in this repository. Load one when the moment matches;
nothing here is enforced.
- about to cut a release, bump a version, or write a CHANGELOG entry → `release-notes`
- delegating to agents, or ending a turn with work still running in the background → `report-progress`
A rules file with no paths frontmatter loads at the start of every session at the same priority
as the project CLAUDE.md. That is the entire mechanism this skill relies on: no forcing, no hook,
no matcher — the right skills are simply in front of the agent before it goes looking.
It is rendered deterministically from the profile, which is what lets check tell a hand edit
from a stale file. Evidence stays in the profile so that the always-loaded text is one line per
skill.
The check hook
One entry per new-process matcher (startup and resume), each running:
'<node>' '<skill folder>/scripts/onboard.mjs' check --hook
It is recognised by that command rather than by its describe key, because Claude Code drops
describe whenever it rewrites settings.json — an installer that recognises its own work by the
describe stops recognising it, and orphaned hooks accumulate. A hook that merely mentions the
script is never taken: removing somebody else's hook is the one mistake here with no undo.
It prints zero bytes unless one of three things is true — a required skill is not installed,
the repository's evidence has moved, or the routing file no longer matches its profile — and then
exactly one line, wrapped in the SessionStart additionalContext envelope. It reads no session
history, never blocks, and fails open: any error at all produces no output rather than a failed
session start.
It lives in the user's settings, so it applies to every project on this machine. That is why it is always its own row in the change list, always marked, and never installed by this skill on the user's behalf.