diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dfcc2d..4cd9f55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,63 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here. Versioning is loose-semver; bumps trigger the in-process self-update on every running client via `LARRY_BASE_URL` + `MANIFEST`. +## v0.8.29 — 2026-05-28 + +**★ READ/INSPECT TOOL VALIDATION PASS — 6 real bugs found & fixed.** Ran every +read/inspect/analysis tool against a real 24-site Cloverleaf integrator fixture +(via BOTH the `lib/.sh` path and the wired `execute_tool` dispatch), with real +thread/site/xlate/table names discovered from the config. Same class as the +v0.8.28 nc-engine arg-parse crash: each only surfaced when actually run. A BSD-awk +word-boundary nit in `nc-status.sh` not-up (gawk-only `\` → portable +`(^|[^a-z])up([^a-z]|$)`) was also caught at the Vera gate and folded in. + +- **`nc-find.sh` `--name` mode returned ZERO matches (BSD/macOS).** The protocol- + name extraction used the GNU-only `sed \+`. BSD sed (macOS) treats `\+` as a + literal `+`, so the thread name came back empty and every `--name` hit was + silently dropped. Now POSIX-portable (`[[:space:]][[:space:]]*` / + `[A-Za-z0-9_][A-Za-z0-9_]*`). Worked on GNU/Cygwin hosts; broke on BSD. +- **`nc-find.sh` exit 1 on success for tsv/jsonl.** The trailing + `[ "$FORMAT" = "table" ] && printf ...` test left the script exit code at 1 for + non-table formats (the `&&` short-circuits). Added explicit `exit 0` — a + successful search (even zero matches) now returns 0; mis-signaled failure to + any `$?`/`&&` caller. +- **`nc-parse.sh` `tclproc-refs` dropped digit-leading proc names.** The `{ PROC` + / `{ PROCS` regexes required a leading `[A-Za-z_]`, so a real Cloverleaf proc + like `3M_check_ack` was never reported — which also blanked `nc_find --tclproc + 3M_check_ack`. Widened to `[A-Za-z0-9_]+`; `PROCSCONTROL` still excluded. +- **`nc-xlate.sh diff` could not find site-scoped xlates.** Unlike show/ops/tree/ + summary, `diff` did not accept `--site`, so `locate_xlate` only checked + `$HCIROOT/Xlate` and always died `no such xlate` for the common case (xlates + under `/Xlate/`). `cmd_diff` now takes `--site` (applied to both names) + and larry.sh forwards it. +- **`nc-diff-interface.sh` printf option-injection.** `printf '---\n\n'` (the + markdown horizontal rule) parsed `---` as printf options → `printf: --: invalid + option`, dropping the rule. Now `printf '%s\n\n' '---'`. +- **`nc-smat-diff.sh` printf option-injection (8 lines).** `printf '- A: ...'` + format strings starting with `- ` errored `printf: - : invalid option` in + NON-interactive bash, dropping every summary bullet. Guarded with `printf --`. +- **`nc-status.sh not-up` crashed on `--format`.** `cmd_not_up` accepted only + `--site`/`--filter`; the larry.sh dispatch ALWAYS appends `--format`, so + `not-up` died `unknown flag: --format` and was unusable via the wired tool. + `cmd_not_up` now accepts `--format` and forwards it to `cmd_threads`. + +All other read/inspect tools (nc_paths up/down/full/all/site-only incl. the +cross-site ADTto_CodaMetrix chain, nc_destinations/sources, nc_list_*, +nc_protocol_*, nc_find_inbound, nc_document single+system, nc_revisions +timeline+diff, nc_msgs raw+field-filter+json, nc_xlate list/show/ops/tree/summary, +nc_xlate_refs, nc_tclproc_refs, hl7_field, hl7_diff, nc_diff_interface, +nc_regression 6-phase + chain-walk command-gen, nc_smat_diff pairing, nc_engine ++ nc_status graceful degrade, list_sites ignore-rules, all 7 nc_tclgen templates +verified `info complete` in tclsh) PASSED unchanged. Test matrix: +`Deliverables/2026-05-28-cloverleaf-v3-tool-test-matrix.md`. + +KNOWN / TRIAGE (not fixed this pass): `hl7-sanitize.sh` is a silent no-op on +LF-delimited input (`RS="\r"` reads the whole file as one record) — fixing needs +a portable CR/LF normalizer (BSD awk has no regex `RS`); `nc_engine route-test/ +testxlate/resend` ignore `--dry-run` (only stop/start/bounce/restart honor it) and +the dispatch never forwards it; `lessons.sh:142` has the same `printf '---'` +option-injection (out-of-scope write tool). + ## v0.8.28 — 2026-05-28 **★ EXPOSE 5 lib-only tools as first-class LLM tools.** A roadmap audit found diff --git a/MANIFEST b/MANIFEST index 9e7430c..4a6c90f 100644 --- a/MANIFEST +++ b/MANIFEST @@ -23,16 +23,16 @@ # scripts/make-manifest.sh and bump VERSION. # Top-level scripts -larry.sh 4bc6355ebd04b3e301c28d9c34a8ec2fdae44fa223f1f065cdcd2790a4676e33 +larry.sh 219c0b4f84aabec17baec7ba20c47849364bce9039bbcaa07ed7ba81b7b38a05 larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831 larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0 install-larry.sh fa36e23a39eacbd0d7ecedd3b42131902f816ee7e98241dfc6e28c6e4ba80423 # Metadata -VERSION 4d1f87fb5ed962079a382ddacb5e4f28b461dd9d6c4a6c4085248832fa8111a5 +VERSION 14624c56b466c22dbace115e50c329bc4bea74a2c25e1f3aa481766ea49ddff7 MANUAL.md c64bd0251a51ad150508b4e1185355bc4826a64071d4de339f92ed550dbfacde -CHANGELOG.md 567da1b7ddb2f8200eb42cfce6387f30ca753fc065abe4994f887e732d2a36f9 +CHANGELOG.md f3a6ac02750188f6cec37e1d7454424c363706f2f9a8a6b041b449b1d783479c # Agent personas (system-prompt overlays) agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1 @@ -91,20 +91,20 @@ lib/table-to-csv.sh ad98e73687bc9e9f6ae0cd79ed5ba26c856076902865230f822dec1a1bea # NetConfig tooling lib/nc-engine.sh e2b12a1c019d40857b96d48d6c185b94aefadab604536ce41077ecc251b0bc58 -lib/nc-status.sh 80d2023babff70c065ffab70b5ecf9bdfd80183ae5808f610335da9c8c27f97f +lib/nc-status.sh fa08c5e48704d3d17e2206dafc8522ae7668b7a2f9b97f3521e05fd0ba739443 lib/nc-table.sh a6d5c11dd460cfb100ea50c74d57c1a46ef49112632037534a32cd28600abe7f -lib/nc-xlate.sh b05caae72889f6404a2a1618ba3ba3666dc34f03d49a779664ea31396dddb112 -lib/nc-smat-diff.sh ac003954701ea6b7f4aa1f6941f8536af5b5cdfbb75e306789753d453f06800e +lib/nc-xlate.sh 8621e6f0ef55524dd6ecba91fee055cf9cdc168791e75ba7c15d9bf501fe09bf +lib/nc-smat-diff.sh 9c04d9e2f35f22c78d5f3c40a884ed23a3b6aaabc53ee27dfbfb66ab3166a567 lib/nc-create-thread.sh 5a9d5407c117183cad831d6b95f0e785b1b806f5ccc67f803c12b3695882b5b7 lib/nc-tclgen.sh 5b8e73d7f6950a2b84f563132562ea82f62f4acac907257e233c7e68d85506c9 -lib/nc-parse.sh 3419b3f8d0cfdaf767f91551d6e2441d0743d80bd31515ffa61c769db1542c2f +lib/nc-parse.sh 52fef42d7a4b361534ab0d921deef74586dfeb6c199c941cebb55abcc2c39d4f lib/nc-paths.sh 388d2f4560736587a01218cadc1de612cd59e392819d16db2f56f19174c1111b lib/nc-inbound.sh 52d28c5f8d97bdf96f0fc7b5300d35b106b8e1226578f4cda430deb2a8b4a91b lib/nc-make-jump.sh 08a0bc58a299c95c60a59a5202792daf0ada3a8a0be7dc1b4cccc5724f5c9c79 lib/nc-msgs.sh 20517922d1153ec7827c833987497fb305d087b579911d1b9067d65ae156a19f lib/nc-document.sh 47211e99089c0446d25a1e84545a734894720a1c9ad8f59b920332035e4ea880 lib/nc-revisions.sh c27856f7decfc4c2e2c990f59eb20136fdff9cf0a52b9d9fbd9370613666a802 -lib/nc-diff-interface.sh 6b64ec3070a3d75d1d79632c9aeb357177fdbcc77c474aa78e6f6929fda1a324 -lib/nc-find.sh 8c79e0acad7de56e4e1f12d61e071a4b98c4e2310a1f7fb183697df521215e3f +lib/nc-diff-interface.sh c922d10323f06346efa53ada68b44d32d9568ff0bd848c59af3404135f29d1ad +lib/nc-find.sh 2264877c56100378a1b780d640dcaa806aa5501ddd204c6b6a8eb5d3e07bf966 lib/nc-insert-protocol.sh ad1fa0bafbf4fdfb12bad20f9c22c3eed519f8846774331e26aa9becd6f8898a lib/nc-regression.sh 70999a60608439f7bf1a3abb9f5e9854b5ea03025ef29ddbca683896346d1bce diff --git a/VERSION b/VERSION index da8dfd3..b5f1919 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.28 +0.8.29 diff --git a/larry.sh b/larry.sh index a7242d1..14d2f73 100755 --- a/larry.sh +++ b/larry.sh @@ -78,7 +78,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.8.28" +LARRY_VERSION="0.8.29" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" # ───────────────────────────────────────────────────────────────────────────── @@ -4235,7 +4235,8 @@ tool_nc_xlate() { [ -n "$site" ] && args+=(--site "$site") ;; diff) [ -n "$name" ] && [ -n "$name2" ] || { echo "ERROR: nc_xlate diff needs name and name2"; return 1; } - args=(diff "$name" "$name2") ;; + args=(diff "$name" "$name2") + [ -n "$site" ] && args+=(--site "$site") ;; "") args=(list); [ -n "$site" ] && args+=(--site "$site") ;; *) echo "ERROR: unknown nc_xlate subcommand: $subcmd (list|show|ops|tree|summary|diff)"; return 1 ;; esac diff --git a/lib/nc-diff-interface.sh b/lib/nc-diff-interface.sh index cc99f47..7161202 100755 --- a/lib/nc-diff-interface.sh +++ b/lib/nc-diff-interface.sh @@ -276,7 +276,7 @@ collect_tclprocs_for() { fi fi - printf '---\n\n' + printf '%s\n\n' '---' printf '_Generated %s by Larry-Anywhere nc-diff-interface.sh (depth=%d)._\n' \ "$(date -Iseconds 2>/dev/null || date)" "$DEPTH" } | out_target diff --git a/lib/nc-find.sh b/lib/nc-find.sh index 0add465..180e268 100755 --- a/lib/nc-find.sh +++ b/lib/nc-find.sh @@ -109,7 +109,11 @@ for nc in "${NCONFIGS[@]}"; do # Partial match (substring) while IFS= read -r raw; do line=$(printf '%s' "$raw" | cut -d: -f1) - thread_name=$(printf '%s' "$raw" | sed -n 's/^[0-9]*:protocol[[:space:]]\+\([A-Za-z0-9_]\+\)[[:space:]]*{.*$/\1/p') + # PORTABILITY: POSIX [[:space:]][[:space:]]* / [A-Za-z0-9_][A-Za-z0-9_]* + # instead of the GNU-only `\+`. BSD sed (macOS) treats `\+` as a + # literal `+`, so the GNU form extracted an empty name and every + # --name match was silently dropped on macOS/BSD hosts. + thread_name=$(printf '%s' "$raw" | sed -n 's/^[0-9]*:protocol[[:space:]][[:space:]]*\([A-Za-z0-9_][A-Za-z0-9_]*\)[[:space:]]*{.*$/\1/p') [ -z "$thread_name" ] && continue pname=$("$NCP" protocol-field "$nc" "$thread_name" PROCESSNAME 2>/dev/null | head -1) pport=$("$NCP" protocol-nested "$nc" "$thread_name" PROTOCOL.PORT 2>/dev/null | head -1 | sed 's/^{}$//') @@ -233,3 +237,7 @@ esac # (printf '%d' "5\r" fails with "invalid number"). n=$(wc -l < "$RESULTS" | tr -cd '0-9') [ "$FORMAT" = "table" ] && printf '\n%d match(es)\n' "${n:-0}" >&2 +# Always exit 0 on a successful search (even zero matches). Without this, the +# trailing `[ "$FORMAT" = "table" ] && ...` test leaves the script exit code at +# 1 for tsv/jsonl (the && short-circuits), mis-signaling failure to callers. +exit 0 diff --git a/lib/nc-parse.sh b/lib/nc-parse.sh index 4d6305f..1a1abf9 100755 --- a/lib/nc-parse.sh +++ b/lib/nc-parse.sh @@ -448,12 +448,14 @@ cmd_tclproc_refs() { { line = $0 # PROC (singleton, e.g. DATAFORMAT.PROC) - if (match(line, /\{ PROC [A-Za-z_][A-Za-z0-9_]*/)) { + # Allow a LEADING DIGIT: Cloverleaf proc names like 3M_check_ack are valid + # TCL proc names. The old [A-Za-z_]-first class silently dropped them. + if (match(line, /\{ PROC [A-Za-z0-9_]+/)) { v = substr(line, RSTART + 7, RLENGTH - 7) print v } # PROCS (singleton) - if (match(line, /\{ PROCS [A-Za-z_][A-Za-z0-9_]*/)) { + if (match(line, /\{ PROCS [A-Za-z0-9_]+/)) { v = substr(line, RSTART + 8, RLENGTH - 8) print v } diff --git a/lib/nc-smat-diff.sh b/lib/nc-smat-diff.sh index e969e36..4344a93 100755 --- a/lib/nc-smat-diff.sh +++ b/lib/nc-smat-diff.sh @@ -124,10 +124,13 @@ B_KEYS=$(awk -F'\t' '{print $1}' "$OUT/b/sorted.tsv" | sort -u) SUMMARY="$OUT/_summary.md" { printf '# smat diff: thread=%s\n\n' "$THREAD" - printf '- A: `%s/%s` (%d messages sampled)\n' "$ENV_A" "$SITE_A" "$A_COUNT" - printf '- B: `%s/%s` (%d messages sampled)\n' "$ENV_B" "$SITE_B" "$B_COUNT" - printf '- pair-on: `%s`\n' "$PAIR_ON" - printf '- ignore: `%s`\n\n' "$IGNORE" + # printf -- guard: a format string starting with '- ' is otherwise parsed as + # an option by the bash printf builtin in NON-interactive shells ("printf: - + # : invalid option"), dropping the line. -- ends option parsing. + printf -- '- A: `%s/%s` (%d messages sampled)\n' "$ENV_A" "$SITE_A" "$A_COUNT" + printf -- '- B: `%s/%s` (%d messages sampled)\n' "$ENV_B" "$SITE_B" "$B_COUNT" + printf -- '- pair-on: `%s`\n' "$PAIR_ON" + printf -- '- ignore: `%s`\n\n' "$IGNORE" printf '## Per-pair diffs\n\n' printf '| %s | diffs | report |\n|---|---|---|\n' "$PAIR_ON" } > "$SUMMARY" @@ -163,10 +166,11 @@ done < <(printf '%s\n%s\n' "$A_KEYS" "$B_KEYS" | sort -u) { printf '\n## Summary\n\n' - printf '- paired (A and B): %d\n' "$PAIRED" - printf '- A-only: %d\n' "$A_ONLY" - printf '- B-only: %d\n' "$B_ONLY" - printf '- total field differences (post-ignore): %d\n' "$DIFFS_TOTAL" + # printf -- guard (see note above): leading '- ' format strings. + printf -- '- paired (A and B): %d\n' "$PAIRED" + printf -- '- A-only: %d\n' "$A_ONLY" + printf -- '- B-only: %d\n' "$B_ONLY" + printf -- '- total field differences (post-ignore): %d\n' "$DIFFS_TOTAL" } >> "$SUMMARY" printf 'done. Summary: %s\n' "$SUMMARY" >&2 diff --git a/lib/nc-status.sh b/lib/nc-status.sh index 8f52545..5ae9fdc 100755 --- a/lib/nc-status.sh +++ b/lib/nc-status.sh @@ -111,16 +111,21 @@ cmd_threads() { cmd_not_up() { local site="${HCISITE:-}" local filter="" + local format="text" while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; --filter) shift; filter="$1" ;; + # Accept --format and forward it to cmd_threads. The larry.sh dispatch + # ALWAYS appends --format, so without this not-up died "unknown flag: + # --format" and was unusable via the wired tool. + --format) shift; format="$1" ;; *) die "unknown flag: $1" ;; esac shift done - cmd_threads --site "$site" ${filter:+--filter "$filter"} \ - | awk 'NR==1 || tolower($0) !~ /\/' + cmd_threads --site "$site" --format "$format" ${filter:+--filter "$filter"} \ + | awk 'NR==1 || tolower($0) !~ /(^|[^a-z])up([^a-z]|$)/' } cmd_connections() { diff --git a/lib/nc-xlate.sh b/lib/nc-xlate.sh index 5c3b4da..f0bb62e 100755 --- a/lib/nc-xlate.sh +++ b/lib/nc-xlate.sh @@ -8,7 +8,7 @@ # ops [--site SITE] list operations as TSV (op, in, out, err) # tree [--site SITE] ASCII tree by op type # summary [--site SITE] counts by operation + segments touched -# diff diff two xlates (semantic, sorted-by-op) +# diff [--site SITE] diff two xlates (semantic, sorted-by-op) set -o pipefail NC_SELF="$0" @@ -135,10 +135,23 @@ cmd_summary() { } cmd_diff() { - local n1="$1" n2="$2" + # Accept --site like every other subcommand. Without it, site-scoped xlates + # (the common case — xlates live under /Xlate/) could not be located + # and diff always died with "no such xlate". --site applies to BOTH names. + local site="${HCISITE:-}" + local positional=() + while [ $# -gt 0 ]; do + case "$1" in + --site) shift; site="$1" ;; + *) positional+=("$1") ;; + esac + shift + done + local n1="${positional[0]:-}" n2="${positional[1]:-}" + [ -n "$n1" ] && [ -n "$n2" ] || die "usage: diff NAME1 NAME2 [--site SITE]" local f1 f2 - f1=$(locate_xlate "$n1") || die "no such xlate: $n1" - f2=$(locate_xlate "$n2") || die "no such xlate: $n2" + f1=$(locate_xlate "$n1" "$site") || die "no such xlate: $n1" + f2=$(locate_xlate "$n2" "$site") || die "no such xlate: $n2" diff -u <(parse_ops "$f1" | sort -k2) <(parse_ops "$f2" | sort -k2) }