@agentsfleet/orly 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +42 -0
- package/.githooks/pre-push +67 -0
- package/AGENTS.md +348 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/SOUL.md +90 -0
- package/audits/agents-md.md +497 -0
- package/audits/agents-md.sh +353 -0
- package/audits/cross-tier-rates.sh +105 -0
- package/audits/data.sh +185 -0
- package/audits/deinit-pairs.sh +224 -0
- package/audits/design-tokens.sh +143 -0
- package/audits/doc-read.sh +183 -0
- package/audits/error-codes.sh +221 -0
- package/audits/fixtures/clean.diff +15 -0
- package/audits/fixtures/dirty.diff +16 -0
- package/audits/lifecycle-anchors.sh +22 -0
- package/audits/logging.sh +217 -0
- package/audits/msid-ui.sh +144 -0
- package/audits/parity-dispatch.sh +51 -0
- package/audits/rule-ledger-lib.sh +264 -0
- package/audits/rule-ledger.sh +225 -0
- package/audits/rule-paths.sh +108 -0
- package/audits/spec-template.sh +258 -0
- package/audits/sql-mod.sh +102 -0
- package/audits/ufs.sh +303 -0
- package/bin/orly +26 -0
- package/core/operating-model.md +256 -0
- package/dispatch/edit_rules.md +69 -0
- package/dispatch/lib.sh +213 -0
- package/dispatch/lifecycle.md +187 -0
- package/dispatch/name_architecture.md +93 -0
- package/dispatch/verify.md +111 -0
- package/dispatch/write_any.md +740 -0
- package/dispatch/write_any.sh +49 -0
- package/dispatch/write_auth.md +36 -0
- package/dispatch/write_changelog.md +64 -0
- package/dispatch/write_documentation.md +45 -0
- package/dispatch/write_go.md +24 -0
- package/dispatch/write_http.md +45 -0
- package/dispatch/write_mdx.md +7 -0
- package/dispatch/write_python.md +8 -0
- package/dispatch/write_rust.md +38 -0
- package/dispatch/write_shell.md +8 -0
- package/dispatch/write_spec.md +105 -0
- package/dispatch/write_sql.md +112 -0
- package/dispatch/write_sql.sh +36 -0
- package/dispatch/write_ts_adhere_bun.md +493 -0
- package/dispatch/write_ts_adhere_bun.sh +37 -0
- package/dispatch/write_zig.md +852 -0
- package/dispatch/write_zig.sh +40 -0
- package/docs/CHANGELOG_VOICE.md +18 -0
- package/docs/DISPATCH_ARCHITECTURE.md +664 -0
- package/docs/DISPATCH_REVIEW_DISPOSITION.md +65 -0
- package/docs/DOCUMENTATION_RULES.md +209 -0
- package/docs/EXECUTE_DOC_READS.md +29 -0
- package/docs/HARNESS_VERIFY_OUTPUT.md +40 -0
- package/docs/LIFECYCLE_PATTERNS.md +316 -0
- package/docs/LOGGING_STANDARD.md +397 -0
- package/docs/ORLY_ARCHITECTURE.md +129 -0
- package/docs/REST_API_DESIGN_GUIDELINES.md +783 -0
- package/docs/RULE_ENFORCEMENT.md +41 -0
- package/docs/SCHEMA_CONVENTIONS.md +93 -0
- package/docs/TEMPLATE.md +411 -0
- package/docs/VERIFY_TIERS.md +57 -0
- package/docs/greptile-learnings/README.md +51 -0
- package/docs/greptile-learnings/RULES.md +1045 -0
- package/fixtures/registry-invalid.json +1 -0
- package/fixtures/registry-valid.json +18 -0
- package/fixtures/unclosed-pack-block.md +8 -0
- package/package.json +63 -0
- package/packs/language/go/rules.md +24 -0
- package/packs/language/mdx/rules.md +7 -0
- package/packs/language/python/rules.md +8 -0
- package/packs/language/rust/rules.md +38 -0
- package/packs/language/shell/rules.md +8 -0
- package/registry.json +405 -0
- package/schemas/evidence.schema.json +43 -0
- package/schemas/profile.schema.json +12 -0
- package/schemas/registry.schema.json +12 -0
- package/skills/orly-babysit-prs/SKILL.md +326 -0
- package/skills/orly-spec-new/SKILL.md +157 -0
- package/skills/orly-write-integration-test/SKILL.md +470 -0
- package/skills/orly-write-unit-test/SKILL.md +520 -0
- package/src/cli.test.ts +70 -0
- package/src/cli.ts +287 -0
- package/src/config.test.ts +226 -0
- package/src/config.ts +288 -0
- package/src/criteria.test.ts +137 -0
- package/src/criteria.ts +166 -0
- package/src/criteria_spec.ts +127 -0
- package/src/criteria_support.ts +53 -0
- package/src/gates.test.ts +183 -0
- package/src/gates.ts +169 -0
- package/src/gates_spec.test.ts +98 -0
- package/src/gates_test_support.ts +194 -0
- package/src/git_env.ts +20 -0
- package/src/install.test.ts +514 -0
- package/src/install.ts +447 -0
- package/src/install_nested.test.ts +32 -0
- package/src/model.test.ts +31 -0
- package/src/model.ts +245 -0
- package/src/references.test.ts +47 -0
- package/src/references.ts +147 -0
- package/src/render.test.ts +60 -0
- package/src/render.ts +94 -0
- package/src/surfaces.test.ts +43 -0
- package/src/surfaces.ts +89 -0
- package/src/validation.test.ts +11 -0
- package/src/validation.ts +87 -0
- package/src/verify.test.ts +13 -0
- package/src/verify.ts +59 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# error-codes.sh — orphan + dead code detection against the canonical
|
|
3
|
+
# error registry at src/agentsfleetd/errors/error_registry.zig, plus a raw-literal check
|
|
4
|
+
# enforcing that UZ-<CAT>-<NNN> strings only appear in registry/allowlist
|
|
5
|
+
# files (every other call site must reference the registry symbol).
|
|
6
|
+
#
|
|
7
|
+
# Dispatch façade: dispatch/write_any.md (Error Registry Gate)
|
|
8
|
+
# Fires in: CONFORM (via `make harness-verify` in `agentsfleet`).
|
|
9
|
+
#
|
|
10
|
+
# TECHNICAL DEBT (acknowledged on migration to dotfiles, 2026-05-11):
|
|
11
|
+
# The script hard-codes:
|
|
12
|
+
# - `UZ-` as the error-code prefix (agentsfleet schema)
|
|
13
|
+
# - `src/agentsfleetd/errors/error_registry.zig` as the canonical registry path
|
|
14
|
+
# - The raw-literal allowlist below (src/agentsfleetd/errors/**, src/runner/engine/client_errors.zig)
|
|
15
|
+
# A parameterised version that reads prefix + registry path + allowlist
|
|
16
|
+
# from a per-project config (e.g. `.audit-error-codes.toml`) is the right
|
|
17
|
+
# long-term shape; not done yet. Today, this script lives in dotfiles so
|
|
18
|
+
# the gate body and the audit binary stay co-located.
|
|
19
|
+
#
|
|
20
|
+
# Definitions:
|
|
21
|
+
# - DECLARED — every UZ-<CAT>-<NNN> string literal in error_registry.zig
|
|
22
|
+
# - USED — every UZ-<CAT>-<NNN> reference in src/**/*.zig (excluding
|
|
23
|
+
# the registry itself). agentsfleet/src/** is intentionally
|
|
24
|
+
# out of scope — the CLI consumes server-emitted UZ-* codes
|
|
25
|
+
# as opaque strings from API responses; the dispatcher's
|
|
26
|
+
# typed CliError variants are the CLI's named surface.
|
|
27
|
+
# - ORPHAN — USED but not DECLARED (BLOCKING)
|
|
28
|
+
# - DEAD — DECLARED but not USED (INFORMATIONAL)
|
|
29
|
+
# - RAW LEAK — UZ-<CAT>-<NNN> string literal in a source file outside
|
|
30
|
+
# the registry/allowlist (BLOCKING — RULE UFS at the error
|
|
31
|
+
# surface; non-allowlisted call sites must use a registry
|
|
32
|
+
# symbol, never duplicate the literal).
|
|
33
|
+
#
|
|
34
|
+
# Raw-literal allowlist (files where UZ-* literals are legitimate):
|
|
35
|
+
# - src/agentsfleetd/errors/** (canonical registry + entry tables)
|
|
36
|
+
# - src/runner/engine/client_errors.zig (the runner engine's own mirror file
|
|
37
|
+
# — see file header for build-module
|
|
38
|
+
# isolation rationale; deliberate
|
|
39
|
+
# duplication, all other executor
|
|
40
|
+
# sources import from there)
|
|
41
|
+
# - **/*_test.zig (tests)
|
|
42
|
+
# - src/zbench_fixtures.zig (benchmark fixture seeds)
|
|
43
|
+
# - src/agentsfleetd/http/test_harness.zig (HTTP test harness)
|
|
44
|
+
# - lines starting with `//` (comments)
|
|
45
|
+
# - lines annotated `// audit-error-codes: intentional-fake`
|
|
46
|
+
#
|
|
47
|
+
# Tests INCLUDED for orphan detection — same code-registry rules apply to
|
|
48
|
+
# test code. A test referencing a fake code (e.g. UZ-FAKE-999) for
|
|
49
|
+
# negative-path testing must annotate the line:
|
|
50
|
+
# `// audit-error-codes: intentional-fake` and the audit will skip it.
|
|
51
|
+
#
|
|
52
|
+
# Modes:
|
|
53
|
+
# --staged diff-scope: only files in `git diff --cached` are checked
|
|
54
|
+
# for new orphan refs (registry checked unconditionally)
|
|
55
|
+
# --all (default) full repo scan
|
|
56
|
+
#
|
|
57
|
+
# Exits 0 clean, 1 on orphans or raw leaks (DEAD findings never block).
|
|
58
|
+
|
|
59
|
+
set -euo pipefail
|
|
60
|
+
|
|
61
|
+
MODE="${1:-${SCOPE:-all}}"
|
|
62
|
+
ROOT="$(git rev-parse --show-toplevel)"
|
|
63
|
+
cd "$ROOT"
|
|
64
|
+
|
|
65
|
+
REGISTRY="src/agentsfleetd/errors/error_registry.zig"
|
|
66
|
+
[[ -f "$REGISTRY" ]] || { printf "FAIL: registry missing: %s\n" "$REGISTRY" >&2; exit 1; }
|
|
67
|
+
|
|
68
|
+
FAIL=0
|
|
69
|
+
fail() { printf "FAIL: %s\n" "$*" >&2; FAIL=1; }
|
|
70
|
+
ok() { printf "OK: %s\n" "$*"; }
|
|
71
|
+
note() { printf "NOTE: %s\n" "$*"; }
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# 1. Extract DECLARED codes from the registry.
|
|
75
|
+
# Pattern: literal "UZ-<CAT>-<NNN>" inside the registry file. <CAT> may
|
|
76
|
+
# contain digits but must start with a letter (e.g. UUIDV7), so the class is
|
|
77
|
+
# [A-Z][A-Z0-9]* — a bare [A-Z]+ stops at the first digit and silently drops
|
|
78
|
+
# the entire UZ-UUIDV7-* family (its category ends in a digit), leaving those
|
|
79
|
+
# codes invisible to the orphan/dead/used passes below.
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
declared_codes=$(grep -oE 'UZ-[A-Z][A-Z0-9]*-[0-9]{3,}\b' "$REGISTRY" | sort -u)
|
|
82
|
+
if [[ -z "$declared_codes" ]]; then
|
|
83
|
+
fail "no codes declared in $REGISTRY (registry empty?)"
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
declared_count=$(printf '%s\n' "$declared_codes" | wc -l | tr -d ' ')
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# 2. Determine USED codes.
|
|
90
|
+
# Source set: src/**/*.zig minus *_test.zig minus the registry itself,
|
|
91
|
+
# plus agentsfleet/src/**.
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
gather_used_paths() {
|
|
94
|
+
case "$MODE" in
|
|
95
|
+
--staged|staged)
|
|
96
|
+
git diff --cached --name-only --diff-filter=ACMRT \
|
|
97
|
+
| grep -E '^src/' \
|
|
98
|
+
| grep -vE '^src/agentsfleetd/errors/error_registry\.zig$' || true
|
|
99
|
+
;;
|
|
100
|
+
--all|all)
|
|
101
|
+
find src -type f -name '*.zig' 2>/dev/null \
|
|
102
|
+
| grep -vE '^src/agentsfleetd/errors/error_registry\.zig$'
|
|
103
|
+
;;
|
|
104
|
+
*)
|
|
105
|
+
printf "usage: %s [--staged|--all]\n" "$0" >&2
|
|
106
|
+
exit 64
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
mapfile -t USED_PATHS < <(gather_used_paths)
|
|
112
|
+
if [[ ${#USED_PATHS[@]} -eq 0 ]]; then
|
|
113
|
+
ok "no source files in scope ($MODE)"
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
used_codes=$(awk '
|
|
118
|
+
# Reset per-file so a trailing intentional-fake marker in the previous
|
|
119
|
+
# file cannot carry skip_next=1 into the first line of the next file.
|
|
120
|
+
FNR == 1 { skip_next = 0 }
|
|
121
|
+
# Skip the line carrying the marker AND the next non-blank line (the
|
|
122
|
+
# marker sits on the comment line above the code line in Zig style).
|
|
123
|
+
/audit-error-codes: intentional-fake/ { skip_next = 1; next }
|
|
124
|
+
/^[[:space:]]*$/ { next }
|
|
125
|
+
skip_next { skip_next = 0; next }
|
|
126
|
+
{
|
|
127
|
+
line = $0;
|
|
128
|
+
while (match(line, /UZ-[A-Z][A-Z0-9]*-[0-9][0-9][0-9][0-9]*/)) {
|
|
129
|
+
m = substr(line, RSTART, RLENGTH);
|
|
130
|
+
# Reject codes whose digit run is followed by a word char (rejects
|
|
131
|
+
# placeholder forms like UZ-INTERNAL-00X that grep would truncate).
|
|
132
|
+
tail_pos = RSTART + RLENGTH;
|
|
133
|
+
if (tail_pos <= length(line)) {
|
|
134
|
+
tail_char = substr(line, tail_pos, 1);
|
|
135
|
+
if (tail_char ~ /[A-Za-z0-9_]/) {
|
|
136
|
+
line = substr(line, tail_pos + 1);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
print m;
|
|
141
|
+
line = substr(line, tail_pos);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
' "${USED_PATHS[@]}" 2>/dev/null | sort -u || true)
|
|
145
|
+
used_count=0
|
|
146
|
+
[[ -n "$used_codes" ]] && used_count=$(printf '%s\n' "$used_codes" | wc -l | tr -d ' ')
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# 3. Compute orphans (USED − DECLARED). Blocking.
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
orphans=$(comm -23 <(printf '%s\n' "$used_codes") <(printf '%s\n' "$declared_codes") | grep -v '^$' || true)
|
|
152
|
+
if [[ -n "$orphans" ]]; then
|
|
153
|
+
fail "orphan codes (used but not declared in $REGISTRY):"
|
|
154
|
+
while IFS= read -r code; do
|
|
155
|
+
[[ -z "$code" ]] && continue
|
|
156
|
+
printf " %s refs:\n" "$code" >&2
|
|
157
|
+
grep -lE "$code" "${USED_PATHS[@]}" 2>/dev/null \
|
|
158
|
+
| head -3 \
|
|
159
|
+
| sed 's/^/ - /' >&2 || true
|
|
160
|
+
done <<<"$orphans"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# 4. Compute dead codes (DECLARED − USED). Informational only.
|
|
165
|
+
# --staged mode skips this (full set isn't computed against partial diff).
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
if [[ "$MODE" = "--all" || "$MODE" = "all" ]]; then
|
|
168
|
+
dead=$(comm -13 <(printf '%s\n' "$used_codes") <(printf '%s\n' "$declared_codes") | grep -v '^$' || true)
|
|
169
|
+
if [[ -n "$dead" ]]; then
|
|
170
|
+
dead_count=$(printf '%s\n' "$dead" | wc -l | tr -d ' ')
|
|
171
|
+
note "dead codes (declared but unreferenced — informational, $dead_count total):"
|
|
172
|
+
printf '%s\n' "$dead" | head -10 | sed 's/^/ /'
|
|
173
|
+
[[ "$dead_count" -gt 10 ]] && printf " ... and %d more\n" "$((dead_count - 10))"
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
# 4.5. Raw-literal check.
|
|
179
|
+
# Any "UZ-…" string literal outside the allowlist (and not inside a
|
|
180
|
+
# `//` comment or annotated with `audit-error-codes: intentional-fake`)
|
|
181
|
+
# is a violation: the call site must reference a registry symbol.
|
|
182
|
+
#
|
|
183
|
+
# Multi-segment codes (e.g. `UZ-OBS-OTEL-LOG-001`) are caught too,
|
|
184
|
+
# even though the orphan/dead detection above uses a stricter
|
|
185
|
+
# 2-segment pattern. We don't want a "UZ-X-Y-NNN" code reintroduced
|
|
186
|
+
# via a local const that the orphan pass silently ignores.
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
raw_leaks=$(awk '
|
|
189
|
+
FNR == 1 { skip_next = 0 }
|
|
190
|
+
# Skip the line carrying the marker AND the next non-blank line.
|
|
191
|
+
/audit-error-codes: intentional-fake/ { skip_next = 1; next }
|
|
192
|
+
/^[[:space:]]*\/\// { next }
|
|
193
|
+
/^[[:space:]]*$/ { next }
|
|
194
|
+
skip_next { skip_next = 0; next }
|
|
195
|
+
# Match any "UZ-XXX(-YYY)*-NNN" string literal.
|
|
196
|
+
/"UZ-[A-Z][A-Z0-9-]*-[0-9]+"/ {
|
|
197
|
+
print FILENAME ":" FNR ":" $0;
|
|
198
|
+
}
|
|
199
|
+
' $(printf '%s\n' "${USED_PATHS[@]}" | grep -vE \
|
|
200
|
+
'^src/agentsfleetd/errors/|/_?test_harness\.zig$|_test\.zig$|^src/runner/engine/client_errors\.zig$|^src/zbench_fixtures\.zig$' \
|
|
201
|
+
|| true) 2>/dev/null || true)
|
|
202
|
+
|
|
203
|
+
if [[ -n "$raw_leaks" ]]; then
|
|
204
|
+
fail "raw UZ-* literals found outside the registry allowlist (must reference a registry symbol):"
|
|
205
|
+
printf '%s\n' "$raw_leaks" | head -20 | sed 's/^/ /' >&2
|
|
206
|
+
raw_count=$(printf '%s\n' "$raw_leaks" | wc -l | tr -d ' ')
|
|
207
|
+
if [[ "$raw_count" -gt 20 ]]; then
|
|
208
|
+
printf " ... and %d more\n" "$((raw_count - 20))" >&2
|
|
209
|
+
fi
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
# 5. Verdict.
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
ok "registry: $declared_count declared, $used_count used"
|
|
216
|
+
if [[ $FAIL -ne 0 ]]; then
|
|
217
|
+
printf "\n🔴 ERROR REGISTRY GATE: violations found. Reference the registry symbol (e.g. \`error_codes.ERR_X\` or \`client_errors.ERR_X\` for the executor crate) instead of duplicating the literal.\n" >&2
|
|
218
|
+
exit 1
|
|
219
|
+
fi
|
|
220
|
+
ok "ERROR REGISTRY GATE: clean"
|
|
221
|
+
exit 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
diff --git a/src/example.zig b/src/example.zig
|
|
2
|
+
new file mode 100644
|
|
3
|
+
index 0000000..3333333
|
|
4
|
+
--- /dev/null
|
|
5
|
+
+++ b/src/example.zig
|
|
6
|
+
@@ -0,0 +1,2 @@
|
|
7
|
+
+const example_name = "sample";
|
|
8
|
+
+fn run() void {}
|
|
9
|
+
diff --git a/ui/packages/app/page.tsx b/ui/packages/app/page.tsx
|
|
10
|
+
new file mode 100644
|
|
11
|
+
index 0000000..4444444
|
|
12
|
+
--- /dev/null
|
|
13
|
+
+++ b/ui/packages/app/page.tsx
|
|
14
|
+
@@ -0,0 +1,1 @@
|
|
15
|
+
+import { Button } from "@design-system"; export default () => <Button>ok</Button>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
diff --git a/src/example.zig b/src/example.zig
|
|
2
|
+
new file mode 100644
|
|
3
|
+
index 0000000..1111111
|
|
4
|
+
--- /dev/null
|
|
5
|
+
+++ b/src/example.zig
|
|
6
|
+
@@ -0,0 +1,3 @@
|
|
7
|
+
+// M40_001 example
|
|
8
|
+
+pub const example_name = "M40_001 sample";
|
|
9
|
+
+pub fn run() void {}
|
|
10
|
+
diff --git a/ui/packages/app/page.tsx b/ui/packages/app/page.tsx
|
|
11
|
+
new file mode 100644
|
|
12
|
+
index 0000000..2222222
|
|
13
|
+
--- /dev/null
|
|
14
|
+
+++ b/ui/packages/app/page.tsx
|
|
15
|
+
@@ -0,0 +1,1 @@
|
|
16
|
+
+export default () => <button>raw</button>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# lifecycle-anchors.sh — semantic-drift tripwire between the rendered
|
|
3
|
+
# operating model and the lifecycle runbook façade. Dispatch parity proves
|
|
4
|
+
# INVENTORY (files ↔ table ↔ REQUIRED); this proves the load-bearing lifecycle
|
|
5
|
+
# strings exist in BOTH AGENTS.md (each stage's binding essence) and
|
|
6
|
+
# dispatch/lifecycle.md (the runbook), so neither side can drift away from the
|
|
7
|
+
# other while `make audit` stays green. Anchor list: LIFECYCLE_ANCHORS in
|
|
8
|
+
# audits/data.sh — extend it when a new load-bearing string lands in both.
|
|
9
|
+
#
|
|
10
|
+
# Sourced by audits/agents-md.sh (label: "lifecycle anchors").
|
|
11
|
+
# check_lifecycle_anchors <ROOT> # echoes FAIL lines, returns 0/1
|
|
12
|
+
|
|
13
|
+
check_lifecycle_anchors() {
|
|
14
|
+
local root="$1" rc=0 anchor
|
|
15
|
+
for anchor in "${LIFECYCLE_ANCHORS[@]}"; do
|
|
16
|
+
grep -qF -- "$anchor" "$root/AGENTS.md" \
|
|
17
|
+
|| { echo "FAIL: lifecycle anchor missing from AGENTS.md: $anchor"; rc=1; }
|
|
18
|
+
grep -qF -- "$anchor" "$root/dispatch/lifecycle.md" \
|
|
19
|
+
|| { echo "FAIL: lifecycle anchor missing from dispatch/lifecycle.md: $anchor"; rc=1; }
|
|
20
|
+
done
|
|
21
|
+
return $rc
|
|
22
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# logging.sh — flag log-emit drift against LOGGING_STANDARD.md.
|
|
3
|
+
#
|
|
4
|
+
# Dispatch façade: dispatch/write_any.md (Logging Gate)
|
|
5
|
+
# Fires in: CONFORM (via `make harness-verify` in `agentsfleet`).
|
|
6
|
+
#
|
|
7
|
+
# TECHNICAL DEBT (acknowledged on migration to dotfiles, 2026-05-11):
|
|
8
|
+
# The script hard-codes:
|
|
9
|
+
# - agentsfleet scope-prefix format from LOGGING_STANDARD §7 (the
|
|
10
|
+
# `log.scoped(...)` API path under `src/logging/`)
|
|
11
|
+
# - `UZ-XXX-NNN` as the `error_code=` substring per LOGGING_STANDARD §5
|
|
12
|
+
# - `src/` + `agentsfleet/src/` as the scan roots
|
|
13
|
+
# A parameterised version that reads prefix + scope-api path + scan
|
|
14
|
+
# roots from a per-project config is the right long-term shape; not
|
|
15
|
+
# done yet. Today, this script lives in dotfiles so the gate body and
|
|
16
|
+
# the audit binary stay co-located.
|
|
17
|
+
#
|
|
18
|
+
# Two severity tiers:
|
|
19
|
+
# BLOCK — exits 1, must fix:
|
|
20
|
+
# - `std.debug.print(` in non-test source under src/.
|
|
21
|
+
# - `console.log/debug/info/warn/error` in agentsfleet/src outside tests.
|
|
22
|
+
# INFO — surfaced for reviewer/agent attention, doesn't block:
|
|
23
|
+
# - `std.log.scoped(...)` outside `src/logging/` (LOGGING_STANDARD §7
|
|
24
|
+
# says only the named-module `log.scoped` API should be used;
|
|
25
|
+
# today's callers are pre-migration).
|
|
26
|
+
# - `std.log.{err,warn,info,debug}` calls (positional fmt format) — the
|
|
27
|
+
# old API. Migration to `log.<level>("event", .{...})` pending.
|
|
28
|
+
# - `err`/`warn` log calls without an `error_code=` substring nearby.
|
|
29
|
+
#
|
|
30
|
+
# Modes:
|
|
31
|
+
# --staged diff-scope: only files in `git diff --cached`
|
|
32
|
+
# --all (default) full src/ + agentsfleet/src/ scan
|
|
33
|
+
# --strict promote every INFO finding to BLOCK (post-migration use)
|
|
34
|
+
#
|
|
35
|
+
# Exits 0 clean, 1 on BLOCK findings.
|
|
36
|
+
|
|
37
|
+
set -euo pipefail
|
|
38
|
+
|
|
39
|
+
MODE="--all"
|
|
40
|
+
STRICT=0
|
|
41
|
+
for arg in "$@"; do
|
|
42
|
+
case "$arg" in
|
|
43
|
+
--staged|staged) MODE="--staged" ;;
|
|
44
|
+
--all|all) MODE="--all" ;;
|
|
45
|
+
--strict) STRICT=1 ;;
|
|
46
|
+
-h|--help)
|
|
47
|
+
printf "usage: %s [--staged|--all] [--strict]\n" "$0"
|
|
48
|
+
exit 0
|
|
49
|
+
;;
|
|
50
|
+
*) printf "unknown arg: %s\n" "$arg" >&2; exit 64 ;;
|
|
51
|
+
esac
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
ROOT="$(git rev-parse --show-toplevel)"
|
|
55
|
+
cd "$ROOT"
|
|
56
|
+
|
|
57
|
+
FAIL=0
|
|
58
|
+
INFO_COUNT=0
|
|
59
|
+
fail() { printf "FAIL: %s\n" "$*" >&2; FAIL=1; }
|
|
60
|
+
ok() { printf "OK: %s\n" "$*"; }
|
|
61
|
+
note() { printf "INFO: %s\n" "$*"; INFO_COUNT=$((INFO_COUNT + 1)); }
|
|
62
|
+
|
|
63
|
+
# Test-file carve-out — skip Zig test sources from every detection
|
|
64
|
+
# pass. Test code legitimately exercises log emits (deliberate err
|
|
65
|
+
# paths, logger smoke tests) and uses raw std.log to keep tests
|
|
66
|
+
# self-contained; the migration discipline applies to production
|
|
67
|
+
# source. Returns 0 (skip) for test paths, 1 (scan) otherwise.
|
|
68
|
+
is_test_zig() {
|
|
69
|
+
case "$1" in
|
|
70
|
+
*_test.zig) return 0 ;;
|
|
71
|
+
*_test_harness.zig) return 0 ;;
|
|
72
|
+
*_test_helper.zig) return 0 ;;
|
|
73
|
+
*/tests/*) return 0 ;;
|
|
74
|
+
esac
|
|
75
|
+
case "$(basename "$1")" in
|
|
76
|
+
test_harness.zig) return 0 ;;
|
|
77
|
+
test_helper.zig) return 0 ;;
|
|
78
|
+
esac
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# 1. Gather files in scope.
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
gather_paths() {
|
|
86
|
+
case "$MODE" in
|
|
87
|
+
--staged)
|
|
88
|
+
git diff --cached --name-only --diff-filter=ACMRT \
|
|
89
|
+
| grep -E '^(src/.*\.zig$|agentsfleet/src/.*\.(js|jsx|ts|tsx)$)' || true
|
|
90
|
+
;;
|
|
91
|
+
--all)
|
|
92
|
+
find src -type f -name '*.zig' 2>/dev/null
|
|
93
|
+
find agentsfleet/src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) 2>/dev/null
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
mapfile -t FILES < <(gather_paths)
|
|
99
|
+
if [[ ${#FILES[@]} -eq 0 ]]; then
|
|
100
|
+
ok "no source files in scope ($MODE)"
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Build the in-scope file subsets once. The per-section loops below
|
|
105
|
+
# previously ran 3–4 forks × N files; M70 perf pass batches them into
|
|
106
|
+
# single awk/grep passes.
|
|
107
|
+
zig_nontest=()
|
|
108
|
+
js_nontest=()
|
|
109
|
+
for f in "${FILES[@]}"; do
|
|
110
|
+
if [[ "$f" == *.zig ]] && ! is_test_zig "$f"; then
|
|
111
|
+
zig_nontest+=("$f")
|
|
112
|
+
fi
|
|
113
|
+
case "$f" in
|
|
114
|
+
agentsfleet/src/*.test.*|agentsfleet/src/*.spec.*|agentsfleet/src/tests/*) ;;
|
|
115
|
+
agentsfleet/src/*.js|agentsfleet/src/*.jsx|agentsfleet/src/*.ts|agentsfleet/src/*.tsx)
|
|
116
|
+
js_nontest+=("$f")
|
|
117
|
+
;;
|
|
118
|
+
esac
|
|
119
|
+
done
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# 2. BLOCKING: std.debug.print in non-test Zig source.
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
debug_print_hits=0
|
|
125
|
+
if [[ ${#zig_nontest[@]} -gt 0 ]]; then
|
|
126
|
+
# Single awk across every non-test .zig file; FNR == 1 resets the
|
|
127
|
+
# `test "..."` block tracker per file. Skips lines inside inline tests.
|
|
128
|
+
while IFS= read -r match; do
|
|
129
|
+
[[ -z "$match" ]] && continue
|
|
130
|
+
f="${match%%:*}"
|
|
131
|
+
ln="${match#*:}"
|
|
132
|
+
fail "$f:$ln — \`std.debug.print\` in non-test source (LOGGING_STANDARD §10A.L1)"
|
|
133
|
+
debug_print_hits=$((debug_print_hits + 1))
|
|
134
|
+
done < <(awk '
|
|
135
|
+
FNR == 1 { in_test = 0 }
|
|
136
|
+
/^test "/ { in_test = 1; next }
|
|
137
|
+
/^}/ { in_test = 0; next }
|
|
138
|
+
/(^|[^A-Za-z0-9_])std\.debug\.print\(/ { if (!in_test) printf "%s:%d\n", FILENAME, FNR }
|
|
139
|
+
' "${zig_nontest[@]}")
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# 3. BLOCKING: console.log/debug/info/warn/error in agentsfleet/src non-test.
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
console_hits=0
|
|
146
|
+
if [[ ${#js_nontest[@]} -gt 0 ]]; then
|
|
147
|
+
while IFS= read -r match; do
|
|
148
|
+
[[ -z "$match" ]] && continue
|
|
149
|
+
f="${match%%:*}"
|
|
150
|
+
rest="${match#*:}"
|
|
151
|
+
ln="${rest%%:*}"
|
|
152
|
+
fail "$f:$ln — \`console.*\` in non-test source (write_ts_adhere_bun.md §10, LOGGING_STANDARD §8)"
|
|
153
|
+
console_hits=$((console_hits + 1))
|
|
154
|
+
done < <(grep -nHE '\bconsole\.(log|debug|info|warn|error)\(' "${js_nontest[@]}" 2>/dev/null || true)
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
# 4. INFO: std.log.scoped outside src/logging/ (pre-migration to the named
|
|
159
|
+
# `log` module's scoped API). The audit no longer carves out src/auth/ —
|
|
160
|
+
# the named module is import-able from layer-isolated trees, so the
|
|
161
|
+
# portability exception is gone.
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
scoped_hits=0
|
|
164
|
+
scoped_eligible=()
|
|
165
|
+
for f in "${zig_nontest[@]}"; do
|
|
166
|
+
case "$f" in
|
|
167
|
+
src/logging/*) continue ;;
|
|
168
|
+
esac
|
|
169
|
+
scoped_eligible+=("$f")
|
|
170
|
+
done
|
|
171
|
+
if [[ ${#scoped_eligible[@]} -gt 0 ]]; then
|
|
172
|
+
while IFS= read -r line; do
|
|
173
|
+
[[ -z "$line" ]] && continue
|
|
174
|
+
count="${line%% *}"
|
|
175
|
+
f="${line#* }"
|
|
176
|
+
note "$f — $count call(s) to \`std.log.scoped\` (migrate to \`logging.scoped\` per LOGGING_STANDARD §7)"
|
|
177
|
+
scoped_hits=$((scoped_hits + count))
|
|
178
|
+
done < <(grep -cHE '\bstd\.log\.scoped\(' "${scoped_eligible[@]}" 2>/dev/null \
|
|
179
|
+
| awk -F: '$2 > 0 { print $2, $1 }')
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# 5. INFO: err/warn logs without `error_code=` substring on the same line.
|
|
184
|
+
# Heuristic — captures the common case where an err/warn line should
|
|
185
|
+
# embed UZ-XXX-NNN per LOGGING_STANDARD §5.
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
missing_code_hits=0
|
|
188
|
+
if [[ ${#zig_nontest[@]} -gt 0 ]]; then
|
|
189
|
+
while IFS= read -r line; do
|
|
190
|
+
[[ -z "$line" ]] && continue
|
|
191
|
+
[[ "$line" == *error_code=* ]] && continue
|
|
192
|
+
f="${line%%:*}"
|
|
193
|
+
rest="${line#*:}"
|
|
194
|
+
ln="${rest%%:*}"
|
|
195
|
+
note "$f:$ln — \`std.log.{err,warn}\` without \`error_code=\` (LOGGING_STANDARD §5)"
|
|
196
|
+
missing_code_hits=$((missing_code_hits + 1))
|
|
197
|
+
done < <(grep -nHE '\bstd\.log\.(err|warn)\b' "${zig_nontest[@]}" 2>/dev/null || true)
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
# 6. Promote INFO to BLOCK in --strict mode.
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
if [[ $STRICT -eq 1 && $INFO_COUNT -gt 0 ]]; then
|
|
204
|
+
fail "--strict: $INFO_COUNT informational findings promoted to blocking"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# 7. Verdict.
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
ok "scanned ${#FILES[@]} files; std.debug.print=$debug_print_hits console.*=$console_hits std.log.scoped=$scoped_hits missing-error_code=$missing_code_hits"
|
|
211
|
+
if [[ $FAIL -ne 0 ]]; then
|
|
212
|
+
printf "\n🔴 LOGGING GATE: blocking violations. See dispatch/write_any.md (Logging Gate).\n" >&2
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
[[ $INFO_COUNT -gt 0 ]] && note "$INFO_COUNT informational findings; not blocking. Use --strict to enforce."
|
|
216
|
+
ok "LOGGING GATE: clean (blocking layer)"
|
|
217
|
+
exit 0
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# msid-ui.sh — single awk pass over the diff, running the two
|
|
3
|
+
# discipline checks that share a regex pass (MS-ID + UI substitution).
|
|
4
|
+
# Previously `combined.sh`; renamed once the PUB clause moved to
|
|
5
|
+
# zlint + agent chat-output discipline (see commit history).
|
|
6
|
+
#
|
|
7
|
+
# Emits per-line hits for:
|
|
8
|
+
# * MS-ID — milestone-id leak in source/config (M{N}_{NNN}, §X.Y, T{N},
|
|
9
|
+
# dim {N}.{N}) outside docs/ + node_modules/ + vendor/.
|
|
10
|
+
# Test files are NOT exempt (RULE TST-NAM).
|
|
11
|
+
# * UI — raw HTML element in ui/packages/app/*.{tsx,jsx} where a
|
|
12
|
+
# design-system primitive exists (UI Component Substitution Gate).
|
|
13
|
+
#
|
|
14
|
+
# Non-empty hit list → exit 1. Otherwise exit 0 + one OK line.
|
|
15
|
+
#
|
|
16
|
+
# Note on PUB:
|
|
17
|
+
# This script no longer flags `pub` declarations. The PUB GATE
|
|
18
|
+
# (dispatch/write_zig.md, pub-surface) has two mechanical enforcers and one
|
|
19
|
+
# human one; this script was a fourth, redundant layer:
|
|
20
|
+
# * Orphan check ("is anyone using this pub?") → zlint's
|
|
21
|
+
# `unused-decls: error` rule, run by `make lint` later in the
|
|
22
|
+
# same pre-commit pass. A `pub` with no in-tree consumer fails
|
|
23
|
+
# there. The audit's regex was duplicating this signal with
|
|
24
|
+
# false positives (e.g. variant lines inside private blocks)
|
|
25
|
+
# and no way to distinguish necessary pubs from gratuitous ones.
|
|
26
|
+
# * Design call ("is this pub shape right?") → the agent's
|
|
27
|
+
# chat-printed PUB GATE verdict block, which the script cannot
|
|
28
|
+
# see by construction. A regex on `^pub ` was approximating
|
|
29
|
+
# this as friction, but produced unfixable false positives on
|
|
30
|
+
# every legitimate new public surface and had no override path.
|
|
31
|
+
# Removing the PUB clause aligns with the gate body's own design
|
|
32
|
+
# (which already delegates mechanical work to zlint and design work
|
|
33
|
+
# to the agent's chat output).
|
|
34
|
+
#
|
|
35
|
+
# Per-check scope (M70):
|
|
36
|
+
# Unlike the rest of the audit-*.sh family, this script stays
|
|
37
|
+
# diff-shaped (asserts on *added* `^\+` lines, not file state).
|
|
38
|
+
# Each sub-check's rule is shaped as "don't introduce X" rather than
|
|
39
|
+
# "X must not exist anywhere", so a full-codebase scan would change
|
|
40
|
+
# the semantic:
|
|
41
|
+
# * MS-ID — flags milestone identifiers added in this commit.
|
|
42
|
+
# Historical docs in `done/` legitimately contain them,
|
|
43
|
+
# and code comments may reference them in old commits.
|
|
44
|
+
# Only newly-added M{N}_{NNN} citations should fail.
|
|
45
|
+
# * UI — flags raw HTML primitives added now where a design-system
|
|
46
|
+
# component exists. Legacy raw-HTML in unrelated files is
|
|
47
|
+
# cleaned by the touch-it-fix-it rule (RULE NLR), not by
|
|
48
|
+
# this audit's scope.
|
|
49
|
+
#
|
|
50
|
+
# Conversion to full-codebase scope needs separate research. Pre-commit
|
|
51
|
+
# sees staged content through `git diff --cached`, so staged-but-uncommitted
|
|
52
|
+
# content is covered by this script's checks.
|
|
53
|
+
#
|
|
54
|
+
# Modes:
|
|
55
|
+
# --staged (default) diff against `git diff --cached -U0`
|
|
56
|
+
# — pre-commit context; index includes staged content
|
|
57
|
+
# --diff diff against origin/main (vs BASE...HEAD)
|
|
58
|
+
# — used by `make harness-verify-all` periodic deep audit
|
|
59
|
+
#
|
|
60
|
+
# Dispatch façades:
|
|
61
|
+
# dispatch/write_any.md (Milestone-ID Gate)
|
|
62
|
+
# dispatch/write_ts_adhere_bun.md (UI Component Substitution)
|
|
63
|
+
# dispatch/write_zig.md (pub-surface) (handled by zlint + agent chat output, not this script)
|
|
64
|
+
|
|
65
|
+
set -euo pipefail
|
|
66
|
+
|
|
67
|
+
MODE="${1:---staged}"
|
|
68
|
+
ROOT="$(git rev-parse --show-toplevel)"
|
|
69
|
+
cd "$ROOT"
|
|
70
|
+
|
|
71
|
+
case "$MODE" in
|
|
72
|
+
--staged|staged)
|
|
73
|
+
DIFF_CMD="git diff --cached -U0"
|
|
74
|
+
LABEL="staged"
|
|
75
|
+
;;
|
|
76
|
+
--diff|diff)
|
|
77
|
+
BASE="${BASE:-origin/main}"
|
|
78
|
+
if ! git rev-parse --verify "$BASE" >/dev/null 2>&1; then BASE="HEAD"; fi
|
|
79
|
+
DIFF_CMD="git diff -U0 ${BASE}...HEAD"
|
|
80
|
+
LABEL="vs $BASE"
|
|
81
|
+
;;
|
|
82
|
+
--all|all)
|
|
83
|
+
# No "all" mode — this audit is diff-shaped by construction (it
|
|
84
|
+
# asserts on *added* lines, not file state). Force callers to pick.
|
|
85
|
+
echo "usage: $0 [--staged|--diff]" >&2
|
|
86
|
+
exit 2
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
echo "usage: $0 [--staged|--diff]" >&2; exit 2 ;;
|
|
90
|
+
esac
|
|
91
|
+
|
|
92
|
+
# Single awk pass; reads stdin (the unified diff).
|
|
93
|
+
# Override-recognition: an immediately-preceding `+` line in the diff
|
|
94
|
+
# containing the relevant override marker suppresses the next hit for
|
|
95
|
+
# that rule on the current `+` line.
|
|
96
|
+
hits=$($DIFF_CMD | awk '
|
|
97
|
+
/^\+\+\+ b\// { f=$2; sub("^b/","",f); prev_added=""; next }
|
|
98
|
+
/^[^+]/ { prev_added=""; next }
|
|
99
|
+
/^\+/ {
|
|
100
|
+
line=$0
|
|
101
|
+
sub(/^\+/,"",line)
|
|
102
|
+
ms_id_override = (prev_added ~ /MILESTONE ID ALLOWED per user override/)
|
|
103
|
+
ui_override = (prev_added ~ /UI GATE: SKIPPED per user override/)
|
|
104
|
+
if (!ms_id_override &&
|
|
105
|
+
f ~ /\.(zig|sql|ts|tsx|js|jsx|py|rs|go|sh|toml|yaml|json)$/ &&
|
|
106
|
+
f !~ /^(docs|node_modules|vendor|third_party)\//) {
|
|
107
|
+
if (match(line, /M[0-9]+_[0-9]+|§[0-9]+(\.[0-9]+)+|(^|[^A-Za-z0-9_])T[0-9]+([^A-Za-z0-9_]|$)|(^|[^A-Za-z0-9_])dim [0-9]+\.[0-9]+([^A-Za-z0-9_]|$)/)) {
|
|
108
|
+
print "MS-ID " f ": " line
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
# react-hook-form caveat: its prescribed shape is a raw
|
|
112
|
+
# <form onSubmit={form.handleSubmit(...)}> inside the design-system <Form>
|
|
113
|
+
# provider (see design-system Form.tsx docstring). No DS form-element
|
|
114
|
+
# primitive exists to substitute to, so the substitute-if-a-primitive-exists
|
|
115
|
+
# rule leaves the RHF <form> as the correct raw element. Exempt exactly that
|
|
116
|
+
# shape; a bare <form> or <form action=...> with no handleSubmit still trips.
|
|
117
|
+
rhf_form = (line ~ /<form[ \t]/ && line ~ /handleSubmit/)
|
|
118
|
+
# <section> landmark caveat: a semantic <section aria-label> region has no DS
|
|
119
|
+
# primitive (Section renders a <div>; role="region" trips oxlint
|
|
120
|
+
# jsx-a11y/prefer-tag-over-role, which mandates the raw tag). The DS pattern
|
|
121
|
+
# is <Section asChild><section …>, so exempt a <section> whose immediately
|
|
122
|
+
# preceding added line opens <Section asChild>. A bare/unwrapped <section>
|
|
123
|
+
# still trips.
|
|
124
|
+
ds_section = (line ~ /<section[ \t]/ && prev_added ~ /<Section asChild>/)
|
|
125
|
+
if (!ui_override && !rhf_form && !ds_section &&
|
|
126
|
+
f ~ /^ui\/packages\/app\/.*\.(tsx|jsx)$/ &&
|
|
127
|
+
line ~ /<(section|button|input|dialog|article|nav|header|form)[ \t>\/]/) {
|
|
128
|
+
print "UI " f ": " line
|
|
129
|
+
}
|
|
130
|
+
prev_added = line
|
|
131
|
+
}
|
|
132
|
+
')
|
|
133
|
+
|
|
134
|
+
if [ -n "$hits" ]; then
|
|
135
|
+
echo "FAIL: audit-msid-ui ($LABEL) — MS-ID / UI hits below"
|
|
136
|
+
printf '%s\n' "$hits"
|
|
137
|
+
echo
|
|
138
|
+
echo "Resolve each hit OR carve out via the relevant gate's override comment."
|
|
139
|
+
echo " MS-ID — strip the marker; production code must be milestone-free (RULE TST-NAM)"
|
|
140
|
+
echo " UI — use the design-system primitive; carve out with 'UI GATE: SKIPPED'"
|
|
141
|
+
exit 1
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
echo "OK: audit-msid-ui ($LABEL) — 0 hits"
|