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
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,69 @@ job** (conditional on the project using LinkML).
First-class (wired and tested): **Pydantic, JSON Schema, OWL, SHACL** — the semantic core. Add the rest
only when a project consumes the target: **TypeScript** (`gen-typescript`), **SQL DDL / SQLAlchemy**
(`gen-sqlddl`/`gen-sqla` — keep the repository around the ORM hand-written, in `adapters/`),
**Markdown/HTML docs** (`gen-doc`), **JSON-LD context** (`gen-jsonld-context`), and **custom generators**.
**Markdown/HTML docs** (`gen-doc`), **JSON-LD context** (`gen-jsonld-context`), **Neo4j constraints +
neomodel OGM** (below), and further **custom generators** as a project needs them.

### Neo4j constraints + neomodel OGM

Two custom generators, both subclassing LinkML's `Generator` per the mechanism above. Documented
here because the behavior is LinkML-generator knowledge — but the code itself is **not a skill
asset**: a Skill's home is reusable *knowledge* (`spec/skill-repo-governance.md`), not a maintained,
tested codebase. The generators live as repo tooling at `tools/linkml_neo4j/` (tested by skillery's
own `tests/`, same pattern as `tools/repo_lint`/`tools/opencode_gen`), and `project-setup` is the one
that projects a pinned copy into a consuming repo — this skill cites them, it doesn't own them.
Enable when a project targets Neo4j:

- **`gen-neo4j-constraints`** emits Cypher DDL (uniqueness, existence, property-type constraints, and
`CREATE INDEX` for any slot annotated `annotations: {neo4j_index: true}`). Targets **Neo4j Community
Edition only** — `--profile community` (uniqueness + indexes, the subset this generator can
currently prove runs on Community) vs `--profile full` (also existence/type, carried over from an
assumption not yet re-verified against a real Community instance; see the generator's own module
docstring for the current state of that verification). It emits no relationship-level constraints
and no application-layer validation (patterns, enum membership, numeric bounds, relationship
cardinality) — that's `gen-neomodel`'s job, on the Python side, not a second Cypher-side mechanism.
- **`gen-neomodel`** emits neomodel `StructuredNode` classes, mirroring the schema's `is_a` hierarchy as
real Python inheritance (abstract LinkML classes become `__abstract_node__ = True` bases) so that a
relationship whose range is an abstract class resolves correctly instead of pointing at an undefined
name. A slot's `any_of` range restriction is documented as a trailing comment on the generated
relationship, not mechanically enforced. Also note: neomodel reserves the Python attribute names
`id`, `deleted`, `element_id` — a schema whose identifier slot is (conventionally) named `id` gets a
`_`-suffixed Python attribute (`id_`) with `db_property=...` preserving the real Neo4j property key.

#### Installing

Pick your role:

| You are… | Do this |
|---|---|
| **Scaffolding a new project** with `project-setup` | Answer LinkML (Q4.5) + Neo4j (Q5.1) in the interview, or pass `--neo4j` directly to `scaffold.sh` (product archetype only). This copies both generators into your repo's `scripts/`, pinned. |
| **Adding it to an existing project** | Re-run `project-setup` (`scaffold.sh ... --neo4j --skip-existing`) — additive, never touches unrelated files. |
| **Working inside skillery itself** | No install needed — run them directly from `tools/linkml_neo4j/`. |

The pinned copy in your repo is a **snapshot**, not a live link back to skillery — refresh it by
re-running `project-setup` with `--force` (shows what changed; never silently overwritten), same
discipline as the `meaningfy` OpenSpec schema pin (`spine-projection.md`).

#### Using

```bash
# From your project root, once scripts/gen_neo4j_constraints.py + gen_neomodel.py exist:
pip install linkml click jinja2 neomodel # generator dependencies (not runtime deps of your project)

python scripts/gen_neo4j_constraints.py model/schema.yaml --profile community > model/generated/neo4j/constraints.cypher
python scripts/gen_neomodel.py model/schema.yaml > model/generated/neomodel/ogm.py
```

Wire both into your project's `make generate-models` (or an equivalent `make neo4j-constraints` /
`make neomodel` target) the same way `gen-pydantic`/`gen-owl`/`gen-shacl` already are — see "The
`make generate-models` bridge" above. `--profile` defaults to `full`; pass `community` explicitly if
you're deploying to Neo4j Community and want only the constraint types this generator can currently
prove run there (see its module docstring for the current verification state).

Both generators ship with a synthetic fixture schema and a vendored real-world fixture as their test
suite (`tests/test_linkml_neo4j_generators.py`); `project-setup` projects a pinned copy into a
consuming repo, refreshed the same way as the `meaningfy` OpenSpec schema (re-run, review the diff,
never silently overwritten).

## Architectural boundary for generated modules

