Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions core.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GENERATED by dotfiles-core sync-core.sh — vendored Core provenance (B1).
# Regenerate after a manual 'git subtree pull' with: make core-lock
core_version=3.6.1
core_sha=3ace724a75c6163f5bf22861fb844b67ff80a347
core_branch=3ace724a75c6163f5bf22861fb844b67ff80a347
core_tag=v3.6.1
core_version=3.7.0
core_sha=e9cf907f5849dfae2f3313d840fcbafb3772b83a
core_branch=e9cf907f5849dfae2f3313d840fcbafb3772b83a
core_tag=v3.7.0
2 changes: 1 addition & 1 deletion core/.claude/commands/drift-triage.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: Interpret fleet-drift into ranked, per-repo remediation (report-first)
argument-hint: "[repo, optional — defaults to the whole fleet]"
allowed-tools: Read, Grep, Glob, Bash, WebSearch
allowed-tools: Read, Grep, Glob, WebSearch, Bash(./scripts/fleet-drift.sh:*), Bash(git log:*), Bash(git describe:*), Bash(git tag:*)
---

# /drift-triage
Expand Down
12 changes: 9 additions & 3 deletions core/.claude/commands/freshness-triage.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: Review open dependency-bump PRs against upstream changelogs
argument-hint: "[PR number, optional — defaults to all open bot PRs]"
allowed-tools: Task, Read, Grep, Glob, WebSearch, WebFetch, Bash(./scripts/update-plugins.sh --check), Bash(./scripts/update-nvim-plugins.sh --check), Bash(git log:*), Bash(git diff:*)
allowed-tools: Task, Read, Grep, Glob, WebSearch, WebFetch, Bash(./scripts/update-plugins.sh --check), Bash(git log:*), Bash(git diff:*), Bash(gh pr list:*), Bash(gh pr view:*), Bash(gh pr diff:*), Bash(gh pr checks:*)
---

# /freshness-triage
Expand All @@ -19,13 +19,19 @@ Target for this run: **$ARGUMENTS** (empty = all open automation PRs).
`automation/freshness-zsh-plugins` and `automation/freshness-nvim-plugins`.
- **`dependabot.yml`** (weekly) — bumps GitHub Actions in `.github/workflows/`.

The `--check` modes are the source of truth for "is it behind":
For the zsh pins, `--check` is the source of truth for "is it behind" and is safe to run
(it only `git ls-remote`s + `zsh -n` parses — no upstream code runs):

```bash
./scripts/update-plugins.sh --check
./scripts/update-nvim-plugins.sh --check
```

For the **nvim** pins, do NOT run `update-nvim-plugins.sh --check` here — it runs
`nvim --headless +Lazy! sync`, which executes upstream plugin _build hooks_ inside this
token-bearing job (a supply-chain path to `CLAUDE_CODE_OAUTH_TOKEN`). Read the staleness
from the open `automation/freshness-nvim-plugins` PR the bot already opened — `gh pr diff`
/ `gh pr view` show exactly which pins moved.

## What to do per PR

1. **Identify what moved** — read the diff: which plugin/action, from which pin to
Expand Down
8 changes: 5 additions & 3 deletions core/.claude/commands/release-readiness.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: Go/no-go readiness check before cutting a Core release
argument-hint: "[target version X.Y.Z — optional]"
allowed-tools: Task, Read, Grep, Glob, Bash(./scripts/audit-core.sh:*), Bash(./scripts/fleet-drift.sh:*), Bash(./scripts/update-plugins.sh --check), Bash(./scripts/update-nvim-plugins.sh --check), Bash(git log:*), Bash(git tag:*), Bash(cat core.version)
allowed-tools: Task, Read, Grep, Glob, Bash(./scripts/audit-core.sh:*), Bash(./scripts/fleet-drift.sh:*), Bash(./scripts/update-plugins.sh --check), Bash(git log:*), Bash(git tag:*), Bash(cat core.version), Bash(gh pr list:*), Bash(gh issue list:*), Bash(gh run list:*)
---

