# SPDX-License-Identifier: PMPL-1.0-or-later
# Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} ({{OWNER}}) <{{AUTHOR_EMAIL}}>
#
# RSR Standard Justfile Template
# https://just.systems/man/en/
#
# Copy this file to new projects and customize the placeholder values.
#
# Run `just` to see all available recipes
# Run `just cookbook` to generate docs/just-cookbook.adoc
# Run `just combinations` to see matrix recipe options
set shell := ["bash", "-uc"]
set dotenv-load := true
set positional-arguments := true
# Import auto-generated contractile recipes (must-check, trust-verify, etc.)
# Re-generate with: contractile gen-just
import? "contractile.just"
# Project metadata — customize these
project := "{{PROJECT_NAME}}"
version := "0.1.0"
tier := "infrastructure" # 1 | 2 | infrastructure
# ═══════════════════════════════════════════════════════════════════════════════
# DEFAULT & HELP
# ═══════════════════════════════════════════════════════════════════════════════
# Show all available recipes with descriptions
default:
@just --list --unsorted
# Show detailed help for a specific recipe
help recipe="":
#!/usr/bin/env bash
if [ -z "{{recipe}}" ]; then
just --list --unsorted
echo ""
echo "Usage: just help <recipe>"
echo " just cookbook # Generate full documentation"
echo " just combinations # Show matrix recipes"
else
just --show "{{recipe}}" 2>/dev/null || echo "Recipe '{{recipe}}' not found"
fi
# Show this project's info
info:
@echo "Project: {{project}}"
@echo "Version: {{version}}"
@echo "RSR Tier: {{tier}}"
@echo "Recipes: $(just --summary | wc -w)"
@[ -f ".machine_readable/STATE.a2ml" ] && grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/STATE.a2ml | head -1 | xargs -I{} echo "Phase: {}" || true
# ═══════════════════════════════════════════════════════════════════════════════
# INIT — Bootstrap a new project from this template
# ═══════════════════════════════════════════════════════════════════════════════
# Interactive project bootstrap — replaces all {{PLACEHOLDER}} tokens
init:
#!/usr/bin/env bash
set -euo pipefail
echo "═══════════════════════════════════════════════════"
echo " RSR Project Bootstrap"
echo "═══════════════════════════════════════════════════"
echo ""
# --- Load defaults from config (if exists) ---
# Create yours: ~/.config/rsr/defaults
# Format: OWNER=myorg AUTHOR="My Name" AUTHOR_EMAIL=me@example.org ...
DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults"
if [ -f "$DEFAULTS" ]; then
echo "Loading defaults from $DEFAULTS"
# shellcheck source=/dev/null
source "$DEFAULTS"
echo ""
fi
# --- Required values (pre-filled from defaults if available) ---
read -rp "Project name (human-readable, e.g. My Project): " PROJECT_NAME
[ -z "$PROJECT_NAME" ] && echo "Error: project name required" && exit 1
read -rp "Repository slug (e.g. my-project): " REPO
[ -z "$REPO" ] && echo "Error: repo slug required" && exit 1
read -rp "Owner [${OWNER:-}]: " _OWNER
OWNER="${_OWNER:-${OWNER:-}}"
[ -z "$OWNER" ] && echo "Error: owner required" && exit 1
read -rp "Author full name [${AUTHOR:-}]: " _AUTHOR
AUTHOR="${_AUTHOR:-${AUTHOR:-}}"
[ -z "$AUTHOR" ] && echo "Error: author name required" && exit 1
read -rp "Author email [${AUTHOR_EMAIL:-}]: " _AUTHOR_EMAIL
AUTHOR_EMAIL="${_AUTHOR_EMAIL:-${AUTHOR_EMAIL:-}}"
[ -z "$AUTHOR_EMAIL" ] && echo "Error: email required" && exit 1
# --- Optional values (pre-filled from defaults if available) ---
read -rp "Author organization [${AUTHOR_ORG:-none}]: " _AUTHOR_ORG
AUTHOR_ORG="${_AUTHOR_ORG:-${AUTHOR_ORG:-}}"
read -rp "Previous/alt email [${AUTHOR_EMAIL_ALT:-none}]: " _AUTHOR_EMAIL_ALT
AUTHOR_EMAIL_ALT="${_AUTHOR_EMAIL_ALT:-${AUTHOR_EMAIL_ALT:-}}"
read -rp "Project description []: " PROJECT_DESCRIPTION
read -rp "Forge domain [${FORGE:-github.com}]: " _FORGE
FORGE="${_FORGE:-${FORGE:-github.com}}"
read -rp "Security contact email [${SECURITY_EMAIL:-$AUTHOR_EMAIL}]: " _SECURITY_EMAIL
SECURITY_EMAIL="${_SECURITY_EMAIL:-${SECURITY_EMAIL:-$AUTHOR_EMAIL}}"
read -rp "Conduct contact email [${CONDUCT_EMAIL:-$AUTHOR_EMAIL}]: " _CONDUCT_EMAIL
CONDUCT_EMAIL="${_CONDUCT_EMAIL:-${CONDUCT_EMAIL:-$AUTHOR_EMAIL}}"
read -rp "Project type (library|binary|monorepo|service|website) [library]: " PROJECT_TYPE
PROJECT_TYPE="${PROJECT_TYPE:-library}"
read -rp "Website URL [https://${FORGE}/${OWNER}/${REPO}]: " WEBSITE
WEBSITE="${WEBSITE:-https://${FORGE}/${OWNER}/${REPO}}"
# --- Container values (optional — only relevant if container/ exists) ---
if [ -d "container" ]; then
echo ""
echo "── Container configuration (optional) ─────────"
read -rp "Service name [${REPO}]: " _SERVICE_NAME
SERVICE_NAME="${_SERVICE_NAME:-${REPO}}"
read -rp "Primary port [8080]: " _PORT
PORT="${_PORT:-8080}"
read -rp "Container registry [ghcr.io/${OWNER}]: " _REGISTRY
REGISTRY="${_REGISTRY:-ghcr.io/${OWNER}}"
else
SERVICE_NAME="${REPO}"
PORT="8080"
REGISTRY="ghcr.io/${OWNER}"
fi
# --- Derived values ---
PROJECT_UPPER=$(echo "$REPO" | tr '[:lower:]-' '[:upper:]_')
PROJECT_LOWER=$(echo "$REPO" | tr '[:upper:]-' '[:lower:]_')
CURRENT_YEAR=$(date +%Y)
CURRENT_DATE=$(date +%Y-%m-%d)
VERSION="0.1.0"
# Derive citation name parts (best-effort split on last space)
AUTHOR_LAST="${AUTHOR##* }"
AUTHOR_FIRST="${AUTHOR% *}"
FIRST_INITIAL="${AUTHOR_FIRST:0:1}."
if [ "$AUTHOR_LAST" = "$AUTHOR_FIRST" ]; then
AUTHOR_FIRST="$AUTHOR"
AUTHOR_LAST=""
FIRST_INITIAL=""
fi
echo ""
echo "── Summary ──────────────────────────────────────"
echo " Project: $PROJECT_NAME"
echo " Repo: $REPO"
echo " Owner: $OWNER"
echo " Author: $AUTHOR <$AUTHOR_EMAIL>"
[ -n "$AUTHOR_ORG" ] && echo " Organization: $AUTHOR_ORG"
echo " Forge: $FORGE"
echo " Year: $CURRENT_YEAR"
echo "────────────────────────────────────────────────"
echo ""
read -rp "Proceed? [Y/n] " CONFIRM
[[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0
echo ""
echo "Replacing placeholders..."
# Brace tokens as variables (hex avoids just interpolation)
LB=$(printf '\x7b\x7b')
RB=$(printf '\x7d\x7d')
# Build the sed expression list
# Note: using | as delimiter since URLs contain /
SED_ARGS=(
-e "s|${LB}PROJECT_NAME${RB}|${PROJECT_NAME}|g"
-e "s|${LB}PROJECT_DESCRIPTION${RB}|${PROJECT_DESCRIPTION}|g"
-e "s|${LB}PROJECT${RB}|${PROJECT_UPPER}|g"
-e "s|${LB}project${RB}|${PROJECT_LOWER}|g"
-e "s|${LB}REPO${RB}|${REPO}|g"
-e "s|${LB}OWNER${RB}|${OWNER}|g"
-e "s|${LB}AUTHOR${RB}|${AUTHOR}|g"
-e "s|${LB}AUTHOR_EMAIL${RB}|${AUTHOR_EMAIL}|g"
-e "s|${LB}AUTHOR_ORG${RB}|${AUTHOR_ORG}|g"
-e "s|${LB}AUTHOR_LAST${RB}|${AUTHOR_LAST}|g"
-e "s|${LB}AUTHOR_FIRST${RB}|${AUTHOR_FIRST}|g"
-e "s|${LB}AUTHOR_INITIALS${RB}|${FIRST_INITIAL}|g"
-e "s|${LB}FORGE${RB}|${FORGE}|g"
-e "s|${LB}CURRENT_YEAR${RB}|${CURRENT_YEAR}|g"
-e "s|${LB}CURRENT_DATE${RB}|${CURRENT_DATE}|g"
-e "s|${LB}DATE${RB}|${CURRENT_DATE}|g"
-e "s|${LB}SECURITY_EMAIL${RB}|${SECURITY_EMAIL}|g"
-e "s|${LB}CONDUCT_EMAIL${RB}|${CONDUCT_EMAIL}|g"
-e "s|${LB}LICENSE${RB}|PMPL-1.0-or-later|g"
-e "s|${LB}CONDUCT_TEAM${RB}|Code of Conduct Committee|g"
-e "s|${LB}RESPONSE_TIME${RB}|48 hours|g"
-e "s|${LB}MAIN_BRANCH${RB}|main|g"
-e "s|${LB}PROJECT_PURPOSE${RB}|${PROJECT_DESCRIPTION}|g"
-e "s|${LB}PROJECT_ROLE${RB}|${PROJECT_TYPE}|g"
-e "s|${LB}PROJECT_TYPE${RB}|${PROJECT_TYPE}|g"
-e "s|${LB}WEBSITE${RB}|${WEBSITE}|g"
-e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g"
-e "s|${LB}PORT${RB}|${PORT}|g"
-e "s|${LB}REGISTRY${RB}|${REGISTRY}|g"
-e "s|${LB}IMAGE${RB}|${REGISTRY}/${SERVICE_NAME}|g"
-e "s|${LB}VERSION${RB}|${VERSION}|g"
-e "s|${LB}EMAIL${RB}|${AUTHOR_EMAIL}|g"
)
[ -n "$AUTHOR_EMAIL_ALT" ] && SED_ARGS+=(-e "s|${LB}AUTHOR_EMAIL_ALT${RB}|${AUTHOR_EMAIL_ALT}|g")
# Replace in all text files (skip .git, LICENSE text, and binaries)
find . -type f \
-not -path './.git/*' \
-not -name 'PMPL-1.0-or-later.txt' \
-not -name '*.png' -not -name '*.jpg' -not -name '*.gif' \
-not -name '*.woff' -not -name '*.woff2' \
| while read -r file; do
if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then
sed -i "${SED_ARGS[@]}" "$file"
fi
done
# Also replace [YOUR-REPO-NAME] and [YOUR-NAME/ORG] in AI manifest
sed -i "s|\[YOUR-REPO-NAME\]|${PROJECT_NAME}|g" 0-AI-MANIFEST.a2ml 2>/dev/null || true
sed -i "s|\[YOUR-NAME/ORG\]|${OWNER}|g" 0-AI-MANIFEST.a2ml 2>/dev/null || true
echo ""
echo "── Validation ───────────────────────────────────"
# Check for remaining placeholders
PATTERN="${LB}[A-Z_]*${RB}"
REMAINING=$(grep -rl "$PATTERN" . --include='*.md' --include='*.adoc' --include='*.yml' --include='*.yaml' --include='*.a2ml' --include='*.toml' --include='*.scm' --include='*.ncl' --include='*.nix' --include='*.json' --include='*.sh' 2>/dev/null | grep -v '.git/' | grep -v '.machine_readable/ai/PLACEHOLDERS.adoc' || true)
if [ -n "$REMAINING" ]; then
echo "WARNING: Remaining placeholders in:"
echo "$REMAINING" | sed 's/^/ /'
echo ""
echo "Run: grep -rn '$LB' . --include='*.md' to inspect"
else
echo "All placeholders replaced successfully!"
fi
# K9-SVC validation (if available)
if command -v k9-svc >/dev/null 2>&1; then
echo ""
echo "Running k9-svc validation..."
k9-svc validate . 2>/dev/null || true
fi
echo ""
echo "Done! Next steps:"
echo " 1. Review changes: git diff"
echo " 2. Remove template cruft: rm .machine_readable/ai/PLACEHOLDERS.adoc"
echo " 3. Customize README.adoc for your project"
echo " 4. Commit: git add -A && git commit -m 'feat: initialize from RSR template'"
echo " 5. Push: git remote add origin git@${FORGE}:${OWNER}/${REPO}.git && git push -u origin main"
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD & COMPILE
# ═══════════════════════════════════════════════════════════════════════════════
# Build the project (debug mode)
build *args:
@echo "Building {{project}} (debug)..."
# TODO: Replace with your build command
# Examples:
# cargo build {{args}} # Rust
# mix compile {{args}} # Elixir
# zig build {{args}} # Zig
# deno task build {{args}} # Deno/ReScript
@echo "Build complete"
# Build in release mode with optimizations
build-release *args:
@echo "Building {{project}} (release)..."
# TODO: Replace with your release build command
# Examples:
# cargo build --release {{args}}
# MIX_ENV=prod mix compile {{args}}
# zig build -Doptimize=ReleaseFast {{args}}
@echo "Release build complete"
# Build and watch for changes (requires entr or similar)
build-watch:
@echo "Watching for changes..."
# TODO: Customize file patterns for your language
# Examples:
# find src -name '*.rs' | entr -c just build
# mix compile --force --warnings-as-errors
# deno task dev
# Clean build artifacts [reversible: rebuild with `just build`]
clean:
@echo "Cleaning..."
# TODO: Customize for your build system
rm -rf target/ _build/ build/ dist/ out/ obj/ bin/
# Deep clean including caches [reversible: rebuild]
clean-all: clean
rm -rf .cache .tmp
# ═══════════════════════════════════════════════════════════════════════════════
# TEST & QUALITY
# ═══════════════════════════════════════════════════════════════════════════════
# Run all tests
test *args:
@echo "Running tests..."
# TODO: Replace with your test command
# Examples:
# cargo test {{args}}
# mix test {{args}}
# zig build test {{args}}
# deno test {{args}}
@echo "Tests passed!"
# Run tests with verbose output
test-verbose:
@echo "Running tests (verbose)..."
# TODO: Replace with verbose test command
# Smoke test
test-smoke:
@echo "Smoke test..."
# TODO: Add basic sanity checks
# Run all quality checks
quality: fmt-check lint test
@echo "All quality checks passed!"
# Fix all auto-fixable issues [reversible: git checkout]
fix: fmt
@echo "Fixed all auto-fixable issues"
# ═══════════════════════════════════════════════════════════════════════════════
# LINT & FORMAT
# ═══════════════════════════════════════════════════════════════════════════════
# Format all source files [reversible: git checkout]
fmt:
@echo "Formatting source files..."
# TODO: Replace with your formatter
# Examples:
# cargo fmt
# mix format
# gleam format
# deno fmt
# Check formatting without changes
fmt-check:
@echo "Checking formatting..."
# TODO: Replace with your format check
# Examples:
# cargo fmt --check
# mix format --check-formatted
# gleam format --check
# Run linter
lint:
@echo "Linting source files..."
# TODO: Replace with your linter
# Examples:
# cargo clippy -- -D warnings
# mix credo --strict
# gleam check
# ═══════════════════════════════════════════════════════════════════════════════
# RUN & EXECUTE
# ═══════════════════════════════════════════════════════════════════════════════
# Run the application
run *args: build
# TODO: Replace with your run command
echo "Run not configured yet"
# Run with verbose output
run-verbose *args: build
# TODO: Replace with verbose run command
echo "Run not configured yet"
# Install to user path
install: build-release
@echo "Installing {{project}}..."
# TODO: Replace with your install command
# ═══════════════════════════════════════════════════════════════════════════════
# DEPENDENCIES
# ═══════════════════════════════════════════════════════════════════════════════
# Install/check all dependencies
deps:
@echo "Checking dependencies..."
# TODO: Replace with your dependency check
# Examples:
# cargo check
# mix deps.get
# gleam deps download
@echo "All dependencies satisfied"
# Audit dependencies for vulnerabilities
deps-audit:
@echo "Auditing for vulnerabilities..."
# TODO: Replace with your audit command
# Examples:
# cargo audit
# mix audit
@command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true
@command -v gitleaks >/dev/null && gitleaks detect --source . --no-git --quiet || true
@echo "Audit complete"
# ═══════════════════════════════════════════════════════════════════════════════
# DOCUMENTATION
# ═══════════════════════════════════════════════════════════════════════════════
# Generate all documentation
docs:
@mkdir -p docs/generated docs/man
just cookbook
just man
@echo "Documentation generated in docs/"
# Generate justfile cookbook documentation
cookbook:
#!/usr/bin/env bash
mkdir -p docs
OUTPUT="docs/just-cookbook.adoc"
echo "= {{project}} Justfile Cookbook" > "$OUTPUT"
echo ":toc: left" >> "$OUTPUT"
echo ":toclevels: 3" >> "$OUTPUT"
echo "" >> "$OUTPUT"
echo "Generated: $(date -Iseconds)" >> "$OUTPUT"
echo "" >> "$OUTPUT"
echo "== Recipes" >> "$OUTPUT"
echo "" >> "$OUTPUT"
just --list --unsorted | while read -r line; do
if [[ "$line" =~ ^[[:space:]]+([a-z_-]+) ]]; then
recipe="${BASH_REMATCH[1]}"
echo "=== $recipe" >> "$OUTPUT"
echo "" >> "$OUTPUT"
echo "[source,bash]" >> "$OUTPUT"
echo "----" >> "$OUTPUT"
echo "just $recipe" >> "$OUTPUT"
echo "----" >> "$OUTPUT"
echo "" >> "$OUTPUT"
fi
done
echo "Generated: $OUTPUT"
# Generate man page
man:
#!/usr/bin/env bash
mkdir -p docs/man
cat > docs/man/{{project}}.1 << EOF
.TH {{project}} 1 "$(date +%Y-%m-%d)" "{{version}}" "{{project}} Manual"
.SH NAME
{{project}} \- RSR-compliant project
.SH SYNOPSIS
.B just
[recipe] [args...]
.SH DESCRIPTION
RSR (Rhodium Standard Repository) project managed with just.
.SH AUTHOR
$(git config user.name 2>/dev/null || echo "Author") <$(git config user.email 2>/dev/null || echo "email")>
EOF
echo "Generated: docs/man/{{project}}.1"
# ═══════════════════════════════════════════════════════════════════════════════
# CONTAINERS (stapeln ecosystem — Podman + Chainguard Wolfi)
# ═══════════════════════════════════════════════════════════════════════════════
# Initialise container templates — substitute placeholders with project values
container-init:
#!/usr/bin/env bash
set -euo pipefail
if [ ! -d "container" ]; then
echo "Error: container/ directory not found."
echo "This repo may not have been created from rsr-template-repo."
exit 1
fi
echo "=== Container Template Initialisation ==="
echo ""
# Load RSR defaults if available
DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults"
if [ -f "$DEFAULTS" ]; then
echo "Loading defaults from $DEFAULTS"
# shellcheck source=/dev/null
source "$DEFAULTS"
echo ""
fi
# Prompt for container-specific values
read -rp "Service name (e.g. my-api) [{{project}}]: " _SERVICE_NAME
SERVICE_NAME="${_SERVICE_NAME:-{{project}}}"
read -rp "Primary port [8080]: " _PORT
PORT="${_PORT:-8080}"
read -rp "Container registry [ghcr.io/${OWNER:-{{OWNER}}}]: " _REGISTRY
REGISTRY="${_REGISTRY:-ghcr.io/${OWNER:-{{OWNER}}}}"
echo ""
echo " Service: $SERVICE_NAME"
echo " Port: $PORT"
echo " Registry: $REGISTRY"
echo ""
read -rp "Proceed? [Y/n] " CONFIRM
[[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0
echo ""
echo "Replacing container placeholders..."
# Brace tokens as variables (hex escapes avoid just interpolation)
LB=$(printf '\x7b\x7b')
RB=$(printf '\x7d\x7d')
SED_ARGS=(
-e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g"
-e "s|${LB}PORT${RB}|${PORT}|g"
-e "s|${LB}REGISTRY${RB}|${REGISTRY}|g"
)
find container/ -type f | while read -r file; do
if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then
sed -i "${SED_ARGS[@]}" "$file"
fi
done
echo "Container templates initialised."
echo ""
echo "Next steps:"
echo " 1. Edit container/Containerfile — add your build commands"
echo " 2. Edit container/entrypoint.sh — set your application binary"
echo " 3. Review container/compose.toml — adjust services and volumes"
echo " 4. Build: just container-build"
# Build container image via cerro-torre pipeline
container-build *args:
#!/usr/bin/env bash
if [ -f "container/ct-build.sh" ]; then
cd container && ./ct-build.sh {{args}}
elif [ -f "container/Containerfile" ]; then
podman build -t {{project}}:latest -f container/Containerfile .
elif [ -f "Containerfile" ]; then
podman build -t {{project}}:latest -f Containerfile .
else
echo "No Containerfile found in container/ or project root"
exit 1
fi
# Verify compose configuration
container-verify:
#!/usr/bin/env bash
if [ ! -f "container/compose.toml" ]; then
echo "No container/compose.toml found"
exit 1
fi
cd container
if command -v selur-compose &>/dev/null; then
selur-compose verify
else
echo "selur-compose not found, falling back to podman compose"
podman compose --file compose.toml config
fi
# Start container stack
container-up *args:
#!/usr/bin/env bash
if [ ! -f "container/compose.toml" ]; then
echo "No container/compose.toml found"
exit 1
fi
cd container
if command -v selur-compose &>/dev/null; then
selur-compose up {{args}}
else
podman compose --file compose.toml up {{args}}
fi
# Stop container stack
container-down:
#!/usr/bin/env bash
cd container 2>/dev/null || { echo "No container/ directory"; exit 1; }
if command -v selur-compose &>/dev/null; then
selur-compose down
else
podman compose --file compose.toml down
fi
# Sign and verify container bundle (build + pack + sign + verify)
container-sign:
#!/usr/bin/env bash
if [ -f "container/ct-build.sh" ]; then
cd container && ./ct-build.sh
else
echo "No container/ct-build.sh found"
exit 1
fi
# Push signed bundle to registry
container-push:
#!/usr/bin/env bash
if [ -f "container/ct-build.sh" ]; then
cd container && ./ct-build.sh --push
else
echo "No container/ct-build.sh found — falling back to podman push"
podman push {{project}}:latest
fi
# Run container interactively (for debugging)
container-run *args:
podman run --rm -it {{project}}:latest {{args}}
# ═══════════════════════════════════════════════════════════════════════════════
# CI & AUTOMATION
# ═══════════════════════════════════════════════════════════════════════════════
# Run full CI pipeline locally
ci: deps quality
@echo "CI pipeline complete!"
# Install git hooks
install-hooks:
@mkdir -p .git/hooks
@cat > .git/hooks/pre-commit << 'HOOKEOF'
#!/bin/bash
just fmt-check || exit 1
just lint || exit 1
HOOKEOF
@chmod +x .git/hooks/pre-commit
@echo "Git hooks installed"
# ═══════════════════════════════════════════════════════════════════════════════
# SECURITY
# ═══════════════════════════════════════════════════════════════════════════════
# Run security audit
security: deps-audit
@echo "=== Security Audit ==="
@command -v gitleaks >/dev/null && gitleaks detect --source . --verbose || true
@command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true
@echo "Security audit complete"
# Generate SBOM
sbom:
@mkdir -p docs/security
@command -v syft >/dev/null && syft . -o spdx-json > docs/security/sbom.spdx.json || echo "syft not found"
# ═══════════════════════════════════════════════════════════════════════════════
# VALIDATION & COMPLIANCE
# ═══════════════════════════════════════════════════════════════════════════════
# Validate RSR compliance
validate-rsr:
#!/usr/bin/env bash
echo "=== RSR Compliance Check ==="
MISSING=""
for f in .editorconfig .gitignore Justfile README.adoc LICENSE 0-AI-MANIFEST.a2ml; do
[ -f "$f" ] || MISSING="$MISSING $f"
done
for f in .machine_readable/STATE.a2ml .machine_readable/META.a2ml .machine_readable/ECOSYSTEM.a2ml .machine_readable/anchors/ANCHOR.a2ml .machine_readable/policies/MAINTENANCE-AXES.a2ml .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml; do
[ -f "$f" ] || MISSING="$MISSING $f"
done
for f in licensing/exhibits/EXHIBIT-A-ETHICAL-USE.txt licensing/exhibits/EXHIBIT-B-QUANTUM-SAFE.txt licensing/texts/PMPL-1.0-or-later.txt; do
[ -f "$f" ] || MISSING="$MISSING $f"
done
for f in src/interface/abi src/interface/ffi src/interface/generated; do
[ -d "$f" ] || MISSING="$MISSING $f"
done
for f in docs/maintenance/MAINTENANCE-CHECKLIST.adoc docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc; do
[ -f "$f" ] || MISSING="$MISSING $f"
done
if [ -f ".machine_readable/META.a2ml" ]; then
grep -q 'axis-1 = "must > intend > like"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:axis-1"
grep -q 'axis-2 = "corrective > adaptive > perfective"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:axis-2"
grep -q 'axis-3 = "systems > compliance > effects"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:axis-3"
grep -q 'scoping-first = true' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:scoping-first"
grep -q 'idris-unsound-scan = "believe_me/assert_total"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:idris-unsound-scan"
grep -q 'audit-focus = "systems in place, documentation explains actual state, safety/security accounted for, observed effects reviewed"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:audit-focus"
grep -q 'compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:compliance-focus"
grep -q 'effects-evidence = "benchmark execution/results and maintainer status dialogue/review"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:effects-evidence"
grep -q 'compliance-tooling = "panic-attack"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:compliance-tooling"
grep -q 'effects-tooling = "ecological checking with sustainabot guidance"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:effects-tooling"
grep -q 'source-human = "docs/maintenance/MAINTENANCE-CHECKLIST.adoc"' .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml || MISSING="$MISSING MAINTENANCE-CHECKLIST.a2ml:source-human"
grep -q 'source-human = "docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc"' .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml || MISSING="$MISSING SOFTWARE-DEVELOPMENT-APPROACH.a2ml:source-human"
fi
if [ -n "$MISSING" ]; then
echo "MISSING:$MISSING"
exit 1
fi
echo "RSR compliance: PASS"
# Validate STATE.a2ml syntax
validate-state:
@if [ -f ".machine_readable/STATE.a2ml" ]; then \
grep -q '^\[metadata\]' .machine_readable/STATE.a2ml && \
grep -q 'project\s*=' .machine_readable/STATE.a2ml && \
echo "STATE.a2ml: valid" || echo "STATE.a2ml: INVALID (missing required sections)"; \
else \
echo "No .machine_readable/STATE.a2ml found"; \
fi
# Validate AI installation guide completeness (finishbot pre-release check)
validate-ai-install:
#!/usr/bin/env bash
echo "=== AI Installation Guide Check ==="
GUIDE="docs/AI_INSTALLATION_GUIDE.adoc"
README="README.adoc"
ERRORS=0
# Check guide exists
if [ ! -f "$GUIDE" ]; then
echo "MISSING: $GUIDE (create from template: docs/AI_INSTALLATION_GUIDE.adoc)"
ERRORS=$((ERRORS + 1))
else
# Check for unfilled TODO markers
TODOS=$(grep -c '\[TODO-AI-INSTALL' "$GUIDE" 2>/dev/null || true)
if [ "$TODOS" -gt 0 ]; then
echo "INCOMPLETE: $GUIDE has $TODOS unfilled [TODO-AI-INSTALL] markers:"
grep -n '\[TODO-AI-INSTALL' "$GUIDE" | head -10
ERRORS=$((ERRORS + 1))
else
echo "$GUIDE: complete (no TODO markers)"
fi
# Check AI implementation section exists
if ! grep -q 'ai-implementation' "$GUIDE" 2>/dev/null; then
echo "MISSING: [[ai-implementation]] anchor in $GUIDE"
ERRORS=$((ERRORS + 1))
fi
# Check privacy notice exists
if ! grep -qi 'privacy' "$GUIDE" 2>/dev/null; then
echo "MISSING: Privacy notice in $GUIDE"
ERRORS=$((ERRORS + 1))
fi
# Check install commands exist (not just placeholders)
if ! grep -q 'git clone' "$GUIDE" 2>/dev/null; then
echo "WARNING: No git clone command found in $GUIDE -- install commands may be incomplete"
fi
fi
# Check README has AI install section
if [ -f "$README" ]; then
if ! grep -qi 'AI-Assisted Installation' "$README" 2>/dev/null; then
echo "MISSING: AI-Assisted Installation section in $README"
echo " Copy from docs/AI-INSTALL-README-SECTION.adoc"
ERRORS=$((ERRORS + 1))
fi
# Check README for unfilled TODO markers
README_TODOS=$(grep -c '\[TODO-AI-INSTALL' "$README" 2>/dev/null || true)
if [ "$README_TODOS" -gt 0 ]; then
echo "INCOMPLETE: $README has $README_TODOS unfilled [TODO-AI-INSTALL] markers"
ERRORS=$((ERRORS + 1))
fi
fi
if [ "$ERRORS" -gt 0 ]; then
echo ""
echo "AI install guide: FAIL ($ERRORS issues)"
exit 1
fi
echo "AI install guide: PASS"
# Full validation suite
validate: validate-rsr validate-state validate-ai-install
@echo "All validations passed!"
# ═══════════════════════════════════════════════════════════════════════════════
# STATE MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════════════
# Update STATE.a2ml timestamp
state-touch:
@if [ -f ".machine_readable/STATE.a2ml" ]; then \
sed -i 's/last-updated = "[^"]*"/last-updated = "'"$(date +%Y-%m-%d)"'"/' .machine_readable/STATE.a2ml && \
echo "STATE.a2ml timestamp updated"; \
fi
# Show current phase from STATE.a2ml
state-phase:
@grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/STATE.a2ml 2>/dev/null | head -1 || echo "unknown"
# ═══════════════════════════════════════════════════════════════════════════════
# GUIX & NIX
# ═══════════════════════════════════════════════════════════════════════════════
# Enter Guix development shell (primary)
guix-shell:
guix shell -D -f guix.scm
# Build with Guix
guix-build:
guix build -f guix.scm
# Enter Nix development shell (fallback)
nix-shell:
@if [ -f "flake.nix" ]; then nix develop; else echo "No flake.nix"; fi
# ═══════════════════════════════════════════════════════════════════════════════
# HYBRID AUTOMATION
# ═══════════════════════════════════════════════════════════════════════════════
# Run local automation tasks
automate task="all":
#!/usr/bin/env bash
case "{{task}}" in
all) just fmt && just lint && just test && just docs && just state-touch ;;
cleanup) just clean && find . -name "*.orig" -delete && find . -name "*~" -delete ;;
update) just deps && just validate ;;
*) echo "Unknown: {{task}}. Use: all, cleanup, update" && exit 1 ;;
esac
# ═══════════════════════════════════════════════════════════════════════════════
# COMBINATORIC MATRIX RECIPES
# ═══════════════════════════════════════════════════════════════════════════════
# Build matrix: [debug|release] x [target] x [features]
build-matrix mode="debug" target="" features="":
@echo "Build matrix: mode={{mode}} target={{target}} features={{features}}"
# Test matrix: [unit|integration|e2e|all] x [verbosity] x [parallel]
test-matrix suite="unit" verbosity="normal" parallel="true":
@echo "Test matrix: suite={{suite}} verbosity={{verbosity}} parallel={{parallel}}"
# Container matrix: [build|run|push|shell|scan] x [registry] x [tag]
container-matrix action="build" registry="ghcr.io/{{OWNER}}" tag="latest":
@echo "Container matrix: action={{action}} registry={{registry}} tag={{tag}}"
# CI matrix: [lint|test|build|security|all] x [quick|full]
ci-matrix stage="all" depth="quick":
@echo "CI matrix: stage={{stage}} depth={{depth}}"
# Show all matrix combinations
combinations:
@echo "=== Combinatoric Matrix Recipes ==="
@echo ""
@echo "Build Matrix: just build-matrix [debug|release] [target] [features]"
@echo "Test Matrix: just test-matrix [unit|integration|e2e|all] [verbosity] [parallel]"
@echo "Container: just container-matrix [build|run|push|shell|scan] [registry] [tag]"
@echo "CI Matrix: just ci-matrix [lint|test|build|security|all] [quick|full]"
# ═══════════════════════════════════════════════════════════════════════════════
# VERSION CONTROL
# ═══════════════════════════════════════════════════════════════════════════════
# Show git status
status:
@git status --short
# Show recent commits
log count="20":
@git log --oneline -{{count}}
# Generate CHANGELOG.md with git-cliff
changelog:
@command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; }
git cliff --config .machine_readable/configs/git-cliff/cliff.toml --output CHANGELOG.md
@echo "Generated CHANGELOG.md"
# Preview changelog for unreleased commits (does not write)
changelog-preview:
@command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; }
git cliff --config .machine_readable/configs/git-cliff/cliff.toml --unreleased --strip header
# Tag a new release (usage: just release-tag 1.2.3)
release-tag version:
#!/usr/bin/env bash
TAG="v{{version}}"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists"
exit 1
fi
just changelog
git add CHANGELOG.md
git commit -m "chore(release): prepare $TAG"
git tag -a "$TAG" -m "Release $TAG"
echo "Created tag $TAG — push with: git push origin main --tags"
# ═══════════════════════════════════════════════════════════════════════════════
# UTILITIES
# ═══════════════════════════════════════════════════════════════════════════════
# Count lines of code
loc:
@find . \( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.gleam" -o -name "*.zig" -o -name "*.idr" -o -name "*.hs" -o -name "*.ncl" -o -name "*.scm" -o -name "*.adb" -o -name "*.ads" \) -not -path './target/*' -not -path './_build/*' 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 || echo "0"
# Show TODO comments
todos:
@grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.ex" --include="*.res" --include="*.gleam" --include="*.zig" --include="*.idr" --include="*.hs" . 2>/dev/null || echo "No TODOs"
# Open in editor
edit:
${EDITOR:-code} .
# Run high-rigor security assault using panic-attacker
maint-assault:
@./.machine_readable/scripts/maintenance/maint-assault.sh
# [AUTO-GENERATED] Multi-arch / RISC-V target
build-riscv:
@echo "Building for RISC-V..."
cross build --target riscv64gc-unknown-linux-gnu