From 688786bb85aa93867d5da631b3903c1c50e38448 Mon Sep 17 00:00:00 2001 From: Shaelz <7167970+Shaelz@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:00:35 +0200 Subject: [PATCH 1/2] harden release installation and GitHub guidance --- CHANGELOG.md | 8 ++++++++ README.md | 20 ++++++++++++++++++-- docs/github-hardening.md | 28 ++++++++++++++++++++++++++++ scripts/install-project.ps1 | 21 +++++++++++++++++---- scripts/install-project.sh | 13 ++++++++++--- scripts/install-user.ps1 | 20 ++++++++++++++++---- scripts/install-user.sh | 12 +++++++++--- scripts/verify-skill-package.ps1 | 1 + scripts/verify-skill-package.sh | 1 + 9 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 docs/github-hardening.md diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9e77e..6f9af69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- Updated the quickstart to install from the exact current stable release tag, + `v1.0.0`, and documented that the pin must move with future releases. +- Changed force installs to replace the validated destination directory so stale files + cannot survive across versions. +- Added a GitHub hardening checklist for repository-side enforcement alongside Trellis. + ## [1.0.0] - 2026-06-19 First stable release of `codebase-trellis`. diff --git a/README.md b/README.md index 11a0bf2..6fa72c3 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,16 @@ It is not a Git command wrapper. The default behavior is inspection and planning ## Quickstart -Clone this repository locally first, then run the install script from the repository root. +Clone this repository locally, check out the exact release tag, then run the install +script from the repository root. **Claude Code - PowerShell** ```powershell git clone https://github.com/Shaelz/codebase-trellis-skill.git cd codebase-trellis-skill +git fetch --tags +git checkout v1.0.0 .\scripts\install-user.ps1 ``` @@ -38,11 +41,17 @@ cd codebase-trellis-skill ```bash git clone https://github.com/Shaelz/codebase-trellis-skill.git cd codebase-trellis-skill +git fetch --tags +git checkout v1.0.0 bash scripts/install-user.sh ``` Then restart Claude Code and type `/codebase-trellis` in any project. +These commands intentionally pin the current published stable release, `v1.0.0`. +The pinned tag must be updated here whenever a newer release is published. +Advanced users may install from `main` only when intentionally testing unreleased changes. + ## Install paths | Goal | Run from | Command | @@ -54,7 +63,11 @@ Then restart Claude Code and type `/codebase-trellis` in any project. User-level install makes the skill available in all projects. Project-local install adds it only to the current project's `.claude/skills/` directory. For project-local installs, navigate to your project root first, then call the script using the full path to where you cloned this repo. -If an installation already exists, the scripts exit with an error unless you pass `-Force` (PowerShell) or `--force` (bash). +If an installation already exists, the scripts exit with an error unless you pass +`-Force` (PowerShell) or `--force` (bash). In the current unreleased source, force +mode removes the existing `codebase-trellis` skill directory after validating its exact +expected path, then copies the selected version. This prevents stale files from surviving +an upgrade. The published `v1.0.0` scripts predate this replacement behavior. ## Usage @@ -90,6 +103,9 @@ Trellis operates within four safety layers: These layers are not equivalent. A skill rule does not substitute for a protected branch. A local grep does not substitute for GitHub secret scanning. +For a practical enforcement checklist, see +[GitHub hardening for Trellis](docs/github-hardening.md). + ## What Trellis never does without explicit approval - `git add .` diff --git a/docs/github-hardening.md b/docs/github-hardening.md new file mode 100644 index 0000000..1404862 --- /dev/null +++ b/docs/github-hardening.md @@ -0,0 +1,28 @@ +# GitHub hardening for Trellis + +Trellis provides behavioral guidance, inspection, and approval gates for AI-assisted +Git work. GitHub branch protections and rulesets provide repository-side enforcement. +Use both when changes to protected branches must be controlled regardless of which +developer, agent, or local tool performs the Git operation. + +Availability varies by repository visibility, GitHub plan, and organization settings. +Use the controls available to your repository and verify the resulting rules directly. + +## Practical checklist + +- Protect `main` with a branch protection rule or ruleset. +- Require pull requests before changes can merge into `main`. +- Require the existing `verify` workflow status check before merge. +- Block force pushes to protected branches. +- Block protected branch deletion where appropriate. +- Enable secret scanning and push protection where available. +- Keep GitHub Actions permissions least-privilege and read-only by default. Grant write + access only to workflows that require it. +- Protect release tags such as `v*` from deletion or retagging where available. + +Local Git hooks and Claude Code hooks can add useful checks earlier in the workflow. +They are optional companion layers, not substitutes for GitHub protections, because +local controls can be absent, bypassed, or configured differently on another machine. + +After configuring protections, test them with a pull request and confirm that GitHub +blocks merge until the required `verify` check passes. diff --git a/scripts/install-project.ps1 b/scripts/install-project.ps1 index df78031..b76dc59 100644 --- a/scripts/install-project.ps1 +++ b/scripts/install-project.ps1 @@ -6,8 +6,9 @@ Run this from the root of the project where you want to install the skill. .PARAMETER Force - Overwrite an existing installation. Without this flag the script exits if the - destination already exists. + Replace an existing installation. Without this flag the script exits if the + destination already exists. Replacement removes the validated skill directory + before copying so stale files cannot survive. .EXAMPLE .\path\to\install-project.ps1 @@ -23,7 +24,9 @@ $ErrorActionPreference = 'Stop' $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $sourceDir = Join-Path $scriptDir '..\skills\codebase-trellis' -$destDir = Join-Path (Get-Location) '.claude\skills\codebase-trellis' +$projectRoot = [System.IO.Path]::GetFullPath((Get-Location).Path) +$expectedParent = [System.IO.Path]::GetFullPath((Join-Path $projectRoot '.claude\skills')) +$destDir = [System.IO.Path]::GetFullPath((Join-Path $expectedParent 'codebase-trellis')) $sourceDir = (Resolve-Path $sourceDir).Path @@ -35,7 +38,17 @@ if (Test-Path $destDir) { Write-Error "Destination already exists: $destDir`nRe-run with -Force to overwrite." exit 1 } - Write-Host "[-Force] Overwriting existing installation." + $hasExpectedParent = [System.StringComparer]::OrdinalIgnoreCase.Equals( + [System.IO.Path]::GetDirectoryName($destDir), + $expectedParent + ) + $hasExpectedLeaf = [System.IO.Path]::GetFileName($destDir) -eq 'codebase-trellis' + if (-not $hasExpectedParent -or -not $hasExpectedLeaf) { + Write-Error "Refusing to remove unexpected destination: $destDir" + exit 1 + } + Write-Host "[-Force] Removing existing installation." + Remove-Item -LiteralPath $destDir -Recurse -Force } if (-not (Test-Path $destDir)) { diff --git a/scripts/install-project.sh b/scripts/install-project.sh index ddb4f5d..3b6f847 100644 --- a/scripts/install-project.sh +++ b/scripts/install-project.sh @@ -11,7 +11,9 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE_DIR="$(cd "$SCRIPT_DIR/../skills/codebase-trellis" && pwd)" -DEST_DIR="$(pwd)/.claude/skills/codebase-trellis" +PROJECT_ROOT="$(pwd)" +EXPECTED_PARENT_DIR="$PROJECT_ROOT/.claude/skills" +DEST_DIR="$EXPECTED_PARENT_DIR/codebase-trellis" FORCE=false for arg in "$@"; do @@ -24,13 +26,18 @@ done echo "Source : $SOURCE_DIR" echo "Dest : $DEST_DIR" -if [ -d "$DEST_DIR" ]; then +if [ -e "$DEST_DIR" ] || [ -L "$DEST_DIR" ]; then if [ "$FORCE" = false ]; then echo "Error: destination already exists: $DEST_DIR" >&2 echo "Re-run with --force to overwrite." >&2 exit 1 fi - echo "[--force] Overwriting existing installation." + if [ "$(dirname "$DEST_DIR")" != "$EXPECTED_PARENT_DIR" ] || [ "$(basename "$DEST_DIR")" != "codebase-trellis" ]; then + echo "Error: refusing to remove unexpected destination: $DEST_DIR" >&2 + exit 1 + fi + echo "[--force] Removing existing installation." + rm -rf -- "$DEST_DIR" fi mkdir -p "$DEST_DIR" diff --git a/scripts/install-user.ps1 b/scripts/install-user.ps1 index 8463199..3bf9941 100644 --- a/scripts/install-user.ps1 +++ b/scripts/install-user.ps1 @@ -4,8 +4,9 @@ Installs the codebase-trellis skill to the user-level Claude Code skills directory. .PARAMETER Force - Overwrite an existing installation. Without this flag the script exits if the - destination already exists. + Replace an existing installation. Without this flag the script exits if the + destination already exists. Replacement removes the validated skill directory + before copying so stale files cannot survive. .EXAMPLE .\scripts\install-user.ps1 @@ -21,7 +22,8 @@ $ErrorActionPreference = 'Stop' $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $sourceDir = Join-Path $scriptDir '..\skills\codebase-trellis' -$destDir = Join-Path $HOME '.claude\skills\codebase-trellis' +$expectedParent = [System.IO.Path]::GetFullPath((Join-Path $HOME '.claude\skills')) +$destDir = [System.IO.Path]::GetFullPath((Join-Path $expectedParent 'codebase-trellis')) $sourceDir = (Resolve-Path $sourceDir).Path @@ -33,7 +35,17 @@ if (Test-Path $destDir) { Write-Error "Destination already exists: $destDir`nRe-run with -Force to overwrite." exit 1 } - Write-Host "[-Force] Overwriting existing installation." + $hasExpectedParent = [System.StringComparer]::OrdinalIgnoreCase.Equals( + [System.IO.Path]::GetDirectoryName($destDir), + $expectedParent + ) + $hasExpectedLeaf = [System.IO.Path]::GetFileName($destDir) -eq 'codebase-trellis' + if (-not $hasExpectedParent -or -not $hasExpectedLeaf) { + Write-Error "Refusing to remove unexpected destination: $destDir" + exit 1 + } + Write-Host "[-Force] Removing existing installation." + Remove-Item -LiteralPath $destDir -Recurse -Force } if (-not (Test-Path $destDir)) { diff --git a/scripts/install-user.sh b/scripts/install-user.sh index 770e4c0..1e940e8 100644 --- a/scripts/install-user.sh +++ b/scripts/install-user.sh @@ -9,7 +9,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE_DIR="$(cd "$SCRIPT_DIR/../skills/codebase-trellis" && pwd)" -DEST_DIR="$HOME/.claude/skills/codebase-trellis" +EXPECTED_PARENT_DIR="$HOME/.claude/skills" +DEST_DIR="$EXPECTED_PARENT_DIR/codebase-trellis" FORCE=false for arg in "$@"; do @@ -22,13 +23,18 @@ done echo "Source : $SOURCE_DIR" echo "Dest : $DEST_DIR" -if [ -d "$DEST_DIR" ]; then +if [ -e "$DEST_DIR" ] || [ -L "$DEST_DIR" ]; then if [ "$FORCE" = false ]; then echo "Error: destination already exists: $DEST_DIR" >&2 echo "Re-run with --force to overwrite." >&2 exit 1 fi - echo "[--force] Overwriting existing installation." + if [ "$(dirname "$DEST_DIR")" != "$EXPECTED_PARENT_DIR" ] || [ "$(basename "$DEST_DIR")" != "codebase-trellis" ]; then + echo "Error: refusing to remove unexpected destination: $DEST_DIR" >&2 + exit 1 + fi + echo "[--force] Removing existing installation." + rm -rf -- "$DEST_DIR" fi mkdir -p "$DEST_DIR" diff --git a/scripts/verify-skill-package.ps1 b/scripts/verify-skill-package.ps1 index 8c5a810..5853e9a 100644 --- a/scripts/verify-skill-package.ps1 +++ b/scripts/verify-skill-package.ps1 @@ -44,6 +44,7 @@ Check-File "scripts\check-ascii-punctuation.ps1" Check-File "docs\FUTURE_BRANCHES.md" Check-File "docs\V1_RELEASE_PLAN.md" Check-File "docs\design-decisions.md" +Check-File "docs\github-hardening.md" Check-File "docs\source-review.md" Check-File ".gitignore" Check-File "README.md" diff --git a/scripts/verify-skill-package.sh b/scripts/verify-skill-package.sh index 050ea43..61cc38f 100644 --- a/scripts/verify-skill-package.sh +++ b/scripts/verify-skill-package.sh @@ -39,6 +39,7 @@ check_file "scripts/check-ascii-punctuation.ps1" check_file "docs/FUTURE_BRANCHES.md" check_file "docs/V1_RELEASE_PLAN.md" check_file "docs/design-decisions.md" +check_file "docs/github-hardening.md" check_file "docs/source-review.md" check_file ".gitignore" check_file "README.md" From 1a236e23e148e8da4ce0705227efa88f06a8e6aa Mon Sep 17 00:00:00 2001 From: Shaelz <7167970+Shaelz@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:03:53 +0200 Subject: [PATCH 2/2] prepare v1.0.1 release --- CHANGELOG.md | 6 ++++-- README.md | 13 ++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f9af69..5fc506b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [1.0.1] - 2026-06-19 + ### Changed -- Updated the quickstart to install from the exact current stable release tag, - `v1.0.0`, and documented that the pin must move with future releases. +- Updated the quickstart to install from the exact `v1.0.1` release tag and documented + that the pin must move with future releases. - Changed force installs to replace the validated destination directory so stale files cannot survive across versions. - Added a GitHub hardening checklist for repository-side enforcement alongside Trellis. diff --git a/README.md b/README.md index 6fa72c3..36219a1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ script from the repository root. git clone https://github.com/Shaelz/codebase-trellis-skill.git cd codebase-trellis-skill git fetch --tags -git checkout v1.0.0 +git checkout v1.0.1 .\scripts\install-user.ps1 ``` @@ -42,13 +42,13 @@ git checkout v1.0.0 git clone https://github.com/Shaelz/codebase-trellis-skill.git cd codebase-trellis-skill git fetch --tags -git checkout v1.0.0 +git checkout v1.0.1 bash scripts/install-user.sh ``` Then restart Claude Code and type `/codebase-trellis` in any project. -These commands intentionally pin the current published stable release, `v1.0.0`. +These commands intentionally pin release `v1.0.1`. The pinned tag must be updated here whenever a newer release is published. Advanced users may install from `main` only when intentionally testing unreleased changes. @@ -64,10 +64,9 @@ Advanced users may install from `main` only when intentionally testing unrelease User-level install makes the skill available in all projects. Project-local install adds it only to the current project's `.claude/skills/` directory. For project-local installs, navigate to your project root first, then call the script using the full path to where you cloned this repo. If an installation already exists, the scripts exit with an error unless you pass -`-Force` (PowerShell) or `--force` (bash). In the current unreleased source, force -mode removes the existing `codebase-trellis` skill directory after validating its exact -expected path, then copies the selected version. This prevents stale files from surviving -an upgrade. The published `v1.0.0` scripts predate this replacement behavior. +`-Force` (PowerShell) or `--force` (bash). Force mode removes the existing +`codebase-trellis` skill directory after validating its exact expected path, then copies +the selected version. This prevents stale files from surviving an upgrade. ## Usage