From 3eed2895b2d23d1d8e468aee6f3dfd8122012fe4 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 5 Jun 2026 00:28:31 -0500 Subject: feat(startup): run make install in Phase A.0 to link new skills A skill added to rulesets and pushed reached each machine's files on the next pull, but not its ~/.claude symlink. make install only links what isn't already linked, and a git pull doesn't run it. So a new skill stayed silently uninstalled until someone re-ran make install by hand. The flush skill sat in that gap from 2026-06-02 until a manual install today. I added a make install step right after the Phase A.0 rulesets pull, the step that brings the skill's files in. It's idempotent: skips already-linked targets, links only what's new, and only writes symlinks under ~/.claude and ~/.local/bin. A grep keeps the all-skip case to one quiet line. Link, relink, and WARN lines get surfaced, and a new Phase C bullet handles them: a skill the harness already picked up mid-session is noted as available, one it didn't gets a restart prompt, a non-symlink collision goes to the user. Now "add a skill, commit, push" is enough to reach every machine on the next session. The step also covers a newly-added rule or script, since make install links those too. The canonical template and the .ai/ mirror both carry the change. --- .ai/workflows/startup.org | 19 +++++++++++++++++++ claude-templates/.ai/workflows/startup.org | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/.ai/workflows/startup.org b/.ai/workflows/startup.org index 16e983d..e630460 100644 --- a/.ai/workflows/startup.org +++ b/.ai/workflows/startup.org @@ -44,6 +44,24 @@ Behavior: - *Dirty working tree* → skip the pull. Don't auto-stash and don't auto-merge — those would either lose work or invite conflicts at the worst possible moment (session start). - *Non-fast-forward history* → =--ff-only= aborts with an error. Surface that to the user; the rsync still proceeds against the working tree as-is. +*** Install rulesets symlinks into ~/.claude (idempotent) + +A skill, rule, or bin script added to rulesets and pushed reaches each machine's *files* on the next pull, but not its =~/.claude= *symlink* — =make install= only links what isn't already linked, and =git pull= doesn't run it. So a newly-added skill stays silently uninstalled until someone re-runs =make install= by hand. The flush skill sat in that gap from 2026-06-02 until a manual install on 2026-06-05. Running =make install= here, right after the rulesets pull, closes it: "add a skill, commit, push" becomes enough for it to reach every machine on the next session. + +=make install= is idempotent — it skips every already-linked target, links only what's new, WARNs on a non-symlink collision, and only ever writes symlinks under =~/.claude= and =~/.local/bin=, so it's safe and reversible. It covers skills, rules, claude config, and bin scripts in one pass, so the same step also picks up a newly-added rule or script, not just a skill. + +#+begin_src bash +if [ -d "$HOME/code/rulesets" ]; then + make -C "$HOME/code/rulesets" install 2>&1 \ + | grep -E '^[[:space:]]+(link|relink|WARN)' \ + || echo "make install: nothing new to link" +else + echo "rulesets: not present — skipping make install" +fi +#+end_src + +The =grep= keeps the all-skip case quiet — a clean machine prints only "nothing new to link". Any =link= / =relink= / =WARN= line is surfaced for Phase C to act on. The placement mirrors the rulesets-pull dependency: the pull brings a new skill's files in, so the install that links them belongs immediately after. It runs in every project's session, not just rulesets sessions, which is the point — the link reaches whatever machine the session is on. + *** Refresh project repo (cwd) Pull down whatever's been pushed to the project's remotes since the last session — could be commits Craig made on another machine, teammate pushes, or any branch that advanced upstream. Without this, the session starts from a stale local view and any new branch work happens on top of an out-of-date base. @@ -166,6 +184,7 @@ This phase touches the user and runs sequentially: - Briefly note significant template updates noticed during sync (new workflows, protocol changes). - *Task-review nudge.* If the Phase A staleness count (step 11) is greater than zero, surface one line: "== top-level tasks unreviewed for >7 days — say 'let's do a task review' to run a cycle." If zero, say nothing. - *Language-bundle sync.* If the Phase A step-12 call (=sync-language-bundle.sh=) printed anything, surface it. =fixed= lines are informational — the drift was already repaired (note that =.claude/= is now dirty if the project commits it). A =drift= line on =settings.json= is surface-only and needs the printed =make install- PROJECT=.= to reconcile; flag it so the user can decide. If the call was silent, say nothing. + - *Newly-installed symlinks.* If the Phase A.0 =make install= step printed any =link= / =relink= / =WARN= line, surface it. A =link= line means a skill, rule, or script added to rulesets is now linked into =~/.claude= for the first time on this machine. For a newly-linked *skill*, check the agent's available-skills list: if the harness already registered it mid-session, note it's available and move on; if it's absent, stop and tell Craig to restart the agent so it loads (whether a mid-session reload works is harness-version-dependent). A =WARN ... not a symlink= line is a real collision at the target path — surface it; it needs a human. If the step printed only "nothing new to link", say nothing. - *Template-sync churn (safety net).* Check whether Phase A's rsync left uncommitted churn in the synced =.ai/= paths — accumulated from a prior session that crashed before wrap-up, or freshly added this session when rulesets advanced. Without surfacing, it builds up silently until it blocks Phase A.0's auto-ff (git won't ff a dirty tree). Skip in the rulesets repo itself (there =.ai/= is a committed mirror, kept honest by the pre-commit hook). The check is sequential here, after the rsync has finished — not a Phase A step, to keep that batch race-free. #+begin_src bash diff --git a/claude-templates/.ai/workflows/startup.org b/claude-templates/.ai/workflows/startup.org index 16e983d..e630460 100644 --- a/claude-templates/.ai/workflows/startup.org +++ b/claude-templates/.ai/workflows/startup.org @@ -44,6 +44,24 @@ Behavior: - *Dirty working tree* → skip the pull. Don't auto-stash and don't auto-merge — those would either lose work or invite conflicts at the worst possible moment (session start). - *Non-fast-forward history* → =--ff-only= aborts with an error. Surface that to the user; the rsync still proceeds against the working tree as-is. +*** Install rulesets symlinks into ~/.claude (idempotent) + +A skill, rule, or bin script added to rulesets and pushed reaches each machine's *files* on the next pull, but not its =~/.claude= *symlink* — =make install= only links what isn't already linked, and =git pull= doesn't run it. So a newly-added skill stays silently uninstalled until someone re-runs =make install= by hand. The flush skill sat in that gap from 2026-06-02 until a manual install on 2026-06-05. Running =make install= here, right after the rulesets pull, closes it: "add a skill, commit, push" becomes enough for it to reach every machine on the next session. + +=make install= is idempotent — it skips every already-linked target, links only what's new, WARNs on a non-symlink collision, and only ever writes symlinks under =~/.claude= and =~/.local/bin=, so it's safe and reversible. It covers skills, rules, claude config, and bin scripts in one pass, so the same step also picks up a newly-added rule or script, not just a skill. + +#+begin_src bash +if [ -d "$HOME/code/rulesets" ]; then + make -C "$HOME/code/rulesets" install 2>&1 \ + | grep -E '^[[:space:]]+(link|relink|WARN)' \ + || echo "make install: nothing new to link" +else + echo "rulesets: not present — skipping make install" +fi +#+end_src + +The =grep= keeps the all-skip case quiet — a clean machine prints only "nothing new to link". Any =link= / =relink= / =WARN= line is surfaced for Phase C to act on. The placement mirrors the rulesets-pull dependency: the pull brings a new skill's files in, so the install that links them belongs immediately after. It runs in every project's session, not just rulesets sessions, which is the point — the link reaches whatever machine the session is on. + *** Refresh project repo (cwd) Pull down whatever's been pushed to the project's remotes since the last session — could be commits Craig made on another machine, teammate pushes, or any branch that advanced upstream. Without this, the session starts from a stale local view and any new branch work happens on top of an out-of-date base. @@ -166,6 +184,7 @@ This phase touches the user and runs sequentially: - Briefly note significant template updates noticed during sync (new workflows, protocol changes). - *Task-review nudge.* If the Phase A staleness count (step 11) is greater than zero, surface one line: "== top-level tasks unreviewed for >7 days — say 'let's do a task review' to run a cycle." If zero, say nothing. - *Language-bundle sync.* If the Phase A step-12 call (=sync-language-bundle.sh=) printed anything, surface it. =fixed= lines are informational — the drift was already repaired (note that =.claude/= is now dirty if the project commits it). A =drift= line on =settings.json= is surface-only and needs the printed =make install- PROJECT=.= to reconcile; flag it so the user can decide. If the call was silent, say nothing. + - *Newly-installed symlinks.* If the Phase A.0 =make install= step printed any =link= / =relink= / =WARN= line, surface it. A =link= line means a skill, rule, or script added to rulesets is now linked into =~/.claude= for the first time on this machine. For a newly-linked *skill*, check the agent's available-skills list: if the harness already registered it mid-session, note it's available and move on; if it's absent, stop and tell Craig to restart the agent so it loads (whether a mid-session reload works is harness-version-dependent). A =WARN ... not a symlink= line is a real collision at the target path — surface it; it needs a human. If the step printed only "nothing new to link", say nothing. - *Template-sync churn (safety net).* Check whether Phase A's rsync left uncommitted churn in the synced =.ai/= paths — accumulated from a prior session that crashed before wrap-up, or freshly added this session when rulesets advanced. Without surfacing, it builds up silently until it blocks Phase A.0's auto-ff (git won't ff a dirty tree). Skip in the rulesets repo itself (there =.ai/= is a committed mirror, kept honest by the pre-commit hook). The check is sequential here, after the rsync has finished — not a Phase A step, to keep that batch race-free. #+begin_src bash -- cgit v1.2.3