#!/usr/bin/env nu # Execute the constraint checks declared by accepted ADRs and report per constraint. # # Constraints live in `.ontoref/adrs/adr-*.ncl` at the constellation root. Each carries a # `check` in one of three shapes: # # NuCmd { cmd, expect_exit } run cmd, compare exit code # Grep { pattern, paths, must_be_empty } # FileExists { path, present } # # Reported states: # ✓ the check ran and passed # ✗ the check ran and failed # ⊘ no check ran — the constraint declares `cmd = "exit 0"` (a no-op that cannot fail) # or its shape is unknown. Reported apart from ✓ on purpose: a constraint that cannot # fail is not a satisfied constraint, and counting it green is the lie this runner exists # to prevent. # # Only `Accepted` ADRs gate. `Proposed` ones are listed as skipped. # # Exits 0 regardless of findings unless `--strict` is passed, so this can be read before it # is enforced. def adrs_dir [] { $env.FILE_PWD | path join ".." ".." ".ontoref" "adrs" | path expand } # A cmd that cannot fail is not a check. Extend this list as no-op idioms appear. def is_noop [cmd: string] { ($cmd | str trim) in ["exit 0", "true", ""] } def run_nucmd [check: record] { if (is_noop ($check.cmd? | default "")) { return { state: "skip", detail: "check is `exit 0` — a no-op that cannot fail" } } let expect = ($check.expect_exit? | default 0) let r = (do --ignore-errors { ^nu -c $check.cmd } | complete) if $r.exit_code == $expect { { state: "pass", detail: $"exit ($r.exit_code) == expected ($expect)" } } else { { state: "fail", detail: $"exit ($r.exit_code), expected ($expect)" } } } def run_grep [check: record] { let paths = ($check.paths? | default []) if ($paths | is-empty) { return { state: "skip", detail: "Grep check declares no paths" } } let existing = ($paths | where { |p| ($p | path exists) }) if ($existing | is-empty) { return { state: "skip", detail: $"none of the declared paths exist: ($paths | str join ', ')" } } let r = (do --ignore-errors { ^rg -n --no-heading $check.pattern ...$existing } | complete) let hits = if ($r.stdout | str trim | is-empty) { 0 } else { $r.stdout | lines | length } let must_be_empty = ($check.must_be_empty? | default true) if $must_be_empty and $hits > 0 { { state: "fail", detail: $"($hits) hits, must_be_empty=true" } } else if (not $must_be_empty) and $hits == 0 { { state: "fail", detail: "0 hits, must_be_empty=false" } } else { { state: "pass", detail: $"($hits) hits" } } } def run_fileexists [check: record] { let want = ($check.present? | default true) let got = ($check.path | path exists) if $got == $want { { state: "pass", detail: $"present=($got), expected ($want)" } } else { { state: "fail", detail: $"present=($got), expected ($want) — ($check.path)" } } } def run_check [check: record] { match ($check.tag? | default "UNKNOWN") { "NuCmd" => (run_nucmd $check), "Grep" => (run_grep $check), "FileExists" => (run_fileexists $check), _ => { state: "skip", detail: $"unknown check shape: ($check.tag? | default '')" }, } } def main [ --strict # exit 1 if any constraint fails --verbose # print the detail line for passing constraints too ] { let dir = (adrs_dir) let files = (glob ($dir | path join "adr-*.ncl") | where { |f| not (($f | path basename) starts-with "_") } | where { |f| ($f | path basename) not-in ["adr-defaults.ncl" "adr-schema.ncl" "adr-constraints.ncl"] } | sort) mut rows = [] mut skipped_adrs = [] for f in $files { let export = (do --ignore-errors { ^nickel export $f } | complete) if $export.exit_code != 0 { print $"(ansi red)✗(ansi reset) ($f | path basename) — nickel export failed" print ($export.stderr | lines | first 3 | str join "\n") continue } let adr = ($export.stdout | from json) if $adr.status != "Accepted" { $skipped_adrs = ($skipped_adrs | append $"($adr.id) [($adr.status)]") continue } for c in $adr.constraints { let res = (run_check $c.check) $rows = ($rows | append { adr: $adr.id, id: $c.id, severity: ($c.severity? | default "Hard"), state: $res.state, detail: $res.detail, }) } } print $"(ansi default_bold)ADR constraints(ansi reset) — ($rows | length) from accepted ADRs\n" for r in $rows { let mark = match $r.state { "pass" => $"(ansi green)✓(ansi reset)", "fail" => $"(ansi red)✗(ansi reset)", _ => $"(ansi yellow)⊘(ansi reset)", } if $r.state == "pass" and (not $verbose) { print $" ($mark) ($r.adr) ($r.id)" } else { print $" ($mark) ($r.adr) ($r.id)" print $" ($r.detail)" } } let passed = ($rows | where state == "pass" | length) let failed = ($rows | where state == "fail" | length) let skipped = ($rows | where state == "skip" | length) print "" print $" (ansi green)($passed) ✓(ansi reset) · (ansi red)($failed) ✗(ansi reset) · (ansi yellow)($skipped) ⊘(ansi reset)" if not ($skipped_adrs | is-empty) { print $" not gating \(status\): ($skipped_adrs | str join ', ')" } if $skipped > 0 { print $" (ansi yellow)⊘(ansi reset) = declared but unexecutable — neither green nor red. A no-op check is not a check." } if $strict and $failed > 0 { exit 1 } }