diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 2f4c93f..be6439e 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -18,6 +18,10 @@ jobs: run: find scripts shared/hooks -name '*.sh' -print0 | xargs -0 -n1 bash -n - name: Build run: REQUIRE_CODEX_SCHEMA=true ./scripts/build.sh --summary + - name: Prompt budget + run: ./scripts/prompt-budget.sh --summary + - name: Repository context cache + run: ./scripts/repository-context.sh --summary - name: Doctor run: ./scripts/doctor.sh --summary - name: Platform package tests diff --git a/adapters/prompt-budget-baseline.json b/adapters/prompt-budget-baseline.json new file mode 100644 index 0000000..89a10d2 --- /dev/null +++ b/adapters/prompt-budget-baseline.json @@ -0,0 +1 @@ +{"Claude permanent context":5030,"Codex permanent context":4828} diff --git a/adapters/prompt-budget-baseline.tsv b/adapters/prompt-budget-baseline.tsv new file mode 100644 index 0000000..14812ee --- /dev/null +++ b/adapters/prompt-budget-baseline.tsv @@ -0,0 +1,2 @@ +Claude 5030 +Codex 4828 diff --git a/scripts/prompt-budget.ps1 b/scripts/prompt-budget.ps1 new file mode 100644 index 0000000..3552062 --- /dev/null +++ b/scripts/prompt-budget.ps1 @@ -0,0 +1,25 @@ +[CmdletBinding()] +param([switch]$Summary) +$ErrorActionPreference = 'Stop' +$root = Split-Path $PSScriptRoot -Parent +$generated = Join-Path $root 'generated' +if (-not (Test-Path (Join-Path $generated 'claude-linux/CLAUDE.md'))) { throw 'Generated output is missing; run build first.' } +function Measure-Tree([string]$path) { + if (-not (Test-Path $path)) { return 0 } + return [int64]((Get-ChildItem $path -File -Recurse | ForEach-Object { $_.Length } | Measure-Object -Sum).Sum) +} +function Tokens([int64]$bytes) { return [int][Math]::Ceiling($bytes / 4.0) } +$claude = (Get-Item (Join-Path $generated 'claude-linux/CLAUDE.md')).Length +$codex = (Get-Item (Join-Path $generated 'codex-linux/AGENTS.md')).Length +$skills = Measure-Tree (Join-Path $generated 'claude-linux/skills') +$triggers = (Get-Item (Join-Path $root 'adapters/claude/rule-skills.tsv')).Length +$lazyItem = Get-ChildItem (Join-Path $root 'shared/rules') -File -Recurse | Sort-Object Length -Descending | Select-Object -First 1 +$lazy = if ($lazyItem) { [int64]$lazyItem.Length } else { 0 } +$baseline = Join-Path $root 'adapters/prompt-budget-baseline.json' +$old = if (Test-Path $baseline) { Get-Content $baseline -Raw | ConvertFrom-Json } else { $null } +$rows = @([pscustomobject]@{Name='Claude permanent context';Bytes=$claude;Tokens=(Tokens $claude)},[pscustomobject]@{Name='Codex permanent context';Bytes=$codex;Tokens=(Tokens $codex)},[pscustomobject]@{Name='Claude skill metadata';Bytes=$skills;Tokens=(Tokens $skills)},[pscustomobject]@{Name='Rule trigger metadata';Bytes=$triggers;Tokens=(Tokens $triggers)},[pscustomobject]@{Name='Largest lazy rule';Bytes=$lazy;Tokens=(Tokens $lazy)}) +$fail = $false +if ($old) { foreach ($row in $rows | Select-Object -First 2) { $previous = [double]$old.($row.Name); if ($previous -gt 0 -and $row.Bytes -gt $previous * 1.15) { $fail = $true } } } +if ($Summary) { Write-Output 'Prompt Budget'; $rows | ForEach-Object { Write-Output ('{0,-28} {1,8} bytes {2,6} tokens' -f $_.Name,$_.Bytes,$_.Tokens) } } else { $rows | ForEach-Object { Write-Output ('{0}: {1} bytes / {2} tokens' -f $_.Name,$_.Bytes,$_.Tokens) } } +if ($fail) { Write-Error 'Prompt budget: permanent context increased by more than 15 percent.'; exit 1 } +exit 0 diff --git a/scripts/prompt-budget.sh b/scripts/prompt-budget.sh new file mode 100644 index 0000000..945bed4 --- /dev/null +++ b/scripts/prompt-budget.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail +summary=false +for arg in "$@"; do case "$arg" in --summary) summary=true;; *) printf 'Unknown argument: %s\n' "$arg" >&2; exit 1;; esac; done +root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +generated="$root/generated" +[ -f "$generated/claude-linux/CLAUDE.md" ] || { printf 'Generated output is missing; run build first.\n' >&2; exit 1; } +bytes() { wc -c < "$1" | tr -d ' '; } +tree_bytes() { find "$1" -type f -printf '%s\n' 2>/dev/null | awk '{s+=$1} END {print s+0}'; } +tokens() { awk -v b="$1" 'BEGIN { printf "%d", (b + 3) / 4 }'; } +claude=$(bytes "$generated/claude-linux/CLAUDE.md"); codex=$(bytes "$generated/codex-linux/AGENTS.md"); skills=$(tree_bytes "$generated/claude-linux/skills"); triggers=$(bytes "$root/adapters/claude/rule-skills.tsv"); lazy=$(find "$root/shared/rules" -type f -printf '%s %p\n' | sort -nr | awk 'NR==1 {print $1+0}'); fail=false +if [ -f "$root/adapters/prompt-budget-baseline.tsv" ]; then while IFS=$'\t' read -r name old; do current=$([ "$name" = Claude ] && printf '%s' "$claude" || printf '%s' "$codex"); awk -v n="$current" -v o="$old" 'BEGIN { exit !(o > 0 && n > o * 1.15) }' && fail=true; done < "$root/adapters/prompt-budget-baseline.tsv"; fi +if [ "$summary" = true ]; then printf 'Prompt Budget\nClaude permanent context %8s bytes %6s tokens\nCodex permanent context %8s bytes %6s tokens\nClaude skill metadata %8s bytes %6s tokens\nRule trigger metadata %8s bytes %6s tokens\nLargest lazy rule %8s bytes %6s tokens\n' "$claude" "$(tokens "$claude")" "$codex" "$(tokens "$codex")" "$skills" "$(tokens "$skills")" "$triggers" "$(tokens "$triggers")" "$lazy" "$(tokens "$lazy")"; else printf 'Prompt budget: Claude %s bytes, Codex %s bytes, skills %s bytes, triggers %s bytes, largest lazy rule %s bytes\n' "$claude" "$codex" "$skills" "$triggers" "$lazy"; fi +$fail && { printf 'Prompt budget: permanent context increased by more than 15 percent.\n' >&2; exit 1; }