@agentsfleet/orly 0.10.13 → 0.11.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 (155) hide show
  1. package/.githooks/pre-push +2 -1
  2. package/AGENTS.md +21 -31
  3. package/README.md +65 -29
  4. package/SOUL.md +13 -23
  5. package/audits/agents-md.md +67 -57
  6. package/audits/deinit-pairs.sh +13 -18
  7. package/audits/design-tokens.sh +9 -23
  8. package/audits/doc-read.sh +10 -33
  9. package/audits/doc-reads.ts +41 -0
  10. package/audits/error-codes.sh +18 -25
  11. package/audits/logging-rust.awk +145 -0
  12. package/audits/logging.sh +10 -166
  13. package/audits/msid-ui.sh +45 -18
  14. package/audits/rule-ledger-lib.sh +9 -6
  15. package/audits/rust-error.sh +20 -15
  16. package/audits/scope.sh +88 -0
  17. package/audits/spec-template.sh +2 -2
  18. package/audits/spec-template.ts +1 -1
  19. package/audits/sql-mod.sh +11 -14
  20. package/audits/ufs-strings.awk +164 -0
  21. package/audits/ufs.sh +10 -188
  22. package/core/operating-model.md +8 -8
  23. package/dispatch/edit_rules.md +8 -6
  24. package/dispatch/lib.sh +41 -26
  25. package/dispatch/lifecycle.md +18 -6
  26. package/dispatch/name_architecture.md +8 -3
  27. package/dispatch/verify.md +1 -1
  28. package/dispatch/write_any.md +10 -3
  29. package/dispatch/write_changelog.md +7 -4
  30. package/dispatch/write_go.md +1 -1
  31. package/dispatch/write_http.md +5 -7
  32. package/dispatch/write_python.md +1 -1
  33. package/dispatch/write_rust.md +17 -1
  34. package/dispatch/write_shell.md +1 -1
  35. package/dispatch/write_spec.md +3 -1
  36. package/dispatch/write_sql.md +28 -6
  37. package/dispatch/write_ts_adhere_bun.md +11 -0
  38. package/dispatch/write_zig.md +53 -21
  39. package/docs/CHANGELOG_VOICE.md +11 -2
  40. package/docs/EXECUTE_DOC_READS.md +10 -10
  41. package/docs/HARNESS_VERIFY_OUTPUT.md +6 -3
  42. package/docs/JUDGMENTS.md +153 -0
  43. package/docs/LIFECYCLE_PATTERNS.md +26 -16
  44. package/docs/LOGGING_STANDARD.md +39 -19
  45. package/docs/ORLY_ARCHITECTURE.md +77 -6
  46. package/docs/RELEASE_TEMPLATE.md +21 -5
  47. package/docs/REST_API_DESIGN_GUIDELINES.md +86 -21
  48. package/docs/RULE_ENFORCEMENT.md +6 -6
  49. package/docs/SCHEMA_CONVENTIONS.md +24 -3
  50. package/docs/TEMPLATE.md +4 -4
  51. package/docs/VERIFY_TIERS.md +2 -2
  52. package/docs/greptile-learnings/RULES.md +81 -26
  53. package/llms.txt +37 -34
  54. package/package.json +6 -1
  55. package/packs/language/go/rules.md +1 -1
  56. package/packs/language/python/rules.md +1 -1
  57. package/packs/language/rust/rules.md +17 -1
  58. package/packs/language/shell/rules.md +1 -1
  59. package/registry.json +22 -7
  60. package/schemas/profile.schema.json +48 -5
  61. package/skills/orly-babysit-prs/SKILL.md +87 -34
  62. package/skills/orly-spec-new/SKILL.md +19 -6
  63. package/skills/orly-write-integration-test/SKILL.md +4 -4
  64. package/skills/orly-write-unit-test/SKILL.md +10 -1
  65. package/src/cli.test.ts +2 -2
  66. package/src/cli.ts +19 -75
  67. package/src/cli_help.ts +69 -0
  68. package/src/command_limits.ts +20 -0
  69. package/src/command_process.test.ts +109 -0
  70. package/src/command_process.ts +88 -0
  71. package/src/command_runner.test.ts +88 -0
  72. package/src/command_runner.ts +33 -0
  73. package/src/comprehension_cache.test.ts +123 -0
  74. package/src/config.test.ts +5 -4
  75. package/src/config.ts +26 -164
  76. package/src/config_discovery.ts +159 -0
  77. package/src/config_validation.test.ts +56 -0
  78. package/src/config_validation.ts +25 -0
  79. package/src/criteria.test.ts +1 -1
  80. package/src/criteria.ts +10 -26
  81. package/src/criteria_spec.ts +12 -20
  82. package/src/criteria_support.ts +2 -19
  83. package/src/doc_reads.test.ts +42 -0
  84. package/src/doc_rules.ts +2 -2
  85. package/src/evaluation_resources.test.ts +64 -0
  86. package/src/execution/config.ts +22 -0
  87. package/src/execution/plan.test.ts +65 -0
  88. package/src/execution/plan.ts +51 -0
  89. package/src/gates.ts +39 -24
  90. package/src/gates_spec.test.ts +110 -4
  91. package/src/gates_test_support.ts +9 -7
  92. package/src/install.test.ts +38 -222
  93. package/src/install.ts +117 -308
  94. package/src/install_loaders.test.ts +203 -0
  95. package/src/install_nested.test.ts +2 -2
  96. package/src/install_packs.test.ts +66 -6
  97. package/src/installation/hooks.ts +63 -0
  98. package/src/installation/lock.test.ts +97 -0
  99. package/src/installation/lock.ts +56 -0
  100. package/src/installation/migration.test.ts +212 -0
  101. package/src/installation/migration.ts +65 -0
  102. package/src/installation/plan.ts +96 -0
  103. package/src/installation/transaction.ts +126 -0
  104. package/src/installation_preservation.test.ts +48 -0
  105. package/src/judgments/advice.test.ts +38 -0
  106. package/src/judgments/advice.ts +32 -0
  107. package/src/judgments/command.integration.test.ts +47 -0
  108. package/src/judgments/command.ts +90 -0
  109. package/src/judgments/constants.ts +55 -0
  110. package/src/judgments/evidence.test.ts +48 -0
  111. package/src/judgments/evidence.ts +34 -0
  112. package/src/judgments/files.test.ts +62 -0
  113. package/src/judgments/files.ts +73 -0
  114. package/src/judgments/questions.test.ts +19 -0
  115. package/src/judgments/questions.ts +85 -0
  116. package/src/judgments/replay.test.ts +43 -0
  117. package/src/judgments/replay.ts +66 -0
  118. package/src/judgments/runner.integration.test.ts +165 -0
  119. package/src/judgments/runner.ts +97 -0
  120. package/src/judgments/scan.test.ts +19 -0
  121. package/src/judgments/scan.ts +40 -0
  122. package/src/judgments/syntax.test.ts +115 -0
  123. package/src/judgments/syntax.ts +71 -0
  124. package/src/judgments/syntax_worker.ts +70 -0
  125. package/src/judgments/test_support.ts +60 -0
  126. package/src/judgments/transport.integration.test.ts +63 -0
  127. package/src/judgments/transport.ts +35 -0
  128. package/src/judgments/types.test.ts +43 -0
  129. package/src/judgments/types.ts +66 -0
  130. package/src/judgments/wire.test.ts +48 -0
  131. package/src/judgments/wire.ts +40 -0
  132. package/src/loaders.test.ts +13 -16
  133. package/src/loaders.ts +28 -10
  134. package/src/model.test.ts +20 -3
  135. package/src/model.ts +7 -18
  136. package/src/pack_hygiene.test.ts +42 -1
  137. package/src/pack_hygiene.ts +18 -8
  138. package/src/references.ts +15 -7
  139. package/src/release_inventory.test.ts +80 -0
  140. package/src/render.test.ts +1 -1
  141. package/src/render.ts +3 -8
  142. package/src/required_scanner.test.ts +36 -0
  143. package/src/review_poll.test.ts +158 -0
  144. package/src/scanner_scope.test.ts +202 -0
  145. package/src/setup.test.ts +9 -6
  146. package/src/spec_baseline.test.ts +1 -1
  147. package/src/spec_completion.test.ts +74 -0
  148. package/src/spec_obligations.ts +64 -0
  149. package/src/spec_rehearsal.test.ts +182 -0
  150. package/src/spec_template_io.test.ts +11 -9
  151. package/src/surfaces.test.ts +2 -0
  152. package/src/surfaces.ts +1 -1
  153. package/src/validation.ts +7 -4
  154. package/src/verify.test.ts +18 -2
  155. package/src/verify.ts +7 -3
