Skip to content

Commit b5d9a21

Browse files
authored
feat: productionalize this repo for reuse. (#1)
* feat: productionalize this repo for reuse.
1 parent ccf9aa7 commit b5d9a21

13 files changed

Lines changed: 436 additions & 10 deletions

File tree

.github/dependabot.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# To get started with Dependabot version updates, you'll need to specify which
2+
# package ecosystems to update and where the package manifests are located.
3+
# Please see the documentation for all configuration options:
4+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5+
6+
version: 2
7+
updates:
8+
# Enable version updates for GitHub Actions
9+
- package-ecosystem: 'github-actions'
10+
directory: '/'
11+
schedule:
12+
interval: 'monthly'
13+
open-pull-requests-limit: 3
14+
rebase-strategy: auto

.github/workflows/release.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Release (Manual)
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
bump:
7+
description: Version bump type
8+
required: true
9+
type: choice
10+
options:
11+
- patch
12+
- minor
13+
- major
14+
15+
jobs:
16+
ci:
17+
uses: ./.github/workflows/test.yml
18+
19+
release:
20+
needs: ci
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: write
24+
steps:
25+
- name: Harden runner
26+
uses: step-security/harden-runner@v2
27+
with:
28+
egress-policy: block
29+
allowed-endpoints: >
30+
api.github.com:443
31+
github.com:443
32+
objects.githubusercontent.com:443
33+
uploads.github.com:443
34+
35+
- uses: actions/checkout@v6
36+
with:
37+
fetch-depth: 0
38+
39+
- name: Compute next version
40+
id: version
41+
run: |
42+
# Match tags with or without leading 'v'
43+
latest=$(git tag --sort=-version:refname \
44+
| grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' \
45+
| head -1)
46+
latest="${latest:-v0.0.0}"
47+
# Normalise to always have a 'v' prefix for parsing
48+
latest="v${latest#v}"
49+
50+
major=$(printf '%s' "$latest" | cut -d. -f1 | tr -d 'v')
51+
minor=$(printf '%s' "$latest" | cut -d. -f2)
52+
patch=$(printf '%s' "$latest" | cut -d. -f3)
53+
54+
case "${{ inputs.bump }}" in
55+
major) major=$((major + 1)); minor=0; patch=0 ;;
56+
minor) minor=$((minor + 1)); patch=0 ;;
57+
patch) patch=$((patch + 1)) ;;
58+
esac
59+
60+
echo "version=v${major}.${minor}.${patch}" >> "$GITHUB_OUTPUT"
61+
62+
- name: Tag and push
63+
run: |
64+
git config user.name "github-actions[bot]"
65+
git config user.email "github-actions[bot]@users.noreply.github.com"
66+
git tag "${{ steps.version.outputs.version }}"
67+
git push origin "${{ steps.version.outputs.version }}"
68+
69+
- name: Create GitHub release
70+
run: |
71+
VERSION="${{ steps.version.outputs.version }}"
72+
gh release create "${VERSION}" \
73+
--title "Release ${VERSION}" \
74+
--generate-notes \
75+
--verify-tag
76+
env:
77+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Tests
2+
3+
on:
4+
workflow_call:
5+
pull_request:
6+
branches:
7+
- main
8+
push:
9+
branches:
10+
- main
11+
paths:
12+
- '.claude-plugin/**'
13+
- 'plugins/slack-publish/**'
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
shell-lint:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v6
23+
24+
- name: Install shellcheck
25+
run: sudo apt-get install -y shellcheck
26+
27+
- name: Lint
28+
run: make lint
29+
30+
shell-test:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v6
34+
35+
- name: Install bats
36+
run: sudo apt-get install -y bats
37+
38+
- name: Test
39+
run: make test

.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# OS
2+
.DS_Store
3+
Thumbs.db
4+
5+
# Editor
6+
.idea/
7+
.vscode/
8+
*.swp
9+
*.swo
10+
*~
11+
12+
# Python
13+
__pycache__/
14+
*.py[cod]
15+
*.pyo
16+
.venv/
17+
venv/
18+
dist/
19+
build/
20+
*.egg-info/
21+
.pytest_cache/
22+
.ruff_cache/
23+
24+
# Shell test artifacts
25+
/tmp/
26+
*.bats.log
27+
28+
# Secrets / env
29+
.env
30+
.env.*
31+
!.env.example

.shellcheckrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
shell=bash
2+
severity=warning
3+
external-sources=true

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.PHONY: test lint sast
2+
3+
test:
4+
bats tests/
5+
6+
lint:
7+
shellcheck bin/* lib/*.sh

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# cli-tools
22

3-
Personal shell utilities for git branch hygiene and GPG cache warming.
3+
Personal shell utilities for git branch hygiene, GPG cache warming, and Claude plugin static analysis.
44

55
## Installation
66

@@ -43,13 +43,30 @@ Warms the GPG agent cache by performing a throwaway clearsign. Useful to pre-unl
4343
cache-gpg
4444
```
4545

46+
### `sast`
47+
48+
Static analysis for Claude plugin markdown files. Scans `plugins/` for risky `allowed-tools` declarations in YAML frontmatter.
49+
50+
| Severity | Check |
51+
|----------|-------|
52+
| ERROR | Bare `Bash` or `Bash(*)` — unrestricted shell access |
53+
| ERROR | Wildcard `[*]`, `Agent(*)`, or `Skill(*)` — all tools granted |
54+
| WARN | Bare `WebFetch` — any domain fetchable |
55+
56+
```sh
57+
sast
58+
```
59+
60+
Exits non-zero if any ERROR findings are found.
61+
4662
## Structure
4763

4864
```
4965
bin/
5066
cache-gpg # warms GPG agent cache
5167
gitprune # wrapper for gitprune()
5268
gitrefresh # wrapper for gitrefresh()
69+
sast # static analysis for claude plugin frontmatter
5370
lib/
5471
gitcmds.sh # shared function definitions
5572
```

bin/gitprune

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2+
# shellcheck source=lib/gitcmds.sh
3+
source "$(dirname "${BASH_SOURCE[0]}")/../lib/gitcmds.sh"
24

3-
source ../lib/gitcmds.sh
4-
5-
gitprune $1
5+
gitprune "$1"

bin/gitrefresh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2+
# shellcheck source=lib/gitcmds.sh
3+
source "$(dirname "${BASH_SOURCE[0]}")/../lib/gitcmds.sh"
24

3-
source ../lib/gitcmds.sh
4-
5-
gitrefresh $1
5+
gitrefresh "$1"

lib/gitcmds.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ gitprune () {
44

55
if [[ "$1" == "--force" ]]; then
66
echo "Force deleting unmerged branches"
7-
git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs -r git branch -D
7+
git branch -r | awk '{print $1}' | grep -Ev -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs -r git branch -D
88
else
9-
git branch -r | awk '{print $1}' | egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs -r git branch -d
9+
git branch -r | awk '{print $1}' | grep -Ev -f /dev/fd/0 <(git branch -vv | grep origin) | awk '{print $1}' | xargs -r git branch -d
1010
fi
1111

1212
git gc --prune=now
@@ -20,7 +20,7 @@ gitrefresh () {
2020
echo "Refreshing local clone."
2121
if [[ "$1" != "" ]]
2222
then
23-
git checkout $1
23+
git checkout "$1"
2424
else
2525
git checkout main
2626
fi

0 commit comments

Comments
 (0)