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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
15 changes: 15 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",

"name": "ChatBotKit Platform",

"image": "mcr.microsoft.com/devcontainers/javascript-node:24-bookworm",

"features": {
"ghcr.io/devcontainers/features/git-lfs:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},

"postCreateCommand": "corepack enable"
}
47 changes: 47 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Dependencies (installed fresh in the container)
node_modules
**/node_modules

# Build outputs and caches
**/.next
**/dist
**/.turbo
**/.swc
**/*.tsbuildinfo
**/coverage
**/storybook-static
platform/sites

# @note content trees stay IN the context deliberately - they are read
# during static generation. Tests do not (the image build skips them via
# SKIP_BUILD_TESTS), and underscore-prefixed files under pages/ are private
# partials and page tests that must not compile into routes - the same set
# the deploy workflow's "Remove private files" step strips, minus the Next.js
# specials at the pages root which are re-included below.
**/*.utest.*
**/*.itest.*
platform/pages/**/_*
!platform/pages/_*

# Environment files (passed at runtime, never baked in)
**/.env
**/.env.*
!**/.env.example

# Git metadata and CI definitions - nothing in the build reads them
.git
**/.github

# Editor and CI
.devcontainer
.vscode
**/.storybook
# @note the storybook stories folder only - platform/content/stories is real
# site content the build imports
platform/stories

# Logs and scratch
**/*.log

# pnpm store fallback location (dev container)
**/.pnpm-store
36 changes: 36 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Binary assets - routed through Git LFS, with no text diffs and no eol
# normalization. The tree carries ~75MB of binaries across ~80 files (app
# banners, example imagery, fonts, media, and the generated embeddings
# archives; the largest single file is ~14MB), and that grows every time a
# screenshot or an asset lands. LFS keeps those bytes out of every clone's
# pack and out of PR diffs.
#
# This mirrors the monorepo root .gitattributes, which already LFS-tracks the
# same extensions, so a file's storage does not change depending on which
# repository it is checked out from.
#
# @note SVG is deliberately absent: it is XML, it diffs usefully, and the
# files here are small.
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.eot filter=lfs diff=lfs merge=lfs -text
*.woff filter=lfs diff=lfs merge=lfs -text
*.woff2 filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.doc filter=lfs diff=lfs merge=lfs -text
*.docx filter=lfs diff=lfs merge=lfs -text
*.ppt filter=lfs diff=lfs merge=lfs -text
*.pptx filter=lfs diff=lfs merge=lfs -text
*.xls filter=lfs diff=lfs merge=lfs -text
*.xlsx filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text

# Generated archives - keep them out of PR diff stats
*.embeddings.json.gz linguist-generated
52 changes: 52 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Bug report
description: Something does not work the way the code says it should
labels: ['bug']
body:
- type: markdown
attributes:
value: >-
Thanks for taking the time. A reproducible report is the fastest path
to a fix - the closer you get us to the failure, the sooner it dies.

- type: textarea
id: what-happened
attributes:
label: What happened
description: >-
What you did, what you expected, and what you got instead. Include the
exact error output where there is one.
validations:
required: true

- type: textarea
id: reproduction
attributes:
label: How to reproduce it
description: >-
The smallest sequence of steps that shows the problem on a fresh
checkout. If it only happens with specific configuration, include the
relevant (redacted) settings.
placeholder: |
1. pnpm install
2. ...
validations:
required: true

- type: textarea
id: environment
attributes:
label: Environment
description: >-
How you are running the platform and on what.
placeholder: |
- run mode: docker compose / pnpm dev / built image
- node: 24.x, pnpm: 11.x
- os: ...
validations:
required: true

- type: textarea
id: extra
attributes:
label: Anything else
description: Logs, screenshots, or a theory about the cause.
7 changes: 7 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Security report
url: https://github.com/chatbotkit/platform/blob/main/SECURITY.md
about: >-
Do not report security issues publicly. Email contact@cbk.ai
with "Security report" in the subject - see SECURITY.md.
28 changes: 28 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Feature request
description: Something the platform should do that it does not
labels: ['enhancement']
body:
- type: textarea
id: problem
attributes:
label: The problem
description: >-
What you are trying to do and what stops you today. Lead with the
problem rather than the solution - it keeps the discussion honest
about alternatives.
validations:
required: true

- type: textarea
id: proposal
attributes:
label: Proposed shape
description: >-
How you imagine it working, if you have a shape in mind. API surface,
configuration, interface - whatever level you have.

- type: textarea
id: alternatives
attributes:
label: What you do instead today
description: Workarounds you use now, and where they fall short.
26 changes: 26 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!--
Thanks for contributing. A short, honest description beats a long template,
so this one stays out of your way - but three things matter:

1. Say WHAT changes and WHY. A reviewer should not have to reverse-engineer
the intent from the diff.
2. Keep it verifiable. CI builds, lints, type-checks and tests every package,
then type-checks the application and runs its sharded unit suite. If you
touched the application, also run `pnpm lint` and the relevant production
build locally because those two application checks are not in CI yet. See
CONTRIBUTING.md for the exact commands.
3. One concern per pull request. Unrelated cleanups make reviews slower for
everyone, including you.
-->

## What