# /release-readiness
Expand All @@ -24,8 +24,10 @@ Target for this run: **$ARGUMENTS** (empty = infer the next version from the unr
fresh one). Propose the next SemVer from the unreleased content: a breaking change → major,
a `feat` → minor, only `fix`/`chore`/`docs` → patch.
4. **Is the fleet in a releasable state?** `fleet-drift.sh` (are the OS repos on the latest
Core?) and pin freshness (`update-*-plugins.sh --check`). A release fans out, so surface
any drift or stale pins that ought to settle first — **advisory**, not hard blockers.
Core?) and pin freshness (`update-plugins.sh --check` for zsh; for nvim, note any open
`automation/freshness-nvim-plugins` PR via `gh pr list` rather than rebuilding locally,
which would run upstream build hooks in this token-bearing job). A release fans out, so
surface any drift or stale pins that ought to settle first — **advisory**, not hard blockers.
5. **Any open blockers?** Open `freshness-triage` **Hold** verdicts, a failing scheduled
sweep, or a security bump that should ride the release.

Expand Down
48 changes: 34 additions & 14 deletions core/.claude/hooks/session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,22 @@ CA=/etc/ssl/certs/ca-certificates.crt
[ -r "$CA" ] && export NODE_EXTRA_CA_CERTS="$CA"

