Rustelo/scripts/adr-constraints.nu

149 lines
5.3 KiB
Text
Raw Normal View History

2026-07-18 20:14:32 +01:00
#!/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 '<none>')" },
}
}
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
}
}