From f516401882464635b21de7254c7865d2490368c4 Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Tue, 7 Jul 2026 14:35:38 +0900 Subject: [PATCH 1/8] Document contribution workflow Move shared contribution guidance into CONTRIBUTING.md and keep AGENTS.md focused on AI-agent workflow. Document issue handling, validation, code conventions, and AI policy references. Assisted-by: Codex:gpt-5.5 --- .pre-commit-config.yaml | 15 ++ AGENTS.md | 295 +++++------------------------- CONTRIBUTING.md | 395 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 459 insertions(+), 246 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a71cd0f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: local + hooks: + - id: cargo-fmt + name: cargo fmt + entry: cargo fmt --all -- --check + language: system + pass_filenames: false + types_or: [rust] + - id: cargo-clippy + name: cargo clippy + entry: cargo clippy --workspace --all-targets -- -D warnings + language: system + pass_filenames: false + types_or: [rust] diff --git a/AGENTS.md b/AGENTS.md index ea8795f..784bb71 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,255 +1,58 @@ # AGENTS.md -## Project role +## Purpose -Gelite is a learning project that is also expected to grow into usable, -production-quality software. Do not treat it as a disposable exercise. The code -may be small, but crate boundaries, type contracts, tests, and documentation -should be chosen as if later work will depend on them. +This file is for AI agents working in this repository. Human and AI +contributors should use [CONTRIBUTING.md](CONTRIBUTING.md) as the main +contribution guide and [AI_POLICY.md](AI_POLICY.md) as the policy for +AI-assisted work. -The project explores a Gel-like query language and schema model implemented in -Rust on top of SQLite. It is not a port of Gel's implementation. Reuse concepts -from Gel where they help, but do not copy its Postgres-specific or -Python/Cython-specific structure. +Before starting work, read: -## Current repository state +- [CONTRIBUTING.md](CONTRIBUTING.md) +- [AI_POLICY.md](AI_POLICY.md) +- the related issue, including branch, scope, and acceptance criteria +- any relevant `spec/` and `plan/` documents named by the issue or affected + code -The repository is past the first empty-scaffold phase. It already contains a -Rust workspace with compiler and tooling crates under `engine/`, `tools/`, and -`tests/`. - -Current implemented areas include: - -- `engine/schema-model`: semantic schema catalog, object types, scalar fields, - links, cardinality, deterministic references, and implicit `id` lookup. -- `engine/schema-parser`: lexer and parser for the current `.geli` schema - syntax. -- `engine/query-ast`: unresolved syntax tree for the supported `select` query - subset. -- `engine/query-parser`: lexer and parser for the current query syntax with - source spans. -- `engine/query-resolver`: AST-to-IR semantic analysis for explicit select - shapes, filters, ordering, and link traversal. -- `engine/query-ir`: backend-independent Semantic IR for select queries. -- `engine/sqlite-query-plan`: SQLite-specific structured select plan. -- `engine/sqlite-query-sqlgen`: SQL renderer for select plans. -- `engine/sqlite-schema-plan`: SQLite-specific initial schema plan. -- `engine/sqlite-schema-sqlgen`: SQL renderer for initial schema DDL and - metadata inserts. -- `engine/sqlite-runner`: runner-facing SQLite schema execution contract. -- `tools/gelite-cli`: top-level command-line binary. -- `tools/gelite-commands`: command orchestration shared by CLI-facing tools. -- `tools/repl`: query pipeline inspection tool. -- `tests/query-pipeline`: cross-crate query pipeline tests. - -The project can parse schema and query sources, plan and apply an initial -SQLite schema, and run the current select subset through CLI/REPL workflows. -It does not yet provide insert/update/delete, migration diffing and migration -history, a full query execution runtime with nested result shaping, an HTTP -server, or a web UI. - -## How to work in this repository - -Act as a corrective pair programmer, not only as an implementation proxy. - -- Explain why a change is needed, not only what changed. -- Point out awkward code, weak abstractions, missing contracts, and spec - mismatches directly. -- Prefer small, reviewable changes over broad rewrites. -- Keep each crate responsible for its own layer. Do not move backend-specific - decisions into syntax or semantic crates. -- Do not compile directly from source text to SQL when the existing staged - pipeline can express the behavior. -- When introducing a concept, give the user one or two practical sentences - about the role it plays in the pipeline. -- If a shortcut is temporary, state why it is temporary and what should replace - it later. - -The user is learning from the implementation. Do not hide important design -decisions behind "it works" changes. At the same time, avoid long theory that -does not connect to the current code. - -## Document priority - -The repository has `spec/` and `plan/` documents. They are not interchangeable. - -- `spec/` defines behavior, meaning, and layer contracts. -- `plan/` defines sequencing, scope control, and implementation rationale. +## Instruction priority When instructions conflict, use this priority: -1. Current user instruction. -2. Relevant `spec/` document. -3. Relevant `plan/` document. -4. Local implementation convenience. - -If implementation must diverge from a spec, report the divergence clearly. -Prefer updating the spec first when the intended meaning has changed. - -## Spec documents - -Read the relevant spec before changing behavior in that area. - -- `spec/schema.md`: minimum schema language, field kinds, `link`, `required`, - `multi`, scalar types, object types, and implicit `id`. -- `spec/query.md`: MVP query language syntax and supported query surface. -- `spec/ir.md`: Semantic IR between AST/resolution and backend planning. -- `spec/storage-sqlite.md`: SQLite physical storage rules, tables, columns, - links, and metadata. -- `spec/sqlite-query-plan.md`: SQLite-specific planning layer between Semantic - IR and SQL generation. - -Specs define contracts. Tests should usually assert these contracts at the -lowest layer that owns them. - -## Plan documents - -Use plan documents to place work in the current build sequence and explain why -the scope is appropriate. - -- `plan/new-db-engine-plan.md`: product scope, MVP boundaries, major - components, and broad build sequence. -- `plan/new-db-engine-design.md`: top-level architecture and component - responsibilities. -- `plan/implementation-start-plan.md`: original first slice for catalog, AST, - IR, and resolver. Useful for intent, but the repository has now moved beyond - parts of this plan. -- `plan/schema-parser-implementation-plan.md`: schema parser sequencing and - parser contracts. -- `plan/query-parser-implementation-plan.md`: query parser sequencing and - parser contracts. -- `plan/query-expression-model-plan.md`: query expression model work. -- `plan/select-path-traversal-plan.md`: select path traversal semantics. -- `plan/sqlite-schema-plan-implementation-plan.md`: SQLite schema planning - implementation sequence. -- `plan/sqlite-query-plan-implementation-plan.md`: SQLite query planner - implementation sequence. -- `plan/cli-and-tooling-plan.md`: CLI, REPL, and tooling workflows. - -Before proposing or implementing a feature, identify which spec defines its -meaning and which plan, if any, explains the intended sequence. - -## Pipeline boundaries - -Preserve the staged pipeline unless there is a deliberate spec or plan change: - -```text -schema/query source - -> lexer/parser - -> AST or schema model - -> resolver / semantic validation - -> backend-independent IR - -> SQLite-specific plan - -> SQL text + bind values - -> runner/runtime behavior -``` - -Keep syntax concerns in parser/AST crates, semantic concerns in model/resolver -and IR crates, SQLite decisions in SQLite planning/sqlgen/runner crates, and -CLI orchestration in `tools/`. - -Examples of boundary violations to avoid: - -- A parser deciding SQLite table or column names. -- SQL generation re-resolving source-level field names. -- `query-ir` depending on SQLite aliasing or join strategy. -- CLI code duplicating parser, planner, or runner logic instead of delegating - to shared command or engine crates. - -## Code change expectations - -Before editing code: - -- Check the relevant spec and plan documents. -- Inspect nearby tests and crate-level APIs. -- Preserve public invariants unless the task explicitly changes them. -- Look for existing naming and error-handling patterns before adding new ones. - -When editing code: - -- Keep changes scoped to the layer that owns the behavior. -- Add or update focused tests when behavior changes. -- Prefer typed representations over stringly typed glue. -- Preserve deterministic ordering where tests or public APIs rely on it. -- Avoid convenience APIs that make invalid schema, query, or plan states easy - to construct. - -When explaining the change: - -- State the contract being protected. -- Mention which test covers the behavior, or why a test was not added. -- Call out remaining unsupported behavior explicitly. - -## Review expectations - -When asked to review, prioritize defects over compliments. Look first for: - -- Spec mismatches. -- Crate boundary leaks. -- Incorrect type or cardinality behavior. -- Resolver and planner behavior that only works for the happy path. -- Names that sound plausible but hide unclear responsibility. -- Tests that assert output shape without checking the semantic contract. -- Over-general abstractions introduced before there are two real users. - -If no issue is found, say so and identify any residual test gaps or assumptions. - -## Documentation style - -Repository documentation should be written in clear, concrete English. It -should record the actual code and design state, not sell the project. - -Prefer: - -- Actual crate, type, and function names. -- Small claims that can be checked against code or tests. -- Explicit unsupported cases. -- Short notes about temporary rules and what will replace them. -- One technical purpose per paragraph. - -Avoid: - -- Generic claims such as "scalable", "robust", "powerful", or "flexible" - unless the mechanism is named. -- Inflated phrasing such as "plays a crucial role", "marks a significant - step", or "underscores the importance". -- Unsupported generalizations such as "best practices suggest". -- Template-like summaries at the end of every section. -- Decorative formatting, unnecessary tables, and forced three-item lists. - -Comments in code should explain boundaries, invariants, or non-obvious reasons. -Do not comment behavior that is already clear from the code. - -## AI-assisted work - -Follow `AI_POLICY.md`. - -Commits that include Codex-assisted design, implementation, review, or -documentation must include this trailer: - -```text -Assisted-by: Codex:gpt-5.5 -``` - -Use professional commit messages. Do not use conversational or character voice -in commits, code, comments, docs, JSON, YAML, SQL, or other artifacts. - -## Validation - -The default full validation command is: - -```sh -cargo test --workspace -``` - -Use narrower tests when they are enough for the change, but run the workspace -tests for changes that affect shared contracts or cross-crate pipeline behavior. - -For CLI behavior, also check the relevant command path, for example: - -```sh -cargo run -p gelite-cli -- --help -cargo run -p gelite-cli -- schema plan examples/blog.geli -``` - -Report any command that could not be run. +1. Repository documents: [AGENTS.md](AGENTS.md), + [CONTRIBUTING.md](CONTRIBUTING.md), [AI_POLICY.md](AI_POLICY.md), and + `.github` templates. +2. The current issue's explicit instructions, including branch, scope, and + acceptance criteria. +3. Relevant `spec/` documents. +4. Relevant `plan/` documents. +5. The current conversation. +6. Local implementation convenience. + +If the current conversation is explicitly about changing repository guidance, +use it as the basis for the document update and make the resulting rule clear +in the changed document. + +## Working rules + +- If an issue gives a branch name, use that exact branch. +- If an issue does not give a branch name, use + `issue--`. +- If scope is unclear, present a plan before editing files. +- Do not treat "handle this issue" as permission to edit files unless the issue + and conversation make the implementation scope clear. +- Keep changes small and reviewable. +- Preserve the staged compiler pipeline described in + [CONTRIBUTING.md](CONTRIBUTING.md). +- Follow [AI_POLICY.md](AI_POLICY.md) for pull request disclosure and commit + trailers. + +## Reporting work + +When summarizing work, state: + +- which issue or task was inspected +- which repository documents were checked +- what changed, if anything +- which validation commands were run +- which commands were not run and why diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..687a6e7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,395 @@ +# Contributing to Gelite + +Gelite is a learning project that is also expected to grow into usable, +production-quality software. The code may be small, but crate boundaries, type +contracts, tests, and documentation should be chosen as if later work will +depend on them. + +The project explores a Gel-like query language and schema model implemented in +Rust on top of SQLite. It is not a port of Gel's implementation. Reuse concepts +from Gel where they help, but do not copy its Postgres-specific or +Python/Cython-specific structure. + +## Repository documents + +Use these files as the source of contribution rules: + +- [CONTRIBUTING.md](CONTRIBUTING.md): contribution workflow, validation, and + code conventions. +- [AGENTS.md](AGENTS.md): additional instructions for AI agents working in this + repository. +- [AI_POLICY.md](AI_POLICY.md): AI assistance disclosure, commit trailers, and + human responsibility. +- [.github/pull_request_template.md](.github/pull_request_template.md): + required pull request structure. +- [.github/ISSUE_TEMPLATE/](.github/ISSUE_TEMPLATE/): issue templates and + required issue fields. +- [spec/](spec/): behavior, meaning, and layer contracts. +- [plan/](plan/): sequencing, scope control, and implementation rationale. + +## Instruction priority + +When instructions conflict, use this priority: + +1. Repository documents: [AGENTS.md](AGENTS.md), + [CONTRIBUTING.md](CONTRIBUTING.md), [AI_POLICY.md](AI_POLICY.md), and + `.github` templates. +2. The current issue's explicit instructions, including branch, scope, and + acceptance criteria. +3. Relevant `spec/` documents. +4. Relevant `plan/` documents. +5. The current conversation. +6. Local implementation convenience. + +If the current conversation is explicitly about changing repository guidance, +use it as the basis for the document update and make the resulting rule clear +in the changed document. + +## Current repository state + +The repository contains a Rust workspace with compiler and tooling crates under +`engine/`, `tools/`, and `tests/`. + +Current implemented areas include: + +- `engine/schema-model`: semantic schema catalog, object types, scalar fields, + links, cardinality, deterministic references, and implicit `id` lookup. +- `engine/schema-parser`: lexer and parser for the current `.geli` schema + syntax. +- `engine/query-ast`: unresolved syntax tree for the supported `select` query + subset. +- `engine/query-parser`: lexer and parser for the current query syntax with + source spans. +- `engine/query-resolver`: AST-to-IR semantic analysis for explicit select + shapes, filters, ordering, and link traversal. +- `engine/query-ir`: backend-independent Semantic IR for select queries. +- `engine/sqlite-query-plan`: SQLite-specific structured select plan. +- `engine/sqlite-query-sqlgen`: SQL renderer for select plans. +- `engine/sqlite-schema-plan`: SQLite-specific initial schema plan. +- `engine/sqlite-schema-sqlgen`: SQL renderer for initial schema DDL and + metadata inserts. +- `engine/sqlite-runner`: runner-facing SQLite schema execution contract. +- `tools/gelite-cli`: top-level command-line binary. +- `tools/gelite-commands`: command orchestration shared by CLI-facing tools. +- `tools/repl`: query pipeline inspection tool. +- `tests/query-pipeline`: cross-crate query pipeline tests. + +The project can parse schema and query sources, plan and apply an initial +SQLite schema, and run the current select subset through CLI/REPL workflows. +It does not yet provide insert/update/delete, migration diffing and migration +history, a full query execution runtime with nested result shaping, an HTTP +server, or a web UI. + +## Issue workflow + +Before changing files for an issue: + +1. Read the full issue body. +2. Check the Branch field. If it is present, use that exact branch. +3. If no branch is specified, create or use + `issue--`. +4. Check the scope, acceptance criteria, related specs, and related plans. +5. Check [.github/pull_request_template.md](.github/pull_request_template.md) + so the eventual PR has the required information. +6. If the issue scope is unclear, write a plan before implementation. + +Do not treat a request to handle an issue as permission to edit files when the +issue or conversation is still ambiguous. Clarify the intended scope first. + +## Document changes + +Documentation changes should describe the actual repository state. Do not use +documentation to advertise unsupported behavior. + +For process or repository guidance changes, update the relevant document as +part of the first step of the work. For example: + +- Update [CONTRIBUTING.md](CONTRIBUTING.md) when the contribution workflow, + branch naming, test expectations, review expectations, or code conventions + change. +- Update [AGENTS.md](AGENTS.md) when AI-agent-only behavior changes. +- Update [AI_POLICY.md](AI_POLICY.md) when AI assistance disclosure or + responsibility rules change. +- Update [README.md](README.md) when user-facing features, examples, or + commands change. + +Documentation-only pull requests may skip `cargo test --workspace` when they do +not change code, examples that are compiled or executed, Cargo metadata, or +documented command behavior. In that case, run `git diff --check`, check links +and file references, and state why code tests were not needed. + +## Spec and plan changes + +The repository has `spec/` and `plan/` documents. They are not interchangeable. + +- `spec/` defines behavior, meaning, and layer contracts. +- `plan/` defines sequencing, scope control, and implementation rationale. + +Update `spec/` when a change affects syntax, query or schema meaning, Semantic +IR shape, SQLite storage rules, planner contracts, SQL generation contracts, or +runtime behavior contracts. + +Update `plan/` when a change affects implementation order, milestone scope, +temporary strategy, or the intended sequence of work. + +If implementation must diverge from a spec, report the divergence clearly. +Prefer updating the spec first when the intended meaning has changed. + +## Relevant specs + +Read the relevant spec before changing behavior in that area. + +- [spec/schema.md](spec/schema.md): minimum schema language, field kinds, + `link`, `required`, `multi`, scalar types, object types, and implicit `id`. +- [spec/query.md](spec/query.md): MVP query language syntax and supported query + surface. +- [spec/ir.md](spec/ir.md): Semantic IR between AST/resolution and backend + planning. +- [spec/storage-sqlite.md](spec/storage-sqlite.md): SQLite physical storage + rules, tables, columns, links, and metadata. +- [spec/sqlite-query-plan.md](spec/sqlite-query-plan.md): SQLite-specific + planning layer between Semantic IR and SQL generation. + +Tests should usually assert spec contracts at the lowest layer that owns them. + +## Relevant plans + +Use plan documents to place work in the current build sequence and explain why +the scope is appropriate. + +- [plan/new-db-engine-plan.md](plan/new-db-engine-plan.md): product scope, MVP + boundaries, major components, and broad build sequence. +- [plan/new-db-engine-design.md](plan/new-db-engine-design.md): top-level + architecture and component responsibilities. +- [plan/implementation-start-plan.md](plan/implementation-start-plan.md): + original first slice for catalog, AST, IR, and resolver. Useful for intent, + but the repository has now moved beyond parts of this plan. +- [plan/schema-parser-implementation-plan.md](plan/schema-parser-implementation-plan.md): + schema parser sequencing and parser contracts. +- [plan/query-parser-implementation-plan.md](plan/query-parser-implementation-plan.md): + query parser sequencing and parser contracts. +- [plan/query-expression-model-plan.md](plan/query-expression-model-plan.md): + query expression model work. +- [plan/select-path-traversal-plan.md](plan/select-path-traversal-plan.md): + select path traversal semantics. +- [plan/sqlite-schema-plan-implementation-plan.md](plan/sqlite-schema-plan-implementation-plan.md): + SQLite schema planning implementation sequence. +- [plan/sqlite-query-plan-implementation-plan.md](plan/sqlite-query-plan-implementation-plan.md): + SQLite query planner implementation sequence. +- [plan/cli-and-tooling-plan.md](plan/cli-and-tooling-plan.md): CLI, REPL, and + tooling workflows. + +## Pipeline boundaries + +Preserve the staged pipeline unless there is a deliberate spec or plan change: + +```text +schema/query source + -> lexer/parser + -> AST or schema model + -> resolver / semantic validation + -> backend-independent IR + -> SQLite-specific plan + -> SQL text + bind values + -> runner/runtime behavior +``` + +Keep syntax concerns in parser/AST crates, semantic concerns in model/resolver +and IR crates, SQLite decisions in SQLite planning/sqlgen/runner crates, and +CLI orchestration in `tools/`. + +Examples of boundary violations to avoid: + +- A parser deciding SQLite table or column names. +- SQL generation re-resolving source-level field names. +- `query-ir` depending on SQLite aliasing or join strategy. +- CLI code duplicating parser, planner, or runner behavior instead of + delegating to shared command or engine crates. + +## Code conventions + +Prefer the patterns already present in the repository. + +- Keep public struct fields private unless there is a clear reason not to. +- Use `new` for simple constructors that cannot fail. +- Use `try_new` and a typed error when construction can reject invalid state. +- Prefer typed values such as `ObjectTypeRef`, `FieldRef`, `Cardinality`, and + `ScalarType` over stringly typed glue. +- Preserve deterministic ordering where tests or public APIs rely on it. +- Avoid convenience APIs that make invalid schema, query, or plan states easy + to construct. +- Keep each crate responsible for its own layer. + +Most `engine/*` crates are `#![no_std]`. Do not add `std`-only requirements to +those crates without a deliberate design change. + +## Errors and panics + +Library and semantic layers should report recoverable failures with typed +errors. Existing examples include `SchemaError`, `ResolveError`, +`ResolvedPathError`, `ParseError`, and `LexError`. + +- Do not panic for invalid user input. +- Avoid `unwrap` in production code. +- Use `expect` only for internal invariants that were already checked nearby, + and include a message explaining the invariant. +- Tests and fixtures may use `expect` and `panic!` when the message explains + the failed contract. +- Do not leave `todo!` or `unimplemented!` on mainline code paths. Represent + intentional unsupported behavior with an explicit error variant. + +## Tests and fixtures + +Put tests at the layer that owns the contract: + +- Parser syntax belongs in parser crate tests. +- Schema invariants belong in `schema-model` or `schema-parser` tests. +- Resolver semantics belong in `query-resolver` tests. +- IR construction invariants belong in `query-ir` tests. +- SQLite physical planning belongs in `sqlite-query-plan` tests. +- SQL rendering belongs in `sqlite-query-sqlgen` or `sqlite-schema-sqlgen` + tests. +- Cross-crate behavior belongs in `tests/query-pipeline`. + +Use `src/tests/fixtures.rs` when setup is shared by multiple tests. A repeated +setup used by two or more tests is a fixture candidate, but do not hide the +assertion being tested behind a fixture helper. + +Raw SQL fixtures are acceptable only when the Gelite pipeline for that behavior +does not exist yet. Add a short comment that explains the temporary rule and +what should replace it later. + +## Dependencies + +Add dependencies conservatively. + +- Do not add a dependency when the standard library or an existing dependency + is sufficient. +- Keep backend and tooling dependencies out of syntax, semantic, and IR crates. +- Add CLI-only dependencies under `tools/*`, not engine crates. +- Distinguish runtime dependencies from dev-dependencies. +- Check `no_std` compatibility before adding a dependency to an engine crate + that currently uses `#![no_std]`. +- In the pull request, explain why the dependency is needed and which crate owns + the need. + +## Rust version + +The workspace uses Rust edition 2024 through `[workspace.package]` in +`Cargo.toml`. The repository does not currently declare an MSRV with +`rust-version`. + +Until an MSRV is declared, validate changes with the current stable Rust +toolchain available to the contributor. + +## Documentation style + +Repository documentation should be written in clear, concrete English. It +should record the actual code and design state, not sell the project. + +Prefer: + +- Actual crate, type, and function names. +- Small claims that can be checked against code or tests. +- Explicit unsupported cases. +- Short notes about temporary rules and what will replace them. +- One technical purpose per paragraph. + +Avoid: + +- Generic claims such as "scalable", "robust", "powerful", or "flexible" + unless the mechanism is named. +- Inflated phrasing such as "plays a crucial role", "marks a significant + step", or "underscores the importance". +- Unsupported generalizations such as "best practices suggest". +- Template-like summaries at the end of every section. +- Decorative formatting, unnecessary tables, and forced three-item lists. + +Comments in code should explain boundaries, invariants, or non-obvious reasons. +Do not comment behavior that is already clear from the code. + +Every crate should have crate-level `//!` docs that state its responsibility and +non-goals. Public types and functions should be documented when they define a +layer contract, invariant, error condition, or non-obvious behavior. Do not add +noise documentation to simple getters. + +## Validation + +Run focused tests while developing. Run the workspace tests for changes that +affect shared contracts or cross-crate pipeline behavior. + +Pull request validation is enforced by the Rust workflow in +[.github/workflows/rust.yml](.github/workflows/rust.yml). Local hooks are a +convenience only; CI is the authoritative validation gate. + +Default full validation: + +```sh +cargo test --workspace +``` + +Useful command checks: + +```sh +cargo run -p gelite-cli -- --help +cargo run -p gelite-cli -- schema plan examples/blog.geli +``` + +For formatting and lint checks: + +```sh +cargo fmt --all -- --check +cargo clippy --workspace --all-targets -- -D warnings +``` + +Report any command that could not be run. + +## Pre-commit hooks + +This repository provides a `.pre-commit-config.yaml` for local convenience. +Pre-commit hooks do not replace pull request validation and can be bypassed +locally. The required checks are the Rust workflow checks. + +Install and run hooks with: + +```sh +pre-commit install +pre-commit run --all-files +``` + +The hooks check Rust formatting and Clippy warnings. If the hook environment is +not available, run the equivalent Cargo commands manually and report that the +pre-commit hook itself was not run. + +## Pull requests + +Use [.github/pull_request_template.md](.github/pull_request_template.md). + +A pull request should: + +- state the behavior or documentation contract it changes +- mention the issue it closes +- list the relevant `spec/` and `plan/` files checked +- describe the main implementation choices +- list the validation commands that were run +- identify unsupported behavior or follow-up work +- disclose AI assistance according to [AI_POLICY.md](AI_POLICY.md) + +If a pull request changes behavior, include tests at the layer that owns the +contract. If a pull request only changes documentation or repository metadata, +explain why code tests were not needed. + +## AI-assisted work + +AI tools are allowed in this repository, but AI output is not a substitute for +understanding, testing, or maintainership. Follow [AI_POLICY.md](AI_POLICY.md). + +Commits that include Codex-assisted design, implementation, review, or +documentation must include this trailer: + +```text +Assisted-by: Codex:gpt-5.5 +``` + +Use professional commit messages. Do not use conversational or character voice +in commits, code, comments, docs, JSON, YAML, SQL, or other artifacts. From 0dbaa810e04f6726e8d4906c4efd466f89ae9f4f Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Tue, 7 Jul 2026 14:36:04 +0900 Subject: [PATCH 2/8] Add Rust validation workflow Run formatting, Clippy, and workspace tests for pushes and pull requests targeting main. Assisted-by: Codex:gpt-5.5 --- .github/workflows/rust.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..90d449c --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,37 @@ +name: Rust + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v6 + + - name: Install stable Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache cargo dependencies + uses: Swatinem/rust-cache@v2 + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + + - name: Run tests + run: cargo test --workspace From 8cd919e1fd784220e84a442065c7d61f4c5a019f Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Tue, 7 Jul 2026 14:36:22 +0900 Subject: [PATCH 3/8] Update .cspell.json --- .cspell.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cspell.json b/.cspell.json index 87b356e..af96a52 100644 --- a/.cspell.json +++ b/.cspell.json @@ -1,4 +1,4 @@ { "version": "0.1", - "words": ["sqlgen"] + "words": ["sqlgen", "gelite"] } From be428ec5f7820b8a57d3a1129aa1256833a4eac6 Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Tue, 7 Jul 2026 18:18:48 +0900 Subject: [PATCH 4/8] Add contribution workflow examples Document concrete workflows for documentation-only changes, query syntax updates, resolver behavior, SQLite planning, and AI-assisted work. Assisted-by: Codex:gpt-5.5 --- CONTRIBUTING.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 687a6e7..83a121b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,78 @@ Before changing files for an issue: Do not treat a request to handle an issue as permission to edit files when the issue or conversation is still ambiguous. Clarify the intended scope first. +## Example workflows + +Use these examples as starting points. If an issue gives different scope or +acceptance criteria, follow the issue and explain the difference in the pull +request. + +### Documentation-only issue + +1. Read the issue body and use the branch named in the issue. +2. Check whether the change affects `CONTRIBUTING.md`, `AGENTS.md`, + `AI_POLICY.md`, `README.md`, `spec/`, or `plan/`. +3. Update the relevant document. +4. Run: + + ```sh + git diff --check + ``` + +5. If no code, examples, Cargo metadata, or documented command behavior changed, + do not run `cargo test --workspace`. State the reason in the pull request. +6. Fill [.github/pull_request_template.md](.github/pull_request_template.md). + +### Query syntax change + +1. Read the issue, [spec/query.md](spec/query.md), and the relevant plan + document. +2. Update [spec/query.md](spec/query.md) first if the syntax or meaning + changes. +3. Update parser tests in `engine/query-parser`. +4. Update AST, resolver, IR, planner, or SQL generation only if the new syntax + reaches those layers. +5. Add pipeline tests in `tests/query-pipeline` only when cross-crate behavior + changes. +6. Run focused tests first, then: + + ```sh + cargo test --workspace + ``` + +### Resolver behavior change + +1. Read the issue, [spec/query.md](spec/query.md), and [spec/ir.md](spec/ir.md). +2. Confirm the parser already represents the syntax needed for the change. +3. Update `engine/query-resolver`. +4. Add resolver tests for success and failure paths. +5. Update `engine/query-ir` only if the backend-independent meaning changes. +6. Run the relevant resolver tests, then `cargo test --workspace`. + +### SQLite planning change + +1. Read [spec/ir.md](spec/ir.md), + [spec/storage-sqlite.md](spec/storage-sqlite.md), and + [spec/sqlite-query-plan.md](spec/sqlite-query-plan.md). +2. Keep SQLite table, column, alias, and join decisions inside SQLite-specific + crates. +3. Update `engine/sqlite-query-plan` or `engine/sqlite-query-sqlgen`. +4. Add tests at the planning or SQL rendering layer. +5. Add `tests/query-pipeline` coverage only if behavior across the full + pipeline changes. + +### AI-assisted change + +1. Read [AI_POLICY.md](AI_POLICY.md). +2. Use AI output as a draft, not as authority. +3. Verify the change against the relevant spec, plan, tests, and code. +4. Disclose AI usage in the pull request template. +5. Add the required commit trailer: + + ```text + Assisted-by: Codex:gpt-5.5 + ``` + ## Document changes Documentation changes should describe the actual repository state. Do not use From dcacb6cd84a0596d3d40cd0aeed51442f348ea3f Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Tue, 7 Jul 2026 21:58:54 +0900 Subject: [PATCH 5/8] Fix nested if let statement --- engine/sqlite-query-plan/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/sqlite-query-plan/src/lib.rs b/engine/sqlite-query-plan/src/lib.rs index 832fa4d..2daa63c 100644 --- a/engine/sqlite-query-plan/src/lib.rs +++ b/engine/sqlite-query-plan/src/lib.rs @@ -628,11 +628,11 @@ fn selected_link_aliases(shape: &query_ir::ResolvedShape) -> Vec { fn collect_selected_link_aliases(shape: &query_ir::ResolvedShape, aliases: &mut Vec) { for item in shape.items() { - if let query_ir::ResolvedShapeItem::Field(field) = item { - if let Some(child_shape) = field.child_shape() { - aliases.push(field.output_name().to_string()); - collect_selected_link_aliases(child_shape, aliases); - } + if let query_ir::ResolvedShapeItem::Field(field) = item + && let Some(child_shape) = field.child_shape() + { + aliases.push(field.output_name().to_string()); + collect_selected_link_aliases(child_shape, aliases); } } } From 5dc6f6d84edc52dbb49845b1e36d695a8d55a09d Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Tue, 7 Jul 2026 22:06:52 +0900 Subject: [PATCH 6/8] cargo clippy --- engine/schema-parser/src/parser.rs | 8 +- engine/sqlite-query-sqlgen/src/lib.rs | 10 +- engine/sqlite-schema-plan/src/tests/mod.rs | 176 ++++++++++----------- tools/gelite-cli/src/main.rs | 10 +- tools/repl/src/lib.rs | 45 ++++-- 5 files changed, 131 insertions(+), 118 deletions(-) diff --git a/engine/schema-parser/src/parser.rs b/engine/schema-parser/src/parser.rs index e850c71..ee6a89a 100644 --- a/engine/schema-parser/src/parser.rs +++ b/engine/schema-parser/src/parser.rs @@ -1,4 +1,5 @@ use crate::{Keyword, LexError, Span, Token, TokenKind, lex}; +use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; use schema_model::{ @@ -23,13 +24,16 @@ fn parse_schema_tokens(tokens: &[Token]) -> Result, span: Option, } impl ParseError { fn new(kind: ParseErrorKind, span: Option) -> Self { - Self { kind, span } + Self { + kind: Box::new(kind), + span, + } } pub fn kind(&self) -> &ParseErrorKind { diff --git a/engine/sqlite-query-sqlgen/src/lib.rs b/engine/sqlite-query-sqlgen/src/lib.rs index 2fd254d..e9a96a2 100644 --- a/engine/sqlite-query-sqlgen/src/lib.rs +++ b/engine/sqlite-query-sqlgen/src/lib.rs @@ -352,19 +352,13 @@ fn render_order_clause( fn render_limit_clause(plan: &SQLiteSelectPlan) -> Option { let limit = plan.limit(); - match limit { - None => None, - Some(val) => Some(format!("LIMIT {val}")), - } + limit.map(|val| format!("LIMIT {val}")) } fn render_offset_clause(plan: &SQLiteSelectPlan) -> Option { let offset = plan.offset(); - match offset { - None => None, - Some(val) => Some(format!("OFFSET {val}")), - } + offset.map(|val| format!("OFFSET {val}")) } /// Rendered SQLite select statement. diff --git a/engine/sqlite-schema-plan/src/tests/mod.rs b/engine/sqlite-schema-plan/src/tests/mod.rs index 420ad16..d59b193 100644 --- a/engine/sqlite-schema-plan/src/tests/mod.rs +++ b/engine/sqlite-schema-plan/src/tests/mod.rs @@ -44,15 +44,15 @@ fn initial_schema_plan_defines_catalog_objects_metadata_table() { let columns = plan.metadata_tables()[1].columns(); assert_eq!(columns[0].name(), "object_id"); assert_eq!(columns[0].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[0].is_nullable(), false); - assert_eq!(columns[0].is_primary_key(), true); - assert_eq!(columns[0].is_unique(), true); + assert!(!columns[0].is_nullable()); + assert!(columns[0].is_primary_key()); + assert!(columns[0].is_unique()); assert_eq!(columns[1].name(), "name"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), false); - assert_eq!(columns[1].is_primary_key(), false); - assert_eq!(columns[1].is_unique(), true); + assert!(!columns[1].is_nullable()); + assert!(!columns[1].is_primary_key()); + assert!(columns[1].is_unique()); } #[test] @@ -66,27 +66,27 @@ fn initial_schema_plan_defines_schema_versions_metadata_table() { let columns = plan.metadata_tables()[0].columns(); assert_eq!(columns[0].name(), "version_id"); assert_eq!(columns[0].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[0].is_nullable(), false); - assert_eq!(columns[0].is_primary_key(), true); - assert_eq!(columns[0].is_unique(), true); + assert!(!columns[0].is_nullable()); + assert!(columns[0].is_primary_key()); + assert!(columns[0].is_unique()); assert_eq!(columns[1].name(), "checksum"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), false); - assert_eq!(columns[1].is_primary_key(), false); - assert_eq!(columns[1].is_unique(), false); + assert!(!columns[1].is_nullable()); + assert!(!columns[1].is_primary_key()); + assert!(!columns[1].is_unique()); assert_eq!(columns[2].name(), "applied_at"); assert_eq!(columns[2].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[2].is_nullable(), false); - assert_eq!(columns[2].is_primary_key(), false); - assert_eq!(columns[2].is_unique(), false); + assert!(!columns[2].is_nullable()); + assert!(!columns[2].is_primary_key()); + assert!(!columns[2].is_unique()); assert_eq!(columns[3].name(), "schema_snapshot"); assert_eq!(columns[3].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[3].is_nullable(), false); - assert_eq!(columns[3].is_primary_key(), false); - assert_eq!(columns[3].is_unique(), false); + assert!(!columns[3].is_nullable()); + assert!(!columns[3].is_primary_key()); + assert!(!columns[3].is_unique()); } #[test] @@ -100,57 +100,57 @@ fn initial_schema_plan_defines_catalog_fields_metadata_table() { let columns = plan.metadata_tables()[2].columns(); assert_eq!(columns[0].name(), "object_id"); assert_eq!(columns[0].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[0].is_nullable(), false); - assert_eq!(columns[0].is_primary_key(), false); - assert_eq!(columns[0].is_unique(), false); + assert!(!columns[0].is_nullable()); + assert!(!columns[0].is_primary_key()); + assert!(!columns[0].is_unique()); assert_eq!(columns[1].name(), "field_id"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[1].is_nullable(), false); - assert_eq!(columns[1].is_primary_key(), false); - assert_eq!(columns[1].is_unique(), false); + assert!(!columns[1].is_nullable()); + assert!(!columns[1].is_primary_key()); + assert!(!columns[1].is_unique()); assert_eq!(columns[2].name(), "name"); assert_eq!(columns[2].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[2].is_nullable(), false); - assert_eq!(columns[2].is_primary_key(), false); - assert_eq!(columns[2].is_unique(), false); + assert!(!columns[2].is_nullable()); + assert!(!columns[2].is_primary_key()); + assert!(!columns[2].is_unique()); assert_eq!(columns[3].name(), "field_kind"); assert_eq!(columns[3].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[3].is_nullable(), false); - assert_eq!(columns[3].is_primary_key(), false); - assert_eq!(columns[3].is_unique(), false); + assert!(!columns[3].is_nullable()); + assert!(!columns[3].is_primary_key()); + assert!(!columns[3].is_unique()); assert_eq!(columns[4].name(), "cardinality"); assert_eq!(columns[4].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[4].is_nullable(), false); - assert_eq!(columns[4].is_primary_key(), false); - assert_eq!(columns[4].is_unique(), false); + assert!(!columns[4].is_nullable()); + assert!(!columns[4].is_primary_key()); + assert!(!columns[4].is_unique()); assert_eq!(columns[5].name(), "scalar_type"); assert_eq!(columns[5].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[5].is_nullable(), true); - assert_eq!(columns[5].is_primary_key(), false); - assert_eq!(columns[5].is_unique(), false); + assert!(columns[5].is_nullable()); + assert!(!columns[5].is_primary_key()); + assert!(!columns[5].is_unique()); assert_eq!(columns[6].name(), "target_object_id"); assert_eq!(columns[6].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[6].is_nullable(), true); - assert_eq!(columns[6].is_primary_key(), false); - assert_eq!(columns[6].is_unique(), false); + assert!(columns[6].is_nullable()); + assert!(!columns[6].is_primary_key()); + assert!(!columns[6].is_unique()); assert_eq!(columns[7].name(), "is_implicit"); assert_eq!(columns[7].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[7].is_nullable(), false); - assert_eq!(columns[7].is_primary_key(), false); - assert_eq!(columns[7].is_unique(), false); + assert!(!columns[7].is_nullable()); + assert!(!columns[7].is_primary_key()); + assert!(!columns[7].is_unique()); assert_eq!(columns[8].name(), "is_unique"); assert_eq!(columns[8].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[8].is_nullable(), false); - assert_eq!(columns[8].is_primary_key(), false); - assert_eq!(columns[8].is_unique(), false); + assert!(!columns[8].is_nullable()); + assert!(!columns[8].is_primary_key()); + assert!(!columns[8].is_unique()); let primary_key = plan.metadata_tables()[2].primary_key().unwrap(); assert_eq!(primary_key.column_names().len(), 2); @@ -209,16 +209,16 @@ fn initial_schema_plan_creates_object_table_for_scalar_fields() { let columns = user.columns(); assert_eq!(columns[0].name(), "id"); assert_eq!(columns[0].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[0].is_nullable(), false); - assert_eq!(columns[0].is_primary_key(), true); + assert!(!columns[0].is_nullable()); + assert!(columns[0].is_primary_key()); assert_eq!(columns[1].name(), "name"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), false); + assert!(!columns[1].is_nullable()); assert_eq!(columns[2].name(), "age"); assert_eq!(columns[2].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[2].is_nullable(), true); + assert!(columns[2].is_nullable()); } #[test] @@ -315,18 +315,18 @@ fn initial_schema_plan_creates_required_single_link_foreign_key_column() { let columns = post.columns(); assert_eq!(columns[0].name(), "id"); assert_eq!(columns[0].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[0].is_nullable(), false); - assert_eq!(columns[0].is_primary_key(), true); + assert!(!columns[0].is_nullable()); + assert!(columns[0].is_primary_key()); assert_eq!(columns[1].name(), "title"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), false); - assert_eq!(columns[1].is_primary_key(), false); + assert!(!columns[1].is_nullable()); + assert!(!columns[1].is_primary_key()); assert_eq!(columns[2].name(), "author_id"); assert_eq!(columns[2].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[2].is_nullable(), false); - assert_eq!(columns[2].is_primary_key(), false); + assert!(!columns[2].is_nullable()); + assert!(!columns[2].is_primary_key()); assert_eq!(post.foreign_keys().len(), 1); @@ -365,8 +365,8 @@ fn initial_schema_plan_creates_optional_single_link_foreign_key_column() { let columns = post.columns(); assert_eq!(columns[1].name(), "author_id"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), true); - assert_eq!(columns[1].is_primary_key(), false); + assert!(columns[1].is_nullable()); + assert!(!columns[1].is_primary_key()); assert_eq!(post.foreign_keys().len(), 1); @@ -398,14 +398,14 @@ fn schema_scalar_field_can_be_marked_unique() { let columns = user.columns(); assert_eq!(columns[0].name(), "id"); assert_eq!(columns[0].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[0].is_nullable(), false); - assert_eq!(columns[0].is_primary_key(), true); - assert_eq!(columns[0].is_unique(), true); + assert!(!columns[0].is_nullable()); + assert!(columns[0].is_primary_key()); + assert!(columns[0].is_unique()); assert_eq!(columns[1].name(), "email"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), false); - assert_eq!(columns[1].is_unique(), true); + assert!(!columns[1].is_nullable()); + assert!(columns[1].is_unique()); } #[test] @@ -438,8 +438,8 @@ fn initial_schema_plan_allows_optional_unique_scalar_field() { let columns = user.columns(); assert_eq!(columns[1].name(), "nickname"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), true); - assert_eq!(columns[1].is_unique(), true); + assert!(columns[1].is_nullable()); + assert!(columns[1].is_unique()); } #[test] @@ -473,9 +473,9 @@ fn initial_schema_plan_marks_required_unique_single_link_column() { let columns = profile.columns(); assert_eq!(columns[1].name(), "user_id"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), false); - assert_eq!(columns[1].is_primary_key(), false); - assert_eq!(columns[1].is_unique(), true); + assert!(!columns[1].is_nullable()); + assert!(!columns[1].is_primary_key()); + assert!(columns[1].is_unique()); assert_eq!(profile.foreign_keys().len(), 1); @@ -516,9 +516,9 @@ fn initial_schema_plan_marks_optional_unique_single_link_column() { let columns = profile.columns(); assert_eq!(columns[1].name(), "user_id"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), true); - assert_eq!(columns[1].is_primary_key(), false); - assert_eq!(columns[1].is_unique(), true); + assert!(columns[1].is_nullable()); + assert!(!columns[1].is_primary_key()); + assert!(columns[1].is_unique()); assert_eq!(profile.foreign_keys().len(), 1); @@ -561,15 +561,15 @@ fn initial_schema_plan_creates_multi_link_join_table() { let columns = user_posts.columns(); assert_eq!(columns[0].name(), "source_id"); assert_eq!(columns[0].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[0].is_nullable(), false); + assert!(!columns[0].is_nullable()); assert_eq!(columns[1].name(), "target_id"); assert_eq!(columns[1].affinity(), SQLiteAffinity::Text); - assert_eq!(columns[1].is_nullable(), false); + assert!(!columns[1].is_nullable()); assert_eq!(columns[2].name(), "position"); assert_eq!(columns[2].affinity(), SQLiteAffinity::Integer); - assert_eq!(columns[2].is_nullable(), true); + assert!(columns[2].is_nullable()); let primary_key = user_posts .primary_key() @@ -668,8 +668,8 @@ fn initial_schema_plan_records_catalog_field_rows() { assert_eq!(rows[0].cardinality(), Cardinality::Required); assert_eq!(rows[0].scalar_type(), Some(ScalarType::Uuid)); assert_eq!(rows[0].target_object_id(), None); - assert_eq!(rows[0].is_implicit(), true); - assert_eq!(rows[0].is_unique(), false); + assert!(rows[0].is_implicit()); + assert!(!rows[0].is_unique()); assert_eq!(rows[1].object_id(), 1); assert_eq!(rows[1].field_id(), 2); @@ -678,8 +678,8 @@ fn initial_schema_plan_records_catalog_field_rows() { assert_eq!(rows[1].cardinality(), Cardinality::Required); assert_eq!(rows[1].scalar_type(), Some(ScalarType::Str)); assert_eq!(rows[1].target_object_id(), None); - assert_eq!(rows[1].is_implicit(), false); - assert_eq!(rows[1].is_unique(), true); + assert!(!rows[1].is_implicit()); + assert!(rows[1].is_unique()); assert_eq!(rows[2].object_id(), 1); assert_eq!(rows[2].field_id(), 3); @@ -688,8 +688,8 @@ fn initial_schema_plan_records_catalog_field_rows() { assert_eq!(rows[2].cardinality(), Cardinality::Many); assert_eq!(rows[2].scalar_type(), None); assert_eq!(rows[2].target_object_id(), Some(2)); - assert_eq!(rows[2].is_implicit(), false); - assert_eq!(rows[2].is_unique(), false); + assert!(!rows[2].is_implicit()); + assert!(!rows[2].is_unique()); assert_eq!(rows[3].object_id(), 2); assert_eq!(rows[3].field_id(), 1); @@ -698,8 +698,8 @@ fn initial_schema_plan_records_catalog_field_rows() { assert_eq!(rows[3].cardinality(), Cardinality::Required); assert_eq!(rows[3].scalar_type(), Some(ScalarType::Uuid)); assert_eq!(rows[3].target_object_id(), None); - assert_eq!(rows[3].is_implicit(), true); - assert_eq!(rows[3].is_unique(), false); + assert!(rows[3].is_implicit()); + assert!(!rows[3].is_unique()); assert_eq!(rows[4].object_id(), 2); assert_eq!(rows[4].field_id(), 2); @@ -708,8 +708,8 @@ fn initial_schema_plan_records_catalog_field_rows() { assert_eq!(rows[4].cardinality(), Cardinality::Required); assert_eq!(rows[4].scalar_type(), Some(ScalarType::Str)); assert_eq!(rows[4].target_object_id(), None); - assert_eq!(rows[4].is_implicit(), false); - assert_eq!(rows[4].is_unique(), false); + assert!(!rows[4].is_implicit()); + assert!(!rows[4].is_unique()); assert_eq!(rows[5].object_id(), 2); assert_eq!(rows[5].field_id(), 3); @@ -718,8 +718,8 @@ fn initial_schema_plan_records_catalog_field_rows() { assert_eq!(rows[5].cardinality(), Cardinality::Required); assert_eq!(rows[5].scalar_type(), None); assert_eq!(rows[5].target_object_id(), Some(1)); - assert_eq!(rows[5].is_implicit(), false); - assert_eq!(rows[5].is_unique(), true); + assert!(!rows[5].is_implicit()); + assert!(rows[5].is_unique()); } #[test] @@ -930,7 +930,7 @@ fn initial_schema_plan_creates_single_link_foreign_key_index() { assert_eq!(indexes[0].table_name(), "post"); assert_eq!(indexes[0].column_names().len(), 1); assert_eq!(indexes[0].column_names()[0], "author_id"); - assert_eq!(indexes[0].is_unique(), false); + assert!(!indexes[0].is_unique()); } #[test] @@ -964,11 +964,11 @@ fn initial_schema_plan_creates_multi_link_join_table_indexes() { assert_eq!(indexes[0].table_name(), "user__posts"); assert_eq!(indexes[0].column_names().len(), 1); assert_eq!(indexes[0].column_names()[0], "source_id"); - assert_eq!(indexes[0].is_unique(), false); + assert!(!indexes[0].is_unique()); assert_eq!(indexes[1].name(), "user__posts__target_id_idx"); assert_eq!(indexes[1].table_name(), "user__posts"); assert_eq!(indexes[1].column_names().len(), 1); assert_eq!(indexes[1].column_names()[0], "target_id"); - assert_eq!(indexes[1].is_unique(), false); + assert!(!indexes[1].is_unique()); } diff --git a/tools/gelite-cli/src/main.rs b/tools/gelite-cli/src/main.rs index 678661f..1ef0fa8 100644 --- a/tools/gelite-cli/src/main.rs +++ b/tools/gelite-cli/src/main.rs @@ -1,4 +1,8 @@ -use std::{fs, path::PathBuf, process::ExitCode}; +use std::{ + fs, + path::{Path, PathBuf}, + process::ExitCode, +}; use clap::{Args, Parser, Subcommand}; use gelite_commands::{SchemaPlanStatement, apply_schema, plan_schema}; @@ -152,10 +156,10 @@ fn run_repl_command(command: ReplCommand) -> Result<(), String> { } None => repl::run_with_catalog(&catalog, options), } - .map_err(|()| "gelite repl failed".to_string()) + .map_err(|_| "gelite repl failed".to_string()) } -fn path_to_str(path: &PathBuf) -> Result<&str, String> { +fn path_to_str(path: &Path) -> Result<&str, String> { path.to_str() .ok_or_else(|| format!("path is not valid UTF-8: {}", path.display())) } diff --git a/tools/repl/src/lib.rs b/tools/repl/src/lib.rs index df0160b..5ee46fd 100644 --- a/tools/repl/src/lib.rs +++ b/tools/repl/src/lib.rs @@ -12,13 +12,19 @@ pub struct ReplOptions { pub query: Option, } -pub fn run(options: ReplOptions) -> Result<(), ()> { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ReplError; + +type QueryExecutor<'a> = + dyn FnMut(&SQLiteSelectStatement) -> Result + 'a; + +pub fn run(options: ReplOptions) -> Result<(), ReplError> { let catalog = build_development_schema(); run_with_catalog(&catalog, options) } -pub fn run_with_catalog(catalog: &SchemaCatalog, options: ReplOptions) -> Result<(), ()> { +pub fn run_with_catalog(catalog: &SchemaCatalog, options: ReplOptions) -> Result<(), ReplError> { let mut runtime = ReplRuntime { executor: None }; runtime.run(catalog, options) @@ -27,8 +33,8 @@ pub fn run_with_catalog(catalog: &SchemaCatalog, options: ReplOptions) -> Result pub fn run_with_executor( catalog: &SchemaCatalog, options: ReplOptions, - executor: &mut dyn FnMut(&SQLiteSelectStatement) -> Result, -) -> Result<(), ()> { + executor: &mut QueryExecutor<'_>, +) -> Result<(), ReplError> { let mut runtime = ReplRuntime { executor: Some(executor), }; @@ -37,8 +43,7 @@ pub fn run_with_executor( } struct ReplRuntime<'a> { - executor: - Option<&'a mut dyn FnMut(&SQLiteSelectStatement) -> Result>, + executor: Option<&'a mut QueryExecutor<'a>>, } enum ReplLoopAction { @@ -47,14 +52,14 @@ enum ReplLoopAction { } impl ReplRuntime<'_> { - fn run(&mut self, catalog: &SchemaCatalog, options: ReplOptions) -> Result<(), ()> { + fn run(&mut self, catalog: &SchemaCatalog, options: ReplOptions) -> Result<(), ReplError> { match options.query { Some(query_text) => self.inspect_query(catalog, &query_text, options.debug), None => self.run_repl(catalog, options.debug), } } - fn run_repl(&mut self, catalog: &SchemaCatalog, debug: bool) -> Result<(), ()> { + fn run_repl(&mut self, catalog: &SchemaCatalog, debug: bool) -> Result<(), ReplError> { run_repl(catalog, debug, self) } @@ -63,13 +68,14 @@ impl ReplRuntime<'_> { catalog: &SchemaCatalog, query_text: &str, debug: bool, - ) -> Result<(), ()> { + ) -> Result<(), ReplError> { let statement = compile_query(catalog, query_text, debug)?; match self.executor.as_deref_mut() { Some(executor) => { let result = executor(&statement).map_err(|error| { eprintln!("failed to execute query: {error}"); + ReplError })?; print_query_result(&result); } @@ -80,7 +86,11 @@ impl ReplRuntime<'_> { } } -fn run_repl(catalog: &SchemaCatalog, debug: bool, runtime: &mut ReplRuntime<'_>) -> Result<(), ()> { +fn run_repl( + catalog: &SchemaCatalog, + debug: bool, + runtime: &mut ReplRuntime<'_>, +) -> Result<(), ReplError> { println!("gelite repl"); println!("Type a select query, or :quit / :exit to leave."); println!("Use balanced braces for multiline input."); @@ -91,6 +101,7 @@ fn run_repl(catalog: &SchemaCatalog, debug: bool, runtime: &mut ReplRuntime<'_>) let mut editor = DefaultEditor::new().map_err(|error| { eprintln!("failed to initialize line editor: {error}"); + ReplError })?; let mut pending = String::new(); let mut interrupt_count = 0; @@ -139,7 +150,7 @@ fn handle_repl_line( pending: &mut String, interrupt_count: &mut i32, line: String, -) -> Result { +) -> Result { *interrupt_count = 0; if pending.is_empty() && is_exit_command(line.trim()) { @@ -174,13 +185,13 @@ fn handle_repl_read_error( error: ReadlineError, pending: &mut String, interrupt_count: &mut i32, -) -> Result { +) -> Result { match error { ReadlineError::Interrupted => handle_repl_interrupt(pending, interrupt_count), ReadlineError::Eof => Ok(ReplLoopAction::Break), error => { eprintln!("failed to read input: {error}"); - Err(()) + Err(ReplError) } } } @@ -188,7 +199,7 @@ fn handle_repl_read_error( fn handle_repl_interrupt( pending: &mut String, interrupt_count: &mut i32, -) -> Result { +) -> Result { pending.clear(); *interrupt_count += 1; @@ -228,12 +239,12 @@ fn compile_query( catalog: &SchemaCatalog, query_text: &str, debug: bool, -) -> Result { +) -> Result { let query = match parse_select(query_text) { Ok(query) => query, Err(error) => { eprintln!("failed to parse query: {error:#?}"); - return Err(()); + return Err(ReplError); } }; @@ -251,7 +262,7 @@ fn compile_query( } Err(error) => { eprintln!("failed to resolve query: {error:#?}"); - Err(()) + Err(ReplError) } } } From 69742fc3daf8b1364767f9ecff417659b03f4334 Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Thu, 9 Jul 2026 16:10:10 +0900 Subject: [PATCH 7/8] Update Cargo.lock --- Cargo.lock | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5364a81..9afeaf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "cexpr" @@ -280,9 +280,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "logos" @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "minimal-lexical" @@ -339,9 +339,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags", "cfg-if", @@ -460,9 +460,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -479,9 +479,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -502,9 +502,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "repl" @@ -523,15 +523,15 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustyline" -version = "18.0.0" +version = "18.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a990b25f351b25139ddc7f21ee3f6f56f86d6846b74ac8fad3a719a287cd4a0" +checksum = "53f6a737db68eb1a8ccff86b584b2fc13eca6a7bb6f78ebc7c529547e3ab9684" dependencies = [ "bitflags", "cfg-if", @@ -568,9 +568,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "sqlite-query-plan" @@ -623,9 +623,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -640,9 +640,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" From f3e04909aa0dd45b46efedb4cce5a6cf14506543 Mon Sep 17 00:00:00 2001 From: "Kim, Hyeonseo" Date: Thu, 9 Jul 2026 16:14:11 +0900 Subject: [PATCH 8/8] Address contribution guide review feedback Prioritize explicit maintainer constraints without weakening repository contracts, and restore the repository-specific review rubric. Assisted-by: Codex:gpt-5.5 --- AGENTS.md | 20 ++++++++++++++------ CONTRIBUTING.md | 31 +++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 784bb71..0b679df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,15 +19,17 @@ Before starting work, read: When instructions conflict, use this priority: -1. Repository documents: [AGENTS.md](AGENTS.md), +1. Explicit maintainer constraints in the current conversation, such as + `review only`, `do not edit files`, `stop`, or a narrowed task scope. +2. Repository documents: [AGENTS.md](AGENTS.md), [CONTRIBUTING.md](CONTRIBUTING.md), [AI_POLICY.md](AI_POLICY.md), and `.github` templates. -2. The current issue's explicit instructions, including branch, scope, and +3. The current issue's explicit instructions, including branch, scope, and acceptance criteria. -3. Relevant `spec/` documents. -4. Relevant `plan/` documents. -5. The current conversation. -6. Local implementation convenience. +4. Relevant `spec/` documents. +5. Relevant `plan/` documents. +6. Other instructions in the current conversation. +7. Local implementation convenience. If the current conversation is explicitly about changing repository guidance, use it as the basis for the document update and make the resulting rule clear @@ -47,6 +49,12 @@ in the changed document. - Follow [AI_POLICY.md](AI_POLICY.md) for pull request disclosure and commit trailers. +## Reviewing changes + +Follow the repository-specific review rubric in +[CONTRIBUTING.md](CONTRIBUTING.md#reviewing-changes). Report defects before +summaries, ordered by severity and supported by file and line references. + ## Reporting work When summarizing work, state: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 83a121b..5e3be02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,15 +31,17 @@ Use these files as the source of contribution rules: When instructions conflict, use this priority: -1. Repository documents: [AGENTS.md](AGENTS.md), +1. Explicit maintainer constraints for the current task, such as `review only`, + `do not edit files`, `stop`, or a narrowed task scope. +2. Repository documents: [AGENTS.md](AGENTS.md), [CONTRIBUTING.md](CONTRIBUTING.md), [AI_POLICY.md](AI_POLICY.md), and `.github` templates. -2. The current issue's explicit instructions, including branch, scope, and +3. The current issue's explicit instructions, including branch, scope, and acceptance criteria. -3. Relevant `spec/` documents. -4. Relevant `plan/` documents. -5. The current conversation. -6. Local implementation convenience. +4. Relevant `spec/` documents. +5. Relevant `plan/` documents. +6. Other instructions for the current task. +7. Local implementation convenience. If the current conversation is explicitly about changing repository guidance, use it as the basis for the document update and make the resulting rule clear @@ -385,6 +387,23 @@ non-goals. Public types and functions should be documented when they define a layer contract, invariant, error condition, or non-obvious behavior. Do not add noise documentation to simple getters. +## Reviewing changes + +Prioritize defects, behavioral regressions, and missing tests over summaries or +style preferences. Review repository-specific risks first: + +- mismatches with the relevant `spec/` documents +- crate boundary leaks, especially backend-specific decisions outside SQLite + crates +- incorrect type, cardinality, resolver, or planner behavior +- happy-path-only handling that omits supported failure cases +- unclear ownership hidden by plausible names or abstractions +- tests that assert output shape without checking the semantic contract +- abstractions introduced before there are at least two concrete users + +Report findings in severity order with file and line references. If no defect +is found, say so and identify remaining test gaps or assumptions. + ## Validation Run focused tests while developing. Run the workspace tests for changes that