Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Rust library for NP-hard problem reductions. Implements computational problems w
- [write-model-in-paper](skills/write-model-in-paper/SKILL.md) -- Write or improve a problem-def entry in the Typst paper. Covers formal definition, background, example with visualization, and algorithm list.
- [write-rule-in-paper](skills/write-rule-in-paper/SKILL.md) -- Write or improve a reduction-rule entry in the Typst paper. Covers complexity citation, self-contained proof, detailed example, and verification.
- [release](skills/release/SKILL.md) -- Create a new crate release. Determines version bump from diff, verifies tests/clippy, then runs `make release`.
- [meta-power](skills/meta-power/SKILL.md) -- Batch-resolve all open `[Model]` and `[Rule]` issues autonomously: plan, implement, review, fix CI, merge — in dependency order (models first).

## Commands
```bash
Expand All @@ -26,7 +27,7 @@ make mdbook # Build and serve mdBook with live reload
make paper # Build Typst paper (runs examples + exports first)
make coverage # Generate coverage report (>95% required)
make check # Quick pre-commit check (fmt + clippy + test)
make rust-export # Generate Rust mapping JSON exports
make rust-export # Generate Julia parity test data (mapping stages)
make export-schemas # Regenerate problem schemas JSON
make qubo-testdata # Regenerate QUBO ground truth JSON
make clean # Clean build artifacts
Expand Down Expand Up @@ -90,6 +91,8 @@ enum Direction { Maximize, Minimize }
```

### Key Patterns
- `variant_params!` macro implements `Problem::variant()` — e.g., `crate::variant_params![G, W]` for two type params, `crate::variant_params![]` for none (see `src/variant.rs`)
- `declare_variants!` proc macro registers concrete type instantiations with best-known complexity — must appear in every model file (see `src/models/graph/maximum_independent_set.rs`). Variable names in complexity strings are validated at compile time against actual getter methods.
- Problems parameterized by graph type `G` and optionally weight type `W` (problem-dependent)
- `ReductionResult` provides `target_problem()` and `extract_solution()`
- `Solver::find_best()` → `Option<Vec<usize>>` for optimization problems; `Solver::find_satisfying()` → `Option<Vec<usize>>` for `Metric = bool`
Expand All @@ -101,7 +104,7 @@ enum Direction { Maximize, Minimize }
- `NumericSize` supertrait bundles common numeric bounds (`Clone + Default + PartialOrd + Num + Zero + Bounded + AddAssign + 'static`)

