@agentsfleet/orly 0.7.0 → 0.7.1

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/audits/logging.sh CHANGED
@@ -9,7 +9,7 @@
9
9
  # - agentsfleet scope-prefix format from LOGGING_STANDARD §7 (the
10
10
  # `log.scoped(...)` API path under `src/logging/`)
11
11
  # - `UZ-XXX-NNN` as the `error_code=` substring per LOGGING_STANDARD §5
12
- # - `src/` + `rustd/` + `agentsfleet/src/` as the scan roots
12
+ # - `src/` + `agentsfleet/src/` as the Zig and TypeScript scan roots
13
13
  # A parameterised version that reads prefix + scope-api path + scan
14
14
  # roots from a per-project config is the right long-term shape; not
15
15
  # done yet. Today, this script lives in dotfiles so the gate body and
@@ -19,7 +19,9 @@
19
19
  # BLOCK — exits 1, must fix:
20
20
  # - `std.debug.print(` in non-test source under src/.
21
21
  # - `println!` / `eprintln!` / `dbg!`, missing `event`, or positional
22
- # tracing formatting in non-test Rust source under rustd/.
22
+ # tracing formatting in runtime Rust source. Direct stream writes
23
+ # may carry `// logging: <reason>` on or immediately above the
24
+ # emit when stdout or stderr is the intended program interface.
23
25
  # - `console.log/debug/info/warn/error` in agentsfleet/src outside tests.
24
26
  # INFO — surfaced for reviewer/agent attention, doesn't block:
25
27
  # - `std.log.scoped(...)` outside `src/logging/` (LOGGING_STANDARD §7
@@ -31,7 +33,8 @@
31
33
  #
32
34
  # Modes:
33
35
  # --staged diff-scope: only files in `git diff --cached`
34
- # --all (default) full src/ + rustd/ + agentsfleet/src/ scan
36
+ # --all (default) full src/ + every tracked/unignored *.rs +
37
+ # agentsfleet/src/ scan
35
38
  # --strict promote every INFO finding to BLOCK (post-migration use)
36
39
  #
37
40
  # Exits 0 clean, 1 on BLOCK findings.
@@ -81,9 +84,9 @@ is_test_zig() {
81
84
  return 1
82
85
  }
83
86
 