@@ -0,0 +1,41 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
+
4
+ type ReadRow = { ts: number; path: string; blob: string; section: string };
5
+ const DIGEST = /^[a-f0-9]+$/;
6
+ const STRING_TYPE = "string";
7
+
8
+ function escaped(value: string): string { return JSON.stringify(value).slice(1, -1); }
9
+
10
+ function readRow(value: unknown): ReadRow | undefined {
11
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
12
+ const row = value as Partial<ReadRow>;
13
+ if (!Number.isSafeInteger(row.ts) || row.ts! < 0 || typeof row.path !== STRING_TYPE || typeof row.blob !== STRING_TYPE || typeof row.section !== STRING_TYPE) return undefined;
14
+ if (!DIGEST.test(row.blob) || row.path.includes("\0")) return undefined;
15
+ return row as ReadRow;
16
+ }
17
+
18
+ function current(log: string, root: string): void {
19
+ for (const line of readFileSync(log, "utf8").split("\n")) {
20
+ let row;
21
+ try { row = readRow(JSON.parse(line)); } catch { continue; }
22
+ if (!row) continue;
23
+ const file = resolve(root, row.path);
24
+ const within = relative(root, file);
25
+ if (isAbsolute(within) || within === ".." || within.startsWith("../")) continue;
26
+ const hash = Bun.spawnSync(["git", "-C", root, "hash-object", file], { stdout: "pipe", stderr: "ignore" });
27
+ if (hash.exitCode === 0 && hash.stdout.toString().trim() === row.blob) {
28
+ process.stdout.write(`${escaped(row.path)}\t${row.ts}\t${escaped(row.section)}\n`);
29
+ }
30
+ }
31
+ }
32
+
33
+ const [mode, ...args] = Bun.argv.slice(2);
34
+ if (mode === "escape") process.stdout.write(escaped(args[0] ?? ""));
35
+ else if (mode === "current") current(args[0]!, args[1]!);
36
+ else if (mode === "log") {
37
+ const [ts, path, blob, section] = args;
38
+ const row = readRow({ ts: Number(ts), path, blob, section });
39
+ if (!row) throw new Error("invalid document read record");
40
+ process.stdout.write(`${JSON.stringify(row)}\n`);
41
+ } else throw new Error("expected log, current or escape");
@@ -49,7 +49,11 @@
49
49
 
