mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Tighten Biome GritQL lint policies
This commit is contained in:
parent
d05b083c12
commit
2888c0cff0
16 changed files with 346 additions and 175 deletions
|
|
@ -16,14 +16,12 @@
|
|||
// Chat feature architecture.
|
||||
"./scripts/lint/no-chat-domain-outer-layer-imports.grit",
|
||||
"./scripts/lint/no-chat-signal-imports.grit",
|
||||
"./scripts/lint/no-chat-state-escape-hatches.grit",
|
||||
"./scripts/lint/no-implicit-dom-bridges.grit",
|
||||
"./scripts/lint/no-pure-chat-state-side-effects.grit",
|
||||
"./scripts/lint/no-ui-root-imports.grit",
|
||||
|
||||
// Project-wide source shape.
|
||||
"./scripts/lint/no-handwritten-reexports.grit",
|
||||
"./scripts/lint/no-non-reexport-index.grit",
|
||||
"./scripts/lint/no-self-referential-initializer-callback.grit",
|
||||
"./scripts/lint/no-unsafe-iterator-value.grit",
|
||||
"./scripts/lint/no-uncontrolled-preact-form-state.grit",
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ language js
|
|||
|
||||
or {
|
||||
or {
|
||||
`client.resumeThread($...)` as $stmt,
|
||||
`client.threadTurnsList($...)` as $stmt,
|
||||
`client.forkThread($...)` as $stmt,
|
||||
`client.rollbackThread($...)` as $stmt
|
||||
`$client.resumeThread($...)` as $stmt,
|
||||
`$client.threadTurnsList($...)` as $stmt,
|
||||
`$client.forkThread($...)` as $stmt,
|
||||
`$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")
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
language js
|
||||
|
||||
or {
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport() as $stmt,
|
||||
JsExportNamedFromClause() as $stmt,
|
||||
JsExportFromClause() as $stmt,
|
||||
TsImportType() as $stmt,
|
||||
JsImportCallExpression() as $stmt
|
||||
} where {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
or {
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$"
|
||||
},
|
||||
|
|
@ -21,18 +25,12 @@ or {
|
|||
},
|
||||
{
|
||||
$filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$",
|
||||
$stmt <: contains `$source` where {
|
||||
not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/turn|src/app-server/protocol/turn)(?:/.*)?[\"']$" }
|
||||
},
|
||||
not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/turn|src/app-server/protocol/turn)(?:/.*)?[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", severity="error")
|
||||
},
|
||||
{
|
||||
$filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$",
|
||||
$stmt <: contains `$source` where {
|
||||
not {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/server-requests|src/app-server/protocol/server-requests)(?:/.*)?[\"']$"
|
||||
}
|
||||
},
|
||||
not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/server-requests|src/app-server/protocol/server-requests)(?:/.*)?[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.", severity="error")
|
||||
}
|
||||
}
|
||||
|
|
@ -44,5 +42,17 @@ or {
|
|||
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")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$",
|
||||
$filename <: r".*/src/features/chat/app-server/(?:inbound/notification-plan|mappers/message-stream/turn-items)\.ts$",
|
||||
not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/turn|src/app-server/protocol/turn)(?:/.*)?[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.", severity="error")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+app-server/protocol|src/app-server/protocol)/[^/\"']+(?:/.*)?[\"']$",
|
||||
$filename <: r".*/src/features/chat/app-server/inbound/(?:handler|routing)\.ts$",
|
||||
not { $source <: r"^[\"'](?:(?:\.\./)+app-server/protocol/server-requests|src/app-server/protocol/server-requests)(?:/.*)?[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.", severity="error")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,26 @@
|
|||
language js
|
||||
|
||||
JsImport() as $stmt where {
|
||||
$filename <: r".*/src/features/chat/domain/.*",
|
||||
$stmt <: contains `$source` where {
|
||||
or {
|
||||
$source <: r"^[\"'](?:\.\./)+(?:app-server|application|host|panel|presentation|ui)(?:/.*)?[\"']$",
|
||||
$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")
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,24 @@
|
|||
language js
|
||||
|
||||
JsImport() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"']@preact/signals[\"']$" },
|
||||
not { $filename <: r".*/src/features/chat/panel/shell-state\.tsx$" },
|
||||
register_diagnostic(span=$stmt, message="Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.", severity="error")
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
or {
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"']@preact/signals[\"']$" },
|
||||
not { $filename <: r".*/src/features/chat/panel/shell-state\.tsx$" },
|
||||
register_diagnostic(span=$stmt, message="Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.", severity="error")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$source <: r"^[\"']@preact/signals[\"']$",
|
||||
not { $filename <: r".*/src/features/chat/panel/shell-state\.tsx$" },
|
||||
register_diagnostic(span=$stmt, message="Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.", severity="error")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
language js
|
||||
|
||||
`"state/patched"` as $stmt where {
|
||||
register_diagnostic(span=$stmt, message="Use a named ChatAction instead of reintroducing the generic state patch escape hatch.", severity="error")
|
||||
}
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
language js
|
||||
|
||||
or {
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport() as $stmt,
|
||||
JsExportNamedFromClause() as $stmt,
|
||||
JsExportFromClause() as $stmt,
|
||||
TsImportType() as $stmt,
|
||||
JsImportCallExpression() as $stmt
|
||||
} where {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
or {
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'].*(?:src/)?generated/app-server(?:/.*)?[\"']$" },
|
||||
not { $filename <: r".*/src/generated/.*" },
|
||||
not { $filename <: r".*/src/app-server/connection/.*" },
|
||||
not { $filename <: r".*/(?:src/generated|src/app-server/connection|tests/app-server)/.*" },
|
||||
not { $filename <: r".*/src/app-server/protocol/(?:turn|server-requests)\.ts$" },
|
||||
not { $filename <: r".*/tests/app-server/.*" },
|
||||
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")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$source <: r"^[\"'].*(?:src/)?generated/app-server(?:/.*)?[\"']$",
|
||||
not { $filename <: r".*/src/generated/.*" },
|
||||
not { $filename <: r".*/src/app-server/connection/.*" },
|
||||
not { $filename <: r".*/(?:src/generated|src/app-server/connection|tests/app-server)/.*" },
|
||||
not { $filename <: r".*/src/app-server/protocol/(?:turn|server-requests)\.ts$" },
|
||||
not { $filename <: r".*/tests/app-server/.*" },
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,43 @@
|
|||
language js
|
||||
|
||||
private pattern js_non_import_module_reference() {
|
||||
or {
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
private pattern js_static_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause()
|
||||
}
|
||||
}
|
||||
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
or {
|
||||
JsImport() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'].*generated/app-server/v2/Thread[\"']$" },
|
||||
$stmt <: contains `Thread`,
|
||||
not { $stmt <: r".*\bThread\s+as\s+AppServerThread\b.*" },
|
||||
register_diagnostic(span=$stmt, message="Import generated app-server Thread as AppServerThread, or use the Panel-owned domain Thread model.", severity="error")
|
||||
},
|
||||
or {
|
||||
JsImport() as $stmt,
|
||||
JsExportNamedFromClause() as $stmt,
|
||||
JsExportFromClause() as $stmt
|
||||
} where {
|
||||
js_non_import_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'].*generated/app-server/v2/Thread[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Import generated app-server Thread as AppServerThread, or use the Panel-owned domain Thread model.", severity="error")
|
||||
},
|
||||
js_static_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/app-server/protocol/turn\.ts$",
|
||||
$stmt <: contains `$source` where {
|
||||
$source <: r"^[\"'].*generated/app-server/.*[\"']$",
|
||||
|
|
@ -18,16 +45,18 @@ or {
|
|||
},
|
||||
register_diagnostic(span=$stmt, message="Only generated ThreadItem is allowed in the turn protocol exception. Model other turn payload shapes locally.", severity="error")
|
||||
},
|
||||
or {
|
||||
JsImport() as $stmt,
|
||||
JsExportNamedFromClause() as $stmt,
|
||||
JsExportFromClause() as $stmt
|
||||
} where {
|
||||
js_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/app-server/protocol/server-requests\.ts$",
|
||||
$stmt <: contains `$source` where {
|
||||
$source <: r"^[\"'].*generated/app-server/.*[\"']$",
|
||||
not { $source <: r"^[\"'].*generated/app-server/(?:RequestId|ServerRequest)[\"']$" }
|
||||
},
|
||||
register_diagnostic(span=$stmt, message="Only generated RequestId and ServerRequest are allowed in the server request protocol exception.", severity="error")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$filename <: r".*/src/app-server/protocol/server-requests\.ts$",
|
||||
$source <: r"^[\"'].*generated/app-server/.*[\"']$",
|
||||
not { $source <: r"^[\"'].*generated/app-server/(?:RequestId|ServerRequest)[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Only generated RequestId and ServerRequest are allowed in the server request protocol exception.", severity="error")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ or {
|
|||
`$target.removeEventListener($...)` as $stmt
|
||||
} where {
|
||||
$filename <: r".*/src/.*\.tsx?$",
|
||||
not { $filename <: r".*\.dom\.tsx?$" },
|
||||
not { $filename <: r".*\.obsidian\.tsx?$" },
|
||||
not { $filename <: r".*\.measure\.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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,54 @@
|
|||
language js
|
||||
|
||||
or {
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport() as $stmt,
|
||||
JsExportNamedFromClause() as $stmt,
|
||||
JsExportFromClause() as $stmt,
|
||||
TsImportType() as $stmt,
|
||||
JsImportCallExpression() as $stmt
|
||||
} where {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
or {
|
||||
js_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/domain/.*",
|
||||
$stmt <: contains `$source` where {
|
||||
or {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+(?:app-server|settings|workspace|shared/ui)|src/(?:app-server|settings|workspace|shared/ui))(?:/.*)?[\"']$",
|
||||
$source <: r"^[\"']obsidian[\"']$"
|
||||
}
|
||||
},
|
||||
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 { or { $source <: r"^[\"'](?:(?:\.\./)+features|src/features)(?:/.*)?[\"']$" } },
|
||||
$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")
|
||||
},
|
||||
or {
|
||||
JsImport() as $stmt,
|
||||
JsExportNamedFromClause() as $stmt,
|
||||
JsExportFromClause() as $stmt,
|
||||
TsImportType() as $stmt,
|
||||
JsImportCallExpression() as $stmt
|
||||
} where {
|
||||
or { $filename <: r".*/src/app-server/protocol/.*", $filename <: r".*/src/(?:domain|shared)/.*" },
|
||||
js_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/(?:app-server/protocol|domain|shared)/.*",
|
||||
$stmt <: contains `$source` where {
|
||||
or { $source <: r"^[\"'](?:(?:\.\./)+(?:app-server/)?connection|src/app-server/connection)(?:/.*)?[\"']$" }
|
||||
$source <: r"^[\"'](?:(?:\.\./)+(?:app-server/)?connection|src/app-server/connection)(?:/.*)?[\"']$"
|
||||
},
|
||||
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")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
$filename <: r".*/src/domain/.*",
|
||||
or {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+(?:app-server|settings|workspace|shared/ui)|src/(?:app-server|settings|workspace|shared/ui))(?:/.*)?[\"']$",
|
||||
$source <: r"^[\"']obsidian[\"']$"
|
||||
},
|
||||
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)/.*",
|
||||
or { $source <: r"^[\"'](?:(?:\.\./)+features|src/features)(?:/.*)?[\"']$" },
|
||||
$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")
|
||||
},
|
||||
`require($source)` as $stmt where {
|
||||
or { $filename <: r".*/src/app-server/protocol/.*", $filename <: r".*/src/(?:domain|shared)/.*" },
|
||||
or { $source <: r"^[\"'](?:(?:\.\./)+(?:app-server/)?connection|src/app-server/connection)(?:/.*)?[\"']$" },
|
||||
$filename <: r".*/src/(?:app-server/protocol|domain|shared)/.*",
|
||||
$source <: r"^[\"'](?:(?:\.\./)+(?:app-server/)?connection|src/app-server/connection)(?:/.*)?[\"']$",
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
language js
|
||||
|
||||
file($name, $body) where {
|
||||
$name <: r".*/src/.*/index\.tsx?$",
|
||||
$body <: contains bubble or {
|
||||
JsImport() as $stmt,
|
||||
JsExport() as $stmt where {
|
||||
not {
|
||||
$stmt <: contains or {
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause()
|
||||
}
|
||||
}
|
||||
}
|
||||
} where { register_diagnostic(span=$stmt, message="Keep src index files as re-export-only boundaries.", severity="error") }
|
||||
}
|
||||
|
|
@ -1,14 +1,7 @@
|
|||
language js
|
||||
|
||||
or {
|
||||
`const $name = new $constructor($callback)` as $stmt where {
|
||||
$callback <: `() => $body`,
|
||||
$body <: contains `$name`,
|
||||
register_diagnostic(span=$stmt, message="Avoid referencing a variable from a callback inside its own initializer; declare it first with an explicit type.", severity="error")
|
||||
},
|
||||
`const $name = new $constructor($callback)` as $stmt where {
|
||||
$callback <: `function() { $body }`,
|
||||
$body <: contains `$name`,
|
||||
register_diagnostic(span=$stmt, message="Avoid referencing a variable from a callback inside its own initializer; declare it first with an explicit type.", severity="error")
|
||||
}
|
||||
`const $name = new $constructor($callback)` as $stmt where {
|
||||
or { $callback <: `() => $body`, $callback <: `function() { $body }` },
|
||||
$body <: contains `$name`,
|
||||
register_diagnostic(span=$stmt, message="Avoid referencing a variable from a callback inside its own initializer; declare it first with an explicit type.", severity="error")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,26 @@
|
|||
language js
|
||||
|
||||
JsImport() as $stmt where {
|
||||
$filename <: r".*/src/.*",
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'].*shared/ui/ui-root\.dom[\"']$" },
|
||||
not { $filename <: r".*\.dom\.tsx?$" },
|
||||
not { $filename <: r".*\.obsidian\.tsx?$" },
|
||||
not { $filename <: r".*\.measure\.tsx?$" },
|
||||
register_diagnostic(span=$stmt, message="Import the Preact root adapter only from explicit root bridge files.", severity="error")
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import type { RequestId } from "../../src/generated/app-server/RequestId";
|
|||
import type { ServerNotification } from "../../src/generated/app-server/ServerNotification";
|
||||
import type { ServerRequest } from "../../src/generated/app-server/ServerRequest";
|
||||
import type { ModelListResponse } from "../../src/generated/app-server/v2/ModelListResponse";
|
||||
import type { Thread as ThreadRecord } from "../../src/generated/app-server/v2/Thread";
|
||||
import type { Thread as AppServerThread } from "../../src/generated/app-server/v2/Thread";
|
||||
import type { ThreadStartResponse } from "../../src/generated/app-server/v2/ThreadStartResponse";
|
||||
import type { TurnStartResponse } from "../../src/generated/app-server/v2/TurnStartResponse";
|
||||
|
||||
|
|
@ -331,7 +331,7 @@ function threadStartResponse(threadId: string): ThreadStartResponse {
|
|||
};
|
||||
}
|
||||
|
||||
function thread(id: string): ThreadRecord {
|
||||
function thread(id: string): AppServerThread {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { threadActivationSnapshotFromAppServerResponse } from "../../src/app-server/threads";
|
||||
import type { Thread as ThreadRecord } from "../../src/generated/app-server/v2/Thread";
|
||||
import type { Thread as AppServerThread } from "../../src/generated/app-server/v2/Thread";
|
||||
import type { ThreadResumeResponse } from "../../src/generated/app-server/v2/ThreadResumeResponse";
|
||||
|
||||
describe("app-server thread activation", () => {
|
||||
|
|
@ -21,7 +21,7 @@ describe("app-server thread activation", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function responseFixture(thread: ThreadRecord): ThreadResumeResponse {
|
||||
function responseFixture(thread: AppServerThread): ThreadResumeResponse {
|
||||
return {
|
||||
thread,
|
||||
model: "gpt-5.5",
|
||||
|
|
@ -40,7 +40,7 @@ function responseFixture(thread: ThreadRecord): ThreadResumeResponse {
|
|||
};
|
||||
}
|
||||
|
||||
function threadFixture(id: string, name: string): ThreadRecord {
|
||||
function threadFixture(id: string, name: string): AppServerThread {
|
||||
return {
|
||||
id,
|
||||
sessionId: "session",
|
||||
|
|
|
|||
|
|
@ -48,28 +48,35 @@ import { signal } from '@preact/signals';
|
|||
export const status = signal("idle");
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/shared/ui/signal-escapes.tsx"),
|
||||
`
|
||||
export { signal } from "@preact/signals";
|
||||
export type SignalValue = import("@preact/signals").Signal<string>;
|
||||
|
||||
const report = biomeLint(["src/features/chat/panel/shell-state.tsx", "src/shared/ui/components.tsx"], cwd);
|
||||
export async function loadSignals() {
|
||||
return import("@preact/signals");
|
||||
}
|
||||
|
||||
const signals = require("@preact/signals");
|
||||
export const loadedSignals = signals;
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(
|
||||
["src/features/chat/panel/shell-state.tsx", "src/shared/ui/components.tsx", "src/shared/ui/signal-escapes.tsx"],
|
||||
cwd,
|
||||
);
|
||||
|
||||
expect(pluginDiagnostics(report, "src/features/chat/panel/shell-state.tsx")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/shared/ui/components.tsx")).toEqual([
|
||||
"Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps removed chat state escape hatches out of source", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-chat-state-escape-hatches.grit"]);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/application/state/root-reducer.ts"),
|
||||
`
|
||||
export const actionType = "state/patched";
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(["src/features/chat/application/state/root-reducer.ts"], cwd);
|
||||
|
||||
expect(pluginMessages(report, "src/features/chat/application/state/root-reducer.ts")).toEqual([
|
||||
"Use a named ChatAction instead of reintroducing the generic state patch escape hatch.",
|
||||
expect(pluginMessages(report, "src/shared/ui/signal-escapes.tsx")).toEqual([
|
||||
"Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.",
|
||||
"Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.",
|
||||
"Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.",
|
||||
"Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -91,11 +98,29 @@ export type View = Presenter;
|
|||
import type { MessageStreamItem } from "./item";
|
||||
|
||||
export type Item = MessageStreamItem;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/domain/message-stream/outer-shapes.ts"),
|
||||
`
|
||||
export { createChatStateStore } from "../../application/state/store";
|
||||
export type OuterStore = import("../../application/state/store").ChatStateStore;
|
||||
|
||||
export async function loadComposer() {
|
||||
return import("src/features/chat/ui/composer");
|
||||
}
|
||||
|
||||
const host = require("../../host/session");
|
||||
export const outerHost = host;
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(
|
||||
["src/features/chat/domain/message-stream/selectors.ts", "src/features/chat/domain/message-stream/items.ts"],
|
||||
[
|
||||
"src/features/chat/domain/message-stream/selectors.ts",
|
||||
"src/features/chat/domain/message-stream/items.ts",
|
||||
"src/features/chat/domain/message-stream/outer-shapes.ts",
|
||||
],
|
||||
cwd,
|
||||
);
|
||||
|
||||
|
|
@ -104,6 +129,12 @@ export type Item = MessageStreamItem;
|
|||
"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.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/features/chat/domain/message-stream/items.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/features/chat/domain/message-stream/outer-shapes.ts")).toEqual([
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("blocks generated app-server imports outside app-server boundaries", async () => {
|
||||
|
|
@ -178,7 +209,9 @@ export type Item = TurnItem;
|
|||
`
|
||||
import { toolInventoryAppsFromAppInfos } from "../../../../app-server/protocol/tool-inventory";
|
||||
|
||||
export const convert = toolInventoryAppsFromAppInfos;
|
||||
const toolInventory = require("../../../../app-server/protocol/tool-inventory");
|
||||
|
||||
export const convert = [toolInventoryAppsFromAppInfos, toolInventory];
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
|
|
@ -208,6 +241,7 @@ export type Item = TurnItem;
|
|||
]);
|
||||
expect(pluginMessages(report, "src/features/chat/app-server/inbound/notification-plan.ts")).toEqual([
|
||||
"Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.",
|
||||
"Chat app-server ingestion and message-stream conversion may consume the app-server turn protocol only. Convert other protocol payloads to local or domain models at the boundary.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/features/chat/app-server/mappers/message-stream/turn-items.ts")).toEqual([]);
|
||||
});
|
||||
|
|
@ -228,6 +262,14 @@ export const response = appServerUserInputResponse;
|
|||
import { appServerUserInputResponse } from "../../../../app-server/protocol/server-requests";
|
||||
|
||||
export const response = appServerUserInputResponse;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/app-server/inbound/handler.ts"),
|
||||
`
|
||||
const runtimeMetrics = require("../../../../app-server/protocol/runtime-metrics");
|
||||
|
||||
export const response = runtimeMetrics;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
|
|
@ -243,6 +285,7 @@ export const response = appServerUserInputResponse;
|
|||
[
|
||||
"src/features/chat/panel/surface/message-stream-presenter.ts",
|
||||
"src/features/chat/app-server/inbound/app-server-logs.ts",
|
||||
"src/features/chat/app-server/inbound/handler.ts",
|
||||
"src/features/chat/app-server/inbound/routing.ts",
|
||||
],
|
||||
cwd,
|
||||
|
|
@ -254,6 +297,9 @@ export const response = appServerUserInputResponse;
|
|||
expect(pluginMessages(report, "src/features/chat/app-server/inbound/app-server-logs.ts")).toEqual([
|
||||
"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.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/features/chat/app-server/inbound/handler.ts")).toEqual([
|
||||
"Chat app-server request handling may consume server request protocol projections only. Convert app-server payloads to chat pending request domain models at this boundary.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/features/chat/app-server/inbound/routing.ts")).toEqual([]);
|
||||
});
|
||||
|
||||
|
|
@ -326,8 +372,37 @@ import { formatDate } from "./format";
|
|||
export const format = formatDate;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/domain/threads/model.ts"),
|
||||
`
|
||||
import { listThreads } from "../../app-server/threads";
|
||||
import { copyText } from "../../shared/ui/clipboard";
|
||||
import type { App } from "obsidian";
|
||||
|
||||
const report = biomeLint(["src/app-server/protocol/catalog.ts", "src/app-server/protocol/diagnostics.ts", "src/shared/date.ts"], cwd);
|
||||
export type Host = App;
|
||||
export const list = listThreads;
|
||||
export const copy = copyText;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/domain/threads/format.ts"),
|
||||
`
|
||||
import { formatDate } from "../../shared/date";
|
||||
|
||||
export const format = formatDate;
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(
|
||||
[
|
||||
"src/app-server/protocol/catalog.ts",
|
||||
"src/app-server/protocol/diagnostics.ts",
|
||||
"src/shared/date.ts",
|
||||
"src/domain/threads/model.ts",
|
||||
"src/domain/threads/format.ts",
|
||||
],
|
||||
cwd,
|
||||
);
|
||||
|
||||
expect(pluginMessages(report, "src/app-server/protocol/catalog.ts")).toEqual([
|
||||
"Lower-level modules must not import feature modules or app-server connection internals. Move shared behavior to shared, domain, or app-server adapters.",
|
||||
|
|
@ -336,6 +411,12 @@ export const format = formatDate;
|
|||
"Lower-level modules must not import feature modules or app-server connection internals. Move shared behavior to shared, domain, or app-server adapters.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/shared/date.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/domain/threads/model.ts")).toEqual([
|
||||
"Domain modules must stay pure and generated-independent; keep app-server, settings, workspace, UI, and Obsidian dependencies at boundary callers.",
|
||||
"Domain modules must stay pure and generated-independent; keep app-server, settings, workspace, UI, and Obsidian dependencies at boundary callers.",
|
||||
"Domain modules must stay pure and generated-independent; keep app-server, settings, workspace, UI, and Obsidian dependencies at boundary callers.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/domain/threads/format.ts")).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps generated app-server import shapes behind narrow aliases and protocol exceptions", async () => {
|
||||
|
|
@ -354,6 +435,14 @@ export type ConnectionThread = Thread;
|
|||
import type { Thread as AppServerThread } from "../../generated/app-server/v2/Thread";
|
||||
|
||||
export type ConnectionThread = AppServerThread;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/app-server/connection/record-thread.ts"),
|
||||
`
|
||||
import type { Thread as ThreadRecord } from "../../generated/app-server/v2/Thread";
|
||||
|
||||
export type ConnectionThread = ThreadRecord;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
|
|
@ -374,6 +463,14 @@ import type { ToolRequestUserInputParams } from "../../generated/app-server/v2/T
|
|||
|
||||
export type Request = ServerRequest;
|
||||
export type Params = ToolRequestUserInputParams;
|
||||
export type InputParams = import("../../generated/app-server/v2/ToolRequestUserInputParams").ToolRequestUserInputParams;
|
||||
|
||||
export async function loadParams() {
|
||||
return import("../../generated/app-server/v2/ToolRequestUserInputParams");
|
||||
}
|
||||
|
||||
const params = require("../../generated/app-server/v2/ToolRequestUserInputParams");
|
||||
export const loadedParams = params;
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
|
|
@ -381,6 +478,7 @@ export type Params = ToolRequestUserInputParams;
|
|||
[
|
||||
"src/app-server/connection/thread.ts",
|
||||
"src/app-server/connection/aliased-thread.ts",
|
||||
"src/app-server/connection/record-thread.ts",
|
||||
"src/app-server/protocol/turn.ts",
|
||||
"src/app-server/protocol/server-requests.ts",
|
||||
],
|
||||
|
|
@ -391,41 +489,20 @@ export type Params = ToolRequestUserInputParams;
|
|||
"Import generated app-server Thread as AppServerThread, or use the Panel-owned domain Thread model.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/app-server/connection/aliased-thread.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/app-server/connection/record-thread.ts")).toEqual([
|
||||
"Import generated app-server Thread as AppServerThread, or use the Panel-owned domain Thread model.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/app-server/protocol/turn.ts")).toEqual([
|
||||
"Only generated ThreadItem is allowed in the turn protocol exception. Model other turn payload shapes locally.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/app-server/protocol/server-requests.ts")).toEqual([
|
||||
"Only generated RequestId and ServerRequest are allowed in the server request protocol exception.",
|
||||
"Only generated RequestId and ServerRequest are allowed in the server request protocol exception.",
|
||||
"Only generated RequestId and ServerRequest are allowed in the server request protocol exception.",
|
||||
"Only generated RequestId and ServerRequest are allowed in the server request protocol exception.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps src index files as re-export-only boundaries", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-non-reexport-index.grit"]);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/index.ts"),
|
||||
`
|
||||
import { value } from "./value";
|
||||
|
||||
export const local = value;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/thread-picker/index.ts"),
|
||||
`
|
||||
export { openThreadPicker } from "./modal";
|
||||
export type { ThreadPickerOptions } from "./types";
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(["src/features/chat/index.ts", "src/features/thread-picker/index.ts"], cwd);
|
||||
|
||||
expect(pluginMessages(report, "src/features/chat/index.ts")).toEqual([
|
||||
"Keep src index files as re-export-only boundaries.",
|
||||
"Keep src index files as re-export-only boundaries.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/features/thread-picker/index.ts")).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps chat application app-server projection RPCs behind facades", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-app-server-projection-rpcs.grit"]);
|
||||
await writeFile(
|
||||
|
|
@ -433,8 +510,8 @@ export type { ThreadPickerOptions } from "./types";
|
|||
`
|
||||
import type { AppServerClient } from "../../../../app-server/connection/client";
|
||||
|
||||
export async function read(client: AppServerClient): Promise<void> {
|
||||
await client.threadTurnsList("thread", null, 20);
|
||||
export async function read(appServerClient: AppServerClient): Promise<void> {
|
||||
await appServerClient.threadTurnsList("thread", null, 20);
|
||||
}
|
||||
`.trimStart(),
|
||||
);
|
||||
|
|
@ -531,13 +608,36 @@ import { renderUiRoot } from "../../../shared/ui/ui-root.dom";
|
|||
export const render = renderUiRoot;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/ui/root-escapes.tsx"),
|
||||
`
|
||||
export { renderUiRoot } from "../../../shared/ui/ui-root.dom";
|
||||
export type RootRenderer = import("../../../shared/ui/ui-root.dom").RootRenderer;
|
||||
|
||||
const report = biomeLint(["src/features/chat/ui/composer.tsx", "src/features/chat/panel/shell.dom.tsx"], cwd);
|
||||
export async function loadRoot() {
|
||||
return import("../../../shared/ui/ui-root.dom");
|
||||
}
|
||||
|
||||
const root = require("../../../shared/ui/ui-root.dom");
|
||||
export const loadedRoot = root;
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(
|
||||
["src/features/chat/ui/composer.tsx", "src/features/chat/panel/shell.dom.tsx", "src/features/chat/ui/root-escapes.tsx"],
|
||||
cwd,
|
||||
);
|
||||
|
||||
expect(pluginMessages(report, "src/features/chat/ui/composer.tsx")).toEqual([
|
||||
"Import the Preact root adapter only from explicit root bridge files.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/features/chat/panel/shell.dom.tsx")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/features/chat/ui/root-escapes.tsx")).toEqual([
|
||||
"Import the Preact root adapter only from explicit root bridge files.",
|
||||
"Import the Preact root adapter only from explicit root bridge files.",
|
||||
"Import the Preact root adapter only from explicit root bridge files.",
|
||||
"Import the Preact root adapter only from explicit root bridge files.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps chat state transforms pure and catches global scheduling calls", async () => {
|
||||
|
|
@ -753,7 +853,7 @@ async function tempBiomeWorkspace(plugins) {
|
|||
await mkdir(path.join(cwd, "src/features/chat/panel"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/features/chat/panel/surface"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/features/chat/ui"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/features/thread-picker"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/domain/threads"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/app-server/connection"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/app-server/protocol"), { recursive: true });
|
||||
await mkdir(path.join(cwd, "src/app-server/services"), { recursive: true });
|
||||
|
|
|
|||
Loading…
Reference in a new issue