Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c0911d8
feat: Make junior deployable as an npm dependency
dcramer Feb 28, 2026
ecbc66c
ref: Move Sentry home content to jr-sentry/ and use home module
dcramer Feb 28, 2026
8b2b969
chore(rebase): Align lockfile and thread normalization test
dcramer Mar 4, 2026
af5eea7
fix(packaging): Restore deployable runtime compatibility
dcramer Mar 4, 2026
46d0e89
ref(packaging): Move to wrapper-first Next.js integration
dcramer Mar 4, 2026
b25a808
fix(next): Declare runtime directly in app routes
dcramer Mar 4, 2026
27eb17c
feat(package): Switch to convention-based Next.js project layout
dcramer Mar 4, 2026
8efbf08
docs(readme): Use JS wrapper files in Next.js setup
dcramer Mar 4, 2026
22bc9c7
fix(packaging): preserve tracing includes and webhook route parity
dcramer Mar 4, 2026
9ed60db
fix(packaging): restore slack file_share handling patch
dcramer Mar 4, 2026
1af3c2d
fix(config): restore AI_MODEL fallback for fast model
dcramer Mar 4, 2026
c144c30
fix(config): dedupe server external package defaults
dcramer Mar 4, 2026
ff84291
fix(build): declare smol-toml as runtime dependency
dcramer Mar 4, 2026
3838d32
fix(packaging): align handler exports and external deps
dcramer Mar 4, 2026
a264bd8
test(packaging): stop asserting handler runtime export
dcramer Mar 4, 2026
4966798
build(workspace): add jr-sentry consumer app workspace
dcramer Mar 4, 2026
cac70e7
build(packaging): remove unused smol-toml dependency
dcramer Mar 4, 2026
6b85ee8
ref(monorepo): relocate junior into packages workspace
dcramer Mar 4, 2026
cca0b01
fix(templates): remove unused sentry release env vars
dcramer Mar 4, 2026
72582c6
fix(build): make junior build stable in workspace layout
dcramer Mar 4, 2026
d73baf3
fix(router): forward oauth callback requests in catch-all handler
dcramer Mar 4, 2026
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
9 changes: 0 additions & 9 deletions .env.example

This file was deleted.

91 changes: 8 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,90 +1,15 @@
# junior
# junior monorepo

Slack bot built with Next.js + Chat SDK.
This repository is organized as a workspace:

Junior responds when mentioned in Slack and can continue replying in subscribed threads. It also supports slash-invoked local skills (`/skill-name ...`) and built-in tools (web search/fetch, image generation, Slack canvases/lists).
- `packages/junior`: publishable `junior` package and source
- `packages/jr-sentry`: smoke-test consumer app that uses `junior` via `workspace:*`

## Requirements

- Node.js 20+
- pnpm
- Vercel CLI
- Slack app credentials already configured in Vercel
- Redis configured in Vercel (`REDIS_URL`)

## Local setup

1. Install dependencies.
Common commands from repo root:

```bash
pnpm install
pnpm build:pkg
pnpm test
pnpm --filter jr-sentry build
```

2. Link this repo to the Sentry Vercel project and pull dev env.

```bash
pnpm dlx vercel@latest login
pnpm dlx vercel@latest switch
pnpm dlx vercel@latest link --yes --scope sentry
pnpm dlx vercel@latest env pull .env --environment=development --scope sentry
```

3. Start the app.

```bash
pnpm dev
```

## Slack tunnel (Cloudflare)

Install `cloudflared` if you don't have it (`brew install cloudflared` on macOS).

### Quick (random hostname each time)

```bash
cloudflared tunnel --url http://localhost:3000
```

### Stable hostname (one-time setup)

Requires a free Cloudflare account and a domain managed through Cloudflare DNS.

```bash
cloudflared tunnel login
cloudflared tunnel create junior-dev
cloudflared tunnel route dns junior-dev junior-dev.yourdomain.com
```

Then each time you develop:

```bash
cloudflared tunnel run --url http://localhost:3000 junior-dev
```

### Configuring Slack

Set Slack Event Subscriptions and Interactivity request URL to:

```text
https://<tunnel-host>/api/webhooks/slack
```

With a stable hostname you only need to do this once. Invite `@junior` to a channel and mention it.

## Evals

Use evals for end-to-end behavior testing of Junior's reply pipeline (prompting, tools, and expected outputs) with LLM-judged numeric scoring.

Evals intentionally exclude live Slack integration concerns (Slack transport, app permissions, and webhook delivery).

Authoring guidance lives in `evals/README.md` and `specs/testing/evals-spec.md`.

```bash
pnpm evals
```

## Test env isolation

Vitest loads `.env`, `.env.local`, `.env.test`, then `.env.test.local` so test-specific values override development/prod values.

