Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
a7e441b
Merge tag '1.2.1' into develop
turegjorup Mar 6, 2026
6b4cd9a
Merge pull request #26 from itk-dev/release/1.2.2
turegjorup May 22, 2026
9d863fc
Merge tag '1.2.2' into develop
turegjorup May 22, 2026
3a35e22
chore: add Claude Code setup adapted to this repo
turegjorup May 22, 2026
fb0a439
style: wrap CLAUDE.md prose at 120 cols for markdownlint
turegjorup May 22, 2026
e7cf497
docs: add CHANGELOG entry for Claude Code setup
turegjorup May 22, 2026
6234209
Merge pull request #27 from itk-dev/feature/claude-code-setup
turegjorup May 22, 2026
017e08e
chore: adapt Claude Code setup from event-database-imports
turegjorup Jul 7, 2026
f11230f
chore: add Claude Code automations (skills, subagent, spec-drift hook)
turegjorup Jul 7, 2026
1de10df
docs: tighten CHANGELOG entry to match house tone
turegjorup Jul 7, 2026
50004ec
fix: resolve composer audit failures
turegjorup Jul 7, 2026
96ecfa8
Merge pull request #30 from itk-dev/fix/composer-audit
turegjorup Jul 7, 2026
8190724
Merge remote-tracking branch 'origin/develop' into feature/adapt-clau…
turegjorup Jul 7, 2026
6dd3fad
Merge branch 'feature/adapt-claude-setup-from-imports' into feature/c…
turegjorup Jul 7, 2026
7074e11
Merge pull request #28 from itk-dev/feature/adapt-claude-setup-from-i…
turegjorup Jul 7, 2026
cb07823
Merge remote-tracking branch 'origin/develop' into feature/claude-aut…
turegjorup Jul 7, 2026
f22895b
Merge pull request #29 from itk-dev/feature/claude-automations
turegjorup Jul 7, 2026
b439776
chore: align dev tooling with event-database-imports
turegjorup Jul 7, 2026
0f60d03
Merge pull request #31 from itk-dev/chore/align-dev-tooling
turegjorup Jul 7, 2026
1aac590
chore: pay down PHPStan baseline
turegjorup Jul 7, 2026
16a06bb
test: expand API contract test coverage
turegjorup May 22, 2026
45414ca
test: add JSON-LD contract tests to guard against API Platform BC cha…
turegjorup Jul 7, 2026
afb110d
docs: note item-as-collection quirk as a TODO for API versioning
turegjorup Jul 7, 2026
1c10945
docs: correct README fixtures wording (Elasticsearch, not Doctrine)
turegjorup Jul 7, 2026
122b396
Merge pull request #33 from itk-dev/test/mature-api-suite
turegjorup Jul 7, 2026
02c8ac8
Merge remote-tracking branch 'origin/develop' into chore/phpstan-base…
turegjorup Jul 7, 2026
da27308
Merge pull request #32 from itk-dev/chore/phpstan-baseline-paydown
turegjorup Jul 7, 2026
0b05b18
Merge remote-tracking branch 'origin/develop' into docs/item-as-colle…
turegjorup Jul 7, 2026
1e39e0c
test: apply production-parity Elasticsearch mappings in the test harness
turegjorup Jul 8, 2026
33a3d2d
test: add unit tests for the Elasticsearch filters
turegjorup Jul 8, 2026
1bb6847
ci: upload test coverage to Codecov
turegjorup Jul 8, 2026
7dc5364
ci: use codecov-action@v7
turegjorup Jul 8, 2026
ea069ee
Merge pull request #34 from itk-dev/docs/item-as-collection-todo
turegjorup Jul 8, 2026
dee1e27
refactor: extract SearchParamsBuilder from ElasticSearchIndex
turegjorup Jul 8, 2026
50ebe59
Merge remote-tracking branch 'origin/develop' into test/prod-parity-m…
turegjorup Jul 8, 2026
9a496cd
Merge remote-tracking branch 'origin/develop' into test/filter-unit-t…
turegjorup Jul 8, 2026
6ac008d
Merge pull request #35 from itk-dev/test/prod-parity-mappings
turegjorup Jul 8, 2026
43bf6d1
Merge remote-tracking branch 'origin/develop' into ci/enable-codecov
turegjorup Jul 8, 2026
f291082
Merge remote-tracking branch 'origin/develop' into refactor/search-pa…
turegjorup Jul 8, 2026
45524f9
Merge remote-tracking branch 'origin/develop' into test/filter-unit-t…
turegjorup Jul 8, 2026
c756231
Merge pull request #36 from itk-dev/test/filter-unit-tests
turegjorup Jul 8, 2026
db75e00
Merge remote-tracking branch 'origin/develop' into ci/enable-codecov
turegjorup Jul 8, 2026
d1e50fc
Merge pull request #37 from itk-dev/ci/enable-codecov
turegjorup Jul 8, 2026
9ce645e
ci: update GitHub Actions to latest versions
turegjorup Jul 8, 2026
4103326
ci: improve the API-spec workflow (adopted from os2display)
turegjorup Jul 8, 2026
9dea214
Merge remote-tracking branch 'origin/develop' into refactor/search-pa…
turegjorup Jul 8, 2026
72f8ee6
ci: run the Code Review workflow via docker compose (drop Task)
turegjorup Jul 8, 2026
8722b8f
Merge pull request #38 from itk-dev/refactor/search-params-builder
turegjorup Jul 8, 2026
d6f37ac
Merge remote-tracking branch 'origin/develop' into ci/checkout-v6
turegjorup Jul 8, 2026
35cca04
Merge remote-tracking branch 'origin/develop' into ci/apispec-adopt-o…
turegjorup Jul 8, 2026
bb37467
Merge remote-tracking branch 'origin/develop' into ci/pr-workflow-doc…
turegjorup Jul 8, 2026
0edad95
Merge pull request #39 from itk-dev/ci/checkout-v6
turegjorup Jul 8, 2026
2211a92
Merge remote-tracking branch 'origin/develop' into ci/apispec-adopt-o…
turegjorup Jul 8, 2026
74de5a0
Merge pull request #40 from itk-dev/ci/apispec-adopt-os2display
turegjorup Jul 8, 2026
5477478
Merge remote-tracking branch 'origin/develop' into ci/pr-workflow-doc…
turegjorup Jul 8, 2026
dec89af
Merge pull request #41 from itk-dev/ci/pr-workflow-docker-compose
turegjorup Jul 8, 2026
107873d
fix: return HTTP 400 for malformed date-range filter input
turegjorup Jul 8, 2026
2550dd3
docs: add CHANGELOG entry for date-range filter error contract fix
turegjorup Jul 8, 2026
c6ac73c
test: assert filter identities, sort order, and pagination edges
turegjorup Jul 8, 2026
2ddd39c
docs: add CHANGELOG entry for filter identity and sort assertions
turegjorup Jul 8, 2026
e251546
Merge pull request #42 from itk-dev/fix/date-range-filter-error-contract
turegjorup Jul 8, 2026
0cb9d10
Merge remote-tracking branch 'origin/develop' into test/filter-identi…
turegjorup Jul 8, 2026
52ed189
Merge pull request #43 from itk-dev/test/filter-identity-and-sort-ass…
turegjorup Jul 8, 2026
d190b09
test: validate deep payload schemas for every resource
turegjorup Jul 8, 2026
826c50e
docs: add CHANGELOG entry for deep payload contract schemas
turegjorup Jul 8, 2026
2677e6d
Merge pull request #44 from itk-dev/test/deep-payload-contract-schemas
turegjorup Jul 8, 2026
b876344
ci: check index mappings against event-database-imports
turegjorup Jul 8, 2026
3ff98bb
docs: add CHANGELOG entry for index mapping drift check
turegjorup Jul 8, 2026
0f763c7
Merge pull request #45 from itk-dev/ci/index-mapping-drift-check
turegjorup Jul 8, 2026
6e1cab1
chore: upgrade api-platform/core 4.1 to 4.3
turegjorup Jul 8, 2026
9c8f55e
docs: add CHANGELOG entry for api-platform 4.3 upgrade
turegjorup Jul 8, 2026
97b066f
test: add aarhusguiden consumer contract suite
turegjorup Jul 8, 2026
2d7e1de
docs: add CHANGELOG entry for aarhusguiden consumer contract suite
turegjorup Jul 8, 2026
2e2967d
test: add os2display consumer contract suite
turegjorup Jul 8, 2026
8d7503e
docs: add CHANGELOG entry for os2display consumer contract suite
turegjorup Jul 8, 2026
1a90e3d
Merge pull request #47 from itk-dev/test/aarhusguiden-consumer-contract
turegjorup Jul 8, 2026
a7f5ea3
Merge remote-tracking branch 'origin/develop' into test/os2display-co…
turegjorup Jul 8, 2026
f787bd3
Merge remote-tracking branch 'origin/develop' into chore/upgrade-api-…
turegjorup Jul 8, 2026
95ffe8e
Merge pull request #48 from itk-dev/test/os2display-consumer-contract
turegjorup Jul 8, 2026
e974407
Merge remote-tracking branch 'origin/develop' into chore/upgrade-api-…
turegjorup Jul 8, 2026
8880aac
ci: bump oasdiff-action from v0.0.44 to v0.1.5
turegjorup Jul 8, 2026
191e1ed
docs: add CHANGELOG entry for oasdiff-action bump
turegjorup Jul 8, 2026
928ffda
Merge pull request #49 from itk-dev/ci/bump-oasdiff-action-v0.1.5
turegjorup Jul 8, 2026
38d1157
Merge remote-tracking branch 'origin/develop' into chore/upgrade-api-…
turegjorup Jul 8, 2026
a7483dc
ci: fold oasdiff summary and review link into one PR comment
turegjorup Jul 8, 2026
9da1a20
docs: add CHANGELOG entry for inline API-spec diff comment
turegjorup Jul 8, 2026
032c052
Merge pull request #51 from itk-dev/ci/oasdiff-inline-summary-comment
turegjorup Jul 8, 2026
7a6f435
Merge remote-tracking branch 'origin/develop' into chore/upgrade-api-…
turegjorup Jul 8, 2026
d130266
Merge pull request #46 from itk-dev/chore/upgrade-api-platform-4.3
turegjorup Jul 8, 2026
947c090
test: pin the problem+json error/negotiation contract
turegjorup Jul 8, 2026
e5c9d61
docs: add CHANGELOG entry for problem+json error contract test
turegjorup Jul 8, 2026
d088d39
ci: compare effective schemas in oasdiff (flatten-allof)
turegjorup Jul 8, 2026
ef1d46f
docs: add CHANGELOG entry for oasdiff flatten-allof
turegjorup Jul 8, 2026
7fa98e3
docs: cite the formats/error config behind the problem+json contract
turegjorup Jul 8, 2026
701598b
chore: add Rector with the relevant sets and apply across the codebase
turegjorup Jul 8, 2026
454daa2
docs: add CHANGELOG entry for Rector
turegjorup Jul 8, 2026
e34ba08
Merge pull request #52 from itk-dev/test/error-contract-problem-json
turegjorup Jul 8, 2026
c2534e3
chore: re-export API spec after Rector's constructor promotion
turegjorup Jul 8, 2026
977a675
Merge remote-tracking branch 'origin/develop' into ci/oasdiff-flatten…
turegjorup Jul 8, 2026
2bc1b91
Merge remote-tracking branch 'origin/develop' into chore/add-rector
turegjorup Jul 8, 2026
04a5a2e
Merge pull request #53 from itk-dev/ci/oasdiff-flatten-allof
turegjorup Jul 8, 2026
ed08be4
Merge remote-tracking branch 'origin/develop' into chore/add-rector
turegjorup Jul 8, 2026
47ba1d6
ci: self-heal the "spec up to date" PR comment
turegjorup Jul 8, 2026
d339a6a
docs: add CHANGELOG entry for api-spec comment self-heal
turegjorup Jul 8, 2026
29ed4de
Merge pull request #54 from itk-dev/chore/add-rector
turegjorup Jul 8, 2026
216c894
Merge remote-tracking branch 'origin/develop' into ci/api-spec-export…
turegjorup Jul 8, 2026
fc11e8d
Merge pull request #55 from itk-dev/ci/api-spec-export-comment-selfheal
turegjorup Jul 8, 2026
98b14d0
ci: scope image pulls to the services each job needs
turegjorup Jul 8, 2026
08a712d
docs: add CHANGELOG entry for scoped CI image pulls
turegjorup Jul 8, 2026
d4463c7
Merge remote-tracking branch 'origin/develop' into ci/scope-ci-image-…
turegjorup Jul 8, 2026
2df028e
Merge pull request #56 from itk-dev/ci/scope-ci-image-pulls
turegjorup Jul 8, 2026
9fcdcf2
chore: run Rector from the Claude Code setup
turegjorup Jul 9, 2026
cad1111
docs: add CHANGELOG entry for Rector Claude Code hook
turegjorup Jul 9, 2026
63839c8
ci: drop redundant root user from the release workflow
turegjorup Jul 9, 2026
c41ac2b
Merge pull request #57 from itk-dev/ci/rector-claude-hook
turegjorup Jul 9, 2026
de52bf3
chore: update dependencies (in-constraint)
turegjorup Jul 9, 2026
5251e67
docs: add CHANGELOG entry for in-constraint dependency update
turegjorup Jul 9, 2026
60bc946
Merge pull request #58 from itk-dev/chore/update-dependencies
turegjorup Jul 9, 2026
24717d6
docs: refresh CLAUDE.md and agent docs for current tooling
turegjorup Jul 9, 2026
7dc5c25
docs: add CHANGELOG entry for docs refresh
turegjorup Jul 9, 2026
3e6860a
Merge pull request #59 from itk-dev/docs/phpstan-level-8
turegjorup Jul 9, 2026
4da346f
chore: upgrade Elasticsearch to 8.19.18
turegjorup Jul 9, 2026
ff315c7
Merge pull request #60 from itk-dev/chore/elasticsearch-8.19
turegjorup Jul 9, 2026
5ce5808
ci: fix api-spec no-change detection for oasdiff v0.1.x
turegjorup Jul 9, 2026
4e3dcfc
docs: add CHANGELOG entry for api-spec no-change detection fix
turegjorup Jul 9, 2026
c323c7b
Merge pull request #61 from itk-dev/ci/api-spec-no-change-detection
turegjorup Jul 9, 2026
1fbdb87
docs: document per-resource API test structure in CLAUDE.md
turegjorup Jul 10, 2026
9ed7d85
chore: release 1.3.0
turegjorup Jul 10, 2026
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
38 changes: 38 additions & 0 deletions .claude/agents/filter-provider-reviewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: filter-provider-reviewer
description: Review changes to Elasticsearch filters and state providers for query-DSL correctness and index-contract alignment. Use after editing anything under src/Api/Filter/ElasticSearch/ or src/Api/State/.
---