Expand Down
7 changes: 7 additions & 0 deletions .opencode/skills/project-setup/references/checklists.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ multi-component) apply only if the interview selected them.
multi-component, model `code-anatomy.md` first, then translate to contracts.
- [ ] **Model** (product) — `model/` (LinkML seed) + the `make generate-models` bridge
(`conceptual-modelling`).
- [ ] **Neo4j generators** (product, LinkML model source + Neo4j datastore selected, Q4.5 + Q5.1) —
project a **pinned copy** of `gen-neo4j-constraints`/`gen-neomodel` (repo tooling at
`tools/linkml_neo4j/` — documented, not owned, by `linkml-engineering`) into `scripts/`; wire
`make neo4j-constraints` / `make neomodel` targets alongside the existing `make
generate-models` ones. Refresh path:
re-run `project-setup`, review the diff — same discipline as the `meaningfy` OpenSpec schema
pin (`spine-projection.md`), never silently overwritten.
- [ ] **Tests** (code) — drop the `tests/` tree with the marker-injecting `conftest.py` and one
smoke unit test + one example feature (`testing-setup.md`).
- [ ] **Agentic** — render the canonical `CLAUDE.md`; create the `AGENTS.md` symlink
Expand Down
5 changes: 4 additions & 1 deletion .opencode/skills/project-setup/references/interview.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ and frameworks apply. A `library` skips them (no process); a `doc-only` skips Gr

**Q5.1 Primary datastore(s)?** (multi-select)
- Options: **MongoDB** (motor), **PostgreSQL** (SQLAlchemy/asyncpg), **Redis** (cache/streams),
*none / in-memory*.
**Neo4j** (`neomodel`), *none / in-memory*.
- **Neo4j + LinkML model source (Q4.5) together** additionally project the vendored
`gen-neo4j-constraints`/`gen-neomodel` custom generators (repo tooling at `tools/linkml_neo4j/`,
documented by `linkml-engineering`) into `scripts/` — see `checklists.md` and `layout.md`.
- Drives runtime deps, the `adapters/` repository skeleton, `infra/compose.yaml` services,
`infra/.env.example`, integration-test markers (`tests/integration/`), and the
`Datastores / external systems` bullet in `CLAUDE.md`.
Expand Down
4 changes: 4 additions & 0 deletions .opencode/skills/project-setup/references/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ first: `domain` (the book's `models/`) → `adapters` → `services` → `entryp
├── model/ # conceptual model (LinkML) — PRODUCT archetype only (R5)
│ └── schema.yaml # the domain source; make generate-models renders the targets
├── scripts/ # PINNED, projected generators — only if LinkML + Neo4j (Q4.5+Q5.1)
│ ├── gen_neo4j_constraints.py # copied from skillery's tools/linkml_neo4j/; refresh
│ └── gen_neomodel.py # = re-run project-setup, review the diff (never silent)
├── openspec/ # the SPINE (see spine-projection.md) — projected into every repo
│ ├── config.yaml # schema: meaningfy ; context: ; the 3 thin per-artifact rules
│ ├── schemas/meaningfy/ # the PINNED meaningfy schema (copied from skillery)
Expand Down
33 changes: 32 additions & 1 deletion .opencode/skills/project-setup/scripts/scaffold.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ PACKAGE="" ; PROJECT_NAME="" ; SLUG="" ; PYVER="3.12"
ORG="meaningfy-ws" ; BRANCH="develop" ; DESC="" ; YEAR="$(date +%Y)"
ARCHETYPE="product" ; TARGET="$(pwd)"
WITH_DOCS=1 ; WITH_INFRA=1 ; WITH_CI=1 ; DEPLOYABLE=0
FORCE=0 ; SKIP_EXISTING=0 ; NO_LOCK=0 ; DRY_RUN=0 ; MINIMAL=0
FORCE=0 ; SKIP_EXISTING=0 ; NO_LOCK=0 ; DRY_RUN=0 ; MINIMAL=0 ; NEO4J=0
# OpenSpec version this skill pins schemas against (kept in sync with spine/openspec-version.txt).
OPENSPEC_PIN="1.4.1"

Expand All @@ -33,6 +33,8 @@ Options:
-a, --archetype TYPE product|library|doc-only (default: product)
legacy aliases service|pipeline|cli -> product
--deployable this repo ships a deployable artifact -> CD TODO stub (ci-cd-delivery)
--neo4j project the PINNED gen-neo4j-constraints/gen-neomodel generators into
scripts/ (product + LinkML only — see references/checklists.md)
--python VER Python version (default: 3.12)
--org ORG GitHub org (default: meaningfy-ws)
--branch BRANCH default/PR branch (default: develop)
Expand Down Expand Up @@ -73,6 +75,7 @@ while [[ $# -gt 0 ]]; do
--desc) DESC="$2"; shift 2;;
--target) TARGET="$2"; shift 2;;
--deployable) DEPLOYABLE=1; shift;;
--neo4j) NEO4J=1; shift;;
--minimal) MINIMAL=1; shift;;
--no-docs) WITH_DOCS=0; shift;;
--no-infra) WITH_INFRA=0; shift;;
Expand Down Expand Up @@ -217,6 +220,33 @@ scaffold_openspec() {
fi
}

