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
46 changes: 30 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ on:
pull_request:
branches: [main]

# All jobs install from uv.lock (`uv sync --locked`) so CI resolves the exact
# versions contributors tested against. The previous recipe mixed
# `uv pip install` (latest resolutions) with `uv run`'s implicit lockfile
# sync, which could downgrade base deps underneath freshly-installed extras —
# a new anyio release made that skew fatal (typing_extensions ImportError at
# collection). If `--locked` fails, run `uv lock` locally and commit the
# refreshed lockfile.

jobs:
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
# One version's failure shouldn't cancel the other's signal.
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]

Expand All @@ -23,11 +33,11 @@ jobs:
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv venv && uv pip install -e ".[dev]"
- name: Install dependencies (locked)
run: uv sync --locked --extra dev --python ${{ matrix.python-version }}

- name: Run tests
run: uv run pytest tests/ -v --tb=short
run: uv run --no-sync pytest tests/ -v --tb=short

lint:
name: Lint
Expand All @@ -41,16 +51,18 @@ jobs:
- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: uv venv && uv pip install -e ".[dev]" ruff
- name: Install dependencies (locked)
run: uv sync --locked --extra dev --python 3.12

- name: Run ruff
run: uv run ruff check src/ tests/
run: uv run --no-sync ruff check src/ tests/

matrix:
full-suite:
# Full test suite — slow markers (domain x framework matrix, performance,
# generated-project venv suites) plus the functional local-file vault
# tests. Runs on every PR and on pushes to main.
name: Full Suite + Domain x Framework Matrix
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4
Expand All @@ -61,13 +73,15 @@ jobs:
- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: uv venv && uv pip install -e ".[dev]"
- name: Install dependencies (locked)
run: uv sync --locked --extra dev --extra connectors --python 3.12

- name: Run full test suite with slow tests (matrix + performance)
run: uv run pytest tests/ -v --tb=short --slow
- name: Run full test suite (slow + functional)
run: uv run --no-sync pytest tests/ -v --tb=short --slow --functional

smoke-test:
# Needs repository secrets (Neo4j + LLM API keys), which aren't available
# to pull requests from forks — stays main-push-only.
name: E2E Smoke Test (${{ matrix.test.framework }})
runs-on: ubuntu-latest
needs: test
Expand Down Expand Up @@ -96,15 +110,15 @@ jobs:
- name: Set up Python
run: uv python install 3.12

- name: Install dependencies
run: uv venv && uv pip install -e ".[all,dev]"
- name: Install dependencies (locked)
run: uv sync --locked --all-extras --python 3.12

- name: Run integration tests
env:
NEO4J_URI: ${{ secrets.NEO4J_URI }}
NEO4J_USERNAME: ${{ secrets.NEO4J_USERNAME }}
NEO4J_PASSWORD: ${{ secrets.NEO4J_PASSWORD }}
run: uv run pytest tests/test_integration.py --integration -v --tb=short
run: uv run --no-sync pytest tests/test_integration.py --integration -v --tb=short

- name: Run smoke test
env:
Expand All @@ -115,7 +129,7 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
run: >
uv run python scripts/e2e_smoke_test.py
uv run --no-sync python scripts/e2e_smoke_test.py
--domain ${{ matrix.test.domain }}
--framework ${{ matrix.test.framework }}
--quick
72 changes: 72 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

29 changes: 17 additions & 12 deletions CLAUDE.md

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ install-all: ## Install all optional dependencies (dev + generate + connectors)

## Testing

test: ## Run fast tests (602 tests, no Neo4j or API keys required)
uv run pytest tests/ -v --tb=short
test: ## Run fast tests (1,454 passing, no Neo4j or API keys required)
uv run --extra dev pytest tests/ -v --tb=short

test-slow: ## Run full suite including slow + functional vault tests (~2.7s extra)
uv run --extra connectors pytest tests/ -v --tb=short --slow --functional
uv run --extra dev --extra connectors pytest tests/ -v --tb=short --slow --functional

test-matrix: ## Run domain x framework matrix only (176 combos)
uv run pytest tests/test_matrix.py -v --tb=short --slow
uv run --extra dev pytest tests/test_matrix.py -v --tb=short --slow

test-coverage: ## Run tests with coverage report
uv run pytest tests/ -v --cov=create_context_graph --cov-report=html
uv run --extra dev pytest tests/ -v --cov=create_context_graph --cov-report=html

test-functional: ## Run optional functional tests (ingest the local-file vault fixture)
uv run --extra connectors pytest tests/test_local_file_vault.py --functional -v --tb=short
uv run --extra dev --extra connectors pytest tests/test_local_file_vault.py --functional -v --tb=short