<!-- The change, in a sentence or two. -->

## Why

<!-- The problem it solves, and a link to the issue if there is one. -->

## How it was verified

<!-- Tests added or updated, manual steps taken, or why neither applies. -->
142 changes: 142 additions & 0 deletions .github/workflows/_verify.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# The repository's quality gate as a reusable workflow: install from the
# committed lockfile and type-check cleanly with the public module defaults
# - the exact environment every fresh checkout gets.
#
# Called from two places:
# - pull-request.yaml, where it gates every pull request (including forks;
# see the security notes there - this workflow uses no secrets and only
# ever needs `contents: read`)
# - publish-ghcr-platform.yaml, where it gates image builds on `next`
# pushes, which land directly without a pull request

name: Verify

on:
workflow_call:

permissions:
contents: read

env:
# pinned to the devcontainer versions
REQUIRED_PNPM_VERSION: 11.24.0
REQUIRED_NODE_VERSION: 24.20.0

jobs:
verify:
runs-on: ubuntu-latest-8-cores-amd64

# @note a healthy sequential run takes low double-digit minutes; anything
# longer is wedged - cut it off and free the hosted runner
timeout-minutes: 45

steps:
- uses: actions/checkout@v7
with:
lfs: true

- uses: pnpm/setup@v2
with:
version: ${{ env.REQUIRED_PNPM_VERSION }}
runtime: node@${{ env.REQUIRED_NODE_VERSION }}
cache: true
install: false

- name: Report runner resources
# @note exit 137 anywhere below is the runner kernel OOM-killing the
# workload: read this step's output for the VM this run actually
# landed on before blaming the workload
run: nproc && grep MemTotal /proc/meminfo && df -h . | tail -1

- name: Install from the lockfile
# @note --frozen-lockfile is the point: it fails when pnpm-lock.yaml
# has drifted from the manifests, which is the most common way a
# fresh checkout silently breaks
run: pnpm install --frozen-lockfile

- name: Audit production dependencies
# @note the published baseline is no known production vulnerabilities,
# so every advisory fails this gate. A temporary exception must name
# the advisory, rationale, owner, compensating controls and expiry in
# the public change that adds the exception before it is ignored here.
run: pnpm audit --prod --audit-level low

- name: Configure the application from the example environment
# @note the example file is the documented minimum a fresh checkout
# runs on; nothing here is a secret
working-directory: platform
run: cp .env.example .env

- name: Create the database and generate its client
# @note runs before the package steps: any package that imports the
# database module needs the generated client to build or type-check.
# The client generator reads the typed SQL against a live database, so
# an empty SQLite file is pushed first. The absolute path is
# deliberate - the push runs from the database module's directory,
# the application from its own
working-directory: platform
run: |
mkdir -p .dev
touch .dev/platform.db
export PRISMA_DATABASE_URL="file:$PWD/.dev/platform.db"
pnpm --filter @chatbotkit-dev/db db:push
pnpm db:gen

- name: Build every package
# @note the application is excluded from this step and the three
# below: its own steps further down provision the environment its
# config asserts at load. Its lint, type-check and tests run below;
# the trusted image-publication workflow builds and smoke-tests it.
run: pnpm turbo run build --continue --filter='!@chatbotkit/platform'

- name: Lint every package
# @note the lint debt that kept this step out was paid down on
# August 24 2026 (13 packages); the gate is born green
run: pnpm turbo run lint --continue --filter='!@chatbotkit/platform'

- name: Check every package
run: pnpm turbo run check --continue --filter='!@chatbotkit/platform'

- name: Test every package
run: pnpm turbo run test --continue --filter='!@chatbotkit/platform'

- name: Cache TypeScript build info
# @note persists only tsc's non-sensitive incremental project state.
# The SHA suffix lets each successful run advance the baseline, while
# the restore prefix selects the newest state built with the same
# lockfile and application compiler configuration. tsc validates file
# signatures, so a stale restore falls back to checking changed files.
# Fork pull requests can read the base cache but cannot write into its
# scope; never add generated environment or credential files here.
uses: actions/cache@v6
with:
path: platform/tsconfig.tsbuildinfo
key: ${{ runner.os }}-${{ runner.arch }}-tsbuildinfo-application-${{ hashFiles('pnpm-lock.yaml', 'platform/tsconfig.json') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-tsbuildinfo-application-${{ hashFiles('pnpm-lock.yaml', 'platform/tsconfig.json') }}-

- name: Cache Jest transform cache
# @note persists only Jest's transform/haste cache - hashed transpiled
# module output, nothing sensitive. Jest validates entries by content
# hash, so a stale restore degrades to re-transforming changed files.
# Same key discipline as the tsbuildinfo cache above: the SHA suffix
# advances the baseline, the restore prefix picks the newest state for
# the same lockfile.
uses: actions/cache@v6
with:
path: platform/.jest-cache
key: ${{ runner.os }}-${{ runner.arch }}-jest-application-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-jest-application-${{ hashFiles('pnpm-lock.yaml') }}-

- name: Lint application
working-directory: platform
run: pnpm lint

- name: Check application types
working-directory: platform
run: pnpm check

- name: Test application with coverage
working-directory: platform
run: pnpm test
Loading