# scaffold_neo4j_generators : project the PINNED gen-neo4j-constraints/gen-neomodel
# generators (tools/linkml_neo4j/ — maintained, tested code; NOT a skill asset, see
# linkml-engineering's generation-and-templates.md for why) into scripts/. Only
# meaningful for a product using LinkML with a Neo4j target (--neo4j); see
# references/checklists.md.
scaffold_neo4j_generators() {
local gen_src="$SCRIPT_DIR/../../../tools/linkml_neo4j"
local gen_dst="$TARGET/scripts"
if [[ "$DRY_RUN" -eq 1 ]]; then
[[ -d "$gen_dst" && -f "$gen_dst/gen_neo4j_constraints.py" ]] \
&& echo " = keep scripts/gen_neo4j_constraints.py, scripts/gen_neomodel.py (pinned)" \
|| echo " + create scripts/gen_neo4j_constraints.py, scripts/gen_neomodel.py (pinned, copied from skillery)"
elif [[ -d "$gen_src" ]]; then
mkdir -p "$gen_dst"
for f in gen_neo4j_constraints.py gen_neomodel.py; do
if [[ -f "$gen_dst/$f" && "$FORCE" -ne 1 ]]; then
echo " skip (exists): scripts/$f (re-run with --force to refresh; never clobbered in place)"
else
cp "$gen_src/$f" "$gen_dst/$f"
echo " copied scripts/$f (PINNED — refresh via --force; see references/checklists.md)"
fi
done
else
echo " NOTE: linkml-engineering generators not found ($gen_src) — copy scripts/gen_neo4j_constraints.py + scripts/gen_neomodel.py from skillery manually."
fi
}

# ---- minimal mode: agentic files + .claude/ layout only -------------------
if [[ "$MINIMAL" -eq 1 ]]; then
echo "MINIMAL mode — agentic files (CLAUDE.md + AGENTS symlink) + .claude/ layout only."
Expand Down Expand Up @@ -346,6 +376,7 @@ fi
# ---- 5. agentic layer (CLAUDE-canonical) + spine (openspec/) ---------------
scaffold_agentic
scaffold_openspec
[[ "$PRODUCT" -eq 1 && "$NEO4J" -eq 1 ]] && scaffold_neo4j_generators

# ---- 6. docs pillar (Antora) ----------------------------------------------
if [[ "$WITH_DOCS" -eq 1 ]]; then
Expand Down
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ that adds only Claude-specific guidance.

## How to maintain / extend the catalogue

- **Adding a new skill** — follow [`spec/CREATING_SKILLS.md`](spec/CREATING_SKILLS.md).
- **Adding a new skill** — follow [`spec/CREATING_SKILLS.md`](spec/CREATING_SKILLS.md), **then run
`make skill-inventory`** to regenerate [`docs/skill-inventory.md`](docs/skill-inventory.md) (the
map + per-bundle tables). This part is manual — you (or the agent) must run it after adding,
renaming, re-bundling, re-describing, or re-categorising a skill. Forgetting is caught
automatically, not silently: `tests/test_skill_inventory.py` fails `make test`/`make validate` if
the committed file has drifted from a fresh regeneration. A new skill also needs a `PURPOSE_OF`
entry in [`tools/skill_inventory.py`](tools/skill_inventory.py) — the one hand-curated mapping in
an otherwise fully generated file; generation itself raises loudly if a skill is missing one.
- **Assigning to a bundle** — bundles are declared in [`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json).
- **Boundary / related-skills** — every skill's frontmatter must declare its `boundary` and list
any `related_skills`. This keeps triggers crisp and prevents collisions with external neighbours.
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: install validate lint test validate-spine fix-spec-links generate-opencode
.PHONY: install validate lint test validate-spine fix-spec-links generate-opencode skill-inventory

install:
python3 -m venv .venv && . .venv/bin/activate && pip install -q -r requirements-dev.txt
Expand All @@ -24,6 +24,12 @@ generate-opencode:
test:
. .venv/bin/activate && python -m pytest tests/ -q

# skill-inventory = regenerate docs/skill-inventory.md (purpose + related skills,
# derived from each SKILL.md + marketplace.json). Freshness enforced by
# tests/test_skill_inventory.py, part of `make test`.
skill-inventory:
. .venv/bin/activate && python -m tools.skill_inventory

# validate-spine = the structural gate on the OpenSpec spine (needs node + npx).
# Kept separate from `validate` so the Python guardrail runs without a node
# toolchain. The clarity gate (semantic, on the PLAN) is run by a human/agent,
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ Bundles are organised by the **role (hat) you wear** — install `meaningfy-core

## What's inside

20 skills in **4 role bundles** — every skill lives in exactly one bundle (no duplication):
22 skills in **4 role bundles** — every skill lives in exactly one bundle (no duplication); the
table below is bundle-level only. For the per-skill picture — purpose, which cross-cutting concern
each one serves, and which skills depend on which — see
[`docs/skill-inventory.md`](docs/skill-inventory.md) (a generated map + tables, not hand-maintained).

| Bundle | Skills | Install if you… |
|--------|--------|-----------------|
Expand Down
Loading
Loading