# AGENTS.md
Last reviewed: 2026-06-17.
This file is the single source of truth for AI coding agents and human contributors working on the `@bitrix24/b24jssdk` repository. The four detailed guides under `.github/contributing/` are referenced from the relevant sections below — load them only when they apply to your task.
## Project Overview
`@bitrix24/b24jssdk` is a JS/TS SDK for the Bitrix24 REST API. It is a pnpm 11 monorepo that ships:
- a framework-agnostic core SDK (`packages/jssdk`, published as `@bitrix24/b24jssdk`, shipping ESM, CommonJS, and UMD; ESM is the recommended entry point),
- a thin Nuxt module wrapper (`packages/jssdk-nuxt`),
- a public docs site (`docs/`, deployed to GitHub Pages),
- manual smoke playgrounds (`playgrounds/cli`, `playgrounds/nuxt`),
- workspace-level Vitest projects under `test/` that hit a real Bitrix24 portal.
The core SDK exposes three concrete entry points over a shared abstract base — `B24Frame` (iframe apps), `B24Hook` (server-side webhooks), `B24OAuth` (local OAuth apps) — plus cross-cutting modules for HTTP, limiting, helpers, push (Pull), logging, and types.
## Project Structure
```text
packages/
├── jssdk/ # core SDK (published)
│ ├── src/
│ │ ├── core/ # AbstractB24, Result, SdkError
│ │ │ ├── actions/ # b24.actions.vX..make()
│ │ │ ├── http/ # transports + limiters
│ │ │ │ ├── limiters/ # rate / operating / adaptive delay
│ │ │ │ ├── ajax-result.ts
│ │ │ │ ├── ajax-error.ts
│ │ │ │ ├── v2.ts
│ │ │ │ └── v3.ts
│ │ │ └── tools/ # internal helpers
│ │ ├── frame/ # B24Frame + iframe managers (auth, slider, dialog, …)
│ │ ├── hook/ # B24Hook (server-side webhook auth)
│ │ ├── oauth/ # B24OAuth (local OAuth apps)
│ │ ├── helper/ # B24HelperManager, useB24Helper composable
│ │ ├── pullClient/ # WebSocket / long-poll Pull client
│ │ ├── tools/ # public tools (Text, Type, Browser, formatters)
│ │ ├── types/ # public types and enums
│ │ ├── logger/ # LoggerBrowser / LoggerFactory
│ │ ├── loader-b24frame.ts # initializeB24Frame()
│ │ └── index.ts # public surface — treat as a contract
│ ├── README-AI.md # caller-facing API guide (being absorbed into this guide)
│ └── build.config.ts # unbuild config (replaces __SDK_VERSION__ etc.)
├── jssdk-nuxt/ # Nuxt module (registers runtime/plugin; build.config.ts injects __SDK_VERSION__ into meta.version)
playgrounds/
├── cli/ # Node smoke
└── nuxt/ # Nuxt smoke (live SDK)
docs/
└── content/docs/ # documentation site (English only)
test/
├── 0_setup/ # integration + under-load setup, env loading
├── integration/ # *.spec.ts — 30s timeouts, real portal
├── under-load/ # sequential, 40-min timeouts
├── some-code-from-docs/ # manually mirrored snippets from docs/ (not auto-run)
└── umd/ # browser smoke (browser.html)
scripts/
└── b24-self-task/ # Python automation that drives Claude Code
```
## Architecture
The core SDK is organised around one abstract base + three concrete entry points:
- [packages/jssdk/src/core/abstract-b24.ts](packages/jssdk/src/core/abstract-b24.ts) — `AbstractB24` exposes the action surface (`b24.actions.vX..make()`) and owns the v2 + v3 HTTP clients, the restriction/rate-limiter stack, the logger, and helper sub-managers. The legacy shortcuts (`callMethod`, `callBatch`, `callListMethod`, `fetchListMethod`, `callBatchByChunk`) live on this class too but are `@deprecated` — see the `@removed` tag on each method for the target removal version. Do not call them from new code.
- [packages/jssdk/src/frame](packages/jssdk/src/frame) — `B24Frame`, used inside the Bitrix24 iframe. Talks to the parent window via `postMessage`, auto-refreshes auth on 401, and exposes UI managers (`auth`, `parent`, `slider`, `dialog`, `placement`, `options`). **Bootstrap only via `initializeB24Frame()`** in [packages/jssdk/src/loader-b24frame.ts](packages/jssdk/src/loader-b24frame.ts) — it deduplicates concurrent inits and parses `window.name` for the portal handshake. Do not expose an alternative constructor path.
- [packages/jssdk/src/hook](packages/jssdk/src/hook) — `B24Hook` for server-side webhook auth (`B24Hook.fromWebhookUrl(url)`). **Must only be used in server-side (Node.js / edge runtime) code.** A webhook URL contains a secret access key; shipping it in a browser bundle exposes the key to every visitor. The class emits a runtime warning when it detects a browser context — do not suppress it with `offClientSideWarning()`.
- [packages/jssdk/src/oauth](packages/jssdk/src/oauth) — `B24OAuth` for OAuth-based local apps; manages refresh-token errors.
Cross-cutting modules:
- [packages/jssdk/src/core/http](packages/jssdk/src/core/http) — `AbstractHttp` transport (axios-based) implementing `TypeHttp`, `AjaxResult` / `AjaxError`, and the limiter stack: `RateLimiter`, `OperatingLimiter`, `AdaptiveDelayer`, `ParamsFactory`. URL patterns: v2 → `/rest/.json`, v3 → `/rest/v3/`. `Result` and `AjaxResult` are the uniform return types — v2 paging uses `isMore()` + `getNext(b24.getHttpClient(ApiVersion.v2))`; v3 has no `getNext` and uses `actions.v3.callList.make` / `fetchList.make` instead. There is no `b24.http` field — always go through `b24.getHttpClient(version)`. Credentials in request payloads are redacted before reaching the logger via [packages/jssdk/src/core/http/redact.ts](packages/jssdk/src/core/http/redact.ts).
- [packages/jssdk/src/helper](packages/jssdk/src/helper) — `B24HelperManager` aggregates `profile`, `app`, `payment`, `license`, `currency`, and `options` sub-managers (each extending [packages/jssdk/src/helper/abstract-helper.ts](packages/jssdk/src/helper/abstract-helper.ts)). `useB24Helper()` is a closure-based composable that wires lifecycle (init/destroy) and Pull client subscription. `LicenseManager` automatically swaps in enterprise restriction params.
- [packages/jssdk/src/pullClient](packages/jssdk/src/pullClient) — Pull (push) client with WebSocket + long-polling connectors, channel manager, JSON-RPC, and protobuf decoders (the protobuf JS files are eslint-ignored — see [eslint.config.mjs](eslint.config.mjs)).
- [packages/jssdk/src/types](packages/jssdk/src/types) — public types and enums (CRM, catalog, bizproc, event, placement, pull, payloads, etc.). Prefer importing enums like `EnumCrmEntityTypeId` from the package root.
- [packages/jssdk/src/tools](packages/jssdk/src/tools) — `Text` (Luxon dates, number/format helpers, UUID v7), `Type` runtime guards, `Browser`, `useFormatters`, `pick / omit / getEnumValue`, scroll/environment utilities.
- [packages/jssdk/src/logger](packages/jssdk/src/logger) — `LoggerBrowser.build(name, isDev)` + `LoggerFactory`. `AbstractB24` defaults to `LoggerFactory.createNullLogger()`; a real logger is installed via `b24.setLogger(logger)`.
- The build-time tokens `__SDK_VERSION__` and `__SDK_USER_AGENT__` are replaced by unbuild ([packages/jssdk/build.config.ts](packages/jssdk/build.config.ts)). The Nuxt module replaces `__SDK_VERSION__` the same way for its `meta.version` ([packages/jssdk-nuxt/build.config.ts](packages/jssdk-nuxt/build.config.ts)).
The Nuxt module ([packages/jssdk-nuxt/src/module.ts](packages/jssdk-nuxt/src/module.ts)) is intentionally tiny — it only registers [packages/jssdk-nuxt/src/runtime/plugin.ts](packages/jssdk-nuxt/src/runtime/plugin.ts). Any new SDK surface that needs Nuxt auto-import / SSR handling has to be wired through that runtime plugin.
> **Keep this section current.** When you add a new cross-cutting module or change the responsibility boundary of an existing one, update this Architecture section in the same PR — the contributing guides are detailed references, but Architecture is the only top-level map agents read by default.
## Agent Skills (`skills/`)
For AI coding agents, the canonical task-focused skills live under `skills/`. Load them by topic — **do not load everything at once**, the skill descriptions are designed for selective consumption.
| Skill | When to use |
|---|---|
| `b24jssdk-core` | First skill to load — entry point pick (`B24Hook` / `B24Frame` / `B24OAuth`), boot/teardown, error taxonomy, `hardErrorCodes` / `softErrorCodes` / `retryOnNetworkError` tuning |
| `b24jssdk-rest` | `actions.v{2,3}.*.make()` — `call` / `batch` / `callList` / `fetchList` / `callTail` / `fetchTail` / `batchByChunk`; `AjaxResult` shape; v2/v3 routing (no v3 allowlist — the server validates) |
| `b24jssdk-filtering` | v2 prefix-keyed filter (`'>=createdTime'`) vs v3 array-of-triples (`[['fld','>=',v]]`); operators; dates via `Text.toB24Format`; `order`-stripping rule of `callList` |
| `b24jssdk-frame-ui` | iframe-only managers: slider / dialog (`selectUser/Users/CRM/Access`) / parent / placement (with `setValue`) / options / auth |
| `b24jssdk-helpers` | `useB24Helper`, `B24HelperManager`, Pull client, currency formatting, app/user options |
| `b24jssdk-recipes` | 12 end-to-end TypeScript mini-apps, type-checked in CI via `pnpm run skills:typecheck` |
| `b24jssdk-vibecode` | When to combine the SDK with the VibeCode HTTP API (rarely — keeps them apart by default) |
Maintenance / audit docs sit alongside in `.github/contributing/maintenance.md` (weekly `docs/llms-full.txt` review playbook), `.github/contributing/report.md` (anchor-facts citing `packages/jssdk/src/...` lines), and `.github/contributing/suggested-examples.md` (prioritised gaps).
Skills under `skills/` mirror the `.github/contributing/` guides from the agent angle — when you change the underlying API, update **both** in the same PR.
## Commands
Run from the repo root unless noted. All scripts go through pnpm workspaces — never `npm`/`yarn`. Use `pnpm --filter ` for package-scoped commands.
```bash
pnpm install
pnpm run dev:prepare # build/stub all packages so workspaces resolve
# Core SDK
pnpm run package-jssdk:build # esm + umd + umd-min via unbuild
pnpm run package-jssdk:typecheck
pnpm run package-jssdk:lint
# Nuxt module
pnpm run package-jssdk-nuxt:build
pnpm run package-jssdk-nuxt:typecheck
# Docs / playgrounds
pnpm run docs:dev
pnpm run playground-nuxt:dev
# Repo-wide
pnpm run lint
pnpm run lint:fix # run before commit
pnpm run typecheck # every workspace, sequential
```
### Tests
Three Vitest projects, defined in [vitest.config.ts](vitest.config.ts):
- `jsSdk:unit` — `test/integration/**/*.unit.spec.ts`, 10s timeouts, **no portal required**. Runs in CI.
- `jsSdk:integration` — `test/integration/**/*.spec.ts` (excluding `*.unit.spec.ts`), 30s timeouts, hits a real portal.
- `jsSdk:underLoad` — `test/under-load/**.spec.ts`, sequential, 40-min timeouts.
`jsSdk:integration` and `jsSdk:underLoad` load `.env.test` (gitignored). Copy `.env.test-example` and set `B24_HOOK` to a real webhook URL — the underlying client constructor in [test/0_setup/setup-integration-jssdk.ts](test/0_setup/setup-integration-jssdk.ts) throws without it. In tests, reach this client through `setupB24Tests()` from [test/0_setup/hooks-integration-jssdk.ts](test/0_setup/hooks-integration-jssdk.ts) — see [`.github/contributing/testing.md`](.github/contributing/testing.md) for the canonical pattern.
**Webhook scopes.** The webhook needs at minimum `crm`, `tasks`, `user`, `im`, and `main`. The `im` scope is required by the issue-23 regression spec (`im.chat.get` inside a batch); `main` is a non-obvious scope (not exposed in the standard webhook scope picker) required by the v3 batch-ref spec that calls `main.eventlog.list`. Add it manually.
```bash
pnpm run package-jssdk:test:run-unit # one-shot, unit project (no portal — also runs in CI)
pnpm run package-jssdk:test # watch, integration project
pnpm run package-jssdk:test:run # one-shot, integration project
pnpm run package-jssdk:test-integration-core # filter by name "core"
pnpm run package-jssdk:test-integration-js-docs # filter by name "js-docs"
# Under-load runners (each targets one named scenario — see package.json for the full list)
pnpm run package-jssdk:test:run-underLoad-v3-call
pnpm run package-jssdk:test:run-underLoad-v3-batch
pnpm run package-jssdk:test:run-underLoad-v2-call-with-operating
# Single test by name from the root:
pnpm vitest run --project jsSdk:integration -t ""
pnpm vitest run --project jsSdk:unit -t ""
```
**Tests hit real Bitrix24 REST endpoints — never mock responses.** They validate API contracts, so a passing mocked test would defeat the suite's purpose.
**`*.unit.spec.ts` files inside `test/integration/`.** A small number of regression specs (`batch-null-result.unit.spec.ts`, `http-logger-redaction.unit.spec.ts`, `retry-client-error.unit.spec.ts`) exercise pure-logic invariants that have nothing to verify against a live portal. They belong to the `jsSdk:unit` project, mock the axios client / construct SDK primitives in isolation, and run without `.env.test` / `B24_HOOK`. Use this naming when the test is about the **SDK's internal behaviour**, not about the REST contract.
**CI runs `jsSdk:unit` automatically** (`pnpm run package-jssdk:test:run-unit`) — no portal needed. `jsSdk:integration` and `jsSdk:underLoad` are your local-only responsibility — make sure the relevant test filter is green against a real portal before pushing.
A nightly **[`smoke-retry.yml`](.github/workflows/smoke-retry.yml)** workflow runs the `playgrounds/cli` retry-policy smoke scenarios against a live portal. It reuses the existing `NUXT_BITRIX24_TEST_WEBHOOK_URL` **repository secret** (a webhook URL), mapping it onto the `B24_HOOK` env var the CLI reads — so no new secret is needed; it skips with a warning when that secret is absent and never runs on pull requests. It fails (non-zero exit) on a definitive PR #45 regression. See [playgrounds/cli/README.md](playgrounds/cli/README.md#running-in-ci-nightly).
### Releasing
Cutting a release (bump → changelog → tag → publish) is documented in **[RELEASING.md](RELEASING.md)**. In brief: `pnpm run release:bump ` sets all three `package.json` files in lockstep (+ refreshes the lockfile), and publishing a GitHub Release on the `v` tag triggers two sibling workflows — [`npm-publish-js-sdk.yml`](.github/workflows/npm-publish-js-sdk.yml) (`@bitrix24/b24jssdk`) and [`npm-publish-js-sdk-nuxt.yml`](.github/workflows/npm-publish-js-sdk-nuxt.yml) (`@bitrix24/b24jssdk-nuxt`) — each running CI then publishing its package. Publishing uses npm OIDC trusted publishing — there is no `NPM_TOKEN` to manage. The two files exist because each package's npm Trusted Publisher entry is keyed to one exact workflow filename; see RELEASING.md for the consolidation path.
## Key Conventions
- **Conventional Commits**: `feat`/`fix` for behaviour, `docs`/`chore` otherwise (e.g. `feat(http): add adaptive delayer`, `fix(frame): refresh auth on 401`). The CHANGELOG is generated from these.
- **No default exports**: every export is named so consumers stay tree-shakeable (`sideEffects: false`).
- **TypeScript strict**: `tsc` for the core SDK; `vue-tsc` for the Nuxt / docs / playground projects.
- **Public contract**: exports from [packages/jssdk/src/index.ts](packages/jssdk/src/index.ts) are a public API. Any breaking change needs a deprecation cycle, not a silent rename.
- **Cross-package awareness**: when you change the core SDK API, check whether [packages/jssdk-nuxt/src/runtime/plugin.ts](packages/jssdk-nuxt/src/runtime/plugin.ts) needs an update.
- **Code formatting**: `@nuxt/eslint-config` (flat) with stylistic overrides — 2-space indent, no trailing commas, 1tbs braces. `.editorconfig` enforces LF + 2 spaces. The protobuf JS files in `packages/jssdk/src/pullClient` are eslint-ignored intentionally.
- **Build tokens**: `__SDK_VERSION__` and `__SDK_USER_AGENT__` are replaced at build time by [packages/jssdk/build.config.ts](packages/jssdk/build.config.ts); the Nuxt module's `meta.version` uses the same `__SDK_VERSION__` token via [packages/jssdk-nuxt/build.config.ts](packages/jssdk-nuxt/build.config.ts). Do not hard-code versions.
- **Pinned Actions**: third-party GitHub Actions in `.github/workflows/` are pinned to a full commit SHA with a `# vX.Y.Z` comment — a moved or compromised mutable tag on a publish-privileged action is a supply-chain risk (#152). Dependabot (weekly, grouped) bumps the SHAs; don't introduce `@vN` tag refs in new steps.
- **No secrets in logs**: inside `packages/jssdk/src/core/http/**`, never pass a URL- or credential-shaped variable into a logger context object — log the bare REST method name (not the formatted URL) and let `redactSensitiveParams()` handle params. A scoped `no-restricted-syntax` ESLint rule enforces this against the #39/#40 webhook-secret-leak class; use `// eslint-disable-next-line no-restricted-syntax` with a reason for a genuine exception (#42).
- **English only** in code, comments, and documentation pages.
## SDK Source (`packages/jssdk/src/` and `test/`)
The references and code conventions below apply specifically when working on files under `packages/jssdk/src/` or `test/`. They do not apply to `docs/`, `playgrounds/`, or the Nuxt module.
### References
Load these based on your task. **Do not load all files at once** — only load what's relevant.
| File | Topics |
|------|--------|
| **[.github/contributing/package-structure.md](.github/contributing/package-structure.md)** | File layout in `packages/jssdk/src/`, AbstractB24-derived classes, managers, public-export rules |
| **[.github/contributing/transports-and-results.md](.github/contributing/transports-and-results.md)** | `AbstractHttp` v2 / v3 transports, `Result` / `AjaxResult` / `AjaxError` / `SdkError`, the limiter stack, `ParamsFactory` presets, paging, batch semantics, log redaction |
| **[.github/contributing/testing.md](.github/contributing/testing.md)** | Vitest projects (integration + under-load), `.env.test`, `setupB24Tests()`, naming filters, no-mock policy + `*.unit.spec.ts` exception |
| **[.github/contributing/documentation.md](.github/contributing/documentation.md)** | `docs/content/docs/` Markdown structure, frontmatter (`links`, `category`, `restApiVersion`), MDC blocks (`::warning`, `::caution`, `::rest-api-version-only`), examples |
| **[.github/contributing/reproducing-user-reports.md](.github/contributing/reproducing-user-reports.md)** | Turning a user report into a runnable REST chain via the nuxt playground `IssueReproHarness.vue`, running it in the app OAuth context, reading the request/response transcript, and deciding SDK-bug vs caller-context vs platform |
### Code Conventions
| Convention | Description |
|------------|-------------|
| Named exports only | No `export default` in `src/`; keeps the package tree-shakeable |
| Type imports | Always separate: `import type { X } from '…'` |
| Public surface | Every type / symbol meant for callers must re-export from [packages/jssdk/src/index.ts](packages/jssdk/src/index.ts) |
| Action surface | Reach REST through `b24.actions.vX..make({ method, params, requestId })`. The top-level shortcuts (`b24.callMethod`, `callBatch`, `callListMethod`, `fetchListMethod`, `callBatchByChunk`) are `@deprecated` (see `@removed` tag on each method) — do not use them in new code |
| Result types | All transport methods return `Result` or `AjaxResult` — never raw axios responses |
| Paging | v2: `result.isMore()` + `result.getNext(b24.getHttpClient(ApiVersion.v2))`. v3: use `b24.actions.v3.callList.make(...)` / `fetchList.make(...)` (v3 has no `getNext`) |
| Errors | Throw `SdkError({ code, description, status })` for SDK-level invariants; HTTP errors are `AjaxError` and surface via `Result.getErrors()` |
| Logger | New modules hold a `LoggerInterface`, default to `LoggerFactory.createNullLogger()`, and accept a real logger through `setLogger(logger)` — not via the constructor. Suppression of warnings (e.g. `offClientSideWarning()`) is for testing only — never call it in production code |
| Limiters | New transport code paths must respect the limiter stack (`RateLimiter`, `OperatingLimiter`, `AdaptiveDelayer`) — do not bypass it. HTTP 4xx is automatically classified as non-retryable; `hardErrorCodes` is for HTTP 2xx domain-level error codes the SDK doesn't recognise as terminal |
| Enterprise | Treat the enterprise restriction params from `LicenseManager` as load-time switches; do not duplicate them |
| Build tokens | Use `__SDK_VERSION__` / `__SDK_USER_AGENT__` placeholders, never literal version strings |
| Deprecation | Mark with JSDoc `@deprecated`, add `@removed X.Y.Z` and (if useful) `@memo` tags, and emit a runtime warning with `LoggerFactory.forcedLog(this._logger, 'warning', { … })`. See `abstract-b24.ts` for the canonical pattern |
| Pull protobuf | The auto-generated protobuf JS is intentionally eslint-ignored — do not "clean it up" |
## Workflows
### Adding a New Surface (manager, action, transport, helper, type module)
Copy this checklist and track progress:
```text
Surface: [name]
Progress:
- [ ] 1. Decide the entry point that owns it (B24Frame, B24Hook, B24OAuth, or shared in core)
- [ ] 2. Implement under packages/jssdk/src// (named exports only)
- [ ] 3. Add / extend types under packages/jssdk/src/types/
- [ ] 4. Re-export from packages/jssdk/src/index.ts if part of the public contract
- [ ] 5. If transport-related: respect RateLimiter / OperatingLimiter / AdaptiveDelayer
- [ ] 6. Add integration test under test/integration// (real portal, no mocks)
- [ ] 7. Add or update the matching docs page under docs/content/docs/
- [ ] 8. If the public API changed: check packages/jssdk-nuxt/src/runtime/plugin.ts
- [ ] 9. Run pnpm run lint:fix
- [ ] 10. Run pnpm run typecheck
- [ ] 11. Run the relevant test filter (e.g. pnpm run package-jssdk:test-integration-core)
```
### Adding a REST Method to an Existing Action
The lightweight path for the most common change — calling a new REST method from caller code or wrapping it in a tiny helper. The full New Surface workflow above is overkill here.
```text
Method: [rest.method.name]
Progress:
- [ ] 1. Determine v2 or v3 (default v3 if available; v2 logs a deprecation warning when v3 exists)
- [ ] 2. Call b24.actions.vX.call.make({ method, params, requestId }) from your code, or add a typed helper if used in 3+ places
- [ ] 3. Add an integration test under test/integration// tagged @apiVx (real portal, no mocks)
- [ ] 4. Update the matching docs page under docs/content/docs/ — parameter table, signature block, examples
- [ ] 5. Run pnpm run lint:fix and pnpm run typecheck
- [ ] 6. Run the relevant test filter locally
```
**Switch to the New Surface workflow** if step 2 grows beyond a single typed helper — i.e. the change introduces a new public type, a new manager class, or a new re-export from `packages/jssdk/src/index.ts`. At that point you owe the full lifecycle (types, Nuxt-module check, README-AI sync).
## PR Review Checklist
When reviewing PRs that touch `packages/jssdk/src/` or `test/`, verify:
```text
PR Review:
- [ ] Follows the file layout in .github/contributing/package-structure.md
- [ ] No default exports introduced
- [ ] Transport changes respect the limiter stack and return Result / AjaxResult
- [ ] Errors use SdkError ({ code, description, status }) / AjaxError — no bare throws or string-form SdkError
- [ ] Public API changes are re-exported from src/index.ts
- [ ] Public API changes have a deprecation cycle (no silent renames): JSDoc @deprecated + @removed tag + LoggerFactory.forcedLog warning
- [ ] Frame changes bootstrap only through initializeB24Frame() (no alternative constructor paths)
- [ ] Helper sub-managers extend AbstractHelper and follow the constructor(b24: TypeB24) + setLogger() shape
- [ ] B24Hook usage is server-side only; no logs of raw request URLs (which contain webhook secrets in the path)
- [ ] Integration tests added / updated and hit a real portal (no response mocks, except *.unit.spec.ts for pure-logic invariants)
- [ ] Matching docs page under docs/content/docs/ updated in the same PR
- [ ] If the change alters a pattern documented in .github/contributing/, the relevant guide is updated in the same PR
- [ ] Conventional commit message
- [ ] All local checks pass (lint, typecheck, relevant test filter)
```
## Documentation Upkeep
After any code change that alters a public API (signatures, accepted parameters, return shapes, runtime behaviour, error codes, or warnings emitted), update the matching Markdown page under `docs/content/docs/`. The docs site is the public source of truth — out-of-date docs are treated as a bug equal to a broken test.
- Find the page that documents the changed surface (e.g. `2.call-list-rest-api-ver2.md` for `CallListV2`, `2.fetch-list-rest-api-ver3.md` for `FetchListV3`). Each page links to its source file in the front-matter `links:` section — use that to confirm the mapping.
- All documentation prose, parameter tables, code samples, and notes must be written in **English**.
- Sync these sections in particular: the parameter table (types and descriptions), the **Method Signature** block, the **Limitations** / **Key Concepts** notes, and any runnable example that uses the changed surface.
- If the change introduces a new `warning`/`error` log message or a new `SdkError` code, mention it in the relevant section (Limitations, Error Handling, or Key Concepts) so users can recognise it.
- Documentation updates belong in the same commit/PR as the code change. Use a `docs:` commit only when the change is documentation-only.
### REST page skeleton (action / tools categories)
Pages with `category: actions` or `category: tools` follow a fixed section order. The lint script `pnpm run docs:lint-pages` enforces this. **Required, in order:**
1. `## Overview`
2. `## Method Signature`
3. `## Examples`
4. `## Alternatives and Recommendations`
`## Error Handling` is recommended (warned if missing) and conventionally placed between `## Method Signature` and `## Examples`. `## Key Concepts`, `## Limitations`, and `## Performance Optimization` are optional but, when present, conventionally appear before `## Examples`.
Cookbook recipes under `docs/content/docs/99.examples/` follow a separate, lighter skeleton and are not skeleton-linted. **Required** sections, in order: `## Goal`, `## Stack & Prerequisites`, `## Full Example`, `## Run It`, `## How It Works`, `## See also`. **Optional**: `## Limitations` (drop it only if the recipe has no known sharp edges).
### `audited:` frontmatter
Every action / tools page should carry an `audited: YYYY-MM-DD` field stating the date its content was verified against the linked source. `pnpm run docs:lint-pages` extracts every `https://github.com/bitrix24/b24jssdk/blob/main/` from the page's `links:` array, runs `git log -1 --format=%cI -- `, and warns if any source's last commit is newer than `audited`. Only non-Markdown sources drive this check — `.md` / `.mdx` link targets (skills, `AGENTS.md`, `CHANGELOG.md`) are parallel docs, not the API source of truth, so editing them never ages a page's audit (a one-line skill edit shouldn't staleify every page that cites it). When you sync a page after a code change, bump `audited` to today's date in the same commit. Use `pnpm run docs:lint-pages:strict` in CI gates that should fail on stale pages.
`pnpm run docs:lint-links` is the companion check — validates every internal `](/docs/...)` and frontmatter `to: /docs/...` against the actual file tree, and validates `#fragment` parts against actual page headings (slugified the way Nuxt Content does at runtime, including the `-1` / `-2` suffixes github-slugger emits on duplicate headings).
## Git Workflow
- Branch from `main` before code changes. Naming: `ai/` (lowercase, hyphens) — e.g. `ai/add-auth-helper`, `ai/fix-validation`. Branches created automatically by Claude Code on the web carry a `claude/` prefix instead — both prefixes are acceptable.
- No branch needed for search / read / exploration.
- Before commit: `pnpm run lint:fix` and `pnpm run typecheck` must both pass.
- Keep commit messages clear and Conventional-Commits compliant.
## Before Submitting
- [ ] `pnpm run lint:fix` clean
- [ ] `pnpm run typecheck` passes
- [ ] Relevant Vitest project run is green against a real portal (CI does not run tests)
- [ ] Documentation updated in the same PR if the public surface changed
- [ ] If a pattern documented in `.github/contributing/*.md` changed, the matching guide is updated in the same PR
- [ ] If you touched `AGENTS.md` or any guide under `.github/contributing/`, refresh the `Last reviewed` stamp at the top to today's date
- [ ] Commit messages follow Conventional Commits
Multiple commits are fine — PRs are squash-merged, so no need to rebase or force-push.
## Resources
> **Conflict resolution.** This `AGENTS.md` is the canonical agent-facing guide. The four guides under `.github/contributing/` are authoritative for their respective scopes. [packages/jssdk/README-AI.md](packages/jssdk/README-AI.md) is the current caller-facing API guide and is being progressively absorbed into this guide and the contributing files — when its content disagrees with this guide, this guide wins.
- [packages/jssdk/README-AI.md](packages/jssdk/README-AI.md) — current caller-facing API guide (transitional)
- [Bitrix24 REST API docs](https://apidocs.bitrix24.com/)
- [Repository on GitHub](https://github.com/bitrix24/b24jssdk)