Operating instructions for Claude Code (and other AI agents) working in this repository. Audience is the agent, not human contributors — for human-facing documentation see README.md.
When the instructions here conflict with the user's global ~/.claude/CLAUDE.md,
this file wins — project-specific rules override the global defaults.
ai-lib is a shared catalog of AI assistants for the Danish public sector.
The application is a Symfony 8 web app built on the ITK Dev Docker
development setup.
- PHP 8.4 running under
phpfpm(Symfony 8 skeleton, PSR-4App\\atsrc/). - Nginx in front of
phpfpm, served via the shared Traefik proxy athttps://ai-lib.local.itkdev.dk. - MariaDB for persistence.
- Mailpit for outbound mail capture at
https://mail-ai-lib.local.itkdev.dk. - ITK Dev Docker template
symfony-8provides the container orchestration.
bin/ Symfony console entry
config/ Symfony configuration (bundles, packages, routes, services)
public/ Web root (public/index.php)
src/ Application code (PSR-4 namespace App\)
assets/ Frontend entry points (placeholder until a stack is picked)
.docker/ nginx config and templates
.github/workflows/ CI workflows (do NOT edit locally — see "Workflows" below)
docs/adr/ Architecture Decision Records (see "ADRs")
All language/build tooling runs inside containers. Never invoke php,
composer, node, npm, npx, prettier, or similar on the host.
Preferred order:
task <name>— the project'sTaskfile.ymlis the entry point for everyday commands. Runtask --listto see what's available.task compose -- <args>/task compose-exec -- <args>— pass-through helpers when no dedicated target exists.itkdev-docker-compose <command>— for cross-project ITK Dev tooling not wrapped by the project Taskfile (e.g.traefik:start).docker compose --profile dev run --rm <service> <args>— direct fallback for the dev-only tooling (prettier,markdownlint) when going around the Taskfile is justified.
# Lifecycle
task start # pull, up, composer install
task down # tear the stack down
# Composer / PHP / Symfony console
task composer -- <command> # e.g. task composer -- require foo/bar
task compose-exec -- phpfpm php <command>
task console -- <command> # e.g. task console -- cache:clear
# Coding standards (check / apply pairs)
task coding-standards-php-check
task coding-standards-php-apply
task coding-standards-twig-check
task coding-standards-twig-apply
task coding-standards-yaml-check
task coding-standards-yaml-apply
task coding-standards-markdown-check
task coding-standards-markdown-apply
task coding-standards-composer-check
task coding-standards-composer-apply
# Run every check at once
task coding-standards-check
# Tests
task test # PHPUnit, no coverage
task test-coverage # PHPUnit + Xdebug coverage, enforces 100% gateThe coverage gate is 100% and is enforced by the Tests GitHub
Actions workflow on every pull request — see .github/workflows/tests.yaml.
Run the matching check before committing changes in that area. For
commands without a dedicated task, fall back to task compose -- <args>
or itkdev-docker-compose <args>.
Config files live at the repo root:
.php-cs-fixer.dist.php— PHP CS Fixer (Symfony ruleset)..twig-cs-fixer.dist.php— Twig CS Fixer..prettierrc.yaml— Prettier (YAML, CSS/SCSS, JS)..markdownlint.jsonc+.markdownlintignore— Markdown lint.
These come from the symfony-8 template — don't edit them without a reason.
If a project-specific override is needed, override via the template's
documented mechanism (e.g. .php-cs-fixer.php next to .php-cs-fixer.dist.php).
Controllers handle routes and template/response rendering only — no business
logic. Push logic into a service class. A controller action looks like:
inject service → call service method → return render() / Response /
RedirectResponse.
Every service class method (public, protected, private) carries a PHPDoc block
with a one-line summary, a description of intent, @param per parameter,
@return, and @throws for every exception that can be raised.
The .github/workflows/*.yaml files are mirrored from
itk-dev/devops_itkdev-docker
and carry a Do not edit this file! header. If a workflow needs to change,
open a PR upstream rather than patching locally.
- Base branch for feature work:
develop.mainis the release/stable line. - Branch name:
feature/issue-<n>-<short-slug>(e.g.feature/issue-5-claude-md). - One issue per branch where possible. Reference the issue number in the branch name and PR.
- PR target:
develop. - A PR must:
- Link the issue with
Fixes #<n>(orCloses #<n>for non-bug issues). UseRefs #<n>when coverage is partial. - Pass all required CI checks before merging.
- Carry a
CHANGELOG.mdupdate under## [Unreleased]for any user-visible change.
- Link the issue with
Use Conventional Commits:
feat:new featurefix:bug fixdocs:documentation onlychore:tooling, build, deps, repo housekeepingrefactor:code change that neither adds a feature nor fixes a bugtest:tests only
Keep subject lines under ~70 characters. Use the body for the why.
CHANGELOG.md follows Keep a Changelog.
Add an entry to ## [Unreleased] under the right section (Added, Changed,
Fixed, Removed, Deprecated, Security) for every meaningful change.
Every issue must have its native issue type set to one of:
- Bug — something is broken.
- Feature — new user-visible capability.
- Task — everything else: chores, tooling, documentation, infrastructure, refactors, ADRs, etc.
Documentation-only work is tracked as a Task type plus the
documentation label. The type classifies the nature of the work,
labels add orthogonal context.
The current gh CLI (≤ 2.92) does not expose --type. To set a type,
fall back to the REST API (PATCH /repos/{owner}/{repo}/issues/{n} with
type=<Name>) when available, otherwise ask the user to set it in the
UI. Labels can always be set with gh issue create --label.
SSH keys aren't available to the Claude session. Push one-off via HTTPS:
git push https://github.com/itk-dev/ai-lib.git HEAD:<branch>Do not change the origin remote URL — SSH is wanted for normal use outside
Claude.
Architectural decisions are recorded as ADRs in docs/adr/. Create and manage
them via the itkdev-adr skill (see issue #11). Open an ADR for decisions
that:
- Change the runtime architecture (storage, integrations, deployment).
- Choose between two viable options with non-trivial trade-offs.
- Establish a convention other contributors must follow.
Small implementation choices belong in code review, not in an ADR.
Do not include a "Follow-up Actions" (or similarly named) checklist inside an ADR. Track follow-up work as GitHub issues and reference the ADR from each issue, not the other way around. The ADR records the decision; the issues track the work derived from it. The one-time cleanup of existing sections is tracked in #44.
- Assistant — a configured AI persona/prompt bundle that can be exported, shared, and re-imported.
- Catalog — the searchable collection of assistants surfaced by the app.
- OpenWebUI export format — the JSON schema used by OpenWebUI for importing/exporting assistants; the canonical interchange format for this project.
- Share/upload flow — the moderated path by which a user submits an assistant to the catalog (metadata + review).
- Tags / categories — taxonomy applied to assistants for filtering and discovery.
- Moderation — validation and review of submitted assistants before they appear in the catalog.
- Prefer an existing pattern in the codebase over inventing a new one.
- For non-trivial decisions, write an ADR or ask the user — don't silently pick.
- For destructive git operations (
reset --hard,push --force, branch deletion), stop and ask the user — these are deny-listed globally for good reason.