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
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# AGENTS.md

The Kontent.ai CLI (`kontent` bin, entry `src/index.ts`): ESM, TypeScript, pnpm.

## After changing code, before handing off

```
pnpm typecheck && pnpm lint && pnpm biome:check && pnpm test
```

- Autofix: `pnpm lint:fix`, `pnpm biome:fix`. Build: `pnpm build`.
- Node is `lts`; `.nvmrc` and CI `runtime:` must stay in step.

## Layers

Dependencies point downward only: `commands -> core -> lib`.

- `src/commands/**` yargs wiring and presentation.
- `src/core/**` orchestration of business logic.
- `src/lib/**` reusable primitives.
- Nested `AGENTS.md` files hold the details: `src/commands/`, `src/core/`, `src/lib/`, `test/`, `evals/`. Read the one covering a folder before editing in it.
- Editing `scripts/generateCommandDocs.ts`: the generated-docs rules are in `src/commands/AGENTS.md`.
- Commands obtain API clients and pass them into core; core operations take clients as input.
- Core reports recoverable failures as `Result` and never writes to the console (exception: interactive flows, see core's file). Commands own presentation and exit codes.

## Auth

- The `kontent login` token is the credential for both iapi and mapi. A logged-in user needs no API key, so a command only ever resolves the environment id.

## Output channels

- stdout: only the command's payload (response body, token, list). `--logLevel none` still prints it.
- stderr: progress, warnings, errors, verbose traces. Never the payload.
- `createLoggerFromArgs` (`src/log.ts`) is the only place resolving `--logLevel`/`--verbose`.

## Conventions

- Functional, not OOP. No classes. Branch with `match` (`ts-pattern`), not `switch`.
- Errors are values: `Result` (`src/lib/result.ts`), `Option` (`src/lib/option.ts`); convert thrown errors at boundaries with `tryAsync`/`fromThrowable`.
- Boolean names start with is/has/can/should/was.
- `const` over `let`.
- No `return` on the same line as its condition.
- Prefer `readonly` and `ReadonlyArray`.
- Relative imports end in `.js`.
- No redundant forwarding wrappers.
- Comments only for non-obvious why: no restating code, no justifying changes to the reviewer.
- Exports first, then private helpers in call order, depth-first; a private constant sits directly above its one user.
- No barrel files except a deliberate public API.

## Telemetry

- Amplitude-based, see `TELEMETRY.md`.
- Env vars come from `process.env`, never yargs options (`src/index.ts` deliberately omits `.env()`).
- Event names and custom property keys are kebab-case (`cli__some-command`, `error-code`); Amplitude built-in fields keep snake_case.
73 changes: 0 additions & 73 deletions CLAUDE.md

This file was deleted.

9 changes: 9 additions & 0 deletions evals/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# evals

- Agent-eval harness: Vitest plus the Agent SDK, `pnpm evals:run`, own `vitest.evals.config.ts`. The paid agent run never runs in `pnpm test` or CI; the harness unit tests in `test/` (helpers in `test/helpers/`) do run in `pnpm test`.
- Gated on `EVALS_MAPI_KEY`/`EVALS_SOURCE_ENV_ID`, which the CLI must never read.
- The agent gets Bash confined to a workspace dir, plus WebFetch limited to `kontent.ai` with key-in-URL denied. The policy is a pure reducer in `lib/policy.ts`; `lib/agent.ts` owns Agent SDK execution and wires the policy into the `PreToolUse` hook.
- Tool calls and denials come from the run's raw messages (`lib/toolCalls.ts`); CLI invocations and exit codes from the shim's per-task log (`lib/invocations.ts`), never from parsing shell text.
- One cloned environment shared by every task, run sequentially in dependency order; a task whose parent did not PASS is BLOCKED.
- `tasks/<id>/task.ts` holds the prompt and check. Checks are deterministic, return `Result` (a failed lookup is an `err`, never a failed assertion), and stay tolerant about names the task did not fix.
- Reports in `lib/report/` are markdown only, removable by deleting the folder and its call site. Playbook: `README.md`.
18 changes: 18 additions & 0 deletions src/commands/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# src/commands

- Yargs wiring and presentation only: register, call core, format output, log, set `process.exitCode`, fire the telemetry tracker. No business logic.
- New command: export `register: RegisterCommand` (see `login/login.ts`), add it to `commandsToRegister` in `registry.ts` or a parent's `subcommandsToRegister`, then run `pnpm docs:generate`.
- Obtain the `iapiClient`/`mapiClient` here and pass them into core.
- Resolving arguments into real inputs (reading the `--input` file, stdin) lives here.
- A handler that logs starts with `const logger = createLoggerFromArgs(args)`; a payload-only handler takes none.

## Generated docs

`pnpm docs:generate` runs `scripts/generateCommandDocs.ts`.

- Rewrites the marker-fenced table in root `README.md` and the `<!-- reference:start/end -->` block in each command README. Prose outside the markers is handwritten, never write inside them.
- A leaf's README lands in its own folder, or the parent group's folder when it has none.
- Two opt-out sets at the top of the script, both keyed by the top-level command name and independent of each other:
- `commandsWithoutPage`: the command gets no README of its own.
- `commandsWithoutIndexEntry`: the command gets no row in the root `README.md` table.
- After a command is renamed or removed, its old README is left behind. The generator fails on it and never deletes it: move the handwritten prose to the new README, then delete the old file yourself.
8 changes: 8 additions & 0 deletions src/core/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# src/core

- Orchestration of business logic. Use `Result` for recoverable failures and `Option` for optional values. Commands own presentation and exit codes.
- Operations receive `iapiClient`/`mapiClient` from the command, e.g. `performBootstrap(params, { logger, iapiClient, mapiClient })`. Core builds a client only where a fresh token first becomes one: `login/login.ts` and `iapi/authenticatedClient.ts`.
- Never writes to stdout or the console. The payload is returned as a value and the command prints it.
- Diagnostics go only through the passed `Logger` (a parameter, or inside `deps`), which writes to stderr.
- Pick the level per message: `logger.info("standard", ...)` for progress the user should see, `"verbose"` for traces. `logger.error(...)` takes no level.
- Exception: an inherently interactive flow may drive its own terminal UI, e.g. `project/bootstrap.ts`. Prompts, spinners and notes must go through the wrappers in `src/lib/ui/prompts.ts`: they force stderr, while clack on its own writes to stdout and breaks the output contract. Everything else stays free of direct writes.
7 changes: 7 additions & 0 deletions src/lib/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# src/lib

- `iapi`: internal API, hand-rolled over `@kontent-ai/core-sdk`, one file per endpoint. `schema` validators must be `zod/mini`; classic zod cannot infer the payload.
- `mapi`: Management API via `@kontent-ai/management-sdk`. `mapi/raw` is a passthrough behind `kontent mapi`: no schema, 4xx/5xx is a result, not an error. Why: doc comments in `raw/client.ts`, `raw/contentType.ts`.
- `learn`: tokenless Learn-MCP client behind `kontent docs`. Schemas declare only the fields read, so unknown keys survive to stdout; `runtimeValidation.validateResponses` must stay on.
- `config`: `cliConfig.ts` is the persisted CLI state (telemetry consent, ids); `kontentUrl.ts` allowlists the domains the CLI may talk to, never bypass it.
- `telemetry`: see `TELEMETRY.md`.
7 changes: 7 additions & 0 deletions test/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# test

- Vitest. `unit/` pure unit tests, `integration/` integration tests, `helpers/` shared helpers, `e2e/` the built binary against a real project.
- Command-level behavior (argument parsing, exit codes, which stream a message hits): fold a command's `register` over a real yargs instance and fake only the core call; see `integration/mapiCommand.test.ts`.
- Inject fakes into core instead of real I/O; for iapi reuse `helpers/iapiTestClient.ts`.
- The evals harness has its own unit tests in `evals/test/`; they run in `pnpm test` too.
- e2e: clone-per-run from an empty template env, gated on `E2E_MAPI_KEY`/`E2E_SOURCE_ENV_ID`, fails fast when unset. `pnpm test:e2e` (own `vitest.e2e.config.ts`, loads `.env`); excluded from `pnpm test` and the gate. CI `.github/workflows/e2e.yml`, fork PRs skipped.
Loading