You review the riskiest code in this repo: the custom Elasticsearch filters (`src/Api/Filter/ElasticSearch/`) and
the state providers (`src/Api/State/`) that assemble them. Unlike stock API Platform filters, these emit **raw
Elasticsearch query DSL** rather than mutating a Doctrine query builder, and the document mappings they target are
owned by `event-database-imports` — so a generic code reviewer misses the failure modes that matter here.

Focus your review on:

1. **Query-DSL correctness.** `apply()` returns ES query fragments, not query-builder mutations. Check the emitted
DSL is well-formed for its intent (`term`/`terms` vs `match`, `range` bounds, `bool` `must`/`should`/`filter`
nesting) and that clauses compose rather than overwrite when multiple filters apply to one request.

2. **Filter vs sort separation.** `AbstractProvider::getFilters()` splits filter clauses from sort clauses via the
`SortFilterInterface` marker. A new sorting filter must implement that marker or it will be treated as a query
clause; a query filter must not.

3. **Index-contract alignment — check the MAPPED field type, not assumed behaviour.** Field names/types referenced
in the DSL must match what `event-database-imports` writes. The production-parity mappings are copied in
`tests/resources/mappings/*.json` — consult them. Matching semantics depend on the mapped type: a `keyword` field
(e.g. `tags`, `slug`, `vocabulary`, `postalCode`) matches **exact, case-sensitive, whole-value** (no tokenisation);
a `text` field (e.g. `title`, `name`, `description`) is **tokenised** (word match). Do not assume dynamic-mapping
behaviour. A typo'd/renamed field, or a filter that assumes token matching on a `keyword` field, yields
empty/incorrect results silently — not an error. Confirm against `tests/resources/mappings/<index>.json` or a live
mapping (`docker compose exec -T phpfpm curl -s http://elasticsearch:9200/<index>/_mapping`).

