Skip to content

Commit 4da9f80

Browse files
Merge pull request #87 from devarshishimpi/refactor/restructure-codra-workspace-packages
Restructure Codra into npm workspaces and extract the review engine into @codra/core
2 parents 972e445 + c996af1 commit 4da9f80

225 files changed

Lines changed: 7782 additions & 8635 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,31 +49,34 @@ jobs:
4949

5050
steps:
5151
- name: Checkout repository
52-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
52+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
5353

5454
- name: Setup Node.js
55-
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
55+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
5656
with:
57-
node-version: 20
57+
node-version: 22
5858
cache: 'npm'
5959

6060
- name: Install dependencies
6161
run: npm ci
6262

6363
- name: Static Analysis (Typecheck)
64-
run: npm run typecheck
64+
run: npm run typecheck && npm run typecheck:all
65+
66+
- name: Boundary Check (@codra/core purity)
67+
run: npm run check:boundaries
6568

66-
# Lint is not cosmetic here: eslint.config.js carries the barrel guards that stop a module from
67-
# importing a mocked barrel's sibling (which would silently void a vi.mock), plus max-lines and
68-
# import-x/no-cycle. Without this step those guards only ever ran on a developer's machine.
6969
- name: Static Analysis (Lint)
7070
run: npm run lint
7171

7272
- name: Automated Tests
7373
run: npm test
7474

75-
# Catches bundler-level breakage typecheck cannot see -- notably a client file pulling zod into
76-
# the browser bundle through @shared/schema. `vite build` rather than `npm run build` so CI does
77-
# not depend on the `wrangler types` step, which only regenerates a local .d.ts.
75+
- name: Automated Tests (packages)
76+
run: npm run test:all
77+
7878
- name: Build (client bundle)
7979
run: npx vite build
80+
81+
- name: Build (worker bundle, dry run)
82+
run: npx wrangler deploy --dry-run --outdir=.wrangler/dry

.github/workflows/codeql.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ jobs:
2222

2323
steps:
2424
- name: Checkout repository
25-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
2626

2727
- name: Initialize CodeQL
28-
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
28+
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9
2929
with:
3030
languages: ${{ matrix.language }}
3131

3232
- name: Perform CodeQL Analysis
33-
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
33+
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9

CONTRIBUTING.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,29 @@ Before we can merge your pull request, you must sign our Contributor License Agr
1212

1313
---
1414

15+
## 📦 Repository Layout
16+
17+
Codra is migrating to an npm workspace monorepo. The repository is structured into `apps/` (deployable entrypoints) and `packages/` (reusable modules):
18+
19+
```text
20+
packages/
21+
├── schema/ # Shared types + zod contracts (zero dependencies)
22+
├── core/ # Review engine (pure ports, depends on schema)
23+
├── db/ # Postgres interactions and migrations (depends on schema, core)
24+
├── models/ # LLM provider integrations (depends on schema, core)
25+
├── provider-github/ # GitHub API adapter (depends on schema, core)
26+
├── api/ # Hono router and API routes (depends on schema, core, db, models, provider-github)
27+
└── ui/ # React design system and primitives (depends on schema)
28+
29+
apps/
30+
├── worker/ # Cloudflare Worker entrypoint (wires bindings to api ports)
31+
└── dashboard/ # React SPA frontend (depends on ui, schema)
32+
```
33+
34+
**Note:** We are incrementally migrating code from the legacy `src/` directory into this workspace structure. New logic should be placed in the appropriate `packages/` or `apps/` directory when possible.
35+
36+
---
37+
1538
## 🛠️ Local Development Setup
1639

1740
Codra is a monorepo-style project built with **Hono** (Worker), **React** (Vite), and **Cloudflare Workers**.

apps/worker/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

apps/worker/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "@codra/worker",
3+
"version": "0.9.4",
4+
"private": true
5+
}

