@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.
- package/.githooks/pre-push +2 -1
- package/AGENTS.md +21 -31
- package/README.md +65 -29
- package/SOUL.md +13 -23
- package/audits/agents-md.md +67 -57
- package/audits/deinit-pairs.sh +13 -18
- package/audits/design-tokens.sh +9 -23
- package/audits/doc-read.sh +10 -33
- package/audits/doc-reads.ts +41 -0
- package/audits/error-codes.sh +18 -25
- package/audits/logging-rust.awk +145 -0
- package/audits/logging.sh +10 -166
- package/audits/msid-ui.sh +45 -18
- package/audits/rule-ledger-lib.sh +9 -6
- package/audits/rust-error.sh +20 -15
- package/audits/scope.sh +88 -0
- package/audits/spec-template.sh +2 -2
- package/audits/spec-template.ts +1 -1
- package/audits/sql-mod.sh +11 -14
- package/audits/ufs-strings.awk +164 -0
- package/audits/ufs.sh +10 -188
- package/core/operating-model.md +8 -8
- package/dispatch/edit_rules.md +8 -6
- package/dispatch/lib.sh +41 -26
- package/dispatch/lifecycle.md +18 -6
- package/dispatch/name_architecture.md +8 -3
- package/dispatch/verify.md +1 -1
- package/dispatch/write_any.md +10 -3
- package/dispatch/write_changelog.md +7 -4
- package/dispatch/write_go.md +1 -1
- package/dispatch/write_http.md +5 -7
- package/dispatch/write_python.md +1 -1
- package/dispatch/write_rust.md +17 -1
- package/dispatch/write_shell.md +1 -1
- package/dispatch/write_spec.md +3 -1
- package/dispatch/write_sql.md +28 -6
- package/dispatch/write_ts_adhere_bun.md +11 -0
- package/dispatch/write_zig.md +53 -21
- package/docs/CHANGELOG_VOICE.md +11 -2
- package/docs/EXECUTE_DOC_READS.md +10 -10
- package/docs/HARNESS_VERIFY_OUTPUT.md +6 -3
- package/docs/JUDGMENTS.md +153 -0
- package/docs/LIFECYCLE_PATTERNS.md +26 -16
- package/docs/LOGGING_STANDARD.md +39 -19
- package/docs/ORLY_ARCHITECTURE.md +77 -6
- package/docs/RELEASE_TEMPLATE.md +21 -5
- package/docs/REST_API_DESIGN_GUIDELINES.md +86 -21
- package/docs/RULE_ENFORCEMENT.md +6 -6
- package/docs/SCHEMA_CONVENTIONS.md +24 -3
- package/docs/TEMPLATE.md +4 -4
- package/docs/VERIFY_TIERS.md +2 -2
- package/docs/greptile-learnings/RULES.md +81 -26
- package/llms.txt +37 -34
- package/package.json +6 -1
- package/packs/language/go/rules.md +1 -1
- package/packs/language/python/rules.md +1 -1
- package/packs/language/rust/rules.md +17 -1
- package/packs/language/shell/rules.md +1 -1
- package/registry.json +22 -7
- package/schemas/profile.schema.json +48 -5
- package/skills/orly-babysit-prs/SKILL.md +87 -34
- package/skills/orly-spec-new/SKILL.md +19 -6
- package/skills/orly-write-integration-test/SKILL.md +4 -4
- package/skills/orly-write-unit-test/SKILL.md +10 -1
- package/src/cli.test.ts +2 -2
- package/src/cli.ts +19 -75
- package/src/cli_help.ts +69 -0
- package/src/command_limits.ts +20 -0
- package/src/command_process.test.ts +109 -0
- package/src/command_process.ts +88 -0
- package/src/command_runner.test.ts +88 -0
- package/src/command_runner.ts +33 -0
- package/src/comprehension_cache.test.ts +123 -0
- package/src/config.test.ts +5 -4
- package/src/config.ts +26 -164
- package/src/config_discovery.ts +159 -0
- package/src/config_validation.test.ts +56 -0
- package/src/config_validation.ts +25 -0
- package/src/criteria.test.ts +1 -1
- package/src/criteria.ts +10 -26
- package/src/criteria_spec.ts +12 -20
- package/src/criteria_support.ts +2 -19
- package/src/doc_reads.test.ts +42 -0
- package/src/doc_rules.ts +2 -2
- package/src/evaluation_resources.test.ts +64 -0
- package/src/execution/config.ts +22 -0
- package/src/execution/plan.test.ts +65 -0
- package/src/execution/plan.ts +51 -0
- package/src/gates.ts +39 -24
- package/src/gates_spec.test.ts +110 -4
- package/src/gates_test_support.ts +9 -7
- package/src/install.test.ts +38 -222
- package/src/install.ts +117 -308
- package/src/install_loaders.test.ts +203 -0
- package/src/install_nested.test.ts +2 -2
- package/src/install_packs.test.ts +66 -6
- package/src/installation/hooks.ts +63 -0
- package/src/installation/lock.test.ts +97 -0
- package/src/installation/lock.ts +56 -0
- package/src/installation/migration.test.ts +212 -0
- package/src/installation/migration.ts +65 -0
- package/src/installation/plan.ts +96 -0
- package/src/installation/transaction.ts +126 -0
- package/src/installation_preservation.test.ts +48 -0
- package/src/judgments/advice.test.ts +38 -0
- package/src/judgments/advice.ts +32 -0
- package/src/judgments/command.integration.test.ts +47 -0
- package/src/judgments/command.ts +90 -0
- package/src/judgments/constants.ts +55 -0
- package/src/judgments/evidence.test.ts +48 -0
- package/src/judgments/evidence.ts +34 -0
- package/src/judgments/files.test.ts +62 -0
- package/src/judgments/files.ts +73 -0
- package/src/judgments/questions.test.ts +19 -0
- package/src/judgments/questions.ts +85 -0
- package/src/judgments/replay.test.ts +43 -0
- package/src/judgments/replay.ts +66 -0
- package/src/judgments/runner.integration.test.ts +165 -0
- package/src/judgments/runner.ts +97 -0
- package/src/judgments/scan.test.ts +19 -0
- package/src/judgments/scan.ts +40 -0
- package/src/judgments/syntax.test.ts +115 -0
- package/src/judgments/syntax.ts +71 -0
- package/src/judgments/syntax_worker.ts +70 -0
- package/src/judgments/test_support.ts +60 -0
- package/src/judgments/transport.integration.test.ts +63 -0
- package/src/judgments/transport.ts +35 -0
- package/src/judgments/types.test.ts +43 -0
- package/src/judgments/types.ts +66 -0
- package/src/judgments/wire.test.ts +48 -0
- package/src/judgments/wire.ts +40 -0
- package/src/loaders.test.ts +13 -16
- package/src/loaders.ts +28 -10
- package/src/model.test.ts +20 -3
- package/src/model.ts +7 -18
- package/src/pack_hygiene.test.ts +42 -1
- package/src/pack_hygiene.ts +18 -8
- package/src/references.ts +15 -7
- package/src/release_inventory.test.ts +80 -0
- package/src/render.test.ts +1 -1
- package/src/render.ts +3 -8
- package/src/required_scanner.test.ts +36 -0
- package/src/review_poll.test.ts +158 -0
- package/src/scanner_scope.test.ts +202 -0
- package/src/setup.test.ts +9 -6
- package/src/spec_baseline.test.ts +1 -1
- package/src/spec_completion.test.ts +74 -0
- package/src/spec_obligations.ts +64 -0
- package/src/spec_rehearsal.test.ts +182 -0
- package/src/spec_template_io.test.ts +11 -9
- package/src/surfaces.test.ts +2 -0
- package/src/surfaces.ts +1 -1
- package/src/validation.ts +7 -4
- package/src/verify.test.ts +18 -2
- 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");
|
package/audits/error-codes.sh
CHANGED
|
@@ -49,7 +49,11 @@
|
|
|
49
49
|
|
|
50
50
|
set -euo pipefail
|
|
51
51
|
|
|
52
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
'
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
84
|
-
|
|
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
|
-
--
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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\" +\"[^\"]+\"" "$
|
|
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' "$
|
|
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 "$
|
|
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 "$
|
|
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 "$
|
|
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="$
|
|
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"
|