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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.githooks/* text eol=lf
2 changes: 2 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
exec just pre-commit
41 changes: 12 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,6 @@ concurrency:
cancel-in-progress: true

jobs:
quality:
name: Quality
if: ${{ (inputs.profile || 'full') == 'full' }}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ inputs.source_sha || github.sha }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
package-manager-cache: false
- run: npm install --global npm@12.0.2
- run: npm ci
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
version: "0.13.0"
- run: npm run format:check
- run: npm run lint
- run: uv run --no-project --python 3.15 python scripts/check_loc.py

correctness:
name: Correctness / Package and browser
runs-on: ubuntu-latest
Expand Down Expand Up @@ -82,7 +59,15 @@ jobs:
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
if: ${{ (inputs.profile || 'full') == 'full' }}
with:
version: "0.13.0"
- name: Quality
if: ${{ (inputs.profile || 'full') == 'full' }}
run: just quality
- name: Correctness lint
if: ${{ inputs.profile == 'deploy' }}
run: just lint-correctness
- run: npm run typecheck
- name: Install Chromium
Expand All @@ -105,22 +90,20 @@ jobs:
result:
name: Qualification result
if: ${{ always() }}
needs: [quality, correctness]
needs: correctness
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Require selected groups
env:
PROFILE: ${{ inputs.profile || 'full' }}
SOURCE_SHA: ${{ needs.correctness.outputs.sha || inputs.source_sha || github.sha }}
QUALITY: ${{ needs.quality.result }}
CORRECTNESS: ${{ needs.correctness.result }}
run: |
node <<'NODE'
const { PROFILE, SOURCE_SHA, QUALITY, CORRECTNESS } = process.env;
console.log(`Qualification ${PROFILE}: ${SOURCE_SHA}; Quality=${QUALITY}; Correctness=${CORRECTNESS}`);
const expectedQuality = { full: "success", deploy: "skipped" }[PROFILE];
if (!expectedQuality || QUALITY !== expectedQuality || CORRECTNESS !== "success") {
const { PROFILE, SOURCE_SHA, CORRECTNESS } = process.env;
console.log(`Qualification ${PROFILE}: ${SOURCE_SHA}; Correctness=${CORRECTNESS}`);
if (!["full", "deploy"].includes(PROFILE) || CORRECTNESS !== "success") {
process.exit(1);
}
NODE
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ Use `just setup` and `just check` for the shared development baseline; see READM
`test:`, `chore:`, `ci:`).
- Do not merge with failing or missing required checks.

## Application secrets

- Use Nextide's Infisical-backed secret service for application-secret editing;
follow the [secrets policy](https://github.com/nextidemedia/meta/blob/main/docs/development-baseline.md#application-secrets)
and the service owner's startup/release contract. Existing services migrate
per owner and environment; `nextide-web` is exempt. Keep environment variables,
nonsecret config, schemas/examples, scoped bootstrap/verifier registries and
independent recovery; retire independent application-secret `.env` copies
after verified migration and the owning rollback window has passed.

## Runtime checks

- Add a validation, guard or limit only when both are true: you can name what
Expand Down Expand Up @@ -85,6 +95,13 @@ Use `just setup` and `just check` for the shared development baseline; see READM

## Validation

- Expose `just pre-commit` for measured-fast, check-only local feedback and run
it through the tracked Git hook after setup. Reuse prepared installed tools;
never install, fetch, format, fix, or alter source or staging in the hook.
Include type checks or existing tests only when measurements justify their
latency. Consolidate compatible tiny CI checks; CI qualifies committed
contents and retains the complete Quality and Correctness contract. See the
[local feedback and hook setup guide](https://github.com/nextidemedia/meta/blob/main/docs/development-baseline.md#local-feedback-and-git-hooks).
- Run targeted local checks as needed; run larger validation remotely on the
working branch. Required checks must pass before merge.
- If remote validation is blocked, proceed with a reasonable local fallback.
Expand Down
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ Run `npm run dev`, then open [http://127.0.0.1:5174](http://127.0.0.1:5174) to v
## Development checks

Install Node 26.9.0 (matching `.node-version` and CI), npm 12.0.2, `just`, and uv. On Windows, install PowerShell 7.5+
with `pwsh` available on PATH.

- `just setup` installs the frozen workspace lockfile.
with `pwsh` available on PATH. Commit checks also need Python 3.15 on PATH.

- `just setup` installs the frozen workspace lockfile and activates the tracked
commit hook. For an already prepared checkout, run `just hooks-install`.
- `just pre-commit` checks the whole working tree: LOC limits, Prettier, full lint,
the supply-chain watchlist, and TypeScript for the package, playground, and E2E.
It uses installed tools without downloading, formatting, staging, or stashing.
Type checks read source directly; they do not rebuild package declarations.
Packed-package and browser qualification remain separate required CI checks.
- Hook setup preserves custom `core.hooksPath` and existing Git hooks. Integrate
`just pre-commit` into those hooks explicitly. The relative `.githooks` path
applies to linked worktrees too; each worktree needs its own prepared tools.
Whole-tree checks include unstaged edits; use `git -c core.hooksPath=/dev/null commit`
for a deliberate one-off bypass, with CI still required before merge.
- `just check` runs Prettier, Vite+ Oxlint, the supply-chain watchlist,
LOC budgets, strict TypeScript checks, and packed-consumer qualification.
- `just fmt`, `just fmt-check`, `just lint`, and `just typecheck` run separately.
Expand Down Expand Up @@ -53,8 +64,10 @@ the complete rule sets still run in Quality. Type safety restrictions and the
supply-chain watchlist remain required in both profiles.

Pull requests, main pushes, and the weekly **Qualification** workflow run full qualification. Manual runs select
`full` or `deploy`; deploy deliberately skips **Quality**. **Qualification result**
requires every selected group to succeed and reports the validated commit. The
`full` or `deploy`; deploy deliberately skips **Quality**. Both profiles share one
Node installation in **Correctness / Package and browser**. Full runs execute
Quality first; its full lint also covers correctness lint and the watchlist.
**Qualification result** requires the selected checks to succeed and reports the validated commit. The
package has no service deployment or database integration lane; its integration
surface is the packed consumer and playground browser suite.

Expand Down
14 changes: 14 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ _forward_args := if os() == "windows" { "@forwarded" } else { '"$@"' }

setup:
npm ci
python scripts/install_hooks.py

hooks-install:
python scripts/install_hooks.py

pre-commit:
python scripts/check_loc.py
node node_modules/prettier/bin/prettier.cjs --check .
node node_modules/vite-plus/bin/vp lint --deny-warnings
npm run security:watchlist
node node_modules/typescript/bin/tsc --noEmit -p packages/ui/tsconfig.json
node node_modules/typescript/bin/tsc --noEmit -p apps/playground/tsconfig.app.json
node node_modules/typescript/bin/tsc --noEmit -p apps/playground/tsconfig.node.json
node node_modules/typescript/bin/tsc --noEmit -p tsconfig.e2e.json

lint:
npm run lint
Expand Down
79 changes: 79 additions & 0 deletions scripts/install_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Activate the tracked hooks without replacing existing local hooks."""

import subprocess
from pathlib import Path


def git(*args: str) -> str:
return subprocess.check_output(["git", *args], text=True).strip()


def main() -> None:
root = Path(git("rev-parse", "--show-toplevel"))
project = Path(__file__).resolve().parents[1]
if project != root.resolve() or Path.cwd().resolve() != project:
raise SystemExit(
"Run git init in this project, then run hook setup from its root."
)
configured = subprocess.run(
["git", "config", "--get", "core.hooksPath"],
capture_output=True,
text=True,
check=False,
)
if configured.returncode not in (0, 1):
raise SystemExit(configured.stderr)
if configured.returncode == 0:
if configured.stdout.strip() == ".githooks":
return
raise SystemExit(
"Existing core.hooksPath preserved; integrate just pre-commit there explicitly."
)
default_hooks = Path(git("rev-parse", "--git-path", "hooks"))
hook_names = {
"applypatch-msg",
"pre-applypatch",
"post-applypatch",
"pre-commit",
"pre-merge-commit",
"prepare-commit-msg",
"commit-msg",
"post-commit",
"pre-rebase",
"post-checkout",
"post-merge",
"pre-push",
"pre-receive",
"update",
"proc-receive",
"post-receive",
"post-update",
"reference-transaction",
"push-to-checkout",
"pre-auto-gc",
"post-rewrite",
"sendemail-validate",
"fsmonitor-watchman",
"p4-changelist",
"p4-prepare-changelist",
"p4-post-changelist",
"p4-pre-submit",
"post-index-change",
}
if default_hooks.exists() and any(
hook.name in hook_names for hook in default_hooks.iterdir()
):
raise SystemExit(
"Existing Git hooks preserved; integrate just pre-commit there explicitly."
)
hook = root / ".githooks" / "pre-commit"
if not hook.is_file():
raise SystemExit("Missing tracked .githooks/pre-commit.")
hook.chmod(hook.stat().st_mode | 0o111)
subprocess.run(
["git", "config", "--local", "core.hooksPath", ".githooks"], check=True
)


if __name__ == "__main__":
main()