From 5d5602858fc3869ec19bae407fddec90320e35c9 Mon Sep 17 00:00:00 2001 From: vyuma Date: Fri, 31 Jul 2026 01:18:38 +0900 Subject: [PATCH] docs: demote DESIGN.md to a design record and move the semantic model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README called DESIGN.md authoritative and CONTRIBUTING told contributors to read it before changing implementation code. It still described the design as it stood before the implementation existed, in Python: a 78-line src/manim_lint/*.py tree, phases 0-6 of future work, a `class Rule(Protocol)`, and fix validation via `ast.parse(feature_version=...)` that the bundled fixed grammar cannot provide. An outside contributor is pointed there first and told it outranks everything else, so following it produces wrong work. An authoritative spec written before the code cannot survive, so demote it. DESIGN.md becomes a design record with a header that says so and names what actually governs. The sections that could only misdirect are removed outright: the Python repository layout (§10), the implementation roadmap (§12), and the issue-sized backlog (§13). The catalog, CLI, and test-strategy sections carry superseded notes naming the living documents, and the drifted claims -- the invented `MLR112 > generic portability` supersession, the missing shipped ones, and the feature_version promise -- are corrected against the code. The part worth keeping is the semantic model, so it moves to docs/architecture.md in English, with the legacy section numbers kept in the headings so existing "DESIGN §3.x" citations still resolve. AGENTS.md no longer says to start with Phase 0 or to withhold claims about reserved rules: the catalog has been 92 implemented / 0 reserved since 0.2.0. Records the language decision the issue asked for: authoritative documentation is English. DESIGN.md stays Japanese as the one deliberate exception, because a design record is preserved rather than maintained. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 17 +-- CHANGELOG.md | 26 +++++ CONTRIBUTING.md | 62 ++++++---- DESIGN.md | 262 +++++++++++-------------------------------- README.md | 13 ++- docs/architecture.md | 202 +++++++++++++++++++++++++++++++-- docs/index.md | 5 +- docs/releasing.md | 4 +- docs/rules/README.md | 5 +- 9 files changed, 354 insertions(+), 242 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b93ab38..de0355e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,15 +1,16 @@ # Repository instructions -Before planning or editing implementation code, read `DESIGN.md` completely. It is the authoritative product, semantic-model, rule-catalog and rollout specification for this repository. +Before planning or editing implementation code, read `docs/architecture.md` completely. It is the map of the implementation: the pipeline, the fact layers, the Manim semantic model the rules rest on, and the invariants every change must keep. The rule catalog is `docs/rules/`; the CLI, configuration and JSON contracts are `docs/reference/`, `docs/guides/configuration.md` and `schemas/`. `CONTRIBUTING.md` is the workflow. + +`DESIGN.md` is a historical design record written in Japanese before the implementation existed, when the plan was to write qual in Python. It is not a specification, it is not synchronized with the code, and where the two disagree the code is right. Do not treat it as authoritative and do not update it to match a change. Authoritative documentation in this repository is written in English. A local Manim checkout (`../manim`, or the path in `QUAL_MANIM_ROOT`) is the Manim source reference. Treat it as read-only while working in this repository unless the user separately asks to change Manim itself. Runtime linting must never import or execute Manim or analyzed user code; use static source and versioned knowledge profiles. -Implementation order: +Standing rules: -1. Start with Phase 0 in `DESIGN.md`. -2. Keep the first three commit themes separate: source/CLI contracts, Manim knowledge/name resolution, then high-confidence rules. -3. Do not claim a reserved rule is implemented until its fixtures and acceptance criteria pass. -4. Prefer conservative `Unknown` state over a high-confidence false positive. -5. Keep upstream Manim semantics and the local optimized-fork overlay separate. +1. The rule catalog is finished: 92 implemented, 0 reserved. There is no implementation phase left to work through and no reserved ID waiting to be claimed. A catalog change is a new rule ID or a fix to an existing one. +2. A released rule ID never changes meaning. Splitting a rule means a new ID. +3. Prefer a conservative `Unknown` state over a high-confidence false positive. +4. Keep upstream Manim semantics and the local optimized-fork overlay separate. -When changing a public diagnostic, configuration or JSON contract, update `DESIGN.md`, its schema tests and rule documentation in the same change. Calibration measurements belong in versioned evidence under `docs/research/`, not in machine-independent rule logic. +When changing a public diagnostic, configuration or JSON contract, update the affected documentation under `docs/`, its schema tests and rule documentation in the same change. Calibration measurements belong in versioned evidence under `docs/research/`, not in machine-independent rule logic. diff --git a/CHANGELOG.md b/CHANGELOG.md index cf74505..051ccce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **`DESIGN.md` is a historical design record, not the specification.** It was + written before the implementation existed and described a Python program: + a 78-line `src/manim_lint/*.py` source tree, phases 0–6 of future work, a + `class Rule(Protocol)`, and a fix validator built on + `ast.parse(feature_version=...)` that the bundled fixed grammar cannot + provide. README and `CONTRIBUTING.md` nevertheless called it authoritative + and told contributors to read it first, so following it produced wrong work. + The sections that could only misdirect — the Python repository layout, the + implementation roadmap, and the issue-sized backlog — are removed; the rule + catalog, CLI, and test-strategy sections carry explicit superseded notes + naming what actually governs; and the drifted claims about supersessions and + fix validation are corrected against the code. +- **The Manim semantic model now lives in `docs/architecture.md`, in English.** + It is the part of the design that is still true and load-bearing — the Scene + lifecycle, the exact `Scene.play` state machine, the `dt`-parameter updater + convention, membership versus visibility, and the renderer-specific point + layouts. Legacy section numbers are kept in the headings so existing + "DESIGN §3.x" citations still resolve. +- `README.md`, `CONTRIBUTING.md`, `AGENTS.md`, `docs/index.md`, + `docs/releasing.md`, and `docs/rules/README.md` now point contributors at + documents that match the repository, and `AGENTS.md` no longer directs + anyone to "start with Phase 0" or withhold claims about reserved rules — the + catalog has been 92 implemented / 0 reserved since 0.2.0. +- **Recorded decision: authoritative documentation is written in English.** + `DESIGN.md` stays Japanese as the one deliberate exception, because a design + record is preserved rather than maintained; no contributor needs to read it. - README, CLI, GitHub, crates.io, and PyPI-facing copy now lead with Qual as the Manim-aware linter: render-time errors, visual bugs, and per-frame performance traps before rendering. The README is a concise product entry diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26297e1..2703510 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,17 +1,30 @@ # Contributing to Qual -Thank you for contributing. Two documents outrank this one: - -- [`DESIGN.md`](DESIGN.md) is the **authoritative specification** — product - scope, Manim semantic model, rule catalog, CLI/JSON contracts, and the - implementation invariants. Read it before changing implementation code. -- [`AGENTS.md`](AGENTS.md) states the standing repository rules. The most +Thank you for contributing. Read +[docs/architecture.md](docs/architecture.md) first: it is the map of the +pipeline and the fact layers, it carries the Manim semantic model the rules +rest on, and it ends with the invariants every change must keep. + +The documents that describe the shipped tool are: + +- [docs/architecture.md](docs/architecture.md) — implementation layout, fact + layers, semantic model, invariants. +- [`docs/rules/`](docs/rules/README.md) — the rule catalog. One page per rule + ID with its fixed meaning, severity, and confidence. +- [`docs/reference/cli.md`](docs/reference/cli.md), + [`docs/guides/configuration.md`](docs/guides/configuration.md), and + `schemas/` — the CLI, configuration, and JSON contracts. +- [`AGENTS.md`](AGENTS.md) — the standing repository rules. The most important one: when you change a public diagnostic, configuration, or JSON - contract, update `DESIGN.md`, its schema tests, and the rule documentation - **in the same change**. + contract, update the affected docs and schema tests **in the same change**. -For a guided tour of the pipeline and the fact layers before diving into -code, read [docs/architecture.md](docs/architecture.md). +**Authoritative documentation is written in English.** README, `docs/`, and +this file are the contract; requiring Japanese of a contributor is not. +[`DESIGN.md`](DESIGN.md) is the one exception, and deliberately so: it is a +Japanese-language *design record* from before the implementation existed, +kept for the reasoning behind the semantic model and the invariants. It is +not a specification, it is not synchronized with the code, and you do not +need to read it to contribute. ## Development environment @@ -73,11 +86,13 @@ tests/ integration tests, golden rule fixtures (tests/fixture ## How to add a rule -Every rule ID already exists in the DESIGN §7 catalog with a fixed meaning, -default severity, and minimum confidence. Implementing one: +The catalog is complete: 92 rules implemented, none reserved. A change here +is a new rule ID or a fix to an existing one, never the implementation of a +placeholder. -1. **Read its catalog row** in DESIGN §7.x (and any prose notes below the - table). The `RuleMetadata` you write must match that row exactly: `id`, +1. **Read its catalog page** under [`docs/rules/`](docs/rules/README.md), and + its neighbours in `src/rules/registry.rs`. The `RuleMetadata` you write + must match the documented meaning exactly: `id`, `summary`, `default_severity`, `minimum_confidence`, `implementation_phase`, `required_profiles`, `required_capabilities`, `supersedes`. Do not invent a new ID and do not change the meaning of an @@ -94,8 +109,8 @@ default severity, and minimum confidence. Implementing one: `portability/`). Rules have no visitors of their own; they query the `RuleContext` fact layers (qualified calls, `LifecycleFacts`, `CostFacts`, statement/binding facts, profiles). - **The canonical traversal rule (DESIGN §5.6): no module-root AST walks - in rule code.** If your rule needs a position or binding the facts do + **The canonical traversal rule: no module-root AST walks in rule + code.** If your rule needs a position or binding the facts do not carry yet, promote it into a frontend fact (`src/frontend/statements.rs` or `index.rs`) instead of re-walking the tree; the only acceptable local traversals are fact-anchored (starting @@ -180,8 +195,8 @@ Calibration measurements belong in versioned evidence under ## Corpus labeling -`tests/corpus/manifest-v1.json` is the labeled release corpus (DESIGN -§11.4), enforced by `tests/corpus_gate.rs` on every `cargo test`. Each +`tests/corpus/manifest-v1.json` is the labeled release corpus, enforced by +`tests/corpus_gate.rs` on every `cargo test`. Each case pins: - `path` — the case source under `tests/corpus/`; @@ -203,8 +218,9 @@ case pins: snapshots keep a license note — see `tests/corpus/cases/manim_example_scenes/README.md`). 2. Run the default check over the file **in isolation** and adjudicate - every diagnostic by hand against Manim semantics (the DESIGN §3 - model / the pinned Manim source). A diagnostic you cannot justify as a + every diagnostic by hand against Manim semantics (the semantic model in + [docs/architecture.md](docs/architecture.md) and the pinned Manim + source). A diagnostic you cannot justify as a true positive is a bug to fix first, not a label to record. 3. Add the manifest entry with `label_revision: 1`, the source sha256, and the adjudicated expectations; state the provenance. @@ -229,7 +245,7 @@ Deleting or weakening a `false-positive-guard` case needs the same justification as deleting a regression test: these cases are the pinned form of real review findings. -## Contributor checklist — the DESIGN §15 invariants +## Contributor checklist — the implementation invariants Every change must keep all of these: @@ -257,5 +273,5 @@ The JSON envelope (`schemas/diagnostics-v1.json`), the baseline format (`schemas/baseline-v1.json`), SARIF output, rule IDs and their meanings, exit codes, and the configuration schema are public contracts. A released rule ID never changes meaning. If your change touches any of these, update -`DESIGN.md`, the schema tests, and the affected rule docs in the same -change — a PR that changes a contract in code only will not be accepted. +[`docs/reference/`](docs/reference/cli.md), the schema tests, and the +affected rule docs in the same change — a PR that changes a contract in code only will not be accepted. diff --git a/DESIGN.md b/DESIGN.md index f6e9c14..f90b714 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,9 +1,32 @@ -# Qual 設計書 - -- 状態: 実装済み(v0.2.0、ルール 92/92) -- 対象: Manim Community 0.20 系(実地参照は 2026-07-17 時点の `0.20.1`、基底コミット `4d25c031`。ローカルフォークの未コミット高速化を含む作業ツリーは `local_0_20_1_4d25c031` オーバーレイ側にのみ反映する) -- 実装言語: Rust 2024 edition(rustc 1.85 以上)。本書が Python 実装を前提に書かれた箇所は歴史的経緯であり、現行の実装配置は `docs/architecture.md` と `CONTRIBUTING.md` が正典 -- CLI / Cargo package / PyPI distribution 名: `qual` / `qual` / `qual-manim` +# Qual 設計記録(historical design record) + +> **この文書は仕様書ではない。実装前に書かれた設計記録である。** +> +> 本書は Rust 実装が存在しない時点で、Python 実装を前提に書かれた。内容は +> コードと同期しておらず、同期させる予定もない。実装を変更する前にこれを +> 読む必要はなく、本書と実装が食い違った場合は常に実装側が正しい。 +> +> 現行の正典は次のとおり: +> +> | 知りたいこと | 正典 | +> | --- | --- | +> | 実装配置・fact layer・不変条件 | [`docs/architecture.md`](docs/architecture.md) | +> | Manim の意味モデル(旧 §3) | [`docs/architecture.md`](docs/architecture.md) の "The Manim semantic model" | +> | rule catalog(92 rules) | [`docs/rules/`](docs/rules/README.md) | +> | CLI・設定・JSON 契約 | [`docs/reference/`](docs/reference/cli.md)、[`schemas/`](schemas/) | +> | 作業手順・rule の追加方法 | [`CONTRIBUTING.md`](CONTRIBUTING.md) | +> +> **言語方針**: 正典となる文書は英語で書く(README、`docs/`、`CONTRIBUTING.md`)。 +> 本書が日本語のみであることは、設計記録として保存する以上そのままにする。 +> 英語話者の contributor に本書の読解を要求しない。 +> +> 残した理由: §3 の意味モデルと §4 の記号的コストモデルの導出、および §15 の +> 不変条件がなぜその形なのかという判断過程は、今も価値がある。ただし +> §3 と §15 はすでに `docs/architecture.md` に英語で移してあり、そちらが正典である。 + +- 執筆時点の想定: Manim Community 0.20 系(実地参照は 2026-07-17 時点の `0.20.1`、基底コミット `4d25c031`) +- 執筆時点の想定実装言語: Python(実際の実装は Rust 2024 edition / rustc 1.85 以上) +- 実装状況: 完了。rule catalog は 92 実装 / 0 予約 ## 1. 結論 @@ -380,7 +403,13 @@ Python AST の `col_offset` / `end_col_offset` は UTF-8 byte offset である - token / comment lookup - newline style と source encoding の保持 -fix 適用後は必ず設定した `target-python` で `ast.parse(feature_version=...)` し、失敗したら全 edit を rollback する。標準 `ast` が parse できるのは実行中 interpreter 以下の grammar だけなので、より新しい target を指定した場合は明示的な CLI error にし、同じか新しい Python で linter を実行するよう案内する。 +fix 適用後は必ず再 parse し、失敗したら全 edit を rollback する。 +(実装との差異: これは Python 実装を前提に `ast.parse(feature_version=...)` を +想定して書かれた。Rust 実装が同梱する grammar は rustpython-parser 0.4 の +Python 3.12 固定で、`feature_version` に相当する pin は持たない。`target-python` +は parse の仕方を変えず、parse された構文が target より新しい場合に `MLC000` として +報告する post-parse gate である。`src/reporting/fixes.rs` と +`src/frontend/features.rs` を参照。) ### 5.3 import と名前解決 @@ -703,6 +732,10 @@ JSON v1 の外部 envelope: ## 7. 初期 rule catalog +> **superseded.** 以下は執筆時点で構想した catalog であり、出荷された 92 rules と +> 一対一では対応しない(例: `MLC001` は本表に無いが実装されている)。ID ごとの +> 正典は [`docs/rules/`](docs/rules/README.md) と `src/rules/registry.rs` である。 + 以下は ID を先に予約する。`minimum confidence` は rule が発火できる最低確度であり、default 表示は設定の `min-confidence` に従う。 ### 7.1 lifecycle / correctness @@ -895,7 +928,10 @@ performance rule は次の emission gate と重複排除を持つ。 `MLP209` の位置は root追加順ではなく、family flatten、`z_index` のstable sort、foregroundを反映したCairo effective display orderから計算する。順序がUnknownなら「83個」のような定量値を出さない。 -specificity の優先関係を `RuleMetadata.supersedes` で表す。初期値は `MLP224 > MLP203`、`MLP226 > MLP201`、`MLP220 > MLP204/MLP211`、`MLR112 > generic portability` とする。同じprimary span・同じ根拠なら最も具体的な一件だけを出す。 +specificity の優先関係を `RuleMetadata.supersedes` で表す。(実装との差異: 出荷されている関係は `MLP224 > MLP203`、`MLP226 > MLP201`、 +`MLP220 > MLP204/MLP211`、`MLP208 > MLP207`、`MLR119 > MLR107`、 +`MLD305 > MLR104`。構想した `MLR112 > generic portability` は存在しない。 +正典は `src/rules/registry.rs` の `RuleMetadata::supersedes`。)同じprimary span・同じ根拠なら最も具体的な一件だけを出す。 `MLP214` と `MLP225`、`cairo_fork_workers` / `cairo_static_layers` のfast-path解釈は local fork overlay専用であり、upstream `v0_20` では無効にする。`MLP217` も knowledge profile が同じprocess-global SVG cache semanticsを宣言する場合だけ有効化する。存在しないAPIや設定を提案しない。 @@ -923,6 +959,12 @@ dot.add_updater(lambda m, dt: m.shift(dt * RIGHT)) # 時間基準 ## 8. CLI、設定、抑制 +> **superseded.** CLI flag、設定 key、JSON/SARIF 契約の正典は +> [`docs/reference/cli.md`](docs/reference/cli.md)、 +> [`docs/guides/configuration.md`](docs/guides/configuration.md)、`schemas/`、 +> および `qual --help` である。本節の一覧には出荷済み flag の欠落がある +> (`--analysis-summary` など)。 + ### 8.1 commands ```text @@ -1118,87 +1160,23 @@ componentはproject-local summary dependencyの推移閉包を含むため、こ lookup 時は source key に加えてentryごとのdependency manifestを再計算し、asset の作成・削除・内容変更・case-only path の変化でも必ず miss にする。source bytes は key 作成と解析で同じ snapshot を使う。SQLite WAL を使い、同じ project への並行 cold writer を許容する。entry は lookup / store ごとの単調な access sequenceで、recent 16 whole-project snapshotsとrecent 256 component snapshotsに制限し、store後に古いものを削除する。DBまたは保存JSONのparse破損時はstderrに警告してDBを削除・再構築し、component外の`FileId`を含む構造不正entryはそのentryだけを削除・再構築する。その他のcache I/O failureはその実行だけcacheを無効にし、必要なcomponentまたはfull analysisを続ける。cacheは正しさに必要な状態ではなく、いつでも捨てられる派生物とする。 -## 10. repository layout +## 10. repository layout(削除済み) -```text -pyproject.toml -README.md -DESIGN.md -src/qual/ - __init__.py - __main__.py - cli.py - application.py - source.py - - config/ - model.py - loader.py - - frontend/ - parser.py - imports.py - names.py - index.py - cfg.py - - knowledge/ - model.py - profiles/ - v0_20.json - - semantic/ - values.py - heap.py - state.py - events.py - summaries.py - interpreter.py - - cost/ - model.py - contexts.py - estimator.py - - rules/ - base.py - registry.py - lifecycle.py - rendering.py - performance.py - portability.py - - reporting/ - suppressions.py - text.py - json.py - sarif.py - fixes.py - baseline.py - - cache.py - -docs/rules/ -docs/rfcs/ - 0001-static-facts-v0.md -schemas/ - diagnostics-v1.json - baseline-v1.json - static-facts-v0.json -tools/ - sync_manim_knowledge.py -tests/ - unit/ - integration/ - fixtures/ - rules/ - resolver/ - lifecycle/ - corpus/ -``` +この節は 78 行の Python source tree(`src/qual/*.py`、`tools/`、`tests/unit/`) +だった。実装は Rust であり、記載されたパスは一つも存在しない。誤誘導しかしない +ため本文ごと削除した。実際の配置は +[`docs/architecture.md`](docs/architecture.md) が正典である。 ## 11. テスト戦略 +> **superseded.** fixture の実際の配置は `tests/fixtures/rules//` と +> `tests/rules_*.rs` の golden test であり、本節が書く `branches.py` / +> `expected.json` という名前ではない。§11.4 の release gate(発火候補 200 件の +> 人手 label、precision 98%、95% Wilson 下限 95%)はどのコードも計算していない。 +> 実際に強制されているのは `tests/corpus_gate.rs` の corpus 件数下限と、 +> `CONTRIBUTING.md` が定める labeling protocol である。正典は +> [`CONTRIBUTING.md`](CONTRIBUTING.md) と `tests/`。 + ### 11.1 rule fixtures ```text @@ -1249,116 +1227,12 @@ OpenGL context は test node ごとに fresh process を原則とする。対象 - peak RSS: 300 MiB 未満 - diagnostic order と JSON は同じ入力で byte-stable -## 12. 実装 roadmap - -### Phase 0: 動く骨格 - -- packaging と console script -- `SourceManager` -- config model / loader -- `Diagnostic` / rule registry -- text / JSON reporter -- syntax error diagnostic - -受入条件: `qual check .` が複数ファイルを解析し、安定順で結果を出す。 - -### Phase 1: 名前解決と direct-call rules - -- Manim 0.20 knowledge profile -- aliases と `from manim import *` -- Scene subclass 発見 -- qualified call facts -- direct-call / literal rules: `MLC101`〜`MLC106`, `MLC109`, `MLC122`, `MLC126`, `MLC127`, `MLR101`, `MLR103`〜`MLR106`, `MLR115`, `MLR117`, `MLR124`, `MLR126` - -受入条件: import style を変えても同じ診断になり、対象コードを一度も import しない。 - -### Phase 2: lifecycle abstract interpreter - -- CFG -- allocation-site identity / alias -- Scene membership / order -- parent / submobject -- updater state -- animation begin / introducer / remover / replacement effect -- remaining lifecycle rules `MLC107`〜`MLC129` と state-dependent `MLR102`, `MLR113`, `MLR114`, `MLR116`, `MLR125`, `MLR127` - -受入条件: branch、helper、fluent chain、同時 Animation を含む fixture で expected state と一致する。 - -### Phase 3: cost model - -- hot-context propagation -- run_time / FPS の constant evaluation -- family / points / curves / pixels の symbolic dimensions -- 先に高確度の `MLP201`, `MLP204`, `MLP205`, `MLP217`, `MLP218`, `MLP220`, `MLP226`, `MLP227` -- cardinality 推定が安定してから `MLP202`, `MLP203`, `MLP207`, `MLP208`, `MLP211`, `MLP216` -- `qual cost` の play / frame / family / pixel breakdown - -受入条件: diagnostic に frequency の根拠が表示され、Unknown のとき偽の数値を出さない。 +## 12–13. 実装 roadmap と初期 backlog(削除済み) -### Phase 4: renderer / visual compatibility - -- Cairo / OpenGL profile -- 3D / fixed-in-frame / camera semantics -- asset / font portability -- conditional diagnostics -- nightly differential render CI -- renderer-dependent `MLR107`〜`MLR112`, `MLR118`〜`MLR123` と `MLD301`〜`MLD307` - -### Phase 5: 運用機能 - -- SARIF -- baseline -- SQLite cache -- safe autofix -- 必要になった時点だけ LibCST fixer - -### Phase 6: static semantic toolchain - -P0: - -1. `rfc(static-facts)`: snapshot内ID、source anchor、Unknown taxonomy、責務境界を固定する。 -2. `feat(static-facts)`: rule selection非依存のversioned projectionと決定的JSON producerを公開する。 -3. `feat(dependency-graph)`: cache partitionから独立したsemantic forward/reverse edgeとreason pathを公開する。 -4. `feat(impact)`: before/after snapshotを比較し、削除・renameを含む保守的なScene/play/object候補を返す。 - -P1: - -5. `feat(source-bridge)`: literal引数、既存`.shift(...)`、明確なmethod chain、一意bindingに限定してhash-guarded patch候補を生成する。 -6. `feat(rematch)`: patch後に再parse・再解析し、`Match | Ambiguous | Missing`とcoverage低下を返す。 - -受入条件: 同一helperの複数call context、loop allocationのnon-singleton、`Transform` / `ReplacementTransform`の区別、理由付きdynamic-call Unknown、shared helperの全caller Sceneへの波及、before graphからの削除/rename追跡、incremental/full一致、worker数間byte一致、Shift-JIS/日本語anchor、rule selection非依存、Manim/user code非実行をfixtureで固定する。 - -catalog entry はすべて `implementation_phase` をmetadataに持つ。まだそのphaseへ到達していないIDは `reserved/deferred` として `qual rules` に表示しても、checkでは登録しない。未実装ruleを「検査済み」と見せない。 - -## 13. 最初の issue-sized backlog - -別の Codex は次の順で実装するとよい。一つの巨大 commit に抽象解釈全体を入れない。 - -1. `pyproject.toml`、package、`SourceManager`、diagnostic schema、`check` command。 -2. Manim symbol profile の schema と `v0_20` の最小 subset。 -3. import alias / star import resolver と Scene discovery。 -4. `MLC101` empty play、`MLC102` invalid play arg、`MLC103` bound-method play。 -5. literal evaluator と `MLC104` duration、`MLC106` frozen stop condition。 -6. callback signature model と `MLC105`。 -7. `tokenize` source literal model と `MLR103` TeX escape。 -8. asset resolver と `MLR104`。 -9. event IR と直線的な `construct` interpreter。 -10. Scene membership / introducer / remover tests。 -11. CFG branch join と helper summaries。 -12. animation write-set と `MLC108`。 -13. frequency context と最初の `MLP201`。 -14. symbolic frame count / family size と diagnostic evidence。 -15. Cairo moving suffix estimator。 -16. parent/Scene membership を分離し、`MLC115` の再出現 fixture。 -17. renderer point-layout profile と `MLR112`。 - -最初の三つの commit theme は次に固定する。 - -```text -1. CLI + source/diagnostic contracts -2. Manim knowledge + qualified name resolution -3. first certain/high-confidence rules -``` +この二節は実装前に書かれた phase 0–6 の計画と issue-sized backlog だった。 +catalog は 92 rules / 0 reserved で完了しており、計画としての役割を終えたため +本文ごと削除した。現在の作業手順は [`CONTRIBUTING.md`](CONTRIBUTING.md) が正典で、 +進行中の作業は GitHub issues が唯一の一覧である。 ## 14. 参照すべき Manim source map diff --git a/README.md b/README.md index 3e64d13..7c738c5 100644 --- a/README.md +++ b/README.md @@ -179,9 +179,16 @@ and validate output against the checked-in schemas. ## Contributing -[`DESIGN.md`](DESIGN.md) is the authoritative semantic model, rule catalog, -and rollout specification. [`CONTRIBUTING.md`](CONTRIBUTING.md) explains the -repository layout, adding a rule, test gates, and knowledge-profile updates. +[`docs/architecture.md`](docs/architecture.md) is the map: the pipeline, the +Manim semantic model the rules rest on, and the invariants every change must +keep. [`docs/rules/`](docs/rules/README.md) is the rule catalog and +[`docs/reference/cli.md`](docs/reference/cli.md) the CLI and JSON contracts. +[`CONTRIBUTING.md`](CONTRIBUTING.md) explains the repository layout, adding a +rule, test gates, and knowledge-profile updates. + +[`DESIGN.md`](DESIGN.md) is a historical design record written in Japanese +before the implementation existed. It is not a specification and is not kept +in sync with the code. ```bash cargo fmt --check diff --git a/docs/architecture.md b/docs/architecture.md index b68b419..be38195 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,8 +2,193 @@ This document is the contributor's map of qual: how a Python source tree becomes diagnostics, which fact layer owns what, and where each piece -lives. [`DESIGN.md`](https://github.com/Poietra/qual/blob/main/DESIGN.md) is the authoritative specification (the -section numbers below refer to it); this page is the guided tour. +lives. Together with [`docs/rules/`](rules/README.md) (the rule catalog), +[`docs/reference/`](reference/cli.md) plus `schemas/` (the CLI and JSON +contracts), and [CONTRIBUTING.md](https://github.com/Poietra/qual/blob/main/CONTRIBUTING.md) +(the workflow), it is what a contributor needs. + +[`DESIGN.md`](https://github.com/Poietra/qual/blob/main/DESIGN.md) is **not** +a specification. It is a Japanese-language design record written before the +Rust implementation existed, when the plan was to write qual in Python; it is +not kept in sync with the code, and where the two disagree the code is right. +Older citations of the form "DESIGN §3.2" refer to the semantic model, which +now lives below in English, with the legacy section numbers kept in the +headings so those references still resolve. "DESIGN §15" is the invariant list +at the end of this page. + +## The Manim semantic model + +Everything qual reports rests on modelling what Manim actually does, rather +than on matching API names. `FadeOut(mob)` auto-adds `mob` during play setup +even when it was never added to the scene, and removes it afterwards as a +remover; conversely a one-argument updater is no reason to call a plain +`wait()` dynamic. The analyzer reproduces these state transitions first and +diagnoses second. + +### Scene lifecycle (§3.1) + +```text +module import + | +Scene.__init__ / renderer, camera, file writer setup + | +Scene.render() + |- setup() + |- construct() + | |- object construction / family mutation + | |- add / remove / foreground / fixed-in-frame + | `- zero or more play / wait + |- tear_down() + `- renderer.scene_finished() / partial movie concatenation +``` + +`Scene.render` calls `setup -> construct -> tear_down` in that order. The base +`setup` and `tear_down` are empty, so omitting `super()` in an override only +*means* something when a user-defined intermediate base Scene actually does +work there. Requiring `super()` in every `setup` override would be name +matching; the rule only fires when the resolved base method has an effect. + +### The exact `Scene.play` state machine (§3.2) + +```text +compile arguments + |- an Animation passes through + |- mob.animate... becomes _AnimationBuilder -> Animation + `- anything else is a runtime TypeError + | +apply play kwargs to the animations + | +auto-add non-introducer animation targets not in the Scene family + | +duration = max(animation.run_time) + | +decide whether a Wait is static or dynamic + | +animation._setup_scene(scene) # introducers Scene.add if needed + | +animation.begin() + |- copy starting_mobject + |- normally suspend the live mobject's updaters + |- Transform copies the target and aligns data + `- interpolate(0) + | +determine the moving / static object scope + | +for each time-grid sample + |- updaters of the animation's private objects + |- animation.interpolate(alpha) + |- recursive updaters of the Scene mobjects + |- mesh updaters + |- Scene updaters + |- raster / readback / encode handoff + `- stop_condition + | +animation.finish() # interpolate(1), resume suspended updaters + | +animation.clean_up_from_scene(scene) + |- removers are Scene.remove'd + `- ReplacementTransform does Scene.replace + | +Scene.update_mobjects(0) +``` + +Consequences the rules depend on: + +- `Transform(mob, target)` normally leaves **`mob` itself** in the target's + shape. It does not leave `target` in the scene. +- `ReplacementTransform(source, target)` swaps source for target during + cleanup. +- Introducer and remover membership effects happen at play setup and cleanup, + **not** when the Animation object is constructed. +- Two animations writing the same live family in one play can overwrite each + other; the later interpolation wins. +- An animation's live-mobject updaters are normally suspended, but Scene + updaters, other scene mobjects, and the starting/target copies each follow + different rules. +- `.animate` is not merely deferred syntax. Taking the builder runs + `generate_target()` immediately, so mutating the live object — or building a + second builder for it — between that point and the `play` call can animate a + stale or overwritten target. + +### Frame time and updaters (§3.3) + +The frame times of a normal render are those of +`np.arange(0, run_time, 1 / frame_rate)`. A static estimate may report +`ceil(run_time * fps)`, but never as an *exact* frame count: the boundary is +floating-point. `finish()`'s `alpha=1` produces the final geometry and +normally writes no extra video frame. + +Within one frame the order is: + +```text +Animation.update_mobjects(dt) +Animation.interpolate(alpha) +Scene.update_mobjects(dt) # top-level, recursing into submobjects +Scene.update_meshes(dt) +Scene.update_self(dt) # Scene updaters run last +Renderer.render(...) +``` + +The mobject-updater calling convention has a trap. Manim inspects the +callback's signature for a parameter *named* `dt`; if there is one it calls +`(mobject, dt)`, otherwise `(mobject)`. Merely taking two arguments does not +make a callback time-based. + +`MLC105` mirrors that branch rather than approximating it by parameter count, +and validates positional binding the way `inspect.Signature.bind` would. So +`lambda dt:`, a keyword-only `dt`, and `lambda mob, delta:` are errors, while +defaults, positional-only parameters, and `*args` are accepted as long as the +call really binds. `Scene.add_updater` is a separate contract and always +passes a single `dt`. + +A `wait()` is dynamic — rather than auto-freezing — when any of these is +present: + +- `always_update_mobjects` +- a Scene updater +- a `stop_condition` +- a time-based updater in the Scene family + +With only one-argument updaters, a default `wait()` can render as a still +frame. That is a Manim-specific wrong-picture case the linter reports +explicitly. + +### Scene membership, family, and draw order (§3.4) + +- `Scene.add` is not a set insert. It removes the object first and appends it, + which changes draw order. +- Adding a parent makes its submobjects visible as part of the family. +- `Mobject.add` ignores a duplicate child and refuses direct self-addition. +- A `VMobject` family normally holds only VMobjects; mixing kinds needs a + `Group`. +- `Scene.remove(child)` does **not** edit the parent's `submobjects`. It + rebuilds the Scene's root list without that child, so re-adding or animating + the original parent later makes the child reappear. Temporary removal and + structural removal are distinct. +- Cairo's z-order and source-over compositing make it redraw the suffix from + the first moving or updater-bearing object onward, so a scene-list position + is also a performance fact. +- Foreground objects can widen the moving scope. +- OpenGL costs differ between a retained render plan and the immediate + fallback a custom subclass or callback forces. + +### 3D and fixed objects (§3.5) + +`ThreeDScene.add_fixed_orientation_mobjects` and `add_fixed_in_frame_mobjects` +add the object to the Scene implicitly. The matching remove APIs differ by +renderer: Cairo only unregisters the camera fixing, while the OpenGL branch +also does `Scene.remove` after unfixing. Code assuming the object stays +visible after unfixing is a renderer-portability finding. + +### Geometry storage is not renderer-independent (§3.6) + +The public `set_points_*` APIs and a raw `.points` assignment are not the same +thing. Cairo's `VMobject` stores cubic Béziers at 4 points per curve, while +`OpenGLVMobject` has paths storing quadratic Béziers at 3 points per curve. So +`points.reshape((-1, 4, 3))`, four-point slicing, and a `set_points` that +assumes the Cairo layout can all be OpenGL portability bugs. The point layout +per renderer lives in the knowledge profile, and raw access is only diagnosed +strongly when both the shape and the target renderer are established. ## The pipeline @@ -102,13 +287,14 @@ for provenance: process-global SVG cache). Everything fork-gated is inert under `upstream_0_20`. - `generator.rs` / `src/bin/sync_manim_knowledge.rs` generate reviewable - candidates and drift-check the shipped profiles (the DESIGN §11.2 - layer-9 gate, also `cargo test --test knowledge_drift -- --ignored`). + candidates and drift-check the shipped profiles + (`cargo test --test knowledge_drift -- --ignored`). Humans curate; the tool never writes into the profiles directory. ### Lifecycle interpreter (`src/semantic/`) -The core of the analyzer (DESIGN §3, §5.5–§5.7): an abstract interpreter +The core of the analyzer, and where the semantic model above is enforced: +an abstract interpreter that runs each discovered Scene subclass's `__init__ → setup → construct → tear_down` lifecycle and produces `LifecycleFacts` — per-scene membership/order/updater state, play facts, @@ -177,7 +363,7 @@ schemas define the external contract. ### Cost model (`src/cost/` and `src/render_order.rs`) -`CostFacts` (DESIGN §4): symbolic, evidence-carrying — never fabricated +`CostFacts`: symbolic, evidence-carrying — never fabricated numbers. - `contexts.rs` — hot-context propagation: updaters, `always_redraw`, @@ -210,7 +396,7 @@ their findings enter the shared deterministic supersession/filter/sort pass. Rules have **no visitors of their own**: they query `RuleContext` fact layers. The canonical traversal rule: no module-root AST walks in rule code — needed positions are promoted into -frontend facts instead (DESIGN §5.6). +frontend facts instead. ### Reporting (`src/reporting/`) @@ -303,7 +489,7 @@ evidence would say "per frame" without a number — and if the factory's target could not be proven frame-varying, the rule would stay silent. `qual coverage` then reports that frontier instead of hiding it. -## The invariants that govern contributions (DESIGN §15) +## The invariants that govern contributions 1. Never import or execute the analyzed code or Manim. 2. Never emit a certain/high diagnostic from `Unknown` facts. diff --git a/docs/index.md b/docs/index.md index 6688592..b681135 100644 --- a/docs/index.md +++ b/docs/index.md @@ -78,8 +78,9 @@ selected render profile prove it. - **Performance work:** use the [cost and coverage reports](guides/cost-and-coverage.md). - **Tool builders:** use the versioned [machine APIs](reference/machine-api.md) and [JSON schemas](reference/schemas.md). -- **Contributors:** read the [architecture](architecture.md) and authoritative - [design specification](https://github.com/Poietra/qual/blob/main/DESIGN.md). +- **Contributors:** read the [architecture](architecture.md) — the pipeline, + the Manim semantic model, and the invariants — then + [CONTRIBUTING.md](https://github.com/Poietra/qual/blob/main/CONTRIBUTING.md). !!! info "Current support" diff --git a/docs/releasing.md b/docs/releasing.md index d6e9c72..5b3a3e5 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -53,7 +53,7 @@ claimed by this workflow. Start from a clean `main` checkout after deciding whether the next version is a SemVer patch or minor release. Public diagnostic/configuration/JSON changes still follow the stronger contract-update rules in `AGENTS.md` and -`DESIGN.md`. +`CONTRIBUTING.md`. ```bash git pull --ff-only @@ -74,7 +74,7 @@ python3 scripts/check_release.py --tag v0.2.1 QUAL_MANIM_ROOT=../manim cargo test --test knowledge_drift -- --ignored ``` -The DESIGN §11.4 benchmark gate must also be run on the pinned reference +The benchmark gate must also be run on the pinned reference machine before approving the release PR. The release workflow reruns the portable quality gates and fetches the public pinned Manim commit for upstream knowledge drift. Hosted Actions deliberately selects only the upstream drift diff --git a/docs/rules/README.md b/docs/rules/README.md index 75d0c79..653ac6d 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -1,8 +1,9 @@ # Rule catalog qual defines 92 rule IDs in four families, and all 92 are -**implemented**. The authoritative catalog definition is -[`DESIGN.md`](https://github.com/Poietra/qual/blob/main/DESIGN.md) section 7. +**implemented**. This directory, together with the `RuleMetadata` in +`src/rules/registry.rs`, is the catalog: one page per rule ID with its fixed +meaning, default severity, and minimum confidence. One implemented rule is opt-in: `MLP225` has `default_enabled: false` and the capabilities `cost-report` + `local-fork-overlay` — its home is the