eslint.config.js

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescrip
77
export default tseslint.config(
88
{
99
ignores: [
10-
'dist/**',
11-
'node_modules/**',
10+
// `**/` matters: a bare `dist/**` only covers the root build output, so emitted .d.ts under
11+
// packages/*/dist was being linted as source.
12+
'**/dist/**',
13+
'**/node_modules/**',
1214
// Generated by `wrangler types`.
1315
'src/server/worker-env.d.ts',
1416
'worker-configuration.d.ts',
@@ -22,8 +24,6 @@ export default tseslint.config(
2224
files: ['**/*.{ts,tsx,js,mjs}'],
2325
plugins: { 'import-x': importX, 'react-hooks': reactHooks },
2426
settings: {
25-
// The resolver has to understand the @server/@client/@shared aliases from tsconfig, or every
26-
// internal import reads as unresolved and no-cycle/no-self-import are silently useless.
2727
'import-x/resolver-next': [
2828
createTypeScriptImportResolver({ project: './tsconfig.json' }),
2929
],
@@ -72,6 +72,18 @@ export default tseslint.config(
7272
files: ['src/client/**/*.{ts,tsx}'],
7373
rules: {
7474
'react-hooks/rules-of-hooks': 'error',
75+
76+
// The zone block at the bottom of this file cannot express this direction: its `files` is
77+
// packages/** + apps/**, so a violation living in src/client is never linted by it.
78+
'import-x/no-restricted-paths': ['error', {
79+
zones: [
80+
{
81+
target: 'src/client/**/*',
82+
from: ['packages/core/**/*', 'src/server/**/*'],
83+
message: 'The review engine and the Worker tree are server-only. Importing either pulls zod/jsonrepair/picomatch into the browser bundle -- exactly what the `vite build` CI step exists to catch. (@codra/schema/review-limits is the sanctioned client-side import.)'
84+
}
85+
]
86+
}],
7587
},
7688
},
7789

@@ -98,10 +110,10 @@ export default tseslint.config(
98110
{ group: ['**/core/github/http', '**/core/github/app-auth', '**/core/github/types', '**/core/github/diff-fetch', '**/core/github/review-post', '**/core/github/labels', '@server/core/github/http', '@server/core/github/app-auth', '@server/core/github/types', '@server/core/github/diff-fetch', '@server/core/github/review-post', '@server/core/github/labels'], message: 'Import from @server/core/github, not a sibling. One spec vi.mocks that specifier. (core/github/oauth is deliberately NOT listed: it is the dashboard OAuth flow, not part of the GitHubClient barrel, and routes/auth.ts imports it directly.)' },
99111
// Covers every sibling in the family, including the three the barrel re-exports publicly
100112
// (budget, diff-cache, request) which were previously unprotected.
101-
{ group: ['**/core/review/*', '@server/core/review/*'], message: 'Import from @server/core/review, not a sibling. One spec vi.mocks that specifier and workflows/review.ts imports only runReviewJob from it.' },
102-
{ group: ['**/core/model-output/*', '@server/core/model-output/*'], message: 'Import from @server/core/model-output, not a sibling.' },
103-
{ group: ['**/core/diff/position', '@server/core/diff/position'], message: 'Import from @server/core/diff, not a sibling.' },
104-
{ group: ['**/shared/schema-claims', '**/shared/schema-repo-config', '**/shared/schema-enums', '@shared/schema-claims', '@shared/schema-repo-config', '@shared/schema-enums'], message: 'Import from @shared/schema, not a sibling. (@shared/review-limits is exempt: the client imports it directly to keep zod out of the browser bundle.)' },
113+
{ group: ['**/core/review/*', '@server/core/review/*', '@codra/core/review/*'], message: 'Import from @server/core/review, not a sibling. One spec vi.mocks that specifier and workflows/review.ts imports only runReviewJob from it.' },
114+
{ group: ['**/core/model-output/*', '@server/core/model-output/*', '@codra/core/model-output/*'], message: 'Import from @codra/core/model-output, not a sibling. (The package exports map already refuses to resolve these; the lint rule gives the error at edit time.)' },
115+
{ group: ['**/core/diff/position', '@server/core/diff/position', '@codra/core/diff/position'], message: 'Import from @codra/core/diff, not a sibling.' },
116+
{ group: ['**/schema-claims', '**/schema-repo-config', '**/schema-enums', '@codra/schema/schema-claims', '@codra/schema/schema-repo-config', '@codra/schema/schema-enums'], message: 'Import from @codra/schema, not a sibling. (@codra/schema/review-limits is exempt: the client imports it directly to keep zod out of the browser bundle.)' },
105117
],
106118
}],
107119
},
@@ -126,10 +138,10 @@ export default tseslint.config(
126138
'src/server/db/file-reviews.ts',
127139
'src/server/services/model.ts',
128140
'src/server/core/github/index.ts',
129-
'src/server/core/review/index.ts',
130-
'src/server/core/diff/index.ts',
131-
'src/server/core/model-output/index.ts',
132-
'src/shared/schema.ts',
141+
// core/review, core/diff and core/model-output are gone from here: they moved to @codra/core and
142+
// what is left at those paths is a re-export shim with no sibling imports to exempt. ESLint does
143+
// not warn about `files` patterns that match nothing, so a stale entry would just rot quietly.
144+
'packages/schema/src/schema.ts',
133145
],
134146
rules: {
135147
'no-restricted-imports': 'off',
@@ -152,4 +164,54 @@ export default tseslint.config(
152164
},
153165
},
154166
},
167+
168+
{
169+
files: ['packages/**/*.{ts,tsx}', 'apps/**/*.{ts,tsx}'],
170+
rules: {
171+
'import-x/no-restricted-paths': ['error', {
172+
zones: [
173+
{
174+
// `src/**` in `from` is what actually holds the extraction in place. The zones below
175+
// only ever described packages -> packages traffic, so nothing stopped a moved file from
176+
// keeping its old `@server/db/jobs` import and quietly re-coupling the package to the
177+
// Worker tree. Traffic goes src -> packages, through src/server/adapters, never back.
178+
target: 'packages/schema/**/*',
179+
from: ['src/**/*', 'test/**/*', 'scripts/**/*', 'packages/core/**/*', 'packages/provider-github/**/*', 'packages/db/**/*', 'packages/models/**/*', 'packages/api/**/*', 'packages/ui/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*']
180+
},
181+
{
182+
target: 'packages/core/**/*',
183+
from: ['src/**/*', 'test/**/*', 'scripts/**/*', 'packages/provider-github/**/*', 'packages/db/**/*', 'packages/models/**/*', 'packages/api/**/*', 'packages/ui/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*']
184+
},
185+
{
186+
target: 'packages/db/**/*',
187+
from: ['packages/provider-github/**/*', 'packages/models/**/*', 'packages/api/**/*', 'packages/ui/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*']
188+
},
189+
{
190+
target: 'packages/provider-github/**/*',
191+
from: ['packages/db/**/*', 'packages/models/**/*', 'packages/api/**/*', 'packages/ui/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*']
192+
},
193+
{
194+
target: 'packages/models/**/*',
195+
from: ['packages/db/**/*', 'packages/provider-github/**/*', 'packages/api/**/*', 'packages/ui/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*']
196+
},
197+
{
198+
target: 'packages/api/**/*',
199+
from: ['packages/ui/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*']
200+
},
201+
{
202+
target: 'packages/ui/**/*',
203+
from: ['packages/core/**/*', 'packages/provider-github/**/*', 'packages/db/**/*', 'packages/models/**/*', 'packages/api/**/*', 'apps/worker/**/*', 'apps/dashboard/**/*']
204+
},
205+
{
206+
target: 'apps/dashboard/**/*',
207+
from: ['packages/core/**/*', 'packages/provider-github/**/*', 'packages/db/**/*', 'packages/models/**/*', 'packages/api/**/*', 'apps/worker/**/*']
208+
},
209+
{
210+
target: 'apps/worker/**/*',
211+
from: ['packages/ui/**/*', 'apps/dashboard/**/*']
212+
}
213+
]
214+
}]
215+
}
216+
}
155217
);