4. **Pagination & limits.** `paginationMaximumItemsPerPage` on the DTO and `AbstractProvider::MAX_PAGE_SIZE_FALLBACK`
(20) bound page size — check a new provider respects them and returns `SearchResults` for collections / a single
array for items.

5. **Filter registration.** Filters are attached via `#[ApiFilter(...)]` on the DTO and picked up through
`api_platform.filter_locator`. Confirm a new filter is actually reachable that way, not just defined.

Report concrete, file:line findings ranked by severity. Prefer verifying a claim against the code or a live ES
query over speculating. Do not restate what is correct at length — surface what is wrong or unverifiable.
31 changes: 31 additions & 0 deletions .claude/agents/pr-readiness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: pr-readiness
description: Run all CI-equivalent checks locally before creating a PR
model: haiku
---

Mirror what `.github/workflows/*.yaml` runs on a PR. Every step executes inside Docker (the project
ships only its `phpfpm` / `prettier` / `markdownlint` services — there is no `node` service and no
JS/CSS to lint). Stop early if a critical check fails.

## Checks

1. **Composer validate**: `docker compose exec -T phpfpm composer validate --strict`
2. **Composer normalize (dry-run)**: `docker compose exec -T phpfpm composer normalize --dry-run`
3. **PHP coding standards**: `task coding-standards:php:check`
4. **Twig coding standards**: `task coding-standards:twig:check`
5. **YAML coding standards**: `task coding-standards:yaml:check`
6. **Markdown coding standards**: `task coding-standards:markdown:check`
7. **PHPStan (level 8)**: `task code-analysis:phpstan`
8. **Rector (dry-run)**: `task code-analysis:rector` — the `Rector` gate fails on any suggested change; run `task code-analysis:rector:apply` to fix.
9. **Test fixtures + API tests**: `task fixtures:load:test --yes && task api:test`. Tests hit a real Elasticsearch, so fixtures must be loaded first. If the load fails with "No alive nodes", run `docker compose up --detach --wait` and retry — see the `reload-fixtures` agent for the full recovery dance.
10. **API spec up to date** (mirrors `.github/workflows/api-spec.yml`):