Slack credentials are intentionally replaced with test values for tests/evals to prevent accidental use of real Slack tokens.
21 changes: 0 additions & 21 deletions next.config.ts

This file was deleted.

60 changes: 12 additions & 48 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,44 +1,18 @@
{
"name": "junior",
"version": "0.1.0",
"name": "junior-monorepo",
"private": true,
"type": "module",
"packageManager": "pnpm@10.30.2",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "pnpm run test:slack-boundary && vitest run",
"test:watch": "vitest",
"preevals": "pnpm run test:slack-boundary",
"evals": "pnpm exec vitest run -c vitest.evals.config.ts",
"test:slack-boundary": "node scripts/check-slack-test-boundary.mjs",
"typecheck": "tsc --noEmit",
"skills:check": "node scripts/check-skills.mjs"
},
"dependencies": {
"@ai-sdk/gateway": "^3.0.57",
"@chat-adapter/slack": "^4.15.0",
"@chat-adapter/state-memory": "^4.15.0",
"@chat-adapter/state-redis": "^4.15.0",
"@mariozechner/pi-agent-core": "^0.55.0",
"@mariozechner/pi-ai": "^0.55.0",
"@sentry/nextjs": "^10.40.0",
"@sinclair/typebox": "^0.34.48",
"@slack/web-api": "^7.14.1",
"@vercel/sandbox": "^1.7.1",
"@workflow/serde": "4.1.0-beta.2",
"ai": "^6.0.103",
"bash-tool": "^1.3.15",
"chat": "^4.15.0",
"just-bash": "^2.10.6",
"next": "^16.1.6",
"node-html-markdown": "^2.0.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"workflow": "4.1.0-beta.60",
"yaml": "^2.8.2",
"zod": "^4.3.6"
"dev": "pnpm --filter junior dev",
"build": "pnpm --filter junior build",
"build:pkg": "pnpm --filter junior build:pkg",
"start": "pnpm --filter junior start",
"test": "pnpm --filter junior test",
"test:watch": "pnpm --filter junior test:watch",
"preevals": "pnpm --filter junior preevals",
"evals": "pnpm --filter junior evals",
"typecheck": "pnpm --filter junior typecheck",
"skills:check": "pnpm --filter junior skills:check"
},
Comment thread
cursor[bot] marked this conversation as resolved.
"pnpm": {
"peerDependencyRules": {
Expand All @@ -48,17 +22,7 @@
}
},
"patchedDependencies": {
"@chat-adapter/slack": "patches/@chat-adapter__slack.patch"
"@chat-adapter/slack": "packages/junior/patches/@chat-adapter__slack.patch"
}
},
"devDependencies": {
"@types/node": "^25.3.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"msw": "^2.12.10",
"typescript": "^5.9.3",
"vercel": "^50.23.2",
"vitest": "^4.0.18",
"vitest-evals": "^0.6.0"
}
}
7 changes: 7 additions & 0 deletions packages/jr-sentry/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
JUNIOR_BOT_NAME=
AI_MODEL=
AI_FAST_MODEL=
REDIS_URL=
NEXT_PUBLIC_SENTRY_DSN=
4 changes: 4 additions & 0 deletions packages/jr-sentry/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
.next/
.env
.env.local
2 changes: 2 additions & 0 deletions packages/jr-sentry/app/api/[...path]/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { GET, POST } from "junior/handler";
export const runtime = "nodejs";
1 change: 1 addition & 0 deletions packages/jr-sentry/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "junior/app/layout";
3 changes: 3 additions & 0 deletions packages/jr-sentry/data/SOUL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# jr-sentry

You are jr-sentry, a helpful assistant.
1 change: 1 addition & 0 deletions packages/jr-sentry/instrumentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { register, onRequestError } from "junior/instrumentation";
11 changes: 11 additions & 0 deletions packages/jr-sentry/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { withJunior } from "junior/config";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default withJunior({
turbopack: {
root: path.resolve(__dirname, "../..")
}
Comment thread
dcramer marked this conversation as resolved.
});
18 changes: 18 additions & 0 deletions packages/jr-sentry/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "jr-sentry",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@sentry/nextjs": "^10.0.0",
"junior": "workspace:*",
"next": "^16.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
Empty file.
Empty file.
11 changes: 11 additions & 0 deletions packages/junior/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
JUNIOR_BOT_NAME= # Defaults to "junior"
AI_MODEL= # Defaults to anthropic/claude-sonnet-4.6
AI_FAST_MODEL= # Defaults to anthropic/claude-haiku-4-5
REDIS_URL=
SKILL_DIRS= # Additional skill directories (colon-separated)
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY=
GITHUB_INSTALLATION_ID=
NEXT_PUBLIC_SENTRY_DSN=
Empty file added packages/junior/.env.test
Empty file.
Loading