### Overhead System
Reduction overhead is expressed using `Expr` AST (in `src/expr.rs`) with the `#[reduction]` macro:
Reduction overhead is expressed using `Expr` AST (in `src/expr.rs`) with the `#[reduction]` macro. The `overhead` attribute is **required** — omitting it is a compile error:
```rust
#[reduction(overhead = {
num_vertices = "num_vertices + num_clauses",
Expand All @@ -110,9 +113,14 @@ Reduction overhead is expressed using `Expr` AST (in `src/expr.rs`) with the `#[
impl ReduceTo<Target> for Source { ... }
```
- Expression strings are parsed at compile time by a Pratt parser in the proc macro crate
- Variable names are validated against actual getter methods on the source type — typos cause compile errors
- Each problem type provides inherent getter methods (e.g., `num_vertices()`, `num_edges()`) that the overhead expressions reference
- `ReductionOverhead` stores `Vec<(&'static str, Expr)>` — field name to symbolic expression mappings
- Expressions support: constants, variables, `+`, `*`, `^`, `exp()`, `log()`, `sqrt()`
- `ReductionEntry` has both symbolic (`overhead_fn`) and compiled (`overhead_eval_fn`) evaluation — the compiled version calls getters directly
- `VariantEntry` has both a complexity string and compiled `complexity_eval_fn` — same pattern
- Expressions support: constants, variables, `+`, `-`, `*`, `/`, `^`, `exp()`, `log()`, `sqrt()`
- Complexity strings must use **concrete numeric values only** (e.g., `"2^(2.372 * num_vertices / 3)"`, not `"2^(omega * num_vertices / 3)"`)
- `Expr::parse()` provides runtime parsing for cross-check tests that compare compiled vs symbolic evaluation

### Problem Names
Problem types use explicit optimization prefixes:
Expand Down Expand Up @@ -216,6 +224,8 @@ The complexity string represents the **worst-case time complexity of the best kn
2. Confirm the worst-case time bound from the original paper or a survey
3. Check that polynomial-time problems (e.g., MaximumMatching, 2-SAT, 2-Coloring) are NOT declared with exponential complexity
4. For NP-hard problems, verify the base of the exponential matches the literature (e.g., 1.1996^n for MIS, not 2^n)
5. Use only concrete numeric values — no symbolic constants (epsilon, omega); inline the actual numbers with citations
6. Variable names must match getter methods on the problem type (enforced at compile time)

### Reduction Overhead (`#[reduction(overhead = {...})]`)
Overhead expressions describe how target problem size relates to source problem size. To verify correctness:
Expand Down
20 changes: 20 additions & 0 deletions .claude/skills/add-model/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ Key decisions:
- **Weight management:** use inherent methods (`weights()`, `set_weights()`, `is_weighted()`), NOT traits
- **`dims()`:** returns the configuration space dimensions (e.g., `vec![2; n]` for binary variables)
- **`evaluate()`:** must check feasibility first, then compute objective
- **`variant()`:** use the `variant_params!` macro — e.g., `crate::variant_params![G, W]` for `Problem<G, W>`, or `crate::variant_params![]` for problems with no type parameters. Each type parameter must implement `VariantParam` (already done for standard types like `SimpleGraph`, `i32`, `One`). See `src/variant.rs`.

## Step 2.5: Register variant complexity

Add `declare_variants!` at the bottom of the model file (after the trait impls, before the test link). Each line declares a concrete type instantiation with its best-known worst-case complexity:

```rust
crate::declare_variants! {
ProblemName<SimpleGraph, i32> => "1.1996^num_vertices",
ProblemName<SimpleGraph, One> => "1.1996^num_vertices",
}
```

- The complexity string references the getter method names from Step 1.5 (e.g., `num_vertices`) — variable names are validated at compile time against actual getters, so typos cause compile errors
- One entry per supported `(graph, weight)` combination
- The string is parsed as an `Expr` AST — supports `+`, `-`, `*`, `/`, `^`, `exp()`, `log()`, `sqrt()`
- Use only concrete numeric values (e.g., `"1.1996^num_vertices"`, not `"(2-epsilon)^num_vertices"`)
- A compiled `complexity_eval_fn` is auto-generated alongside the symbolic expression
- See `src/models/graph/maximum_independent_set.rs` for the reference pattern

## Step 3: Register the model

Expand Down Expand Up @@ -146,5 +165,6 @@ Then run the [review-implementation](../review-implementation/SKILL.md) skill to
| Missing `#[path]` test link | Add `#[cfg(test)] #[path = "..."] mod tests;` at file bottom |
| Wrong `dims()` | Must match the actual configuration space (e.g., `vec![2; n]` for binary) |
| Not registering in `mod.rs` | Must update both `<category>/mod.rs` and `models/mod.rs` |
| Forgetting `declare_variants!` | Required for variant complexity metadata used by the paper's auto-generated table |
| Forgetting CLI dispatch | Must add match arms in `dispatch.rs` (`load_problem` + `serialize_any_problem`) |
| Forgetting CLI alias | Must add lowercase entry in `problem_name.rs` `resolve_alias()` |
9 changes: 5 additions & 4 deletions .claude/skills/add-rule/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl ReductionResult for ReductionXToY {
}
```

**ReduceTo with `#[reduction]` macro:**
**ReduceTo with `#[reduction]` macro** (overhead is **required**):
```rust
#[reduction(overhead = {
field_name = "source_field",
Expand Down Expand Up @@ -131,11 +131,12 @@ example_fn!(test_<source>_to_<target>, reduction_<source>_to_<target>);

Invoke the `/write-rule-in-paper` skill to write the reduction-rule entry in `docs/paper/reductions.typ`. That skill covers the full authoring process: complexity citation, self-contained proof, detailed worked example, and verification checklist.

## Step 6: Regenerate graph and verify
## Step 6: Regenerate exports and verify

```bash
cargo run --example export_graph # Update reduction_graph.json
make test clippy # Must pass
cargo run --example export_graph # Update reduction_graph.json
cargo run --example export_schemas # Update problem schemas
make test clippy # Must pass
```

Then run the [review-implementation](../review-implementation/SKILL.md) skill to verify all structural and semantic checks pass.
Expand Down
19 changes: 11 additions & 8 deletions .claude/skills/fix-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ Resolve PR review comments, fix CI failures, and address codecov coverage gaps f

**IMPORTANT:** Do NOT use `gh api --jq` for extracting data — it uses a built-in jq that
chokes on response bodies containing backslashes (common in Copilot code suggestions).
Always pipe to `python3 -c` instead.
Always pipe to `python3 -c` instead. (`gh pr view --jq` is fine — only `gh api --jq` is affected.)

```bash
# Get repo identifiers
REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner) # e.g., "owner/repo"

# Get PR number
PR=$(gh pr view --json number --jq .number)

# Get PR head SHA (on remote)
HEAD_SHA=$(gh api repos/{owner}/{repo}/pulls/$PR | python3 -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
HEAD_SHA=$(gh api repos/$REPO/pulls/$PR | python3 -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
```

### 1a. Fetch Review Comments
Expand All @@ -27,23 +30,23 @@ Three sources of feedback to check:

```bash
# Copilot and user inline review comments (on code lines)
gh api repos/{owner}/{repo}/pulls/$PR/comments | python3 -c "
gh api repos/$REPO/pulls/$PR/comments | python3 -c "
import sys,json
for c in json.load(sys.stdin):
line = c.get('line') or c.get('original_line') or '?'
print(f'[{c[\"user\"][\"login\"]}] {c[\"path\"]}:{line} — {c[\"body\"]}')
"

# Review-level comments (top-level review body)
gh api repos/{owner}/{repo}/pulls/$PR/reviews | python3 -c "
gh api repos/$REPO/pulls/$PR/reviews | python3 -c "
import sys,json
for r in json.load(sys.stdin):
if r.get('body'):
print(f'[{r[\"user\"][\"login\"]}] {r[\"state\"]}: {r[\"body\"]}')
"

# Issue-level comments (general discussion, excluding bots)
gh api repos/{owner}/{repo}/issues/$PR/comments | python3 -c "
gh api repos/$REPO/issues/$PR/comments | python3 -c "
import sys,json
for c in json.load(sys.stdin):
login = c['user']['login']
Expand All @@ -56,7 +59,7 @@ for c in json.load(sys.stdin):

```bash
# All check runs on the PR head
gh api repos/{owner}/{repo}/commits/$HEAD_SHA/check-runs | python3 -c "
gh api repos/$REPO/commits/$HEAD_SHA/check-runs | python3 -c "
import sys,json
for cr in json.load(sys.stdin)['check_runs']:
print(f'{cr[\"name\"]}: {cr.get(\"conclusion\") or cr[\"status\"]}')
Expand All @@ -67,7 +70,7 @@ for cr in json.load(sys.stdin)['check_runs']:

```bash
# Codecov bot comment with coverage diff
gh api repos/{owner}/{repo}/issues/$PR/comments | python3 -c "
gh api repos/$REPO/issues/$PR/comments | python3 -c "
import sys,json
for c in json.load(sys.stdin):
if c['user']['login'] == 'codecov[bot]':
Expand Down Expand Up @@ -129,7 +132,7 @@ For detailed line-by-line coverage, use the Codecov API:

```bash
# Get file-level coverage for the PR
gh api repos/{owner}/{repo}/issues/$PR/comments | python3 -c "
gh api repos/$REPO/issues/$PR/comments | python3 -c "
import sys,json,re
for c in json.load(sys.stdin):
if c['user']['login'] == 'codecov[bot]':
Expand Down
11 changes: 9 additions & 2 deletions .claude/skills/issue-to-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,14 @@ Include the concrete details from the issue (problem definition, reduction algor

Create a pull request with only the plan file.

**Pre-flight checks** (before creating the branch):
1. Verify clean working tree: `git status --porcelain` must be empty. If not, STOP and ask user to stash or commit.
2. Check if branch already exists: `git rev-parse --verify issue-<number>-<slug> 2>/dev/null`. If it exists, switch to it with `git checkout` (no `-b`) instead of creating a new one.

```bash
# Create branch
git checkout -b issue-<number>-<slug>
# Create branch (from main)
git checkout main
git rev-parse --verify issue-<number>-<slug> 2>/dev/null && git checkout issue-<number>-<slug> || git checkout -b issue-<number>-<slug>
Comment on lines +90 to +91
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branch name issue-<number>-<slug> is interpolated directly into shell commands without quoting or sanitization. If <slug> ever contains shell metacharacters derived from issue titles or user-provided text (e.g., ;, &&, $(), backticks), running this skill could execute arbitrary commands on the developer machine when git checkout/git push are invoked. To harden this flow, construct the branch name from a sanitized slug restricted to safe characters (e.g., [A-Za-z0-9_-]) or pass it via a quoted variable instead of embedding it inline in the command text.

Copilot uses AI. Check for mistakes.

Comment on lines 84 to 92
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pre-flight checks say to git checkout issue-<number>-<slug> without -b if the branch already exists, but the example snippet always runs git checkout -b issue-<number>-<slug>, which will fail on retries. Consider updating the snippet to reflect the conditional logic (or show both commands in an if/else).

Copilot uses AI. Check for mistakes.
# Stage the plan file
git add docs/plans/<plan-file>.md
Expand Down Expand Up @@ -131,3 +136,5 @@ Created PR #45: Fix #42: Add IndependentSet -> QUBO reduction
| Generic plan | Use specifics from the issue, mapped to add-model/add-rule steps |
| Skipping CLI registration in plan | add-model requires CLI dispatch updates -- include in plan |
| Not verifying facts from issue | Use WebSearch/WebFetch to cross-check claims |
| Branch already exists on retry | Check with `git rev-parse --verify` before `git checkout -b` |
| Dirty working tree | Verify `git status --porcelain` is empty before branching |
Loading