# Helper: install a pinned release tarball to /usr/local/bin (like CI does).
install_tarball() { # install_tarball <bin> <url> <tar-flags> [member]
local bin="$1" url="$2" flags="$3" member="${4:-}"
# Downloads to a file, VERIFIES the pinned SHA-256 (fail closed — no checksum, no
# install), THEN extracts. Mirrors setup-core-tools/action.yml: the tarball must
# never be piped straight into tar unverified (a MITM'd asset would run as the gate).
install_tarball() { # install_tarball <bin> <url> <sha256> <tar-flags> [member]
local bin="$1" url="$2" sha="$3" flags="$4" member="${5:-}"
have "$bin" && return 0
local tmp
local tmp f
tmp="$(mktemp -d)"
if curl -fsSL "$url" | tar "$flags" -C "$tmp" ${member:+"$member"} 2>/dev/null; then
f="$tmp/${url##*/}"
if [ -z "$sha" ]; then
log "no pinned SHA-256 for $bin — refusing unverified install (check scripts/tool-versions.env)"
elif ! curl -fsSL -o "$f" "$url"; then
log "could not download $bin from $url"
elif ! echo "${sha} ${f}" | sha256sum -c - >/dev/null 2>&1; then
log "SHA-256 mismatch for $bin — refusing to install (expected ${sha})"
elif tar "$flags" -f "$f" -C "$tmp" ${member:+"$member"} 2>/dev/null; then
local found
found="$(find "$tmp" -type f -name "$bin" 2>/dev/null | head -n1)"
[ -n "$found" ] && $SUDO install -m755 "$found" "/usr/local/bin/$bin" 2>/dev/null
Expand All @@ -87,25 +97,35 @@ install_tarball() { # install_tarball <bin> <url> <tar-flags> [member]
have "$bin" || log "could not install $bin from $url"
}

# ── shellcheck (pinned) ───────────────────────────────────────────────────────
# ── shellcheck (pinned + verified) ────────────────────────────────────────────
[ -n "${SHELLCHECK_VERSION:-}" ] && install_tarball shellcheck \
"https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" -xJ
"https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" "${SHELLCHECK_SHA256:-}" -xJ

# ── actionlint (pinned) ───────────────────────────────────────────────────────
# ── actionlint (pinned + verified) ────────────────────────────────────────────
[ -n "${ACTIONLINT_VERSION:-}" ] && install_tarball actionlint \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" -xz actionlint
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" "${ACTIONLINT_SHA256:-}" -xz actionlint

# ── gitleaks (pinned) — the audit's secrets section runs `gitleaks dir`, so without
# this a remote session's secrets gate would SKIP: a "green because absent" for secrets,
# the exact false-green this hook exists to prevent. Same tarball pattern as above. ──
# ── gitleaks (pinned + verified) — the audit's secrets section runs `gitleaks dir`, so
# without this a remote session's secrets gate would SKIP: a "green because absent" for
# secrets, the exact false-green this hook exists to prevent. Same tarball pattern above. ──
[ -n "${GITLEAKS_VERSION:-}" ] && install_tarball gitleaks \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -xz gitleaks
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" "${GITLEAKS_SHA256:-}" -xz gitleaks

# ── neovim (pinned) — extracted to /opt, symlinked onto PATH (CI's Linux path) ─
# ── neovim (pinned + verified) — extracted to /opt, symlinked onto PATH (CI's Linux path).
# Verify the pinned SHA-256 before unpacking as root — same fail-closed rule as above. ──
if [ -n "${NVIM_VERSION:-}" ] && ! have nvim; then
if curl -fsSL "https://github.com/neovim/neovim/releases/download/v${NVIM_VERSION}/nvim-linux-x86_64.tar.gz" | $SUDO tar -xz -C /opt 2>/dev/null; then
nvim_tmp="$(mktemp -d)"
nvim_tgz="$nvim_tmp/nvim-linux-x86_64.tar.gz"
if [ -z "${NVIM_SHA256:-}" ]; then
log "no pinned SHA-256 for neovim — refusing unverified install (check scripts/tool-versions.env)"
elif ! curl -fsSL -o "$nvim_tgz" "https://github.com/neovim/neovim/releases/download/v${NVIM_VERSION}/nvim-linux-x86_64.tar.gz"; then
log "could not download neovim"
elif ! echo "${NVIM_SHA256} ${nvim_tgz}" | sha256sum -c - >/dev/null 2>&1; then
log "SHA-256 mismatch for neovim — refusing to install (expected ${NVIM_SHA256})"
elif $SUDO tar -xz -f "$nvim_tgz" -C /opt 2>/dev/null; then
$SUDO ln -sf /opt/nvim-linux-x86_64/bin/nvim /usr/local/bin/nvim 2>/dev/null
fi
rm -rf "$nvim_tmp"
have nvim || log "could not install neovim"
fi

Expand Down
26 changes: 19 additions & 7 deletions core/.github/workflows/claude-routines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ name: claude-routines
# INERT BY DEFAULT: with no CLAUDE_CODE_OAUTH_TOKEN secret the jobs no-op with a warning (so a
# fork — or this repo before you opt in — never gets a red X). Add the secret to enable.
# Scheduled runs draw on the subscription's usage; the weekly cadence keeps that predictable.
#
# LEAST PRIVILEGE: each job's `--allowedTools` MIRRORS the routine's own `allowed-tools`
# frontmatter (.claude/commands/<routine>.md) and is never broader. Critically, a job that
# reads web content (WebFetch/WebSearch) must NOT also grant unrestricted `Bash` — a prompt
# injection in fetched content could otherwise run arbitrary shell and exfiltrate
# CLAUDE_CODE_OAUTH_TOKEN. Scope Bash to the exact verbs the routine runs (see shell-review).
# For the same reason, freshness-triage / release-readiness are NOT granted
# `update-nvim-plugins.sh --check`: that script runs `nvim --headless +Lazy! sync`, which
# executes UPSTREAM plugin build hooks in this token-bearing job (a supply-chain path to the
# token). They read nvim-pin staleness from the open freshness PR via `gh` instead. The zsh
# `update-plugins.sh --check` stays — it only `git ls-remote`s + `zsh -n` parses, no code exec.

on:
schedule:
Expand Down Expand Up @@ -90,7 +101,7 @@ jobs:
claude -p "/tool-scout" \
--model opus \
--permission-mode default \
--allowedTools "Task,Read,Grep,Glob,Bash,WebSearch,WebFetch" \
--allowedTools "Task,Read,Grep,Glob,WebSearch,WebFetch" \
--output-format text | tee "$RUNNER_TEMP/tool-scout.md"
- name: File the report as an issue
run: bash .github/workflows/file-routine-issue.sh "$ISSUE_TITLE" "$RUNNER_TEMP/tool-scout.md"
Expand Down Expand Up @@ -139,7 +150,7 @@ jobs:
--add-dir "$GITHUB_WORKSPACE" \
--model opus \
--permission-mode default \
--allowedTools "Task,Read,Grep,Glob,Bash" \
--allowedTools "Task,Read,Grep,Glob,Bash(git status:*),Bash(git diff:*),Bash(git ls-files:*),Bash(git log:*),Bash(ls:*)" \
--output-format text | tee "$RUNNER_TEMP/doc-audit.md"
- name: File the report as an issue
working-directory: dotfiles-core
Expand Down Expand Up @@ -177,7 +188,7 @@ jobs:
claude -p "/freshness-triage" \
--model opus \
--permission-mode default \
--allowedTools "Task,Read,Grep,Glob,Bash,WebSearch,WebFetch" \
--allowedTools "Task,Read,Grep,Glob,WebSearch,WebFetch,Bash(./scripts/update-plugins.sh --check),Bash(git log:*),Bash(git diff:*),Bash(gh pr list:*),Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr checks:*)" \
--output-format text | tee "$RUNNER_TEMP/freshness-triage.md"
- name: File the report as an issue
run: bash .github/workflows/file-routine-issue.sh "$ISSUE_TITLE" "$RUNNER_TEMP/freshness-triage.md"
Expand Down Expand Up @@ -214,7 +225,7 @@ jobs:
claude -p "/modernize" \
--model opus \
--permission-mode default \
--allowedTools "Task,Read,Grep,Glob,Bash,WebSearch,WebFetch" \
--allowedTools "Task,Read,Grep,Glob,WebSearch,WebFetch,Bash(./scripts/check-modern.sh),Bash(git ls-files:*),Bash(git log:*)" \
--output-format text | tee "$RUNNER_TEMP/modernize.md"
- name: File the report as an issue
run: bash .github/workflows/file-routine-issue.sh "$ISSUE_TITLE" "$RUNNER_TEMP/modernize.md"
Expand All @@ -235,6 +246,7 @@ jobs:
permissions:
contents: read
issues: write
pull-requests: read # gh pr list — observe the open freshness PR + Hold verdicts
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
GH_TOKEN: ${{ github.token }}
Expand All @@ -252,7 +264,7 @@ jobs:
claude -p "/release-readiness" \
--model opus \
--permission-mode default \
--allowedTools "Task,Read,Grep,Glob,Bash,WebSearch,WebFetch" \
--allowedTools "Task,Read,Grep,Glob,Bash(./scripts/audit-core.sh:*),Bash(./scripts/fleet-drift.sh:*),Bash(./scripts/update-plugins.sh --check),Bash(git log:*),Bash(git tag:*),Bash(cat core.version),Bash(gh pr list:*),Bash(gh issue list:*),Bash(gh run list:*)" \
--output-format text | tee "$RUNNER_TEMP/release-readiness.md"
- name: File the report as an issue
run: bash .github/workflows/file-routine-issue.sh "$ISSUE_TITLE" "$RUNNER_TEMP/release-readiness.md"
Expand Down Expand Up @@ -287,7 +299,7 @@ jobs:
claude -p "/release-notes" \
--model opus \
--permission-mode default \
--allowedTools "Task,Read,Grep,Glob,Bash" \
--allowedTools "Task,Read,Grep,Glob,Bash(make release-notes),Bash(./scripts/gen-release-notes.sh:*),Bash(git log:*),Bash(git tag:*)" \
--output-format text | tee "$RUNNER_TEMP/release-notes.md"
- name: File the report as an issue
run: bash .github/workflows/file-routine-issue.sh "$ISSUE_TITLE" "$RUNNER_TEMP/release-notes.md"
Expand Down Expand Up @@ -372,7 +384,7 @@ jobs:
--add-dir "$GITHUB_WORKSPACE" \
--model opus \
--permission-mode default \
--allowedTools "Read,Grep,Glob,Bash,WebSearch" \
--allowedTools "Read,Grep,Glob,WebSearch,Bash(./scripts/fleet-drift.sh:*),Bash(git log:*),Bash(git describe:*),Bash(git tag:*)" \
--output-format text | tee "$RUNNER_TEMP/drift-triage.md"
- name: File the report as an issue
working-directory: dotfiles-core
Expand Down
65 changes: 65 additions & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,71 @@ commit (`git tag -a vX.Y.Z -m vX.Y.Z`).

## [Unreleased]

## [v3.7.0] - 2026-07-17

### Changed

- **Neovim clipboard gains a gated OSC52 last-resort provider.** When no native clipboard
backend is on PATH (no Core `clip`/`clip-paste`, no `clip.exe`, and none of `pbcopy` /
`wl-copy` / `xclip` / `xsel` / `win32yank`), `"+y`/`"+p` route over the terminal's OSC52
sequence — closing the "yank does nothing over tmux/psmux/SSH" gap on headless/remote boxes.
A working native provider is never overridden, so a normal desktop is unaffected.
- **Neovim clipboard paste uses `Get-Clipboard -Raw` on Windows**, so multi-line pastes no
longer arrive split/CRLF-mangled.
- **Windows-correct Neovim Python DAP + LuaSnip build.** debugpy/venv interpreters resolve to
`Scripts\python.exe` (and `python`, not `python3`) on Windows; LuaSnip skips its
`make install_jsregexp` build where there's no toolchain. All gated on `has("win32")`.
- **Neovim LSP capabilities fetch is `pcall`-guarded** (a blink load failure no longer aborts
the whole server stack), and `emmet_ls` now attaches to `html`.
- **Starship prompt gains venv, package, git-metrics, and WSL indicators.** `[python]` shows the
active `$virtualenv`; a `$package` version indicator and an `$env_var` slot are wired into
`format` (surfacing a previously-unplaced `ENGAGEMENT` var and a new `WSL_DISTRO_NAME` badge);
`git_metrics` is enabled; a documented opt-in `docker_version` custom is included (off by default).
- **`git_main_branch` resolves the trunk in one call.** It reads `origin/HEAD` directly
(`git symbolic-ref`) and only falls back to probing the trunk-name candidate list when
that is unset — instead of firing up to 18 `git show-ref` subprocesses every call. It sits
on the hot path (`gcom` / `gswm` / `grbm`).
- **`git_current_branch` uses the git porcelain.** It now reads `git branch --show-current`
(git 2.22+) instead of the hand-rolled `symbolic-ref` + return-code dance; the short-SHA
fallback on a detached HEAD and the empty result outside a repo are both preserved.
- **`_core_suggest` no longer forks per candidate.** `_core_lev` gained a fork-free out-var
mode, so scoring a mistyped Core verb against the alias/subcommand list runs in-process
rather than spawning a command substitution for each candidate (~80+ on a bad `please`).
- **`pullall` tallies its summary in a single `awk` pass** instead of four `grep -c` scans of
the same buffer.
- **Collapsed a dead `status-left` conditional** in `tmux/tmux.conf` to a constant — all three
branches resolved to the same colour.
- **Decoupled `--help` from header line numbers.** `update-plugins.sh`,
`update-nvim-plugins.sh`, and `freshness-dashboard.sh` now print usage from a heredoc rather
than `sed -n '<a>,<b>p' "$0"`, which silently mis-printed whenever the header comment moved
(the coupling `sync-core.sh` already documents having removed).
- **Removed redundant zsh history setopts** (`zsh/history.zsh`): `INC_APPEND_HISTORY` (implied
by `SHARE_HISTORY`) and `HIST_IGNORE_DUPS` (superseded by `HIST_IGNORE_ALL_DUPS`) were
no-ops, so they are dropped.

### Security

- **session-start hook verifies its tool downloads.** `install_tarball` and the neovim
install in `.claude/hooks/session-start.sh` now download to a file and check the pinned
SHA-256 from `scripts/tool-versions.env` before extracting — failing closed when a
checksum is absent or mismatched — instead of piping `curl … | tar` unverified. Brings the
remote-session gate toolchain in line with the CI composite action, which already verifies.
- **claude-routines run with least-privilege tools.** Each job's `--allowedTools` now mirrors
its routine's own `allowed-tools` frontmatter rather than granting unrestricted `Bash`; the
web-reading routines (tool-scout, freshness-triage, modernize, release-readiness,
drift-triage) no longer pair arbitrary shell with `WebFetch`/`WebSearch`, closing a
prompt-injection → `CLAUDE_CODE_OAUTH_TOKEN` exfiltration path. `drift-triage`'s command
frontmatter is scoped to match.
- **`HISTORY_IGNORE` covers more secret shapes** (`zsh/history.zsh`): `--flag=value` forms and
env-assignment credentials (`TOKEN=`, `PASSWORD=`, `*ACCESS_KEY=`, `APIKEY`) that the
space-only patterns let slip now stay out of the plaintext `$HISTFILE`.

### Fixed

- **Cheatsheet uses a non-deprecated highlight API.** `nvim/lua/gerrrt/cheatsheet.lua` now
calls `nvim_buf_set_extmark` instead of the deprecated `nvim_buf_add_highlight` (slated for
removal on Neovim nightly), so `:Cheatsheet` / `<leader>?` keeps working on current Neovim.

## [v3.6.1] - 2026-07-16

### Documentation
Expand Down
2 changes: 1 addition & 1 deletion core/core.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.6.1
3.7.0
Loading
Loading