Skip to content

Commit d76fb99

Browse files
committed
Merge branch 'wip/land-uncommitted-20260906' into main (fleet integrate 20260912)
Source: REMOTE wip/land-uncommitted-20260906 @ ca2a806, 1 commit(s) not on main. Merged by the fleet integrate lane; conflicts, if any, are resolved semantically (both sides kept) and noted below. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G7RjB22qc4AaKq4zqeVBVY Conflict resolution (mechanical, semantics-preserving): package.json partial resolved=0 left=1 package.json JSON-UNION added=0 ours-wins=0 FLAGS: ignore-list files: set-union of both sides; empty-base hunks: both additions kept (ours first); Cargo.lock: ours + cargo update --workspace to reconcile with the merged manifests.
2 parents ad4e680 + ca2a806 commit d76fb99

16 files changed

Lines changed: 2045 additions & 0 deletions

‎.ores-lint/README.md‎

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# ores-lint
2+
3+
A vendored, dependency-free lint baseline for every JavaScript/TypeScript,
4+
Rust, Dart/Flutter and Gleam repo in the org fleet. Everything it needs is in
5+
this directory — there is nothing to install from a registry and nothing to
6+
keep in version sync.
7+
8+
Only universally accepted linters are used as hosts:
9+
10+
| language | host linter | custom house rules |
11+
|---|---|---|
12+
| TypeScript / JS | ESLint 9+ (flat config) | `ores/require-send`, `ores/semi` |
13+
| Rust | clippy | `implicit_return` house style; `#[must_use]` on log `Event`; require-send scanner |
14+
| Dart / Flutter | `dart analyze` | require-send scanner |
15+
| Gleam | `gleam format --check` + `gleam check` | require-send scanner |
16+
17+
There is no ESLint-quality plugin host for Gleam, and Dart's `custom_lint` /
18+
Rust's `dylint` would pull registry packages into every repo. The one house
19+
rule that those hosts cannot express — **logger chains must end in `send()`** —
20+
is implemented as a vendored ESLint rule for TS and a small Node scanner for
21+
the other three languages.
22+
23+
## Running it
24+
25+
```sh
26+
sh .ores-lint/lint.sh # lints whatever this repo contains
27+
sh .ores-lint/selftest.sh # verifies the toolkit still works after a toolchain upgrade
28+
```
29+
30+
For JS repos it is also wired into `npm run lint:ores`, and runs automatically
31+
before `npm run build` (`prebuild`) and before `npm publish` (`prepublishOnly`).
32+
33+
## What it enforces
34+
35+
**JavaScript / TypeScript** — via ESLint (flat config):
36+
37+
| rule | why |
38+
|---|---|
39+
| `semi` | house style: semicolons are required, missing ones warn |
40+
| `ores/require-send` | a logging chain that reaches `.info()`/`.warn()`/… but never calls `.send()` or `.send(boolean)` builds an event that is never delivered |
41+
| correctness set | `eqeqeq`, `no-unreachable`, `no-dupe-keys`, `use-isnan`, `valid-typeof`, `no-async-promise-executor`, and similar low-false-positive checks |
42+
43+
**Rust** — via clippy, plus rustc `#[must_use]` on ores-otel `Event`:
44+
45+
| lint | why |
46+
|---|---|
47+
| `clippy::implicit_return` | house style: prefer an explicit `return` at tail position |
48+
| `clippy::correctness`, `clippy::suspicious` | real defects |
49+
| `unwrap_used`, `expect_used`, `panic_in_result_fn`, `todo`, `dbg_macro` | things that should not reach a publish |
50+
51+
**Dart / Flutter** — via `dart analyze` (or `flutter analyze`). If the repo has
52+
no `analysis_options.yaml`, rollout drops a baseline of analyzer-shipped
53+
correctness lints. Existing files are never overwritten. Prefer
54+
`package:lints` / `package:flutter_lints` when the package already depends on
55+
them — those are the Dart equivalents of `eslint:recommended`.
56+
57+
**Gleam** — via `gleam format --check` and `gleam check`. Unused values already
58+
catch many forgotten `send` calls; the scanner catches the rest (assigned
59+
events, unfinished pipes).
60+
61+
**require-send (all four languages).** The ores-otel logger builds an event
62+
through method chaining (TS/Rust/Dart) or pipes (Gleam). Delivery is
63+
`send()`, `send(boolean)`, or `send_with_store(...)`. Forgetting that call
64+
means the event is built and then dropped — unless shutdown recovers it, which
65+
is a fallback, not the API. Tests that deliberately build unsent events are
66+
skipped unless `ORES_LINT_REQUIRE_SEND_INCLUDE_TESTS=1`.
67+
68+
## Overriding a finding on one line
69+
70+
This is expected. Some tests, shutdown-recovery fixtures, and rare control
71+
flow should not call `send()`. Use the comment that matches the host linter,
72+
or the unified ores-lint form which works in every language:
73+
74+
```
75+
// ores-lint-disable-next-line require-send
76+
// ores-lint-disable-line require-send
77+
// ores-lint-disable-file require-send
78+
```
79+
80+
Language-native equivalents, also honoured:
81+
82+
| language | one-line override |
83+
|---|---|
84+
| TypeScript | `// eslint-disable-next-line ores/require-send` |
85+
| Rust (`#[must_use]`) | `#[allow(unused_must_use)]` on the next statement |
86+
| Dart analyzer | `// ignore: name_of_lint` (analyzer rules only; require-send uses the ores-lint form) |
87+
| Gleam | the ores-lint form above |
88+
89+
Do not disable the rule for a whole file unless the file is a generated
90+
fixture. Prefer the next-line form.
91+
92+
## The two things worth knowing
93+
94+
95+
**1. Implicit-return findings are capped.** `clippy::implicit_return` fires once
96+
per implicit return, which on a real crate is hundreds of identical lines. The
97+
lint stays fully enabled so nothing is missed, but `rust.sh` collapses it into a
98+
single warning showing at most 5 concrete locations plus `... and N more`. The
99+
same cap applies to every ESLint rule via `eslint/formatter.mjs`. Change it with
100+
`ORES_LINT_MAX_EXAMPLES`.
101+
102+
**2. `clippy::needless_return` had to be disabled.** It ships enabled in
103+
clippy's default `style` group and warns on exactly the explicit returns this
104+
house style asks for. Enabling `implicit_return` without allowing
105+
`needless_return` makes the two lints contradict each other on every function in
106+
the crate. `selftest.sh` asserts this stays true.
107+
108+
## Scope: sub-projects and repo boundaries
109+
110+
The linter does **not** assume the repo root is the only project.
111+
112+
- **Rust** — `rust.sh` finds every crate in the repo, including ones under
113+
`apps/` or `clients/`. Crates that are workspace members of an already-linted
114+
root are skipped (via `cargo metadata --no-deps`) so nothing is linted twice,
115+
and findings from every crate are aggregated into **one** capped report.
116+
- **JS/TS** — a flat config at the repo root makes `eslint .` reach nested
117+
packages, so the config goes in even when the JS lives in a subdirectory.
118+
119+
**Nested git repositories are a hard boundary.** A repo checked out inside
120+
another repo gets its own ores-lint install; the parent must not lint it, or the
121+
same findings get reported twice under the wrong repo name and the same
122+
`package.json` gets wired with two conflicting relative paths. `rollout.mjs`
123+
records those boundaries in `.ores-lint/nested-repos.json`, and both halves of
124+
the linter read it.
125+
126+
To exclude a repo entirely — vendored upstream forks, for instance — drop an
127+
empty `.ores-lint-ignore` file at its root.
128+
129+
## Legacy config migration
130+
131+
ESLint 9+ reads flat config **only**. Three older mechanisms are silently
132+
ignored, which means any repo still relying on them has not been linted at all:
133+
134+
| legacy mechanism | status |
135+
|---|---|
136+
| `.eslintrc*` | ignored entirely; rules are dead |
137+
| `eslintConfig` key in `package.json` | ignored entirely |
138+
| `.eslintignore` | ignored, with a warning |
139+
140+
`audit.mjs` reports every repo in each category. `.eslintignore` is ported
141+
automatically into flat-config `ignores` by `base.mjs` (gitignore semantics
142+
preserved), so its intent keeps applying. The other two need a human decision
143+
and are migrated per repo — porting the rules that still make sense, and saying
144+
in a comment which ones were dropped and why.
145+
146+
## Warn-only, by design
147+
148+
`lint.sh` exits 0 no matter what it finds. It is wired into build and publish
149+
hooks across hundreds of repos, so it is built to be incapable of breaking one
150+
unless a human opts in.
151+
152+
To make findings blocking for a single repo, create `.ores-lint/local.sh`:
153+
154+
```sh
155+
ORES_LINT_STRICT=1
156+
```
157+
158+
`local.sh` is yours — the rollout script never overwrites it. Everything else in
159+
this directory is managed and will be replaced on the next rollout.
160+
161+
## Knobs
162+
163+
| variable | default | meaning |
164+
|---|---|---|
165+
| `ORES_LINT_MAX_EXAMPLES` | `5` | example locations shown per rule |
166+
| `ORES_LINT_STRICT` | `0` | `1` makes any finding exit non-zero |
167+
| `ORES_LINT_SKIP_JS` / `ORES_LINT_SKIP_RUST` / `ORES_LINT_SKIP_DART` / `ORES_LINT_SKIP_GLEAM` | `0` | skip one language host |
168+
| `ORES_LINT_SKIP_REQUIRE_SEND` | `0` | skip the cross-language send() scanner |
169+
| `ORES_LINT_REQUIRE_SEND_INCLUDE_TESTS` | `0` | `1` also scans `test/` / `*_test.dart` / etc. |
170+
| `ORES_LINT_RUST_ALL_TARGETS` | `0` | `1` also lints tests/benches/examples |
171+
| `ORES_LINT_RUST_EXTRA` | — | extra flags appended to the clippy invocation |
172+
173+
## Graceful degradation
174+
175+
Nothing here is allowed to fail loudly for an environmental reason. ESLint not
176+
installed, too old, clippy not installed, dart/flutter not installed, gleam not
177+
installed, no TypeScript parser available, crate deps not fetchable — each is
178+
reported as an actionable skip, not an error.
179+
Repo-specific ESLint config that already existed is never overwritten.
180+
The same is true of `analysis_options.yaml`.
181+
182+
CI follows the same model: the workflow runs `npm i -g eslint typescript-eslint`
183+
and never runs `npm install` for the repo itself, so linting a PR does not
184+
require the repo's dependency tree to resolve.
185+
186+
## Per-repo customisation
187+
188+
`eslint.config.mjs` at the repo root takes options:
189+
190+
```js
191+
export default await oresConfig({
192+
requireSend: { loggerNames: ['myLogger'], terminalMethods: ['send', 'flush'] },
193+
rules: { 'no-console': 'warn' },
194+
ignores: ['**/generated/**'],
195+
});
196+
```
197+
198+
Once you edit that file the rollout script leaves it alone.
199+
200+
---
201+
202+
## Fleet operations (from the `codes` directory)
203+
204+
```sh
205+
node .ores-lint-toolkit/audit.mjs # report the fleet's lint posture
206+
node .ores-lint-toolkit/audit.mjs --json out.json # ...as machine-readable data
207+
node .ores-lint-toolkit/rollout.mjs --dry-run # preview
208+
node .ores-lint-toolkit/rollout.mjs # install / re-install everywhere
209+
node .ores-lint-toolkit/rollout.mjs --only ores-otel
210+
node .ores-lint-toolkit/rollout.mjs --shard 0/8 # one slice of a fleet-wide run
211+
node .ores-lint-toolkit/verify.mjs # assert every repo is correctly installed
212+
```
213+
214+
A full rollout over ~900 repos takes a few minutes. `--shard k/n` splits it into
215+
bounded chunks, which matters when the runner has a per-command time limit.
216+
217+
Re-run the rollout after editing anything in `.ores-lint-toolkit/` — it is
218+
idempotent and propagates the change to every repo.

