diff --git a/README.md b/README.md index fbb0258..c820c53 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Community plugins for [`lowfat`](https://github.com/zdk/lowfat) — each one com ## Quickstart -Install `lowfat` first ([how](https://github.com/zdk/lowfat#install)), then: +Install `lowfat` 0.9.0 or newer first ([how](https://github.com/zdk/lowfat#install)), then: ```sh # Clone every plugin into lowfat's scan directory @@ -97,7 +97,7 @@ lowfat level # show current level Override per-run with `LOWFAT_LEVEL=ultra lowfat terraform plan`. -**Failure is always safe.** Every plugin opens each subcommand with `if exit failed: raw`, so when a command fails you get the full error — compile errors, terraform diagnostics, pytest tracebacks — even at `ultra`. +**Failure is always safe.** Every plugin passes a failed command through raw, with `if exit failed: raw` or a `rule` that has `exit: failed`. So when a command fails you get the full error — compile errors, terraform diagnostics, pytest tracebacks — even at `ultra`. ## Troubleshooting @@ -180,6 +180,29 @@ build|check: | `if exit failed:` … `elif level X:` … `else:` | Branch on exit code / level | | `match level: ultra: / lite: / else:` | Compact alternative to an elif chain | | `cmd1\|cmd2:` / `*:` | Subcommand selector with alternation / catch-all | +| `rule name:` + `sub:` `level:` `exit:` `flag:` | Named rule: runs its `do:` body when every field holds | + +### Named rules + +A named rule lists what it matches as fields. The first rule whose fields +all hold runs, so shared policy is written once. Needs lowfat 0.9.0. +See [`kubectl-compact`](kubectl/kubectl-compact/filter.lf) for a full file. + +``` +rule failed: + exit: failed + do: raw + +rule json: + flag: -o|--output json + do: truncate-json + +rule get: + sub: get + do: + drop-managed-fields-yaml + head 80 +``` ### Test as you go @@ -189,7 +212,7 @@ cat samples/build-full.txt | lowfat filter filter.lf --sub=build --explain lowfat plugin bench mytool-compact ``` -**Please keep the failure guard.** Every subcommand should open with `if exit failed: raw` (or a near-raw failure view) so agents get the full error when things break. PRs that filter the failure path will be asked to add it. +**Please keep the failure guard.** Every subcommand should open with `if exit failed: raw` (or a near-raw failure view), or the file should start with a `rule` that has `exit: failed`, so agents get the full error when things break. PRs that filter the failure path will be asked to add it. ## Contributing diff --git a/kubectl/kubectl-compact/filter.lf b/kubectl/kubectl-compact/filter.lf index cec8149..e628490 100644 --- a/kubectl/kubectl-compact/filter.lf +++ b/kubectl/kubectl-compact/filter.lf @@ -1,7 +1,7 @@ #!/usr/bin/env lowfat-filter # kubectl-compact — compact kubectl/k output for LLM contexts. # env: $sub $level $exit $args -# Every rule passes raw output straight through on a non-zero exit. +# Rules run top to bottom. The first rule whose fields all hold wins. # `truncate-json` — shared with terraform-compact. include ../../lib/json.lf @@ -24,80 +24,63 @@ define drop-describe-noise: drop /^\s+ConfigMapOptional:/ drop /^\s+DownwardAPI:/ +# `get` YAML at each level. Macros cannot hold a `match`, so each level +# gets its own macro and the rules below pick one. +define compact-yaml-ultra: + drop-managed-fields-yaml + drop /^\s*$/ + head 30 + +define compact-yaml-lite: + drop-managed-fields-yaml + head 200 + +define compact-yaml: + drop-managed-fields-yaml + drop /kubectl\.kubernetes\.io\/last-applied-configuration/ + drop /^\s*f:[a-zA-Z]/ + head 80 + +# ── any subcommand ──────────────────────────────────────────────── +# A failed command keeps its full error output. +rule failed: + exit: failed + do: raw + +# AST-aware truncation: valid JSON in, valid JSON out. +rule json: + flag: -o|--output json + do: truncate-json + # ── get ─────────────────────────────────────────────────────────── -# `-o json` → AST-aware truncation. `jsonpath`/unknown `-o` → raw for -# downstream parsers. `-o yaml` or no `-o` → prune managedFields. -get: - if exit failed: - raw - elif -o json: - truncate-json - elif --output json: - truncate-json - elif -o jsonpath: - raw - elif --output jsonpath: - raw - elif -o yaml: +rule get-yaml: + sub: get + flag: -o|--output yaml + do: match level: - ultra: - drop-managed-fields-yaml - drop /^\s*$/ - head 30 - lite: - drop-managed-fields-yaml - head 200 - else: - drop-managed-fields-yaml - drop /kubectl\.kubernetes\.io\/last-applied-configuration/ - drop /^\s*f:[a-zA-Z]/ - head 80 - elif --output yaml: - match level: - ultra: - drop-managed-fields-yaml - drop /^\s*$/ - head 30 - lite: - drop-managed-fields-yaml - head 200 - else: - drop-managed-fields-yaml - drop /kubectl\.kubernetes\.io\/last-applied-configuration/ - drop /^\s*f:[a-zA-Z]/ - head 80 - elif -o: - raw - elif --output: - raw - else: + ultra: compact-yaml-ultra + lite: compact-yaml-lite + else: compact-yaml + +# Any other explicit format (`jsonpath`, `name`, `wide`, …) is headed for a +# downstream parser and must stay byte-exact. +rule formatted: + flag: -o|--output + do: raw + +# No `-o`: same pruning as YAML. +rule get: + sub: get + do: match level: - ultra: - drop-managed-fields-yaml - drop /^\s*$/ - head 30 - lite: - drop-managed-fields-yaml - head 200 - else: - drop-managed-fields-yaml - drop /kubectl\.kubernetes\.io\/last-applied-configuration/ - drop /^\s*f:[a-zA-Z]/ - head 80 + ultra: compact-yaml-ultra + lite: compact-yaml-lite + else: compact-yaml # ── describe ────────────────────────────────────────────────────── -describe: - if exit failed: - raw - elif -o json: - truncate-json - elif --output json: - truncate-json - elif -o: - raw - elif --output: - raw - else: +rule describe: + sub: describe + do: match level: ultra: keep /^(Name:|Namespace:|Labels:|Status:|Type |Node:|IP:|Events:| Type| ----| Normal| Warning|Conditions:|Ready|Replicas:)/ @@ -113,10 +96,9 @@ describe: head 150 # ── apply / delete ──────────────────────────────────────────────── -apply: - if exit failed: - raw - else: +rule apply: + sub: apply + do: match level: ultra: keep /(configured|created|unchanged|error|warning)/ @@ -126,10 +108,9 @@ apply: else: head 40 -delete: - if exit failed: - raw - else: +rule delete: + sub: delete + do: match level: ultra: keep /(deleted|error|warning|not found)/ @@ -140,20 +121,18 @@ delete: head 30 # ── logs (always tail) ──────────────────────────────────────────── -logs: - if exit failed: - raw - else: +rule logs: + sub: logs + do: match level: ultra: tail 50 lite: tail 200 else: tail 100 # ── events ──────────────────────────────────────────────────────── -events: - if exit failed: - raw - else: +rule events: + sub: events + do: match level: ultra: keep /(Warning|Error|LAST SEEN|TYPE)/ @@ -164,10 +143,5 @@ events: head 50 # ── default ─────────────────────────────────────────────────────── -*: - if exit failed: raw - elif -o json: truncate-json - elif --output json: truncate-json - elif -o: raw - elif --output: raw - else: head 40 +rule other: + do: head 40 diff --git a/kubectl/kubectl-compact/lowfat.toml b/kubectl/kubectl-compact/lowfat.toml index 0d7b30c..b8e2acd 100644 --- a/kubectl/kubectl-compact/lowfat.toml +++ b/kubectl/kubectl-compact/lowfat.toml @@ -1,6 +1,6 @@ [plugin] name = "kubectl-compact" -version = "0.6.1" +version = "0.7.0" description = "Compact kubectl/k output for LLM contexts" author = "zdk" commands = ["kubectl", "k"]