mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Refine lower-level import boundary policies
This commit is contained in:
parent
f71f40b61a
commit
660544687e
7 changed files with 246 additions and 146 deletions
23
biome.jsonc
23
biome.jsonc
|
|
@ -22,10 +22,23 @@
|
|||
|
||||
// Shared source layering boundaries.
|
||||
{
|
||||
"path": "./scripts/lint/no-lower-level-boundary-imports.grit",
|
||||
"path": "./scripts/lint/no-domain-outer-layer-imports.grit",
|
||||
"includes": [
|
||||
"**/src/app-server/**/*.ts",
|
||||
"**/src/app-server/**/*.tsx",
|
||||
"**/src/domain/**/*.ts",
|
||||
"**/src/domain/**/*.tsx",
|
||||
"**/src/features/chat/domain/**/*.ts",
|
||||
"**/src/features/chat/domain/**/*.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-lower-level-feature-imports.grit",
|
||||
"includes": ["**/src/app-server/**/*.ts", "**/src/app-server/**/*.tsx", "**/src/shared/**/*.ts", "**/src/shared/**/*.tsx"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-app-server-connection-boundary-imports.grit",
|
||||
"includes": [
|
||||
"**/src/app-server/protocol/**/*.ts",
|
||||
"**/src/app-server/protocol/**/*.tsx",
|
||||
"**/src/domain/**/*.ts",
|
||||
"**/src/domain/**/*.tsx",
|
||||
"**/src/shared/**/*.ts",
|
||||
|
|
@ -34,10 +47,6 @@
|
|||
},
|
||||
|
||||
// Chat feature architecture.
|
||||
{
|
||||
"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"]
|
||||
|
|
|
|||
32
scripts/lint/no-app-server-connection-boundary-imports.grit
Normal file
32
scripts/lint/no-app-server-connection-boundary-imports.grit
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
language js
|
||||
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
private pattern app_server_protocol_connection_source() { r"^[\"'](?:\.\./)+connection(?:/.*)?[\"']$" }
|
||||
|
||||
private pattern root_app_server_connection_source() {
|
||||
or {
|
||||
r"^[\"'](?:\.\./)+app-server/connection(?:/.*)?[\"']$",
|
||||
r"^[\"']src/app-server/connection(?:/.*)?[\"']$"
|
||||
}
|
||||
}
|
||||
|
||||
private pattern app_server_connection_boundary_source() {
|
||||
or {
|
||||
app_server_protocol_connection_source(),
|
||||
root_app_server_connection_source()
|
||||
}
|
||||
}
|
||||
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: app_server_connection_boundary_source() },
|
||||
register_diagnostic(span=$stmt, message="App-server protocol, domain, and shared modules must not import app-server connection internals. Keep connection usage at app-server adapters.", severity="error")
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
language js
|
||||
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
js_module_reference() as $stmt where {
|
||||
$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")
|
||||
}
|
||||
33
scripts/lint/no-domain-outer-layer-imports.grit
Normal file
33
scripts/lint/no-domain-outer-layer-imports.grit
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
language js
|
||||
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
private pattern root_outer_layer_source() {
|
||||
or {
|
||||
r"^[\"'](?:\.\./)+(?:app-server|settings|workspace|shared/ui|features)(?:/.*)?[\"']$",
|
||||
r"^[\"']src/(?:app-server|settings|workspace|shared/ui|features)(?:/.*)?[\"']$",
|
||||
r"^[\"']obsidian[\"']$"
|
||||
}
|
||||
}
|
||||
|
||||
private pattern chat_domain_outer_layer_source() { r"^[\"'](?:\.\./)+(?:application|host|panel|presentation|ui)(?:/.*)?[\"']$" }
|
||||
|
||||
private pattern domain_outer_layer_source() {
|
||||
or {
|
||||
root_outer_layer_source(),
|
||||
chat_domain_outer_layer_source()
|
||||
}
|
||||
}
|
||||
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: domain_outer_layer_source() },
|
||||
register_diagnostic(span=$stmt, message="Domain modules must stay pure; outer layers may depend on domain, not the reverse.", severity="error")
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
language js
|
||||
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
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 {
|
||||
$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")
|
||||
},
|
||||
js_module_reference() as $stmt where {
|
||||
$filename <: r".*/src/(?:app-server/protocol|domain|shared)/.*",
|
||||
$stmt <: contains `$source` where {
|
||||
$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")
|
||||
}
|
||||
}
|
||||
16
scripts/lint/no-lower-level-feature-imports.grit
Normal file
16
scripts/lint/no-lower-level-feature-imports.grit
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
language js
|
||||
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where { $source <: r"^[\"'](?:(?:\.\./)+features|src/features)(?:/.*)?[\"']$" },
|
||||
register_diagnostic(span=$stmt, message="Lower-level modules must not import feature modules. Move shared behavior to shared, domain, or app-server adapters.", severity="error")
|
||||
}
|
||||
|
|
@ -134,7 +134,6 @@ runner = new Runner(() => runner.stop());
|
|||
|
||||
it("keeps chat architecture policies behind their intended boundaries", async () => {
|
||||
const cwd = await tempBiomeWorkspace([
|
||||
"no-chat-domain-outer-layer-imports.grit",
|
||||
"no-chat-signal-imports.grit",
|
||||
"no-implicit-dom-bridges.grit",
|
||||
"no-pure-chat-state-side-effects.grit",
|
||||
|
|
@ -168,38 +167,6 @@ export async function loadSignals() {
|
|||
|
||||
const signals = await import("@preact/signals");
|
||||
export const loadedSignals = signals;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/domain/message-stream/selectors.ts"),
|
||||
`
|
||||
import type { ChatStateStore } from "../../application/state/store";
|
||||
import type { Presenter } from 'src/features/chat/presentation/view';
|
||||
|
||||
export type Store = ChatStateStore;
|
||||
export type View = Presenter;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/domain/message-stream/items.ts"),
|
||||
`
|
||||
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 = await import("../../host/session");
|
||||
export const outerHost = host;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
|
|
@ -282,9 +249,6 @@ export function timestamp(): number {
|
|||
"src/features/chat/panel/shell-state.tsx",
|
||||
"src/shared/ui/components.tsx",
|
||||
"src/shared/ui/signal-escapes.tsx",
|
||||
"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",
|
||||
"src/features/chat/ui/dom-bridge-escape.tsx",
|
||||
"src/features/chat/ui/dom-bridge.dom.tsx",
|
||||
"src/app-server/services/abortable-operation.ts",
|
||||
|
|
@ -307,17 +271,6 @@ export function timestamp(): number {
|
|||
"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.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/features/chat/domain/message-stream/selectors.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.",
|
||||
]);
|
||||
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.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/features/chat/ui/dom-bridge-escape.tsx")).toEqual([
|
||||
"Keep imperative DOM writes and event wiring in files named with a .dom, .obsidian, or .measure suffix.",
|
||||
"Keep imperative DOM writes and event wiring in files named with a .dom, .obsidian, or .measure suffix.",
|
||||
|
|
@ -532,40 +485,18 @@ export const response = [appServerUserInputResponse, runtimeMetrics] satisfies u
|
|||
]);
|
||||
});
|
||||
|
||||
it("keeps lower-level source independent from connection and feature layers", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-lower-level-boundary-imports.grit"]);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/app-server/protocol/catalog.ts"),
|
||||
`
|
||||
import type { AppServerClient } from "../connection/client";
|
||||
|
||||
export type Client = AppServerClient;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/app-server/protocol/diagnostics.ts"),
|
||||
`
|
||||
import type { ThreadPickerModal } from '../../features/thread-picker/modal';
|
||||
|
||||
export type Modal = ThreadPickerModal;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/shared/date.ts"),
|
||||
`
|
||||
import { formatDate } from "./format";
|
||||
|
||||
export const format = formatDate;
|
||||
`.trimStart(),
|
||||
);
|
||||
it("keeps domain modules independent from outer layers", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-domain-outer-layer-imports.grit"]);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/domain/threads/model.ts"),
|
||||
`
|
||||
import { listThreads } from "../../app-server/threads";
|
||||
import type { ThreadPickerModal } from "../../features/thread-picker/modal";
|
||||
import { copyText } from "../../shared/ui/clipboard";
|
||||
import type { App } from "obsidian";
|
||||
|
||||
export type Host = App;
|
||||
export type Modal = ThreadPickerModal;
|
||||
export const list = listThreads;
|
||||
export const copy = copyText;
|
||||
`.trimStart(),
|
||||
|
|
@ -576,33 +507,165 @@ export const copy = copyText;
|
|||
import { formatDate } from "../../shared/date";
|
||||
|
||||
export const format = formatDate;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/domain/message-stream/selectors.ts"),
|
||||
`
|
||||
import type { ChatStateStore } from "../../application/state/store";
|
||||
import type { Presenter } from 'src/features/chat/presentation/view';
|
||||
|
||||
export type Store = ChatStateStore;
|
||||
export type View = Presenter;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/domain/message-stream/items.ts"),
|
||||
`
|
||||
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 = await import("../../host/session");
|
||||
export const outerHost = host;
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(
|
||||
[
|
||||
"src/domain/threads/model.ts",
|
||||
"src/domain/threads/format.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,
|
||||
);
|
||||
|
||||
expect(pluginMessages(report, "src/domain/threads/model.ts")).toEqual([
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/domain/threads/format.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/features/chat/domain/message-stream/selectors.ts")).toEqual([
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
"Domain modules must stay pure; outer 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([
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
"Domain modules must stay pure; outer layers may depend on domain, not the reverse.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps lower-level modules independent from feature modules", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-lower-level-feature-imports.grit"]);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/app-server/protocol/diagnostics.ts"),
|
||||
`
|
||||
import type { ThreadPickerModal } from '../../features/thread-picker/modal';
|
||||
|
||||
export type Modal = ThreadPickerModal;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/shared/thread-picker.ts"),
|
||||
`
|
||||
import type { ThreadPickerModal } from "../features/thread-picker/modal";
|
||||
|
||||
export type Modal = ThreadPickerModal;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/shared/date.ts"),
|
||||
`
|
||||
import { formatDate } from "./format";
|
||||
|
||||
export const format = formatDate;
|
||||
`.trimStart(),
|
||||
);
|
||||
|
||||
const report = biomeLint(["src/app-server/protocol/diagnostics.ts", "src/shared/thread-picker.ts", "src/shared/date.ts"], cwd);
|
||||
|
||||
expect(pluginMessages(report, "src/app-server/protocol/diagnostics.ts")).toEqual([
|
||||
"Lower-level modules must not import feature modules. Move shared behavior to shared, domain, or app-server adapters.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/shared/thread-picker.ts")).toEqual([
|
||||
"Lower-level modules must not import feature modules. Move shared behavior to shared, domain, or app-server adapters.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/shared/date.ts")).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps app-server connection internals behind app-server adapters", async () => {
|
||||
const cwd = await tempBiomeWorkspace(["no-app-server-connection-boundary-imports.grit"]);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/app-server/protocol/catalog.ts"),
|
||||
`
|
||||
import type { AppServerClient } from "../connection/client";
|
||||
|
||||
export type Client = AppServerClient;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/app-server/services/catalog.ts"),
|
||||
`
|
||||
import type { AppServerClient } from "../connection/client";
|
||||
|
||||
export type Client = AppServerClient;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/domain/connection-client.ts"),
|
||||
`
|
||||
import type { AppServerClient } from "../app-server/connection/client";
|
||||
|
||||
export type Client = AppServerClient;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/shared/connection-client.ts"),
|
||||
`
|
||||
import type { AppServerClient } from "src/app-server/connection/client";
|
||||
|
||||
export type Client = AppServerClient;
|
||||
`.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",
|
||||
"src/app-server/services/catalog.ts",
|
||||
"src/domain/connection-client.ts",
|
||||
"src/shared/connection-client.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.",
|
||||
"App-server protocol, domain, and shared modules must not import app-server connection internals. Keep connection usage at app-server adapters.",
|
||||
]);
|
||||
expect(pluginMessages(report, "src/app-server/protocol/diagnostics.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.",
|
||||
expect(pluginDiagnostics(report, "src/app-server/services/catalog.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/domain/connection-client.ts")).toEqual([
|
||||
"App-server protocol, domain, and shared modules must not import app-server connection internals. Keep connection usage at 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(pluginMessages(report, "src/shared/connection-client.ts")).toEqual([
|
||||
"App-server protocol, domain, and shared modules must not import app-server connection internals. Keep connection usage at app-server adapters.",
|
||||
]);
|
||||
expect(pluginDiagnostics(report, "src/domain/threads/format.ts")).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps chat application app-server projection RPCs behind facades", async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue