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
17 changes: 17 additions & 0 deletions .claude/rules/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Architecture Rules

## Port Traits

1. **Per-feature port traits** — each feature/use-case defines its own port trait. Never create a single god trait for all graph operations.
2. **Port trait methods must use domain types only** — if a method signature contains query text, query names, or generic parameter maps, the abstraction is wrong.
3. **Port traits must have ≤5 methods** — if more are needed, split into separate per-consumer traits.
Comment on lines +5 to +7
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scope these constraints to new per-feature graph ports (or add explicit legacy exceptions).

Rules #3 and #7 are currently written as universal, but existing retained legacy ports in crates/tankyu-core/src/domain/ports.rs still exceed 5 methods and use anyhow::Result. As-is, the rule set is self-contradictory and not enforceable.

Suggested wording adjustment
-3. **Port traits must have ≤5 methods** — if more are needed, split into separate per-consumer traits.
+3. **New per-feature graph port traits must have ≤5 methods** — if more are needed, split into separate per-consumer traits. Legacy JSON-store ports in `domain/ports/` are exempt until migrated.

-7. **Port traits return `Result<T, TankyuError>`** — never `anyhow::Result` in port trait signatures.
+7. **Per-feature core port traits return `Result<T, TankyuError>`** — never `anyhow::Result` in new/updated feature port trait signatures. Legacy `domain/ports/` traits must migrate incrementally.

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/rules/architecture.md around lines 5 - 7, Scope the per-feature port
constraints to new graph ports (e.g., add a rule header "Applies to new
per-feature graph ports only") or add explicit legacy exceptions for existing
retained ports in the domain::ports module; specifically update the rule text
that mandates "≤5 methods" and "use domain types only" so it excludes legacy
traits in domain::ports (which currently exceed 5 methods and return
anyhow::Result) or call out them by name to be exempted, and ensure the guidance
references the use of anyhow::Result as a legacy trait behavior that should be
grandfathered until refactoring.


## Layer Boundaries

4. **No `include_str!` across module boundaries** — query files, schema files, and templates must only be referenced by the infrastructure module that owns them. Features must never use `include_str!("../../infrastructure/...")`.
5. **Features must not import from infrastructure** — except in `#[cfg(test)]` blocks for integration tests using `open_in_memory()`.
6. **Infrastructure owns all engine-specific details** — query syntax, query file paths, engine configuration. Features see only domain-typed trait methods.

## Error Handling

7. **Port traits return `Result<T, TankyuError>`** — never `anyhow::Result` in port trait signatures.
2 changes: 1 addition & 1 deletion .claude/rules/core-domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ When working with tankyu-core:
2. **Serde struct convention**: `#[serde(rename_all = "camelCase")]` on all structs — the JSON files are produced by TypeScript and use camelCase
3. **Serde enum convention**: `#[serde(rename_all = "kebab-case")]` on all enums — matches TypeScript enum serialization
4. **Nullable vs optional fields**: TS `.nullable()` → `Option<T>` + `#[serde(default)]`; TS `.optional()` → `Option<T>` with no extra attribute
5. **Port traits** (in `domain/ports/`) define the storage interface — never add methods to a port trait just for convenience; prefer manager-level logic over widening the trait surface
5. **Port traits** are defined per-feature in each feature module (e.g. `CountStats` in `features/status/`, `GraphDoctor` in `features/doctor/`). Old JSON store ports remain in `domain/ports/`. Never add methods to a port trait just for convenience; prefer manager-level logic over widening the trait surface
6. **Error handling**: all fallible operations return `Result<T, TankyuError>` using `thiserror` — never use `anyhow` in core
7. **Feature managers** take `Arc<dyn Port>` dependencies — never take concrete store types; this keeps test stubs simple
8. **No manager-to-manager dependencies** — if a manager needs data from another domain, take the shared port (`Arc<dyn IGraphStore>`) directly
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install protoc
uses: arduino/setup-protoc@v3
Comment on lines +27 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify arduino/setup-protoc action and latest protoc version

# Check the action's README for version parameter support
curl -s https://raw.githubusercontent.com/arduino/setup-protoc/v3/README.md | grep -A5 "version"

# Get latest protoc release
curl -s https://api.github.com/repos/protocolbuffers/protobuf/releases/latest | jq -r '.tag_name'

Repository: cmbays/tankyu-rs

Length of output: 862


Good addition; consider pinning the protoc version for reproducibility.

Adding protoc installation consistently across all test jobs is correct for supporting nanograph's transitive dependencies. However, the action is called without specifying a protoc version, which means it will use the action's default (typically the latest). This could lead to non-deterministic builds if protoc behavior changes between versions. The arduino/setup-protoc@v3 action supports version pinning via the with parameter—use this to ensure builds remain reproducible.

📌 Suggested enhancement to pin protoc version
       - name: Install protoc
         uses: arduino/setup-protoc@v3
+        with:
+          version: "34.x"

Apply the same change to all four jobs' protoc installation steps (lines 27-28, 78-79, 131-132, and 229-230).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Install protoc
uses: arduino/setup-protoc@v3
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
version: "34.x"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 27 - 28, The arduino/setup-protoc@v3
step is unpinned and can produce non-deterministic builds; update each protoc
installation step that uses arduino/setup-protoc@v3 to include a with: parameter
that pins the protoc version (e.g., with: protoc: "3.21.12" or your chosen
stable version) so the action installs a fixed protoc release, and apply this
change to all four jobs that call arduino/setup-protoc@v3.

with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Rust stable
uses: dtolnay/rust-toolchain@1.91.0
with:
Expand Down Expand Up @@ -72,6 +77,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Rust stable
uses: dtolnay/rust-toolchain@1.91.0
with:
Expand Down Expand Up @@ -122,6 +132,11 @@ jobs:
with:
fetch-depth: 0

- name: Install protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Rust stable
uses: dtolnay/rust-toolchain@1.91.0

Expand Down Expand Up @@ -217,6 +232,11 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Rust stable
uses: dtolnay/rust-toolchain@1.91.0

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
mutants.out/
mutants.out.old/
docs/private/
.firecrawl/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: Unrelated change bundled in PR.

The .firecrawl/ entry is unrelated to the nanograph integration work described in the PR objectives. While this is a harmless addition, consider keeping gitignore updates for local tooling in separate commits or PRs to maintain clearer change history.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 10, The .gitignore change added an unrelated entry
".firecrawl/"; revert that line from this PR or move it into a separate
commit/PR dedicated to local tooling/gitignore updates so the nanograph
integration PR only contains relevant changes—remove the ".firecrawl/" entry
from .gitignore in this branch or create a new commit containing just that
gitignore update.


# Claude Code — track rules, ignore everything else
.claude/*
Expand Down
9 changes: 9 additions & 0 deletions .workflow/observations.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{"type":"decision","phase":"discover","step":"framing","text":"Option B selected: collapse 6 store traits into single IResearchGraph backed by nanograph. Eliminates glue code, leverages open_in_memory() for tests.","date":"2026-03-19"}
{"type":"decision","phase":"discover","step":"framing","text":"Clean start for v1 — no JSON data migration required. Migration is mechanical (JSON→JSONL) and can be done later.","date":"2026-03-19"}
{"type":"decision","phase":"discover","step":"framing","text":"Architecture uplift: introduce application/use-case layer, enforce domain/infra boundary via module visibility + CI lint.","date":"2026-03-19"}
{"type":"assumption","phase":"discover","step":"framing","text":"nanograph enum() syntax supports kebab-case values — needs verification against schema parser.","date":"2026-03-19","resolved":"2026-03-19","resolution":"CONFIRMED — kebab-case enums work, auto-sorted alphabetically."}
{"type":"assumption","phase":"discover","step":"framing","text":"UUID strings as @key will work for nanograph node identity — needs verification (nanograph examples use slugs).","date":"2026-03-19","resolved":"2026-03-19","resolution":"CONFIRMED — UUID strings as String @key work for nodes and edge resolution."}
{"type":"gap","phase":"discover","step":"framing","text":"No automated Rust tool for detecting domain/infra layer violations. Will build lightweight CI lint (grep-based).","date":"2026-03-19"}
{"type":"decision","phase":"discover","step":"research","text":"Feature-gate nanograph behind cargo feature flag. Binary goes from 1.4MB to 58MB (+341 deps). Default build stays lean; graph backend is opt-in.","date":"2026-03-19"}
{"type":"gap","phase":"discover","step":"research","text":"protoc (protobuf compiler) required at build time when nanograph feature is enabled — needs CI setup.","date":"2026-03-19"}
{"type":"outcome","session":"S1.1b","gate":"atdd","timestamp":"2026-03-21","result":"ALL_PASS","details":{"bdd":"PASS — doctor 3/3, status 2/3 (1 pre-existing skip)","tdd":"PASS — 165 tests, clippy clean","crap":"SKIPPED — Rust project","mutation":"PASS — 14 caught, 7 unviable, 0 survivors","arch_auto":"PASS — no boundary violations","arch_review":"PASS — 0 HIGH, 0 MEDIUM (fixed DoctorReport placement)"},"gaps":["CRAP analysis skipped — crap4ts not available for Rust"]}
34 changes: 34 additions & 0 deletions .workflow/phases/build/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"phase": "build",
"status": "running",
"steps": [
{
"name": "S1.1",
"description": "Schema + IResearchGraph + NanographStore",
"status": "completed",
"startedAt": "2026-03-19T00:00:00Z",
"completedAt": "2026-03-21T00:00:00Z",
"branch": "feat/nanograph-v1-schema",
"notes": "Schema (.pg), IResearchGraph port, NanographStore impl, StatusUseCase, DoctorUseCase, slug_from_name, topic queries. 6+8 unit tests green."
},
{
"name": "S1.2",
"description": "Wire Status + Doctor BDD scenarios",
"status": "completed",
"startedAt": "2026-03-19T00:00:00Z",
"completedAt": "2026-03-21T00:00:00Z",
"branch": "feat/nanograph-v1-schema",
"notes": "Doctor 3/3 green, Status 2/3 green + 1 skipped (depends on source-topic link from S2.2). All wiring done in S1.1 commit — no separate branch needed.",
"bddScenarios": "5/6 passing (1 blocked on S2.2 prerequisite)"
},
{
"name": "S2.1",
"description": "Topic CRUD via nanograph",
"status": "pending",
"branch": "feat/nanograph-v2-topics",
"bddScenarios": 7
}
],
"startedAt": "2026-03-21T00:00:00Z",
"completedAt": null
}
7 changes: 7 additions & 0 deletions .workflow/phases/closeout/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"phase": "closeout",
"status": "pending",
"steps": [],
"startedAt": null,
"completedAt": null
}
32 changes: 32 additions & 0 deletions .workflow/phases/discover/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"phase": "discover",
"status": "completed",
"steps": [
{
"name": "existing-decisions",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z"
},
{
"name": "framing",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"artifact": "~/Github/ops/workspace/tankyu-rs/tankyu-rs/20260319-nanograph-integration/frame.md"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded home directory paths are non-portable.

The artifact paths at lines 14, 21, and 27 use ~ expansion with user-specific directory structures. These paths won't work across different environments or users. Consider using repository-relative paths or configurable workspace directories.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.workflow/phases/discover/state.json at line 14, The artifact entries use
hardcoded home-directory expansion ("~") making them non-portable; update each
"artifact" value that starts with "~" to use repository-relative or configurable
workspace variables instead (e.g., replace "~/<user>/.../frame.md" with a
repo-relative path like "workspace/tankyu-rs/.../frame.md" or a variable such as
"${WORKSPACE}/.../frame.md"), and ensure the code that consumes the JSON reads
the configured workspace/env var rather than relying on shell tilde expansion;
target the "artifact" JSON keys in the state file and update all three
occurrences matching that pattern.

},
{
"name": "research",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"notes": "Spikes: kebab-case enums + UUID @key confirmed. Binary 1.4→58MB. Nullability/concurrency/negation/mutations/migration all tested. `id` field naming bug found and worked around (use `slug`).",
"artifact": "~/Github/ops/research/tankyu-rs/nanograph-v1.0.0-capabilities.md"
},
{
"name": "interrogation",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"artifact": "~/Github/ops/workspace/tankyu-rs/tankyu-rs/20260319-nanograph-integration/requirements.md"
}
],
"startedAt": "2026-03-19T00:00:00Z",
"completedAt": "2026-03-19T00:00:00Z"
}
15 changes: 15 additions & 0 deletions .workflow/phases/plan/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"phase": "plan",
"status": "completed",
"steps": [
{
"name": "design-doc",
"status": "completed",
"startedAt": "2026-03-19T00:00:00Z",
"completedAt": "2026-03-20T00:00:00Z",
"artifact": "~/Github/ops/workspace/tankyu-rs/tankyu-rs/20260319-nanograph-integration/impl-plan.md"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded home directory path is non-portable.

The artifact path uses ~ expansion and includes user-specific directory structure (~/Github/ops/workspace/...). This path won't work for other users, in CI environments, or when the repository is cloned elsewhere. Consider using relative paths from the repository root or a configurable workspace directory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.workflow/phases/plan/state.json at line 10, The "artifact" field currently
contains a hardcoded, user-specific path using "~", which is non-portable;
update the "artifact" value to use a repository-relative path or an
environment-based workspace variable (e.g., using GITHUB_WORKSPACE or a
configurable WORKSPACE_DIR) instead of the absolute/home-prefixed path so the
artifact resolves correctly across different machines and CI; edit the
"artifact" key in the JSON to reference the relative path to impl-plan.md or a
templated/env variable that points at the repo workspace.

}
],
"startedAt": "2026-03-19T00:00:00Z",
"completedAt": "2026-03-20T00:00:00Z"
}
7 changes: 7 additions & 0 deletions .workflow/phases/review/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"phase": "review",
"status": "pending",
"steps": [],
"startedAt": null,
"completedAt": null
}
40 changes: 40 additions & 0 deletions .workflow/phases/shape/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"phase": "shape",
"status": "completed",
"steps": [
{
"name": "existing-decisions",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"notes": "7 ADRs from write-side-mutations pipeline. No product decisions yet."
},
{
"name": "shaping",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"notes": "Shape A selected: Thin Port, Fat Use Cases. 8 parts defined.",
"artifact": "~/Github/ops/workspace/tankyu-rs/tankyu-rs/20260319-nanograph-integration/shaping.md"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded home directory paths are non-portable.

The artifact paths at lines 16 and 23 use ~ expansion with user-specific directory structures. These won't work across different environments. Consider using repository-relative paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.workflow/phases/shape/state.json at line 16, The artifact entries using
tilde expansion (the "artifact" JSON fields) are non-portable; update those
"artifact" values to use repository-relative paths or environment-aware
variables instead of "~" (e.g., replace "~/Github/..." with a path relative to
the repo root or use a CI artifact directory variable) so the state.json no
longer depends on a user-specific home directory; update both artifact
occurrences referenced in the file.

},
{
"name": "breadboarding",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"notes": "14 CLI commands mapped, 6 vertical slices defined. AddSource is most complex flow.",
"artifact": "~/Github/ops/workspace/tankyu-rs/tankyu-rs/20260319-nanograph-integration/breadboard.md"
},
{
"name": "breadboard-reflection",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"notes": "7 smells fixed + 8 nanograph cross-reference findings. All resolved including F2 edge endpoints."
},
{
"name": "bdd-formulation",
"status": "completed",
"completedAt": "2026-03-19T00:00:00Z",
"notes": "6 .feature files: status, topic, source, entry, health, doctor. 42 scenarios total. Slug-based identity, graph traversal, auto-init, idempotent source add, cascade delete."
}
],
"startedAt": "2026-03-19T00:00:00Z",
"completedAt": "2026-03-19T00:00:00Z"
}
10 changes: 10 additions & 0 deletions .workflow/pipeline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "tankyu-rs/20260319-nanograph-integration",
"description": "Discover and shape nanograph v1.0.0 integration as tankyu's graph backend, replacing JsonGraphStore",
"template": "vertical",
"phaseSequence": ["discover", "shape", "plan", "build", "review", "closeout"],
"currentPhase": "build",
"status": "running",
"startedAt": "2026-03-19T00:00:00Z",
"completedAt": null
}
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ Cargo workspace with two crates:
Reads `~/.tankyu/` data directory produced by the TypeScript CLI.
Set `TANKYU_DIR` env var to override the data directory (used in tests).

## Language Context

Language: Rust
Framework: clap (CLI)
Idiom reference: `~/.claude/knowledge/languages/rust/`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a portable idiom-reference location instead of a home-directory path.

~/.claude/knowledge/languages/rust/ is machine-specific and may be invalid for other contributors/CI. Prefer a repo-relative path or an external canonical URL so the guidance is reproducible.

Suggested doc tweak
-Idiom reference: `~/.claude/knowledge/languages/rust/`
+Idiom reference: `.claude/knowledge/languages/rust/` (repo-relative) or the team-approved Rust style guide URL
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 30, Replace the hard-coded home-directory idiom
`~/.claude/knowledge/languages/rust/` with a portable, reproducible location:
use a repo-relative path (for example `knowledge/languages/rust/`), or reference
a canonical external URL, or allow an environment-configurable base (e.g. use
`CLAUDE_HOME` or respect `XDG_CONFIG_HOME`) so contributors and CI can resolve
the path consistently; update any occurrences and documentation to use the
chosen portable form.


Consult the idiom reference before making architectural decisions (trait design, module boundaries, port abstractions).

## Key Conventions

- `#![forbid(unsafe_code)]` in every crate root
Expand Down
Loading
Loading