smoke-test: ## E2E smoke test: scaffold, start, and chat for 3 key frameworks (requires Neo4j + API keys)
@echo "Running smoke tests for pydanticai, google-adk, and strands..."
Expand All @@ -38,10 +38,10 @@ smoke-test: ## E2E smoke test: scaffold, start, and chat for 3 key frameworks (
## Linting

lint: ## Run ruff linter
uv run ruff check src/ tests/
uv run --extra dev ruff check src/ tests/

lint-fix: ## Auto-fix lint issues
uv run ruff check src/ tests/ --fix
uv run --extra dev ruff check src/ tests/ --fix

## Build & Publish

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/explanation/memory-backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ But the two backends have **different operational profiles**, and each makes sen
| **Arbitrary Cypher reads** | Yes (`client.query.cypher`, read-only) | Yes |
| **Arbitrary Cypher writes** | No (REST enforces read-only) | Yes |
| **GDS algorithms** | No (501 Not Implemented) | Yes |
| **`make reset`** | Slow (per-entity REST delete) | Fast (`MATCH (n) DETACH DELETE n`) |
| **`make reset`** | Not available (no delete API in NAMS REST / neo4j-agent-memory 0.5.x — use the NAMS dashboard) | Fast (`MATCH (n) DETACH DELETE n`) |
| **Data residency** | Hosted by Neo4j Labs | Wherever your Neo4j runs |
| **Offline development** | No (needs network) | Yes (with Docker / neo4j-local) |

Expand Down
6 changes: 6 additions & 0 deletions docs/docs/how-to/add-custom-domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ Write a domain YAML file from scratch and point the CLI at it:
create-context-graph my-app --ontology-file ./my-domain.yaml --framework langgraph
```

The scaffold uses the domain id declared inside the YAML (overriding any
`--domain` value), and the file is copied into the project as
`data/ontology.yaml` alongside `_base.yaml`, so the generated project stays
self-contained. `--ontology-file` and `--custom-domain` are mutually
exclusive — pass one or the other.

Your YAML must follow the domain ontology schema. At minimum, include:

```yaml
Expand Down
41 changes: 35 additions & 6 deletions docs/docs/how-to/use-nams.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,44 @@ cd backend
uv pip install 'neo4j-agent-memory[litellm,sentence-transformers,extraction,fuzzy]>=0.4.0,<0.6.0'
```

## Resetting NAMS state
## Domain ontology activation

NAMS binds every workspace to a generic `nams-default` ontology until an
explicit one is activated — and it pre-registers a server-side ontology for
every bundled create-context-graph domain. As of v0.14.0, generated apps
handle this automatically:

- **On startup** (`connect_memory()`), the app checks the workspace's active
ontology. If it doesn't match the app's domain, it activates the matching
catalog ontology (e.g. `healthcare`), so stored entities are stamped with
the domain's ontology version and server-side extraction uses the domain
vocabulary.
- **Custom domains** (`--custom-domain` / `--ontology-file`) aren't in the
server catalog, so the app **creates** the ontology from the scaffold's
`backend/app/ontology_document.json` (written at generation time from your
domain YAML) and activates it.
- The same binding runs at the start of `make import` and CLI-side
`--ingest`, so imported data is domain-stamped too.

This is best-effort: if the ontology API is unavailable, the app logs a
warning and continues on `nams-default` — memory still works, just without
domain-shaped extraction. Note that the *stored* entity `type` currently
remains one of Person/Organization/Location/custom regardless of the active
ontology; activation governs the ontology-version stamp, validation mode,
and extraction vocabulary.

`make reset` on a NAMS project enumerates all entities via REST and deletes them one by one. Slow but correct:
## Resetting NAMS state

```bash
make reset
```
Resetting from the CLI is **not currently possible**: neither the NAMS REST
API nor `neo4j-agent-memory` (through 0.5.x) exposes an entity delete
endpoint, and the NAMS cypher API is read-only. `make reset` and
`--reset-database` on a NAMS project print an explanation (with the current
entity count) instead of pretending to delete.

For fast resets, use a self-hosted scaffold — `MATCH (n) DETACH DELETE n` runs in milliseconds.
Manage stored data from the NAMS dashboard at
[memory.neo4jlabs.com](https://memory.neo4jlabs.com), or use a self-hosted
scaffold for full control — `MATCH (n) DETACH DELETE n` runs in milliseconds
on bolt.

## Troubleshooting

Expand Down
14 changes: 13 additions & 1 deletion docs/docs/how-to/use-neo4j-aura.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,22 @@ uvx create-context-graph my-app \
```

This will:
- Parse the `.env` file for connection details
- Parse the `.env` file for connection details (including `NEO4J_DATABASE` when present)
- Configure the generated project to use your Aura instance
- Set `neo4j_type` to `aura` automatically

