@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.
Files changed (112) hide show
  1. package/.githooks/pre-commit +42 -0
  2. package/.githooks/pre-push +67 -0
  3. package/AGENTS.md +348 -0
  4. package/LICENSE +21 -0
  5. package/README.md +249 -0
  6. package/SOUL.md +90 -0
  7. package/audits/agents-md.md +497 -0
  8. package/audits/agents-md.sh +353 -0
  9. package/audits/cross-tier-rates.sh +105 -0
  10. package/audits/data.sh +185 -0
  11. package/audits/deinit-pairs.sh +224 -0
  12. package/audits/design-tokens.sh +143 -0
  13. package/audits/doc-read.sh +183 -0
  14. package/audits/error-codes.sh +221 -0
  15. package/audits/fixtures/clean.diff +15 -0
  16. package/audits/fixtures/dirty.diff +16 -0
  17. package/audits/lifecycle-anchors.sh +22 -0
  18. package/audits/logging.sh +217 -0
  19. package/audits/msid-ui.sh +144 -0
  20. package/audits/parity-dispatch.sh +51 -0
  21. package/audits/rule-ledger-lib.sh +264 -0
  22. package/audits/rule-ledger.sh +225 -0
  23. package/audits/rule-paths.sh +108 -0
  24. package/audits/spec-template.sh +258 -0
  25. package/audits/sql-mod.sh +102 -0
  26. package/audits/ufs.sh +303 -0
  27. package/bin/orly +26 -0
  28. package/core/operating-model.md +256 -0
  29. package/dispatch/edit_rules.md +69 -0
  30. package/dispatch/lib.sh +213 -0
  31. package/dispatch/lifecycle.md +187 -0
  32. package/dispatch/name_architecture.md +93 -0
  33. package/dispatch/verify.md +111 -0
  34. package/dispatch/write_any.md +740 -0
  35. package/dispatch/write_any.sh +49 -0
  36. package/dispatch/write_auth.md +36 -0
  37. package/dispatch/write_changelog.md +64 -0
  38. package/dispatch/write_documentation.md +45 -0
  39. package/dispatch/write_go.md +24 -0
  40. package/dispatch/write_http.md +45 -0
  41. package/dispatch/write_mdx.md +7 -0
  42. package/dispatch/write_python.md +8 -0
  43. package/dispatch/write_rust.md +38 -0
  44. package/dispatch/write_shell.md +8 -0
  45. package/dispatch/write_spec.md +105 -0
  46. package/dispatch/write_sql.md +112 -0
  47. package/dispatch/write_sql.sh +36 -0
  48. package/dispatch/write_ts_adhere_bun.md +493 -0
  49. package/dispatch/write_ts_adhere_bun.sh +37 -0
  50. package/dispatch/write_zig.md +852 -0
  51. package/dispatch/write_zig.sh +40 -0
  52. package/docs/CHANGELOG_VOICE.md +18 -0
  53. package/docs/DISPATCH_ARCHITECTURE.md +664 -0
  54. package/docs/DISPATCH_REVIEW_DISPOSITION.md +65 -0
  55. package/docs/DOCUMENTATION_RULES.md +209 -0
  56. package/docs/EXECUTE_DOC_READS.md +29 -0
  57. package/docs/HARNESS_VERIFY_OUTPUT.md +40 -0
  58. package/docs/LIFECYCLE_PATTERNS.md +316 -0
  59. package/docs/LOGGING_STANDARD.md +397 -0
  60. package/docs/ORLY_ARCHITECTURE.md +129 -0
  61. package/docs/REST_API_DESIGN_GUIDELINES.md +783 -0
  62. package/docs/RULE_ENFORCEMENT.md +41 -0
  63. package/docs/SCHEMA_CONVENTIONS.md +93 -0
  64. package/docs/TEMPLATE.md +411 -0
  65. package/docs/VERIFY_TIERS.md +57 -0
  66. package/docs/greptile-learnings/README.md +51 -0
  67. package/docs/greptile-learnings/RULES.md +1045 -0
  68. package/fixtures/registry-invalid.json +1 -0
  69. package/fixtures/registry-valid.json +18 -0
  70. package/fixtures/unclosed-pack-block.md +8 -0
  71. package/package.json +63 -0
  72. package/packs/language/go/rules.md +24 -0
  73. package/packs/language/mdx/rules.md +7 -0
  74. package/packs/language/python/rules.md +8 -0
  75. package/packs/language/rust/rules.md +38 -0
  76. package/packs/language/shell/rules.md +8 -0
  77. package/registry.json +405 -0
  78. package/schemas/evidence.schema.json +43 -0
  79. package/schemas/profile.schema.json +12 -0
  80. package/schemas/registry.schema.json +12 -0
  81. package/skills/orly-babysit-prs/SKILL.md +326 -0
  82. package/skills/orly-spec-new/SKILL.md +157 -0
  83. package/skills/orly-write-integration-test/SKILL.md +470 -0
  84. package/skills/orly-write-unit-test/SKILL.md +520 -0
  85. package/src/cli.test.ts +70 -0
  86. package/src/cli.ts +287 -0
  87. package/src/config.test.ts +226 -0
  88. package/src/config.ts +288 -0
  89. package/src/criteria.test.ts +137 -0
  90. package/src/criteria.ts +166 -0
  91. package/src/criteria_spec.ts +127 -0
  92. package/src/criteria_support.ts +53 -0
  93. package/src/gates.test.ts +183 -0
  94. package/src/gates.ts +169 -0
  95. package/src/gates_spec.test.ts +98 -0
  96. package/src/gates_test_support.ts +194 -0
  97. package/src/git_env.ts +20 -0
  98. package/src/install.test.ts +514 -0
  99. package/src/install.ts +447 -0
  100. package/src/install_nested.test.ts +32 -0
  101. package/src/model.test.ts +31 -0
  102. package/src/model.ts +245 -0
  103. package/src/references.test.ts +47 -0
  104. package/src/references.ts +147 -0
  105. package/src/render.test.ts +60 -0
  106. package/src/render.ts +94 -0
  107. package/src/surfaces.test.ts +43 -0
  108. package/src/surfaces.ts +89 -0
  109. package/src/validation.test.ts +11 -0
  110. package/src/validation.ts +87 -0
  111. package/src/verify.test.ts +13 -0
  112. 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"