50
50
  set -euo pipefail
51
51
 
52
- MODE="${1:-${SCOPE:-all}}"
52
+ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
53
+ audit_scope_init --all "$@"
54
+ audit_index_snapshot "$@"
55
+
56
+ MODE="$AUDIT_MODE"
53
57
  ROOT="$(git rev-parse --show-toplevel)"
54
58
  cd "$ROOT"
55
59
 
@@ -62,8 +66,8 @@ CODE_RE="${PREFIX}-[A-Z][A-Z0-9]*-[0-9]{3,}"
62
66
  REGISTRY_PATHS=()
63
67
  RUNTIME=""
64
68
  if [[ -n "${ORLY_ERROR_REGISTRY:-}" ]]; then
65
- while IFS= read -r f; do [[ -n "$f" ]] && REGISTRY_PATHS+=("$f"); done < <(
66
- git ls-files -- $ORLY_ERROR_REGISTRY 2>/dev/null || true)
69
+ while IFS= read -r -d '' f; do [[ -n "$f" ]] && REGISTRY_PATHS+=("$f"); done < <(
70
+ git ls-files -z -- "$ORLY_ERROR_REGISTRY")
67
71
  [[ ${#REGISTRY_PATHS[@]} -gt 0 ]] || {
68
72
  printf "FAIL: ORLY_ERROR_REGISTRY matched no tracked file: %s\n" "$ORLY_ERROR_REGISTRY" >&2
69
73
  exit 1
@@ -127,27 +131,14 @@ declared_count=$(printf '%s\n' "$declared_codes" | wc -l | tr -d ' ')
127
131
  # Source set: src/**/*.zig minus *_test.zig minus the registry itself,
128
132
  # plus agentsfleet/src/**.
129
133
  # ---------------------------------------------------------------------------
130
- gather_used_paths() {
131
- case "$MODE" in
132
- --staged|staged)
133
- git diff --cached --name-only --diff-filter=ACMRT -- $SOURCE_GLOB 2>/dev/null \
134
- | grep -vE "^(${REGISTRY_RE})$" || true
135
- ;;
136
- --all|all)
137
- git ls-files -- $SOURCE_GLOB 2>/dev/null \
138
- | grep -vE "^(${REGISTRY_RE})$" || true
139
- ;;
140
- *)
141
- printf "usage: %s [--staged|--all]\n" "$0" >&2
142
- exit 64
143
- ;;
144
- esac
145
- }
146
-
147
- # `while read` rather than mapfile: mapfile is bash 4+ and macOS ships 3.2 —
148
- # the portability rule scripts/run-playbook-tests.sh already records.
149
134
  USED_PATHS=()
