initialise-skills
Populate and keep accurate the per-skill config.json files that the shared
agent skills (changelog, send-it, cleanup-repo, linear-sync, triage-pr,
…) read at runtime. Run inside the host repo, it detects repo facts, maps them
onto each installed skill's config schema, and writes accurate configs — without
ever clobbering a value a human deliberately set.
It is dry-run first and idempotent: the first step always previews the diff, writes happen only after you confirm, and a re-run with nothing new to detect leaves every file byte-for-byte unchanged.
How it decides what to write
For each installed skill it loads two things: the skill's own
config.example.json (which defines the set of keys to reconcile) and the
existing config.json (which may be absent on a fresh install). Each key is then
classified by a three-way comparison — example placeholder vs existing value vs
detected value:
| Status | Meaning | Action |
|---|---|---|
inferred |
No value yet, or still the example placeholder | Write the detected value |
unchanged |
Existing value already equals what we detected | No-op |
drift |
A real value that differs from detection — a deliberate edit | Keep it; report both values |
needs-manual-input |
No detector and no value (e.g. a Linear slug with no MCP) | Leave for you to supply |
manual-kept |
A real value we have no detector for | Keep it |
unknown-kept |
A key in config.json no skill template knows about |
Keep it, untouched |
Detection is keyed by config-key name, not by skill, so one detector serves
every skill that uses a key (one baseBranch detector covers changelog,
send-it; one issueKeys detector covers changelog, cleanup-repo,
linear-sync). See references/detectable-keys.md
for the full table of keys, their detection sources, and fallbacks. Changelog's
monorepo gate (affectedPackages / packageRoots) and how to flip a host
between single-package and monorepo are in
references/monorepo-config.md.
preflight is intentionally skipped: it self-detects its base branch and
workspaces and reads an optional preflight.config.json at the repo root, not
an in-bundle config.json — so there is nothing for this skill to populate. (Its
one trace here is the .gitignore step below: when preflight is installed, its
.preflight-summary.json scratch output is added to the repo's .gitignore.)
The .gitignore step
Two reconciles touch the host repo's root .gitignore:
Preflight scratch (A-569). The
preflightskill writes.preflight-summary.jsonto the repo root on every real run, so without an ignore rule it surfaces as an untracked change after a/send-itrun. Whenpreflightis installed, this skill ensures the host repo's root.gitignoreexcludes it. The edit is append-only and idempotent: it adds the commented entry only when absent (creating.gitignoreif there is none), and never reorders or removes existing lines for this entry.Skill-config ignore strip (A-812). Some consumers (notably repos spawned from
npm-package-templatebefore the A-812 fix) incorrectly gitignore.claude/skills/*/config.jsonand.agents/skills/*/config.json. That pattern is correct only in the agent-skills source repo (skills/*/config.json, A-615 — soskills add --copynever vendors ACME identity) and as a template seed guard. In a consumer, the resolvedconfig.jsonmust be committed. This skill strips those erroneous consumer patterns (and the accompanying comment block) so CI/fresh clones can load runnable config. It never touches the source-reposkills/*/config.jsonrule.
The dry-run report shows pending edits (will add … / will strip …); a re-run
after writing reports already ignored / no erroneous skill-config ignore rules.
The skills.lock step
Alongside the config reconcile, this skill emits a committed .claude/skills.lock
at the repo root — a machine-readable inventory of which skill versions are installed
and where they came from:
{ "source": "https://github.com/rheged-studio/agent-skills", "ref": "main",
"skills": { "changelog": "1.2.0", "send-it": "2.1.3", "…": "…" } }
skills— a full inventory of every installed bundle (includingpreflightand this skill), read from eachSKILL.mdmetadata.version. Keys are sorted, so a re-run with no version changes is a byte-stable no-op (the file only rewrites when a version actually moves). The lock lives at the fixed.claude/skills.lockpath regardless of where the bundles were vendored (skills/,.claude/skills/,.agents/skills/), and consumers commit it.source/ref— provenance the script cannot derive (skills.sh records nowhere where a consumer installed from). Supply them asfacts.lockSource/facts.lockRefin the write step's stdin (see step 2); an existing lock's values are preserved when omitted. When neither is available the field is written asnulland the report flags it (source/ref not supplied) — never fabricated.
This is the foundation for detecting which repos are behind — see Checking for updates below.
Process
Dry run. From the host repo root, run the bundled script for a machine-readable preview:
node <skills-dir>/initialise-skills/scripts/initialise.mjs --dry-run --json<skills-dir>is wherever the bundles are installed (e.g.skills/,.claude/skills/,.agents/skills/); the script auto-detects its siblings relative to its own location, so usually you can just run it from the repo root. Parse the JSON:skills[]with per-keystatus, plusdriftKeys,manualKeys, andtotals.Fill the facts. For each
needs-manual-inputLinear key (linearTeamName,linearWorkspaceSlug, andfollowUpProjectwhen capture is on), fetch the value via the Linear MCP when it is available —mcp__linear-server__list_teamsfor the team name,mcp__linear-server__list_projectsfor the fallback catch-all project (Rheged estate:Follow-up issues— not a per-repo home project; triage-pr inherits the PR's live Linear project when it can, A-1541), and the workspace slug from the team/organisation — otherwise ask the user. Collect these into afactsobject. Also add the lock provenance here:lockSource(the source repo the skills were installed from — the agent-skills repo URL) andlockRef(the ref installed from; defaultmain, the fleet convention, unless a tag/SHA was pinned). Skip either when an existing.claude/skills.lockalready records it — its value is preserved.Present the diff and confirm. Show the human report (re-run without
--json, or render the parsed JSON). Call out theinferredkeys that will be written, thedriftkeys that will be kept, and theneeds-manual-inputkeys. For eachdriftkey, ask whether to accept the detected value (the per-key opt-in). Gather the accepted ones into anacceptDriftmap keyed by skill name, e.g.{ "changelog": ["issueKeys"] }. This is the confirmation gate — do not write before it.Write. Re-run with
--write, piping the gathered facts and drift opt-ins as stdin JSON:echo '{"facts":{"linearTeamName":"…","linearWorkspaceSlug":"…","followUpProject":"…","lockSource":"https://github.com/rheged-studio/agent-skills","lockRef":"main"},"acceptDrift":{"changelog":["issueKeys"]}}' \ | node <skills-dir>/initialise-skills/scripts/initialise.mjs --write --jsonReport what was written from the returned
totals, plus thegitignorefield (itsstatus—added,created,present, ornegated; the field is absent entirely whenpreflightisn't installed, as the.gitignorestep is skipped) and thelockfield (itsstatus—written,unchanged, orwould-write;needsFacts: truemeanslockSource/lockRefstill need supplying).Confirm idempotency. Run the dry run once more; every key should now be
unchanged(apart from drifts you chose to keep and any still-missing manual values). Whenpreflightis installed,gitignore.statusshould bepresent(ornegated, if the repo deliberately un-ignores the file — also a stable no-op); when it isn't, the.gitignorestep is skipped and there's nogitignorefield to check.lock.statusshould beunchanged. This proves the configs, the.gitignore, and theskills.lockare stable and a future re-run is a no-op.GitHub App & token check. If this repo will run the shared Claude workflows (
reusable-claude*.ymland their caller stubs), the GitHub App must be installed and theCLAUDE_CODE_OAUTH_TOKENrepository Actions secret set — the workflows authenticate with it and fail on an empty token (A-646). The required secret isCLAUDE_CODE_OAUTH_TOKEN, notANTHROPIC_API_KEY.Probe for the secret (best-effort — skip silently if
ghis unavailable or unauthenticated; a repo that runs no Claude workflows needs neither). Run the listing and the name-check as two separate steps and read each result — do not collapse them into onegh … | greppipe, which would report the same failure for agherror and a genuine absence, hiding the can't-verify case:# step 1 — list the repo's Actions secrets; a non-zero exit here is "can't verify" gh secret list --repo <owner>/<repo> --app actions # step 2 — only when step 1 succeeded, check whether the name is in that output- present (step 1 succeeds and lists the name) → report OK; nothing to do.
- absent (step 1 succeeds but the name is missing) → warn and remind the
operator to run
/install-github-app, which installs the App and adds the secret. - can't verify (step 1 itself errors — e.g. a
403without repo-admin scope, orghnot installed) → surface it as "couldn't verify the token — please confirmCLAUDE_CODE_OAUTH_TOKENis set manually", never block or fail the run. A can't-tell is not an absence.
The App install itself can't be reliably introspected without the App's own token, so the secret's presence is the reliable proxy; the
/install-github-appreminder covers installing the App and setting the secret together.Multi-bundle repos — one manual step. If this repo itself ships several independently-versioned skill bundles,
send-it'sbundleVersioningis not auto-written (it isn't insend-it'sconfig.example.jsonkey set, so detection has nothing to populate). Add it tosend-it/config.jsonby hand —{ "root": "<bundle-dir>", "manifest": "package.json", "skillFile": "SKILL.md" }— to enable the per-bundle version-bump check. Single-package repos skip this.
Reviewing an existing config
To inspect what a repo's skills are currently configured with — without reconciling or writing anything — run the read-only review:
node <skills-dir>/initialise-skills/scripts/initialise.mjs --review
For each installed skill it prints its full config.json: every key's current
value, its classification (inferred / unchanged / drift / manual-kept /
needs-manual-input / unknown-kept — see the table above), and a one-line
description of what the key is and where its value comes from, drawn from
references/detectable-keys.md. Keys a consumer
set that no skill template knows about show as unknown-kept (kept verbatim, no
description), and template keys not yet present in config.json show as
— not set, so the review is the whole picture rather than just the pending
diff a dry-run would show. The human text shows each key's used by … — <detection source> line, and — for an unset key — the fallback: default that
applies until it's configured (set keys omit it, since the live value already
shows what's in effect). Add --json for the
machine-readable form (a skills[] array of { key, value, isSet, status, usedBy, detectionSource, fallback } entries, plus totals). It never writes to
disk and skips the .gitignore step.
Changing a setting later
Once a consumer's config.json exists, hand-editing it is a supported way to
change a setting — you don't have to route every change through this skill. Open
skills/<name>/config.json (or wherever the bundle is vendored), change the value,
and save. It is a real file the consumer owns; the shared skills read it at runtime.
A manual edit like that survives future initialise-skills re-runs. On the next
run the reconcile classifies your value as drift — a real value that differs from
what detection would produce — and keeps it, reporting both the kept value and
the detected one (see the status table above). It is
never silently overwritten: drift is only replaced if you explicitly opt in for that
key (the per-key acceptDrift gate in step 3). So a deliberate manual edit and a
detected fact coexist — the tool reconciles the facts it can detect without clobbering
the ones you set by hand.
Prefer --set <skill>.<key>=<value> below when you
want the same change made through the tool — it validates the key against the
skill's config.example.json and preserves key order and formatting — but a direct
hand-edit is equally valid and equally safe.
Setting an arbitrary value
Detection, the stdin facts, and acceptDrift between them cover every value the
script can derive or accept — but not a value you simply want to choose (a
non-default base branch, a bespoke changelog directory, a boolean toggle). For
those, --set <skill>.<key>=<value> writes an arbitrary value straight into a
named skill's config.json:
# dry-run first (default) — preview the change, write nothing
node <skills-dir>/initialise-skills/scripts/initialise.mjs \
--set changelog.baseBranch=develop \
--set changelog.affectedPackages=false
# re-run with --write to apply
node <skills-dir>/initialise-skills/scripts/initialise.mjs \
--set changelog.baseBranch=develop --write
The flag is repeatable and the address is <skill>.<key> — the skill's bundle
directory name, then a top-level key. The value is parsed as JSON (true / 42 /
["A"] type correctly) and falls back to a bare string when it isn't valid JSON
(so develop stays "develop"). It is validated up front, before anything is
written: the skill must be installed, the key must exist in that skill's
config.example.json (unknown keys are refused, never silently created), and
the value's type must match that key's example placeholder (so a string can't land
in a boolean field). Any failure exits non-zero and touches nothing.
--set rides the normal reconcile — detection still runs and your values are
layered on top, winning over what a detector would produce for the same key — and
goes through the same merge/serialise path, so key order and formatting are
preserved and a re-run with the same value is a no-op. It is a write mode, so it
cannot be combined with --review (which is read-only). In the report a set
key shows as set to <value> (was <old>).
Flags
--dry-run(default) — detect, merge and report; write nothing.--write— apply the reconcile to each skill'sconfig.json.--review— read-only. Print every installed skill's full current config: each key's current value, its classification (the same six statuses), and a short description sourced fromreferences/detectable-keys.md. Unlike the dry-run it shows the current value of every key — includingunknown-keptkeys no template knows about — so it is a complete picture, not just the pending diff. Writes nothing and skips the.gitignorestep. See Reviewing an existing config.--set <skill>.<key>=<value>— repeatable. Write an arbitrary value into a named skill'sconfig.json(a value detection wouldn't produce). The key must exist in that skill'sconfig.example.jsonand the value's type must match its placeholder, else it's refused. Rides the normal reconcile (dry-run first;--writeto apply) and overrides detection for that key. Cannot be combined with--review. See Setting an arbitrary value.--json— emit the machine-readable report (parse this to drive steps 2–3, or to consume the--reviewsnapshot); human text otherwise.--repo-root <path>— the host repo the detectors scan (default: cwd).--skills-dir <path>— where the sibling bundles live (default: auto-detected relative to this script).- stdin JSON —
{ "facts": { … }, "acceptDrift": { "<skill>": ["<key>"] } }, read when stdin is piped (not a TTY). EachacceptDriftkey may be a skill name ("changelog") or the repo-relative config path ("skills/changelog/config.json"); its value is an array of key names.factsalso carries the lock provenancelockSource/lockRef(see step 2).
Checking for updates
To see which installed skills are behind the source repo, run the bundled
check-updates.mjs against a checkout of the source (the consumer holds only its
old vendored copies, so the target versions come from the source):
node <skills-dir>/initialise-skills/scripts/check-updates.mjs \
--source <path-to-agent-skills-checkout> [--ref <tag-or-sha>] [--json]
It diffs the consumer's .claude/skills.lock against the source's bundle versions —
at --ref (via git show) when given, else the source working tree — and prints
the per-skill bump list: updates (behind — the actionable list), plus added
(new upstream skills), removed, downgrades (consumer ahead), and upToDate.
--lock <path> targets a specific consumer's lock (default <cwd>/.claude/skills.lock),
so a fleet orchestrator can check any repo without changing directory. See
references/skills-lock.md for the lock schema.
Safety
- Dry-run first, write only after confirmation. Nothing is written without an
explicit
--writepass gated on the user's go-ahead. - Never clobbers deliberate edits. Drift is preserved unless you opt in per key.
- No deletes, no reordering. Existing keys keep their order; only changed keys
are touched; consumer-added keys are left alone. A malformed existing
config.jsonis skipped (reported, never overwritten). - The
.gitignoreedits are scoped. Outside a skill'sconfig.json, this skill touches the repo's root.gitignoreto (1) append.preflight-summary.jsonwhen missing (A-569 — never reordering or removing existing lines for that entry) and (2) strip erroneous.claude/.agentsskill-config ignore patterns that would prevent consumers from committing resolvedconfig.json(A-812). It never touches the agent-skills source ruleskills/*/config.json. - The
skills.lockwrite is deterministic and byte-stable. The other file touched outside aconfig.jsonis.claude/skills.lock, fully regenerated with sorted keys and no timestamp — so it only rewrites when a version actually changes, and a no-op run leaves it byte-identical. It preserves an existing lock'ssource/refand never fabricates them. - The GitHub App / token probe is read-only.
gh secret listreturns secret names only, never values, and the skill makes no GitHub writes of any kind — on an absent or unverifiable secret it only ever prints a reminder.
Prerequisites
- The skills whose configs you want populated are installed alongside this one.
- A git repository with an
originremote for full base-branch / issue-key detection (both degrade to sensible fallbacks when absent). - The Linear MCP server for the team name / workspace slug (optional — those two keys are flagged for manual input without it).
- The
ghCLI authenticated with repo-admin scope enables the GitHub App / token probe (step 6). Without that scope (or withoutghat all) the probe can't read the secret list, so it degrades to a "couldn't verify — confirmCLAUDE_CODE_OAUTH_TOKENmanually" note — a can't-tell, never a failure. The textual/install-github-appreminder is the separate absent outcome, emitted only when the probe succeeds and finds the secret genuinely missing. Either way the skill still runs fully.