‎.ores-lint/VERSION‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.3.0

‎.ores-lint/config.sh‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/sh
2+
# ores-lint shared configuration. Sourced by lint.sh, js.sh and rust.sh.
3+
# Every value can be overridden from the environment, or per repo in local.sh.
4+
5+
# Maximum number of concrete example locations shown for any one rule.
6+
: "${ORES_LINT_MAX_EXAMPLES:=5}"
7+
8+
# Warn-only by default: lint.sh exits 0 no matter what it finds.
9+
# Flip to 1 (per repo, or in CI) once a repo's debt is paid down.
10+
: "${ORES_LINT_STRICT:=0}"
11+
12+
: "${ORES_LINT_SKIP_RUST:=0}"
13+
: "${ORES_LINT_SKIP_JS:=0}"
14+
: "${ORES_LINT_SKIP_DART:=0}"
15+
: "${ORES_LINT_SKIP_GLEAM:=0}"
16+
: "${ORES_LINT_SKIP_REQUIRE_SEND:=0}"
17+
: "${ORES_LINT_REQUIRE_SEND_INCLUDE_TESTS:=0}"
18+
19+
# How deep to search for nested sub-projects (crates and packages). Repos here
20+
# routinely hold crates under apps/ and clients/ that a root-only lint misses.
21+
: "${ORES_LINT_DEPTH:=5}"
22+
23+
# Include tests/benches/examples in the Rust pass. Off by default so the
24+
# pre-publish signal is about shipped code.
25+
: "${ORES_LINT_RUST_ALL_TARGETS:=0}"
26+
27+
# Minimum ESLint major version. Flat config needs 9+. ESLint is expected to be
28+
# installed GLOBALLY, once - see required-tools.json. Nothing is ever installed
29+
# into a repo's node_modules.
30+
: "${ORES_LINT_ESLINT_MIN_MAJOR:=9}"
31+
32+
# The exact clippy diagnostic text for `clippy::implicit_return`. selftest.sh
33+
# verifies this still matches, so a future clippy rewording surfaces as a test
34+
# failure rather than as a silently empty report.
35+
: "${ORES_LINT_IMPLICIT_RETURN_MSG:=missing \`return\` statement}"
36+
37+
export ORES_LINT_MAX_EXAMPLES ORES_LINT_STRICT ORES_LINT_SKIP_RUST ORES_LINT_SKIP_JS
38+
export ORES_LINT_SKIP_DART ORES_LINT_SKIP_GLEAM ORES_LINT_SKIP_REQUIRE_SEND
39+
export ORES_LINT_REQUIRE_SEND_INCLUDE_TESTS
40+
export ORES_LINT_DEPTH ORES_LINT_RUST_ALL_TARGETS ORES_LINT_ESLINT_MIN_MAJOR
41+
export ORES_LINT_IMPLICIT_RETURN_MSG
42+
43+
# Repo-local overrides, never overwritten by the rollout script. Sourced last so
44+
# anything set here wins.
45+
ORES_LINT_CFG_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
46+
[ -f "$ORES_LINT_CFG_DIR/local.sh" ] && . "$ORES_LINT_CFG_DIR/local.sh"

