Development¶
What is where, which command runs which gate, and the conventions a change is expected to follow.
Repository layout¶
Maljan/
├── apps/
│ ├── api/ FastAPI app, arq worker, Alembic migrations ("maljan-api")
│ └── web/ Next.js console, Playwright specs under e2e/
├── src/maljan/ the core package: agents, pipeline, analysis layers,
│ providers, memory, reporting, core (config, container)
├── services/ stdio MCP sidecars, one server.py each
├── scripts/
│ ├── ci/ the uv.lock dependency-graph submission the workflow runs
│ ├── dev/ fetch_external.sh and the Ghidra image manager
│ ├── goldens/ capture scripts that write tests/fixtures/golden/, plus
│ │ render_team_graphs.py, which draws docs/assets/team-*.svg
│ └── knowledge/ builders for the data/ assets
├── tests/ unit/ api/ integration/ fixtures/
├── data/ tracked knowledge assets, loaded lazily, each with a fallback
├── docker/ Dockerfiles, the compose stack and its dev overlay
├── docs/ this documentation set and assets/
├── Makefile every gate and every generator
└── pyproject.toml uv.lock one uv workspace: maljan plus apps/api
The repository is one uv workspace with one lockfile. uv sync --all-extras
--all-packages installs both Python packages, so import maljan and
import app work from any directory without PYTHONPATH, in the venv, in the
backend image and in CI alike.
Route-local React components stay in their route folder; a component two routes
use moves to apps/web/src/components/. tests/unit/ mirrors src/maljan,
one subdirectory per subpackage, and is where a new test starts.
Host-specific helpers (a launcher for a local llama-server, a memory guard, a
restart wrapper for long evaluations) live outside the repository. For the
record, the local model server the measurements in this repository were taken
with ran ik_llama.cpp as
llama-server -m Qwen3.6-35B-A3B-IQ3_K_R4.gguf -c 131072 -t 16 -fa on -ctk q8_0 -ctv q8_0 -ngl 999 -ot 'blk\.([1-3][0-9])\.ffn_(up|gate|down)_exps=CPU' --context-shift on --jinja
on loopback port 8080.
Make targets¶
| Target | What it does |
|---|---|
make setup |
uv sync --all-extras --all-packages, pre-commit hooks, external/. |
make test |
pytest tests/ -q. test-unit, test-integration and test-qdrant narrow it. |
make lint / make format / make format-check |
ruff over src/ tests/ apps/api/ services/ scripts/. |
make typecheck |
mypy over src/ and apps/api/. |
make check |
lint, format check, typecheck, tests — the local mirror of CI. |
make semgrep |
the four rulesets CI runs, at the pinned version, over the same targets. |
make migrate |
alembic upgrade head against DATABASE_URL. |
make dev-up / dev-down / dev-logs |
the compose stack with the development overlay. |
make fe-rebuild / worker-restart |
make a source edit real on the production stack. |
make external |
refetch the third-party trees at their pinned refs. |
make ghidra-status / -sync / -build / -watch |
the Ghidra MCP manager. |
Tests¶
- Python —
uv run pytest tests/ -q.tests/unit/is the default home;tests/api/covers the FastAPI surface andtests/integration/the flows that cross the worker, the database and object storage. The suite needs no secret in the environment: a pytest-only JWT secret is substituted, and the auth bypass is forced off so real 401 and 403 assertions still mean something. - Frontend unit —
cd apps/web && npm run test:unit(vitest), alongsidenpx tsc --noEmitandnpm run lint. - Playwright —
cd apps/web && npm run test:e2e. The suite starts its ownnext devon port 3100, forcesNEXT_PUBLIC_AUTH_DISABLED=falseand points the client at the Next server's own origin, so it is hermetic and contacts no backend; every API call is mocked ine2e/mocks.ts. Run a named spec while developing (npx playwright test e2e/settings-configuration.spec.ts --project=chromium) rather than the whole suite, and stop any dev server you started yourself first — the browsers are memory-hungry, which is why the local worker count and the timeouts are raised deliberately.
The evaluation harness behind the published measurements is no longer part of this tree; see paper.md.
Documentation site¶
The pages under docs/ are also built into a MkDocs Material site; preview it
locally with uv run --group docs mkdocs serve.
Continuous integration¶
.github/workflows/ci.yml
runs on pushes to
main and dev and on pull requests into main, dev or feat/**:
| Job | Contents |
|---|---|
quality |
Ruff lint, ruff format check, mypy. Every other job needs it. |
semgrep |
p/python, p/security-audit, p/typescript and p/react at the pinned version, over src/ apps/api/ services/ scripts/ apps/web/src/. .semgrepignore keeps out what is not source: build output, the Playwright suite (e2e/*.spec.ts) and the vitest specs (__tests__/*.test.ts). |
test |
pytest tests/ -q --tb=short on Python 3.13. |
test-qdrant |
tests/unit/test_qdrant_store.py against a live Qdrant service container. |
frontend |
tsc --noEmit, eslint, vitest and a production next build. |
e2e |
Playwright, through the same npm run test:e2e entry point developers use. |
Three more workflows run beside CI. CodeQL analyses Python, TypeScript and
the workflow files themselves on every push and pull request to main and
dev, with the security-extended query suite, and weekly. Dependency
review blocks a pull request that introduces a dependency with a known high
or critical vulnerability or a copyleft licence. OpenSSF Scorecard runs on
main weekly and publishes its findings to code scanning. Dependabot opens
weekly update pull requests against dev for the uv workspace, the web
console, the pinned GitHub Actions and the Docker base images; security
updates arrive as soon as an advisory matches. Every action in a workflow is
pinned to a commit with its version in a comment, which Dependabot keeps
current. A dependency submission workflow posts the packages resolved in
uv.lock to the dependency graph on every push to main, so Dependabot's
Python alerts follow the lockfile the way the console's follow
package-lock.json. Secret scanning with push protection is on for the repository, and
vulnerabilities are reported through private vulnerability reporting (see
SECURITY.md).
Conventions¶
- Conventional commits, one logical slice per commit.
- Feature branches start from
devand are merged intodev;mainis branch-protected and only ever advanced by an explicit promotion pull request fromdev. - No question sentences in headings, comments or documentation.
- A comment explains why the code is the way it is. Process tags — dated audit identifiers, ticket numbers, "new in phase B" — do not belong in the tree; the reasoning stays, the bookkeeping goes.
- New settings need an entry in
src/maljan/core/settings_annotations.py, or they appear in the console under their dotted path with no description. - A removal is not done until
grepshows zero remaining references acrosssrc apps tests scripts docsand the affected tests are updated.