- `task api:spec:export`
- `git diff --exit-code public/spec.yaml` — must be clean.

1. **CHANGELOG updated**: `git diff develop -- CHANGELOG.md` should show at least one entry under `## [Unreleased]`.

## Output

Report a summary table with columns: Check Name, Status (pass/fail), and error output for failures.
25 changes: 25 additions & 0 deletions .claude/agents/reload-fixtures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: reload-fixtures
description: Reload Elasticsearch fixtures and recover from a not-ready cluster
model: haiku
---

This project's "data layer" is Elasticsearch — there is no Doctrine database to migrate. After changing
API resources, filters, or anything that affects search behavior you generally want to reload fixtures
so subsequent manual / test runs see a consistent dataset.

Two flavours of fixtures exist (see `src/Model/IndexName.php` for the seven index names):

- **Dev fixtures** — pulled from the `event-database-imports` repo on GitHub: `task fixtures:load`
- **Test fixtures** — read from `tests/resources/*.json` and used by the PHPUnit suite: `task fixtures:load:test --yes`

## Steps

1. Confirm with the user (or take it from their prompt) whether to load **dev** or **test** fixtures.
2. Make sure the stack is up: `docker compose up --detach --wait`. The `--wait` is important — Elasticsearch is slow to become ready and the fixture loader fails fast with "No alive nodes" otherwise.
3. Run the load command (`task fixtures:load` or `task fixtures:load:test --yes`). The Taskfile prompts for confirmation unless `--yes` is passed.
4. If the command fails with "No alive nodes" or any Elasticsearch connection error:
- Poll the cluster health endpoint until it returns HTTP 200:
`docker compose exec elasticsearch curl 'http://localhost:9200/_cluster/health?wait_for_status=yellow&timeout=5s' --verbose`
- Re-run the fixture load command.
5. Report which indices were loaded and any non-fatal warnings.
197 changes: 197 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"COMPOSE_USER": "deploy"
},
"permissions": {
"allow": [
"Bash(cat:*)",
"Bash(diff:*)",
"Bash(echo:*)",
"Bash(find:*)",
"Bash(gh:*)",
"Bash(git:*)",
"Bash(grep:*)",
"Bash(head:*)",
"Bash(ls:*)",
"Bash(pwd)",
"Bash(tail:*)",
"Bash(task:*)",
"Bash(tree:*)",
"Bash(wc:*)",
"Bash(which:*)",
"Bash(docker compose exec:*)",
"Bash(docker compose run:*)",
"Bash(docker compose up:*)",
"Bash(docker compose ps:*)",
"Bash(docker compose logs:*)",
"Bash(docker compose top:*)",
"Bash(docker compose config:*)",
"Bash(docker compose pull:*)",
"Bash(docker compose images:*)",
"Bash(docker network:*)"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(gh issue delete:*)",
"Bash(gh release delete:*)",
"Bash(gh repo delete:*)",
"Bash(gh label delete:*)",
"Read(./.env.local)",
"Read(./.env.local.*)",
"Read(./config/secrets/*)"
],
"ask": [
"Bash(docker compose down:*)",
"Bash(docker compose stop:*)",
"Bash(docker compose rm:*)",
"Bash(docker compose restart:*)",
"Bash(gh issue create:*)",
"Bash(gh issue close:*)",
"Bash(gh issue edit:*)",
"Bash(gh issue comment:*)",
"Bash(gh pr create:*)",
"Bash(gh pr close:*)",
"Bash(gh pr merge:*)",
"Bash(gh pr edit:*)",
"Bash(gh pr comment:*)",
"Bash(gh pr review:*)",
"Bash(gh release create:*)",
"Bash(gh release edit:*)",
"Bash(gh repo create:*)",
"Bash(gh label create:*)",
"Bash(gh label edit:*)",
"Bash(git push:*)",
"Bash(git branch -d:*)",
"Bash(git branch -D:*)",
"Bash(git tag -d:*)",
"Bash(git tag -a:*)",
"Bash(git tag :*)",
"Bash(git reset:*)",
"Bash(git rebase:*)",
"Bash(git merge:*)",
"Bash(git stash drop:*)",
"Bash(git clean:*)",
"Bash(git checkout -- :*)",
"Bash(git restore:*)",
"Bash(git commit:*)"
]
},
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "docker compose up --detach --quiet-pull 2>/dev/null || true",
"timeout": 60,
"statusMessage": "Starting Docker services..."
}
]
},
{
"matcher": "startup|resume|clear",
"hooks": [
{
"type": "command",
"command": "command -v jq >/dev/null 2>&1 || { echo '⚠️ jq is not installed on the host — the Edit/Write formatting hooks read the edited file path from the tool payload via jq and will silently no-op until you install it (brew install jq).' >&2; exit 2; }",
"timeout": 10,
"statusMessage": "Checking host prerequisites..."
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in */composer.lock|*/symfony.lock|*/.env.local|*/.env.*.local|*/config/reference.php|*/vendor/*|*/node_modules/*|*/var/cache/*) echo 'BLOCKED: this path is generated, locked, or operator-only — edit the source instead' >&2; exit 2 ;; esac"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); REL_PATH=\"${f#\"$CLAUDE_PROJECT_DIR\"/}\"; case \"$REL_PATH\" in src/*.php|tests/*.php) docker compose exec -T phpfpm vendor/bin/rector process --no-progress-bar \"$REL_PATH\" >/dev/null 2>&1 || true ;; esac",
"timeout": 60
},
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in *.php) REL_PATH=\"${f#\"$CLAUDE_PROJECT_DIR\"/}\"; docker compose exec -T phpfpm vendor/bin/php-cs-fixer fix --quiet \"$REL_PATH\" 2>/dev/null || true ;; esac",
"timeout": 30
},
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in *.php) REL_PATH=\"${f#\"$CLAUDE_PROJECT_DIR\"/}\"; docker compose exec -T phpfpm vendor/bin/phpstan analyse --no-progress --error-format=raw \"$REL_PATH\" 2>/dev/null || true ;; esac",
"timeout": 45
},
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in *.twig) REL_PATH=\"${f#\"$CLAUDE_PROJECT_DIR\"/}\"; docker compose exec -T phpfpm vendor/bin/twig-cs-fixer lint --fix \"$REL_PATH\" 2>/dev/null || true ;; esac",
"timeout": 15
},
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in */composer.json) docker compose exec -T phpfpm composer normalize --quiet 2>/dev/null || true ;; esac",
"timeout": 30
},
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in *.yaml|*.yml|*.js|*.css) REL_PATH=\"${f#\"$CLAUDE_PROJECT_DIR\"/}\"; docker compose run --rm -T prettier \"$REL_PATH\" --write 2>/dev/null || true ;; esac",
"timeout": 30
},
{
"type": "command",
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in *.md) REL_PATH=\"${f#\"$CLAUDE_PROJECT_DIR\"/}\"; docker compose run --rm -T markdownlint markdownlint --fix \"$REL_PATH\" 2>/dev/null || true ;; esac",
"timeout": 30
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "docker compose exec -T phpfpm bin/console lint:container 2>/dev/null || true",
"timeout": 30,
"statusMessage": "Validating Symfony DI container..."
},
{
"type": "command",
"command": "sh \"$CLAUDE_PROJECT_DIR/scripts/claude-hook-check-index-contract.sh\" || true",
"timeout": 10,
"statusMessage": "Checking Elasticsearch index contract..."
},
{
"type": "command",
"command": "sh \"$CLAUDE_PROJECT_DIR/scripts/claude-hook-check-spec-drift.sh\" || true",
"timeout": 10,
"statusMessage": "Checking OpenAPI spec is up to date..."
}
]
}
]
},
"enabledPlugins": {
"php-lsp@claude-plugins-official": true,
"code-simplifier@claude-plugins-official": true,
"context7@claude-plugins-official": true,
"code-review@claude-plugins-official": true,
"security-guidance@claude-plugins-official": true,
"playwright@claude-plugins-official": true,
"feature-dev@claude-plugins-official": true,
"itkdev-skills@itkdev-marketplace": true
},
"enabledMcpjsonServers": [
"context7"
],
"enableAllProjectMcpServers": true
}
45 changes: 45 additions & 0 deletions .claude/skills/changelog-entry/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: changelog-entry
description: Add or fix this repo's CHANGELOG.md entry for the current PR, in the exact house format. Use before opening a PR or when resolving a CHANGELOG merge conflict.
disable-model-invocation: true
---