:::caution Database name

Aura instances provisioned through the Aura API or CLI often name their
database after the instance id rather than the literal string `neo4j`. If your
`.env` download includes a `NEO4J_DATABASE` line, it is imported automatically;
otherwise pass `--neo4j-database <name>` explicitly. Without it, the app
connects fine but every memory write targets a database that doesn't exist —
the `/health` endpoint will report `"memory": false` with a classified
`memory_error` when this happens.

:::

## Step 4: Verify the Connection

After scaffolding, verify the connection:
Expand Down
7 changes: 5 additions & 2 deletions docs/docs/reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ create-context-graph [PROJECT_NAME] [OPTIONS]
|--------|------|---------|-------------|
| `--domain` | `string` | *(wizard)* | Domain ID (e.g., `healthcare`, `financial-services`). Use `--list-domains` to see all. |
| `--framework` | `choice` | *(wizard)* | Agent framework: `pydanticai`, `claude-agent-sdk`, `strands`, `google-adk`, `openai-agents`, `langgraph`, `crewai`, `anthropic-tools`. |
| `--custom-domain` | `string` | -- | Natural language domain description. Requires `--anthropic-api-key`. |
| `--custom-domain` | `string` | -- | Natural language domain description. Requires `--anthropic-api-key`. Mutually exclusive with `--ontology-file`. |
| `--ontology-file` | `path` | -- | Path to a hand-written domain ontology YAML. Scaffolds directly from the file (no LLM call); overrides `--domain`, and the YAML is copied into the project as `data/ontology.yaml`. |
| `--output-dir` | `path` | `./<project-slug>` | Directory for generated project. |
| `--with-mcp` | `flag` | `false` | Generate MCP server config for Claude Desktop. |
| `--mcp-profile` | `choice` | `extended` | MCP tool profile: `core` (6 tools) or `extended` (16 tools). |
Expand Down Expand Up @@ -104,7 +105,8 @@ All of the following imply `--self-hosted` if passed without `--nams-api-key`.
| `--neo4j-uri` | `string` | `$NEO4J_URI` or `neo4j://localhost:7687` | Neo4j Bolt connection URI. |
| `--neo4j-username` | `string` | `$NEO4J_USERNAME` or `neo4j` | Neo4j username. |
| `--neo4j-password` | `string` | `$NEO4J_PASSWORD` or `password` | Neo4j password. |
| `--neo4j-aura-env` | `path` | -- | Path to Aura `.env` file. Auto-sets `neo4j_type=aura`. |
| `--neo4j-database` | `string` | `$NEO4J_DATABASE` or blank | Database name. Blank defers to the driver default (`neo4j`) — set this for instances whose database has a different name (e.g. Aura instances provisioned via the Aura API/CLI, which often name it after the instance id). Threaded through the generated app, `--ingest` seeding, and `--reset-database`. |
| `--neo4j-aura-env` | `path` | -- | Path to Aura `.env` file. Auto-sets `neo4j_type=aura`. Also imports `NEO4J_DATABASE` when the file contains one (an explicit `--neo4j-database` wins). |
| `--neo4j-local` | `flag` | `false` | Use `@johnymontana/neo4j-local` (no Docker). |