150
- while IFS= read -r p; do USED_PATHS+=("$p"); done < <(gather_used_paths)
135
+ RAW_PATHS=()
136
+ while IFS= read -r -d '' p; do
137
+ [ -f "$p" ] || continue
138
+ if printf '%s\n' "$p" | grep -qE "^(${REGISTRY_RE})$"; then continue; fi
139
+ USED_PATHS+=("$p")
140
+ if ! printf '%s\n' "$p" | grep -qE "$ALLOWLIST_RE"; then RAW_PATHS+=("$p"); fi
141
+ done < <(audit_scope_paths "$SOURCE_GLOB")
151
142
  if [[ ${#USED_PATHS[@]} -eq 0 ]]; then
152
143
  ok "no source files in scope ($MODE)"
153
144
  exit 0
@@ -246,6 +237,8 @@ fi
246
237
  # 2-segment pattern. We don't want a "UZ-X-Y-NNN" code reintroduced
247
238
  # via a local const that the orphan pass silently ignores.
248
239
  # ---------------------------------------------------------------------------
240
+ raw_leaks=""
241
+ if [ "${#RAW_PATHS[@]}" -gt 0 ]; then
249
242
  raw_leaks=$(awk -v prefix="$PREFIX" '
250
243
  FNR == 1 { skip_next = 0; in_rs_test = 0; rs_open = 0; rs_depth = 0 }
251
244
  # Rust keeps its unit tests INSIDE the file they cover, under
@@ -275,8 +268,8 @@ raw_leaks=$(awk -v prefix="$PREFIX" '
275
268
  $0 ~ "\"" prefix "-[A-Z][A-Z0-9-]*-[0-9]+\"" {
276
269
  print FILENAME ":" FNR ":" $0;
277
270
  }
278
- ' $(printf '%s\n' "${USED_PATHS[@]}" | grep -vE \
279
- "$ALLOWLIST_RE" || true) 2>/dev/null || true)
271
+ ' "${RAW_PATHS[@]}" 2>/dev/null || true)
272
+ fi
280
273
 
281
274
  if [[ -n "$raw_leaks" ]]; then
282
275
  fail "raw UZ-* literals found outside the registry allowlist (must reference a registry symbol):"
@@ -0,0 +1,145 @@
1
+ # Rust tracing and diagnostic detection; the shell owns scope and findings.
2
+ function count_char(text, char, copy, count) {
3
+ copy = text
4
+ count = gsub(char, "", copy)
5
+ return count
6
+ }
7
+ function code_only(text, output, cursor, char, escaped, quoted) {
8
+ output = ""
9
+ escaped = 0
10
+ quoted = 0
11
+ for (cursor = 1; cursor <= length(text); cursor++) {
12
+ char = substr(text, cursor, 1)
13
+ if (quoted) {
14
+ if (escaped) escaped = 0
15
+ else if (char == "\\") escaped = 1
16
+ else if (char == "\"") quoted = 0
17
+ continue
18
+ }
19
+ if (char == "\"") {
20
+ quoted = 1
21
+ continue
22
+ }
23
+ if (char == "/" && substr(text, cursor + 1, 1) == "/") break
24
+ output = output char
25
+ }
26
+ return output
27
+ }
28
+ function without_comment(text, output, cursor, char, escaped, quoted) {
29
+ output = ""
30
+ escaped = 0
31
+ quoted = 0
32
+ for (cursor = 1; cursor <= length(text); cursor++) {
33
+ char = substr(text, cursor, 1)
34
+ if (quoted) {
35
+ output = output char
36
+ if (escaped) escaped = 0
37
+ else if (char == "\\") escaped = 1
38
+ else if (char == "\"") quoted = 0
39
+ continue
40
+ }
41
+ if (char == "\"") {
42
+ quoted = 1
43
+ output = output char
44
+ continue
45
+ }
46
+ if (char == "/" && substr(text, cursor + 1, 1) == "/") break
47
+ output = output char
48
+ }
49
+ return output
50
+ }
51
+ function comment_text(text, cursor, char, escaped, quoted) {
52
+ escaped = 0
53
+ quoted = 0
54
+ for (cursor = 1; cursor <= length(text); cursor++) {
55
+ char = substr(text, cursor, 1)
56
+ if (quoted) {
57
+ if (escaped) escaped = 0
58
+ else if (char == "\\") escaped = 1
59
+ else if (char == "\"") quoted = 0
60
+ continue
61
+ }
62
+ if (char == "\"") {
63
+ quoted = 1
64
+ continue
65
+ }
66
+ if (char == "/" && substr(text, cursor + 1, 1) == "/")
67
+ return substr(text, cursor + 2)
68
+ }
69
+ return ""
70
+ }
71
+ function has_logging_reason(text, comment) {
72
+ comment = comment_text(text)
73
+ return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
74
+ }
75
+ function check_emit() {
76
+ if (emit_code !~ /event[[:space:]]*=/ && emit_code !~ /[(,][[:space:]]*event[[:space:]]*[,)]/)
77
+ printf "event|%s|%d\n", FILENAME, emit_line
78
+ if (emit_source ~ /"[^"\n]*\{[^"\n]*\}[^"\n]*"/)
79
+ printf "positional|%s|%d\n", FILENAME, emit_line
80
+ emit_code = ""
81
+ emit_source = ""
82
+ in_emit = 0
83
+ }
84
+ FNR == 1 {
85
+ depth = 0
86
+ test_depth = 0
87
+ cfg_test = 0
88
+ in_emit = 0
89
+ emit_parens = 0
90
+ emit_code = ""
91
+ emit_source = ""
92
+ previous_annotation = 0
93
+ }
94
+ {
95
+ current_annotation = has_logging_reason($0)
96
+ code = code_only($0)
97
+ if (!in_emit && code ~ /^[[:space:]]*$/) {
98
+ previous_annotation = current_annotation
99
+ next
100
+ }
101
+ opens = count_char(code, "\\{")
102
+ closes = count_char(code, "\\}")
103
+ if (test_depth > 0) {
104
+ depth += opens - closes
105
+ if (depth < test_depth) test_depth = 0
106
+ previous_annotation = current_annotation
107
+ next
108
+ }
109
+ if (code ~ /^[[:space:]]*#\[cfg\(test\)\]/) {
110
+ if (opens > 0) {
111
+ test_depth = depth + 1
112
+ depth += opens - closes
113
+ if (depth < test_depth) test_depth = 0
114
+ } else cfg_test = 1
115
+ previous_annotation = current_annotation
116
+ next
117
+ }
118
+ if (cfg_test) {
119
+ if (opens > 0) {
120
+ test_depth = depth + 1
121
+ depth += opens - closes
122
+ if (depth < test_depth) test_depth = 0
123
+ cfg_test = 0
124
+ } else if (code ~ /;/) cfg_test = 0
125
+ previous_annotation = current_annotation
126
+ next
127
+ }
128
+ if (code ~ /(^|[^[:alnum:]_])(println|eprintln|dbg)!/ && !current_annotation && !previous_annotation)
129
+ printf "direct|%s|%d\n", FILENAME, FNR
130
+ if (!in_emit && match(code, /tracing::(error|warn|info|debug|trace)!/)) {
131
+ in_emit = 1
132
+ emit_line = FNR
133
+ emit_code = code
134
+ emit_source = without_comment($0)
135
+ macro = substr(code, RSTART)
136
+ emit_parens = count_char(macro, "\\(") - count_char(macro, "\\)")
137
+ } else if (in_emit) {
138
+ emit_code = emit_code " " code
139
+ emit_source = emit_source " " without_comment($0)
140
+ emit_parens += count_char(code, "\\(") - count_char(code, "\\)")
141
+ }
142
+ if (in_emit && emit_parens <= 0) check_emit()
143
+ depth += opens - closes
144
+ previous_annotation = current_annotation
145
+ }
package/audits/logging.sh CHANGED
@@ -41,6 +41,10 @@
41
41
 
42
42
  set -euo pipefail
43
43
 
44
+ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
45
+ audit_scope_init --all "$@"
46
+ audit_index_snapshot "$@"
47
+
44
48
  MODE="--all"
45
49
  STRICT=0
46
50
  for arg in "$@"; do
@@ -52,7 +56,8 @@ for arg in "$@"; do
52
56
  printf "usage: %s [--staged|--all] [--strict]\n" "$0"
53
57
  exit 0
54
58
  ;;
55
- *) printf "unknown arg: %s\n" "$arg" >&2; exit 64 ;;
59
+ -*) printf "unknown arg: %s\n" "$arg" >&2; exit 2 ;;
60
+ *) MODE=explicit ;;
56
61
  esac
57
62
  done
58
63
 
@@ -94,26 +99,10 @@ is_non_runtime_rust_path() {
94
99
  # ---------------------------------------------------------------------------
95
100
  # 1. Gather files in scope.
96
101
  # ---------------------------------------------------------------------------
97
- gather_paths() {
98
- case "$MODE" in
99
- --staged)
100
- git diff --cached --name-only --diff-filter=ACMRT \
101
- | grep -E '(^src/.*\.zig$|\.rs$|^agentsfleet/src/.*\.(js|jsx|ts|tsx)$)' || true
102
- ;;
103
- --all)
104
- find src -type f -name '*.zig' 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')
108
- find agentsfleet/src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) 2>/dev/null || true
109
- ;;
110
- esac
111
- }
112
-
113
- # `while read` rather than mapfile: mapfile is bash 4+ and macOS ships 3.2 —
114
- # the portability rule scripts/run-playbook-tests.sh already records.
115
102
  FILES=()