Add a changelog line for the current work to `CHANGELOG.md`, following this repo's convention exactly (getting it wrong causes the `changelog` CI check in `.github/workflows/changelog.yaml` to fail and creates repeated merge conflicts).

## Format

Under the `## [Unreleased]` heading, entries are a **two-line wrapped** list item:

```
- [PR-N](https://github.com/itk-dev/event-database-api/pull/N)
Short imperative description of the change
```

Rules:

- One entry per PR. Ordered by **descending PR number** (newest on top).
- The URL is always the `event-database-api` repo (not `event-database-imports`).
- **Keep it short and terse — match the surrounding entries.** The description is a single concise noun or
imperative phrase naming *what* changed, not a sentence (or paragraph) explaining *how*. It fits on one wrapped
line and almost always well under the 120-char limit (markdownlint MD013). If you're tempted to list mechanisms,
reasons, or multiple clauses, cut them — that detail belongs in the PR description, not the changelog.
- Good (real entries): `Symfony 7.3 and PHP 8.4` · `Re-lint YAML files` ·
`Add Claude Code project setup (CLAUDE.md, agents, skills)`
- Too long: `Adapt Claude Code setup from event-database-imports: fix the Edit/Write hook mechanism (read the
file path from the tool payload via jq instead of the unset CLAUDE_FILE_PATH), guard the …`
- Some historical entries are plain bullets without a PR link (e.g. dependency bumps) — that's fine for changes
without a PR, but PR-based work always gets the `[PR-N]` form.