### API Keys
Expand Down Expand Up @@ -316,6 +318,7 @@ The following environment variables are read as defaults for their corresponding
| `NEO4J_URI` | `--neo4j-uri` |
| `NEO4J_USERNAME` | `--neo4j-username` |
| `NEO4J_PASSWORD` | `--neo4j-password` |
| `NEO4J_DATABASE` | `--neo4j-database` |
| `ANTHROPIC_API_KEY` | `--anthropic-api-key` |
| `OPENAI_API_KEY` | `--openai-api-key` |
| `GOOGLE_API_KEY` | `--google-api-key` |
Expand Down
8 changes: 7 additions & 1 deletion docs/docs/reference/generated-project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ my-app/
│ │ ├── constants.py # Shared constants (index names, graph projections)
│ │ ├── context_graph_client.py # Neo4j read/write client with query timeouts
│ │ ├── gds_client.py # Neo4j Graph Data Science client (label-validated)
│ │ └── vector_client.py # Vector search client with logging
│ │ ├── vector_client.py # Vector search client with logging
│ │ └── ontology_document.json # NAMS ontology doc — activated (or created, for custom domains) on connect
│ ├── tests/
│ │ ├── __init__.py
│ │ └── test_routes.py # Generated test scaffold (health, scenarios)
Expand Down Expand Up @@ -275,9 +276,14 @@ Generated demo data in a structured format:
NEO4J_URI=neo4j://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=password
NEO4J_DATABASE=
ANTHROPIC_API_KEY=
```

`NEO4J_DATABASE` is blank by default (the driver default `neo4j` is used).
Set it when your instance's database has a different name — the generated
app's Cypher sessions, memory layer, and `make import` all honor it.

### `docker-compose.yml`

Defines a Neo4j container with APOC and GDS plugins, mapped to ports 7474 (browser) and 7687 (Bolt).
Expand Down
32 changes: 31 additions & 1 deletion docs/docs/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,37 @@ title: "What's New"

Recent additions and changes to create-context-graph and its documentation.

## v0.13.0 (Current) — v0.12.0 feedback report fixes
## v0.14.0 (Current) — community PR hardening

Integrates five community PRs (#52, #56, #58, #59, #60), closes the gaps found reviewing them, and adds ~90 tests across unit, app-level, integration, and generated-project suites.

### New Features

- **`NEO4J_DATABASE` support** (self-hosted backend). New `--neo4j-database` flag / `NEO4J_DATABASE` env var, threaded through the CLI, wizard, Aura `.env` import, generated `.env`, memory layer, raw Cypher sessions, scaffold-time `--ingest`/`--reset-database`, and the generated `make import` script. Set it for Aura instances whose database isn't literally named `neo4j` (common when provisioned via the Aura API/CLI). See [Neo4j Aura guide](/docs/how-to/use-neo4j-aura).
- **Memory failures surface in `/health`.** The bolt health response gains `memory`, `memory_error`, and `memory_error_detail`; live `store_message()` failures (e.g. a wrong database name) flip the status to `degraded` instead of failing silently behind an "ok".
- **Agent tools and `POST /cypher` work on NAMS.** `execute_cypher()` dispatches read queries through the NAMS query API with result-shape coercion; routes return 503 with guidance when the NAMS client never connected.
- **`--ontology-file` scaffolds from hand-written YAML** — documented since v0.12 but previously unimplemented (issue #50). The file's declared domain id drives the scaffold and the YAML is copied to `data/ontology.yaml`. See [Add a Custom Domain](/docs/how-to/add-custom-domain).
- **Custom domains load by id** (issue #30). Anything `--list-domains` advertises — including domains saved to `~/.create-context-graph/custom-domains/` — now actually loads.

### Bug Fixes

- Schema DDL splitter rewrite: five indexes/constraints that sat behind comment headers (`person_name`, `document_title`, `document_domain`, `document_name_unique`, `local_file_fulltext`) were silently never created by `make seed`/ingest, and one comment fragment executed as garbage Cypher. A shared comment-aware `split_cypher_statements()` fixes every consumer.
- Domains without `demo_scenarios` (or with an empty `prompts` list) no longer crash scaffold generation.
- `.env.example` documents `NEO4J_DATABASE`.

### NAMS domain ontology activation

Generated apps now bind their NAMS workspace to the domain ontology on startup: NAMS pre-registers every bundled domain server-side but leaves workspaces on a generic `nams-default` ontology until one is activated — which nothing did before. Catalog domains activate in one call; custom domains are created from the scaffold's new `ontology_document.json` and then activated. The same binding runs before `make import` and CLI `--ingest`, so all stored data is stamped with the domain's ontology version and server-side extraction uses the domain vocabulary. See the [NAMS guide](/docs/how-to/use-nams).

### Live NAMS fixes

The whole NAMS flow was verified against the production service with `neo4j-agent-memory` 0.5.0, which surfaced and fixed five breaks: conversation memory silently failing on every message (the service only accepts conversation ids it minted — generated apps now create conversations per session), document/body ingest rejected wholesale (`role="document"` isn't a valid role — now `role="user"` with a metadata kind marker over server-created channels), empty document browser and schema view (the service coerces `OBJECT`/`EVENT` types to `custom` — adapters are now cypher-first using the scaffold's own description markers), dead graph expand (`get_entity(id)` doesn't exist in the 0.5.x client — resolved via the cypher API), and a NAMS "reset" that always reported success while deleting nothing (no delete API exists — `make reset` now says so and points at the [NAMS dashboard](https://memory.neo4jlabs.com)).

See the [CHANGELOG](https://github.com/neo4j-labs/create-context-graph/blob/main/CHANGELOG.md) for the full list, including the v0.13.1 feedback-triage release (dry-run credential gate fix, `/schema/models` endpoint, composite-key regression tests).

---

## v0.13.0 — v0.12.0 feedback report fixes

Addresses the May 19, 2026 v0.12.0 feedback report. The headline fix is a runtime bug on the `--self-hosted` connector ingest path that NAMS users never hit; everything else is a cluster of smaller-but-real frontend, backend, and documentation gaps. Test suite: 1,335 passing, 231 skipped (matrix/integration unchanged from baseline).

Expand Down
Loading
Loading