84
- is_test_rust_path() {
87
+ is_non_runtime_rust_path() {
85
88
  case "$1" in
86
- */tests/*|*/benches/*) return 0 ;;
89
+ build.rs|*/build.rs|tests/*|*/tests/*|benches/*|*/benches/*|examples/*|*/examples/*) return 0 ;;
87
90
  esac
88
91
  return 1
89
92
  }
@@ -95,11 +98,13 @@ gather_paths() {
95
98
  case "$MODE" in
96
99
  --staged)
97
100
  git diff --cached --name-only --diff-filter=ACMRT \
98
- | grep -E '^(src/.*\.zig$|rustd/.*\.rs$|agentsfleet/src/.*\.(js|jsx|ts|tsx)$)' || true
101
+ | grep -E '(^src/.*\.zig$|\.rs$|^agentsfleet/src/.*\.(js|jsx|ts|tsx)$)' || true
99
102
  ;;
100
103
  --all)
101
104
  find src -type f -name '*.zig' 2>/dev/null || true
102
- find rustd -type f -name '*.rs' 2>/dev/null || true
105
+ while IFS= read -r path; do
106
+ [[ -f "$path" ]] && printf '%s\n' "$path"
107
+ done < <(git ls-files --cached --others --exclude-standard -- '*.rs')
103
108
  find agentsfleet/src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) 2>/dev/null || true
104
109
  ;;
105
110
  esac
@@ -116,12 +121,13 @@ fi
116
121
  # single awk/grep passes.
117
122
  zig_nontest=()
118
123
  rust_nontest=()
124
+ rust_candidates=()
119
125
  js_nontest=()
120
126
  for f in "${FILES[@]}"; do
121
127
  if [[ "$f" == *.zig ]] && ! is_test_zig "$f"; then
122
128
  zig_nontest+=("$f")
123
129
  fi
124
- if [[ "$f" == *.rs ]] && ! is_test_rust_path "$f"; then
130
+ if [[ "$f" == *.rs ]] && ! is_non_runtime_rust_path "$f"; then
125
131
  rust_nontest+=("$f")
126
132
  fi
127
133
  case "$f" in
@@ -132,6 +138,10 @@ for f in "${FILES[@]}"; do
132
138
  esac
133
139
  done
134
140
 
141
+ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
142
+ mapfile -t rust_candidates < <(grep -lE '(^|[^[:alnum:]_])(println|eprintln|dbg)!|tracing::(error|warn|info|debug|trace)!' "${rust_nontest[@]}" 2>/dev/null || true)
143
+ fi
144
+
135
145
  # ---------------------------------------------------------------------------
136
146
  # 2. BLOCKING: std.debug.print in non-test Zig source.
137
147
  # ---------------------------------------------------------------------------
@@ -146,10 +156,40 @@ if [[ ${#zig_nontest[@]} -gt 0 ]]; then
146
156
  fail "$f:$ln — \`std.debug.print\` in non-test source (LOGGING_STANDARD §10A.L1)"
147
157
  debug_print_hits=$((debug_print_hits + 1))
148
158
  done < <(awk '
149
- FNR == 1 { in_test = 0 }
150
- /^test "/ { in_test = 1; next }
151
- /^}/ { in_test = 0; next }
152
- /(^|[^A-Za-z0-9_])std\.debug\.print\(/ { if (!in_test) printf "%s:%d\n", FILENAME, FNR }
159
+ function comment_text(text, output, cursor, char, escaped, quoted) {
160
+ escaped = 0
161
+ quoted = 0
162
+ for (cursor = 1; cursor <= length(text); cursor++) {
163
+ char = substr(text, cursor, 1)
164
+ if (quoted) {
165
+ if (escaped) escaped = 0
166
+ else if (char == "\\") escaped = 1
167
+ else if (char == "\"") quoted = 0
168
+ continue
169
+ }
170
+ if (char == "\"") {
171
+ quoted = 1
172
+ continue
173
+ }
174
+ if (char == "/" && substr(text, cursor + 1, 1) == "/")
175
+ return substr(text, cursor + 2)
176
+ }
177
+ return ""
178
+ }
179
+ function has_logging_reason(text, comment) {
180
+ if (text ~ /^[[:space:]]*\\\\/) return 0
181
+ comment = comment_text(text)
182
+ return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
183
+ }
184
+ FNR == 1 { in_test = 0; previous_annotation = 0 }
185
+ {
186
+ annotated = has_logging_reason($0) || previous_annotation
187
+ if ($0 ~ /^test "/) in_test = 1
188
+ else if ($0 ~ /^}/) in_test = 0
189
+ else if ($0 ~ /(^|[^A-Za-z0-9_])std\.debug\.print\(/ && !in_test && !annotated)
190
+ printf "%s:%d\n", FILENAME, FNR
191
+ previous_annotation = has_logging_reason($0)
192
+ }
153
193
  ' "${zig_nontest[@]}")
154
194
  fi
155
195
 
@@ -160,7 +200,7 @@ fi
160
200
  rust_direct_hits=0
161
201
  rust_missing_event_hits=0
162
202
  rust_positional_hits=0
163
- if [[ ${#rust_nontest[@]} -gt 0 ]]; then
203
+ if [[ ${#rust_candidates[@]} -gt 0 ]]; then
164
204
  while IFS='|' read -r kind f ln; do
165
205
  [[ -z "$kind" ]] && continue
166
206
  case "$kind" in
@@ -227,6 +267,30 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
227
267
  }
228
268
  return output
229
269
  }
270
+ function comment_text(text, cursor, char, escaped, quoted) {
271
+ escaped = 0
272
+ quoted = 0
273
+ for (cursor = 1; cursor <= length(text); cursor++) {
274
+ char = substr(text, cursor, 1)
275
+ if (quoted) {
276
+ if (escaped) escaped = 0
277
+ else if (char == "\\") escaped = 1
278
+ else if (char == "\"") quoted = 0
279
+ continue
280
+ }
281
+ if (char == "\"") {
282
+ quoted = 1
283
+ continue
284
+ }
285
+ if (char == "/" && substr(text, cursor + 1, 1) == "/")
286
+ return substr(text, cursor + 2)
287
+ }
288
+ return ""
289
+ }
290
+ function has_logging_reason(text, comment) {
291
+ comment = comment_text(text)
292
+ return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
293
+ }
230
294
  function check_emit() {
231
295
  if (emit_code !~ /event[[:space:]]*=/ && emit_code !~ /[(,][[:space:]]*event[[:space:]]*[,)]/)
232
296
  printf "event|%s|%d\n", FILENAME, emit_line
@@ -244,15 +308,21 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
244
308
  emit_parens = 0
245
309
  emit_code = ""
246
310
  emit_source = ""
311
+ previous_annotation = 0
247
312
  }
248
313
  {
314
+ current_annotation = has_logging_reason($0)
249
315
  code = code_only($0)
250
- if (!in_emit && code ~ /^[[:space:]]*$/) next
316
+ if (!in_emit && code ~ /^[[:space:]]*$/) {
317
+ previous_annotation = current_annotation
318
+ next
319
+ }
251
320
  opens = count_char(code, "\\{")
252
321
  closes = count_char(code, "\\}")
253
322
  if (test_depth > 0) {
254
323
  depth += opens - closes
255
324
  if (depth < test_depth) test_depth = 0
325
+ previous_annotation = current_annotation
256
326
  next
257
327
  }
258
328
  if (code ~ /^[[:space:]]*#\[cfg\(test\)\]/) {
@@ -261,6 +331,7 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
261
331
  depth += opens - closes
262
332
  if (depth < test_depth) test_depth = 0
263
333
  } else cfg_test = 1
334
+ previous_annotation = current_annotation
264
335
  next
265
336
  }
266
337
  if (cfg_test) {
@@ -270,9 +341,10 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
270
341
  if (depth < test_depth) test_depth = 0
271
342
  cfg_test = 0
272
343
  } else if (code ~ /;/) cfg_test = 0
344
+ previous_annotation = current_annotation
273
345
  next
274
346
  }
275
- if (code ~ /(^|[^[:alnum:]_])(println|eprintln|dbg)!/)
347
+ if (code ~ /(^|[^[:alnum:]_])(println|eprintln|dbg)!/ && !current_annotation && !previous_annotation)
276
348
  printf "direct|%s|%d\n", FILENAME, FNR
277
349
  if (!in_emit && match(code, /tracing::(error|warn|info|debug|trace)!/)) {
278
350
  in_emit = 1
@@ -288,8 +360,9 @@ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
288
360
  }
289
361
  if (in_emit && emit_parens <= 0) check_emit()
290
362
  depth += opens - closes
363
+ previous_annotation = current_annotation
291
364
  }
292
- ' "${rust_nontest[@]}")
365
+ ' "${rust_candidates[@]}")
293
366
  fi
294
367
 
295
368
  # ---------------------------------------------------------------------------
@@ -363,6 +436,7 @@ fi
363
436
  ok "scanned ${#FILES[@]} files; std.debug.print=$debug_print_hits rust-direct=$rust_direct_hits rust-missing-event=$rust_missing_event_hits rust-positional=$rust_positional_hits console.*=$console_hits std.log.scoped=$scoped_hits missing-error_code=$missing_code_hits"
364
437
  if [[ $FAIL -ne 0 ]]; then
365
438
  printf "\n🔴 LOGGING GATE: blocking violations. See dispatch/write_any.md (Logging Gate).\n" >&2
439
+ printf "Intentional direct Zig or Rust stream output requires '// logging: <reason>' on or immediately above the emit; the reason must be non-empty.\n" >&2
366
440
  exit 1
367
441
  fi
368
442
  [[ $INFO_COUNT -gt 0 ]] && note "$INFO_COUNT informational findings; not blocking. Use --strict to enforce."
@@ -127,7 +127,7 @@ shebang and is the authoritative check.
127
127
  **Triggers** — every `Edit`/`Write` that adds, removes, or changes a log emit:
128
128
 
129
129
  - `*.zig` outside `vendor/`/`third_party/`/`.zig-cache/`/`*_test.zig` — `std.log.*`, `std.debug.print`, raw stderr writes, calls into the `log` named module (source: `src/logging/mod.zig`).
130
- - `*.rs` under `rustd/` outside `tests/`, `benches/`, and `#[cfg(test)]` items — `tracing::*`, `println!`, `eprintln!`, and `dbg!`.
130
+ - Every tracked or unignored `*.rs` outside `build.rs`, `examples/`, `tests/`, `benches/`, and `#[cfg(test)]` items — `tracing::*`, `println!`, `eprintln!`, and `dbg!`.
131
131
  - `*.ts`/`*.tsx`/`*.js`/`*.jsx` outside `vendor/`/`node_modules/`/`*.test.*`/`*.spec.*` — `console.*`, custom logger calls.
132
132
  - `*.sh` outside generated dirs — `echo`/`printf` to `&2`.
133
133
 
@@ -150,7 +150,8 @@ shebang and is the authoritative check.
150
150
  | New `err`/`warn` log mapping to a domain failure | registry-scheme `error_code=` field required (agentsfleet: `UZ-XXX-NNN`). Registry entry must land in same commit. |
151
151
  | Per-iteration / hot-loop log | Use `debug` (hidden by default), not `info`. |
152
152
  | `info` level | No allow-list — `info` is open (§4 rule 2). Two checks instead: a boundary-crossing operation needs its `_started`/`_completed`\|`_failed` pair on every exit path (rule 1), and a per-iteration path is `debug` (rule 3). Hot-poll boundary pairs remain complete but use `debug`. |
153
- | `console.log`/`std.debug.print`/Rust print macro in non-test source | Forbidden. Convert to the structured logger or delete before commit. |
153
+ | `console.log` in non-test source | Forbidden. Convert to the structured logger or delete before commit. |
154
+ | `std.debug.print` or a Rust print macro in runtime source | Convert to structured logging. When the stream output is the program interface, add `// logging: <reason>` on or immediately above the emit. The reason is mandatory. |
154
155
  | `std.log.scoped` outside `src/logging/` | Migration target. The `log` named module's `scoped` API is the only non-test entry point; flip the file's alias and migrate every call site in the same commit. |
155
156
  | `msg=` field | ≤ 300 chars. Stack traces emit as separate `event=stack_trace` debug record. |
156
157
  | Multi-line values | Newlines must be `\n` literal (two chars), not raw newline byte. |
@@ -190,13 +191,13 @@ LOGGING GATE: <file>
190
191
 
191
192
  > [DETERMINISTIC → LOG]
192
193
 
193
- `logging.sh` walks the **full `src/` + `rustd/` + `agentsfleet/src/` working tree**. The staged mode reads the index, so a fix staged in pre-commit satisfies the check on the same hook run. `--staged` is preserved as an opt-in narrowing mode for iterative dev.
194
+ `logging.sh` walks `src/**/*.zig`, every tracked or unignored `*.rs`, and `agentsfleet/src/**/*.{js,jsx,ts,tsx}`. Rust repositories do not need to use a fixed source-root name. The staged mode reads the index, so a fix staged in pre-commit satisfies the check on the same hook run. `--staged` is preserved as an opt-in narrowing mode for iterative development.
194
195
 
195
196
  #### End-of-turn audit
196
197
 
197
198
  > [DETERMINISTIC → LOG]
198
199
 
199
- `audits/logging.sh` runs as part of `make lint`. It greps `std.log.*`, Rust `tracing` and direct-print macros, `console.*`, and raw stderr writes. It does NOT see calls through the Zig `log` named module's `scoped()`, so a clean run is not evidence that §4/§5 hold everywhere. Mechanical today: direct Zig/Rust/TypeScript diagnostics, Rust's required `event` field and positional-format ban, `std.log.scoped` outside `src/lib/logging/`, and legacy Zig error-code hints. Reviewer-side: severity choice, entry/exit pairing, registry applicability, inline Rust expression hoisting, and Personally Identifiable Information (PII) spot-checks.
200
+ `audits/logging.sh` runs as part of `make lint`. It greps `std.log.*`, Rust `tracing` and direct-print macros, `console.*`, and raw stderr writes. It accepts a direct Zig or Rust stream write only when `// logging: <reason>` appears on or immediately above the emit with a non-empty reason. It does NOT see calls through the Zig `log` named module's `scoped()`, so a clean run is not evidence that §4/§5 hold everywhere. Mechanical today: direct Zig/Rust/TypeScript diagnostics, Rust's required `event` field and positional-format ban, `std.log.scoped` outside `src/lib/logging/`, and legacy Zig error-code hints. Reviewer-side: reason validity, severity choice, entry/exit pairing, registry applicability, inline Rust expression hoisting, and Personally Identifiable Information (PII) spot-checks.
200
201
 
201
202
  #### Family
202
203
 
@@ -250,7 +250,7 @@ The `comptime if ... return` makes the entire emit path disappear when the scope
250
250
  | Pattern | Why banned | Fix |
251
251
  |---|---|---|
252
252
  | `std.log.info("..."` outside `logging.scoped(.tag)` | Bypasses scope/event/field discipline. | Convert to `logging.scoped(.tag).info("event", .{...})`. |
253
- | `std.debug.print(` in non-test source | No level, no scope, no field structure. Dev-only debugging that escaped to main. | Convert or delete before commit. |
253
+ | `std.debug.print(` in non-test source | No level, no scope, no field structure. Dev-only debugging that escaped to main. | Convert or delete. If stdout or stderr is the program interface, add `// logging: <reason>` on or immediately above the emit. |
254
254
  | `std.log.err` without `error_code=` field | Drops registry traceability. | Add the registry code, or add a registry entry if missing. |
255
255
  | `std.log.scoped(...)` outside `src/lib/logging/` | Free-form printf-style emit. | Switch the file's alias to `const log = logging.scoped(.tag);` and migrate every call site to the event+fields form. |
256
256
  | Positional `{s} {d}` placeholders | Not logfmt; not greppable. | Convert to struct-of-fields. |
@@ -295,7 +295,7 @@ class AgentError extends Error {
295
295
 
296
296
  Throw-style modules `throw new AgentError({...})`. Result-style modules return `{ ok: false, error: new AgentError({...}) }`. Render path picks human or JSON based on the runtime mode.
297
297
 
298
- ## §8A · Per-language binding — Rust (`rustd`)
298
+ ## §8A · Per-language binding — Rust
299
299
 
300
300
  > [DETERMINISTIC → LOG]
301
301
 
@@ -333,10 +333,20 @@ match. Renaming is a separate, intentional observability migration.
333
333
 
334
334
  | Pattern | Why banned | Fix |
335
335
  |---|---|---|
336
- | `println!`, `eprintln!`, or `dbg!` in non-test source | No level, event, or structured fields. | Convert to a `tracing` emit or delete. |
336
+ | `println!`, `eprintln!`, or `dbg!` in runtime source | No level, event, or structured fields. | Convert to a `tracing` emit or delete. If the stream is the program interface, add `// logging: <reason>` on or immediately above the emit. |
337
337
  | `tracing::<level>!` without `event = ...` | Drops the stable §3 event key. | Add the byte-stable snake_case event field. |
338
338
  | Positional formatting in a `tracing` message | Hides values inside text and defeats field queries. | Hoist values and emit named fields. |
339
339
 
340
+ The annotation reason is mandatory and must be non-empty. A bare `// logging:`
341
+ marker still fails. The annotation is for direct program output, such as a
342
+ command result or a fatal renderer that must work before tracing starts. It does
343
+ not excuse an incomplete `tracing` emit.
344
+
345
+ ```rust
346
+ // logging: stdout is this command's answer, not a log record
347
+ println!("{}", rendered_summary);
348
+ ```
349
+
340
350
  ## §9 · Pretty-printer (dev-only render variant)
341
351
 
342
352
  In production: every record on the wire is logfmt. No exceptions.
@@ -379,7 +389,7 @@ Failure modes the audit script and reviewer must close. These are **not aspirati
379
389
 
380
390
  | # | Rationalization | Closure |
381
391
  |---|---|---|
382
- | L1 | "Temporary debug print, I'll remove later" | `logging.sh` greps `std.debug.print`, Rust `println!` / `eprintln!` / `dbg!`, and `console.log` / `console.debug` / `console.info` in non-test source unconditionally. Found in commit → gate fails. No "temporary" carve-out. |
392
+ | L1 | "Temporary debug print, I'll remove later" | `logging.sh` rejects `std.debug.print`, Rust `println!` / `eprintln!` / `dbg!`, and `console.log` / `console.debug` / `console.info` in runtime source. Direct Zig or Rust stream output needs `// logging: <reason>` on or immediately above the emit. The reason must be non-empty and explain why the stream is the program interface. |
383
393
  | L2 | "`std.log.scoped` is fine, `obs.scoped` is just a wrapper" | `std.log.scoped` is **forbidden** in `src/**/*.zig` outside `src/lib/logging/`. Only `obs.scoped` is callable. Audit flags every `std.log.` call site. |
384
394
  | L3 | "I added `error_code=UZ-NEW-001` — registry entry coming next commit" | The registry entry **must land in the same commit** as the first reference. `error-codes.sh` runs against the staged diff; missing entry = blocking. |
385
395
  | L4 | "This per-iteration event matters for debugging — `info`-level" | Per-iteration and per-row paths are `debug`, including mandatory boundary pairs on hot polls. `debug` is the level with a volume switch. This closes the ONLY `info` prohibition — there is no allow-list of event names and none may be reintroduced (§4). The inverse rationalization is closed too: "this operation is obviously fine, no need to log it" does not survive §4 rule 1, which requires a `_started`/`_completed`\|`_failed` pair on every boundary-crossing operation. Reviewer checks the pair and the loop, never the spelling. |
@@ -6,7 +6,7 @@ regenerates this file and fails on any difference. To update it, run
6
6
 
7
7
  | Rule document | enforced by | judged | acknowledged | classified | trigger |
8
8
  |---|---|--:|--:|--:|---|
9
- | `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 11 | 13 | 28/28 | mechanical |
9
+ | `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 11 | 13 | 30/30 | mechanical |
10
10
  | `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/74 | mechanical |
11
11
  | `docs/SCHEMA_CONVENTIONS.md` | — | 0 | 0 | 0/16 | mechanical |
12
12
  | `docs/DOCUMENTATION_RULES.md` | — | 0 | 0 | 0/22 | latent |
@@ -14,7 +14,7 @@ regenerates this file and fails on any difference. To update it, run
14
14
  | `docs/CHANGELOG_VOICE.md` | — | 0 | 0 | 0/3 | latent |
15
15
  | `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/4 | uncited |
16
16
  | `docs/greptile-learnings/RULES.md` | — | 0 | 0 | 0/114 | mechanical |
17
- | **corpus** | | | | **28/283** | |
17
+ | **corpus** | | | | **30/285** | |
18
18
 
19
19
  ## How to read this
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsfleet/orly",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",