## Two-step PR number

The PR number isn't known until the PR exists. So:

1. Add the entry now with a `PR-XX` placeholder and `pull/XX` URL.
2. After the PR is opened (`gh pr view --json number`), replace `XX` with the real number in a follow-up commit (`docs: set PR number in CHANGELOG entry`).

## Steps

1. `git fetch origin develop` and read the current `[Unreleased]` block so you insert in the right order and don't duplicate.
2. Insert the entry at the correct descending-number position.
3. Lint: `docker compose run --rm markdownlint markdownlint CHANGELOG.md`.
4. If a PR already exists, set the real number; otherwise leave `XX` and remind the user of step 2.
39 changes: 39 additions & 0 deletions .claude/skills/new-resource/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: new-resource
description: Scaffold a new read-only API resource backed by an Elasticsearch index, following this repo's DTO + provider + filter pattern. Use when exposing a new index (or a new shape of an existing one) under /api/v2.
disable-model-invocation: true
---

Add a new API Platform resource. This repo's resources are **DTOs backed by Elasticsearch**, never Doctrine
entities — every resource follows the same three-part shape, with `Event` as the canonical template. Read
`src/Api/Dto/Event.php`, `src/Api/State/EventRepresentationProvider.php`, and `tests/ApiPlatform/EventsTest.php`
before starting, and mirror them.