116
- while IFS= read -r p; do FILES+=("$p"); done < <(gather_paths)
103
+ while IFS= read -r -d '' p; do
104
+ [ ! -f "$p" ] || FILES+=("$p")
105
+ done < <(audit_scope_paths '*.zig' '*.rs' 'agentsfleet/src/*.js' 'agentsfleet/src/*.jsx' 'agentsfleet/src/*.ts' 'agentsfleet/src/*.tsx')
117
106
  if [[ ${#FILES[@]} -eq 0 ]]; then
118
107
  ok "no source files in scope ($MODE)"
119
108
  exit 0
@@ -222,152 +211,7 @@ if [[ ${#rust_candidates[@]} -gt 0 ]]; then
222
211
  rust_positional_hits=$((rust_positional_hits + 1))
223
212
  ;;
224
213
  esac
225
- done < <(awk '
226
- function count_char(text, char, copy, count) {
227
- copy = text
228
- count = gsub(char, "", copy)
229
- return count
230
- }
231
- function code_only(text, output, cursor, char, escaped, quoted) {
232
- output = ""
233
- escaped = 0
234
- quoted = 0
235
- for (cursor = 1; cursor <= length(text); cursor++) {
236
- char = substr(text, cursor, 1)
237
- if (quoted) {
238
- if (escaped) escaped = 0
239
- else if (char == "\\") escaped = 1
240
- else if (char == "\"") quoted = 0
241
- continue
242
- }
243
- if (char == "\"") {
244
- quoted = 1
245
- continue
246
- }
247
- if (char == "/" && substr(text, cursor + 1, 1) == "/") break
248
- output = output char
249
- }
250
- return output
251
- }
252
- function without_comment(text, output, cursor, char, escaped, quoted) {
253
- output = ""
254
- escaped = 0
255
- quoted = 0
256
- for (cursor = 1; cursor <= length(text); cursor++) {
257
- char = substr(text, cursor, 1)
258
- if (quoted) {
259
- output = output char
260
- if (escaped) escaped = 0
261
- else if (char == "\\") escaped = 1
262
- else if (char == "\"") quoted = 0
263
- continue
264
- }
265
- if (char == "\"") {
266
- quoted = 1
267
- output = output char
268
- continue
269
- }
270
- if (char == "/" && substr(text, cursor + 1, 1) == "/") break
271
- output = output char
272
- }
273
- return output
274
- }
275
- function comment_text(text, cursor, char, escaped, quoted) {
276
- escaped = 0
277
- quoted = 0
278
- for (cursor = 1; cursor <= length(text); cursor++) {
279
- char = substr(text, cursor, 1)
280
- if (quoted) {
281
- if (escaped) escaped = 0
282
- else if (char == "\\") escaped = 1
283
- else if (char == "\"") quoted = 0
284
- continue
285
- }
286
- if (char == "\"") {
287
- quoted = 1
288
- continue
289
- }
290
- if (char == "/" && substr(text, cursor + 1, 1) == "/")
291
- return substr(text, cursor + 2)
292
- }
293
- return ""
294
- }
295
- function has_logging_reason(text, comment) {
296
- comment = comment_text(text)
297
- return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
298
- }
299
- function check_emit() {
300
- if (emit_code !~ /event[[:space:]]*=/ && emit_code !~ /[(,][[:space:]]*event[[:space:]]*[,)]/)
301
- printf "event|%s|%d\n", FILENAME, emit_line
302
- if (emit_source ~ /"[^"\n]*\{[^"\n]*\}[^"\n]*"/)
303
- printf "positional|%s|%d\n", FILENAME, emit_line
304
- emit_code = ""
305
- emit_source = ""
306
- in_emit = 0
307
- }
308
- FNR == 1 {
309
- depth = 0
310
- test_depth = 0
311
- cfg_test = 0
312
- in_emit = 0
313
- emit_parens = 0
314
- emit_code = ""
315
- emit_source = ""
316
- previous_annotation = 0
317
- }
318
- {
319
- current_annotation = has_logging_reason($0)
320
- code = code_only($0)
321
- if (!in_emit && code ~ /^[[:space:]]*$/) {
322
- previous_annotation = current_annotation
323
- next
324
- }
325
- opens = count_char(code, "\\{")
326
- closes = count_char(code, "\\}")
327
- if (test_depth > 0) {
328
- depth += opens - closes
329
- if (depth < test_depth) test_depth = 0
330
- previous_annotation = current_annotation
331
- next
332
- }
333
- if (code ~ /^[[:space:]]*#\[cfg\(test\)\]/) {
334
- if (opens > 0) {
335
- test_depth = depth + 1
336
- depth += opens - closes
337
- if (depth < test_depth) test_depth = 0
338
- } else cfg_test = 1
339
- previous_annotation = current_annotation
340
- next
341
- }
342
- if (cfg_test) {
343
- if (opens > 0) {
344
- test_depth = depth + 1
345
- depth += opens - closes
346
- if (depth < test_depth) test_depth = 0
347
- cfg_test = 0
348
- } else if (code ~ /;/) cfg_test = 0
349
- previous_annotation = current_annotation
350
- next
351
- }
352
- if (code ~ /(^|[^[:alnum:]_])(println|eprintln|dbg)!/ && !current_annotation && !previous_annotation)
353
- printf "direct|%s|%d\n", FILENAME, FNR
354
- if (!in_emit && match(code, /tracing::(error|warn|info|debug|trace)!/)) {
355
- in_emit = 1
356
- emit_line = FNR
357
- emit_code = code
358
- emit_source = without_comment($0)
359
- macro = substr(code, RSTART)
360
- emit_parens = count_char(macro, "\\(") - count_char(macro, "\\)")
361
- } else if (in_emit) {
362
- emit_code = emit_code " " code
363
- emit_source = emit_source " " without_comment($0)
364
- emit_parens += count_char(code, "\\(") - count_char(code, "\\)")
365
- }
366
- if (in_emit && emit_parens <= 0) check_emit()
367
- depth += opens - closes
368
- previous_annotation = current_annotation
369
- }
370
- ' "${rust_candidates[@]}")
214
+ done < <(awk -f "$AUDIT_HOME/logging-rust.awk" "${rust_candidates[@]}")
371
215
  fi
372
216
 
373
217
  # ---------------------------------------------------------------------------
package/audits/msid-ui.sh CHANGED
@@ -72,27 +72,31 @@ MODE="${1:---staged}"
72
72
  ROOT="$(git rev-parse --show-toplevel)"
73
73
  cd "$ROOT"
74
74
 
75
+ EXPLICIT=()
75
76
  case "$MODE" in
76
77
  --staged|staged)
77
- DIFF_CMD="git diff --cached -U0"
78
+ [ "$#" -le 1 ] || { printf 'choose a mode or explicit files\n' >&2; exit 2; }
79
+ DIFF_ARGS=(--cached)
78
80
  POST_REF=":"
79
81
  LABEL="staged"
80
82
  ;;
81
83
  --diff|diff)
84
+ [ "$#" -le 1 ] || { printf 'choose a mode or explicit files\n' >&2; exit 2; }
82
85
  BASE="${BASE:-origin/main}"
83
- if ! git rev-parse --verify "$BASE" >/dev/null 2>&1; then BASE="HEAD"; fi
84
- DIFF_CMD="git diff -U0 ${BASE}...HEAD"
86
+ if ! git rev-parse --verify "${BASE}^{commit}" >/dev/null 2>&1; then
87
+ printf 'FAIL: comparison revision is missing: %s\n' "$BASE" >&2; exit 2
88
+ fi
89
+ DIFF_ARGS=("${BASE}...HEAD")
85
90
  POST_REF="HEAD:"
86
91
  LABEL="vs $BASE"
87
92
  ;;
88
- --all|all)
89
- # No "all" mode — this audit is diff-shaped by construction (it
90
- # asserts on *added* lines, not file state). Force callers to pick.
91
- echo "usage: $0 [--staged|--diff]" >&2
92
- exit 2
93
- ;;
93
+ -h|--help) printf 'usage: %s [--staged|--diff|file ...]\n' "$0"; exit 0 ;;
94
+ -*) printf 'usage: %s [--staged|--diff|file ...]\n' "$0" >&2; exit 2 ;;
94
95
  *)
95
- echo "usage: $0 [--staged|--diff]" >&2; exit 2 ;;
96
+ for path in "$@"; do
97
+ case "$path" in -*) printf 'unknown audit option: %s\n' "$path" >&2; exit 2 ;; esac
98
+ done
99
+ EXPLICIT=("$@"); POST_REF=""; LABEL="explicit files" ;;
96
100
  esac
97
101
 
98
102
  # Single awk pass; reads stdin (the unified diff).
@@ -111,10 +115,11 @@ esac
111
115
  # The hunk header carries the post-image line number, so `line_above` can fetch
112
116
  # the neighbouring line out of the post-image the mode named. Same shape as
113
117
  # audits/design-tokens.sh, which has always read its override out of the file.
114
- hits=$($DIFF_CMD | awk -v post_ref="$POST_REF" '
118
+ scan_diff() {
119
+ ORLY_AUDIT_PATH="$1" awk -v post_ref="$POST_REF" '
115
120
  # BWK awk (macOS) has no gensub and no --posix quoting help; APOSTROPHE is
116
121
  # built rather than written, because this program is inside single quotes.
117
- BEGIN { APOSTROPHE = sprintf("%c", 39) }
122
+ BEGIN { APOSTROPHE = sprintf("%c", 39); f = ENVIRON["ORLY_AUDIT_PATH"] }
118
123
 
119
124
  # The source line above line `n` of `path`, from the post-image, or "" when
120
125
  # there is none. Cached: a hunk may ask about the same neighbour repeatedly,
@@ -124,16 +129,23 @@ hits=$($DIFF_CMD | awk -v post_ref="$POST_REF" '
124
129
  key = path SUBSEP n
125
130
  if (key in above) return above[key]
126
131
  above[key] = ""
127
- # A path holding an apostrophe cannot go through the quoting below. No
128
- # carve-out beats a mis-quoted shell command built from a filename.
129
- if (index(path, APOSTROPHE) > 0) return ""
130
- cmd = "git show " APOSTROPHE post_ref path APOSTROPHE " 2>/dev/null | sed -n " (n - 1) "p"
132
+ if (post_ref == "") cmd = "sed -n " (n - 1) "p " shell_quote(path)
133
+ else cmd = "git show " shell_quote(post_ref path) " 2>/dev/null | sed -n " (n - 1) "p"
131
134
  if ((cmd | getline out) > 0) above[key] = out
132
135
  close(cmd)
133
136
  return above[key]
134
137
  }
135
138
 
136
- /^\+\+\+ b\// { f=$2; sub("^b/","",f); next }
139
+ function shell_quote(s, i,c,out) {
140
+ out = APOSTROPHE
141
+ for (i = 1; i <= length(s); i++) {
142
+ c = substr(s, i, 1)
143
+ if (c == APOSTROPHE) out = out APOSTROPHE "\\" APOSTROPHE APOSTROPHE
144
+ else out = out c
145
+ }
146
+ return out APOSTROPHE
147
+ }
148
+ /^\+\+\+/ { next }
137
149
  # `@@ -a,b +c,d @@` — $3 is the post-image start line for this hunk.
138
150
  /^@@/ { start=$3; sub(/^\+/,"",start); split(start,parts,","); nl=parts[1]+0; next }
139
151
  /^[^+]/ { next }
@@ -173,7 +185,22 @@ hits=$($DIFF_CMD | awk -v post_ref="$POST_REF" '
173
185
  print "UI " f ": " line
174
186
  }
175
187
  }
176
- ')
188
+ '
189
+ }
190
+
191
+ hits=$(
192
+ if [ "${#EXPLICIT[@]}" -gt 0 ]; then
193
+ for path in "${EXPLICIT[@]}"; do
194
+ [ -f "$path" ] || continue
195
+ # diff returns one for differences; that is expected input here.
196
+ { git diff --no-index --no-ext-diff -U0 -- /dev/null "$path" || [ "$?" -eq 1 ]; } | scan_diff "$path"
197
+ done
198
+ else
199
+ while IFS= read -r -d '' path; do
200
+ git diff --no-ext-diff -U0 "${DIFF_ARGS[@]}" -- "$path" | scan_diff "$path"
201
+ done < <(git diff --name-only -z --diff-filter=ACMRT "${DIFF_ARGS[@]}")
202
+ fi
203
+ )
177
204
 
178
205
  if [ -n "$hits" ]; then
179
206
  echo "FAIL: audit-msid-ui ($LABEL) — MS-ID / UI hits below"
@@ -63,6 +63,9 @@ JUDGMENT_TAG='\[JUDGMENT → [A-Za-z0-9_:-]+\]'
63
63
  UNENFORCED_TAG='\[UNENFORCED → [^]]+\]'
64
64
 
65
65
  LEDGER_ROOT="${ORLY_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
66
+ LEDGER_SOURCE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
67
+ LEDGER_RULES_ROOT="$LEDGER_ROOT"
68
+ case "$LEDGER_SOURCE_ROOT" in */.orly) LEDGER_RULES_ROOT="$LEDGER_SOURCE_ROOT" ;; esac
66
69
 