‎.ores-lint/dart.sh‎

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/bin/sh
2+
# ores-lint :: Dart / Flutter
3+
#
4+
# Uses the Dart analyzer (`dart analyze` / `flutter analyze`) - the universally
5+
# accepted linter for the language, equivalent to ESLint for TypeScript. Custom
6+
# house rules that the analyzer cannot express (require-send) live in
7+
# require-send.mjs and run from lint.sh.
8+
#
9+
# Nothing is installed. Missing dart/flutter is an actionable skip.
10+
11+
set -u
12+
DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
13+
. "$DIR/config.sh"
14+
ROOT=${1:-.}
15+
ROOT=$(CDPATH= cd -- "$ROOT" && pwd)
16+
17+
[ "${ORES_LINT_SKIP_DART}" = "1" ] && { echo "ores-lint[dart]: skipped (ORES_LINT_SKIP_DART=1)"; exit 0; }
18+
19+
has_dart=0
20+
if find "$ROOT" -maxdepth "$ORES_LINT_DEPTH" \
21+
\( -name node_modules -o -name target -o -name .git -o -name vendor -o -name .vendor -o -name build \) -prune -o \
22+
\( -type f -name pubspec.yaml -o -type f -name '*.dart' \) -print 2>/dev/null | head -1 | grep -q .; then
23+
has_dart=1
24+
fi
25+
[ "$has_dart" = "0" ] && exit 0
26+
27+
ANALYZE=""
28+
if command -v dart >/dev/null 2>&1; then
29+
ANALYZE="dart analyze"
30+
elif command -v flutter >/dev/null 2>&1; then
31+
ANALYZE="flutter analyze"
32+
else
33+
echo "ores-lint[dart]: dart/flutter not found on PATH - skipping"
34+
echo " install the Dart SDK, or Flutter, then re-run"
35+
exit 0
36+
fi
37+
38+
# Nested git repos are someone else's analyzer run.
39+
NESTED_FILE="$DIR/nested-repos.json"
40+
EXCLUDE=""
41+
if [ -f "$NESTED_FILE" ]; then
42+
NESTED=$(grep -o '"[^"]*"' "$NESTED_FILE" 2>/dev/null | tr -d '"')
43+
for nrepo in $NESTED; do
44+
[ -n "$nrepo" ] && EXCLUDE="$EXCLUDE --fatal-infos=false"
45+
done
46+
fi
47+
48+
OUT=$(mktemp) || exit 0
49+
RC=0
50+
( cd "$ROOT" && $ANALYZE --format=machine 2>/dev/null || $ANALYZE ) >"$OUT" 2>&1 || RC=$?
51+
52+
if grep -q 'No issues found!' "$OUT"; then
53+
echo "ores-lint[dart]: clean"
54+
rm -f "$OUT"
55+
exit 0
56+
fi
57+
58+
if [ "$RC" -ne 0 ] && ! grep -qE 'error|warning|info|ERROR|WARNING' "$OUT"; then
59+
echo "ores-lint[dart]: analyzer could not run in $ROOT (exit $RC). First lines:"
60+
sed -n '1,6p' "$OUT" | sed 's/^/ | /'
61+
rm -f "$OUT"
62+
exit 0
63+
fi
64+
65+
awk -v MAXEX="$ORES_LINT_MAX_EXAMPLES" '
66+
BEGIN { max = MAXEX + 0; if (max < 1) max = 1; n = 0; FS = "|" }
67+
# machine format: SEVERITY|TYPE|FILE|LINE|COLUMN|LENGTH|CODE|MESSAGE
68+
NF >= 8 && $1 ~ /^(ERROR|WARNING|INFO)$/ {
69+
loc = $3 ":" $4 ":" $5
70+
msg = $7 ": " $8
71+
sev = tolower($1)
72+
if (sev == "info") sev = "warning"
73+
key = loc "|" msg
74+
if (key in seen) next
75+
seen[key] = 1
76+
if (!(msg in count)) { order[++n] = msg; sev_of[msg] = sev }
77+
count[msg]++
78+
if (shown[msg] < max) { ex[msg] = ex[msg] (shown[msg]++ ? "\n" : "") " " loc }
79+
next
80+
}
81+
# human format fallback: " warning - path:line:col - message - code"
82+
{
83+
line = $0
84+
if (match(line, /(error|warning|info) • /) || match(line, /(error|warning|info) - /)) {
85+
n++
86+
raw[++human] = line
87+
}
88+
}
89+
END {
90+
if (n == 0 && human == 0) { print "ores-lint[dart]: clean"; exit 0 }
91+
if (n == 0) {
92+
printf "ores-lint[dart]: %d finding(s)\n", human
93+
limit = (human < max ? human : max)
94+
for (i = 1; i <= limit; i++) print " " raw[i]
95+
if (human > max) printf " ... and %d more\n", human - max
96+
print ""
97+
exit 0
98+
}
99+
total = 0
100+
for (i = 1; i <= n; i++) total += count[order[i]]
101+
printf "ores-lint[dart]: %d finding(s) across %d rule(s)\n", total, n
102+
for (i = 1; i <= n; i++) {
103+
msg = order[i]
104+
printf "\n %s: %s\n", sev_of[msg], msg
105+
printf " %d instance(s); showing %d:\n", count[msg], (count[msg] < max ? count[msg] : max)
106+
print ex[msg]
107+
if (count[msg] > max) printf " ... and %d more\n", count[msg] - max
108+
}
109+
print ""
110+
}
111+
' "$OUT"
112+
113+
rm -f "$OUT"
114+
exit 0

0 commit comments

Comments
 (0)