## Steps

1. **Confirm the index.** The seven indices live in `src/Model/IndexName.php`. If the resource maps to an existing
index, reuse that enum case. If it's a genuinely new index, add a case — and remember the mapping is owned by
`event-database-imports` (see CLAUDE.md → "Works with event-database-imports"); coordinate the contract there.

2. **DTO** — `src/Api/Dto/<Resource>.php`: an `#[ApiResource]` class declaring operations, pagination
(`paginationMaximumItemsPerPage`), and `#[ApiFilter(...)]` attributes. The `$id` property is identifier-only
(PHPStan "unused" errors on it are intentionally ignored); real fields are filled by the provider. Attach
filters from `src/Api/Filter/ElasticSearch/` (`MatchFilter`, `IdFilter`, `BooleanFilter`, `DateRangeFilter`,
`DateFilter`, `TagFilter`) — do not invent a Doctrine filter.

3. **Provider** — `src/Api/State/<Resource>RepresentationProvider.php`: implement `ProviderInterface` by extending
`AbstractProvider`. Use `getFilters()` to compile the declared `#[ApiFilter]` attributes into ES query
fragments, call `IndexInterface::search()`, and return a `SearchResults` (collection op) or a single array
(item op). Wire the DTO's `provider:` to this class. Providers are autoconfigured — only touch
`config/services.yaml` if you need explicit arguments.

