# Contributing

Source: https://docs.externa.qiick.io/docs/contributing

Contributions to **externa-core** should match the existing Laravel + Inertia React stack, keep enums as the source of truth for permissions/field types/roles, and leave tests green.

Work in the `externa-core` repository. Operators on Packagist / Docker prod paths can skip contributor hooks.

## Local git hooks (Lefthook)

After `npm ci` (or `npx lefthook install`), clones get:

| Hook         | Checks                                                        |
| ------------ | ------------------------------------------------------------- |
| `pre-commit` | Pint (`--dirty`), ESLint + Prettier on staged frontend files  |
| `commit-msg` | [commitlint](https://commitlint.js.org/) Conventional Commits |
| `pre-push`   | `pint --test` + `eslint` — **not** full Pest / browser        |

**GitHub Actions remains the source of truth.** Skip locally with `LEFTHOOK=0` or `git commit --no-verify` (emergency only). See repo [`CONTRIBUTING.md`](https://github.com/qiick-io/externa-core/blob/develop/CONTRIBUTING.md).

npm `prepare` installs Lefthook only when `.git` exists — Docker image builds without a git checkout stay green (#94).

## Tooling

### PHP — Pint

Laravel Pint formats PHP. Composer scripts:

```bash
composer lint          # pint --parallel
composer lint:check    # pint --parallel --test
```

`composer test` runs `lint:check` before Pest.

### Frontend — ESLint & Prettier

From `externa-core`:

```bash
npm run lint           # eslint . --fix
npm run lint:check     # eslint .
npm run format         # prettier --write resources/
npm run format:check   # prettier --check resources/
npm run types:check    # tsc --noEmit
```

CI-style gate:

```bash
composer ci:check
```

(runs npm lint/format/types checks + `composer test`).

### Wayfinder

Laravel Wayfinder + `@laravel/vite-plugin-wayfinder` generates typed route/action helpers under `resources/js` during Vite build/dev.

- Prefer Wayfinder helpers over hard-coded URL strings in React.
- After adding/renaming named routes, run Vite (`npm run dev` or `build`) so generated clients refresh.
- Do not hand-edit generated route trees as a long-term source of truth.

### Pest

Feature and browser tests use **Pest 4**. See [Testing](/docs/testing) for layout and how to run suites.

```bash
composer test
# or focused:
php artisan test --filter=SomeTestName
```

## Coding conventions

### Enums are the source of truth

| Concern                | Enum                       | Sync / mirror                                                                 |
| ---------------------- | -------------------------- | ----------------------------------------------------------------------------- |
| Permissions            | `App\Enums\PermissionEnum` | `php artisan permissions:sync` → DB + `resources/js/enums/permission-enum.ts` |
| Roles (built-in names) | `App\Enums\RoleEnum`       | Seeded in `RoleSeeder`                                                        |
| Field types            | `App\Enums\FieldTypeEnum`  | Validation, normalizer, UI catalog, AI instructions                           |
| File kinds             | `App\Enums\FileTypeEnum`   | File manager / storage helpers                                                |

Do not invent permission or field-type strings in controllers or React without updating the enum first. Extension recipes: [Extending Externa](/docs/extending).

### Actions & Services

- **`app/Services/`** — domain orchestration used by controllers, jobs, and AI tools (`FileService`, `Authorization\EffectivePermissionResolver`, Collections services, …).
- **`app/Actions/`** — single-purpose actions (Fortify actions live under `app/Actions/Fortify`). Prefer a small Action class when a use-case is reused or would bloat a controller.
- Controllers stay thin: Form Requests validate/authorize; Services/Actions mutate state; Jobs handle async work.

### HTTP & auth

- Admin/files/AI routes: enforce permissions with middleware or Form Requests (`AuthorizesWithPermission` / `EffectivePermissionResolver`).
- Collections HTTP currently relies on UI + AI checks — if you harden that, document it and add tests ([Collections API](/docs/collections-api)).
- AI tools must use `ChecksAiPermissions` for mutating/domain operations.

### Raw SQL allowlist ([core#208](https://github.com/qiick-io/externa-core/issues/208))

Prefer Eloquent / the query builder. Raw SQL is **disallowed by default** in PHPStan (`spaze/phpstan-disallowed-calls` in `phpstan.neon`):

- Query builder: `whereRaw`, `orWhereRaw`, `selectRaw`, `orderByRaw`, `havingRaw`, `groupByRaw`
- Facades / connection: `DB::raw`, `DB::select`, `DB::statement`, `DB::unprepared` (and matching `Connection` methods)

Reviewed call sites must:

1. Live in a file listed under `allowIn` in `phpstan.neon` (both `disallowedMethodCalls` and `disallowedStaticCalls` blocks as needed).
2. Carry an inline `// raw-sql: reviewed` marker next to the call.

Do **not** widen the allowlist without a security/perf justification in the PR. Run `composer analyse` (or the CI analyse job) before merge. Pest expectations for new features: [Testing](/docs/testing#raw-sql).

### Frontend

- Pages under `resources/js/pages/…` matching Inertia names.
- Gate UI with `useCan()` + `PermissionEnum` from the generated TS enum.
- Follow existing shadcn/Radix + Tailwind patterns; avoid one-off design systems.

### Jobs & schedule

New async work should use the queue (not `dispatchSync` in request paths for heavy I/O). If you add a scheduled command, register it in `routes/console.php` and document it under [Operations](/docs/operations).

## Graphify knowledge graph

This monorepo maintains a graphify index under `graphify-out/` at the Externa workspace root.

After you change **code** (PHP/TS that affects architecture or APIs):

```bash
cd /path/to/Externa
graphify update .
```

Use `graphify query "…"` when exploring relationships before large refactors. Skip `graphify update` for docs-only or pure config-comment edits that do not change behaviour.

## Dependency audits (CI)

`.github/workflows/audit.yml` ([core#184](https://github.com/qiick-io/externa-core/issues/184)) runs on PRs / pushes to `develop` / `main` / `master`, on a **weekly Monday 06:00 UTC** schedule, and via `workflow_dispatch`:

| Check         | Command                                                                  | Policy (GA)                   |
| ------------- | ------------------------------------------------------------------------ | ----------------------------- |
| Composer      | `composer audit --locked --ignore-severity=low --ignore-severity=medium` | **Blocking** on high/critical |
| npm (runtime) | `npm audit --omit=dev --audit-level=high`                                | **Blocking** on high/critical |

DevDependencies are not audited in the blocking job. Do **not** run `npm audit fix --force` in CI or as a blind local fix. Operator checklist: [Security checklist — Dependency audits](/docs/security-checklist#dependency-audits-ci).

### Reading failures

1. Open the failed **audit** job log; note package + advisory ID / CVE.
2. Prefer upgrading within the current major (`composer update pkg` / `npm update pkg`) and re-run locally.
3. False positives / accepted risk: document in the PR with justification. Composer: temporary `--ignore` entries only with a tracked follow-up. npm: avoid `.npmrc` force; prefer upgrading or switching package.
4. Path to GA (#24): keep audits **blocking** (no `continue-on-error`).

See also [Security checklist](/docs/security-checklist#dependency-audits-ci) · repo [`SECURITY.md`](https://github.com/qiick-io/externa-core/blob/develop/SECURITY.md).

## Dependabot

`.github/dependabot.yml` opens **weekly** PRs (Monday) for Composer, npm, and GitHub Actions. Minor/patch updates are **grouped** per ecosystem; open-PR limits keep noise down. Majors arrive as separate PRs.

### Reviewing a Dependabot PR

1. Confirm CI green (lint, tests, **audit** jobs).
2. Skim release notes / changelog for breaking changes — do not merge major bumps blind.
3. Prefer one ecosystem group merge at a time; re-run audits locally if unsure.
4. After merge, note notable upgrades in `CHANGELOG.md` when cutting a release (see Release process / #22).

## PR expectations

1. Pint / ESLint / Prettier / `tsc` clean.
2. Pest coverage for new behaviour (see [Testing](/docs/testing)).
3. Enum + `permissions:sync` when permissions change; commit regenerated TS enum.
4. Note public route or config contract changes in the PR so product docs can be updated.
5. `graphify update .` when the code graph should reflect your changes.
6. Dependency audit CI green (or justified ignore with follow-up).

## Releases

Maintainers cutting a version: [Releasing & versions](/docs/releasing) (CHANGELOG, semver, GitHub Release template). Prefer noting user-facing changes in `CHANGELOG.md` Unreleased while the PR is open.

## Maintainer checklists (not indexed)

Internal sign-off pages stay out of the sidebar and sitemap (`noindex`). Reach them here when needed:

- [GA readiness](/docs/ga-readiness)
- [Website ↔ docs messaging](/docs/website-docs-messaging)
- [Post-v1 polish smoke](/docs/post-v1-polish-smoke)