67
70
  # A tag alone on its line (the `> [DETERMINISTIC → FLL]` form dispatch/*.md
68
71
  # already uses) covers every clause under it until the next heading. A tag at
@@ -139,7 +142,7 @@ ledger_doc_codes() {
139
142
  # code that loses its helper stops claiming enforcement here the same commit.
140
143
  ledger_code_script() {
141
144
  local code="$1" script
142
- script="$(grep -hoE "^dispatch_run_helper +\"$code\" +\"[^\"]+\"" "$LEDGER_ROOT"/dispatch/*.sh 2>/dev/null \
145
+ script="$(grep -hoE "^dispatch_run_helper +\"$code\" +\"[^\"]+\"" "$LEDGER_RULES_ROOT"/dispatch/*.sh 2>/dev/null \
143
146
  | sed -E 's/.*"([^"]+)"$/\1/' | head -1)"
144
147
  printf '%s' "${script:-$UNWIRED_CODE_LABEL}"
145
148
  }
@@ -153,7 +156,7 @@ UNWIRED_CODE_LABEL="no helper row"
153
156
  # form and the ~/Projects/dotfiles/-anchored form appear in the corpus, so the
154
157
  # anchor is stripped before comparison.
155
158
  ledger_cited_docs() {
156
- grep -rhoE '(~/Projects/dotfiles/)?docs/[A-Za-z0-9_/-]+\.md' "$LEDGER_ROOT"/dispatch/*.md 2>/dev/null \
159
+ grep -rhoE '(~/Projects/dotfiles/)?docs/[A-Za-z0-9_/-]+\.md' "$LEDGER_RULES_ROOT"/dispatch/*.md 2>/dev/null \
157
160
  | sed -E 's|^~/Projects/dotfiles/||' \
158
161
  | sort -u
159
162
  }
@@ -199,7 +202,7 @@ FACADE_SCOPE_TAG='^[[:space:]]*<!--[[:space:]]*oracle-scope:[^>]*-->[[:space:]]*
199
202
 
200
203
  ledger_facade_pages() {
201
204
  local file
202
- for file in "$LEDGER_ROOT"/dispatch/*.md; do
205
+ for file in "$LEDGER_RULES_ROOT"/dispatch/*.md; do
203
206
  [ -f "$file" ] || continue
204
207
  printf '%s\n' "$file"
205
208
  done
@@ -223,7 +226,7 @@ ledger_facade_scope() {
223
226
 
224
227
  ledger_facade_scripts() {
225
228
  local file
226
- for file in "$LEDGER_ROOT"/dispatch/*.sh; do
229
+ for file in "$LEDGER_RULES_ROOT"/dispatch/*.sh; do
227
230
  [ -f "$file" ] || continue
228
231
  [ "$(basename "$file")" = "$DISPATCH_LIB_BASENAME" ] && continue
229
232
  printf '%s\n' "$file"
@@ -267,7 +270,7 @@ ledger_match_count() {
267
270
  # the bare and the ~/Projects/dotfiles/-anchored spelling of the same path.
268
271
  ledger_facades_citing() {
269
272
  local doc="$1" page
270
- for page in "$LEDGER_ROOT"/dispatch/*.md; do
273
+ for page in "$LEDGER_RULES_ROOT"/dispatch/*.md; do
271
274
  [ -f "$page" ] || continue
272
275
  grep -qF "$doc" "$page" 2>/dev/null && printf '%s\n' "$(basename "$page" .md)"
273
276
  done
@@ -288,7 +291,7 @@ ledger_doc_trigger() {
288
291
  while IFS= read -r stem; do
289
292
  [ -n "$stem" ] || continue
290
293
  cited=0
291
- script="$LEDGER_ROOT/dispatch/$stem.sh"
294
+ script="$LEDGER_RULES_ROOT/dispatch/$stem.sh"
292
295
  [ -f "$script" ] || continue
293
296
  [ -n "$(ledger_facade_globs "$script")" ] || continue
294
297
  printf '%s' "$TRIGGER_MECHANICAL"