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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ 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 `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.

## [1.0.0] - 2026-06-19

First stable release of `codebase-trellis`.
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.1
.\scripts\install-user.ps1
```

Expand All @@ -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.1
bash scripts/install-user.sh
```

Then restart Claude Code and type `/codebase-trellis` in any project.

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.

## Install paths

| Goal | Run from | Command |
Expand All @@ -54,7 +63,10 @@ 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). 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

Expand Down Expand Up @@ -90,6 +102,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 .`
Expand Down
28 changes: 28 additions & 0 deletions docs/github-hardening.md
Original file line number Diff line number Diff line change
@@ -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.
21 changes: 17 additions & 4 deletions scripts/install-project.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)) {
Expand Down
13 changes: 10 additions & 3 deletions scripts/install-project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
20 changes: 16 additions & 4 deletions scripts/install-user.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)) {
Expand Down
12 changes: 9 additions & 3 deletions scripts/install-user.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions scripts/verify-skill-package.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions scripts/verify-skill-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading