-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy patheslint.config.mjs
More file actions
162 lines (160 loc) · 6.68 KB
/
Copy patheslint.config.mjs
File metadata and controls
162 lines (160 loc) · 6.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
".next-prod/**",
"out/**",
"build/**",
"dist/**",
"next-env.d.ts",
// Plain JS — not part of Next.js/TS source
"chrome-extension/**",
"scripts/**",
// Vendored minified pdf.js assets
"public/pdfjs/**",
// Vendored frontend libs + widget bundles generated by
// scripts/build-html-lib.mjs — third-party output, not our source
"public/html-lib/**",
// Standalone marketing site with its own toolchain
"website/**",
]),
// ============================================================
// 2-layer dependency rule: feature-* → shared-*
// ============================================================
// - feature-*: may import other @cockpit/feature-* (supporting subdomain
// pattern, e.g. agent → explorer for code-rendering primitives) and
// @cockpit/shared-*. Cycles must be avoided — the current dependency
// graph is acyclic (workspace → all features; agent → explorer →
// comments → shared-*; review/skills → comments).
// By convention, only feature-workspace imports across multiple
// features (it's the application integrator). Other features should
// import from another feature only when there's a clear "supporting
// subdomain" relationship.
// - shared-*: leaf layer. Must NOT import any @cockpit/feature-*. Use
// IoC slots if host-injected behavior is needed.
//
// See CLAUDE.md / MODULES.md for the full architecture doc.
{
files: ["packages/shared/*/src/**/*.{ts,tsx}"],
// v2 P8 exception: effect-runtime/server/runtime.ts is the AppLayer
// assembly hub and must import every feature's Live layer to merge them —
// the proper place to implement the IoC inversion. A later phase could
// switch to server.mjs collecting and injecting the layers (BACKLOG).
ignores: ["packages/shared/effect-runtime/src/server/runtime.ts"],
rules: {
"no-restricted-imports": ["error", {
patterns: [{
group: ["@cockpit/feature-*", "@cockpit/feature-*/*"],
message: "Shared packages are leaves and must not import from feature packages. Use IoC slots if you need host-injected behavior.",
}],
}],
},
},
{
rules: {
// Allow <img> — Next.js <Image> is unnecessary for a local-only app
"@next/next/no-img-element": "off",
// Deps are intentionally omitted in many hooks to avoid re-fire
"react-hooks/exhaustive-deps": "off",
// Ref-in-render and setState-in-effect are used intentionally
"react-hooks/refs": "off",
"react-hooks/set-state-in-effect": "off",
// Third-party lib compat warnings are not actionable
"react-hooks/incompatible-library": "off",
// Allow _prefixed unused vars (destructuring, catch, callbacks)
"@typescript-eslint/no-unused-vars": ["warn", {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
}],
},
},
// ============================================================
// v2 Effect paradigm constraints (enabled in P8 at warning level — they
// don't block merges, but drive continuous cleanup through P8+)
// See EFFECT.md §10 for details
// ============================================================
{
files: [
"src/app/api/**/*.ts",
"src/lib/effect/**/*.ts",
"packages/feature/*/src/effect/**/*.ts",
"packages/feature/*/src/server/effect/**/*.ts",
"packages/feature/*/src/server/api/**/*.ts",
],
rules: {
"no-restricted-syntax": [
"warn",
{
selector:
"CallExpression[callee.object.name='Promise'][callee.property.name='all']",
message:
"v2: use `Effect.all([...], { concurrency: 'unbounded' })` instead of `Promise.all`.",
},
{
selector:
"CallExpression[callee.object.name='Promise'][callee.property.name='race']",
message: "v2: use `Effect.race` instead of `Promise.race`.",
},
{
selector: "CallExpression[callee.name='setTimeout']",
message:
"v2: use `Effect.delay` / `Schedule.spaced` / `wsReconnectDelayMs` instead of bare setTimeout for retry/delay logic.",
},
{
selector: "CallExpression[callee.name='setInterval']",
message:
"v2: use `Effect.repeat(Schedule.spaced(...))` or `Scheduler.schedule` instead of bare setInterval.",
},
],
},
},
{
files: ["packages/feature/*/src/client/**/*.{ts,tsx}"],
rules: {
"no-restricted-syntax": [
"warn",
{
selector:
"CallExpression[callee.object.object.name='window'][callee.object.property.name='parent'][callee.property.name='postMessage']",
message:
"v2: use `publishTopic(Topics.X, payload)` from `@cockpit/effect-react` (with corresponding entry in `@cockpit/effect-services/topics`).",
},
],
},
},
// ============================================================
// No hand-rolled HTTP clients in components
// ============================================================
// Scoped to .tsx (components) rather than all client code, deliberately.
// The .ts hooks that call fetch — useChatHistory, usePinnedSessions,
// usePushSubscription, … — already wrap it in `Effect.tryPromise` and ARE
// the IO layer; they're just not filed under effect/. Components are the
// ones with no business doing IO, and that is precisely where the bug came
// from: ShortIdBadge hand-rolled a fetch for /api/terminal/bubble-order and
// read `j.data.titles` off a response that has no `data` envelope, silently
// losing every saved bubble title across reloads. `.json()` returns `any`,
// so no amount of shared contract typing can catch that — only keeping the
// call out of the component can. See CLAUDE.md line 52.
{
files: ["packages/feature/*/src/client/**/*.tsx"],
rules: {
"no-restricted-syntax": [
"warn",
{
selector: "CallExpression[callee.name='fetch']",
message:
"Do not call fetch directly in a component — use (or add) a wrapper in the feature's client/effect/ module so the body and response are typed against the endpoint's contract.",
},
],
},
},
]);
export default eslintConfig;