Skip to main content

review

Strict end-of-task review. Use before marking any non-trivial task done. Returns axis-by-axis pass/fail; any FAIL blocks completion. Trigger on user /review or end-of-task self-check.

来源信息

仓库
metabase/metabase-cli
最近来源活动
2026年10月7日 17:29
检测到的 SKILL.md 语言
英语
星标
16
分支
6

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
review
description
Strict end-of-task review. Use before marking any non-trivial task done. Returns axis-by-axis pass/fail; any FAIL blocks completion. Trigger on user /review or end-of-task self-check.
# review Non-deterministic architectural check that complements the deterministic gate (`bun run check` — typecheck, lint, format, unit tests, skill lint). Determinism catches typos; this catches drift. ## When to invoke - After finishing any non-trivial change, before claiming "done." - On user `/review`. ## Inputs 1. Diff scope: - `git diff` (uncommitted) AND - `git diff main...HEAD` if on a feature branch. - If outside a git repo, review uncommitted changes only. 2. `CLAUDE.md`. Read both before forming any judgment. All other rules required for this review are embedded in this skill below — do not depend on any external doc. ## Project layering (memorize) A bun workspace with two packages. `packages/client` (`@metabase/client`) is the private Metabase API client, built by `tsdown` to a `dist/` its `exports` map names; `packages/cli` (`@metabase/cli`) is the publishable CLI built on it, and inlines the client at build time. The e2e tier lives at the repo root under `tests/e2e/` and drives the built binary; unit tests sit beside their source under `packages/<p>/src/**/*.test.ts`. ``` packages/client/src/ index.ts public barrel: client factory, error taxonomy, Credential, parseJson client.ts createClient — composes the resource namespaces onto one transport resources/ one file per Metabase resource; every /api/ path and transport call http/ the HTTP boundary — transport.ts, errors.ts, network-error.ts, response-shape.ts, sanitize.ts, retry.ts, oauth.ts testing/ the shipped test doubles — fake-client.ts, fetch-capture.ts auth/ OAuth login flow: credential.ts, pkce.ts, callback-server.ts, oauth-*.ts version/ tag.ts, probe.ts, known-range.ts (KNOWN_RANGE), profile.ts (ServerProfile), features.ts (FEATURE_RULES — the only file comparing a Metabase major), requirements.ts (METHOD_REQUIREMENTS), requirement-check.ts (checkFeatures), capability-error.ts, refusal.ts (explainer → explain / explainWalk / refuse / refuseAfterReading, explainRefusal) domain/ Metabase API resource Zod schemas + inferred types errors.ts MetabaseError taxonomy, toMetabaseError, isFileNotFoundError, errorMessage, isNonInterruptFailure list.ts ListResult<T> = { data, total } — what a one-response list method returns url.ts normalizeUrl, displayUrl, assertEndpointOrigin — the only home for new URL() json.ts parseJson — the only home for JSON.parse poll.ts pollUntil — the wait-loop home (http/retry.ts drives its own backoff wait) paginate.ts, signal.ts, predicates.ts, json-pointer.ts packages/cli/src/ cli.ts entrypoint; only file allowed to call process.exit main.ts root citty command, lazy subCommands commands/ CLI shell — defineMetabaseCommand, flags, prompts, orchestration core/ pure logic, no CLI deps core/auth/ credential storage + verification core/config.ts flag → env → stored resolver core/env.ts readEnv — every MB_* env name, no raw process.env reads elsewhere core/user-agent.ts USER_AGENT, the CLI's wire identity core/npm-registry.ts the upgrade check's dist-tags lookup — the CLI's only fetch() core/system-ca.ts trustSystemCa — the only node:tls in the repo, a CLI prerogative output/ presentation; takes typed values; renders text or JSON runtime/ platform glue: stdin, csv, yaml, citty helpers ``` Allowed import direction — the package boundary first, then within each package: - **Nothing in the client may import from the CLI** — not by an `@metabase/cli` specifier, not by a relative path that escapes the client's own src tree. The reverse is expected: the CLI consumes the client as `@metabase/client/<path>`. - The client's dependency budget is `zod` + `node:` builtins. Root devDependencies hoist, so the resolver will not reject a third dependency — a production client file importing anything else is FAIL even though it type-checks. - `domain/*` imports only `zod`, sibling `domain/*` files and — for a reader that selects a wire schema by generation (`<resource><Endpoint>Schema(features)`) — `import type { Features } from "../version/features"`. A value import from `version/`, or anything else from elsewhere in either package, is FAIL. - `resources/*` is imported by `client.ts` and by sibling `resources/*` files, and by nothing else. A `domain/`, `http/`, `auth/` or `version/` file importing a resource is FAIL — the direction runs one way, and the permitted importers are an allowlist, so a directory added later is not exempt for having arrived after the rule. - `commands/*` may import `core/*`, `output/*`, `runtime/*`, and any `@metabase/client/*` path. - `core/*` may import `runtime/*` and `@metabase/client/*`. Never `commands/`, never `output/`. - `output/*` may import `runtime/*`, `core/*` except `core/auth/`, and `@metabase/client/*` **except `@metabase/client/http/`** — presentation consumes the error taxonomy from `@metabase/client/errors`, not the HTTP boundary. Never `commands/`. - `runtime/*` may import `@metabase/client/*` (typed errors are the project's pattern: `runtime/body.ts` uses `parseJson` and `ConfigError`, `runtime/yaml.ts` uses `ValidationError`). It must not import from `commands/`, `core/`, or `output/`. ## Domain-file contract Every Metabase API resource in `packages/client/src/domain/<resource-singular>.ts` exports exactly two things per resource — the client's agent-facing contract: 1. **`<Resource>`** — `z.object({ ... }).loose()` for the full API shape, with the type inferred via `z.infer`. `.loose()` is the default so Metabase API additions don't break the parse — tighten over time, don't preempt. (Zod 4 — `.passthrough()` is deprecated; use `.loose()`.) 2. **`<Resource>Compact`** — `<Resource>.pick({ ... }).strip()` projection with its inferred type. The trailing `.strip()` is **mandatory**: `.pick()` on a `.loose()` parent inherits the loose catchall and silently passes every API field through at parse time, which means the "compact" projection isn't actually compact. A diff that lands a Compact missing `.strip()` is FAIL — quote `<Resource>.pick({...})` without trailing `.strip()`. Presentation is CLI-owned, never the client's: the `<resource>View` (`ResourceView<T>` with `compactPick: <Resource>Compact` and `tableColumns: ColumnDef<T>[]`) lives in `packages/cli/src/output/views/<r>.ts`, with any `format:` cell helper colocated there. A `ResourceView`, a `ColumnDef`, a `tableColumns` list, or a terminal-presentation `format:` helper appearing in `packages/client/src/domain/` is FAIL. Inlining a column list inside a command is FAIL. Naming: `<Resource>` is PascalCase (`Card`); the CLI-side `<resource>View` is camelCase (`cardView`). `<Resource>` is the _resource name_, not the file name — a file may host multiple resources (e.g. `domain/user.ts` → `CurrentUser` + `CurrentUserCompact`); the pair contract holds per-resource. Adding a resource is additive: a new `packages/client/src/domain/<r>.ts` for the pair, `packages/client/src/resources/<r>.ts` for the methods, and `packages/cli/src/output/views/<r>.ts` for the view — consumed by commands as `client.<r>.<method>(…)`, `@metabase/client/domain/<r>` and `../../output/views/<r>`. A schema declared in `packages/cli` instead — or a command importing one by a relative path into the client's src tree — is FAIL. Editing existing commands to wire the schema is FAIL — commands import what they need. Do **not** require a fixture + parse-test pair — that is a tautology against Zod itself. The schema is contract-tested by the e2e tier when a command consuming it runs against the live API. A new or modified API resource without both exports, or that omits `.loose()` on the full schema, or that omits `.strip()` after `.pick()` on the Compact, or that hand-rolls `Array<Record<string, unknown>>` / inline `as { ... }` casts in commands instead of consuming `z.infer<typeof Schema>`, is a structural failure. `Record<string, unknown>` is forbidden in command files. The single exemption is `packages/cli/src/output/projection.ts` — the canonical structurally-untyped envelope for dot-path `--fields` projection over already-validated payloads. ## Resource-method contract `packages/client/src/resources/<r>.ts` exports `<r>Resource(transport)` and holds every `/api/` path the client sends. `packages/client/src/client.ts` composes each namespace onto the client, so a resource file with no line in `client.ts` is unreachable and FAIL. Eight conventions bind every method: 1. `client.<resource>.<method>(...)`, namespace named after the API resource (`mb.card`, singular). The resource is what Metabase calls the thing, not what it calls the route: the file and the namespace are `snippet` even though the endpoint is `/api/native-query-snippet`, because the domain schema, the CLI noun and the Metabase concept all read `snippet`. 2. Path parameters positional, then params, then options: `update(id, params, options?)`. 3. Params use Metabase's own field names verbatim — `f`, `model_id`, `include_inactive`. **No mapping layer, no renaming.** 4. Transport concerns (`signal`, `timeoutMs`, `retries`) live in the trailing `options`, never in params. 5. Wire envelope schemas are module-private to `resources/`; they never appear in `domain/`. 6. Methods return domain values, never wire envelopes. Non-paginated lists return `ListResult<T>` = `{ data, total }`. 7. A string path parameter always goes through `encodeURIComponent`. 8. Every method carries the endpoint's description as a doc comment. A method violating any of the eight is FAIL — quote the signature or the path template. Convention 7 is the one with a silent failure mode: an un-escaped string interpolated into a path template lets a `/` or `?` in a setting key or collection ref rewrite the request. A new or changed method comes with its wire test in `packages/client/src/resources/<r>.test.ts` — one `it` per method, asserting URL, verb, headers and body in a single `toEqual` over `capture.calls` from `@metabase/client/testing/fetch-capture`. That test is what pins the path and query vocabulary; a method landed without one is FAIL. ## Version contract `packages/client/src/version/features.ts` is the only production file that compares a Metabase major (`major-comparison-guard.test.ts` scans both packages); `profile.ts` places a version and `tag.ts` parses one. A `.major` comparison or a literal major used as a decision anywhere else is FAIL. Feature names describe a behaviour (`transformTargetTableId`), never a version (`transformV61Shape` is FAIL), and every version-gated rule must flip somewhere inside `KNOWN_RANGE` — `features.test.ts` enforces it, so a diff that raises `KNOWN_RANGE.min` without deleting the rules that went constant is FAIL. Every method in `resources/*.ts` has a matching `METHOD_REQUIREMENTS` entry under its own namespace, strictest feature first (`requirements.test.ts`). A gated method not exported through exactly one of the resource's `explain`, `explainWalk`, `refuse` or `refuseAfterReading` (`explainer(transport, "<ns>")`) is FAIL (`explain-guard.test.ts`). A method refusing before the wire is FAIL unless the refusal guards a parameter an unsupporting server would drop or rewrite without a word, asked through `transport.requireFeatures` or a value check on `transport.preflightServer()` whose refusal is thrown through `transport.refuseBeforeSending` (a refusal thrown directly, or a value check reading `verifiedServer`, is FAIL: the caller could not skip it), or the method is one whose every call such a server would answer wrongly without a word, exported through `refuse` and ending in optional `RequestOptions`, or through `refuseAfterReading` with a reader when it answers from a setting the session properties carry (a `refuse`-wrapped method that reads the session properties itself pays a second request); either way with a comment saying what the server would do; a parameter the server rejects belongs in `explain`'s parameter callback, with the request fields it travels in. A method that chooses what to send from `transport.server()`'s `features` is FAIL — that profile may be a stale cache; send what every server accepts, or decide against `verifiedServer`. A method without an entry, or a command under `packages/cli/src/commands/` declaring `requires` that differs from the `client.<ns>.<method>(` calls in its body (`requires-guard.test.ts`), is FAIL. A `defineMetabaseCommand` declaring a version floor or a token feature is FAIL — the features are derived from `requires`, never declared. Version-driven drift is a `<Resource>WireV<N>` schema plus a converter, selected by a feature and handed to `requestParsed` through an exported `<resource><Endpoint>Schema(features): z.ZodType<Resource>` reader. The canonical schema is the newest server's shape; a field an older server cannot report is `.nullable()`. FAIL: `.optional()` meaning "an older server omits it"; a converter mapping two distinguishable wire states onto one canonical state; a `z.union` of generation shapes parsed by trial (runtime shape detection); a converter that leaves a wire-only field on the canonical value; a discriminated union of generations as a method's public return type; version prose in `packages/cli/skill-data/` where a `requires:` frontmatter key or a `<!-- requires: … -->` … `<!-- /requires -->` fence belongs (markers on their own lines, never inside a table). ## Forbidden patterns and their permitted homes CLAUDE.md declares these as a **separate policy per package**, applied to production source only (`*.test.ts` files are exempt). A pattern's permitted home in one package says nothing about the other: `JSON.parse` has a home in the client and none in the CLI, `fetch` has a home in each but at different paths. The e2e-side restrictions below are part of this review too. ### The client — every file under `packages/client/src/` | Pattern | Permitted only in | | ---------------------------------------------------------------------------------------------- | --------------------------------------- | | bare `fetch(`, `globalThis.fetch`, third-party HTTP libs (`got`/`axios`/`node-fetch`/`undici`) | `http/**` | | `new URL(` | `http/**`, `url.ts` | | `JSON.parse(` | `json.ts` | | `setTimeout` inside an `await` wait loop | `poll.ts`, `http/retry.ts` | | `process.stdout.write`, `process.stderr.write` | nowhere — the client owns no output | | `process.exit(` | nowhere — the client never ends the run | | `@clack/prompts`, `@clack/core` | nowhere — prompting is the CLI's job | | `fs`, `fs/promises`, `node:fs`, `node:fs/promises` | nowhere — the client touches no files | | `child_process`, `node:child_process` | nowhere — the client spawns nothing | | a `.ts` extension in an import specifier | nowhere | | any dependency beyond `zod` and `node:` builtins | nowhere | | an import resolving into `packages/cli` | nowhere | ### The CLI — every file under `packages/cli/src/` | Pattern | Permitted only in |
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看