4. **Test** — `tests/ApiPlatform/<Resource>Test.php`: extend `AbstractApiTestCase`, `use` `GetEntitiesTestTrait`
and `GetItemTestTrait`, and set the static props (`$requestPath`, `$resourceClass`, `$itemId`,
`$unknownItemId`). Add resource-specific filter assertions in a `<Resource>FilterTest.php`. Add fixture rows to
`tests/resources/<index>.json` so `$itemId` resolves (see `tests/resources/README.md`).

5. **Regenerate the OpenAPI spec** — run the `/update-api-spec` skill (or `task api:spec:export`). CI
(`.github/workflows/api-spec.yml`) fails if `public/spec.yaml` is stale.

6. **Verify** — `task fixtures:load:test --yes` then `task api:test -- --filter <Resource>`. Run
`task coding-standards:check` and `task code-analysis` before opening the PR.
18 changes: 18 additions & 0 deletions .claude/skills/update-api-spec/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: update-api-spec
description: Regenerate and stage the OpenAPI spec after API resource changes
user-invocable: true
---

The committed OpenAPI spec lives at `public/spec.yaml` (single YAML file — there is no separate JSON
export). `.github/workflows/api-spec.yml` fails the PR if it drifts from the resources defined under
`src/Api/`, and additionally diffs the spec against the base branch to flag breaking changes.

After touching anything under `src/Api/Dto/`, `src/Api/State/`, or `src/Api/Filter/`:

1. Regenerate the spec: `task api:spec:export`
(This is shorthand for `bin/console api:openapi:export --yaml --output=public/spec.yaml --no-interaction`.)
2. Inspect changes: `git diff public/spec.yaml`
3. If the diff is non-empty, stage the file: `git add public/spec.yaml`
4. Summarise what changed — new/removed operations, modified parameters, schema diffs — so the human
reviewer can sanity-check for unintended breaking changes before pushing.
Loading
Loading