mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Scope Biome Grit plugins
This commit is contained in:
parent
dd538d9e44
commit
7c5da3d520
10 changed files with 108 additions and 41 deletions
93
biome.jsonc
93
biome.jsonc
|
|
@ -6,28 +6,89 @@
|
|||
"useIgnoreFile": true
|
||||
},
|
||||
"plugins": [
|
||||
// App-server and generated binding boundaries.
|
||||
"./scripts/lint/no-app-server-projection-rpcs.grit",
|
||||
"./scripts/lint/no-app-server-protocol-boundary-imports.grit",
|
||||
"./scripts/lint/no-generated-app-server-boundary-imports.grit",
|
||||
"./scripts/lint/no-generated-app-server-import-shapes.grit",
|
||||
"./scripts/lint/no-lower-level-boundary-imports.grit",
|
||||
// App-server protocol and generated binding boundaries.
|
||||
{
|
||||
"path": "./scripts/lint/no-app-server-projection-rpcs.grit",
|
||||
"includes": ["**/src/features/chat/application/**/*.ts", "**/src/features/chat/application/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-app-server-protocol-boundary-imports.grit",
|
||||
"includes": ["**/src/**/*.ts", "**/src/**/*.tsx", "!**/src/app-server/**/*.ts", "!**/src/app-server/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-generated-app-server-boundary-imports.grit",
|
||||
"includes": [
|
||||
"**/src/**/*.ts",
|
||||
"**/src/**/*.tsx",
|
||||
"!**/src/app-server/connection/**",
|
||||
"!**/src/app-server/protocol/server-requests.ts"
|
||||
]
|
||||
},
|
||||
|
||||
// Shared source layering boundaries.
|
||||
{
|
||||
"path": "./scripts/lint/no-generated-app-server-import-shapes.grit",
|
||||
"includes": ["**/src/**/*.ts", "**/src/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-lower-level-boundary-imports.grit",
|
||||
"includes": [
|
||||
"**/src/app-server/**/*.ts",
|
||||
"**/src/app-server/**/*.tsx",
|
||||
"**/src/domain/**/*.ts",
|
||||
"**/src/domain/**/*.tsx",
|
||||
"**/src/shared/**/*.ts",
|
||||
"**/src/shared/**/*.tsx"
|
||||
]
|
||||
},
|
||||
|
||||
// Chat feature architecture.
|
||||
"./scripts/lint/no-chat-domain-outer-layer-imports.grit",
|
||||
"./scripts/lint/no-chat-signal-imports.grit",
|
||||
"./scripts/lint/no-implicit-dom-bridges.grit",
|
||||
"./scripts/lint/no-pure-chat-state-side-effects.grit",
|
||||
"./scripts/lint/no-ui-root-imports.grit",
|
||||
{
|
||||
"path": "./scripts/lint/no-chat-domain-outer-layer-imports.grit",
|
||||
"includes": ["**/src/features/chat/domain/**/*.ts", "**/src/features/chat/domain/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-chat-signal-imports.grit",
|
||||
"includes": ["**/src/**/*.ts", "**/src/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-pure-chat-state-side-effects.grit",
|
||||
"includes": ["**/src/features/chat/application/state/**/*.ts", "**/src/features/chat/application/state/**/*.tsx"]
|
||||
},
|
||||
|
||||
// UI and DOM source boundaries.
|
||||
{
|
||||
"path": "./scripts/lint/no-implicit-dom-bridges.grit",
|
||||
"includes": ["**/src/**/*.ts", "**/src/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-ui-root-imports.grit",
|
||||
"includes": ["**/src/**/*.ts", "**/src/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-uncontrolled-preact-form-state.grit",
|
||||
"includes": ["**/src/**/*.tsx"]
|
||||
},
|
||||
|
||||
// Project-wide source shape.
|
||||
"./scripts/lint/no-handwritten-reexports.grit",
|
||||
"./scripts/lint/no-self-referential-initializer-callback.grit",
|
||||
"./scripts/lint/no-unsafe-iterator-value.grit",
|
||||
"./scripts/lint/no-uncontrolled-preact-form-state.grit",
|
||||
{
|
||||
"path": "./scripts/lint/no-handwritten-reexports.grit",
|
||||
"includes": ["**/*.ts", "**/*.tsx", "**/*.mjs"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-self-referential-initializer-callback.grit",
|
||||
"includes": ["**/*.ts", "**/*.tsx", "**/*.mjs"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-unsafe-iterator-value.grit",
|
||||
"includes": ["**/*.ts", "**/*.tsx", "**/*.mjs"]
|
||||
},
|
||||
|
||||
// CSS source policy.
|
||||
"./scripts/lint/no-restricted-css-policy.grit"
|
||||
{
|
||||
"path": "./scripts/lint/no-restricted-css-policy.grit",
|
||||
"includes": ["**/src/styles/**/*.css"]
|
||||
}
|
||||
],
|
||||
"files": {
|
||||
"includes": [
|
||||
|
|
|
|||
|
|
@ -7,6 +7,5 @@ or {
|
|||
`$client.rollbackThread($...)` as $stmt
|
||||
} where {
|
||||
$client <: r"^(?:client|[A-Za-z_$][A-Za-z0-9_$]*Client)$",
|
||||
$filename <: r".*/src/features/chat/application/.*",
|
||||
register_diagnostic(span=$stmt, message="Keep app-server projection RPCs behind app-server facades; chat application code should consume Panel-owned snapshots or view models.", severity="error")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ or {
|
|||
},
|
||||
or {
|
||||
{
|
||||
$filename <: r".*/src/.*",
|
||||
not { $filename <: r".*/src/app-server/.*" },
|
||||
not { $filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$" },
|
||||
not { $filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$" },
|
||||
register_diagnostic(span=$stmt, message="Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", severity="error")
|
||||
|
|
@ -37,8 +35,6 @@ or {
|
|||
},
|
||||
`require($source)` as $stmt where {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$",
|
||||
$filename <: r".*/src/.*",
|
||||
not { $filename <: r".*/src/app-server/.*" },
|
||||
not { $filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$" },
|
||||
not { $filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$" },
|
||||
register_diagnostic(span=$stmt, message="Source modules outside app-server must use domain models and app-server services instead of app-server protocol modules. Chat ingestion and message-stream conversion may consume app-server turn protocol at the boundary; feature state and UI must use Panel-owned models.", severity="error")
|
||||
|
|
|
|||
|
|
@ -12,14 +12,12 @@ private pattern js_module_reference() {
|
|||
|
||||
or {
|
||||
js_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/features/chat/domain/.*",
|
||||
$stmt <: contains `$source` where {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+|src/features/chat/)(?:app-server|application|host|panel|presentation|ui)(?:/.*)?[\"']$"
|
||||
},
|
||||
register_diagnostic(span=$stmt, message="Keep chat/domain as Panel-owned meaning models and pure derivations; app-server, application, host, panel, presentation, and UI layers may depend on domain, not the reverse.", severity="error")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$filename <: r".*/src/features/chat/domain/.*",
|
||||
$source <: r"^[\"'](?:(?:\.\./)+|src/features/chat/)(?:app-server|application|host|panel|presentation|ui)(?:/.*)?[\"']$",
|
||||
register_diagnostic(span=$stmt, message="Keep chat/domain as Panel-owned meaning models and pure derivations; app-server, application, host, panel, presentation, and UI layers may depend on domain, not the reverse.", severity="error")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,14 +13,10 @@ private pattern js_module_reference() {
|
|||
or {
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'].*(?:src/)?generated/app-server(?:/.*)?[\"']$" },
|
||||
not { $filename <: r".*/(?:src/generated|src/app-server/connection|tests/app-server)/.*" },
|
||||
not { $filename <: r".*/src/app-server/protocol/server-requests\.ts$" },
|
||||
register_diagnostic(span=$stmt, message="Keep generated app-server types behind src/app-server and tests/app-server; expose Panel-owned models outside raw app-server adapters.", severity="error")
|
||||
register_diagnostic(span=$stmt, message="Keep generated app-server types behind src/app-server adapters; expose Panel-owned models outside raw app-server boundaries.", severity="error")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$source <: r"^[\"'].*(?:src/)?generated/app-server(?:/.*)?[\"']$",
|
||||
not { $filename <: r".*/(?:src/generated|src/app-server/connection|tests/app-server)/.*" },
|
||||
not { $filename <: r".*/src/app-server/protocol/server-requests\.ts$" },
|
||||
register_diagnostic(span=$stmt, message="Keep generated app-server types behind src/app-server and tests/app-server; expose Panel-owned models outside raw app-server adapters.", severity="error")
|
||||
register_diagnostic(span=$stmt, message="Keep generated app-server types behind src/app-server adapters; expose Panel-owned models outside raw app-server boundaries.", severity="error")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ or {
|
|||
`$target.addEventListener($...)` as $stmt,
|
||||
`$target.removeEventListener($...)` as $stmt
|
||||
} where {
|
||||
$filename <: r".*/src/.*\.tsx?$",
|
||||
not { $filename <: r".*\.(?:dom|obsidian|measure)\.tsx?$" },
|
||||
not { $target <: `signal` },
|
||||
register_diagnostic(span=$stmt, message="Keep imperative DOM writes and event wiring in files named with a .dom, .obsidian, or .measure suffix.", severity="error")
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ or {
|
|||
register_diagnostic(span=$stmt, message="Domain modules must stay pure and generated-independent; keep app-server, settings, workspace, UI, and Obsidian dependencies at boundary callers.", severity="error")
|
||||
},
|
||||
js_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/(?:app-server|domain|shared)/.*",
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'](?:(?:\.\./)+features|src/features)(?:/.*)?[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Lower-level modules must not import feature modules or app-server connection internals. Move shared behavior to shared, domain, or app-server adapters.", severity="error")
|
||||
},
|
||||
|
|
@ -42,7 +41,6 @@ or {
|
|||
register_diagnostic(span=$stmt, message="Domain modules must stay pure and generated-independent; keep app-server, settings, workspace, UI, and Obsidian dependencies at boundary callers.", severity="error")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$filename <: r".*/src/(?:app-server|domain|shared)/.*",
|
||||
$source <: r"^[\"'](?:(?:\.\./)+features|src/features)(?:/.*)?[\"']$",
|
||||
register_diagnostic(span=$stmt, message="Lower-level modules must not import feature modules or app-server connection internals. Move shared behavior to shared, domain, or app-server adapters.", severity="error")
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,5 @@ or {
|
|||
`localStorage.$property` as $stmt,
|
||||
`sessionStorage.$property` as $stmt
|
||||
} where {
|
||||
$filename <: r".*/src/features/chat/application/state/.*",
|
||||
register_diagnostic(span=$stmt, message="Keep chat state transforms deterministic and free of app-server, Obsidian, scheduling, and browser side effects.", severity="error")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,11 @@ private pattern js_module_reference() {
|
|||
|
||||
or {
|
||||
js_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/.*",
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'].*shared/ui/ui-root\.dom[\"']$" },
|
||||
not { $filename <: r".*\.(?:dom|obsidian|measure)\.tsx?$" },
|
||||
register_diagnostic(span=$stmt, message="Import the Preact root adapter only from explicit root bridge files.", severity="error")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$filename <: r".*/src/.*",
|
||||
$source <: r"^[\"'].*shared/ui/ui-root\.dom[\"']$",
|
||||
not { $filename <: r".*\.(?:dom|obsidian|measure)\.tsx?$" },
|
||||
register_diagnostic(span=$stmt, message="Import the Preact root adapter only from explicit root bridge files.", severity="error")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { spawnSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
|
@ -7,6 +8,12 @@ import { describe, expect, it } from "vitest";
|
|||
const repoRoot = process.cwd();
|
||||
const biomeBin = path.join(repoRoot, "node_modules", ".bin", "biome");
|
||||
const workspaceByPlugins = new Map();
|
||||
const projectPluginByName = new Map(
|
||||
parseJsonc(readFileSync(path.join(repoRoot, "biome.jsonc"), "utf8")).plugins.map((plugin) => {
|
||||
const pluginPath = typeof plugin === "string" ? plugin : plugin.path;
|
||||
return [path.basename(pluginPath), plugin];
|
||||
}),
|
||||
);
|
||||
|
||||
describe("GritQL source policy", () => {
|
||||
it("keeps project-wide source-shape policies enforceable as Biome plugin diagnostics", async () => {
|
||||
|
|
@ -410,21 +417,21 @@ export type Request = ServerRequest;
|
|||
);
|
||||
|
||||
expect(pluginMessages(report, "src/features/chat/domain/generated-thread.ts")).toEqual([
|
||||
"Keep generated app-server types behind src/app-server and tests/app-server; expose Panel-owned models outside raw app-server adapters.",
|
||||
"Keep generated app-server types behind src/app-server adapters; expose Panel-owned models outside raw app-server boundaries.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/features/chat/domain/generated-thread-import.ts")).toEqual([
|
||||
"Keep generated app-server types behind src/app-server and tests/app-server; expose Panel-owned models outside raw app-server adapters.",
|
||||
"Keep generated app-server types behind src/app-server adapters; expose Panel-owned models outside raw app-server boundaries.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/app-server/connection/generated-thread.ts")).toEqual([]);
|
||||
expect(pluginDiagnostics(report, "tests/app-server/generated-thread.test.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/app-server/protocol/request-input.ts")).toEqual([
|
||||
"Keep generated app-server types behind src/app-server and tests/app-server; expose Panel-owned models outside raw app-server adapters.",
|
||||
"Keep generated app-server types behind src/app-server adapters; expose Panel-owned models outside raw app-server boundaries.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/app-server/services/runtime-overrides.ts")).toEqual([
|
||||
"Keep generated app-server types behind src/app-server and tests/app-server; expose Panel-owned models outside raw app-server adapters.",
|
||||
"Keep generated app-server types behind src/app-server adapters; expose Panel-owned models outside raw app-server boundaries.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/app-server/protocol/turn.ts")).toEqual([
|
||||
"Keep generated app-server types behind src/app-server and tests/app-server; expose Panel-owned models outside raw app-server adapters.",
|
||||
"Keep generated app-server types behind src/app-server adapters; expose Panel-owned models outside raw app-server boundaries.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/app-server/protocol/server-requests.ts")).toEqual([]);
|
||||
});
|
||||
|
|
@ -836,7 +843,7 @@ async function tempBiomeWorkspace(plugins) {
|
|||
JSON.stringify({
|
||||
$schema: "https://biomejs.dev/schemas/2.5.1/schema.json",
|
||||
vcs: { enabled: false },
|
||||
plugins: plugins.map((plugin) => path.join(repoRoot, "scripts", "lint", plugin)),
|
||||
plugins: plugins.map((plugin) => projectPluginConfig(plugin)),
|
||||
css: { linter: { enabled: true } },
|
||||
}),
|
||||
);
|
||||
|
|
@ -861,6 +868,18 @@ function biomeLint(files, cwd, options = {}) {
|
|||
return report;
|
||||
}
|
||||
|
||||
function projectPluginConfig(plugin) {
|
||||
const projectPlugin = projectPluginByName.get(plugin);
|
||||
if (!projectPlugin) {
|
||||
throw new Error(`Missing ${plugin} in biome.jsonc plugins`);
|
||||
}
|
||||
const pluginPath = path.join(repoRoot, "scripts", "lint", plugin);
|
||||
if (typeof projectPlugin === "string") {
|
||||
return pluginPath;
|
||||
}
|
||||
return { ...projectPlugin, path: pluginPath };
|
||||
}
|
||||
|
||||
function pluginDiagnostics(report, filePath) {
|
||||
return report.diagnostics
|
||||
.filter((diagnostic) => diagnostic.category === "plugin" && diagnosticMatchesFile(report, diagnostic, filePath))
|
||||
|
|
@ -900,3 +919,7 @@ function parseBiomeJsonReport(stdout, stderr) {
|
|||
}
|
||||
return JSON.parse(stdout.slice(jsonStart));
|
||||
}
|
||||
|
||||
function parseJsonc(source) {
|
||||
return JSON.parse(source.replace(/^\s*\/\/.*$/gm, ""));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue