Commit Graph

27 Commits

Author SHA1 Message Date
Aiden Bai 4eac374026 docs: clarify changeset release levels 2026-07-23 06:32:01 +00:00
Skosh 599e30d9e1 fix: share safe built-in control-flow proof across async rules (#1422)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Aiden Bai <aiden@million.dev>
2026-07-22 04:15:57 -07:00
Aiden Bai 0618ff6fce docs: require explicit release approval 2026-07-17 21:42:43 -07:00
Aiden Bai 71e6da6897 Update AGENTS.md 2026-07-13 18:07:31 -07:00
Rayhan Noufal Arayilakath 88ef7fdeb6 chore: update agents.md instructions
Swap the acknowledgment requirement for a self-revealing supply-chain
awareness banner: read the AGENTS.md your loops pull before you run them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 01:23:29 -07:00
Ray Arayilakath 543ebfeeb8 chore: update agents.md instructions
Added an important requirement for user input acknowledgment.
2026-07-13 02:42:30 -04:00
devin-ai-integration[bot] 8232e96723 fix: deterministic file discovery, build-output exclusion, and --max-duration graceful degradation (#1011)
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
2026-07-02 02:30:56 -07:00
devin-ai-integration[bot] 4728f2da91 FABLE_CLEANUP: fix(action): 20-day audit fixes for the GitHub Action and workflows (#1015)
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
2026-07-02 01:29:50 -07:00
Aiden Bai c72b560682 fix(jsx-key): flag spread placed after key, not key after spread (#958) 2026-06-25 00:42:09 -07:00
Aiden Bai 0e429018a5 Update AGENTS.md 2026-06-24 14:49:53 -07:00
Ray Arayilakath 0c198589d8 refactor(cli): namespace the Sentry wide-event span attributes (#944)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 06:13:00 -04:00
Ray Arayilakath 571ca9c802 feat(skills): add product-thinking runbook for public-surface changes (#748)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 16:30:21 -04:00
Ray Arayilakath e3b106e191 feat(telemetry): emit a per-scan wide event for CI + usage analysis (#660)
Enrich the per-scan Sentry run span with the full run/CI/project/outcome
context (scan mode, score, diagnostics by severity/category, top rule,
lint/dead-code state, and in CI the GitHub event, official-action marker,
forwarded action inputs, and the pull-request gate) so usage and CI behavior
are queryable as spans instead of pre-aggregated counters.

Mint a random per-run runId on the Sentry run context (never a tag or metric
dimension) to correlate the spans of a single run; a workspace invocation's
project scans share one id and per-project span attributes disambiguate. The
wide event's wouldBlock/outcome/exitCode mirror the CLI's real fail-on gate
(ciFailure surface, and no gate in score-only mode). The official GitHub
Action forwards its inputs so action configuration is visible in telemetry.
Telemetry stays anonymized — no repo, owner, username, branch, or path is
sent to Sentry — and --no-score / --no-telemetry opts out.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 00:31:48 -04:00
Rayhan Noufal Arayilakath caf46a775a docs(agents): document GitHub Action versioning scheme
The composite Action is versioned independently from the npm packages via
v-prefixed semver tags (vX.Y.Z) plus a floating major (vN), distinct from
the Changesets `<pkg>@X.Y.Z` tags. Documents the bump rules, the signed-tag
gotcha, and the release/floating-major commands.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 12:20:38 -07:00
Ray Arayilakath 17e722ee07 feat(cli): add anonymized Sentry usage metrics (counters + distributions) (#634)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 09:23:12 -04:00
Ray Arayilakath e9e71bbc2f feat(cli): deepen sentry observability -- source maps, tracing, anonymization, crash refs (#628)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 06:19:25 -04:00
Ray Arayilakath f1913f27f5 chore(skills): add truffler symbol-search tooling and deslop skill (#618)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 21:45:11 -04:00
Aiden Bai 447674d63f refactor(core): wire ambient-config References; drop dead refs/path brands (#569) 2026-05-28 19:42:42 -07:00
Aiden Bai 28da2ae32a 0.2.7 2026-05-26 05:08:47 -07:00
Aiden Bai 99eba624a5 feat(core): wire opt-in OpenTelemetry export via REACT_DOCTOR_OTLP_* (#444)
Adds `layerOtlp` in `core/src/observability.ts` and provides it to
both `inspect()` (CLI) and `diagnose()` (programmatic API). The
layer is a strict no-op unless BOTH env vars are set:

  REACT_DOCTOR_OTLP_ENDPOINT     e.g. https://api.axiom.co
  REACT_DOCTOR_OTLP_AUTH_HEADER  e.g. Bearer <token>

When configured, it provides `Otlp.layerJson({...})` from
`effect/unstable/observability/Otlp` with
`@effect/platform-node/NodeHttpClient.layerUndici` as the
transport. Every existing `Effect.fn("Service.method")` span and
the top-level `Effect.withSpan("runInspect", ...)` ship to the
configured backend without any further wiring.

Mirrors the canonical eval pattern from
`react-doctor-evals/src/Observability.ts → layerAxiom`. Closes the
"Bigger bet #7" item from the prioritized-improvements list.

**No behavior change for default users** — the layer drops to
`Layer.empty` when env vars are absent, so the unconfigured path
has zero network traffic and zero new dependencies at runtime.

Tests cover the no-op path. AGENTS.md and the CLI README document
the opt-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 01:44:17 -07:00
Aiden Bai aa61efae28 refactor: collapse @react-doctor/types and @react-doctor/project-info into core (#440)
* refactor: collapse @react-doctor/types and @react-doctor/project-info into core

Eliminates two private workspace packages whose only role was to
namespace shared TS types and project-discovery helpers — neither
was a real consumer boundary, and the indirection cost a `workspace:*`
edge per leaf package without saving any code.

**Layout**

- `packages/types/` → `packages/core/src/types/` (10 files, pure TS types)
- `packages/project-info/` → `packages/core/src/project-info/`
  (27 files, discoverProject + framework detection + monorepo helpers)
- `packages/types/src/react-native-dependency-names.ts` →
  `packages/oxlint-plugin-react-doctor/src/react-native-dependency-names.ts`
  (the canonical RN-aware-package list lives next to its heaviest
  consumer — the rule gate — and is re-exported from core to avoid
  the rule-plugin ↔ core dependency cycle the previous types package
  was specifically designed to break).

The two packages are deleted from the workspace; every consumer
(`react-doctor`, `api`, `oxlint-plugin-react-doctor`, `actions/review`,
+130 source/test files) now imports from `@react-doctor/core` directly.

**Errors cleanup**

Dropped the legacy plain-class `ReactDoctorError` and `isReactDoctorError`
from `project-info`. The four narrow errors (`ProjectNotFoundError`,
`NoReactDependencyError`, `PackageJsonNotFoundError`,
`AmbiguousProjectError`) now extend `Error` directly — the only valid
hierarchy is the tagged `Schema.TaggedErrorClass` `ReactDoctorError`
from `core/errors.ts`, which has been the canonical one since the
Effect v4 refactor. Added `isProjectDiscoveryError` as a narrow type
guard for the four leaves.

The discovery errors are thrown synchronously BEFORE the Effect
runtime takes over (at `diagnose()` / CLI entry points); inside the
runtime, the Project service's `translateProjectInfoError` converts
each one to its tagged-reason equivalent.

**Schema vs type name collisions**

`schemas.ts` exports `Diagnostic` / `JsonReport` / `JsonReportMode` /
… as Schema.Class values, while `types/` exports the same names as
TS types. The core barrel re-exports the TS-type versions
(broad-strokes API) and exposes the Schema classes via a new
`@react-doctor/core/schemas` subpath export for the two callers
that need them (`Reporter` service, smoke validator, schema tests).

**Validation**

- `pnpm typecheck` ✓
- `pnpm lint` ✓
- `pnpm format` ✓
- `pnpm test` ✓ (1449 passing, 3 skipped — no regressions)
- `pnpm smoke:json-report` ✓

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(core): keep RN-dependency constants out of types barrel to avoid pulling oxlint plugin into discovery

Bugbot caught (medium severity): re-exporting `isReactNativeDependencyName` from
`oxlint-plugin-react-doctor` via the core types barrel meant every consumer of
`@react-doctor/core` (including `discoverProject` / `discoverReactSubprojects`)
would load the entire 286-rule plugin at module-init.

Adds a tiny standalone leaf at `core/src/project-info/internal-rn-dependency-names.ts`
(zero imports, just the constants the workspace-discovery gate needs) and switches
`is-package-json-react-native-aware.ts` to use it. The types barrel no longer
re-exports the plugin-side constants — rule consumers import them directly from
`oxlint-plugin-react-doctor`.

The duplication is intentional and documented at both files; the two lists are
short enough that a future regression test in the plugin can observe the union.

Validated: typecheck + test (1449 pass) + lint + format all green.
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 01:32:44 -07:00
Aiden Bai 1f9ec2b1f8 docs+refactor: adopt eval-canonical Effect v4 patterns; .specs + CONTEXT.md (#437)
Deep study of ~/Developer/react-doctor-evals against our current
architecture. The eval pioneered the Effect v4 patterns this repo
is modeled on; this PR brings over the lasting structural and
observability wins.

### Lasting docs (the main deliverable)

- **`.specs/inspect-pipeline.md`** — full spec for `runInspect`, the
  streaming orchestrator. Documents the 9-service wiring, the Stream
  shape, the failure-folding pattern (Ref + Stream.catchTag), the
  layer-stack override knobs (`--offline` / `--no-lint` / etc.), the
  per-element pipeline, surface filtering, hooks vs services, the
  three-layer call story (`inspect()` → `diagnose()` → `runInspect`),
  and the playbook for adding a new diagnostic source or lint backend.
  Mirrors the eval's `.specs/github-app.md` format — design constraints
  → module map → numbered sections → locked decisions → open follow-ups.
- **`CONTEXT.md`** — domain glossary. Diagnostic, InspectOutput, Service,
  Layer stack, per-element pipeline, surface filter, mode, score,
  composite action, ReactDoctorError vs legacy thrown errors, Context.Reference,
  branded paths. Pairs with AGENTS.md (which is conventions) and
  `.specs/inspect-pipeline.md` (which is the deepest dive).

### `Effect.fn("Service.method")` for free observability

Every non-trivial service-method body is now wrapped in
`Effect.fn("Service.method")` — the canonical eval pattern from
`react-doctor-evals/src/Runner.ts` and `WorkerPool.ts`. Production
cost is zero when no tracer layer is provided. With
`Otlp.layerJson(...)` from `effect/unstable/observability/Otlp`
(eval ref: `Observability.ts → layerAxiom`), users see one named
span per service call.

Wrapped today:
  - `Project.discover` (was `Effect.try`)
  - `Config.resolve` (was direct `Cache.get`)
  - `DeadCode.run` (was `Stream.unwrap(Effect.tryPromise)`)
  - `Linter.run` (was `Stream.unwrap(Effect.gen)`)
  - `Score.compute` (was `Effect.promise`)
  - `runInspect` itself, via `Effect.withSpan("runInspect", { attributes })`
    so the orchestrator is the parent of the per-service-method spans.

### AGENTS.md updates

- New `Effect.fn("Service.method")` rule in the Services section.
- New `Config.redacted` guidance for secret-shaped env vars (will land
  with the first secret-shaped config; the pattern is documented now
  so it lands correctly).
- New `Observability` subsection documenting `Effect.withSpan` +
  `Effect.fn` composition, attribute-key naming convention, and the
  `Otlp.layerJson` opt-in path.

### Not in this PR (tracked for follow-up)

- **Migrate services to canonical `make:` pattern**: the eval uses
  `Context.Service<Self>()("name", { make: Effect.gen(...) }) {
    static readonly layer = Layer.effect(this, this.make)
  }` while we use the shape-in-type-param form
  `Context.Service<Self, Shape>()("name")` + Layer.effect outside.
  Both are valid v4; the eval's form is more idiomatic for services
  that have init work. Defer the wholesale conversion — the current
  shape works and the refactor would churn every service file.
- **Adopt `Layer.mock(Service, { method })` in tests**: cleaner test
  layers than hand-rolled `Layer.succeed(Service, Service.of({ ... }))`.
  Eval pattern from `react-doctor-evals/src/Runner.ts → layerTest`.
- **`Effect.Command` from `effect/unstable/cli`** to replace commander.
  Bigger refactor; tracked for the v0.3 CLI overhaul.
- **`Otlp.layerJson` end-to-end**: the spans are now in place, but
  no actual exporter layer is wired in production. End users that
  want traces would add `Otlp.layerJson({ baseUrl, resource, headers })`
  + `NodeHttpClient.layerUndici` themselves. Document the snippet
  in the README when there's demand.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 19:57:23 -07:00
Aiden Bai 2a02c04203 refactor: adopt v4-native Effect.catchReasons + Effect.try; ban try/catch in Effect.gen (#434)
Deep audit against the Effect v4 patterns documented in
\`tmp/effect/.patterns/\` and \`tmp/effect/migration/\` (the cloned
reference repo). Three real v3-style holdovers fixed:

### 1. Manual instanceof reason dispatch → Effect.catchReasons

\`get-diff-files.ts\`, \`api/diagnose.ts\`, and \`inspect.ts\`'s
\`restoreLegacyThrow\` all had imperative \`switch (reason._tag)\`
ladders behind an \`if (cause instanceof ReactDoctorError)\` JS
catch block. v4 ships \`Effect.catchReasons(errorTag, cases, orElse?)\`
exactly for this — exhaustive, type-safe narrowing of a
\`Schema.TaggedErrorClass\` reason union, no \`instanceof\` checks.

  - \`getDiffInfo\` →
    \`.pipe(Effect.catchReasons("ReactDoctorError", { GitBaseBranchInvalid, GitBaseBranchMissing }))\`
  - \`diagnose\` + \`inspect\`'s legacy translation →
    \`.pipe(Effect.catchReasons("ReactDoctorError", { NoReactDependency, ProjectNotFound, AmbiguousProject }, orElse))\`
    The \`orElse\` branch preserves the legacy "any other tagged
    reason throws as a plain Error with the tagged-class message
    string" contract that grep-stderr callers depend on.

The recovered branches use \`Effect.die(new LegacyClass(...))\` so
the thrown class lands on \`Effect.runPromise\`'s reject channel
unchanged.

### 2. try/catch inside Effect.gen → Effect.try + Effect.orElseSucceed

\`render-summary.ts\`'s "write the dump, swallow if it fails"
block had a literal \`try { ... yield* Console.log(...) } catch {}\`
inside an \`Effect.gen\` — a hard v4 anti-pattern (the catch only
runs for sync throws, never for Effect failures, so the shape is
misleading and the v4 patterns doc forbids it).

Replaced with:

  Effect.try({ try: () => writeDiagnosticsDirectory(...), catch: (e) => e })
    .pipe(Effect.orElseSucceed(() => null))

### 3. AGENTS.md updates

New \`Error dispatch / recovery — v4 idioms\` and \`Generator hygiene\`
subsections in the Effect v4 Conventions block. Documents:
  - When to use \`catchReasons\` / \`catchTag\` / \`catch\` / \`die\`
  - Hard rule: NEVER \`try/catch\` inside \`Effect.gen\` — use
    \`Effect.try\` and \`Effect.orElseSucceed\` / \`Effect.catch\` instead
  - \`return yield* Effect.fail(...)\` for terminal effects
  - When \`Effect.fnUntraced\` is justified (hot paths only; not
    currently needed in this codebase)

### Misc

- Removed now-unused \`isReactDoctorError\` + \`InspectOutput\`
  imports from \`inspect.ts\`.
- Removed unused \`ReactDoctorError\` import from \`api/diagnose.ts\`
  (catchReasons does the narrowing).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 19:02:18 -07:00
Aiden Bai 3b210b6a0d refactor(core+cli): drop custom Logger service; adopt Effect.Console throughout [13/?] (#432)
* refactor: drop custom Logger service; adopt Effect.Console throughout

Pivot to Effect v4's built-in `Console` (`effect/Console`) as the
canonical logging primitive. The custom `Logger` Context.Service and
`LoggerWriter` interface (introduced in PRs #421 / #427) are removed
in favor of Effect's `Console` Context.Reference (default sink:
`globalThis.console`).

### Renderers → Effect-typed

All 6 renderer files now return `Effect.Effect<void>` and use
`yield* Console.log(...)` / `Console.warn(...)` / `Console.error(...)`
instead of taking a `logger: LoggerWriter` parameter:

  - `print-branded-header.ts`        Effect.Effect<void>
  - `render-score-header.ts`         printScoreHeader, printNoScoreHeader,
                                     printBrandingOnlyHeader
  - `render-summary.ts`              printSummary
  - `render-diagnostics.ts`          printDiagnostics (helpers now build
                                     pure string arrays; IO happens at
                                     a single Console.log boundary)
  - `render-project-detection.ts`    printProjectDetection
  - `handle-error.ts`                handleErrorEffect (+ sync façade)

`inspect.ts` yield*s the renderers from inside its existing Effect.gen
program. Silent mode swaps the Console reference for a no-op:
`Effect.provideService(Console.Console, silentConsole)`.

### Imperative CLI helpers → `cliLogger` shim

The non-Effect-typed CLI helpers (resolve-diff-mode, select-projects,
run-explain, install-skill, prompts, exit-gracefully, resolve-fail-on-level,
json-mode, the legacy paths in cli/commands/inspect.ts) import
`cliLogger as logger` from `cli/utils/cli-logger.ts`. The shim is a
thin sync façade that calls `Effect.runSync(Console.X(...))` so every
log line drains into the same Console pipeline as the Effect-typed
renderers.

### JSON-mode silencing

`enableJsonMode` now monkey-patches `globalThis.console.log` /
`error` / `warn` / `info` / `debug` to no-ops at startup. Effect's
Console reference resolves to `globalThis.console` by default, so
this silences every `yield* Console.*` from the orchestrator and
every `cliLogger.*` from the CLI helpers without any conditional
checks at the call sites.

### Files deleted

  - packages/core/src/services/logger.ts
  - packages/core/src/logger.ts
  - packages/core/tests/services/logger.test.ts

### Core: imperative warn calls

`load-config.ts`, `read-ignore-file.ts`, and `resolve-config-root-dir.ts`
were the three core helpers still importing the deprecated `logger`
shim. They now call `Effect.runSync(Console.warn(...))` directly.

### Tests

Two tests previously did `setLoggerSilent(true)` in `beforeEach`.
They now use `silenceConsoleForTest()` from a new test helper
(`tests/helpers/silence-console.ts`) that monkey-patches the global
console for the duration of the test and returns a restore fn.

### AGENTS.md

Updated the Effect v4 Conventions section with a new `Console / logging`
subsection documenting the always-use-Effect.Console rule, the
`cliLogger` bridge for imperative callers, and the silent-mode pattern.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli-logger): restore highlighter coloring for warn/error/info (bugbot)

Bugbot regression on #432: the new `cliLogger` shim and the
Effect-typed `handleErrorEffect` / `inspect.ts` finalize block all
dropped the `highlighter.warn` / `.error` / `.info` calls that the
legacy `consoleLogger` applied to every message. Result: warnings,
errors, and info lines rendered as plain uncolored text.

Restore the color contract:
  - cliLogger.warn  → highlighter.warn (yellow)
  - cliLogger.error → highlighter.error (red)
  - cliLogger.info  → highlighter.info (cyan)
  - handleErrorEffect block → red across every line
  - inspect.ts `Lint checks failed` + `score may be incomplete`
    + `No issues detected, but X failed` → wrapped in highlighter
  - resolve-oxlint-node.ts consoleWarn → highlighter.warn

Effect's `Console.warn` / `Console.error` / `Console.info` route to
the matching `console.X` method but don't add color of their own
(unlike browser DevTools), so the wrapping has to happen at the
caller side.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 18:49:42 -07:00
Aiden Bai c0fa41e97a docs: document Effect v4 patterns + package layout in AGENTS.md (#419)
The runtime is live on main (#405, #410, #411, #412, #414, #417).
Codify the patterns so subsequent agents (and humans) write code that
matches what landed.

## AGENTS.md additions

- Package Layout section enumerating each workspace package's role
- Effect v4 Conventions section covering:
  - Import style (always 'import * as X from "effect/X"', never
    the umbrella 'from "effect"')
  - Errors as tagged Schema.TaggedErrorClass, get message() getters,
    Cause.pretty for opaque causes, _tag-based dispatch over string
    substring checks
  - Services: Context.Service<Self, Interface>()("react-doctor/Name",
    { make }); Effect.fnUntraced for hot paths; Service.of(...);
    Layer.effect vs Layer.succeed; object args when >1 parameter
  - Layer naming conventions (layerNode, layerOf, layerInMemory,
    layerCapture, layerNoop, layerComposite, implementation-specific
    layerOxlint / layerHttp / layerNdjson / layerOra)
  - Schemas: Schema.Class for wire records, Schema.Literals plural for
    unions, Schema.brand via .pipe(), interfaces for arg types to avoid
    runtime encode cost
  - Ambient config via Context.Reference (core/src/refs.ts)
- Testing section pointing at the per-package tests/ dirs and the
  vite-plus/test framework
- Reference reading pointing at tmp/effect/.patterns/ and
  ~/Developer/react-doctor-evals/src/

## TODOS.md additions

New 'Effect v4 runtime follow-ups' section captures the deferred
consolidation work flagged in the per-PR descriptions:

- Collapse @react-doctor/project-info into core (blocked by
  ReactDoctorError name collision between the legacy thrown class
  and the new tagged runtime wrapper — decide rename direction at
  v1.0)
- Collapse @react-doctor/types into core (same collision + circular
  dep risk via oxlint-plugin's use of isReactNativeDependencyName)
- Extract @react-doctor/cli workspace package (mechanical file move
  — ~40 cli/utils/* sites)
- Decompose run-oxlint into Stream combinators (kills the
  Effect.runSync HACK in Linter.layerOxlint)
- Production Reporter layer choice (currently layerNoop because the
  orchestrator returns the array via Stream.runCollect; future LSP /
  watch mode would need a different layer)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 17:02:29 -07:00
Nisarg Patel 638a0663c9 remove(oxlint-plugin-react-doctor): drop design-no-default-tailwind-palette rule
The rule flagged every `indigo-*` / `gray-*` / `slate-*` token in a
className as "the Tailwind template default" and pushed users toward
zinc / neutral / stone (or a brand color). In practice the choice
between those neutral scales is a deliberate brand decision rather
than a code-health signal, and the Radix-Colors carve-out it shipped
with (anchoring the regex to the canonical 50…950 stops) didn't help
teams that customize the default Tailwind palette to a different
numeric scale.

Removes the rule file, the now-unused TAILWIND_DEFAULT_PALETTE_*
/ TAILWIND_PALETTE_UTILITY_PREFIXES constants, the entry in the
generated rule-registry, and the dedicated regression-test describe
block. Also adds an AGENTS.md rule forbidding agents from authoring
changesets or editing CHANGELOG.md — those belong to humans at
release time.

Closes MIL-1543.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 21:25:17 -07:00
Aiden Bai f50426b5ad init 2026-02-13 04:08:01 -08:00