package-lock.json

Lines changed: 42 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,37 @@
1313
"bugs": {
1414
"url": "https://github.com/devarshishimpi/codra/issues"
1515
},
16+
"workspaces": [
17+
"packages/*",
18+
"apps/*"
19+
],
1620
"scripts": {
1721
"build": "vite build && npm run cf-typegen",
22+
"build:all": "npm run build --workspaces --if-present",
1823
"cf-typegen": "wrangler types ./src/server/worker-env.d.ts",
1924
"deploy": "npm run build && npm run migrate && wrangler deploy",
2025
"dev": "concurrently -k -n CLIENT,WORKER -c cyan,green \"npm:dev:client\" \"npm:dev:worker\"",
2126
"dev:client": "vite build --watch --mode development",
2227
"dev:worker": "wrangler dev --local",
23-
"lint": "eslint src test scripts",
28+
"lint": "eslint src test scripts packages apps",
29+
"lint:all": "npm run lint --workspaces --if-present",
30+
"check:boundaries": "node scripts/check-core-boundary.mjs",
2431
"density": "node scripts/comment-density.mjs --top",
2532
"start": "npm run dev",
2633
"setup:cloudflare": "node scripts/setup-cloudflare.js",
2734
"migrate": "node scripts/migrate.mjs",
2835
"test": "node scripts/test.mjs",
36+
"test:all": "npm run test --workspaces --if-present",
2937
"test:watch": "vitest",
30-
"typecheck": "tsc --noEmit"
38+
"typecheck": "tsc --noEmit",
39+
"typecheck:all": "npm run typecheck --workspaces --if-present"
3140
},
3241
"devDependencies": {
3342
"@eslint/js": "^10.0.1",
3443
"@tailwindcss/vite": "^4.2.2",
3544
"@testing-library/dom": "^10.4.1",
3645
"@testing-library/react": "^16.3.2",
3746
"@types/node": "^25.6.0",
38-
"@types/picomatch": "^4.0.3",
3947
"@types/react": "^19.2.18",
4048
"@types/react-dom": "^19.2.4",
4149
"@vitejs/plugin-react": "^6.0.5",
@@ -56,6 +64,8 @@
5664
"wrangler": "^4.114.0"
5765
},
5866
"dependencies": {
67+
"@codra/core": "*",
68+
"@codra/schema": "*",
5969
"@base-ui/react": "^1.6.0",
6070
"class-variance-authority": "^0.7.1",
6171
"clsx": "^2.1.1",
@@ -64,7 +74,6 @@
6474
"lenis": "^1.3.26",
6575
"lucide-react": "^1.8.0",
6676
"motion": "^12.42.2",
67-
"picomatch": "^4.0.5",
6877
"postgres": "^3.4.9",
6978
"react": "^19.2.8",
7079
"react-dom": "^19.2.8",

0 commit comments

Comments
 (0)