@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
package/audits/rust-error.sh
CHANGED
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
set -euo pipefail
|
|
44
44
|
|
|
45
|
+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
|
|
46
|
+
audit_scope_init --all "$@"
|
|
47
|
+
audit_index_snapshot "$@"
|
|
48
|
+
|
|
45
49
|
MODE_STAGED="--staged"
|
|
46
50
|
MODE_ALL="--all"
|
|
47
51
|
RULE="ERR-RS"
|
|
@@ -63,7 +67,8 @@ for arg in "$@"; do
|
|
|
63
67
|
printf 'usage: %s [--staged|--all]\n' "$0"
|
|
64
68
|
exit 0
|
|
65
69
|
;;
|
|
66
|
-
|
|
70
|
+
-*) printf 'unknown arg: %s\n' "$arg" >&2; exit 2 ;;
|
|
71
|
+
*) MODE=explicit ;;
|
|
67
72
|
esac
|
|
68
73
|
done
|
|
69
74
|
|
|
@@ -83,18 +88,11 @@ is_non_runtime_rust_path() {
|
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
gather_paths() {
|
|
86
|
-
|
|
87
|
-
"$MODE_STAGED")
|
|
88
|
-
git diff --cached --name-only --diff-filter=ACMRT -- '*.rs' || true
|
|
89
|
-
;;
|
|
90
|
-
"$MODE_ALL")
|
|
91
|
-
git ls-files --cached --others --exclude-standard -- '*.rs' || true
|
|
92
|
-
;;
|
|
93
|
-
esac
|
|
91
|
+
audit_scope_paths '*.rs'
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
FILES=()
|
|
97
|
-
while IFS= read -r path; do
|
|
95
|
+
while IFS= read -r -d '' path; do
|
|
98
96
|
[[ -n "$path" && -f "$path" ]] || continue
|
|
99
97
|
is_non_runtime_rust_path "$path" && continue
|
|
100
98
|
FILES+=("$path")
|
|
@@ -204,17 +202,24 @@ crate_root_of() {
|
|
|
204
202
|
}
|
|
205
203
|
|
|
206
204
|
alias_count=0
|
|
207
|
-
checked_crates=
|
|
205
|
+
checked_crates=()
|
|
208
206
|
for path in ${error_type_files[@]+"${error_type_files[@]}"}; do
|
|
209
207
|
crate=""
|
|
210
208
|
crate_root_of "$path" > /dev/null 2>&1 && crate="$(crate_root_of "$path")"
|
|
211
209
|
# A .rs file outside any Cargo crate has no crate to carry the alias; the
|
|
212
210
|
# lossy-map_err half still judged it.
|
|
213
211
|
[[ -n "$crate" ]] || continue
|
|
214
|
-
|
|
215
|
-
checked_crates
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
already_checked=0
|
|
213
|
+
for checked in ${checked_crates[@]+"${checked_crates[@]}"}; do
|
|
214
|
+
[ "$checked" != "$crate" ] || already_checked=1
|
|
215
|
+
done
|
|
216
|
+
[ "$already_checked" -eq 0 ] || continue
|
|
217
|
+
checked_crates+=("$crate")
|
|
218
|
+
alias_found=0
|
|
219
|
+
while IFS= read -r -d '' alias_path; do
|
|
220
|
+
if grep -qE "$RESULT_ALIAS_PATTERN" "$alias_path"; then alias_found=1; break; fi
|
|
221
|
+
done < <(git ls-files --cached --others --exclude-standard -z -- "$crate/*.rs")
|
|
222
|
+
if [ "$alias_found" -eq 0 ]; then
|
|
218
223
|
alias_count=$((alias_count + 1))
|
|
219
224
|
fail "$path $RULE: crate '$crate' declares a public Error type with no \`pub type Result<T, E = Error>\` beside it — a reader must not have to check WHICH error a signature returns."
|
|
220
225
|
fi
|
package/audits/scope.sh
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared filename scope and coherent index reads for deterministic scanners.
|
|
3
|
+
|
|
4
|
+
AUDIT_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
|
|
6
|
+
audit_scope_init() {
|
|
7
|
+
AUDIT_MODE="${SCOPE:-$1}"; shift
|
|
8
|
+
AUDIT_TARGETS=()
|
|
9
|
+
local arg selected=0 literal=0
|
|
10
|
+
for arg in "$@"; do
|
|
11
|
+
if [ "$literal" -eq 1 ]; then AUDIT_TARGETS+=("$arg"); continue; fi
|
|
12
|
+
case "$arg" in
|
|
13
|
+
--) literal=1 ;;
|
|
14
|
+
--staged|staged|--all|all)
|
|
15
|
+
[ "$selected" -eq 0 ] || { printf 'choose one audit mode\n' >&2; return 2; }
|
|
16
|
+
AUDIT_MODE="$arg"; selected=1 ;;
|
|
17
|
+
--strict)
|
|
18
|
+
[ "${0##*/}" = logging.sh ] || { printf 'unsupported audit option: %s\n' "$arg" >&2; return 2; } ;;
|
|
19
|
+
-h|--help) printf 'usage: %s [--all|--staged] | file [...]\n' "$0"; exit 0 ;;
|
|
20
|
+
-*) printf 'unknown audit option: %s\n' "$arg" >&2; return 2 ;;
|
|
21
|
+
*) AUDIT_TARGETS+=("$arg") ;;
|
|
22
|
+
esac
|
|
23
|
+
done
|
|
24
|
+
if [ "${#AUDIT_TARGETS[@]}" -gt 0 ]; then
|
|
25
|
+
[ "$selected" -eq 0 ] || { printf 'audit modes and explicit files cannot be mixed\n' >&2; return 2; }
|
|
26
|
+
AUDIT_MODE=explicit
|
|
27
|
+
fi
|
|
28
|
+
case "$AUDIT_MODE" in
|
|
29
|
+
staged|--staged) AUDIT_MODE=--staged ;;
|
|
30
|
+
all|--all) AUDIT_MODE=--all ;;
|
|
31
|
+
explicit) ;;
|
|
32
|
+
*) printf 'unknown audit scope: %s\n' "$AUDIT_MODE" >&2; return 2 ;;
|
|
33
|
+
esac
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Source and dependencies share a copied index. Git's object store is read-only;
|
|
37
|
+
# the index copy and checked-out files belong to this invocation alone.
|
|
38
|
+
audit_index_snapshot() {
|
|
39
|
+
[ "$AUDIT_MODE" = --staged ] || return 0
|
|
40
|
+
local gitdir index
|
|
41
|
+
AUDIT_SNAPSHOT_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/orly-index.XXXXXX")" || return 1
|
|
42
|
+
trap 'rm -rf -- "$AUDIT_SNAPSHOT_ROOT"' EXIT
|
|
43
|
+
trap 'exit 130' INT
|
|
44
|
+
trap 'exit 143' TERM
|
|
45
|
+
gitdir="$(git rev-parse --absolute-git-dir)" || exit 1
|
|
46
|
+
index="$(git rev-parse --git-path index)" || exit 1
|
|
47
|
+
cp "$index" "$AUDIT_SNAPSHOT_ROOT/index" || exit 1
|
|
48
|
+
export GIT_DIR="$gitdir" GIT_INDEX_FILE="$AUDIT_SNAPSHOT_ROOT/index" GIT_OPTIONAL_LOCKS=0
|
|
49
|
+
export GIT_WORK_TREE="$AUDIT_SNAPSHOT_ROOT/tree"
|
|
50
|
+
mkdir "$GIT_WORK_TREE" || exit 1
|
|
51
|
+
git checkout-index --all --prefix="$GIT_WORK_TREE/" || exit 1
|
|
52
|
+
cd "$GIT_WORK_TREE" || exit 1
|
|
53
|
+
[ -z "${TARGET_ROOT:-}" ] || TARGET_ROOT="$GIT_WORK_TREE"
|
|
54
|
+
audit_index_links || exit 2
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
audit_index_links() {
|
|
58
|
+
local entry path
|
|
59
|
+
git ls-files --stage -z > "$AUDIT_SNAPSHOT_ROOT/entries" || return 1
|
|
60
|
+
while IFS= read -r -d '' entry; do
|
|
61
|
+
case "$entry" in 120000*) ;; *) continue ;; esac
|
|
62
|
+
path="${entry#*$'\t'}"
|
|
63
|
+
bun -e 'import {realpathSync} from "node:fs";
|
|
64
|
+
import {relative, isAbsolute} from "node:path";
|
|
65
|
+
try {
|
|
66
|
+
const p = relative(process.cwd(), realpathSync(process.argv[1]));
|
|
67
|
+
if (p === ".." || p.startsWith("../") || isAbsolute(p)) throw new Error("escaping index link");
|
|
68
|
+
} catch { console.error("unsafe indexed symbolic link: " + process.argv[1]); process.exit(2); }' "$path" || return 2
|
|
69
|
+
done < "$AUDIT_SNAPSHOT_ROOT/entries"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Zero-delimited throughout: spaces, tabs, quotes and newlines are filenames.
|
|
73
|
+
audit_scope_paths() {
|
|
74
|
+
local path pattern matched
|
|
75
|
+
if [ "$AUDIT_MODE" = explicit ]; then
|
|
76
|
+
for path in "${AUDIT_TARGETS[@]}"; do
|
|
77
|
+
matched=0
|
|
78
|
+
for pattern in "$@"; do
|
|
79
|
+
case "$path" in $pattern) matched=1 ;; esac
|
|
80
|
+
done
|
|
81
|
+
[ "$matched" -eq 0 ] || printf '%s\0' "$path"
|
|
82
|
+
done
|
|
83
|
+
elif [ "$AUDIT_MODE" = --staged ]; then
|
|
84
|
+
git diff --cached --name-only -z --diff-filter=ACMRT -- "$@"
|
|
85
|
+
else
|
|
86
|
+
git ls-files --cached --others --exclude-standard -z -- "$@"
|
|
87
|
+
fi
|
|
88
|
+
}
|
package/audits/spec-template.sh
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# half that makes a spec "built for the agent".
|
|
15
15
|
# 2b. DECLARED-COMMAND PARITY — a pending/active spec's Acceptance Rubric
|
|
16
16
|
# must quote the repository's declared conform + applicable verify.* commands
|
|
17
|
-
# (.
|
|
17
|
+
# (.orly/orly.json) verbatim: the rubric and `orly gate` grade one
|
|
18
18
|
# boundary. Staged/file scope only; done/ specs stay historical.
|
|
19
19
|
#
|
|
20
20
|
# Dispatch façade: dispatch/write_spec.md (SPEC TEMPLATE GATE)
|
|
@@ -51,7 +51,7 @@ case "$MODE" in
|
|
|
51
51
|
--staged|staged)
|
|
52
52
|
# while read, not mapfile — bash-3.2 portability (see scripts/run-playbook-tests.sh).
|
|
53
53
|
SPECS=()
|
|
54
|
-
while IFS= read -r s; do SPECS+=("$s"); done < <(git diff --cached --name-only --diff-filter=ACMRT | grep -E '^docs/v[0-9]
|
|
54
|
+
while IFS= read -r s; do SPECS+=("$s"); done < <(git diff --cached --name-only --diff-filter=ACMRT | grep -E '^docs/v[0-9]+(\.[0-9]+)*/(pending|active|done)/.*\.md$' || true)
|
|
55
55
|
;;
|
|
56
56
|
--all|all)
|
|
57
57
|
SPECS=()
|
package/audits/spec-template.ts
CHANGED
|
@@ -6,7 +6,7 @@ type MarkdownNode = string | MarkdownElement;
|
|
|
6
6
|
type Section = { title: string; nodes: MarkdownNode[] };
|
|
7
7
|
type Commands = Record<string, string[][]>;
|
|
8
8
|
|
|
9
|
-
const CONFIG_PATH = ".
|
|
9
|
+
const CONFIG_PATH = ".orly/orly.json";
|
|
10
10
|
const SPEC_LINE_LIMIT = 320;
|
|
11
11
|
const BACKTICK = "`";
|
|
12
12
|
const EMPTY = "";
|
package/audits/sql-mod.sh
CHANGED
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
|
|
24
24
|
set -euo pipefail
|
|
25
25
|
|
|
26
|
+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
|
|
27
|
+
audit_scope_init --all "$@"
|
|
28
|
+
audit_index_snapshot "$@"
|
|
29
|
+
|
|
26
30
|
MODE="${1:-${SCOPE:---all}}"
|
|
27
31
|
ROOT="$(git rev-parse --show-toplevel)"
|
|
28
32
|
cd "$ROOT"
|
|
@@ -44,20 +48,13 @@ added_hit() {
|
|
|
44
48
|
| grep -nE "$DEF_RE|$BODY_RE" | head -1 || true
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
FILES=()
|
|
55
|
-
while IFS= read -r f; do FILES+=("$f"); done < <(find src -type f -name '*.zig' 2>/dev/null || true)
|
|
56
|
-
STAGED=0 ;;
|
|
57
|
-
*)
|
|
58
|
-
printf "usage: %s [--staged|--all]\n" "$0" >&2
|
|
59
|
-
exit 64 ;;
|
|
60
|
-
esac
|
|
51
|
+
MODE="$AUDIT_MODE"
|
|
52
|
+
FILES=()
|
|
53
|
+
while IFS= read -r -d '' f; do
|
|
54
|
+
[ ! -f "$f" ] || FILES+=("$f")
|
|
55
|
+
done < <(audit_scope_paths '*.zig')
|
|
56
|
+
STAGED=0
|
|
57
|
+
[ "$MODE" != --staged ] || STAGED=1
|
|
61
58
|
|
|
62
59
|
if [[ ${#FILES[@]} -eq 0 ]]; then
|
|
63
60
|
ok "no zig source files in scope ($MODE)"
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Extracted intact; the owning shell scanner supplies the filename scope.
|
|
2
|
+
FNR == 1 {
|
|
3
|
+
prev_file = FILENAME
|
|
4
|
+
# Language lane. Each lane below carves out what its syntax makes
|
|
5
|
+
# UNFIXABLE — a literal a named const cannot legally replace. Anything
|
|
6
|
+
# a const CAN replace stays in scope, in every language.
|
|
7
|
+
lang = "other"
|
|
8
|
+
if (FILENAME ~ /\.zig$/) lang = "zig"
|
|
9
|
+
else if (FILENAME ~ /\.rs$/) lang = "rust"
|
|
10
|
+
else if (FILENAME ~ /\.go$/) lang = "go"
|
|
11
|
+
# Test files: repetition is fixture data, not magic strings.
|
|
12
|
+
# Skip string-dup-file for tests; other checks still apply.
|
|
13
|
+
is_test = (FILENAME ~ /(_test\.zig|_test\.go|\.test\.|\.spec\.|\.unit\.test|\.integration\.test|\/test\/|\/tests\/)/)
|
|
14
|
+
# Directory-shaped test and benchmark trees. Rust puts both at the CRATE
|
|
15
|
+
# ROOT (`tests/`, `benches/`) with no leading slash, so the `\/tests\/`
|
|
16
|
+
# form above — which needs a parent directory — walked straight past them
|
|
17
|
+
# and audited 100 fixture hits as production literals.
|
|
18
|
+
if (FILENAME ~ "(^|/)(test|tests|benches|__tests__)/") is_test = 1
|
|
19
|
+
}
|
|
20
|
+
is_test { next }
|
|
21
|
+
# ui/ files: extracting class-strings or short literals to file-local
|
|
22
|
+
# consts is fragile in TypeScript (type-position uses widen string|
|
|
23
|
+
# literal types, and the cleanup belongs in a UI-aware spec). Skip
|
|
24
|
+
# string-dup-file for ui/packages/*/src; cross-runtime-orphan still
|
|
25
|
+
# runs against ui/ to catch ERR_* parity drift.
|
|
26
|
+
# macOS/BSD awk aborts on a `/` inside a `[...]` class in a regex *literal*
|
|
27
|
+
# ("nonterminated character class"); use a dynamic-regex STRING — identical
|
|
28
|
+
# match on gawk, portable on BWK awk (the macOS default). Do not re-inline.
|
|
29
|
+
FILENAME ~ "^ui/packages/[^/]+/(src|app|tests|components|lib|hooks)/" { next }
|
|
30
|
+
FNR == 1 { in_test_block = 0; test_depth = 0; in_block_comment = 0; in_rs_test = 0; rs_open = 0; rs_depth = 0; in_go_const = 0 }
|
|
31
|
+
{
|
|
32
|
+
line = $0
|
|
33
|
+
# Block-comment exclusion (TS/JS/Zig non-applicable): skip lines inside /* ... */
|
|
34
|
+
if (in_block_comment) {
|
|
35
|
+
if (line ~ /\*\//) in_block_comment = 0
|
|
36
|
+
next
|
|
37
|
+
}
|
|
38
|
+
if (line ~ /\/\*/ && line !~ /\*\//) {
|
|
39
|
+
in_block_comment = 1
|
|
40
|
+
next
|
|
41
|
+
}
|
|
42
|
+
# Zig multi-line string literal — lines start with `\\` after whitespace.
|
|
43
|
+
if (lang == "zig" && line ~ /^[[:space:]]*\\\\/) next
|
|
44
|
+
# Inline-test exclusion (Zig): track depth across `test "..." {` blocks.
|
|
45
|
+
if (lang == "zig") {
|
|
46
|
+
if (in_test_block) {
|
|
47
|
+
test_depth += gsub(/\{/, "{", line) - gsub(/\}/, "}", line)
|
|
48
|
+
if (test_depth <= 0) in_test_block = 0
|
|
49
|
+
line = $0 # restore
|
|
50
|
+
next
|
|
51
|
+
}
|
|
52
|
+
if (line ~ /^test[[:space:]]+"/) {
|
|
53
|
+
in_test_block = 1
|
|
54
|
+
tmp = line
|
|
55
|
+
test_depth = gsub(/\{/, "{", tmp) - gsub(/\}/, "}", tmp)
|
|
56
|
+
if (test_depth <= 0) in_test_block = 0
|
|
57
|
+
next
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
# Inline-test exclusion (Rust): Rust keeps its unit tests INSIDE the file
|
|
61
|
+
# they cover, under `#[cfg(test)] mod tests { ... }`, so without this the
|
|
62
|
+
# fixture keys of a well-tested module read as the worst literal debt in
|
|
63
|
+
# the production half of that same file — a real crate reported `"key1"`
|
|
64
|
+
# 15 times from one such block. Same brace-depth shape as the Zig branch.
|
|
65
|
+
if (lang == "rust") {
|
|
66
|
+
if (in_rs_test) {
|
|
67
|
+
tmp = line
|
|
68
|
+
rs_depth += gsub(/\{/, "{", tmp) - gsub(/\}/, "}", tmp)
|
|
69
|
+
if (!rs_open && index(line, "{") > 0) rs_open = 1
|
|
70
|
+
if (rs_open && rs_depth <= 0) in_rs_test = 0
|
|
71
|
+
else if (!rs_open && line ~ /;[[:space:]]*$/) in_rs_test = 0
|
|
72
|
+
next
|
|
73
|
+
}
|
|
74
|
+
# Three ways Rust marks a block as fixture data, not shipped behaviour:
|
|
75
|
+
# `#[cfg(test)]`, a `#[test]`/`#[tokio::test]` function, and a
|
|
76
|
+
# FEATURE-GATED test seam — `#[cfg(feature = "test-util")]`, the
|
|
77
|
+
# convention for a helper that must compile into the crate so an
|
|
78
|
+
# integration test in a sibling file can call it (a `one_of_each_kind`
|
|
79
|
+
# error-surface enumerator is the usual shape). The feature name is
|
|
80
|
+
# matched narrowly, `test` as a whole word or a `-`/`_` segment, so a
|
|
81
|
+
# production gate like `#[cfg(feature = "redis")]` still counts and
|
|
82
|
+
# `"latest"` is not mistaken for a test seam.
|
|
83
|
+
if (line ~ /^[[:space:]]*#\[cfg\(test\)\]/ ||
|
|
84
|
+
line ~ /^[[:space:]]*#\[[A-Za-z_:]*test\]/ ||
|
|
85
|
+
line ~ /^[[:space:]]*#\[cfg\(.*feature[[:space:]]*=[[:space:]]*"([A-Za-z0-9]+[-_])?test(ing)?([-_][A-Za-z0-9_-]+)?"/) {
|
|
86
|
+
in_rs_test = 1; rs_open = 0; rs_depth = 0
|
|
87
|
+
next
|
|
88
|
+
}
|
|
89
|
+
# Attribute literals are UNFIXABLE, not undisciplined. `#[serde(rename =
|
|
90
|
+
# "...")]`, `#[cfg(feature = "...")]` and `#[doc = "..."]` take a literal
|
|
91
|
+
# token by language rule — a const is not accepted there, so naming one
|
|
92
|
+
# cannot resolve the hit and reporting it only teaches people to ignore
|
|
93
|
+
# the gate. Ordered after the cfg(test) branch, which is also a `#[`.
|
|
94
|
+
if (line ~ /^[[:space:]]*#!?\[/) next
|
|
95
|
+
}
|
|
96
|
+
# Grouped-const exclusion (Go): `const ( ... )` states the binding keyword
|
|
97
|
+
# ONCE, on the opening line, so a member like `metaKeyQoS = "conf_..."` is
|
|
98
|
+
# a definition carrying no `const` for the binding carve-out below to see.
|
|
99
|
+
# `var (` groups are deliberately NOT tracked — the carve-out is const-only
|
|
100
|
+
# in every other language and widening it here would be a silent divergence.
|
|
101
|
+
if (lang == "go") {
|
|
102
|
+
if (in_go_const) {
|
|
103
|
+
if (line ~ /^[[:space:]]*\)/) { in_go_const = 0; next }
|
|
104
|
+
if (line ~ /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*([[:space:]]+[A-Za-z0-9_.\[\]*]+)?[[:space:]]*=/) next
|
|
105
|
+
}
|
|
106
|
+
else if (line ~ /^[[:space:]]*const[[:space:]]*\([[:space:]]*$/) { in_go_const = 1; next }
|
|
107
|
+
}
|
|
108
|
+
sub(/\/\/.*$/, "", line)
|
|
109
|
+
# Strip single-line /* ... */ inline block comments (jsdoc/etc) so
|
|
110
|
+
# literals inside them are not counted.
|
|
111
|
+
gsub(/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//, "", line)
|
|
112
|
+
# Const-BINDING carve-out (narrower than the numeric-suspect is_const_decl
|
|
113
|
+
# exemption below — deliberately): a literal that IS the right-hand side of a
|
|
114
|
+
# `const`/`pub const`/`export const` binding is its single-source DEFINITION,
|
|
115
|
+
# not a magic-string use. Two distinct named constants may legitimately share
|
|
116
|
+
# a value across domains (e.g. a runner status and a lease status both
|
|
117
|
+
# "active") — RULE UFS targets UN-named repetition, and both sites there are
|
|
118
|
+
# already named. But a literal passed as a CALL ARGUMENT on a const line
|
|
119
|
+
# (`const x = foo("lit");`) is NOT named by that const — it counts and flags
|
|
120
|
+
# like any other use. (A prior line-level skip exempted those too and silently
|
|
121
|
+
# gutted the check for most Zig code; the ufs_dup_string eval fixture pins the
|
|
122
|
+
# binding-level semantic.)
|
|
123
|
+
# `static` joins the keyword set for Rust: `static NAME: &str = "..."` binds
|
|
124
|
+
# a literal to a name exactly as `const` does, and the leading
|
|
125
|
+
# `(^|[[:space:]])` anchor already carries the `pub`/`pub(crate)` prefixes.
|
|
126
|
+
# The second type slot belongs to Go, which spells the type with no colon
|
|
127
|
+
# (`const name string = "..."`), which the colon-only slot could not see.
|
|
128
|
+
if (line ~ /(^|[[:space:]])(pub[[:space:]]+const|export[[:space:]]+const|const|static)[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*([[:space:]]*:[^=]*|[[:space:]]+[A-Za-z_][A-Za-z0-9_.\[\]*]*)?[[:space:]]*=[[:space:]]*"((\\.)|[^\\"])+"[[:space:]]*(as[[:space:]]+const)?[[:space:]]*[;,]?[[:space:]]*$/) next
|
|
129
|
+
rest = line
|
|
130
|
+
# Strip Zig identifier-escape syntax @"name" — body is an identifier,
|
|
131
|
+
# not a string literal, but the regex below would otherwise match it.
|
|
132
|
+
gsub(/@"[^"]*"/, "", rest)
|
|
133
|
+
# Strip Go backtick runs. They are overwhelmingly struct tags
|
|
134
|
+
# (`json:"id" yaml:"id"`), whose quoted halves are tag SYNTAX addressed by
|
|
135
|
+
# reflection — a const cannot appear inside one, so the repeat is unfixable.
|
|
136
|
+
# This also drops single-line Go raw strings, which is the accepted cost:
|
|
137
|
+
# the alternative reports every `json:"id"` in a wire struct as a violation.
|
|
138
|
+
# A backtick run spanning lines is not tracked (best-effort, as documented).
|
|
139
|
+
if (lang == "go") gsub(/`[^`]*`/, "", rest)
|
|
140
|
+
# Strip empty string literals so the regex below cannot fuse
|
|
141
|
+
# two adjacent Zig literals through their inner gap.
|
|
142
|
+
gsub(/""/, "", rest)
|
|
143
|
+
# Match a quoted string literal honouring \" escapes: open quote,
|
|
144
|
+
# then runs of (backslash+any-char) | (non-backslash-non-quote),
|
|
145
|
+
# then close quote. Min 3 chars total ensures literal body ≥1 char
|
|
146
|
+
# (back-compat with the {2,} body-min from the prior regex).
|
|
147
|
+
while (match(rest, /"((\\.)|[^\\"])+"/)) {
|
|
148
|
+
lit = substr(rest, RSTART, RLENGTH)
|
|
149
|
+
rest = substr(rest, RSTART + RLENGTH)
|
|
150
|
+
if (length(lit) < 4) continue
|
|
151
|
+
if (lit ~ /^"(http|https|file|\/|\\\\|\\\\n)/) continue
|
|
152
|
+
key = FILENAME "\034" lit
|
|
153
|
+
count[key]++
|
|
154
|
+
file_of[key] = FILENAME
|
|
155
|
+
lit_of[key] = lit
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
END {
|
|
159
|
+
for (k in count) {
|
|
160
|
+
if (count[k] >= 2) {
|
|
161
|
+
printf "string-dup-file %s %s %d\n", file_of[k], lit_of[k], count[k]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
package/audits/ufs.sh
CHANGED
|
@@ -53,20 +53,15 @@
|
|
|
53
53
|
|
|
54
54
|
set -euo pipefail
|
|
55
55
|
|
|
56
|
+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scope.sh"
|
|
57
|
+
audit_scope_init --all "$@"
|
|
58
|
+
audit_index_snapshot "$@"
|
|
59
|
+
|
|
56
60
|
# Default: full-codebase scan (`--all` is an explicit alias). `--staged` is the
|
|
57
61
|
# pre-commit lens — it narrows the per-file checks to `git diff --cached`. The
|
|
58
62
|
# retired `--diff` (BASE...HEAD) mode stays rejected; `--staged` reads the index
|
|
59
63
|
# and so is not blind to staged-but-uncommitted fixes (M70's concern).
|
|
60
|
-
MODE="$
|
|
61
|
-
case "$MODE" in
|
|
62
|
-
""|--all|all) MODE="--all" ;;
|
|
63
|
-
--staged|staged) MODE="--staged" ;;
|
|
64
|
-
*)
|
|
65
|
-
printf "usage: %s [--all|--staged]\n" "$0" >&2
|
|
66
|
-
printf "note: --diff was retired in M70 — see dispatch/write_any.md (UFS Gate → Scope). Use --staged for the pre-commit (index) lens.\n" >&2
|
|
67
|
-
exit 2
|
|
68
|
-
;;
|
|
69
|
-
esac
|
|
64
|
+
MODE="$AUDIT_MODE"
|
|
70
65
|
ROOT="$(git rev-parse --show-toplevel)"
|
|
71
66
|
cd "$ROOT"
|
|
72
67
|
|
|
@@ -111,19 +106,10 @@ is_source() {
|
|
|
111
106
|
|
|
112
107
|
# Per-file check scope (string-dup-file, numeric-suspect). --staged narrows to
|
|
113
108
|
# the commit; cross-runtime-orphan (below) always scans the full tree.
|
|
114
|
-
if [ "$MODE" = "--staged" ]; then
|
|
115
|
-
scope_files() { git diff --cached --name-only --diff-filter=ACMRT; }
|
|
116
|
-
else
|
|
117
|
-
scope_files() { git ls-files; }
|
|
118
|
-
fi
|
|
119
|
-
# `while read` rather than mapfile: mapfile is bash 4+ and macOS ships 3.2 —
|
|
120
|
-
# the portability rule scripts/run-playbook-tests.sh already records.
|
|
121
109
|
FILES=()
|
|
122
|
-
while IFS= read -r f; do
|
|
123
|
-
FILES+=("$f")
|
|
124
|
-
done < <(
|
|
125
|
-
is_source "$f" && echo "$f"
|
|
126
|
-
done)
|
|
110
|
+
while IFS= read -r -d '' f; do
|
|
111
|
+
if is_source "$f" && [ -f "$f" ]; then FILES+=("$f"); fi
|
|
112
|
+
done < <(audit_scope_paths '*.zig' '*.ts' '*.tsx' '*.js' '*.jsx' '*.rs' '*.go')
|
|
127
113
|
|
|
128
114
|
[ "${#FILES[@]}" -eq 0 ] && { ok "audit-ufs: no source files in scope"; exit 0; }
|
|
129
115
|
|
|
@@ -142,171 +128,7 @@ done)
|
|
|
142
128
|
# a subshell and silently dropped violations.
|
|
143
129
|
while IFS= read -r row; do
|
|
144
130
|
record "$row"
|
|
145
|
-
done < <(awk
|
|
146
|
-
FNR == 1 {
|
|
147
|
-
prev_file = FILENAME
|
|
148
|
-
# Language lane. Each lane below carves out what its syntax makes
|
|
149
|
-
# UNFIXABLE — a literal a named const cannot legally replace. Anything
|
|
150
|
-
# a const CAN replace stays in scope, in every language.
|
|
151
|
-
lang = "other"
|
|
152
|
-
if (FILENAME ~ /\.zig$/) lang = "zig"
|
|
153
|
-
else if (FILENAME ~ /\.rs$/) lang = "rust"
|
|
154
|
-
else if (FILENAME ~ /\.go$/) lang = "go"
|
|
155
|
-
# Test files: repetition is fixture data, not magic strings.
|
|
156
|
-
# Skip string-dup-file for tests; other checks still apply.
|
|
157
|
-
is_test = (FILENAME ~ /(_test\.zig|_test\.go|\.test\.|\.spec\.|\.unit\.test|\.integration\.test|\/test\/|\/tests\/)/)
|
|
158
|
-
# Directory-shaped test and benchmark trees. Rust puts both at the CRATE
|
|
159
|
-
# ROOT (`tests/`, `benches/`) with no leading slash, so the `\/tests\/`
|
|
160
|
-
# form above — which needs a parent directory — walked straight past them
|
|
161
|
-
# and audited 100 fixture hits as production literals.
|
|
162
|
-
if (FILENAME ~ "(^|/)(test|tests|benches|__tests__)/") is_test = 1
|
|
163
|
-
}
|
|
164
|
-
is_test { next }
|
|
165
|
-
# ui/ files: extracting class-strings or short literals to file-local
|
|
166
|
-
# consts is fragile in TypeScript (type-position uses widen string|
|
|
167
|
-
# literal types, and the cleanup belongs in a UI-aware spec). Skip
|
|
168
|
-
# string-dup-file for ui/packages/*/src; cross-runtime-orphan still
|
|
169
|
-
# runs against ui/ to catch ERR_* parity drift.
|
|
170
|
-
# macOS/BSD awk aborts on a `/` inside a `[...]` class in a regex *literal*
|
|
171
|
-
# ("nonterminated character class"); use a dynamic-regex STRING — identical
|
|
172
|
-
# match on gawk, portable on BWK awk (the macOS default). Do not re-inline.
|
|
173
|
-
FILENAME ~ "^ui/packages/[^/]+/(src|app|tests|components|lib|hooks)/" { next }
|
|
174
|
-
FNR == 1 { in_test_block = 0; test_depth = 0; in_block_comment = 0; in_rs_test = 0; rs_open = 0; rs_depth = 0; in_go_const = 0 }
|
|
175
|
-
{
|
|
176
|
-
line = $0
|
|
177
|
-
# Block-comment exclusion (TS/JS/Zig non-applicable): skip lines inside /* ... */
|
|
178
|
-
if (in_block_comment) {
|
|
179
|
-
if (line ~ /\*\//) in_block_comment = 0
|
|
180
|
-
next
|
|
181
|
-
}
|
|
182
|
-
if (line ~ /\/\*/ && line !~ /\*\//) {
|
|
183
|
-
in_block_comment = 1
|
|
184
|
-
next
|
|
185
|
-
}
|
|
186
|
-
# Zig multi-line string literal — lines start with `\\` after whitespace.
|
|
187
|
-
if (lang == "zig" && line ~ /^[[:space:]]*\\\\/) next
|
|
188
|
-
# Inline-test exclusion (Zig): track depth across `test "..." {` blocks.
|
|
189
|
-
if (lang == "zig") {
|
|
190
|
-
if (in_test_block) {
|
|
191
|
-
test_depth += gsub(/\{/, "{", line) - gsub(/\}/, "}", line)
|
|
192
|
-
if (test_depth <= 0) in_test_block = 0
|
|
193
|
-
line = $0 # restore
|
|
194
|
-
next
|
|
195
|
-
}
|
|
196
|
-
if (line ~ /^test[[:space:]]+"/) {
|
|
197
|
-
in_test_block = 1
|
|
198
|
-
tmp = line
|
|
199
|
-
test_depth = gsub(/\{/, "{", tmp) - gsub(/\}/, "}", tmp)
|
|
200
|
-
if (test_depth <= 0) in_test_block = 0
|
|
201
|
-
next
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
# Inline-test exclusion (Rust): Rust keeps its unit tests INSIDE the file
|
|
205
|
-
# they cover, under `#[cfg(test)] mod tests { ... }`, so without this the
|
|
206
|
-
# fixture keys of a well-tested module read as the worst literal debt in
|
|
207
|
-
# the production half of that same file — a real crate reported `"key1"`
|
|
208
|
-
# 15 times from one such block. Same brace-depth shape as the Zig branch.
|
|
209
|
-
if (lang == "rust") {
|
|
210
|
-
if (in_rs_test) {
|
|
211
|
-
tmp = line
|
|
212
|
-
rs_depth += gsub(/\{/, "{", tmp) - gsub(/\}/, "}", tmp)
|
|
213
|
-
if (!rs_open && index(line, "{") > 0) rs_open = 1
|
|
214
|
-
if (rs_open && rs_depth <= 0) in_rs_test = 0
|
|
215
|
-
else if (!rs_open && line ~ /;[[:space:]]*$/) in_rs_test = 0
|
|
216
|
-
next
|
|
217
|
-
}
|
|
218
|
-
# Three ways Rust marks a block as fixture data, not shipped behaviour:
|
|
219
|
-
# `#[cfg(test)]`, a `#[test]`/`#[tokio::test]` function, and a
|
|
220
|
-
# FEATURE-GATED test seam — `#[cfg(feature = "test-util")]`, the
|
|
221
|
-
# convention for a helper that must compile into the crate so an
|
|
222
|
-
# integration test in a sibling file can call it (a `one_of_each_kind`
|
|
223
|
-
# error-surface enumerator is the usual shape). The feature name is
|
|
224
|
-
# matched narrowly, `test` as a whole word or a `-`/`_` segment, so a
|
|
225
|
-
# production gate like `#[cfg(feature = "redis")]` still counts and
|
|
226
|
-
# `"latest"` is not mistaken for a test seam.
|
|
227
|
-
if (line ~ /^[[:space:]]*#\[cfg\(test\)\]/ ||
|
|
228
|
-
line ~ /^[[:space:]]*#\[[A-Za-z_:]*test\]/ ||
|
|
229
|
-
line ~ /^[[:space:]]*#\[cfg\(.*feature[[:space:]]*=[[:space:]]*"([A-Za-z0-9]+[-_])?test(ing)?([-_][A-Za-z0-9_-]+)?"/) {
|
|
230
|
-
in_rs_test = 1; rs_open = 0; rs_depth = 0
|
|
231
|
-
next
|
|
232
|
-
}
|
|
233
|
-
# Attribute literals are UNFIXABLE, not undisciplined. `#[serde(rename =
|
|
234
|
-
# "...")]`, `#[cfg(feature = "...")]` and `#[doc = "..."]` take a literal
|
|
235
|
-
# token by language rule — a const is not accepted there, so naming one
|
|
236
|
-
# cannot resolve the hit and reporting it only teaches people to ignore
|
|
237
|
-
# the gate. Ordered after the cfg(test) branch, which is also a `#[`.
|
|
238
|
-
if (line ~ /^[[:space:]]*#!?\[/) next
|
|
239
|
-
}
|
|
240
|
-
# Grouped-const exclusion (Go): `const ( ... )` states the binding keyword
|
|
241
|
-
# ONCE, on the opening line, so a member like `metaKeyQoS = "conf_..."` is
|
|
242
|
-
# a definition carrying no `const` for the binding carve-out below to see.
|
|
243
|
-
# `var (` groups are deliberately NOT tracked — the carve-out is const-only
|
|
244
|
-
# in every other language and widening it here would be a silent divergence.
|
|
245
|
-
if (lang == "go") {
|
|
246
|
-
if (in_go_const) {
|
|
247
|
-
if (line ~ /^[[:space:]]*\)/) { in_go_const = 0; next }
|
|
248
|
-
if (line ~ /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*([[:space:]]+[A-Za-z0-9_.\[\]*]+)?[[:space:]]*=/) next
|
|
249
|
-
}
|
|
250
|
-
else if (line ~ /^[[:space:]]*const[[:space:]]*\([[:space:]]*$/) { in_go_const = 1; next }
|
|
251
|
-
}
|
|
252
|
-
sub(/\/\/.*$/, "", line)
|
|
253
|
-
# Strip single-line /* ... */ inline block comments (jsdoc/etc) so
|
|
254
|
-
# literals inside them are not counted.
|
|
255
|
-
gsub(/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//, "", line)
|
|
256
|
-
# Const-BINDING carve-out (narrower than the numeric-suspect is_const_decl
|
|
257
|
-
# exemption below — deliberately): a literal that IS the right-hand side of a
|
|
258
|
-
# `const`/`pub const`/`export const` binding is its single-source DEFINITION,
|
|
259
|
-
# not a magic-string use. Two distinct named constants may legitimately share
|
|
260
|
-
# a value across domains (e.g. a runner status and a lease status both
|
|
261
|
-
# "active") — RULE UFS targets UN-named repetition, and both sites there are
|
|
262
|
-
# already named. But a literal passed as a CALL ARGUMENT on a const line
|
|
263
|
-
# (`const x = foo("lit");`) is NOT named by that const — it counts and flags
|
|
264
|
-
# like any other use. (A prior line-level skip exempted those too and silently
|
|
265
|
-
# gutted the check for most Zig code; the ufs_dup_string eval fixture pins the
|
|
266
|
-
# binding-level semantic.)
|
|
267
|
-
# `static` joins the keyword set for Rust: `static NAME: &str = "..."` binds
|
|
268
|
-
# a literal to a name exactly as `const` does, and the leading
|
|
269
|
-
# `(^|[[:space:]])` anchor already carries the `pub`/`pub(crate)` prefixes.
|
|
270
|
-
# The second type slot belongs to Go, which spells the type with no colon
|
|
271
|
-
# (`const name string = "..."`), which the colon-only slot could not see.
|
|
272
|
-
if (line ~ /(^|[[:space:]])(pub[[:space:]]+const|export[[:space:]]+const|const|static)[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*([[:space:]]*:[^=]*|[[:space:]]+[A-Za-z_][A-Za-z0-9_.\[\]*]*)?[[:space:]]*=[[:space:]]*"((\\.)|[^\\"])+"[[:space:]]*(as[[:space:]]+const)?[[:space:]]*[;,]?[[:space:]]*$/) next
|
|
273
|
-
rest = line
|
|
274
|
-
# Strip Zig identifier-escape syntax @"name" — body is an identifier,
|
|
275
|
-
# not a string literal, but the regex below would otherwise match it.
|
|
276
|
-
gsub(/@"[^"]*"/, "", rest)
|
|
277
|
-
# Strip Go backtick runs. They are overwhelmingly struct tags
|
|
278
|
-
# (`json:"id" yaml:"id"`), whose quoted halves are tag SYNTAX addressed by
|
|
279
|
-
# reflection — a const cannot appear inside one, so the repeat is unfixable.
|
|
280
|
-
# This also drops single-line Go raw strings, which is the accepted cost:
|
|
281
|
-
# the alternative reports every `json:"id"` in a wire struct as a violation.
|
|
282
|
-
# A backtick run spanning lines is not tracked (best-effort, as documented).
|
|
283
|
-
if (lang == "go") gsub(/`[^`]*`/, "", rest)
|
|
284
|
-
# Strip empty string literals so the regex below cannot fuse
|
|
285
|
-
# two adjacent Zig literals through their inner gap.
|
|
286
|
-
gsub(/""/, "", rest)
|
|
287
|
-
# Match a quoted string literal honouring \" escapes: open quote,
|
|
288
|
-
# then runs of (backslash+any-char) | (non-backslash-non-quote),
|
|
289
|
-
# then close quote. Min 3 chars total ensures literal body ≥1 char
|
|
290
|
-
# (back-compat with the {2,} body-min from the prior regex).
|
|
291
|
-
while (match(rest, /"((\\.)|[^\\"])+"/)) {
|
|
292
|
-
lit = substr(rest, RSTART, RLENGTH)
|
|
293
|
-
rest = substr(rest, RSTART + RLENGTH)
|
|
294
|
-
if (length(lit) < 4) continue
|
|
295
|
-
if (lit ~ /^"(http|https|file|\/|\\\\|\\\\n)/) continue
|
|
296
|
-
key = FILENAME "\034" lit
|
|
297
|
-
count[key]++
|
|
298
|
-
file_of[key] = FILENAME
|
|
299
|
-
lit_of[key] = lit
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
END {
|
|
303
|
-
for (k in count) {
|
|
304
|
-
if (count[k] >= 2) {
|
|
305
|
-
printf "string-dup-file %s %s %d\n", file_of[k], lit_of[k], count[k]
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
' "${FILES[@]}")
|
|
131
|
+
done < <(awk -f "$AUDIT_HOME/ufs-strings.awk" "${FILES[@]}")
|
|
310
132
|
|
|
311
133
|
# ── 2. numeric-suspect ──────────────────────────────────────────────────────
|
|
312
134
|
# Flag bare power-of-ten and unit-factor numerics in expressions.
|
|
@@ -321,7 +143,7 @@ done < <(awk '
|
|
|
321
143
|
# Boundaries are capturing groups, not \b: macOS/BSD awk treats \b as a backspace,
|
|
322
144
|
# so word-boundary anchors silently match nothing. The leading/trailing boundary
|
|
323
145
|
# chars are trimmed back off the extracted token below (see RSTART/RLENGTH use).
|
|
324
|
-
NUMERIC_RE='(^|[^0-9A-Za-z_])(1[_e]?0{3,12}|1_000(_000)*|10_000_000|100_000|1024|1048576|3600|86400)([^0-9A-Za-z_]|$)'
|
|
146
|
+
NUMERIC_RE='(^|[^0-9A-Za-z_])(1[_e]?0{3,12}|1_000(_000)*|10_000_000|100_000|1024|1048576|60|3600|86400)([^0-9A-Za-z_]|$)'
|
|
325
147
|
|
|
326
148
|
# Perf (M70): single awk across all files; FNR == 1 detects file boundary
|
|
327
149
|
# so the "prev line carve-out" stays per-file. Process substitution keeps
|